├── .eslintignore ├── .github └── workflows │ └── tests.yml ├── .gitignore ├── .nowignore ├── .npmignore ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── demo.png ├── dist ├── paintable.common.js ├── paintable.umd.js └── paintable.umd.min.js ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── index.html └── manifest.json ├── src ├── App.vue ├── Paintable.vue ├── components │ └── Navigation.vue ├── main.js └── plugin.js ├── tests └── unit │ ├── .eslintrc.js │ └── example.spec.js ├── vercel.json └── vue.config.js /.eslintignore: -------------------------------------------------------------------------------- 1 | dist/ -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | strategy: 11 | matrix: 12 | node-version: [10.x, 12.x] 13 | 14 | steps: 15 | - uses: actions/checkout@v1 16 | - name: Use Node.js ${{ matrix.node-version }} 17 | uses: actions/setup-node@v1 18 | with: 19 | node-version: ${{ matrix.node-version }} 20 | - name: npm install, lint, build and test 21 | run: | 22 | npm install 23 | npm test 24 | npm run lint 25 | npm run now-build 26 | env: 27 | CI: true 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | 4 | # local env files 5 | .env.local 6 | .env.*.local 7 | 8 | # Log files 9 | npm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | 13 | # Editor directories and files 14 | .idea 15 | .vscode 16 | *.suo 17 | *.ntvs* 18 | *.njsproj 19 | *.sln 20 | *.sw? 21 | 22 | demo -------------------------------------------------------------------------------- /.nowignore: -------------------------------------------------------------------------------- 1 | demo.png 2 | .vscode 3 | tests 4 | public 5 | dist 6 | demo 7 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src/ 2 | webpack.config.js 3 | travis.yml 4 | .vscode 5 | .prettierrc 6 | demo/ 7 | coverage/ 8 | .git/ 9 | scripts/ 10 | demo.png -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "travis.repository": "vue-paintable", 3 | "travis.username": "ph1p" 4 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 appcom interactive GmbH 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-paintable [![npm](https://badge.fury.io/js/vue-paintable.svg)](https://www.npmjs.com/package/vue-paintable) [![](https://img.shields.io/badge/vercel-demo-black.svg)](https://vue-paintable.vercel.app/) 2 | 3 | With this vue plugin and component you can add a paintable canvas through your page. 4 | All paintings are saved by default into localStorage. 5 | 6 | 7 |

8 | 9 | 10 | 11 |

