├── .classpath ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── dependabot.yml └── workflows │ └── codeql-analysis.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── assembly ├── MANIFEST.MF └── assembly.xml ├── package-lock.json ├── package.json ├── pom.xml ├── src ├── main │ ├── java │ │ └── de │ │ │ └── f0rce │ │ │ └── signaturepad │ │ │ ├── ImageEncode.java │ │ │ └── SignaturePad.java │ └── resources │ │ └── META-INF │ │ └── resources │ │ └── frontend │ │ └── @f0rce │ │ └── signature-widget.js └── test │ └── java │ └── de │ └── f0rce │ └── signaturepad │ ├── AbstractViewTest.java │ ├── View.java │ └── ViewIT.java ├── webpack.config.js └── webpack.generated.js /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: f0rce 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "npm" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "daily" 12 | 13 | - package-ecosystem: "maven" # See documentation for possible values 14 | directory: "/" # Location of package manifests 15 | schedule: 16 | interval: "daily" 17 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ master ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ master ] 20 | schedule: 21 | - cron: '20 21 * * 1' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | 28 | strategy: 29 | fail-fast: false 30 | matrix: 31 | language: [ 'java', 'javascript' ] 32 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 33 | # Learn more: 34 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed 35 | 36 | steps: 37 | - name: Checkout repository 38 | uses: actions/checkout@v2 39 | 40 | # Initializes the CodeQL tools for scanning. 41 | - name: Initialize CodeQL 42 | uses: github/codeql-action/init@v1 43 | with: 44 | languages: ${{ matrix.language }} 45 | # If you wish to specify custom queries, you can do so here or in a config file. 46 | # By default, queries listed here will override any specified in a config file. 47 | # Prefix the list here with "+" to use these queries and those in the config file. 48 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 49 | 50 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 51 | # If this step fails, then you should remove it and run the build manually (see below) 52 | - name: Autobuild 53 | uses: github/codeql-action/autobuild@v1 54 | 55 | # ℹ️ Command-line programs to run using the OS shell. 56 | # 📚 https://git.io/JvXDl 57 | 58 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 59 | # and modify them (or add more) to build your code if your project 60 | # uses a compiled language 61 | 62 | #- run: | 63 | # make bootstrap 64 | # make release 65 | 66 | - name: Perform CodeQL Analysis 67 | uses: github/codeql-action/analyze@v1 68 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Eclipse ### 2 | .metadata 3 | bin/ 4 | tmp/ 5 | *.tmp 6 | *.bak 7 | *.swp 8 | *~.nib 9 | local.properties 10 | .settings/ 11 | .loadpath 12 | .recommenders 13 | 14 | # External tool builders 15 | .externalToolBuilders/ 16 | 17 | # Locally stored "Eclipse launch configurations" 18 | *.launch 19 | 20 | # PyDev specific (Python IDE for Eclipse) 21 | *.pydevproject 22 | 23 | # CDT-specific (C/C++ Development Tooling) 24 | .cproject 25 | 26 | # CDT- autotools 27 | .autotools 28 | 29 | # Java annotation processor (APT) 30 | .factorypath 31 | 32 | # PDT-specific (PHP Development Tools) 33 | .buildpath 34 | 35 | # sbteclipse plugin 36 | .target 37 | 38 | # Tern plugin 39 | .tern-project 40 | 41 | # TeXlipse plugin 42 | .texlipse 43 | 44 | # STS (Spring Tool Suite) 45 | .springBeans 46 | 47 | # Code Recommenders 48 | .recommenders/ 49 | 50 | # Annotation Processing 51 | .apt_generated/ 52 | .apt_generated_test/ 53 | 54 | # Scala IDE specific (Scala & Java development for Eclipse) 55 | .cache-main 56 | .scala_dependencies 57 | .worksheet 58 | 59 | # Uncomment this line if you wish to ignore the project description file. 60 | # Typically, this file would be tracked if it contains build/dependency configurations: 61 | #.project 62 | 63 | ### Eclipse Patch ### 64 | # Spring Boot Tooling 65 | .sts4-cache/ 66 | 67 | ### Java ### 68 | # Compiled class file 69 | *.class 70 | 71 | # Log file 72 | *.log 73 | 74 | # BlueJ files 75 | *.ctxt 76 | 77 | # Mobile Tools for Java (J2ME) 78 | .mtj.tmp/ 79 | 80 | # Package Files # 81 | *.jar 82 | *.war 83 | *.nar 84 | *.ear 85 | *.zip 86 | *.tar.gz 87 | *.rar 88 | 89 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 90 | hs_err_pid* 91 | replay_pid* 92 | 93 | ### Maven ### 94 | target/ 95 | pom.xml.tag 96 | pom.xml.releaseBackup 97 | pom.xml.versionsBackup 98 | pom.xml.next 99 | release.properties 100 | dependency-reduced-pom.xml 101 | buildNumber.properties 102 | .mvn/timing.properties 103 | # https://github.com/takari/maven-wrapper#usage-without-binary-jar 104 | .mvn/wrapper/maven-wrapper.jar 105 | 106 | # Eclipse m2e generated files 107 | # Eclipse Core 108 | .project 109 | # JDT-specific (Eclipse Java Development Tools) 110 | .classpath 111 | 112 | ### Node ### 113 | # Logs 114 | logs 115 | npm-debug.log* 116 | yarn-debug.log* 117 | yarn-error.log* 118 | lerna-debug.log* 119 | .pnpm-debug.log* 120 | 121 | # Diagnostic reports (https://nodejs.org/api/report.html) 122 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 123 | 124 | # Runtime data 125 | pids 126 | *.pid 127 | *.seed 128 | *.pid.lock 129 | 130 | # Directory for instrumented libs generated by jscoverage/JSCover 131 | lib-cov 132 | 133 | # Coverage directory used by tools like istanbul 134 | coverage 135 | *.lcov 136 | 137 | # nyc test coverage 138 | .nyc_output 139 | 140 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 141 | .grunt 142 | 143 | # Bower dependency directory (https://bower.io/) 144 | bower_components 145 | 146 | # node-waf configuration 147 | .lock-wscript 148 | 149 | # Compiled binary addons (https://nodejs.org/api/addons.html) 150 | build/Release 151 | 152 | # Dependency directories 153 | node_modules/ 154 | jspm_packages/ 155 | 156 | # Snowpack dependency directory (https://snowpack.dev/) 157 | web_modules/ 158 | 159 | # TypeScript cache 160 | *.tsbuildinfo 161 | 162 | # Optional npm cache directory 163 | .npm 164 | 165 | # Optional eslint cache 166 | .eslintcache 167 | 168 | # Optional stylelint cache 169 | .stylelintcache 170 | 171 | # Microbundle cache 172 | .rpt2_cache/ 173 | .rts2_cache_cjs/ 174 | .rts2_cache_es/ 175 | .rts2_cache_umd/ 176 | 177 | # Optional REPL history 178 | .node_repl_history 179 | 180 | # Output of 'npm pack' 181 | *.tgz 182 | 183 | # Yarn Integrity file 184 | .yarn-integrity 185 | 186 | # dotenv environment variable files 187 | .env 188 | .env.development.local 189 | .env.test.local 190 | .env.production.local 191 | .env.local 192 | 193 | # parcel-bundler cache (https://parceljs.org/) 194 | .cache 195 | .parcel-cache 196 | 197 | # Next.js build output 198 | .next 199 | out 200 | 201 | # Nuxt.js build / generate output 202 | .nuxt 203 | dist 204 | 205 | # Gatsby files 206 | .cache/ 207 | # Comment in the public line in if your project uses Gatsby and not Next.js 208 | # https://nextjs.org/blog/next-9-1#public-directory-support 209 | # public 210 | 211 | # vuepress build output 212 | .vuepress/dist 213 | 214 | # vuepress v2.x temp and cache directory 215 | .temp 216 | 217 | # Docusaurus cache and generated files 218 | .docusaurus 219 | 220 | # Serverless directories 221 | .serverless/ 222 | 223 | # FuseBox cache 224 | .fusebox/ 225 | 226 | # DynamoDB Local files 227 | .dynamodb/ 228 | 229 | # TernJS port file 230 | .tern-port 231 | 232 | # Stores VSCode versions used for testing VSCode extensions 233 | .vscode-test 234 | 235 | # yarn v2 236 | .yarn/cache 237 | .yarn/unplugged 238 | .yarn/build-state.yml 239 | .yarn/install-state.gz 240 | .pnp.* 241 | 242 | ### Node Patch ### 243 | # Serverless Webpack directories 244 | .webpack/ 245 | 246 | # Optional stylelint cache 247 | 248 | # SvelteKit build / generate output 249 | .svelte-kit 250 | 251 | ### Vaadin ### 252 | .vaadin-designer 253 | .designer 254 | 255 | ### VisualStudioCode ### 256 | .vscode/* 257 | !.vscode/settings.json 258 | !.vscode/tasks.json 259 | !.vscode/launch.json 260 | !.vscode/extensions.json 261 | !.vscode/*.code-snippets 262 | 263 | # Local History for Visual Studio Code 264 | .history/ 265 | 266 | # Built Visual Studio Code Extensions 267 | *.vsix 268 | 269 | ### VisualStudioCode Patch ### 270 | # Ignore all local history of files 271 | .history 272 | .ionide 273 | 274 | ### docs.f0rce.de ### 275 | docs/ 276 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ## 2.0.2 (2022-04-13) 6 | 7 | 8 | ### Bug Fixes 9 | 10 | * **signature_pad:** removing unused properties ([5a41163](https://github.com/F0rce/signature-widget/commit/5a41163ace09625900943992184993462361760f)) 11 | * **signature_pad:** using EventEmitter (onEnd was deprecated) ([ab6d38d](https://github.com/F0rce/signature-widget/commit/ab6d38d38cbeac9e60bee8b569333907bcca96c6)), closes [#236](https://github.com/F0rce/signature-widget/issues/236) 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2021-2022 David "F0rce" Dodlek 2 | 3 | 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: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | 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. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Vaadin Directory](https://img.shields.io/vaadin-directory/status/signature-widget) 2 | ![Vaadin Directory](https://img.shields.io/vaadin-directory/rating/signature-widget) 3 | ![Vaadin Directory](https://img.shields.io/vaadin-directory/v/signature-widget) 4 | ![Vaadin Directory](https://img.shields.io/vaadin-directory/release-date/signature-widget) 5 | ![Website](https://img.shields.io/website?down_color=red&down_message=offline&label=documentation&up_color=limegreen&up_message=online&url=https%3A%2F%2Fdocs.f0rce.de%2Fsignature) 6 | [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2FF0rce%2Fsignature-widget.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2FF0rce%2Fsignature-widget?ref=badge_shield) 7 | 8 | # Signature Widget 9 | 10 | [Signature Pad](https://github.com/szimek/signature_pad) is a JavaScript library for drawing smooth signatures. Now you are able 11 | to create and use signatures in **your** Vaadin 14+ project with this custom add-on. 12 | 13 | ![Example](https://f.cloud.github.com/assets/9873/268046/9ced3454-8efc-11e2-816e-a9b170a51004.png) 14 | 15 | 16 | ## Install 17 | 18 | Install the component using [Vaadin Directory](https://vaadin.com/directory/component/signature-widget) 19 | 20 | 21 | ## Documentation 22 | 23 | [docs.f0rce.de/signature](https://docs.f0rce.de/signature) 24 | 25 | 26 | ## License 27 | 28 | [MIT License](https://github.com/F0rce/signature-widget/blob/master/LICENSE) 29 | 30 | 31 | [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2FF0rce%2Fsignature-widget.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2FF0rce%2Fsignature-widget?ref=badge_large) 32 | -------------------------------------------------------------------------------- /assembly/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Vaadin-Package-Version: 1 3 | Vaadin-Addon: ${project.build.finalName}.${project.packaging} 4 | Implementation-Vendor: ${organization.name} 5 | Implementation-Title: ${project.name} 6 | Implementation-Version: ${project.version} 7 | -------------------------------------------------------------------------------- /assembly/assembly.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | game-card 7 | 8 | 9 | zip 10 | 11 | 12 | 13 | false 14 | 15 | 16 | 17 | .. 18 | 19 | LICENSE 20 | README.md 21 | 22 | 23 | 24 | target 25 | 26 | 27 | *.jar 28 | *.pdf 29 | 30 | 31 | 32 | 33 | 34 | 36 | 37 | assembly/MANIFEST.MF 38 | META-INF 39 | true 40 | 41 | 42 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "no-name", 3 | "license": "UNLICENSED", 4 | "dependencies": { 5 | "@polymer/iron-a11y-announcer": "3.0.2", 6 | "@polymer/iron-a11y-keys-behavior": "3.0.1", 7 | "@polymer/iron-fit-behavior": "3.0.2", 8 | "@polymer/iron-flex-layout": "3.0.1", 9 | "@polymer/iron-icon": "3.0.1", 10 | "@polymer/iron-iconset-svg": "3.0.1", 11 | "@polymer/iron-list": "3.1.0", 12 | "@polymer/iron-media-query": "3.0.1", 13 | "@polymer/iron-meta": "3.0.1", 14 | "@polymer/iron-overlay-behavior": "3.0.3", 15 | "@polymer/iron-resizable-behavior": "3.0.1", 16 | "@polymer/iron-scroll-target-behavior": "3.0.1", 17 | "@polymer/polymer": "3.2.0", 18 | "@vaadin/vaadin-accordion": "1.2.0", 19 | "@vaadin/vaadin-app-layout": "2.2.0", 20 | "@vaadin/vaadin-avatar": "1.0.4", 21 | "@vaadin/vaadin-board": "2.2.0", 22 | "@vaadin/vaadin-button": "2.4.0", 23 | "@vaadin/vaadin-charts": "6.3.3", 24 | "@vaadin/vaadin-checkbox": "2.5.1", 25 | "@vaadin/vaadin-combo-box": "5.4.12", 26 | "@vaadin/vaadin-confirm-dialog": "1.3.0", 27 | "@vaadin/vaadin-context-menu": "4.6.0", 28 | "@vaadin/vaadin-control-state-mixin": "2.2.6", 29 | "@vaadin/vaadin-cookie-consent": "1.2.0", 30 | "@vaadin/vaadin-core-shrinkwrap": "14.8.8", 31 | "@vaadin/vaadin-crud": "1.3.1", 32 | "@vaadin/vaadin-custom-field": "1.3.1", 33 | "@vaadin/vaadin-date-picker": "4.4.2", 34 | "@vaadin/vaadin-date-time-picker": "1.4.0", 35 | "@vaadin/vaadin-details": "1.2.1", 36 | "@vaadin/vaadin-development-mode-detector": "2.0.5", 37 | "@vaadin/vaadin-dialog": "2.6.0", 38 | "@vaadin/vaadin-element-mixin": "2.4.2", 39 | "@vaadin/vaadin-form-layout": "2.3.0", 40 | "@vaadin/vaadin-grid": "5.9.4", 41 | "@vaadin/vaadin-grid-pro": "2.3.0", 42 | "@vaadin/vaadin-icons": "4.3.2", 43 | "@vaadin/vaadin-item": "2.3.0", 44 | "@vaadin/vaadin-list-box": "1.4.0", 45 | "@vaadin/vaadin-list-mixin": "2.5.1", 46 | "@vaadin/vaadin-login": "1.2.0", 47 | "@vaadin/vaadin-lumo-styles": "1.6.1", 48 | "@vaadin/vaadin-material-styles": "1.3.2", 49 | "@vaadin/vaadin-menu-bar": "1.3.0", 50 | "@vaadin/vaadin-messages": "1.0.2", 51 | "@vaadin/vaadin-notification": "1.6.2", 52 | "@vaadin/vaadin-ordered-layout": "1.4.0", 53 | "@vaadin/vaadin-overlay": "3.5.1", 54 | "@vaadin/vaadin-progress-bar": "1.3.0", 55 | "@vaadin/vaadin-radio-button": "1.5.4", 56 | "@vaadin/vaadin-rich-text-editor": "1.3.1", 57 | "@vaadin/vaadin-select": "2.4.3", 58 | "@vaadin/vaadin-split-layout": "4.3.1", 59 | "@vaadin/vaadin-tabs": "3.2.0", 60 | "@vaadin/vaadin-text-field": "2.9.2", 61 | "@vaadin/vaadin-themable-mixin": "1.6.2", 62 | "@vaadin/vaadin-time-picker": "2.4.0", 63 | "@vaadin/vaadin-upload": "4.4.3", 64 | "@vaadin/vaadin-usage-statistics": "2.1.2", 65 | "@webcomponents/shadycss": "1.8.0", 66 | "@webcomponents/webcomponentsjs": "^2.2.10", 67 | "construct-style-sheets-polyfill": "3.0.4", 68 | "lit-element": "2.5.1", 69 | "lit-html": "1.4.1", 70 | "signature_pad": "4.0.4" 71 | }, 72 | "devDependencies": { 73 | "babel-loader": "8.2.2", 74 | "chokidar": "^3.5.0", 75 | "compression-webpack-plugin": "4.0.1", 76 | "copy-webpack-plugin": "5.1.2", 77 | "css-loader": "4.2.1", 78 | "extra-watch-webpack-plugin": "1.0.3", 79 | "extract-loader": "5.1.0", 80 | "file-loader": "6.1.0", 81 | "html-webpack-plugin": "4.5.2", 82 | "lit-css-loader": "0.0.4", 83 | "raw-loader": "3.1.0", 84 | "ts-loader": "8.0.12", 85 | "typescript": "4.0.3", 86 | "webpack": "4.42.0", 87 | "webpack-babel-multi-target-plugin": "2.5.0", 88 | "webpack-cli": "3.3.11", 89 | "webpack-dev-server": "3.11.0", 90 | "webpack-merge": "4.2.2" 91 | }, 92 | "vaadin": { 93 | "dependencies": { 94 | "@polymer/iron-a11y-announcer": "3.0.2", 95 | "@polymer/iron-a11y-keys-behavior": "3.0.1", 96 | "@polymer/iron-fit-behavior": "3.0.2", 97 | "@polymer/iron-flex-layout": "3.0.1", 98 | "@polymer/iron-icon": "3.0.1", 99 | "@polymer/iron-iconset-svg": "3.0.1", 100 | "@polymer/iron-list": "3.1.0", 101 | "@polymer/iron-media-query": "3.0.1", 102 | "@polymer/iron-meta": "3.0.1", 103 | "@polymer/iron-overlay-behavior": "3.0.3", 104 | "@polymer/iron-resizable-behavior": "3.0.1", 105 | "@polymer/iron-scroll-target-behavior": "3.0.1", 106 | "@polymer/polymer": "3.2.0", 107 | "@vaadin/vaadin-accordion": "1.2.0", 108 | "@vaadin/vaadin-app-layout": "2.2.0", 109 | "@vaadin/vaadin-avatar": "1.0.4", 110 | "@vaadin/vaadin-board": "2.2.0", 111 | "@vaadin/vaadin-button": "2.4.0", 112 | "@vaadin/vaadin-charts": "6.3.3", 113 | "@vaadin/vaadin-checkbox": "2.5.1", 114 | "@vaadin/vaadin-combo-box": "5.4.12", 115 | "@vaadin/vaadin-confirm-dialog": "1.3.0", 116 | "@vaadin/vaadin-context-menu": "4.6.0", 117 | "@vaadin/vaadin-control-state-mixin": "2.2.6", 118 | "@vaadin/vaadin-cookie-consent": "1.2.0", 119 | "@vaadin/vaadin-core-shrinkwrap": "14.8.8", 120 | "@vaadin/vaadin-crud": "1.3.1", 121 | "@vaadin/vaadin-custom-field": "1.3.1", 122 | "@vaadin/vaadin-date-picker": "4.4.2", 123 | "@vaadin/vaadin-date-time-picker": "1.4.0", 124 | "@vaadin/vaadin-details": "1.2.1", 125 | "@vaadin/vaadin-development-mode-detector": "2.0.5", 126 | "@vaadin/vaadin-dialog": "2.6.0", 127 | "@vaadin/vaadin-element-mixin": "2.4.2", 128 | "@vaadin/vaadin-form-layout": "2.3.0", 129 | "@vaadin/vaadin-grid": "5.9.4", 130 | "@vaadin/vaadin-grid-pro": "2.3.0", 131 | "@vaadin/vaadin-icons": "4.3.2", 132 | "@vaadin/vaadin-item": "2.3.0", 133 | "@vaadin/vaadin-list-box": "1.4.0", 134 | "@vaadin/vaadin-list-mixin": "2.5.1", 135 | "@vaadin/vaadin-login": "1.2.0", 136 | "@vaadin/vaadin-lumo-styles": "1.6.1", 137 | "@vaadin/vaadin-material-styles": "1.3.2", 138 | "@vaadin/vaadin-menu-bar": "1.3.0", 139 | "@vaadin/vaadin-messages": "1.0.2", 140 | "@vaadin/vaadin-notification": "1.6.2", 141 | "@vaadin/vaadin-ordered-layout": "1.4.0", 142 | "@vaadin/vaadin-overlay": "3.5.1", 143 | "@vaadin/vaadin-progress-bar": "1.3.0", 144 | "@vaadin/vaadin-radio-button": "1.5.4", 145 | "@vaadin/vaadin-rich-text-editor": "1.3.1", 146 | "@vaadin/vaadin-select": "2.4.3", 147 | "@vaadin/vaadin-split-layout": "4.3.1", 148 | "@vaadin/vaadin-tabs": "3.2.0", 149 | "@vaadin/vaadin-text-field": "2.9.2", 150 | "@vaadin/vaadin-themable-mixin": "1.6.2", 151 | "@vaadin/vaadin-time-picker": "2.4.0", 152 | "@vaadin/vaadin-upload": "4.4.3", 153 | "@vaadin/vaadin-usage-statistics": "2.1.2", 154 | "@webcomponents/shadycss": "1.8.0", 155 | "@webcomponents/webcomponentsjs": "^2.2.10", 156 | "construct-style-sheets-polyfill": "3.0.4", 157 | "lit-element": "2.5.1", 158 | "lit-html": "1.4.1", 159 | "signature_pad": "4.0.4" 160 | }, 161 | "devDependencies": { 162 | "babel-loader": "8.2.2", 163 | "chokidar": "^3.5.0", 164 | "compression-webpack-plugin": "4.0.1", 165 | "copy-webpack-plugin": "5.1.2", 166 | "css-loader": "4.2.1", 167 | "extra-watch-webpack-plugin": "1.0.3", 168 | "extract-loader": "5.1.0", 169 | "file-loader": "6.1.0", 170 | "html-webpack-plugin": "4.5.2", 171 | "lit-css-loader": "0.0.4", 172 | "raw-loader": "3.1.0", 173 | "ts-loader": "8.0.12", 174 | "typescript": "4.0.3", 175 | "webpack": "4.42.0", 176 | "webpack-babel-multi-target-plugin": "2.5.0", 177 | "webpack-cli": "3.3.11", 178 | "webpack-dev-server": "3.11.0", 179 | "webpack-merge": "4.2.2" 180 | }, 181 | "hash": "e42fabda37ef1c852573ca8078ec75ec9ba0ef7355e98075c1b2ec0ffac5ab24" 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | de.f0rce.signaturepad 6 | signature-widget 7 | 8 | 2.0.2 9 | signature-widget 10 | Lit-Element Wrapper around Signature Pad (https://github.com/szimek/signature_pad) for Vaadin 14+ 11 | 12 | 13 | 14.8.8 14 | 15 | 1.8 16 | 1.8 17 | UTF-8 18 | UTF-8 19 | 20 | 9.4.28.v20200408 21 | 22 | 23 | David "F0rce" Dodlek 24 | 25 | 26 | 27 | MIT 28 | https://opensource.org/licenses/MIT 29 | repo 30 | 31 | 32 | 33 | 34 | 35 | com.vaadin 36 | vaadin-bom 37 | pom 38 | import 39 | ${vaadin.version} 40 | 41 | 42 | 43 | 44 | 45 | 46 | central 47 | https://repo.maven.apache.org/maven2 48 | 49 | false 50 | 51 | 52 | 53 | Vaadin Directory 54 | https://maven.vaadin.com/vaadin-addons 55 | 56 | 57 | 58 | Vaadin prereleases 59 | https://maven.vaadin.com/vaadin-prereleases 60 | 61 | 62 | 63 | vaadin-snapshots 64 | https://oss.sonatype.org/content/repositories/vaadin-snapshots/ 65 | 66 | 67 | 68 | 69 | 70 | central 71 | https://repo.maven.apache.org/maven2 72 | 73 | false 74 | 75 | 76 | 77 | 78 | Vaadin prereleases 79 | https://maven.vaadin.com/vaadin-prereleases 80 | 81 | 82 | vaadin-snapshots 83 | https://oss.sonatype.org/content/repositories/vaadin-snapshots/ 84 | 85 | false 86 | 87 | 88 | 89 | 90 | 91 | 92 | com.vaadin 93 | 94 | vaadin-core 95 | 96 | 100 | 101 | com.vaadin.webjar 102 | * 103 | 104 | 105 | org.webjars.bowergithub.insites 106 | * 107 | 108 | 109 | org.webjars.bowergithub.polymer 110 | * 111 | 112 | 113 | org.webjars.bowergithub.polymerelements 114 | * 115 | 116 | 117 | org.webjars.bowergithub.vaadin 118 | * 119 | 120 | 121 | org.webjars.bowergithub.webcomponents 122 | * 123 | 124 | 125 | 126 | 127 | org.slf4j 128 | slf4j-simple 129 | test 130 | 131 | 132 | com.vaadin 133 | vaadin-testbench 134 | test 135 | 136 | 137 | 138 | io.github.bonigarcia 139 | webdrivermanager 140 | 3.8.1 141 | test 142 | 143 | 144 | 145 | 146 | jetty:run 147 | 148 | 149 | org.apache.maven.plugins 150 | maven-jar-plugin 151 | 3.1.0 152 | 153 | 154 | true 155 | 156 | false 157 | true 158 | 159 | 160 | 1 161 | 162 | 163 | 164 | 165 | 166 | com.vaadin 167 | vaadin-maven-plugin 168 | ${vaadin.version} 169 | 170 | 171 | 172 | prepare-frontend 173 | 174 | 175 | 176 | 177 | 178 | org.eclipse.jetty 179 | jetty-maven-plugin 180 | ${jetty.version} 181 | 182 | 3 183 | 184 | true 185 | 186 | jar 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | directory 196 | 197 | 198 | 199 | org.apache.maven.plugins 200 | maven-assembly-plugin 201 | 3.1.0 202 | 203 | false 204 | 205 | assembly/assembly.xml 206 | 207 | 208 | 209 | 210 | 211 | single 212 | 213 | install 214 | 215 | 216 | 217 | 218 | org.apache.maven.plugins 219 | maven-source-plugin 220 | 3.0.1 221 | 222 | 223 | attach-sources 224 | verify 225 | 226 | jar-no-fork 227 | 228 | 229 | 230 | 231 | 232 | org.apache.maven.plugins 233 | maven-javadoc-plugin 234 | 3.0.1 235 | 236 | 237 | attach-javadocs 238 | verify 239 | 240 | jar 241 | 242 | 243 | 244 | 245 | true 246 | -Xdoclint:none 247 | 248 | 249 | 250 | org.apache.maven.plugins 251 | maven-jar-plugin 252 | 3.1.2 253 | 254 | 255 | 256 | META-INF/VAADIN/config/flow-build-info.json 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | production 267 | 268 | true 269 | 270 | 271 | 272 | com.vaadin 273 | flow-server-production-mode 274 | 275 | 276 | 277 | 278 | 279 | 280 | com.vaadin 281 | vaadin-maven-plugin 282 | 283 | 284 | 285 | build-frontend 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | it 296 | 297 | 298 | 299 | org.eclipse.jetty 300 | jetty-maven-plugin 301 | ${jetty.version} 302 | 303 | 0 304 | 305 | jar 306 | 307 | ${project.artifactId} 308 | 8081 309 | 310 | 311 | 312 | start-jetty 313 | pre-integration-test 314 | 315 | start 316 | 317 | 318 | 319 | stop-jetty 320 | post-integration-test 321 | 322 | stop 323 | 324 | 325 | 326 | 327 | 328 | org.apache.maven.plugins 329 | maven-failsafe-plugin 330 | 2.22.2 331 | 332 | 333 | 334 | integration-test 335 | verify 336 | 337 | 338 | 339 | 340 | false 341 | true 342 | 343 | 344 | 345 | maven-resources-plugin 346 | 3.1.0 347 | 348 | 349 | 351 | copy-test-to-classes 352 | process-test-classes 353 | 354 | copy-resources 355 | 356 | 357 | ${project.build.outputDirectory} 358 | 359 | 360 | ${project.build.testOutputDirectory} 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | -------------------------------------------------------------------------------- /src/main/java/de/f0rce/signaturepad/ImageEncode.java: -------------------------------------------------------------------------------- 1 | package de.f0rce.signaturepad; 2 | 3 | import com.vaadin.flow.component.ComponentEvent; 4 | import com.vaadin.flow.component.DomEvent; 5 | import com.vaadin.flow.component.EventData; 6 | 7 | /** 8 | * This class is used to listen to the image-encode event sent by the frontend. It contains the 9 | * image URI and the MIME-Type. 10 | * 11 | * @author David "F0rce" Dodlek 12 | */ 13 | @DomEvent("image-encode") 14 | public class ImageEncode extends ComponentEvent { 15 | 16 | private String image; 17 | private String type; 18 | private boolean isEmpty; 19 | 20 | public ImageEncode( 21 | SignaturePad source, 22 | boolean fromClient, 23 | @EventData("event.detail.image") String image, 24 | @EventData("event.detail.type") String type, 25 | @EventData("event.detail.isEmpty") Boolean isEmpty) { 26 | super(source, fromClient); 27 | this.image = image; 28 | this.type = type; 29 | this.isEmpty = isEmpty; 30 | } 31 | 32 | /** 33 | * Returns the dataUrl of the encoded image. 34 | * 35 | * @return {@link String} 36 | */ 37 | public String getImage() { 38 | return this.image; 39 | } 40 | 41 | /** 42 | * Returns the type, the image has been encoded with. 43 | * 44 | * @return {@link String} 45 | */ 46 | public String getType() { 47 | return this.type; 48 | } 49 | 50 | /** 51 | * Returns if the signature is empty. 52 | * 53 | * @return boolean 54 | */ 55 | public boolean isEmpty() { 56 | return this.isEmpty; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/de/f0rce/signaturepad/SignaturePad.java: -------------------------------------------------------------------------------- 1 | package de.f0rce.signaturepad; 2 | 3 | import java.nio.charset.StandardCharsets; 4 | import java.util.Base64; 5 | 6 | import com.vaadin.flow.component.Component; 7 | import com.vaadin.flow.component.HasSize; 8 | import com.vaadin.flow.component.Tag; 9 | import com.vaadin.flow.component.dependency.JsModule; 10 | import com.vaadin.flow.component.dependency.NpmPackage; 11 | 12 | /** @author David "F0rce" Dodlek */ 13 | @Tag("lit-signature-pad") 14 | @JsModule("./@f0rce/signature-widget.js") 15 | @NpmPackage(value = "signature_pad", version = "4.0.4") 16 | public class SignaturePad extends Component implements HasSize { 17 | 18 | private double dotSize; 19 | private double lineMinWidth = 0.5; 20 | private double lineMaxWidth = 2.5; 21 | private int throttle = 16; 22 | private int minDistance = 5; 23 | private String backgroundColor = "#ffffff"; 24 | private String penColor = "#000000"; 25 | private double velocityFilterWeight = 0.7; 26 | private String imageUri = ""; 27 | private String type = "image/png"; 28 | private boolean readOnly = false; 29 | private double encoderQuality = 0.85; 30 | private boolean isEmpty = true; 31 | 32 | /** Default constructor. By default height is set to 100px and width to 300px. */ 33 | public SignaturePad() { 34 | super.addListener(ImageEncode.class, this::updateImage); 35 | this.setHeight("100px"); 36 | this.setWidth("300px"); 37 | } 38 | 39 | // Used to sync the private variables (called after the frontend event has been 40 | // fired) 41 | private void updateImage(ImageEncode event) { 42 | this.imageUri = event.getImage(); 43 | this.type = event.getType(); 44 | this.isEmpty = event.isEmpty(); 45 | } 46 | 47 | /** Clears the widget. */ 48 | public void clear() { 49 | this.getElement().callJsFunction("clear"); 50 | } 51 | 52 | /** 53 | * Sets the radius of a single dot. 54 | * 55 | * @param dotSize int 56 | */ 57 | public void setDotSize(int dotSize) { 58 | double dotSizeD = Double.valueOf(dotSize); 59 | this.getElement().setProperty("dotSize", dotSizeD); 60 | this.dotSize = dotSizeD; 61 | } 62 | 63 | /** 64 | * Sets the radius of a single dot. 65 | * 66 | * @param dotSize double 67 | */ 68 | public void setDotSize(double dotSize) { 69 | this.getElement().setProperty("dotSize", dotSize); 70 | this.dotSize = dotSize; 71 | } 72 | 73 | /** 74 | * Returns the current set radius of a single dot. 75 | * 76 | * @return double 77 | */ 78 | public double getDotSize() { 79 | return this.dotSize; 80 | } 81 | 82 | /** 83 | * Sets the minimum width of a line. Defaults to 0.5. 84 | * 85 | * @param lineMinWidth int 86 | */ 87 | public void setLineMinWidth(int lineMinWidth) { 88 | double lineMinWidthD = Double.valueOf(lineMinWidth); 89 | this.getElement().setProperty("minWidth", lineMinWidthD); 90 | this.lineMinWidth = lineMinWidthD; 91 | } 92 | 93 | /** 94 | * Sets the minimum width of a line. Defaults to 0.5. 95 | * 96 | * @param lineMinWidth double 97 | */ 98 | public void setLineMinWidth(double lineMinWidth) { 99 | this.getElement().setProperty("minWidth", lineMinWidth); 100 | this.lineMinWidth = lineMinWidth; 101 | } 102 | 103 | /** 104 | * Returns the current set minimum width of a line. 105 | * 106 | * @return double 107 | */ 108 | public double getLineMinWidth() { 109 | return this.lineMinWidth; 110 | } 111 | 112 | /** 113 | * Sets the maximum width of a line. Defaults to 2.5. 114 | * 115 | * @param lineMaxWidth int 116 | */ 117 | public void setLineMaxWidth(int lineMaxWidth) { 118 | double lineMaxWidthD = Double.valueOf(lineMaxWidth); 119 | this.getElement().setProperty("maxWidth", lineMaxWidthD); 120 | this.lineMaxWidth = lineMaxWidthD; 121 | } 122 | 123 | /** 124 | * Sets the maximum width of a line. Defaults to 2.5. 125 | * 126 | * @param lineMaxWidth double 127 | */ 128 | public void setLineMaxWidth(double lineMaxWidth) { 129 | this.getElement().setProperty("maxWidth", lineMaxWidth); 130 | this.lineMaxWidth = lineMaxWidth; 131 | } 132 | 133 | /** 134 | * Returns the current set maximum width of a line. 135 | * 136 | * @return double 137 | */ 138 | public double getLineMaxWidth() { 139 | return this.lineMaxWidth; 140 | } 141 | 142 | /** 143 | * Sets the throttle, which will draw the next point at most once per every x milliseconds. Set it 144 | * to 0 to turn off throttling. Defaults to 16. 145 | * 146 | * @param throttle int 147 | */ 148 | public void setThrottle(int throttle) { 149 | this.getElement().setProperty("throttle", throttle); 150 | this.throttle = throttle; 151 | } 152 | 153 | /** 154 | * Returns the current set throttle. 155 | * 156 | * @return int 157 | */ 158 | public int getThrottle() { 159 | return this.throttle; 160 | } 161 | 162 | /** 163 | * Sets the minDistance, which adds the next point only if the previous one is farther than x 164 | * pixels. Defaults to 5. 165 | * 166 | * @param minDistance int 167 | */ 168 | public void setMinDistance(int minDistance) { 169 | this.getElement().setProperty("minDistance", minDistance); 170 | this.minDistance = minDistance; 171 | } 172 | 173 | /** 174 | * Returns the current set min distance. 175 | * 176 | * @return int 177 | */ 178 | public int getMinDistance() { 179 | return this.minDistance; 180 | } 181 | 182 | /** 183 | * Sets the background color in RGB format. 184 | * 185 | * @param red int 186 | * @param green int 187 | * @param blue int 188 | */ 189 | public void setBackgroundColor(int red, int green, int blue) { 190 | String rgb = "rgb(" + red + ", " + green + ", " + blue + ")"; 191 | this.getElement().setProperty("backgroundColor", rgb); 192 | this.backgroundColor = rgb; 193 | } 194 | 195 | /** 196 | * Sets the background color in RGBA format. 197 | * 198 | * @param red int 199 | * @param green int 200 | * @param blue int 201 | * @param alpha int 202 | */ 203 | public void setBackgroundColor(int red, int green, int blue, int alpha) { 204 | String rgba = "rgba(" + red + ", " + green + ", " + blue + ", " + alpha + ")"; 205 | this.getElement().setProperty("backgroundColor", rgba); 206 | this.backgroundColor = rgba; 207 | } 208 | 209 | /** 210 | * Sets the background color in hex format. 211 | * 212 | * @param hex {@link String} 213 | */ 214 | public void setBackgroundColor(String hex) { 215 | this.getElement().setProperty("backgroundColor", hex); 216 | this.backgroundColor = hex; 217 | } 218 | 219 | /** 220 | * Returns the current set background color. 221 | * 222 | * @return {@link String} 223 | */ 224 | public String getBackgroundColor() { 225 | return this.backgroundColor; 226 | } 227 | 228 | /** 229 | * Sets the pen color in RGB format. 230 | * 231 | * @param red int 232 | * @param green int 233 | * @param blue int 234 | */ 235 | public void setPenColor(int red, int green, int blue) { 236 | String rgb = "rgb(" + red + ", " + green + ", " + blue + ")"; 237 | this.getElement().setProperty("penColor", rgb); 238 | this.penColor = rgb; 239 | } 240 | 241 | /** 242 | * Sets the pen color in hex format. 243 | * 244 | * @param hex {@link String} 245 | */ 246 | public void setPenColor(String hex) { 247 | this.getElement().setProperty("penColor", hex); 248 | this.penColor = hex; 249 | } 250 | 251 | /** 252 | * Returns the current set pen color. 253 | * 254 | * @return {@link String} 255 | */ 256 | public String getPenColor() { 257 | return this.penColor; 258 | } 259 | 260 | /** 261 | * Sets the velocityFilterWeight, which is used to modify new velocity based on the previous 262 | * velocity. Defaults to 0.7 263 | * 264 | * @param velocityFilterWeight int 265 | */ 266 | public void setVelocityFilterWeight(int velocityFilterWeight) { 267 | double velocityFilterWeightD = Double.valueOf(velocityFilterWeight); 268 | this.getElement().getProperty("velocityFilterWeight", velocityFilterWeightD); 269 | this.velocityFilterWeight = velocityFilterWeightD; 270 | } 271 | 272 | /** 273 | * Sets the velocityFilterWeight, which is used to modify new velocity based on the previous 274 | * velocity. Defaults to 0.7 275 | * 276 | * @param velocityFilterWeight double 277 | */ 278 | public void setVelocityFilterWeight(double velocityFilterWeight) { 279 | this.getElement().getProperty("velocityFilterWeight", velocityFilterWeight); 280 | this.velocityFilterWeight = velocityFilterWeight; 281 | } 282 | 283 | /** 284 | * Returns the current set velocityFilterWeight. 285 | * 286 | * @return double 287 | */ 288 | public double getVelocityFilterWeight() { 289 | return this.velocityFilterWeight; 290 | } 291 | 292 | /** 293 | * Returns the current shown image in URI format. 294 | * 295 | * @return {@link String} 296 | */ 297 | public String getImageURI() { 298 | return this.imageUri; 299 | } 300 | 301 | /** 302 | * Returns the current shown image as Base64 decoded byte array. 303 | * 304 | * @return byte[] 305 | */ 306 | public byte[] getImageBase64() { 307 | if (this.imageUri.equals("")) { 308 | return null; 309 | } 310 | String split = this.imageUri.split(",")[1]; 311 | return Base64.getDecoder().decode(split.getBytes(StandardCharsets.UTF_8)); 312 | } 313 | 314 | /** 315 | * Sets the MIME-Type for the image encoder. Has to start with "image/"! 316 | * 317 | * @param type {@link String} 318 | */ 319 | public void setType(String type) { 320 | if (!type.contains("image/")) { 321 | type = "image/png"; 322 | } 323 | this.getElement().setProperty("type", type); 324 | this.type = type; 325 | } 326 | 327 | /** 328 | * Returns the current set MIME-Type. Defaults to image/png. 329 | * 330 | * @return {@link String} 331 | */ 332 | public String getType() { 333 | return this.type; 334 | } 335 | 336 | /** 337 | * Sets the widget read only. 338 | * 339 | * @param readOnly boolean 340 | */ 341 | public void setReadOnly(boolean readOnly) { 342 | this.getElement().setProperty("readOnly", readOnly); 343 | this.readOnly = readOnly; 344 | } 345 | 346 | /** 347 | * Returns if the widget is set to read only. 348 | * 349 | * @return boolean 350 | */ 351 | public boolean isReadOnly() { 352 | return this.readOnly; 353 | } 354 | 355 | /** 356 | * Sets the image of the widget in URI Format. 357 | * 358 | * @param uri {@link String} 359 | */ 360 | public void setImage(String uri) { 361 | this.getElement().setProperty("img", uri); 362 | this.imageUri = uri; 363 | } 364 | 365 | /** 366 | * Sets the encoder quality. All values between 0-1 are accepted. Defaults to 0.85. 367 | * 368 | * @param encoderQuality double 369 | */ 370 | public void setEncoderQuality(double encoderQuality) { 371 | double x = Math.abs(encoderQuality); 372 | if (x > 1) { 373 | x = 1.00; 374 | } 375 | this.getElement().setProperty("encoderOptions", x); 376 | this.encoderQuality = x; 377 | } 378 | 379 | /** 380 | * Returns the current set encoder quality. 381 | * 382 | * @return double 383 | */ 384 | public double getEncoderQuality() { 385 | return this.encoderQuality; 386 | } 387 | 388 | /** Reverts the last change you did to the canvas itself. */ 389 | public void undo() { 390 | this.getElement().callJsFunction("undo"); 391 | } 392 | 393 | /** 394 | * Returns if the widget is empty. 395 | * 396 | * @return boolean 397 | */ 398 | public boolean isEmpty() { 399 | return this.isEmpty; 400 | } 401 | 402 | /** Sets the background color transparent. */ 403 | public void setTransparentBackground() { 404 | this.getElement().setProperty("backgroundColor", "rgb(255, 255, 255)"); 405 | } 406 | } 407 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/frontend/@f0rce/signature-widget.js: -------------------------------------------------------------------------------- 1 | /** 2 | @license MIT 3 | Copyright 2021-2022 David "F0rce" Dodlek 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 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 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. 7 | */ 8 | import { LitElement, html, css } from "lit-element"; 9 | 10 | import SignaturePad from "signature_pad"; 11 | class LitSignaturePad extends LitElement { 12 | static get properties() { 13 | return { 14 | dotSize: { type: Number }, 15 | minWidth: { type: Number }, 16 | maxWidth: { type: Number }, 17 | throttle: { type: Number }, 18 | minDistance: { type: Number }, 19 | backgroundColor: { type: String }, 20 | penColor: { type: String }, 21 | velocityFilterWeight: { type: Number }, 22 | type: { type: String }, 23 | encoderOptions: { type: Number }, 24 | readOnly: { type: Boolean }, 25 | img: { type: String }, 26 | }; 27 | } 28 | 29 | constructor() { 30 | super(); 31 | this.minWidth = 0.5; 32 | this.maxWidth = 2.5; 33 | this.throttle = 16; 34 | this.minDistance = 5; 35 | this.backgroundColor = "#ffffff"; 36 | this.penColor = "#000000"; 37 | this.velocityFilterWeight = 0.7; 38 | this.type = "image/png"; 39 | this.encoderOptions = 0.85; 40 | this.readOnly = false; 41 | } 42 | 43 | static get styles() { 44 | return css` 45 | :host { 46 | display: block; 47 | width: 100%; 48 | height: 100%; 49 | } 50 | #signature { 51 | border: 1px solid var(--lumo-contrast-20pct); 52 | border-radius: var(--lumo-border-radius-s); 53 | width: 100%; 54 | height: 100%; 55 | } 56 | `; 57 | } 58 | 59 | render() { 60 | return html` `; 61 | } 62 | 63 | firstUpdated() { 64 | this.signatureCanvas = this.shadowRoot.getElementById("signature"); 65 | 66 | var ratio = Math.max(window.devicePixelRatio || 1, 1); 67 | this.signatureCanvas.width = this.signatureCanvas.offsetWidth * ratio; 68 | this.signatureCanvas.height = this.signatureCanvas.offsetHeight * ratio; 69 | this.signatureCanvas.getContext("2d").scale(ratio, ratio); 70 | 71 | let self = this; 72 | this.observer = new ResizeObserver(function (entries) { 73 | entries.forEach(function (entry) { 74 | self.resizeSignature(); 75 | }); 76 | }); 77 | 78 | let intersectionObserver = new IntersectionObserver(function ( 79 | entries, 80 | observer 81 | ) { 82 | entries.forEach(function (entry) { 83 | if (entry.isIntersecting) { 84 | self.resizeSignature(); 85 | } 86 | }); 87 | }); 88 | 89 | intersectionObserver.observe(this.signatureCanvas); 90 | this.initSignaturePad(); 91 | } 92 | 93 | updated(changedProperties) { 94 | changedProperties.forEach((oldValue, propName) => { 95 | if (propName == "type" || propName == "encoderOptions") { 96 | return this.onEncodingChanged(); 97 | } 98 | let funcToCall = propName + "Changed"; 99 | if (typeof this[funcToCall] == "function") { 100 | this[funcToCall](); 101 | } 102 | }); 103 | } 104 | 105 | initSignaturePad() { 106 | this.signaturePad = new SignaturePad(this.signatureCanvas, { 107 | dotSize: this.dotSize, 108 | minWidth: this.minWidth, 109 | maxWidth: this.maxWidth, 110 | throttle: this.throttle, 111 | backgroundColor: this.backgroundColor, 112 | penColor: this.penColor, 113 | velocityFilterWeight: this.velocityFilterWeight, 114 | minDistance: this.minDistance, 115 | }); 116 | 117 | this.signaturePad.addEventListener("endStroke", () => { 118 | this.encodeImage(); 119 | }); 120 | 121 | this.signaturePad.clear(); 122 | } 123 | 124 | clear() { 125 | if (!this.signaturePad) return; 126 | this.signaturePad.clear(); 127 | this.encodeImage(); 128 | } 129 | 130 | undo() { 131 | if (!this.signaturePad) return; 132 | var data = this.signaturePad.toData(); 133 | if (data.length !== 0) { 134 | data.pop(); 135 | this.signaturePad.fromData(data); 136 | this.encodeImage(); 137 | } 138 | } 139 | 140 | encodeImage() { 141 | if (!this.signaturePad) return; 142 | var uri = this.signaturePad.toDataURL(this.type, this.encodingOptions); 143 | this.dispatchEvent( 144 | new CustomEvent("image-encode", { 145 | detail: { 146 | image: uri, 147 | type: this.type, 148 | isEmpty: this.signaturePad.isEmpty(), 149 | }, 150 | }) 151 | ); 152 | } 153 | 154 | dotSizeChanged() { 155 | if (!this.signaturePad) return; 156 | this.signaturePad.dotSize = this.dotSize; 157 | } 158 | 159 | minWidthChanged() { 160 | if (!this.signaturePad) return; 161 | this.signaturePad.minWidth = this.minWidth; 162 | } 163 | 164 | maxWidthChanged() { 165 | if (!this.signaturePad) return; 166 | this.signaturePad.maxWidth = this.maxWidth; 167 | } 168 | 169 | throttleChanged() { 170 | if (!this.signaturePad) return; 171 | this.signaturePad.throttle = this.throttle; 172 | } 173 | 174 | minDistanceChanged() { 175 | if (!this.signaturePad) return; 176 | this.signaturePad.minDistance = this.minDistance; 177 | } 178 | 179 | backgroundColorChanged() { 180 | if (!this.signaturePad) return; 181 | this.signaturePad.backgroundColor = this.backgroundColor; 182 | } 183 | 184 | penColorChanged() { 185 | if (!this.signaturePad) return; 186 | this.signaturePad.penColor = this.penColor; 187 | } 188 | 189 | velocityFilterWeightChanged() { 190 | if (!this.signaturePad) return; 191 | this.signaturePad.velocityFilterWeight = this.velocityFilterWeight; 192 | } 193 | 194 | readOnlyChanged() { 195 | if (!this.signaturePad) return; 196 | if (this.readOnly === true) { 197 | this.signaturePad.off(); 198 | } else { 199 | this.signaturePad.on(); 200 | } 201 | } 202 | 203 | imgChanged() { 204 | if (!this.signaturePad) return; 205 | this.signaturePad.fromDataURL(this.img); 206 | this.encodeImage(); 207 | } 208 | 209 | onEncodingChanged() { 210 | if (!this.signaturePad) return; 211 | this.encodeImage(); 212 | } 213 | 214 | resizeSignature() { 215 | var dataUrl = this.signaturePad.toDataURL(this.type, this.encoderOptions); 216 | var ratio = Math.max(window.devicePixelRatio || 1, 1); 217 | this.signatureCanvas.width = this.signatureCanvas.offsetWidth * ratio; 218 | this.signatureCanvas.height = this.signatureCanvas.offsetHeight * ratio; 219 | this.signatureCanvas.getContext("2d").scale(ratio, ratio); 220 | this.signaturePad.fromDataURL(dataUrl); 221 | } 222 | } 223 | 224 | customElements.define("lit-signature-pad", LitSignaturePad); 225 | -------------------------------------------------------------------------------- /src/test/java/de/f0rce/signaturepad/AbstractViewTest.java: -------------------------------------------------------------------------------- 1 | package de.f0rce.signaturepad; 2 | 3 | import io.github.bonigarcia.wdm.WebDriverManager; 4 | import org.junit.Before; 5 | import org.junit.BeforeClass; 6 | import org.junit.Rule; 7 | import org.openqa.selenium.chrome.ChromeDriver; 8 | 9 | import com.vaadin.testbench.ScreenshotOnFailureRule; 10 | import com.vaadin.testbench.TestBench; 11 | import com.vaadin.testbench.parallel.ParallelTest; 12 | 13 | /** 14 | * Base class for ITs 15 | *