12 | 13 | ### How to use? 14 | 15 | ```bash 16 | npm install vue-paintable 17 | ``` 18 | 19 | or 20 | 21 | ```bash 22 | yarn add vue-paintable 23 | ``` 24 | 25 | Inside your main (typically main.js) 26 | 27 | ```javascript 28 | import Vue from 'vue'; 29 | import Paintable from 'vue-paintable'; 30 | 31 | Vue.use(Paintable, { 32 | // optional methods 33 | setItem(key, image) { 34 | localStorage.setItem(key, image); 35 | }, 36 | // you also can use async 37 | getItem(key) { 38 | return localStorage.getItem(key); 39 | }, 40 | removeItem(key) { 41 | localStorage.removeItem(key); 42 | } 43 | }); 44 | //... 45 | ``` 46 | 47 | #### Inside your components 48 | 49 | ```html 50 | 74 | ``` 75 | 76 | ### Navigation 77 | 78 | Set your own navigation content by adding an object to your `` component. 79 | 80 | ```javascript 81 | { 82 | 'draw-save': { 83 | body: 'draw', 84 | activeBody: 'save' 85 | }, 86 | color: { 87 | body: 'CP' 88 | } 89 | } 90 | ``` 91 | 92 | **Display navigation horizontal** 93 | 94 | To display the navigation horizontally add `horizontalNavigation` to prop list. 95 | 96 | **Available navigation items:** 97 | 98 | - color 99 | - line-width 100 | - undo 101 | - redo 102 | - delete 103 | - cancel 104 | 105 | **has active state (activeBody):** 106 | 107 | - draw-save 108 | - eraser-pencil 109 | 110 | ### Custom Navigation 111 | 112 | To use a custom navigation disable the default navigation with `disableNavigation`. 113 | 114 | #### use \$refs to call paintable methods 115 | 116 | ```html 117 | content 118 | 119 | 120 | 121 | 122 | 125 | 126 | ``` 127 | 128 | Take a look at the [demo](https://vue-paintable.vercel.app/) (`/src/App.vue`) 129 | 130 | 131 | ### Props 132 | 133 | | name | type | required | default | description | 134 | | -------------------- | ------------------------------- | -------- | ------------------------------------------------------------------------ | ---------------------------------------------------------------| 135 | | name | string - required | true | - | unique identifier | 136 | | showUndoRedo | boolean | false | true | show undo and redo button | 137 | | hide | boolean | false | false | hide the complete paintable | 138 | | colors | Array of colors (rgb, hex etc.) | false | ['black', '#f00', '#4481c7', 'rgba(255, 235, 59, 0.4)', '#999', 'green'] | array of choosable colors | 139 | | width | number | false | window.innerWidth | canvas width | 140 | | height | number | false | window.innerHeight | canvas height | 141 | | showLineWidth | boolean | false | true | show button to set line width | 142 | | lineWidth | number | false | 5 | line width | 143 | | alwaysOnTop | boolean | false | true | set canvas always as top layer | 144 | | factor | number | false | 1 | set a scale factor if needed | 145 | | lineWidthEraser | number | false | 20 | set eraser line width | 146 | | horizontalNavigation | boolean | false | true | display the navigation horizontally or vertically | 147 | | disableNavigation | boolean | false | false | hide navigation | 148 | | active | boolean | false | false | set paintable active/inactive | 149 | | color | string | false | #000 | current color | 150 | | useEraser | boolean | false | false | set to true, to use the eraser | 151 | | threshold | number | false | 0 | set the threshold on which an event gets triggered (see events)| 152 | 153 | ### Events 154 | 155 | | name | type | description | 156 | | ---------------- | ------- | ---------------------------------------------------- | 157 | | toggle-paintable | boolean | Is emitted, when changing paintable state | 158 | | thresholdReached | boolean | Is emitted, when the speciefied threshold is reached | 159 | 160 | ```html 161 | 162 | 163 | 164 | ``` 165 | 166 | ### development 167 | 168 | If you want to develop with this plugin, follow these steps: 169 | 170 | - clone repo 171 | - run `yarn install` or `npm install` 172 | - run `yarn serve` or `npm run serve` 173 | 174 | ### build 175 | 176 | You can find all built files inside the `dist` folder. 177 | 178 | - run `yarn build` or `npm run build` 179 | -------------------------------------------------------------------------------- /demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ph1p/vue-paintable/15514940151106055fee5325ac5936f1d9222d7a/demo.png -------------------------------------------------------------------------------- /dist/paintable.common.js: -------------------------------------------------------------------------------- 1 | module.exports = 2 | /******/ (function(modules) { // webpackBootstrap 3 | /******/ // The module cache 4 | /******/ var installedModules = {}; 5 | /******/ 6 | /******/ // The require function 7 | /******/ function __webpack_require__(moduleId) { 8 | /******/ 9 | /******/ // Check if module is in cache 10 | /******/ if(installedModules[moduleId]) { 11 | /******/ return installedModules[moduleId].exports; 12 | /******/ } 13 | /******/ // Create a new module (and put it into the cache) 14 | /******/ var module = installedModules[moduleId] = { 15 | /******/ i: moduleId, 16 | /******/ l: false, 17 | /******/ exports: {} 18 | /******/ }; 19 | /******/ 20 | /******/ // Execute the module function 21 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 22 | /******/ 23 | /******/ // Flag the module as loaded 24 | /******/ module.l = true; 25 | /******/ 26 | /******/ // Return the exports of the module 27 | /******/ return module.exports; 28 | /******/ } 29 | /******/ 30 | /******/ 31 | /******/ // expose the modules object (__webpack_modules__) 32 | /******/ __webpack_require__.m = modules; 33 | /******/ 34 | /******/ // expose the module cache 35 | /******/ __webpack_require__.c = installedModules; 36 | /******/ 37 | /******/ // define getter function for harmony exports 38 | /******/ __webpack_require__.d = function(exports, name, getter) { 39 | /******/ if(!__webpack_require__.o(exports, name)) { 40 | /******/ Object.defineProperty(exports, name, { enumerable: true, get: getter }); 41 | /******/ } 42 | /******/ }; 43 | /******/ 44 | /******/ // define __esModule on exports 45 | /******/ __webpack_require__.r = function(exports) { 46 | /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { 47 | /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); 48 | /******/ } 49 | /******/ Object.defineProperty(exports, '__esModule', { value: true }); 50 | /******/ }; 51 | /******/ 52 | /******/ // create a fake namespace object 53 | /******/ // mode & 1: value is a module id, require it 54 | /******/ // mode & 2: merge all properties of value into the ns 55 | /******/ // mode & 4: return value when already ns object 56 | /******/ // mode & 8|1: behave like require 57 | /******/ __webpack_require__.t = function(value, mode) { 58 | /******/ if(mode & 1) value = __webpack_require__(value); 59 | /******/ if(mode & 8) return value; 60 | /******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value; 61 | /******/ var ns = Object.create(null); 62 | /******/ __webpack_require__.r(ns); 63 | /******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value }); 64 | /******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key)); 65 | /******/ return ns; 66 | /******/ }; 67 | /******/ 68 | /******/ // getDefaultExport function for compatibility with non-harmony modules 69 | /******/ __webpack_require__.n = function(module) { 70 | /******/ var getter = module && module.__esModule ? 71 | /******/ function getDefault() { return module['default']; } : 72 | /******/ function getModuleExports() { return module; }; 73 | /******/ __webpack_require__.d(getter, 'a', getter); 74 | /******/ return getter; 75 | /******/ }; 76 | /******/ 77 | /******/ // Object.prototype.hasOwnProperty.call 78 | /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; 79 | /******/ 80 | /******/ // __webpack_public_path__ 81 | /******/ __webpack_require__.p = ""; 82 | /******/ 83 | /******/ 84 | /******/ // Load entry module and return exports 85 | /******/ return __webpack_require__(__webpack_require__.s = "fb15"); 86 | /******/ }) 87 | /************************************************************************/ 88 | /******/ ({ 89 | 90 | /***/ "2443": 91 | /***/ (function(module, exports, __webpack_require__) { 92 | 93 | // style-loader: Adds some css to the DOM by adding a 183 | -------------------------------------------------------------------------------- /src/Paintable.vue: -------------------------------------------------------------------------------- 1 | 45 | 46 | 534 | 535 | 566 | -------------------------------------------------------------------------------- /src/components/Navigation.vue: -------------------------------------------------------------------------------- 1 | 70 | 71 | 234 | 235 | 318 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import App from './App.vue'; 3 | import Paintable from './plugin'; 4 | 5 | Vue.use(Paintable); 6 | 7 | new Vue({ 8 | render: h => h(App) 9 | }).$mount('#app'); 10 | -------------------------------------------------------------------------------- /src/plugin.js: -------------------------------------------------------------------------------- 1 | import Paintable from './Paintable.vue'; 2 | 3 | const PaintablePlugin = { 4 | install(Vue, options) { 5 | if (options && options.setItem) { 6 | Paintable.methods.setItem = options.setItem; 7 | } 8 | 9 | if (options && options.getItem) { 10 | Paintable.methods.getItem = options.getItem; 11 | } 12 | 13 | if (options && options.removeItem) { 14 | Paintable.methods.removeItem = options.removeItem; 15 | } 16 | 17 | Vue.component('paintable', Paintable); 18 | } 19 | }; 20 | 21 | export default PaintablePlugin; 22 | -------------------------------------------------------------------------------- /tests/unit/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | jest: true 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /tests/unit/example.spec.js: -------------------------------------------------------------------------------- 1 | describe('', () => { 2 | it('', () => {}); 3 | }); 4 | -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "name": "vue-paintable", 4 | "alias": ["vue-paintable.vercel.app"], 5 | "builds": [ 6 | { 7 | "src": "./package.json", 8 | "use": "@vercel/static-build", 9 | "config": { 10 | "distDir": "demo" 11 | } 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | productionSourceMap: false, 3 | css: { 4 | extract: false 5 | } 6 | }; 7 | --------------------------------------------------------------------------------