16 | * The tests use Chrome driver (see pom.xml for integration-tests profile) to 17 | * run integration tests on a headless Chrome. If a property {@code test.use 18 | * .hub} is set to true, {@code AbstractViewTest} will assume that the 19 | * TestBench test is running in a CI environment. In order to keep the this 20 | * class light, it makes certain assumptions about the CI environment (such 21 | * as available environment variables). It is not advisable to use this class 22 | * as a base class for you own TestBench tests. 23 | *

24 | * To learn more about TestBench, visit 25 | * Vaadin TestBench. 26 | */ 27 | public abstract class AbstractViewTest extends ParallelTest { 28 | private static final int SERVER_PORT = 8080; 29 | 30 | private final String route; 31 | 32 | @Rule 33 | public ScreenshotOnFailureRule rule = new ScreenshotOnFailureRule(this, 34 | true); 35 | 36 | @BeforeClass 37 | public static void setupClass() { 38 | WebDriverManager.chromedriver().setup(); 39 | } 40 | 41 | public AbstractViewTest() { 42 | this(""); 43 | } 44 | 45 | protected AbstractViewTest(String route) { 46 | this.route = route; 47 | } 48 | 49 | @Before 50 | public void setup() throws Exception { 51 | if (isUsingHub()) { 52 | super.setup(); 53 | } else { 54 | setDriver(TestBench.createDriver(new ChromeDriver())); 55 | } 56 | getDriver().get(getURL(route)); 57 | } 58 | 59 | /** 60 | * Returns deployment host name concatenated with route. 61 | * 62 | * @return URL to route 63 | */ 64 | private static String getURL(String route) { 65 | return String.format("http://%s:%d/%s", getDeploymentHostname(), 66 | SERVER_PORT, route); 67 | } 68 | 69 | /** 70 | * Property set to true when running on a test hub. 71 | */ 72 | private static final String USE_HUB_PROPERTY = "test.use.hub"; 73 | 74 | /** 75 | * Returns whether we are using a test hub. This means that the starter 76 | * is running tests in Vaadin's CI environment, and uses TestBench to 77 | * connect to the testing hub. 78 | * 79 | * @return whether we are using a test hub 80 | */ 81 | private static boolean isUsingHub() { 82 | return Boolean.TRUE.toString().equals( 83 | System.getProperty(USE_HUB_PROPERTY)); 84 | } 85 | 86 | /** 87 | * If running on CI, get the host name from environment variable HOSTNAME 88 | * 89 | * @return the host name 90 | */ 91 | private static String getDeploymentHostname() { 92 | return isUsingHub() ? System.getenv("HOSTNAME") : "localhost"; 93 | } 94 | } -------------------------------------------------------------------------------- /src/test/java/de/f0rce/signaturepad/View.java: -------------------------------------------------------------------------------- 1 | package de.f0rce.signaturepad; 2 | 3 | import com.vaadin.flow.component.button.Button; 4 | import com.vaadin.flow.component.dialog.Dialog; 5 | import com.vaadin.flow.component.html.Div; 6 | import com.vaadin.flow.component.html.Image; 7 | import com.vaadin.flow.component.orderedlayout.HorizontalLayout; 8 | import com.vaadin.flow.router.Route; 9 | 10 | @Route("") 11 | public class View extends Div { 12 | 13 | public View() { 14 | HorizontalLayout vl = new HorizontalLayout(); 15 | 16 | SignaturePad signature = new SignaturePad(); 17 | signature.setHeight("100px"); 18 | signature.setWidth("300px"); 19 | 20 | Button button = new Button("Undo"); 21 | Button button2 = new Button("Save"); 22 | 23 | Dialog dialog = new Dialog(); 24 | dialog.setSizeFull(); 25 | dialog.setCloseOnOutsideClick(true); 26 | dialog.setCloseOnEsc(true); 27 | dialog.setResizable(true); 28 | 29 | Image sign = new Image(); 30 | 31 | vl.add(signature, sign); 32 | 33 | dialog.add(vl, button, button2); 34 | 35 | Button button3 = new Button("Open Sign Dialog"); 36 | 37 | this.add(dialog, button3); 38 | 39 | button3.addClickListener(event -> { 40 | dialog.open(); 41 | }); 42 | 43 | button.addClickListener(event -> { 44 | signature.undo(); 45 | }); 46 | 47 | button2.addClickListener(event -> { 48 | sign.setSrc(signature.getImageURI()); 49 | }); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/test/java/de/f0rce/signaturepad/ViewIT.java: -------------------------------------------------------------------------------- 1 | package de.f0rce.signaturepad; 2 | 3 | import org.junit.Assert; 4 | import org.junit.Test; 5 | 6 | import com.vaadin.testbench.TestBenchElement; 7 | 8 | public class ViewIT extends AbstractViewTest { 9 | 10 | @Test 11 | public void componentWorks() { 12 | final TestBenchElement paperSlider = $("signature_pad").first(); 13 | // Check that signature_pad contains at least one other element, which means that 14 | // is has been upgraded to a custom element and not just rendered as an empty 15 | // tag 16 | Assert.assertTrue( 17 | paperSlider.$(TestBenchElement.class).all().size() > 0); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file contains project specific customizations for the webpack build. 3 | * It is autogenerated if it didn't exist or if it was made for an older 4 | * incompatible version. 5 | * 6 | * Defaults are provided in an autogenerated webpack.generated.js file and used by this file. 7 | * The webpack.generated.js file is overwritten on each build and no customization can be done there. 8 | */ 9 | const merge = require('webpack-merge'); 10 | const flowDefaults = require('./webpack.generated.js'); 11 | 12 | module.exports = merge(flowDefaults, { 13 | 14 | }); 15 | 16 | /** 17 | * This file can be used to configure the flow plugin defaults. 18 | * 19 | * // Add a custom plugin 20 | * flowDefaults.plugins.push(new MyPlugin()); 21 | * 22 | * // Update the rules to also transpile `.mjs` files 23 | * if (!flowDefaults.module.rules[0].test) { 24 | * throw "Unexpected structure in generated webpack config"; 25 | * } 26 | * flowDefaults.module.rules[0].test = /\.m?js$/ 27 | * 28 | * // Include a custom JS in the entry point in addition to generated-flow-imports.js 29 | * if (typeof flowDefaults.entry.index != "string") { 30 | * throw "Unexpected structure in generated webpack config"; 31 | * } 32 | * flowDefaults.entry.index = [flowDefaults.entry.index, "myCustomFile.js"]; 33 | * 34 | * or add new configuration in the merge block. 35 | * 36 | * module.exports = merge(flowDefaults, { 37 | * mode: 'development', 38 | * devtool: 'inline-source-map' 39 | * }); 40 | * 41 | */ 42 | -------------------------------------------------------------------------------- /webpack.generated.js: -------------------------------------------------------------------------------- 1 | /** 2 | * NOTICE: this is an auto-generated file 3 | * 4 | * This file has been generated by the `flow:prepare-frontend` maven goal. 5 | * This file will be overwritten on every run. Any custom changes should be made to webpack.config.js 6 | */ 7 | const fs = require('fs'); 8 | const CopyWebpackPlugin = require('copy-webpack-plugin'); 9 | const CompressionPlugin = require('compression-webpack-plugin'); 10 | const {BabelMultiTargetPlugin} = require('webpack-babel-multi-target-plugin'); 11 | const ExtraWatchWebpackPlugin = require('extra-watch-webpack-plugin'); 12 | 13 | // Flow plugins 14 | const StatsPlugin = require('@vaadin/stats-plugin'); 15 | const ThemeLiveReloadPlugin = require('@vaadin/theme-live-reload-plugin'); 16 | const { ApplicationThemePlugin, processThemeResources, extractThemeName, findParentThemes } = require('@vaadin/application-theme-plugin'); 17 | 18 | const path = require('path'); 19 | const baseDir = path.resolve(__dirname); 20 | // the folder of app resources (main.js and flow templates) 21 | 22 | // this matches /themes/my-theme/ and is used to check css url handling and file path build. 23 | const themePartRegex = /(\\|\/)themes\1[\s\S]*?\1/; 24 | 25 | const frontendFolder = require('path').resolve(__dirname, 'frontend'); 26 | 27 | const fileNameOfTheFlowGeneratedMainEntryPoint = require('path').resolve(__dirname, 'target/frontend/generated-flow-imports.js'); 28 | const mavenOutputFolderForFlowBundledFiles = require('path').resolve(__dirname, 'target/classes/META-INF/VAADIN'); 29 | 30 | const devmodeGizmoJS = '@vaadin/flow-frontend/VaadinDevmodeGizmo.js' 31 | 32 | // public path for resources, must match Flow VAADIN_BUILD 33 | const build = 'build'; 34 | // public path for resources, must match the request used in flow to get the /build/stats.json file 35 | const config = 'config'; 36 | // folder for outputting index.js bundle, etc. 37 | const buildFolder = `${mavenOutputFolderForFlowBundledFiles}/${build}`; 38 | // folder for outputting stats.json 39 | const confFolder = `${mavenOutputFolderForFlowBundledFiles}/${config}`; 40 | // file which is used by flow to read templates for server `@Id` binding 41 | const statsFile = `${confFolder}/stats.json`; 42 | 43 | // Folders in the project which can contain static assets. 44 | const projectStaticAssetsFolders = [ 45 | path.resolve(__dirname, 'src', 'main', 'resources', 'META-INF', 'resources'), 46 | path.resolve(__dirname, 'src', 'main', 'resources', 'static'), 47 | frontendFolder 48 | ]; 49 | 50 | const projectStaticAssetsOutputFolder = require('path').resolve(__dirname, 'target/classes/META-INF/VAADIN/static'); 51 | 52 | // Folders in the project which can contain application themes 53 | const themeProjectFolders = projectStaticAssetsFolders.map((folder) => 54 | path.resolve(folder, 'themes') 55 | ); 56 | 57 | 58 | // Target flow-fronted auto generated to be the actual target folder 59 | const flowFrontendFolder = require('path').resolve(__dirname, 'target/frontend'); 60 | 61 | // make sure that build folder exists before outputting anything 62 | const mkdirp = require('mkdirp'); 63 | 64 | const devMode = process.argv.find(v => v.indexOf('webpack-dev-server') >= 0); 65 | 66 | !devMode && mkdirp(buildFolder); 67 | mkdirp(confFolder); 68 | 69 | let stats; 70 | 71 | const transpile = !devMode || process.argv.find(v => v.indexOf('--transpile-es5') >= 0); 72 | 73 | const watchDogPrefix = '--watchDogPort='; 74 | let watchDogPort = devMode && process.argv.find(v => v.indexOf(watchDogPrefix) >= 0); 75 | let client; 76 | if (watchDogPort) { 77 | watchDogPort = watchDogPort.substr(watchDogPrefix.length); 78 | const runWatchDog = () => { 79 | client = new require('net').Socket(); 80 | client.setEncoding('utf8'); 81 | client.on('error', function () { 82 | console.log("Watchdog connection error. Terminating webpack process..."); 83 | client.destroy(); 84 | process.exit(0); 85 | }); 86 | client.on('close', function () { 87 | client.destroy(); 88 | runWatchDog(); 89 | }); 90 | 91 | client.connect(watchDogPort, 'localhost'); 92 | } 93 | 94 | runWatchDog(); 95 | } 96 | 97 | const flowFrontendThemesFolder = path.resolve(flowFrontendFolder, 'themes'); 98 | const frontendGeneratedFolder = path.resolve(frontendFolder, "generated"); 99 | const themeOptions = { 100 | devMode: devMode, 101 | // The following matches ./frontend/generated/theme.js 102 | // and for theme in JAR that is copied to target/frontend/themes/ 103 | themeResourceFolder: flowFrontendThemesFolder, 104 | themeProjectFolders: themeProjectFolders, 105 | projectStaticAssetsOutputFolder: projectStaticAssetsOutputFolder, 106 | frontendGeneratedFolder: frontendGeneratedFolder 107 | }; 108 | let themeName = undefined; 109 | let themeWatchFolders = undefined; 110 | if (devMode) { 111 | // Current theme name is being extracted from theme.js located in 112 | // frontend/generated folder 113 | themeName = extractThemeName(frontendGeneratedFolder); 114 | const parentThemePaths = findParentThemes(themeName, themeOptions); 115 | const currentThemeFolders = [...projectStaticAssetsFolders 116 | .map((folder) => path.resolve(folder, "themes", themeName)), 117 | path.resolve(flowFrontendThemesFolder, themeName)]; 118 | // Watch the components folders for component styles update in both 119 | // current theme and parent themes. Other folders or CSS files except 120 | // 'styles.css' should be referenced from `styles.css` anyway, so no need 121 | // to watch them. 122 | themeWatchFolders = [...currentThemeFolders, ...parentThemePaths] 123 | .map((themeFolder) => path.resolve(themeFolder, "components")); 124 | } 125 | 126 | const processThemeResourcesCallback = (logger) => processThemeResources(themeOptions, logger); 127 | 128 | exports = { 129 | frontendFolder: `${frontendFolder}`, 130 | buildFolder: `${buildFolder}`, 131 | confFolder: `${confFolder}` 132 | }; 133 | 134 | module.exports = { 135 | mode: 'production', 136 | context: frontendFolder, 137 | entry: { 138 | bundle: fileNameOfTheFlowGeneratedMainEntryPoint, 139 | ...(devMode && { gizmo: devmodeGizmoJS }) 140 | }, 141 | 142 | output: { 143 | filename: `${build}/vaadin-[name]-[contenthash].cache.js`, 144 | path: mavenOutputFolderForFlowBundledFiles, 145 | publicPath: 'VAADIN/', 146 | }, 147 | 148 | resolve: { 149 | // Search for import 'x/y' inside these folders, used at least for importing an application theme 150 | modules: [ 151 | 'node_modules', 152 | flowFrontendFolder, 153 | ...projectStaticAssetsFolders, 154 | ], 155 | extensions: ['.ts', '.js'], 156 | alias: { 157 | Frontend: frontendFolder 158 | } 159 | }, 160 | 161 | devServer: { 162 | // webpack-dev-server serves ./ , webpack-generated, and java webapp 163 | contentBase: [mavenOutputFolderForFlowBundledFiles, 'src/main/webapp'], 164 | after: function(app, server) { 165 | app.get(`/stats.json`, function(req, res) { 166 | res.json(stats); 167 | }); 168 | app.get(`/stats.hash`, function(req, res) { 169 | res.json(stats.hash.toString()); 170 | }); 171 | app.get(`/assetsByChunkName`, function(req, res) { 172 | res.json(stats.assetsByChunkName); 173 | }); 174 | app.get(`/stop`, function(req, res) { 175 | // eslint-disable-next-line no-console 176 | console.log("Stopped 'webpack-dev-server'"); 177 | process.exit(0); 178 | }); 179 | } 180 | }, 181 | 182 | module: { 183 | rules: [ 184 | ...(transpile ? [ 185 | { 186 | test: /\.tsx?$/, 187 | use: [ BabelMultiTargetPlugin.loader(), 'ts-loader' ], 188 | } 189 | ] : [{ 190 | test: /\.tsx?$/, 191 | use: ['ts-loader'] 192 | }]), 193 | ...(transpile ? [{ // Files that Babel has to transpile 194 | test: /\.js$/, 195 | use: [BabelMultiTargetPlugin.loader()] 196 | }] : []), 197 | { 198 | test: /\.css$/i, 199 | use: [ 200 | { 201 | loader: 'css-loader', 202 | options: { 203 | url: (url, resourcePath) => { 204 | // css urls may contain query string or fragment identifiers 205 | // that should removed before resolving real path 206 | // e.g 207 | // ../webfonts/fa-solid-900.svg#fontawesome 208 | // ../webfonts/fa-brands-400.eot?#iefix 209 | if(url.includes('?')) 210 | url = url.substring(0, url.indexOf('?')); 211 | if(url.includes('#')) 212 | url = url.substring(0, url.indexOf('#')); 213 | 214 | // Only translate files from node_modules 215 | const resolve = resourcePath.match(/(\\|\/)node_modules\1/) 216 | && fs.existsSync(path.resolve(path.dirname(resourcePath), url)); 217 | const themeResource = resourcePath.match(themePartRegex) && url.match(/^themes\/[\s\S]*?\//); 218 | return resolve || themeResource; 219 | }, 220 | // use theme-loader to also handle any imports in css files 221 | importLoaders: 1 222 | }, 223 | }, 224 | { 225 | // theme-loader will change any url starting with './' to start with 'VAADIN/static' instead 226 | // NOTE! this loader should be here so it's run before css-loader as loaders are applied Right-To-Left 227 | loader: '@vaadin/theme-loader', 228 | options: { 229 | devMode: devMode 230 | } 231 | } 232 | ], 233 | }, 234 | { 235 | // File-loader only copies files used as imports in .js files or handled by css-loader 236 | test: /\.(png|gif|jpg|jpeg|svg|eot|woff|woff2|otf|ttf)$/, 237 | use: [{ 238 | loader: 'file-loader', 239 | options: { 240 | outputPath: 'static/', 241 | name(resourcePath, resourceQuery) { 242 | if (resourcePath.match(/(\\|\/)node_modules\1/)) { 243 | return /(\\|\/)node_modules\1(?!.*node_modules)([\S]+)/.exec(resourcePath)[2].replace(/\\/g, "/"); 244 | } 245 | if (resourcePath.match(/(\\|\/)frontend\1/)) { 246 | return /(\\|\/)frontend\1(?!.*frontend)([\S]+)/.exec(resourcePath)[2].replace(/\\/g, "/"); 247 | } 248 | return '[path][name].[ext]'; 249 | } 250 | } 251 | }], 252 | }, 253 | ] 254 | }, 255 | performance: { 256 | maxEntrypointSize: 2097152, // 2MB 257 | maxAssetSize: 2097152 // 2MB 258 | }, 259 | plugins: [ 260 | // Generate compressed bundles when not devMode 261 | ...(devMode ? [] : [new CompressionPlugin()]), 262 | 263 | // Transpile with babel, and produce different bundles per browser 264 | ...(transpile ? [new BabelMultiTargetPlugin({ 265 | babel: { 266 | plugins: [ 267 | // workaround for Safari 10 scope issue (https://bugs.webkit.org/show_bug.cgi?id=159270) 268 | "@babel/plugin-transform-block-scoping", 269 | 270 | // Edge does not support spread '...' syntax in object literals (#7321) 271 | "@babel/plugin-proposal-object-rest-spread" 272 | ], 273 | 274 | presetOptions: { 275 | useBuiltIns: false // polyfills are provided from webcomponents-loader.js 276 | } 277 | }, 278 | targets: { 279 | 'es6': { // Evergreen browsers 280 | browsers: [ 281 | // It guarantees that babel outputs pure es6 in bundle and in stats.json 282 | // In the case of browsers no supporting certain feature it will be 283 | // covered by the webcomponents-loader.js 284 | 'last 1 Chrome major versions' 285 | ], 286 | }, 287 | 'es5': { // IE11 288 | browsers: [ 289 | 'ie 11' 290 | ], 291 | tagAssetsWithKey: true, // append a suffix to the file name 292 | } 293 | } 294 | })] : []), 295 | 296 | new ApplicationThemePlugin(themeOptions), 297 | 298 | ...(devMode && themeName ? [new ExtraWatchWebpackPlugin({ 299 | files: [], 300 | dirs: themeWatchFolders 301 | }), new ThemeLiveReloadPlugin(processThemeResourcesCallback)] : []), 302 | 303 | new StatsPlugin({ 304 | devMode: devMode, 305 | statsFile: statsFile, 306 | setResults: function (statsFile) { 307 | stats = statsFile; 308 | } 309 | }), 310 | 311 | // Generates the stats file for flow `@Id` binding. 312 | function (compiler) { 313 | compiler.hooks.done.tapAsync('FlowIdPlugin', (compilation, done) => { 314 | // trigger live reload via server 315 | if (client) { 316 | client.write('reload\n'); 317 | } 318 | done(); 319 | }); 320 | }, 321 | 322 | // Copy webcomponents polyfills. They are not bundled because they 323 | // have its own loader based on browser quirks. 324 | new CopyWebpackPlugin([{ 325 | from: `${baseDir}/node_modules/@webcomponents/webcomponentsjs`, 326 | to: `${build}/webcomponentsjs/`, 327 | ignore: ['*.md', '*.json'] 328 | }]), 329 | ] 330 | }; 331 | --------------------------------------------------------------------------------