├── .github └── workflows │ └── node.js.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── demos ├── base.css ├── certificate.html ├── contract.html ├── custom_save.html ├── default.html ├── deliveryslip.html ├── fonts │ ├── lobster.ttf │ └── tangerine.ttf └── invoice.html ├── package-lock.json ├── package.json ├── src ├── Document.js ├── PopupWindow.js ├── ReportBro.js ├── ajaxload.gif ├── commands │ ├── AddDeleteDocElementCmd.js │ ├── AddDeleteParameterCmd.js │ ├── AddDeleteStyleCmd.js │ ├── Command.js │ ├── CommandGroupCmd.js │ ├── MovePanelItemCmd.js │ └── SetValueCmd.js ├── container │ ├── Band.js │ ├── Container.js │ ├── Frame.js │ └── Page.js ├── data │ ├── DocumentProperties.js │ ├── Parameter.js │ └── Style.js ├── elements │ ├── BarCodeElement.js │ ├── DocElement.js │ ├── FrameElement.js │ ├── ImageElement.js │ ├── LineElement.js │ ├── PageBreakElement.js │ ├── SectionBandElement.js │ ├── SectionElement.js │ ├── TableBandElement.js │ ├── TableElement.js │ ├── TableTextElement.js │ ├── TextElement.js │ ├── WatermarkImageElement.js │ └── WatermarkTextElement.js ├── fonts │ ├── font_style.css │ ├── open-sans-v34-latin-300.woff │ ├── open-sans-v34-latin-300.woff2 │ ├── open-sans-v34-latin-600.woff │ ├── open-sans-v34-latin-600.woff2 │ ├── open-sans-v34-latin-800.woff │ ├── open-sans-v34-latin-800.woff2 │ ├── open-sans-v34-latin-regular.woff │ └── open-sans-v34-latin-regular.woff2 ├── i18n │ ├── locale_de_de.js │ ├── locale_en_us.js │ └── locales.js ├── iconfonts │ ├── reportbro.svg │ ├── reportbro.ttf │ ├── reportbro.woff │ ├── reportbro.woff2 │ └── style.css ├── main.css ├── main.js ├── menu │ ├── MainPanel.js │ ├── MainPanelItem.js │ └── MenuPanel.js ├── panels │ ├── DocElementPanel.js │ ├── DocumentPropertiesPanel.js │ ├── EmptyDetailPanel.js │ ├── PanelBase.js │ ├── ParameterPanel.js │ └── StylePanel.js ├── quill.reportbro.css ├── rb_logo_dark.png ├── rb_logo_white.png ├── toggle-switch.css └── utils.js ├── webpack.common.js ├── webpack.config.js └── webpack.config.prod.js /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Node.js CI 5 | 6 | on: 7 | release: 8 | types: [released] 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | 14 | permissions: 15 | contents: write 16 | actions: read 17 | checks: write 18 | issues: read 19 | packages: write 20 | pull-requests: read 21 | repository-projects: read 22 | statuses: read 23 | 24 | strategy: 25 | matrix: 26 | node-version: [20] 27 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ 28 | 29 | steps: 30 | - uses: actions/checkout@v4 31 | - name: Use Node.js ${{ matrix.node-version }} 32 | uses: actions/setup-node@v4 33 | with: 34 | node-version: ${{ matrix.node-version }} 35 | cache: 'npm' 36 | - run: npm ci 37 | - run: npm run build-prod 38 | - run: npm pack 39 | 40 | - name: Upload Release Assets 41 | uses: alexellis/upload-assets@0.4.1 42 | env: 43 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 44 | with: 45 | asset_paths: '["reportbro-designer-*.tgz", "reportbro-designer-*.zip"]' 46 | 47 | - uses: JS-DevTools/npm-publish@v3 48 | with: 49 | token: ${{ secrets.NPM_TOKEN }} 50 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | 3 | # Logs 4 | logs 5 | *.log 6 | npm-debug.log* 7 | yarn-debug.log* 8 | yarn-error.log* 9 | 10 | # Runtime data 11 | pids 12 | *.pid 13 | *.seed 14 | *.pid.lock 15 | 16 | # Directory for instrumented libs generated by jscoverage/JSCover 17 | lib-cov 18 | 19 | # Coverage directory used by tools like istanbul 20 | coverage 21 | 22 | # nyc test coverage 23 | .nyc_output 24 | 25 | # IDE 26 | .vs_code 27 | .vscode 28 | .idea 29 | 30 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 31 | .grunt 32 | 33 | # Bower dependency directory (https://bower.io/) 34 | bower_components 35 | 36 | # node-waf configuration 37 | .lock-wscript 38 | 39 | # Compiled binary addons (http://nodejs.org/api/addons.html) 40 | build/Release 41 | 42 | # Dependency directories 43 | node_modules/ 44 | jspm_packages/ 45 | 46 | # Typescript v1 declaration files 47 | typings/ 48 | 49 | # Optional npm cache directory 50 | .npm 51 | 52 | # Optional eslint cache 53 | .eslintcache 54 | 55 | # Optional REPL history 56 | .node_repl_history 57 | 58 | # Output of 'npm pack' 59 | *.tgz 60 | *.zip 61 | 62 | # Yarn Integrity file 63 | .yarn-integrity 64 | 65 | # dotenv environment variables file 66 | .env 67 | 68 | # demos for local testing 69 | demos/temp/ 70 | 71 | .DS_Store 72 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [3.10.0] - 2025-01-03 4 | 5 | ### Features 6 | * Support copy & paste of elements across ReportBro instances 7 | * Add "patternLocales" setting to set available pattern locales in document properties 8 | 9 | ### Changes 10 | * Add "pt" locale to pattern locales in document properties 11 | 12 | ### Bug Fixes 13 | 14 | ## [3.9.2] - 2024-10-07 15 | 16 | ### Features 17 | * Add option for watermark elements to show in foreground 18 | 19 | ### Changes 20 | * Do not restrict watermark elements to page boundaries 21 | * Do not allow to delete default parameters "page_count" and "page_number" 22 | 23 | ### Bug Fixes 24 | * Fix error on xlsx download 25 | * Fix deleting watermark element 26 | * Fix validating decimal input value in test data popup 27 | 28 | ## [3.9.1] - 2024-08-28 29 | 30 | ### Bug Fixes 31 | * Fix rendering style select 32 | 33 | ## [3.9.0] - 2024-08-27 34 | 35 | ### Features 36 | * Add text and image watermarks with rotation and transparency (PLUS version) 37 | * Support styles for various element types 38 | 39 | ## [3.8.0] - 2024-05-23 40 | 41 | ### Features 42 | * Support background color for section bands 43 | * Options to set spreadsheet cell type and pattern 44 | 45 | ### Changes 46 | * Allow custom parameters in reportServerUrl 47 | 48 | ### Bug Fixes 49 | * Fix deleting table row 50 | 51 | ## [3.7.0] - 2024-02-09 52 | 53 | ### Features 54 | * Rich Text parameters for formatted content (PLUS version) 55 | 56 | ### Bug Fixes 57 | * Fix display of page dimension errors in document properties 58 | * Fix setting boolean parameter test data and clearing parameter test data image 59 | * Fix setting save button enabled/disabled for undo/redo command after report was saved 60 | 61 | ## [3.6.0] - 2024-01-15 62 | 63 | ### Changes 64 | * Allow Rich Text in table cells (PLUS version) 65 | * Add parent parameter for createParameter API method to allow creation of field inside Collection/List parameter 66 | * Support retrieving list field with getParameterByName API method 67 | * Show error message when text contains characters that are not contained in font 68 | 69 | ### Bug Fixes 70 | * Hide empty test data row for image and boolean parameter 71 | * Show nullable option for image parameter 72 | 73 | ## [3.5.1] - 2023-11-20 74 | 75 | ### Bug Fixes 76 | * Set data source prefix for parameters in popup window when used in parameter expression 77 | * Show parameter name when displaying error message for missing data 78 | * Do not show Sum/Average and evaluated parameters when editing test data for Collection/List parameter 79 | 80 | ## [3.5.0] - 2023-11-16 81 | 82 | ### Changes 83 | * Add prefix for root parameters in popup window when inside an element with a data source 84 | 85 | ### Bug Fixes 86 | * Fix display of error message for invalid collection or list parameter 87 | 88 | ## [3.4.0] - 2023-10-23 89 | 90 | ### Features 91 | * Add autoSaveOnPreview setting, if set to true the "save" method is called when report is previewed 92 | * Add imageMaxSize setting to downscale images in report template 93 | * Convert static images and images for parameter test data to WebP format to reduce report template size 94 | 95 | ### Changes 96 | * Display report errors on xlsx download if errors are available 97 | * Add imageLimit setting to define maximum number of allowed image elements in report template 98 | 99 | ## [3.3.0] - 2023-09-12 100 | 101 | ### Features 102 | * Support multiple conditional styles 103 | 104 | ### Changes 105 | * Add data source prefix when selecting parameter of outer data source in popup window 106 | 107 | ### Bug Fixes 108 | * Show width property for text element 109 | * Show data source parameters in popup window also for data sources inside collection 110 | * Fix scrolling error message into view 111 | * Hide test data rows for "page_count" and "page_number" parameters 112 | 113 | ## [3.2.0] - 2023-05-12 114 | 115 | ### Features 116 | * Add additional barcodes CODE39, EAN-8, EAN-13 and UPC 117 | * Option to rotate barcode 118 | 119 | ### Bug Fixes 120 | * Fix updating barcode when switching from QR Code 121 | * Clear test data when changing parameter type to/from List, Simple List or Collection 122 | * Fix editing test data and preview rendering of "Simple List" parameter when report template 123 | was saved in ReportBro Designer version < 3.0 124 | * Fix parameter panel display and hide add/delete buttons for parameter when adminMode is deactivated 125 | 126 | ## [3.1.0] - 2023-04-19 127 | 128 | ### Features 129 | * Add frame option to align frame to bottom of page 130 | * Add option to set thousands separator symbol for number formatting 131 | 132 | ### Bug Fixes 133 | * Fix display of error messages 134 | * Fix deleting existing elements before loading report 135 | 136 | ## [3.0.0] - 2022-10-10 137 | 138 | ### Features 139 | * Nested parameters (PLUS version) 140 | * Option to specify image file for test data of image parameter 141 | * Option to set bar width for code128 barcode 142 | 143 | ### Changes 144 | * Remove jquery dependency (see README.md for changed initialization) 145 | * Add validation of ReportBro properties to avoid invalid values 146 | * Replace reference to external google fonts with local files 147 | 148 | ### Bug Fixes 149 | * Fix dragging multiple elements to different container 150 | 151 | ## [2.1.1] - 2022-05-17 152 | 153 | ### Changes 154 | * update dependenices 155 | * non functional change generate zip and tgz with pack 156 | 157 | ## [2.1.0] - 2022-04-22 158 | 159 | ### Changes 160 | * add printIf for page break 161 | 162 | ## [2.0.1] - 2022-01-28 163 | 164 | ### Changes 165 | * add requestCallback which is called before a preview request (pdf/xlsx) and 166 | can be used to change request parameters 167 | * support request headers for preview request (pdf/xlsx) 168 | * upgrade dependencies to fix potential security vulnerabilities 169 | 170 | ## [2.0.0] - 2021-08-06 171 | 172 | ### Features 173 | * Rich Text (PLUS version) 174 | * QR Code 175 | * option to set basic auth for report preview request 176 | * option to set custom headers for report preview request 177 | * option to repeat table group on each page 178 | * option to set colors for color palette 179 | * option to set available font sizes 180 | 181 | ### Changes 182 | * update all package dependencies and adapt webpack configuration for webpack 5 183 | to remove build warnings (upgrade to npm v7) 184 | * remove external dependencies for autosize, JsBarcode and spectrum 185 | * add processErrors API method to display report errors 186 | * add clearErrors API method to clear existing report errors 187 | 188 | ## [1.6.0] - 2021-03-19 189 | 190 | ### Features 191 | * zoom buttons for document panel 192 | 193 | ### Changes 194 | * add parameter to cmdExecutedCallback which indicates if command was done or undone 195 | * add "copy" suffix to name of pasted parameter and style for unique names 196 | * only update parameter references on name change if the parameter name is unique 197 | * Allow starting area selection inside container (frame/section) and 198 | do not include container element in area selection 199 | 200 | ### Bug Fixes 201 | * set correct cell height when table is created/updated 202 | 203 | ## [1.5.2] - 2020-10-06 204 | 205 | ### Changes 206 | * option to set default font for new text and style 207 | * update logo and css styling 208 | * option to set css theme 209 | * add getClassName method for introspection to command, data and element classes 210 | 211 | ## [1.5.1] - 2020-07-27 212 | 213 | ### Features 214 | * option to highlight unused parameters on report load 215 | 216 | ### Changes 217 | * add destroy method to remove dom nodes and event handlers 218 | * allow border width in 0.5 steps 219 | 220 | ### Bug Fixes 221 | * update table width after deleting column 222 | 223 | ## [1.5.0] - 2020-07-15 224 | 225 | ### Features 226 | * option to expand column width if there are hidden columns 227 | * option to force page break for each new group in a table 228 | * option to enable text wrap in spreadsheet cell 229 | 230 | ### Changes 231 | * show list parameters in popup window for expression 232 | (this allows to reference fields of the same row in the expression) 233 | 234 | ### Bug Fixes 235 | * fix javascript error when parameter name is empty 236 | 237 | ## [1.4.0] - 2020-04-20 238 | 239 | ### Features 240 | * dynamic document element panel which allows modifying multiple 241 | document elements (also of different kinds) at once 242 | * allow modifying text style settings when a style is selected 243 | 244 | ### Changes 245 | * add selectCallback which is called when an object is selected/deselected 246 | * add isModified API method to return modified flag 247 | * allow image parameter type in list parameter 248 | * add smaller font sizes to drop down (starting from 4) 249 | * show image preview for images specified by url 250 | 251 | ### Bug Fixes 252 | * fix initialization of ReportBro when called without properties 253 | * fix adding new elements when preview tab exists (Chrome on macOS) 254 | 255 | ## [1.3.4] - 2019-12-23 256 | 257 | ### Changes 258 | * option to show menu buttons to log report template to console and load report template from text 259 | 260 | ### Bug Fixes 261 | * fix changing table columns when table contains cells with colspan 262 | * fix loading report with small table (< 200px) with table positioned near right border 263 | 264 | ## [1.3.3] - 2019-11-08 265 | 266 | ### Bug Fixes 267 | * do not show resize mouse cursor in corners when table is selected 268 | * fix setting transparent color 269 | * fix parameter type drop down options in Safari 270 | * fix content area and ReportBro logo alignment when sidebar is active 271 | 272 | ## [1.3.2] - 2019-09-03 273 | 274 | ### Bug Fixes 275 | * fix freeze when inserting table columns left or right to selected table cell 276 | * fix checking bounds when table is dragged over right border 277 | 278 | ## [1.3.1] - 2019-09-02 279 | 280 | ### Changes 281 | * do not allow deletion of internal row_number parameter 282 | * insert internal row_number parameter at top 283 | 284 | ### Bug Fixes 285 | * do not show internal row_number parameter in "Edit test data" popup 286 | 287 | ## [1.3.0] - 2019-08-26 288 | 289 | ### Features 290 | * sizer to change main panel width 291 | * column span field for table text element 292 | * add internal parameter row_number for list parameters 293 | * add locales for separate language files (English and German available) 294 | 295 | ### Changes 296 | * focus text input when text element is double clicked 297 | 298 | ### Bug Fixes 299 | * disable save button depending on modified flag 300 | * do not allow setting invalid color value 301 | 302 | ## [1.2.1] - 2019-07-22 303 | 304 | ### Bug Fixes 305 | * fix drag & drop and resize of document elements in Firefox 306 | * fix npm package 307 | 308 | ## [1.2.0] - 2019-07-05 309 | 310 | ### Features 311 | * basic touch support to drag & drop and resize document elements 312 | * public API methods to get, add and delete objects: 313 | getUniqueId, getDocElementById, getStyleById, getParameterById, getParameterByName, 314 | createDocElement, createParameter, createStyle, deleteDocElement, deleteParameter, deleteStyle 315 | 316 | ### Changes 317 | * add cmdExecutedCallback which is called when a command is exuected 318 | 319 | ### Bug Fixes 320 | * delete existing document elements when loading report 321 | * fix issue when editing image size 322 | 323 | ## [1.1.0] - 2019-01-10 324 | 325 | ### Features 326 | * menu action buttons to add columns to the left/right of selected table column, 327 | and buttons to add content rows above/below of selected table column 328 | * allow dragging of selected section and of section band height 329 | * link property for text and image element to create an external link 330 | * strikethrough text style 331 | 332 | ### Bug Fixes 333 | * fix updating parameter references when renaming a parameter which is used inside a table in a section 334 | * remove references when a style is deleted 335 | * update element using style when style is changed 336 | * fix menu item display in sidebar menu 337 | * fix deletion of section: internal containers for section bands were not deleted, 338 | undo delete action did not work properly (elements inside section bands were not restored) 339 | * fix display of error messages for table band print-if field 340 | * do not ignore test data for boolean parameter in report preview 341 | 342 | ### Changes 343 | * allow up to 99 table content rows 344 | * add printIf and removeEmptyElement fields for table 345 | * section and frame can be selected with double click 346 | * more options for text line spacing 347 | * copy pasted element to current scroll position instead of upper left corner 348 | 349 | ## [1.0.0] - 2018-09-25 350 | 351 | ### Bug Fixes 352 | * allow edit text element pattern field and add button to open pattern popup window 353 | * do not show search field in pattern popup 354 | * fix drag & drop of collection/list parameter in menu panel 355 | * fix updating parameter references when renaming a parameter 356 | * fix dragging element into frame with border 357 | 358 | ### Changes 359 | * show separator for data source parameters in parameter popup window 360 | * make sure there is enough space for popup below input, otherwise just show it over input field 361 | * setModified method to change modified status (defines if save button is enabled) 362 | * return ReportBro instance when ReportBro is initialized 363 | 364 | ## [0.12.1] - 2018-06-06 365 | 366 | ### Features 367 | * section elements to iterate lists 368 | * column range spreadsheet property 369 | * copy & paste of parameters and styles 370 | * search filter in parameter popup 371 | * allow decimal values for border width 372 | * selection of elements by dragging a rectangle in the layout editor 373 | * better design for nested menu panel items 374 | 375 | ### Bug Fixes 376 | * fix drag & drop and resizing of multiple selected elements when an element is not aligned on the grid 377 | * allow undo of pasted elements 378 | * keep position of nested elements when pasting frames 379 | * test if dragging menu panel is allowed to new destination (e.g. element cannot be dragged into table band or table text element) 380 | * only show horizontal/vertical alignment buttons if appropriate (container of selected elements must have same x/y-offset) 381 | 382 | ## [0.11.2] - 2018-04-10 383 | 384 | ### Features 385 | * support for dynamic table column (column containing simple array parameter will be expanded to multiple columns) 386 | 387 | ### Bug Fixes 388 | * text element styling when element uses predefined style with borders 389 | * fix undo of deleted frame element (restore nested elements) 390 | * fix display of table column texts when padding of a column text is modified 391 | 392 | ## [0.11.1] - 2018-03-21 393 | 394 | ### Features 395 | * multiple content row definitions for tables 396 | * group expression and print if for table content rows 397 | * boolean parameter type 398 | * simple list parameter type (list items with basic type like string, number, boolean, date) 399 | * nullable setting for parameter to explicitly allow nullable parameters, non-nullable parameters automatically get default value in case there is no data (e.g. 0 for numbers or '' for strings) 400 | 401 | ### Bug Fixes 402 | * update table column element height when table row height is changed 403 | * copy&paste of frame elements 404 | * save eval setting of table column text 405 | 406 | ## [0.10.1] - 2017-11-02 407 | 408 | ### Features 409 | * frame elements to group document elements 410 | 411 | ## [0.9.9] - 2017-08-19 412 | 413 | Initial release. 414 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ReportBro Designer 2 | 3 | A javascript plugin to create PDF and XLSX report templates. 4 | 5 | ReportBro Designer can be easily integrated into your web application. Everyone can design & edit document templates, and preview them directly in the browser. The reports can be generated with 6 | [ReportBro Lib](https://github.com/jobsta/reportbro-lib) (a Python package) on the server. 7 | 8 | See the ReportBro project website on https://www.reportbro.com for full documentation and demos. 9 | 10 |

11 | ReportBro Designer in action 12 |

13 | 14 | ## Installation 15 | 16 | + Download ReportBro Designer (`reportbro-designer-.tgz`) from https://github.com/jobsta/reportbro-designer/releases 17 | + Extract .tgz file and move `dist` folder to your application 18 | + In your .html document reference reportbro.js and reportbro.css. See basic usage below. 19 | 20 | ### Install via npm: 21 | 22 | `npm install reportbro-designer --save` 23 | 24 | ## Basic usage 25 | 26 | Go to the [docs](https://www.reportbro.com/framework/api) for more information. There are demos for different use cases available at: https://www.reportbro.com/demo/invoice. 27 | 28 | Include the ReportBro JavaScript file as well as the ReportBro stylesheet in the `` of your page. 29 | 30 | ```html 31 | 32 | 33 | ``` 34 | 35 | Place an empty div within the `` of a web page: 36 | ```html 37 |
38 | ``` 39 | 40 | Initialize ReportBro: 41 | ```html 42 | 45 | ```` 46 | 47 | ## Build 48 | 49 | ### prerequisites: 50 | 51 | Install Node.js and npm. 52 | 53 | Troubleshooting for Ubuntu/Linux: If you get an error like "/usr/bin/env: node: No such file or directory" you can easily fix it with a symbolic link: 54 | 55 | `ln -s /usr/bin/nodejs /usr/bin/node` 56 | 57 | Go to reportbro-designer root directory and install node modules: 58 | 59 | `npm install` 60 | 61 | ### development: 62 | 63 | `npm run build` 64 | 65 | This is a fast way to build ReportBro Designer and easily debug the code. You can use the generated reportbro.js file from the dist folder in any modern browser supporting ES6 (ECMAScript 2015). 66 | 67 | ### production: 68 | 69 | `npm run build-prod` 70 | 71 | Transpiles javascript code from ES6 to ES5 to support older browsers and minifies the generated js file. Use this build step to generate ReportBro Designer for production environment. 72 | 73 | ## Notes 74 | 75 | ### Running demos from local filesystem 76 | 77 | You need to run 78 | 79 | `npm run build-prod` 80 | 81 | at least once before starting any local demos. This build step creates ReportBro Designer in the `dist` directory which is referenced in the demos. 82 | 83 | ## License 84 | 85 | ### Commercial license 86 | 87 | If you want to use ReportBro to develop commercial applications and projects, the Commercial license is the appropriate license. With this license, your source code is kept proprietary. Purchase a ReportBro Commercial license at https://www.reportbro.com/framework/license 88 | 89 | This license includes ReportBro PLUS with additional features. 90 | 91 | ### Open-source license 92 | 93 | If you are creating an open-source application under a license compatible with the [GNU AGPL license v3](https://www.gnu.org/licenses/agpl-3.0.html), you may use ReportBro under the terms of the AGPLv3. 94 | 95 | Read more about ReportBro's license options at https://www.reportbro.com/framework/license. 96 | -------------------------------------------------------------------------------- /demos/base.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | margin: 0; 3 | padding: 0; 4 | } -------------------------------------------------------------------------------- /demos/custom_save.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ReportBro Demo 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /demos/default.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ReportBro Demo 5 | 6 | 7 | 8 | 9 | 10 | 20 | 21 | 22 |
23 | 24 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /demos/fonts/lobster.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobsta/reportbro-designer/7f482651dd25bc6bb968c0402df6c492d03ecde1/demos/fonts/lobster.ttf -------------------------------------------------------------------------------- /demos/fonts/tangerine.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobsta/reportbro-designer/7f482651dd25bc6bb968c0402df6c492d03ecde1/demos/fonts/tangerine.ttf -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reportbro-designer", 3 | "version": "3.10.0", 4 | "description": "Designer to create pdf and excel report layouts. The reports can be generated with reportbro-lib (a Python package) on the server.", 5 | "keywords": [ 6 | "report", 7 | "reporting", 8 | "designer", 9 | "gui", 10 | "pdf", 11 | "document", 12 | "excel", 13 | "xlsx" 14 | ], 15 | "homepage": "https://www.reportbro.com", 16 | "license": "AGPL-3.0", 17 | "author": { 18 | "name": "jobsta", 19 | "email": "alex@reportbro.com" 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "http://github.com/jobsta/reportbro-designer" 24 | }, 25 | "files": [ 26 | "dist", 27 | "src" 28 | ], 29 | "main": "dist/reportbro.js", 30 | "scripts": { 31 | "build": "webpack", 32 | "build-prod": "webpack --config webpack.config.prod.js" 33 | }, 34 | "dependencies": { 35 | "@babel/preset-env": "^7.17.12", 36 | "@babel/runtime": "^7.17.9", 37 | "autosize": "^5.0.1", 38 | "jsbarcode": "^3.11.5", 39 | "qrcode": "^1.4.4", 40 | "quill": "1.3.7" 41 | }, 42 | "devDependencies": { 43 | "@babel/cli": "^7.17.10", 44 | "@babel/core": "^7.17.12", 45 | "@babel/plugin-transform-runtime": "^7.17.12", 46 | "babel-loader": "^8.2.5", 47 | "babelify": "^10.0.0", 48 | "browserify": "^17.0.0", 49 | "copy-webpack-plugin": "^9.0.0", 50 | "css-loader": "^6.7.1", 51 | "file-loader": "^6.2.0", 52 | "mini-css-extract-plugin": "^2.6.0", 53 | "style-loader": "^3.1.1", 54 | "webpack": "^5.72.1", 55 | "webpack-cli": "^4.9.2", 56 | "webpack-merge": "^5.10.0" 57 | }, 58 | "optionalDependencies": { 59 | "tar-to-zip": "^3.0.0" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/ajaxload.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobsta/reportbro-designer/7f482651dd25bc6bb968c0402df6c492d03ecde1/src/ajaxload.gif -------------------------------------------------------------------------------- /src/commands/AddDeleteDocElementCmd.js: -------------------------------------------------------------------------------- 1 | import Command from './Command'; 2 | import BarCodeElement from '../elements/BarCodeElement'; 3 | import DocElement from '../elements/DocElement'; 4 | import FrameElement from '../elements/FrameElement'; 5 | import ImageElement from '../elements/ImageElement'; 6 | import LineElement from '../elements/LineElement'; 7 | import PageBreakElement from '../elements/PageBreakElement'; 8 | import SectionElement from '../elements/SectionElement'; 9 | import TableElement from '../elements/TableElement'; 10 | import TextElement from '../elements/TextElement'; 11 | import WatermarkImageElement from '../elements/WatermarkImageElement'; 12 | import WatermarkTextElement from '../elements/WatermarkTextElement'; 13 | import MainPanelItem from '../menu/MainPanelItem'; 14 | 15 | /** 16 | * Command to add and delete a doc element. 17 | * @class 18 | */ 19 | export default class AddDeleteDocElementCmd extends Command { 20 | constructor(add, elementType, initialData, id, parentId, position, rb) { 21 | super(); 22 | this.add = add; 23 | this.elementType = elementType; 24 | this.initialData = initialData; 25 | this.parentId = parentId; 26 | this.position = position; 27 | this.rb = rb; 28 | this.id = id; 29 | this.firstExecution = true; 30 | } 31 | 32 | getName() { 33 | if (this.add) { 34 | return 'Add element'; 35 | } else { 36 | return 'Delete element'; 37 | } 38 | } 39 | 40 | do() { 41 | if (this.add) { 42 | this.addElement(); 43 | } else { 44 | this.deleteElement(); 45 | } 46 | this.firstExecution = false; 47 | } 48 | 49 | undo() { 50 | if (this.add) { 51 | this.deleteElement(); 52 | } else { 53 | this.addElement(); 54 | } 55 | } 56 | 57 | addElement() { 58 | let parent = this.rb.getDataObject(this.parentId); 59 | if (parent !== null) { 60 | let element = AddDeleteDocElementCmd.createElement( 61 | this.id, this.initialData, this.elementType, this.position, true, this.rb); 62 | 63 | this.rb.notifyEvent(element, Command.operation.add); 64 | this.rb.selectObject(this.id, true); 65 | 66 | if (this.add && this.firstExecution) { 67 | // in case of add command we serialize initialData on first execution so it contains all data 68 | // created during setup (e.g. ids of table bands and table cells for a table) 69 | this.initialData = element.toJS(); 70 | } 71 | } 72 | } 73 | 74 | deleteElement() { 75 | let element = this.rb.getDataObject(this.id); 76 | if (element !== null) { 77 | this.rb.deleteDocElement(element); 78 | } 79 | } 80 | 81 | static createElement(id, data, elementType, panelPos, openPanelItem, rb) { 82 | let element; 83 | let properties = { draggable: true }; 84 | if (elementType === DocElement.type.text) { 85 | element = new TextElement(id, data, rb); 86 | } else if (elementType === DocElement.type.line) { 87 | element = new LineElement(id, data, rb); 88 | } else if (elementType === DocElement.type.image) { 89 | element = new ImageElement(id, data, rb); 90 | } else if (elementType === DocElement.type.pageBreak) { 91 | element = new PageBreakElement(id, data, rb); 92 | } else if (elementType === DocElement.type.table) { 93 | element = new TableElement(id, data, rb); 94 | properties.hasChildren = true; 95 | } else if (elementType === DocElement.type.frame) { 96 | element = new FrameElement(id, data, rb); 97 | properties.hasChildren = true; 98 | } else if (elementType === DocElement.type.section) { 99 | element = new SectionElement(id, data, rb); 100 | properties.hasChildren = true; 101 | } else if (elementType === DocElement.type.barCode) { 102 | element = new BarCodeElement(id, data, rb); 103 | } else if (elementType === DocElement.type.watermarkText) { 104 | element = new WatermarkTextElement(id, data, rb); 105 | } else if (elementType === DocElement.type.watermarkImage) { 106 | element = new WatermarkImageElement(id, data, rb); 107 | } 108 | rb.addDocElement(element); 109 | let parentPanel = element.getContainer().getPanelItem(); 110 | let panelItem = new MainPanelItem(elementType, parentPanel, element, properties, rb); 111 | element.setPanelItem(panelItem); 112 | parentPanel.insertChild(panelPos, panelItem); 113 | element.setup(openPanelItem); 114 | return element; 115 | } 116 | 117 | /** 118 | * Returns class name. 119 | * This can be useful for introspection when the class names are mangled 120 | * due to the webpack uglification process. 121 | * @returns {string} 122 | */ 123 | getClassName() { 124 | return 'AddDeleteDocElementCmd'; 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/commands/AddDeleteParameterCmd.js: -------------------------------------------------------------------------------- 1 | import Command from './Command'; 2 | import Parameter from '../data/Parameter'; 3 | import MainPanelItem from '../menu/MainPanelItem'; 4 | 5 | /** 6 | * Command to add and delete a parameter. 7 | * @class 8 | */ 9 | export default class AddDeleteParameterCmd extends Command { 10 | constructor(add, initialData, id, parentId, position, rb) { 11 | super(); 12 | this.add = add; 13 | this.initialData = initialData; 14 | this.parentId = parentId; 15 | this.position = position; 16 | this.rb = rb; 17 | this.id = id; 18 | } 19 | 20 | getName() { 21 | if (this.add) { 22 | return 'Add parameter'; 23 | } else { 24 | return 'Delete parameter'; 25 | } 26 | } 27 | 28 | do() { 29 | if (this.add) { 30 | this.addParameter(); 31 | } else { 32 | this.deleteParameter(); 33 | } 34 | } 35 | 36 | undo() { 37 | if (this.add) { 38 | this.deleteParameter(); 39 | } else { 40 | this.addParameter(); 41 | } 42 | } 43 | 44 | addParameter() { 45 | let parent = this.rb.getDataObject(this.parentId); 46 | if (parent !== null) { 47 | let parameter = new Parameter(this.id, this.initialData, this.rb); 48 | this.rb.addParameter(parameter); 49 | const showOnlyNameType = parameter.getValue('showOnlyNameType'); 50 | let panelItem = new MainPanelItem( 51 | 'parameter', parent.getPanelItem(), parameter, { 52 | hasChildren: !showOnlyNameType, showAdd: !showOnlyNameType, showDelete: !showOnlyNameType, 53 | draggable: true }, this.rb); 54 | panelItem.openParentItems(); 55 | parameter.setPanelItem(panelItem); 56 | parent.getPanelItem().insertChild(this.position, panelItem); 57 | parameter.setup(); 58 | this.rb.notifyEvent(parameter, Command.operation.add); 59 | } 60 | } 61 | 62 | deleteParameter() { 63 | let parameter = this.rb.getDataObject(this.id); 64 | if (parameter !== null) { 65 | this.initialData = parameter.toJS(); 66 | this.rb.deleteParameter(parameter); 67 | } 68 | } 69 | 70 | /** 71 | * Returns class name. 72 | * This can be useful for introspection when the class names are mangled 73 | * due to the webpack uglification process. 74 | * @returns {string} 75 | */ 76 | getClassName() { 77 | return 'AddDeleteParameterCmd'; 78 | } 79 | } -------------------------------------------------------------------------------- /src/commands/AddDeleteStyleCmd.js: -------------------------------------------------------------------------------- 1 | import Command from './Command'; 2 | import Style from '../data/Style'; 3 | import MainPanelItem from '../menu/MainPanelItem'; 4 | 5 | /** 6 | * Command to add and delete a style. 7 | * @class 8 | */ 9 | export default class AddDeleteStyleCmd extends Command { 10 | constructor(add, initialData, id, parentId, position, rb) { 11 | super(); 12 | this.add = add; 13 | this.initialData = initialData; 14 | this.parentId = parentId; 15 | this.position = position; 16 | this.rb = rb; 17 | this.id = id; 18 | } 19 | 20 | getName() { 21 | if (this.add) { 22 | return 'Add style'; 23 | } else { 24 | return 'Delete style'; 25 | } 26 | } 27 | 28 | do() { 29 | if (this.add) { 30 | this.addStyle(); 31 | } else { 32 | this.deleteStyle(); 33 | } 34 | } 35 | 36 | undo() { 37 | if (this.add) { 38 | this.deleteStyle(); 39 | } else { 40 | this.addStyle(); 41 | } 42 | } 43 | 44 | addStyle() { 45 | let parent = this.rb.getDataObject(this.parentId); 46 | if (parent !== null) { 47 | let style = new Style(this.id, this.initialData, this.rb); 48 | let panelItem = new MainPanelItem('style', parent.getPanelItem(), style, { draggable: true }, this.rb); 49 | panelItem.openParentItems(); 50 | style.setPanelItem(panelItem); 51 | parent.getPanelItem().insertChild(this.position, panelItem); 52 | this.rb.addStyle(style); 53 | } 54 | } 55 | 56 | deleteStyle() { 57 | let style = this.rb.getDataObject(this.id); 58 | if (style !== null) { 59 | this.initialData = style.toJS(); 60 | this.rb.deleteStyle(style); 61 | } 62 | } 63 | 64 | /** 65 | * Returns class name. 66 | * This can be useful for introspection when the class names are mangled 67 | * due to the webpack uglification process. 68 | * @returns {string} 69 | */ 70 | getClassName() { 71 | return 'AddDeleteStyleCmd'; 72 | } 73 | } -------------------------------------------------------------------------------- /src/commands/Command.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Base class for all commands. 3 | * @class 4 | */ 5 | export default class Command { 6 | constructor() { 7 | } 8 | 9 | getName() {} 10 | do() {} 11 | undo() {} 12 | 13 | /** 14 | * Returns true if the command can replace the given other command. 15 | * @param {Command} otherCmd 16 | * @returns {boolean} 17 | */ 18 | allowReplace(otherCmd) { 19 | return false; 20 | } 21 | 22 | /** 23 | * Must be called when the command replaces the other command. 24 | 25 | * This must only be called if allowReplace for the same command returned true. 26 | * @param {Command} otherCmd 27 | */ 28 | replace(otherCmd) { 29 | } 30 | } 31 | 32 | Command.operation = { 33 | rename: 'rename', 34 | change: 'change', 35 | add: 'add', 36 | remove: 'remove', 37 | move: 'move' 38 | } -------------------------------------------------------------------------------- /src/commands/CommandGroupCmd.js: -------------------------------------------------------------------------------- 1 | import Command from './Command'; 2 | import SetValueCmd from './SetValueCmd'; 3 | 4 | /** 5 | * Command container for multiple commands. All commands of this container will be executed 6 | * in a single do/undo operation. 7 | * @class 8 | */ 9 | export default class CommandGroupCmd extends Command { 10 | constructor(name, rb) { 11 | super(); 12 | this.name = name; 13 | this.rb = rb; 14 | this.commands = []; 15 | this.selectObjectIds = []; 16 | // command index in commands list for each entry in selectObjectIds 17 | this.selectionCmdIndex = []; 18 | } 19 | 20 | getName() { 21 | return this.name; 22 | } 23 | 24 | do() { 25 | if (this.selectionCmdIndex.length > 0) { 26 | // enable notifyEvent only for SetValue commands of last selected object. 27 | // the change event may only be fired for the last object because in between command execution 28 | // the objects contain different values (although they will be changed to the same value 29 | // with the last command) and this can lead to reseting the cursor caret in an input field 30 | // if the cursor is not at the end of the input text. 31 | let lastSelectionCmdIndex = this.selectionCmdIndex[this.selectionCmdIndex.length - 1]; 32 | for (let i=0; i < this.commands.length; i++) { 33 | let cmd = this.commands[i]; 34 | if (cmd instanceof SetValueCmd) { 35 | cmd.setNotifyChange(i >= lastSelectionCmdIndex); 36 | } 37 | } 38 | } 39 | 40 | for (let i=0; i < this.commands.length; i++) { 41 | this.commands[i].do(); 42 | } 43 | this.selectObjects(); 44 | } 45 | 46 | undo() { 47 | if (this.selectionCmdIndex.length > 0) { 48 | // enable notifyEvent only for SetValue commands of last selected object. 49 | // the change event may only be fired for the last object because in between command execution 50 | // the objects contain different values (although they will be changed to the same value 51 | // with the last command) and this can lead to reseting the cursor caret in an input field 52 | // if the cursor is not at the end of the input text. 53 | let secondSelectionCmdIndex = this.selectionCmdIndex.length > 1 ? 54 | this.selectionCmdIndex[1] : this.commands.length; 55 | for (let i=this.commands.length - 1; i >= 0; i--) { 56 | let cmd = this.commands[i]; 57 | if (cmd instanceof SetValueCmd) { 58 | cmd.setNotifyChange(i < secondSelectionCmdIndex); 59 | } 60 | } 61 | } 62 | 63 | for (let i=this.commands.length - 1; i >= 0; i--) { 64 | this.commands[i].undo(); 65 | } 66 | this.selectObjects(); 67 | } 68 | 69 | addCommand(cmd) { 70 | if (cmd instanceof SetValueCmd) { 71 | // disable select for specific command, selection is handled in command group 72 | // when the commands are executed 73 | cmd.disableSelect(); 74 | } 75 | this.commands.push(cmd); 76 | } 77 | 78 | /** 79 | * Add id of object which should be selected when this command group is executed. 80 | * @param {Number} objId - object id 81 | */ 82 | addSelection(objId) { 83 | if (this.selectObjectIds.indexOf(objId) === -1) { 84 | this.selectObjectIds.push(objId); 85 | } 86 | // notification of change event will only be enabled for commands after 87 | // the last selection 88 | this.selectionCmdIndex.push(this.commands.length); 89 | } 90 | 91 | isEmpty() { 92 | return this.commands.length === 0; 93 | } 94 | 95 | getCommands() { 96 | return this.commands; 97 | } 98 | 99 | selectObjects() { 100 | let allObjectsSelected = true; 101 | for (let objId of this.selectObjectIds) { 102 | if (!this.rb.isSelectedObject(objId)) { 103 | allObjectsSelected = false; 104 | break; 105 | } 106 | } 107 | if (!allObjectsSelected) { 108 | // only select objects if at least one object is not already selected 109 | let firstSelection = true; 110 | for (let objId of this.selectObjectIds) { 111 | this.rb.selectObject(objId, firstSelection); 112 | firstSelection = false; 113 | } 114 | } 115 | } 116 | 117 | /** 118 | * Returns true if the command can replace the given other command. 119 | * 120 | * This information can be useful to avoid separate commands for every keystroke 121 | * in a text field and generate just one command for the whole changed text instead. 122 | * @param {Command} otherCmd 123 | * @returns {boolean} 124 | */ 125 | allowReplace(otherCmd) { 126 | if (otherCmd instanceof CommandGroupCmd) { 127 | let otherCommands = otherCmd.getCommands(); 128 | if (this.commands.length === otherCommands.length) { 129 | for (let i=0; i < this.commands.length; i++) { 130 | if (!this.commands[i].allowReplace(otherCommands[i])) { 131 | return false; 132 | } 133 | } 134 | // we are allowed to replace all commands of the command group 135 | return true; 136 | } 137 | } 138 | return false; 139 | } 140 | 141 | /** 142 | * Must be called when the command replaces the other command. 143 | 144 | * This must only be called if allowReplace for the same command returned true. 145 | * @param {Command} otherCmd 146 | */ 147 | replace(otherCmd) { 148 | let otherCommands = otherCmd.getCommands(); 149 | for (let i=0; i < this.commands.length; i++) { 150 | this.commands[i].replace(otherCommands[i]); 151 | } 152 | } 153 | 154 | /** 155 | * Returns class name. 156 | * This can be useful for introspection when the class names are mangled 157 | * due to the webpack uglification process. 158 | * @returns {string} 159 | */ 160 | getClassName() { 161 | return 'CommandGroupCmd'; 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/commands/MovePanelItemCmd.js: -------------------------------------------------------------------------------- 1 | import Command from './Command'; 2 | import DocElement from '../elements/DocElement'; 3 | 4 | /** 5 | * Command to move a menu panel item. In case the item is moved to a different container 6 | * (e.g. from content to header band) the corresponding doc element is moved to the new container as well. 7 | * @class 8 | */ 9 | export default class MovePanelItemCmd extends Command { 10 | constructor(panelItem, moveToParentPanel, moveToPosition, rb) { 11 | super(); 12 | this.objId = panelItem.getId(); 13 | this.moveToParentId = moveToParentPanel.getId(); 14 | this.moveToPosition = moveToPosition; 15 | this.oldParentId = panelItem.getParent().getId(); 16 | this.oldPosition = panelItem.getSiblingPosition(); 17 | this.oldContainerId = null; 18 | this.moveToContainerId = null; 19 | if (panelItem.getData() instanceof DocElement) { 20 | let docElement = panelItem.getData(); 21 | this.oldContainerId = docElement.getValue('containerId'); 22 | let moveToContainer = rb.getMainPanel().getContainerByItem(moveToParentPanel); 23 | if (moveToContainer !== null) { 24 | this.moveToContainerId = moveToContainer.getId(); 25 | } 26 | } 27 | this.rb = rb; 28 | } 29 | 30 | getName() { 31 | return 'Move panel item'; 32 | } 33 | 34 | do() { 35 | let pos = this.moveToPosition; 36 | if (this.moveToParentId === this.oldParentId && this.oldPosition < pos) { 37 | pos--; 38 | } 39 | this.moveTo( 40 | this.moveToParentId, pos, 41 | (this.moveToContainerId !== this.oldContainerId) ? this.moveToContainerId : null); 42 | } 43 | 44 | undo() { 45 | this.moveTo( 46 | this.oldParentId, this.oldPosition, 47 | (this.moveToContainerId !== this.oldContainerId) ? this.oldContainerId : null); 48 | } 49 | 50 | moveTo(toParentId, toPosition, toContainerId) { 51 | let obj = this.rb.getDataObject(this.objId); 52 | let parent = this.rb.getDataObject(toParentId); 53 | if (obj !== null && parent !== null) { 54 | obj.getPanelItem().moveToPosition(parent.getPanelItem(), toPosition); 55 | obj.getPanelItem().openParentItems(); 56 | this.rb.notifyEvent(obj, Command.operation.move); 57 | } 58 | } 59 | 60 | /** 61 | * Returns class name. 62 | * This can be useful for introspection when the class names are mangled 63 | * due to the webpack uglification process. 64 | * @returns {string} 65 | */ 66 | getClassName() { 67 | return 'MovePanelItemCmd'; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/commands/SetValueCmd.js: -------------------------------------------------------------------------------- 1 | import Command from './Command'; 2 | 3 | /** 4 | * Command to set a single value of a data object. 5 | * @class 6 | */ 7 | export default class SetValueCmd extends Command { 8 | constructor(objId, field, value, type, rb) { 9 | super(); 10 | this.objId = objId; 11 | this.field = field; 12 | this.value = value; 13 | this.type = type; 14 | this.rb = rb; 15 | 16 | let obj = rb.getDataObject(objId); 17 | this.oldValue = obj.getValue(field); 18 | this.firstExecution = true; 19 | this.select = true; 20 | this.notifyChange = true; 21 | } 22 | 23 | getName() { 24 | return 'Set value'; 25 | } 26 | 27 | getObjId() { 28 | return this.objId; 29 | } 30 | 31 | do() { 32 | if (!this.firstExecution && this.select) { 33 | this.rb.selectObject(this.objId, true); 34 | } 35 | this.setValue(this.value); 36 | this.firstExecution = false; 37 | } 38 | 39 | undo() { 40 | if (this.select) { 41 | this.rb.selectObject(this.objId, true); 42 | } 43 | this.setValue(this.oldValue); 44 | } 45 | 46 | setValue(value) { 47 | let obj = this.rb.getDataObject(this.objId); 48 | obj.setValue(this.field, value); 49 | 50 | if (this.field === 'name') { 51 | const elMenuItemName = document.getElementById(`rbro_menu_item_name${this.objId}`); 52 | elMenuItemName.textContent = value; 53 | elMenuItemName.setAttribute('title', value); 54 | this.rb.notifyEvent(obj, Command.operation.rename); 55 | } 56 | // do not send event notification for setting richText value the first time because 57 | // this would loose current status (cursor position) in rich text editor while typing 58 | if (this.notifyChange && (this.type !== SetValueCmd.type.richText || !this.firstExecution)) { 59 | this.rb.notifyEvent(obj, Command.operation.change, this.field); 60 | } 61 | } 62 | 63 | /** 64 | * Disables selection of the element containing the changed field. By default an element is automatically 65 | * selected after one of its fields was changed. 66 | */ 67 | disableSelect() { 68 | this.select = false; 69 | } 70 | 71 | setNotifyChange(notify) { 72 | this.notifyChange = notify; 73 | } 74 | 75 | /** 76 | * Returns true if the command can replace the given other command because they target the same field. 77 | * 78 | * This information can be useful to avoid separate commands for every keystroke 79 | * in a text field and generate just one command for the whole changed text instead. 80 | * @param {Command} otherCmd 81 | * @returns {boolean} 82 | */ 83 | allowReplace(otherCmd) { 84 | return (otherCmd instanceof SetValueCmd && 85 | (this.type === SetValueCmd.type.text || this.type === SetValueCmd.type.richText) && 86 | this.objId === otherCmd.objId && this.field === otherCmd.field); 87 | } 88 | 89 | /** 90 | * Must be called when the command replaces the other command. 91 | 92 | * This must only be called if allowReplace for the same command returned true. 93 | * @param {Command} otherCmd 94 | */ 95 | replace(otherCmd) { 96 | this.oldValue = otherCmd.oldValue; 97 | } 98 | 99 | /** 100 | * Returns class name. 101 | * This can be useful for introspection when the class names are mangled 102 | * due to the webpack uglification process. 103 | * @returns {string} 104 | */ 105 | getClassName() { 106 | return 'SetValueCmd'; 107 | } 108 | } 109 | 110 | SetValueCmd.type = { 111 | text: 'text', 112 | richText: 'richText', 113 | select: 'select', 114 | file: 'file', 115 | filename: 'filename', 116 | checkbox: 'checkbox', 117 | button: 'button', 118 | buttonGroup: 'buttonGroup', // one button inside a group of buttons with only one active button 119 | color: 'color', 120 | internal: 'internal' 121 | }; 122 | -------------------------------------------------------------------------------- /src/container/Band.js: -------------------------------------------------------------------------------- 1 | import Container from './Container'; 2 | import DocElement from '../elements/DocElement'; 3 | import * as utils from '../utils'; 4 | 5 | /** 6 | * Standard band container for header, content and footer band. 7 | * @class 8 | */ 9 | export default class Band extends Container { 10 | constructor(bandType, section, id, name, rb) { 11 | super(id, name, rb); 12 | this.panelItem = null; 13 | this.bandType = bandType; 14 | this.section = section; 15 | if (!section) { 16 | if (bandType === Band.bandType.header) { 17 | this.id = '0_header'; 18 | this.name = rb.getLabel('bandHeader'); 19 | } else if (bandType === Band.bandType.content) { 20 | this.id = '0_content'; 21 | this.name = rb.getLabel('bandContent'); 22 | this.allowAllElements = true; 23 | } else if (bandType === Band.bandType.footer) { 24 | this.id = '0_footer'; 25 | this.name = rb.getLabel('bandFooter'); 26 | } 27 | } 28 | this.el = null; 29 | } 30 | 31 | /** 32 | * Called after initialization is finished. 33 | */ 34 | setup() { 35 | if (!this.section) { 36 | this.el = this.rb.getDocument().getElement(this.bandType); 37 | this.elContent = this.el; 38 | } 39 | } 40 | 41 | /** 42 | * Returns true if the given element type can be added to this container. 43 | * @param {String} elementType 44 | */ 45 | isElementAllowed(elementType) { 46 | if (elementType === DocElement.type.tableText) { 47 | return false; 48 | } 49 | return (this.bandType === Band.bandType.content || 50 | (elementType !== DocElement.type.pageBreak && elementType !== DocElement.type.table && 51 | elementType !== DocElement.type.section)); 52 | } 53 | 54 | /** 55 | * Returns absolute container offset. 56 | * @returns {Object} x and y offset coordinates. 57 | */ 58 | getOffset() { 59 | let y = 0; 60 | if (this.section) { 61 | if (this.owner !== null) { 62 | let absPos = this.owner.getAbsolutePosition(); 63 | y = absPos.y; 64 | } 65 | } else { 66 | let docProperties = this.rb.getDocumentProperties(); 67 | if (this.bandType === Band.bandType.content && docProperties.getValue('header')) { 68 | y = utils.convertInputToNumber(docProperties.getValue('headerSize')); 69 | } else if (this.bandType === Band.bandType.footer) { 70 | y = this.rb.getDocument().getHeight() - 71 | utils.convertInputToNumber(docProperties.getValue('footerSize')); 72 | } 73 | } 74 | return { x: 0, y: y }; 75 | } 76 | 77 | /** 78 | * Returns container size. 79 | * @returns {Object} width and height of container. 80 | */ 81 | getSize() { 82 | let documentProperties = this.rb.getDocumentProperties(); 83 | let width = documentProperties.getValue('width') - 84 | documentProperties.getValue('marginLeftVal') - documentProperties.getValue('marginRightVal'); 85 | let height = 0; 86 | if (this.section) { 87 | if (this.owner !== null) { 88 | height = this.owner.getValue('heightVal'); 89 | } 90 | } else if (this.bandType === Band.bandType.header) { 91 | height = documentProperties.getValue('headerSizeVal'); 92 | } else if (this.bandType === Band.bandType.content) { 93 | height = documentProperties.getValue('height') - documentProperties.getValue('headerSizeVal') - 94 | documentProperties.getValue('footerSizeVal') - 95 | documentProperties.getValue('marginTopVal') - documentProperties.getValue('marginBottomVal'); 96 | } else if (this.bandType === Band.bandType.footer) { 97 | height = documentProperties.getValue('footerSizeVal'); 98 | } 99 | return { width: width, height: height }; 100 | } 101 | 102 | /** 103 | * Returns container content size. Same as container size. 104 | * @returns {Object} width and height of container. 105 | */ 106 | getContentSize() { 107 | return this.getSize(); 108 | } 109 | 110 | isInside(posX, posY) { 111 | if (this.section && this.owner !== null && this.owner && !this.owner.isVisible()) { 112 | return false; 113 | } 114 | return super.isInside(posX, posY); 115 | } 116 | } 117 | 118 | Band.bandType = { 119 | header: 'header', 120 | content: 'content', 121 | footer: 'footer' 122 | }; 123 | -------------------------------------------------------------------------------- /src/container/Container.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Container can contain doc elements. The doc elements are always relative to the container offset. 3 | * @class 4 | */ 5 | export default class Container { 6 | constructor(id, name, rb) { 7 | this.rb = rb; 8 | this.id = id; 9 | this.panelItem = null; 10 | this.name = name; 11 | this.el = null; 12 | this.elContent = null; 13 | this.owner = null; 14 | this.level = 0; // number of containers "above" 15 | this.parent = null; // parent container 16 | } 17 | 18 | init(owner) { 19 | this.owner = owner; 20 | this.el = owner.getElement(); 21 | this.elContent = owner.getContentElement(); 22 | this.panelItem = owner.getPanelItem(); 23 | this.parent = owner.getContainer(); 24 | this.initLevel(); 25 | } 26 | 27 | /** 28 | * Set nested level of container. 29 | * Should be alled after initialization and whenever the container is moved to a new parent. 30 | */ 31 | initLevel() { 32 | this.level = 0; 33 | let parent = this.parent; 34 | while (parent !== null) { 35 | this.level++; 36 | parent = parent.getParent(); 37 | } 38 | } 39 | 40 | /** 41 | * Called after initialization is finished. 42 | */ 43 | setup() { 44 | } 45 | 46 | remove() { 47 | } 48 | 49 | appendElement(el) { 50 | if (this.elContent !== null) { 51 | this.elContent.append(el); 52 | } 53 | } 54 | 55 | getId() { 56 | return this.id; 57 | } 58 | 59 | getName() { 60 | return this.name; 61 | } 62 | 63 | getPanelItem() { 64 | return this.panelItem; 65 | } 66 | 67 | setPanelItem(panelItem) { 68 | this.panelItem = panelItem; 69 | } 70 | 71 | getLevel() { 72 | return this.level; 73 | } 74 | 75 | getParent() { 76 | return this.parent; 77 | } 78 | 79 | /** 80 | * Must be called when the container is moved to a new parent 81 | * (i.e. element of container is moved into another container). 82 | * @param {DocElement} parent 83 | */ 84 | setParent(parent) { 85 | this.parent = parent; 86 | // because the parent was changed the container can now have a different container level 87 | this.initLevel(); 88 | } 89 | 90 | /** 91 | * Return true if this container is a child of the given container. 92 | * @param {Container} container 93 | * @return {Boolean} 94 | */ 95 | isChildOf(container) { 96 | let parent = this.getParent(); 97 | while (parent !== null) { 98 | if (parent === container) { 99 | return true; 100 | } 101 | parent = parent.getParent(); 102 | } 103 | return false; 104 | } 105 | 106 | isSelected() { 107 | if (this.owner !== null && this.rb.isSelectedObject(this.owner.getId())) { 108 | return true; 109 | } 110 | return false; 111 | } 112 | 113 | /** 114 | * Returns true if the given element type can be added to this container. 115 | * @param {String} elementType 116 | */ 117 | isElementAllowed(elementType) { 118 | return false; 119 | } 120 | 121 | /** 122 | * Update container style when an element is currently dragged over this container. 123 | */ 124 | dragOver() { 125 | if (this.el !== null) { 126 | this.el.classList.add('rbroElementDragOver'); 127 | } 128 | } 129 | 130 | /** 131 | * Returns absolute container offset. 132 | * @returns {Object} x and y offset coordinates. 133 | */ 134 | getOffset() { 135 | return { x: 0, y: 0 }; 136 | } 137 | 138 | /** 139 | * Returns offset relative to other container. 140 | * @param {Container} otherContainer 141 | * @returns {Object} x and y offset coordinates. 142 | */ 143 | getOffsetTo(otherContainer) { 144 | if (otherContainer !== null && otherContainer !== this) { 145 | const offset = this.getOffset(); 146 | const otherOffset = otherContainer.getOffset(); 147 | return { x: offset.x - otherOffset.x, y: offset.y - otherOffset.y }; 148 | } 149 | return { x: 0, y: 0 }; 150 | } 151 | 152 | /** 153 | * Returns container size. 154 | * @returns {Object} width and height of container. 155 | */ 156 | getSize() { 157 | return { width: 0, height: 0 }; 158 | } 159 | 160 | /** 161 | * Returns container content size. 162 | * @returns {Object} width and height of container content area. 163 | */ 164 | getContentSize() { 165 | return { width: 0, height: 0 }; 166 | } 167 | 168 | /** 169 | * Returns true if given absolute position is inside container. 170 | * @param {Number} posX - absolute x coordinate. 171 | * @param {Number} posY - absolute y coordinate. 172 | */ 173 | isInside(posX, posY) { 174 | const offset = this.getOffset(); 175 | const size = this.getSize(); 176 | posX -= offset.x; 177 | posY -= offset.y; 178 | return (posX >= 0 && posY >= 0 && posX < size.width && posY < size.height); 179 | } 180 | 181 | clearErrors() { 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /src/container/Frame.js: -------------------------------------------------------------------------------- 1 | import Container from './Container'; 2 | import DocElement from '../elements/DocElement'; 3 | 4 | /** 5 | * A frame container which can contain various doc elements. 6 | * @class 7 | */ 8 | export default class Frame extends Container { 9 | constructor(id, name, rb) { 10 | super(id, name, rb); 11 | } 12 | 13 | /** 14 | * Called after initialization is finished. 15 | */ 16 | setup() { 17 | this.el = this.rb.getDocument().getElement(this.band); 18 | } 19 | 20 | /** 21 | * Returns true if the given element type can be added to this container. 22 | * @param {String} elementType 23 | */ 24 | isElementAllowed(elementType) { 25 | return elementType !== DocElement.type.pageBreak && elementType !== DocElement.type.frame && 26 | elementType !== DocElement.type.section; 27 | } 28 | 29 | /** 30 | * Returns absolute container offset. 31 | * @returns {Object} x and y offset coordinates. 32 | */ 33 | getOffset() { 34 | let x = 0, y = 0; 35 | if (this.owner !== null) { 36 | x = this.owner.getValue('xVal'); 37 | y = this.owner.getValue('yVal'); 38 | } 39 | if (this.parent !== null) { 40 | let offset = this.parent.getOffset(); 41 | x += offset.x; 42 | y += offset.y; 43 | } 44 | return { x: x, y: y }; 45 | } 46 | 47 | /** 48 | * Returns container size. 49 | * @returns {Object} width and height of container. 50 | */ 51 | getSize() { 52 | let width = 0, height = 0; 53 | if (this.owner !== null) { 54 | width = this.owner.getValue('widthVal'); 55 | height = this.owner.getValue('heightVal'); 56 | } 57 | return { width: width, height: height }; 58 | } 59 | 60 | /** 61 | * Returns container content size. 62 | * This is the container minus optional borders, thus the available area for 63 | * elements inside the frame. 64 | * @returns {Object} width and height of container content area. 65 | */ 66 | getContentSize() { 67 | let width = 0, height = 0; 68 | if (this.owner !== null) { 69 | width = this.owner.getValue('widthVal'); 70 | height = this.owner.getValue('heightVal'); 71 | let borderWidth = this.owner.getValue('borderWidthVal'); 72 | if (this.owner.getValue('borderLeft')) { 73 | width -= borderWidth; 74 | } 75 | if (this.owner.getValue('borderRight')) { 76 | width -= borderWidth; 77 | } 78 | if (this.owner.getValue('borderTop')) { 79 | height -= borderWidth; 80 | } 81 | if (this.owner.getValue('borderBottom')) { 82 | height -= borderWidth; 83 | } 84 | } 85 | return { width: width, height: height }; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/container/Page.js: -------------------------------------------------------------------------------- 1 | import Container from './Container'; 2 | 3 | /** 4 | * Page container to hold elements for page background, e.g. watermark elements. 5 | * @class 6 | */ 7 | export default class Page extends Container { 8 | constructor(id, name, rb) { 9 | super(id, name, rb); 10 | } 11 | 12 | /** 13 | * Called after initialization is finished. 14 | */ 15 | setup() { 16 | this.el = this.rb.getDocument().getPageElement(); 17 | this.elContent = this.el; 18 | } 19 | 20 | /** 21 | * Returns container size. 22 | * @returns {Object} width and height of container. 23 | */ 24 | getSize() { 25 | const documentProperties = this.rb.getDocumentProperties(); 26 | return { width: documentProperties.getValue('width'), height: documentProperties.getValue('height') }; 27 | } 28 | 29 | /** 30 | * Returns container content size. Same as container size. 31 | * @returns {Object} width and height of container. 32 | */ 33 | getContentSize() { 34 | return this.getSize(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/data/DocumentProperties.js: -------------------------------------------------------------------------------- 1 | import SectionElement from '../elements/SectionElement'; 2 | import * as utils from '../utils'; 3 | 4 | /** 5 | * Data object containing all document properties like page size, margins, etc. 6 | * @class 7 | */ 8 | export default class DocumentProperties { 9 | constructor(rb) { 10 | this.rb = rb; 11 | this.id = '0_document_properties'; 12 | this.panelItem = null; 13 | this.errors = []; 14 | 15 | this.pageFormat = DocumentProperties.pageFormat.A4; 16 | this.pageWidth = ''; 17 | this.pageHeight = ''; 18 | this.unit = DocumentProperties.unit.mm; 19 | this.orientation = DocumentProperties.orientation.portrait; 20 | this.contentHeight = ''; 21 | this.marginLeft = ''; 22 | this.marginLeftVal = 0; 23 | this.marginTop = ''; 24 | this.marginTopVal = 0; 25 | this.marginRight = ''; 26 | this.marginRightVal = 0; 27 | this.marginBottom = ''; 28 | this.marginBottomVal = 0; 29 | 30 | this.header = true; 31 | this.headerSize = '80'; 32 | this.headerDisplay = DocumentProperties.display.always; 33 | this.footer = true; 34 | this.footerSize = '80'; 35 | this.footerDisplay = DocumentProperties.display.always; 36 | 37 | this.headerSizeVal = this.header ? utils.convertInputToNumber(this.headerSize) : 0; 38 | this.footerSizeVal = this.footer ? utils.convertInputToNumber(this.footerSize) : 0; 39 | 40 | this.watermark = false; 41 | 42 | this.patternLocale = rb.getProperty('patternLocale'); 43 | this.patternCurrencySymbol = rb.getProperty('patternCurrencySymbol'); 44 | this.patternNumberGroupSymbol = rb.getProperty('patternNumberGroupSymbol'); 45 | 46 | // width and height in pixel 47 | this.width = 0; 48 | this.height = 0; 49 | } 50 | 51 | setInitialData(initialData) { 52 | for (let key in initialData) { 53 | if (initialData.hasOwnProperty(key) && this.hasOwnProperty(key)) { 54 | this[key] = initialData[key]; 55 | } 56 | } 57 | this.headerSizeVal = this.header ? utils.convertInputToNumber(this.headerSize) : 0; 58 | this.footerSizeVal = this.footer ? utils.convertInputToNumber(this.footerSize) : 0; 59 | this.marginLeftVal = utils.convertInputToNumber(this.marginLeft); 60 | this.marginTopVal = utils.convertInputToNumber(this.marginTop); 61 | this.marginRightVal = utils.convertInputToNumber(this.marginRight); 62 | this.marginBottomVal = utils.convertInputToNumber(this.marginBottom); 63 | } 64 | 65 | /** 66 | * Called after initialization is finished. 67 | */ 68 | setup() { 69 | let size = this.getPageSize(); 70 | this.updatePageSize(size); 71 | this.rb.getDocument().updatePageMargins(); 72 | this.rb.getDocument().updateHeader(); 73 | this.rb.getDocument().updateFooter(); 74 | this.updateHeader(); 75 | this.updateFooter(); 76 | this.updateWatermark(); 77 | } 78 | 79 | /** 80 | * Returns all data fields of this object. The fields are used when serializing the object. 81 | * @returns {String[]} 82 | */ 83 | getFields() { 84 | return [ 85 | 'pageFormat', 'pageWidth', 'pageHeight', 'unit', 'orientation', 86 | 'contentHeight', 'marginLeft', 'marginTop', 'marginRight', 'marginBottom', 87 | 'header', 'headerSize', 'headerDisplay', 'footer', 'footerSize', 'footerDisplay', 88 | 'watermark', 'patternLocale', 'patternCurrencySymbol', 'patternNumberGroupSymbol', 89 | ]; 90 | } 91 | 92 | /** 93 | * Returns all fields of this object that can be modified in the properties panel. 94 | * @returns {String[]} 95 | */ 96 | getProperties() { 97 | return this.getFields(); 98 | } 99 | 100 | 101 | getId() { 102 | return this.id; 103 | } 104 | 105 | getName() { 106 | return this.rb.getLabel('documentProperties'); 107 | } 108 | 109 | getPanelItem() { 110 | return this.panelItem; 111 | } 112 | 113 | setPanelItem(panelItem) { 114 | this.panelItem = panelItem; 115 | } 116 | 117 | getValue(field) { 118 | return this[field]; 119 | } 120 | 121 | setValue(field, value) { 122 | this[field] = value; 123 | if (field === 'marginLeft' || field === 'marginTop' || field === 'marginRight' || field === 'marginBottom') { 124 | this[field + 'Val'] = utils.convertInputToNumber(value); 125 | this.rb.getDocument().updatePageMargins(); 126 | this.rb.getDocument().updateHeader(); 127 | this.rb.getDocument().updateFooter(); 128 | } else if (field === 'header') { 129 | this.updateHeader(); 130 | } else if (field === 'footer') { 131 | this.updateFooter(); 132 | } else if (field === 'watermark') { 133 | this.updateWatermark(); 134 | } 135 | 136 | if (field === 'header' || field === 'headerSize') { 137 | this.rb.getDocument().updateHeader(); 138 | this.headerSizeVal = this.header ? utils.convertInputToNumber(this.headerSize) : 0; 139 | } else if (field === 'footer' || field === 'footerSize') { 140 | this.rb.getDocument().updateFooter(); 141 | this.footerSizeVal = this.footer ? utils.convertInputToNumber(this.footerSize) : 0; 142 | } else if (field === 'pageFormat' ||field === 'pageWidth' || field === 'pageHeight' || field === 'unit' || 143 | field === 'orientation' || field === 'contentHeight' || 144 | field === 'marginTop' || field === 'marginBottom') { 145 | let size = this.getPageSize(); 146 | this.updatePageSize(size); 147 | } 148 | } 149 | 150 | /** 151 | * Returns value to use for updating input control. 152 | * Can be overridden in case update value can be different from internal value, e.g. 153 | * width for table cells with colspan > 1. 154 | * @param {String} field - field name. 155 | * @param {String} value - value for update. 156 | */ 157 | getUpdateValue(field, value) { 158 | return value; 159 | } 160 | 161 | updatePageSize(size) { 162 | this.width = size.width; 163 | this.height = size.height; 164 | this.rb.getDocument().updatePageSize(size.width, size.height); 165 | 166 | // update width of all elements which cover full width 167 | let docElements = this.rb.getDocElements(true); 168 | for (let docElement of docElements) { 169 | if (docElement instanceof SectionElement) { 170 | docElement.setWidth(size.width); 171 | } 172 | } 173 | this.rb.getDocument().pageSizeChanged(); 174 | } 175 | 176 | updateHeader() { 177 | if (this.header) { 178 | this.rb.getMainPanel().showHeader(); 179 | } else { 180 | this.rb.getMainPanel().hideHeader(); 181 | } 182 | } 183 | 184 | updateFooter() { 185 | if (this.footer) { 186 | this.rb.getMainPanel().showFooter(); 187 | } else { 188 | this.rb.getMainPanel().hideFooter(); 189 | } 190 | } 191 | 192 | updateWatermark() { 193 | if (this.watermark) { 194 | this.rb.getMainPanel().showWatermarks(); 195 | } else { 196 | this.rb.getMainPanel().hideWatermarks(); 197 | } 198 | } 199 | 200 | /** 201 | * Returns page size in pixels at 72 dpi. 202 | * @returns {Object} width, height 203 | */ 204 | getPageSize() { 205 | let pageWidth; 206 | let pageHeight; 207 | let unit; 208 | let dpi = 72; 209 | if (this.pageFormat === DocumentProperties.pageFormat.A4) { 210 | if (this.orientation === DocumentProperties.orientation.portrait) { 211 | pageWidth = 210; 212 | pageHeight = 297; 213 | } else { 214 | pageWidth = 297; 215 | pageHeight = 210; 216 | } 217 | unit = DocumentProperties.unit.mm; 218 | } else if (this.pageFormat === DocumentProperties.pageFormat.A5) { 219 | if (this.orientation === DocumentProperties.orientation.portrait) { 220 | pageWidth = 148; 221 | pageHeight = 210; 222 | } else { 223 | pageWidth = 210; 224 | pageHeight = 148; 225 | } 226 | unit = DocumentProperties.unit.mm; 227 | } else if (this.pageFormat === DocumentProperties.pageFormat.letter) { 228 | if (this.orientation === DocumentProperties.orientation.portrait) { 229 | pageWidth = 8.5; 230 | pageHeight = 11; 231 | } else { 232 | pageWidth = 11; 233 | pageHeight = 8.5; 234 | } 235 | unit = DocumentProperties.unit.inch; 236 | } else { 237 | pageWidth = utils.convertInputToNumber(this.pageWidth); 238 | pageHeight = utils.convertInputToNumber(this.pageHeight); 239 | unit = this.unit; 240 | } 241 | if (unit === DocumentProperties.unit.mm) { 242 | pageWidth = Math.round((dpi * pageWidth) / 25.4); 243 | pageHeight = Math.round((dpi * pageHeight) / 25.4); 244 | } else { 245 | pageWidth = Math.round(dpi * pageWidth); 246 | pageHeight = Math.round(dpi * pageHeight); 247 | } 248 | if (this.contentHeight.trim() !== '') { 249 | pageHeight = utils.convertInputToNumber(this.contentHeight) + 250 | this.marginTopVal + this.marginBottomVal + this.headerSizeVal + this.footerSizeVal; 251 | } 252 | return { width: pageWidth, height: pageHeight }; 253 | } 254 | 255 | /** 256 | * Returns size of content band without any margins. 257 | * @returns {Object} width, height 258 | */ 259 | getContentSize() { 260 | let size = this.getPageSize(); 261 | let height; 262 | if (this.contentHeight.trim() !== '') { 263 | height = utils.convertInputToNumber(this.contentHeight); 264 | } else { 265 | height = size.height - this.marginTopVal - this.marginBottomVal - 266 | this.headerSizeVal - this.footerSizeVal; 267 | } 268 | return { width: size.width - this.marginLeftVal - this.marginRightVal, 269 | height: height }; 270 | } 271 | 272 | addError(error) { 273 | this.errors.push(error); 274 | } 275 | 276 | clearErrors() { 277 | this.errors = []; 278 | } 279 | 280 | getErrors() { 281 | return this.errors; 282 | } 283 | 284 | remove() { 285 | } 286 | 287 | select() { 288 | } 289 | 290 | deselect() { 291 | } 292 | 293 | toJS() { 294 | let ret = {}; 295 | for (let field of this.getFields()) { 296 | ret[field] = this.getValue(field); 297 | } 298 | return ret; 299 | } 300 | 301 | /** 302 | * Returns class name. 303 | * This can be useful for introspection when the class names are mangled 304 | * due to the webpack uglification process. 305 | * @returns {string} 306 | */ 307 | getClassName() { 308 | return 'DocumentProperties'; 309 | } 310 | } 311 | 312 | DocumentProperties.outputFormat = { 313 | pdf: 'pdf', 314 | xlsx: 'xlsx' 315 | }; 316 | 317 | DocumentProperties.pageFormat = { 318 | A4: 'A4', 319 | A5: 'A5', 320 | letter: 'letter', // 215.9 x 279.4 mm 321 | userDefined: 'user_defined' 322 | }; 323 | 324 | DocumentProperties.unit = { 325 | mm: 'mm', 326 | inch: 'inch' 327 | }; 328 | 329 | DocumentProperties.orientation = { 330 | portrait: 'portrait', 331 | landscape: 'landscape' 332 | }; 333 | 334 | DocumentProperties.display = { 335 | always: 'always', 336 | notOnFirstPage: 'not_on_first_page' 337 | }; 338 | -------------------------------------------------------------------------------- /src/data/Style.js: -------------------------------------------------------------------------------- 1 | import AddDeleteStyleCmd from "../commands/AddDeleteStyleCmd"; 2 | import Command from "../commands/Command"; 3 | import SetValueCmd from "../commands/SetValueCmd"; 4 | import DocElement from "../elements/DocElement"; 5 | import * as utils from "../utils"; 6 | 7 | /** 8 | * Style data object. Contains all text styles (alignment, border, etc.): 9 | * @class 10 | */ 11 | export default class Style { 12 | constructor(id, initialData, rb) { 13 | this.rb = rb; 14 | this.id = id; 15 | this.name = rb.getLabel('style'); 16 | this.panelItem = null; 17 | this.errors = []; 18 | 19 | this.type = Style.type.text; 20 | this.bold = false; 21 | this.italic = false; 22 | this.underline = false; 23 | this.strikethrough = false; 24 | this.horizontalAlignment = Style.alignment.left; 25 | this.verticalAlignment = Style.alignment.top; 26 | this.color = '#000000'; 27 | this.textColor = '#000000'; 28 | this.backgroundColor = ''; 29 | this.alternateBackgroundColor = ''; 30 | this.font = rb.getProperty('defaultFont'); 31 | this.fontSize = 12; 32 | this.lineSpacing = 1; 33 | this.border = 'grid'; 34 | this.borderColor = '#000000'; 35 | this.borderWidth = '1'; 36 | this.borderAll = false; 37 | this.borderLeft = false; 38 | this.borderTop = false; 39 | this.borderRight = false; 40 | this.borderBottom = false; 41 | this.paddingLeft = ''; 42 | this.paddingTop = ''; 43 | this.paddingRight = ''; 44 | this.paddingBottom = ''; 45 | 46 | this.borderWidthVal = 0; 47 | 48 | this.setInitialData(initialData); 49 | } 50 | 51 | setInitialData(initialData) { 52 | for (let key in initialData) { 53 | if (initialData.hasOwnProperty(key) && this.hasOwnProperty(key)) { 54 | this[key] = initialData[key]; 55 | } 56 | } 57 | this.borderWidthVal = utils.convertInputToNumber(this.borderWidth); 58 | } 59 | 60 | /** 61 | * Returns all data fields of this object. The fields are used when serializing the object. 62 | * @returns {String[]} 63 | */ 64 | getFields() { 65 | const fields = this.getProperties(); 66 | fields.splice(0, 0, 'id'); 67 | return fields; 68 | } 69 | 70 | /** 71 | * Returns all fields of this object that can be modified in the properties panel. 72 | * @returns {String[]} 73 | */ 74 | getProperties() { 75 | return [ 76 | 'name', 'type', 'bold', 'italic', 'underline', 'strikethrough', 77 | 'horizontalAlignment', 'verticalAlignment', 78 | 'color', 'textColor', 'backgroundColor', 'alternateBackgroundColor', 79 | 'font', 'fontSize', 'lineSpacing', 'borderColor', 'borderWidth', 80 | 'borderAll', 'borderLeft', 'borderTop', 'borderRight', 'borderBottom', 'border', 81 | 'paddingLeft', 'paddingTop', 'paddingRight', 'paddingBottom' 82 | ]; 83 | } 84 | 85 | /** 86 | * Returns all fields of this object that are style properties which are also available . 87 | * @returns {String[]} 88 | */ 89 | getStyleProperties() { 90 | // get all properties except name and type 91 | return this.getProperties().slice(2); 92 | } 93 | 94 | getId() { 95 | return this.id; 96 | } 97 | 98 | getName() { 99 | return this.name; 100 | } 101 | 102 | getPanelItem() { 103 | return this.panelItem; 104 | } 105 | 106 | setPanelItem(panelItem) { 107 | this.panelItem = panelItem; 108 | } 109 | 110 | getValue(field) { 111 | return this[field]; 112 | } 113 | 114 | setValue(field, value) { 115 | this[field] = value; 116 | 117 | if (field.indexOf('border') !== -1) { 118 | if (field === 'borderWidth') { 119 | this.borderWidthVal = utils.convertInputToNumber(value); 120 | } 121 | Style.setBorderValue(this, field, '', value, this.rb); 122 | } 123 | 124 | if (field !== 'name') { 125 | for (let docElement of this.rb.getDocElements(true)) { 126 | docElement.updateChangedStyle(this.getId()); 127 | } 128 | } 129 | } 130 | 131 | /** 132 | * Returns value to use for updating input control. 133 | * Can be overridden in case update value can be different from internal value, e.g. 134 | * width for table cells with colspan > 1. 135 | * @param {String} field - field name. 136 | * @param {String} value - value for update. 137 | */ 138 | getUpdateValue(field, value) { 139 | return value; 140 | } 141 | 142 | /** 143 | * Adds commands to command group parameter to set changed property value 144 | * for all document elements using this style. 145 | * 146 | * This should be called when a property of this style was changed so the property 147 | * will be updated for all document elements as well. 148 | * 149 | * @param {String} field - changed field of this style. 150 | * @param {Object} value - new value for given field. 151 | * @param {String} type - property type for SetValueCmd. 152 | * @param {CommandGroupCmd} cmdGroup - commands will be added to this command group. 153 | */ 154 | addCommandsForChangedProperty(field, value, type, cmdGroup) { 155 | let strId = '' + this.getId(); 156 | for (let docElement of this.rb.getDocElements(true)) { 157 | if (docElement.hasProperty('styleId')) { 158 | if (docElement.getValue('styleId') === strId && 159 | docElement.getValue(field) !== value) { 160 | let cmd = new SetValueCmd( 161 | docElement.getId(), field, value, type, this.rb); 162 | cmd.disableSelect(); 163 | cmdGroup.addCommand(cmd); 164 | } 165 | if (docElement.getValue('cs_styleId') === strId && 166 | docElement.getValue('cs_' + field) !== value) { 167 | let cmd = new SetValueCmd( 168 | docElement.getId(), 'cs_' + field, value, type, this.rb); 169 | cmd.disableSelect(); 170 | cmdGroup.addCommand(cmd); 171 | } 172 | } 173 | } 174 | } 175 | 176 | /** 177 | * Adds commands to command group parameter to delete this style and reset any references to it. 178 | * @param {CommandGroupCmd} cmdGroup - commands for deletion of style will be added to this command group. 179 | */ 180 | addCommandsForDelete(cmdGroup) { 181 | let cmd; 182 | let elements = this.rb.getDocElements(true); 183 | for (let element of elements) { 184 | if ((element.getElementType() === DocElement.type.text || 185 | element.getElementType() === DocElement.type.tableText) && element.getValue('styleId') && 186 | utils.convertInputToNumber(element.getValue('styleId')) === this.id) { 187 | cmd = new SetValueCmd( 188 | element.getId(), 'styleId', '', SetValueCmd.type.text, this.rb); 189 | cmdGroup.addCommand(cmd); 190 | } 191 | } 192 | cmd = new AddDeleteStyleCmd( 193 | false, this.toJS(), this.getId(), this.getPanelItem().getParent().getId(), 194 | this.getPanelItem().getSiblingPosition(), this.rb); 195 | cmdGroup.addCommand(cmd); 196 | } 197 | 198 | addError(error) { 199 | this.errors.push(error); 200 | } 201 | 202 | clearErrors() { 203 | this.errors = []; 204 | } 205 | 206 | getErrors() { 207 | return this.errors; 208 | } 209 | 210 | remove() { 211 | } 212 | 213 | select() { 214 | } 215 | 216 | deselect() { 217 | } 218 | 219 | toJS() { 220 | let ret = {}; 221 | for (let field of this.getFields()) { 222 | ret[field] = this.getValue(field); 223 | } 224 | return ret; 225 | } 226 | 227 | /** 228 | * Updates GUI for border settings and borderAll setting of object. 229 | * @param {Object} obj - document element of which the border settings will be updated. 230 | * @param {String} field - border field which was modified. 231 | * @param {String} fieldPrefix - prefix of field to reuse style settings for different 232 | * sections (e.g. for conditional style). 233 | * @param {Boolean} value - new value for specified field. 234 | * @param {ReportBro} rb - ReportBro instance. 235 | */ 236 | static setBorderValue(obj, field, fieldPrefix, value, rb) { 237 | let fieldWithoutPrefix = field; 238 | if (fieldPrefix.length > 0) { 239 | fieldWithoutPrefix = fieldWithoutPrefix.substring(fieldPrefix.length); 240 | } 241 | if (fieldWithoutPrefix === 'borderLeft' || fieldWithoutPrefix === 'borderTop' || 242 | fieldWithoutPrefix === 'borderRight' || fieldWithoutPrefix === 'borderBottom') { 243 | let borderAll = (obj.getValue(`${fieldPrefix}borderLeft`) && obj.getValue(`${fieldPrefix}borderTop`) && 244 | obj.getValue(`${fieldPrefix}borderRight`) && obj.getValue(`${fieldPrefix}borderBottom`)); 245 | let borderAllField = `${fieldPrefix}borderAll`; 246 | if (borderAll !== obj[borderAllField]) { 247 | obj[borderAllField] = borderAll; 248 | rb.notifyEvent(obj, Command.operation.change, borderAllField); 249 | } 250 | } 251 | } 252 | 253 | /** 254 | * Returns class name. 255 | * This can be useful for introspection when the class names are mangled 256 | * due to the webpack uglification process. 257 | * @returns {string} 258 | */ 259 | getClassName() { 260 | return 'Style'; 261 | } 262 | } 263 | 264 | Style.type = { 265 | text: 'text', 266 | line: 'line', 267 | image: 'image', 268 | table: 'table', 269 | tableBand: 'tableBand', 270 | frame: 'frame', 271 | sectionBand: 'sectionBand', 272 | } 273 | 274 | // Verdana, Arial 275 | // ['Courier', 'Courier-Bold', 'Courier-BoldOblique', 'Courier-Oblique', 'Helvetica', 'Helvetica-Bold', 276 | // 'Helvetica-BoldOblique', 'Helvetica-Oblique', 'Symbol', 'Times-Bold', 'Times-BoldItalic', 'Times-Italic', 277 | // 'Times-Roman', 'ZapfDingbats'] 278 | Style.font = { 279 | courier: 'courier', 280 | helvetica: 'helvetica', 281 | times: 'times' 282 | }; 283 | 284 | Style.alignment = { 285 | // horizontal 286 | left: 'left', 287 | center: 'center', 288 | right: 'right', 289 | justify: 'justify', 290 | // vertical 291 | top: 'top', 292 | middle: 'middle', 293 | bottom: 'bottom' 294 | }; 295 | -------------------------------------------------------------------------------- /src/elements/BarCodeElement.js: -------------------------------------------------------------------------------- 1 | import DocElement from './DocElement'; 2 | import JsBarcode from 'jsbarcode'; 3 | import QRCode from 'qrcode'; 4 | import * as utils from '../utils'; 5 | 6 | /** 7 | * Barcode doc element. Currently only Code-128 is supported. 8 | * @class 9 | */ 10 | export default class BarCodeElement extends DocElement { 11 | constructor(id, initialData, rb) { 12 | super(rb.getLabel('docElementImage'), id, 80, 80, rb); 13 | this.elBarCode = null; 14 | this.elContent = null; 15 | this.content = ''; 16 | this.format = 'CODE128'; 17 | this.displayValue = false; 18 | this.barWidth = '2'; 19 | this.guardBar = false; 20 | this.errorCorrectionLevel = 'M'; 21 | this.rotate = false; 22 | this.spreadsheet_hide = false; 23 | this.spreadsheet_column = ''; 24 | this.spreadsheet_colspan = ''; 25 | this.spreadsheet_addEmptyRow = false; 26 | this.setInitialData(initialData); 27 | this.name = this.rb.getLabel('docElementBarCode'); 28 | } 29 | 30 | setup(openPanelItem) { 31 | super.setup(openPanelItem); 32 | this.createElement(); 33 | if (this.content !== '') { 34 | this.updateBarCode(); 35 | } 36 | this.updateDisplay(); 37 | this.updateStyle(); 38 | } 39 | 40 | setValue(field, value) { 41 | super.setValue(field, value); 42 | if (field === 'content' ||field === 'format' || field === 'displayValue' || 43 | field === 'barWidth' || field === 'guardBar' || 44 | field === 'width' || field === 'height' || field === 'errorCorrectionLevel' || field === 'rotate') { 45 | this.updateBarCode(); 46 | this.updateDisplay(); 47 | if (field === 'rotate') { 48 | // if rotate setting was changed and object is selected we select it again so the 49 | // sizers are shown correctly (sizers for x axis are only available when bar code is rotated) 50 | if (this.rb.isSelectedObject(this.getId())) { 51 | this.rb.deselectObject(this.getId()); 52 | this.rb.selectObject(this.getId()); 53 | } 54 | } 55 | } 56 | } 57 | 58 | /** 59 | * Returns all fields of this object that can be modified in the properties panel. 60 | * @returns {String[]} 61 | */ 62 | getProperties() { 63 | return [ 64 | 'x', 'y', 'width', 'height', 'content', 'format', 'displayValue', 65 | 'barWidth', 'guardBar', 'errorCorrectionLevel', 66 | 'printIf', 'removeEmptyElement', 'rotate', 67 | 'spreadsheet_hide', 'spreadsheet_column', 'spreadsheet_colspan', 'spreadsheet_addEmptyRow' 68 | ]; 69 | } 70 | 71 | getElementType() { 72 | return DocElement.type.barCode; 73 | } 74 | 75 | updateDisplayInternal(x, y, width, height) { 76 | if (this.el !== null) { 77 | this.el.style.left = this.rb.toPixel(x); 78 | this.el.style.top = this.rb.toPixel(y); 79 | this.el.style.width = this.rb.toPixel(width); 80 | this.el.style.height = this.rb.toPixel(height); 81 | } 82 | } 83 | 84 | /** 85 | * Returns allowed sizers when element is selected. 86 | * @returns {String[]} 87 | */ 88 | getSizers() { 89 | if (this.format !== 'QRCode' && this.rotate) { 90 | // when the bar code is rotated it is possible to set the width (i.e. the actual bar code height) 91 | // and the height, the height is only relevant for layout of following elements since the 92 | // actual height depends on the generated bar code 93 | return ['N', 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW']; 94 | } else { 95 | return ['N', 'S']; 96 | } 97 | } 98 | 99 | createElement() { 100 | this.el = utils.createElement('div', { id: `rbro_el${this.id}`, class: 'rbroDocElement rbroBarCodeElement' }); 101 | // content element is needed for overflow hidden which is set for rotated bar code 102 | this.elContent = utils.createElement('div', { id: `rbro_el_content${this.id}`, style: 'height: 100%' }); 103 | this.elBarCode = utils.createElement('canvas', { id: `rbro_el_barcode${this.id}` } ); 104 | this.elContent.append(this.elBarCode); 105 | this.el.append(this.elContent); 106 | this.appendToContainer(); 107 | this.updateBarCode(); 108 | super.registerEventHandlers(); 109 | } 110 | 111 | remove() { 112 | super.remove(); 113 | } 114 | 115 | updateBarCode() { 116 | if (this.format === 'QRCode') { 117 | this.widthVal = this.heightVal; 118 | this.width = '' + this.widthVal; 119 | let content = this.content; 120 | if (content === '') { 121 | content = 'https://www.reportbro.com'; 122 | } 123 | let options = { 124 | width: this.widthVal, 125 | margin: 0, 126 | errorCorrectionLevel : this.errorCorrectionLevel 127 | }; 128 | this.clearRotateStyle(); 129 | QRCode.toCanvas(this.elBarCode, content, options); 130 | } else { 131 | let valid = false; 132 | let height = this.rotate ? this.widthVal : this.heightVal; 133 | // height is total height for bar code element, 134 | // remove height for value and guard bars if necessary so the bar code plus value and guard bars 135 | // does not exceed the total height 136 | if (this.displayValue) { 137 | height -= 22; 138 | } 139 | if ((this.format === 'EAN8' || this.format === 'EAN13') && this.guardBar) { 140 | height -= 12; 141 | } 142 | let options = { 143 | format: this.format, height: height, 144 | margin: 0, displayValue: this.displayValue, width: 2 145 | }; 146 | if (this.format === 'EAN8' || this.format === 'EAN13') { 147 | options.flat = !this.guardBar; 148 | } else if (this.format === 'UPC') { 149 | options.flat = true; // guard bars are currently not supported in reportbro-lib for UPC 150 | } 151 | const barWidthVal = utils.convertInputToNumber(this.barWidth); 152 | if (barWidthVal) { 153 | options.width = barWidthVal; 154 | } 155 | 156 | // clear width and height which is set on canvas element when QR code is generated 157 | this.elBarCode.style.width = ''; 158 | this.elBarCode.style.height = ''; 159 | 160 | if (this.content !== '' && this.content.indexOf('${') === -1) { 161 | try { 162 | JsBarcode('#' + this.elBarCode.id, this.content, options); 163 | valid = true; 164 | } catch (ex) { 165 | } 166 | } 167 | if (!valid) { 168 | // in case barcode cannot be created because of invalid input use default content appropriate 169 | // for selected format 170 | let content = ''; 171 | if (this.format === 'CODE39' || this.format === 'CODE128') { 172 | content = '12345678'; 173 | } else if (this.format === 'EAN13') { 174 | content = '5901234123457'; 175 | } else if (this.format === 'EAN8') { 176 | content = '96385074'; 177 | } else if (this.format === 'EAN5') { 178 | content = '12345'; 179 | } else if (this.format === 'EAN2') { 180 | content = '12'; 181 | } else if (this.format === 'ITF14') { 182 | content = '12345678901231'; 183 | } else if (this.format === 'UPC') { 184 | content = '036000291452'; 185 | } else if (this.format === 'MSI' ||this.format === 'MSI10' || this.format === 'MSI11' || 186 | this.format === 'MSI1010' || this.format === 'MSI1110' || this.format === 'pharmacode') { 187 | content = '1234'; 188 | } 189 | JsBarcode('#' + this.elBarCode.id, content, options); 190 | } 191 | if (this.rotate) { 192 | const offset_x = -(this.elBarCode.clientWidth - this.widthVal) / 2; 193 | const offset_y = -offset_x; 194 | this.elBarCode.style.transform = `translate(${offset_x}px, ${offset_y}px) rotate(90deg)`; 195 | this.elContent.style.overflow = 'hidden'; 196 | } else { 197 | this.widthVal = this.elBarCode.clientWidth; 198 | this.width = '' + this.widthVal; 199 | this.clearRotateStyle(); 200 | } 201 | } 202 | } 203 | 204 | /** 205 | * Must be called when bar code is created / updated and bar code is not rotated. 206 | */ 207 | clearRotateStyle() { 208 | this.elBarCode.style.transform = ''; 209 | this.elContent.style.overflow = ''; 210 | } 211 | 212 | /** 213 | * Adds SetValue commands to command group parameter in case the specified parameter is used in any of 214 | * the object fields. 215 | * @param {Parameter} parameter - parameter which will be renamed. 216 | * @param {String} newParameterName - new name of the parameter. 217 | * @param {CommandGroupCmd} cmdGroup - possible SetValue commands will be added to this command group. 218 | */ 219 | addCommandsForChangedParameterName(parameter, newParameterName, cmdGroup) { 220 | this.addCommandForChangedParameterName(parameter, newParameterName, 'content', cmdGroup); 221 | this.addCommandForChangedParameterName(parameter, newParameterName, 'printIf', cmdGroup); 222 | } 223 | 224 | /** 225 | * Returns class name. 226 | * This can be useful for introspection when the class names are mangled 227 | * due to the webpack uglification process. 228 | * @returns {string} 229 | */ 230 | getClassName() { 231 | return 'BarCodeElement'; 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /src/elements/FrameElement.js: -------------------------------------------------------------------------------- 1 | import DocElement from './DocElement'; 2 | import Frame from '../container/Frame'; 3 | import Style from '../data/Style'; 4 | import * as utils from '../utils'; 5 | 6 | /** 7 | * Frame element. Frames can contain any number of other doc element. These doc elements 8 | * are positioned relative to the frame. 9 | * @class 10 | */ 11 | export default class FrameElement extends DocElement { 12 | constructor(id, initialData, rb) { 13 | super(rb.getLabel('docElementFrame'), id, 100, 100, rb); 14 | this.frame = null; 15 | this.setupComplete = false; 16 | this.elContent = null; 17 | this.elContentFrame = null; 18 | this.label = ''; 19 | this.backgroundColor = ''; 20 | this.borderAll = false; 21 | this.borderLeft = false; 22 | this.borderTop = false; 23 | this.borderRight = false; 24 | this.borderBottom = false; 25 | this.borderColor = '#000000'; 26 | this.borderWidth = '1'; 27 | 28 | this.shrinkToContentHeight = false; 29 | this.alignToPageBottom = false; 30 | 31 | this.spreadsheet_hide = false; 32 | this.spreadsheet_column = ''; 33 | this.spreadsheet_addEmptyRow = false; 34 | 35 | this.setInitialData(initialData); 36 | 37 | this.borderWidthVal = utils.convertInputToNumber(this.borderWidth); 38 | } 39 | 40 | setup(openPanelItem) { 41 | this.borderWidthVal = utils.convertInputToNumber(this.borderWidth); 42 | super.setup(); 43 | this.createElement(); 44 | this.updateDisplay(); 45 | 46 | if (this.linkedContainerId === null) { 47 | this.linkedContainerId = this.rb.getUniqueId(); 48 | } 49 | this.frame = new Frame(this.linkedContainerId, 'frame_' + this.linkedContainerId, this.rb); 50 | this.frame.init(this); 51 | this.rb.addContainer(this.frame); 52 | 53 | this.setupComplete = true; 54 | this.updateStyle(); 55 | this.updateName(); 56 | if (openPanelItem){ 57 | this.panelItem.open(); 58 | } 59 | } 60 | 61 | /** 62 | * Register event handler for a container element so it can be dragged and 63 | * allow selection on double click. 64 | */ 65 | registerEventHandlers() { 66 | super.registerContainerEventHandlers(); 67 | } 68 | 69 | /** 70 | * Returns highest id of this component, this is the id of the linked container because it is 71 | * created after the frame element. 72 | * @returns {Number} 73 | */ 74 | getMaxId() { 75 | return this.linkedContainerId; 76 | } 77 | 78 | setValue(field, value) { 79 | if (field.indexOf('border') !== -1) { 80 | // Style.setBorderValue needs to be called before super.setValue 81 | // because it calls updateStyle() which expects the correct border settings 82 | this[field] = value; 83 | if (field === 'borderWidth') { 84 | this.borderWidthVal = utils.convertInputToNumber(value); 85 | } 86 | Style.setBorderValue(this, field, '', value, this.rb); 87 | } 88 | 89 | super.setValue(field, value); 90 | 91 | if (field === 'label') { 92 | this.updateName(); 93 | } 94 | } 95 | 96 | updateDisplayInternal(x, y, width, height) { 97 | if (this.el !== null) { 98 | this.el.style.left = this.rb.toPixel(x); 99 | this.el.style.top = this.rb.toPixel(y); 100 | this.el.style.width = this.rb.toPixel(width); 101 | this.el.style.height = this.rb.toPixel(height); 102 | } 103 | // update inner frame element size 104 | if (this.borderLeft) { 105 | width -= this.borderWidthVal; 106 | } 107 | if (this.borderRight) { 108 | width -= this.borderWidthVal; 109 | } 110 | if (this.borderTop) { 111 | height -= this.borderWidthVal; 112 | } 113 | if (this.borderBottom) { 114 | height -= this.borderWidthVal; 115 | } 116 | 117 | this.elContentFrame.style.width = this.rb.toPixel(width); 118 | this.elContentFrame.style.height = this.rb.toPixel(height); 119 | } 120 | 121 | updateStyle() { 122 | let borderStyleProperties = {}; 123 | let borderStyle; 124 | if (this.getValue('borderLeft') || this.getValue('borderTop') || 125 | this.getValue('borderRight') || this.getValue('borderBottom')) { 126 | borderStyle = this.getValue('borderTop') ? 'solid' : 'none'; 127 | borderStyle += this.getValue('borderRight') ? ' solid' : ' none'; 128 | borderStyle += this.getValue('borderBottom') ? ' solid' : ' none'; 129 | borderStyle += this.getValue('borderLeft') ? ' solid' : ' none'; 130 | this.elContent.style.borderWidth = this.getValue('borderWidthVal') + 'px'; 131 | this.elContent.style.borderColor = this.getValue('borderColor'); 132 | } else { 133 | borderStyle = 'none'; 134 | } 135 | this.elContent.style.borderStyle = borderStyle; 136 | this.el.style.backgroundColor = this.getValue('backgroundColor'); 137 | } 138 | 139 | /** 140 | * Returns all data fields of this object. The fields are used when serializing the object. 141 | * @returns {String[]} 142 | */ 143 | getFields() { 144 | let fields = this.getProperties(); 145 | fields.splice(0, 0, 'id', 'containerId', 'linkedContainerId'); 146 | return fields; 147 | } 148 | 149 | /** 150 | * Returns all fields of this object that can be modified in the properties panel. 151 | * @returns {String[]} 152 | */ 153 | getProperties() { 154 | return [ 155 | 'label', 'x', 'y', 'width', 'height', 'styleId', 'backgroundColor', 156 | 'borderAll', 'borderLeft', 'borderTop', 'borderRight', 'borderBottom', 'borderColor', 'borderWidth', 157 | 'printIf', 'removeEmptyElement', 'shrinkToContentHeight', 'alignToPageBottom', 158 | 'spreadsheet_hide', 'spreadsheet_column', 'spreadsheet_addEmptyRow' 159 | ]; 160 | } 161 | 162 | getElementType() { 163 | return DocElement.type.frame; 164 | } 165 | 166 | isAreaSelectionAllowed() { 167 | return false; 168 | } 169 | 170 | createElement() { 171 | this.el = utils.createElement( 172 | 'div', { id: `rbro_el${this.id}`, class: 'rbroDocElement rbroFrameElement rbroElementContainer' }); 173 | // rbroContentContainerHelper contains border styles 174 | // rbroDocElementContentFrame contains width and height 175 | this.elContent = utils.createElement( 176 | 'div', { id: `rbro_el_content${this.id}`, class: 'rbroContentContainerHelper' }); 177 | this.elContentFrame = utils.createElement( 178 | 'div', { id: `rbro_el_content_frame${this.id}`, class: 'rbroDocElementContentFrame' }); 179 | this.elContent.append(this.elContentFrame); 180 | this.el.append(this.elContent); 181 | this.appendToContainer(); 182 | this.registerEventHandlers(); 183 | } 184 | 185 | getContentElement() { 186 | return this.elContentFrame; 187 | } 188 | 189 | remove() { 190 | super.remove(); 191 | this.rb.deleteContainer(this.frame); 192 | } 193 | 194 | updateName() { 195 | if (this.label.trim() !== '') { 196 | this.name = this.label; 197 | } else { 198 | this.name = this.rb.getLabel('docElementFrame'); 199 | } 200 | document.getElementById(`rbro_menu_item_name${this.id}`).textContent = this.name; 201 | } 202 | 203 | /** 204 | * Adds SetValue commands to command group parameter in case the specified parameter is used in any of 205 | * the object fields. 206 | * @param {Parameter} parameter - parameter which will be renamed. 207 | * @param {String} newParameterName - new name of the parameter. 208 | * @param {CommandGroupCmd} cmdGroup - possible SetValue commands will be added to this command group. 209 | */ 210 | addCommandsForChangedParameterName(parameter, newParameterName, cmdGroup) { 211 | this.addCommandForChangedParameterName(parameter, newParameterName, 'printIf', cmdGroup); 212 | } 213 | 214 | /** 215 | * Returns class name. 216 | * This can be useful for introspection when the class names are mangled 217 | * due to the webpack uglification process. 218 | * @returns {string} 219 | */ 220 | getClassName() { 221 | return 'FrameElement'; 222 | } 223 | } -------------------------------------------------------------------------------- /src/elements/ImageElement.js: -------------------------------------------------------------------------------- 1 | import DocElement from './DocElement'; 2 | import SetValueCmd from '../commands/SetValueCmd'; 3 | import Style from '../data/Style'; 4 | import * as utils from '../utils'; 5 | 6 | /** 7 | * Image doc element. Supported formats are png and jpg. 8 | * @class 9 | */ 10 | export default class ImageElement extends DocElement { 11 | constructor(id, initialData, rb) { 12 | super(rb.getLabel('docElementImage'), id, 80, 80, rb); 13 | this.source = ''; 14 | this.image = ''; 15 | this.imageWidth = 0; 16 | this.imageHeight = 0; 17 | this.imageRatio = 0; 18 | this.imageFilename = ''; 19 | this.elImg = null; 20 | this.elContent = null; 21 | this.horizontalAlignment = Style.alignment.left; 22 | this.verticalAlignment = Style.alignment.top; 23 | this.backgroundColor = ''; 24 | this.link = ''; 25 | this.spreadsheet_hide = false; 26 | this.spreadsheet_column = ''; 27 | this.spreadsheet_addEmptyRow = false; 28 | this.setInitialData(initialData); 29 | } 30 | 31 | setup(openPanelItem) { 32 | super.setup(openPanelItem); 33 | this.createElement(); 34 | // setImage must be called after createElement so load event handler of image element is triggered 35 | this.setImage(); 36 | this.updateDisplay(); 37 | this.updateStyle(); 38 | this.updateName(); 39 | } 40 | 41 | setValue(field, value) { 42 | super.setValue(field, value); 43 | if (field === 'source' || field === 'imageFilename') { 44 | this.updateName(); 45 | } 46 | if (field === 'source' || field === 'image') { 47 | this.setImage(); 48 | } 49 | } 50 | 51 | /** 52 | * Returns all fields of this object that can be modified in the properties panel. 53 | * @returns {String[]} 54 | */ 55 | getProperties() { 56 | return [ 57 | 'x', 'y', 'width', 'height', 'source', 'image', 'imageFilename', 58 | 'styleId', 'horizontalAlignment', 'verticalAlignment', 'backgroundColor', 59 | 'printIf', 'removeEmptyElement', 'link', 60 | 'spreadsheet_hide', 'spreadsheet_column', 'spreadsheet_addEmptyRow' 61 | ]; 62 | } 63 | 64 | getElementType() { 65 | return DocElement.type.image; 66 | } 67 | 68 | updateDisplayInternal(x, y, width, height) { 69 | if (this.el !== null) { 70 | this.el.style.left = this.rb.toPixel(x); 71 | this.el.style.top = this.rb.toPixel(y); 72 | this.el.style.width = this.rb.toPixel(width); 73 | this.el.style.height = this.rb.toPixel(height); 74 | 75 | let imgWidth = 0; 76 | let imgHeight = 0; 77 | if (this.imageRatio !== 0) { 78 | imgWidth = (this.imageWidth < width) ? this.imageWidth : width; 79 | imgHeight = (this.imageHeight < height) ? this.imageHeight : height; 80 | if (imgWidth !== this.imageWidth || imgHeight !== this.imageHeight) { 81 | let scaledWidth = Math.floor(imgHeight * this.imageRatio); 82 | if (scaledWidth < width) { 83 | imgWidth = scaledWidth; 84 | } else { 85 | imgHeight = Math.floor(imgWidth / this.imageRatio); 86 | } 87 | } 88 | } 89 | this.elImg.style.width = this.rb.toPixel(imgWidth); 90 | this.elImg.style.height = this.rb.toPixel(imgHeight); 91 | } 92 | } 93 | 94 | updateStyle() { 95 | let horizontalAlignment = this.getValue('horizontalAlignment'); 96 | let verticalAlignment = this.getValue('verticalAlignment'); 97 | let alignClass = 'rbroDocElementAlign' + horizontalAlignment.charAt(0).toUpperCase() + 98 | horizontalAlignment.slice(1); 99 | let valignClass = 'rbroDocElementVAlign' + verticalAlignment.charAt(0).toUpperCase() + 100 | verticalAlignment.slice(1); 101 | this.elContent.style.textAlign = horizontalAlignment; 102 | this.elContent.style.verticalAlign = verticalAlignment; 103 | this.elContent.style.backgroundColor = this.getValue('backgroundColor'); 104 | this.elContent.className = ''; 105 | this.elContent.classList.add('rbroContentContainerHelper'); 106 | this.elContent.classList.add(alignClass); 107 | this.elContent.classList.add(valignClass); 108 | } 109 | 110 | createElement() { 111 | this.el = utils.createElement('div', { id: `rbro_el${this.id}`, class: 'rbroDocElement rbroImageElement' }); 112 | this.elImg = utils.createElement('img', { src: '' }); 113 | this.elImg.addEventListener('load', (event) => { 114 | // get image width and height in load event, because width/height are not 115 | // directly available in some browsers after setting src 116 | this.imageWidth = this.elImg.naturalWidth; 117 | this.imageHeight = this.elImg.naturalHeight; 118 | if (this.imageHeight !== 0) { 119 | this.imageRatio = this.imageWidth / this.imageHeight; 120 | } else { 121 | this.imageRatio = 0; 122 | } 123 | this.updateDisplay(); 124 | }); 125 | this.elContent = utils.createElement( 126 | 'div', { id: `rbro_el_content${this.id}`, class: 'rbroContentContainerHelper' }); 127 | this.elContent.append(this.elImg); 128 | this.el.append(this.elContent); 129 | this.appendToContainer(); 130 | super.registerEventHandlers(); 131 | } 132 | 133 | remove() { 134 | this.elImg = null; 135 | super.remove(); 136 | } 137 | 138 | setImage() { 139 | this.elImg.setAttribute('src', ''); 140 | if (this.source.startsWith('https://') || this.source.startsWith('http://')) { 141 | // image specified by url 142 | this.elImg.setAttribute('src', this.source); 143 | } else if (this.image !== '') { 144 | // image base64 encoded 145 | this.elImg.setAttribute('src', this.image); 146 | } else { 147 | // no image preview 148 | this.imageWidth = 0; 149 | this.imageHeight = 0; 150 | this.imageRatio = 0; 151 | this.updateDisplay(); 152 | } 153 | } 154 | 155 | updateName() { 156 | if (this.getValue('imageFilename').trim() !== '') { 157 | this.name = this.getValue('imageFilename') 158 | } else if (this.getValue('source').trim() !== '') { 159 | this.name = this.getValue('source'); 160 | } else { 161 | this.name = this.rb.getLabel('docElementImage'); 162 | } 163 | const elMenuItem = document.getElementById(`rbro_menu_item_name${this.id}`); 164 | elMenuItem.textContent = this.name; 165 | elMenuItem.setAttribute('title', this.name); 166 | } 167 | 168 | /** 169 | * Adds SetValue commands to command group parameter in case the specified parameter is used in any of 170 | * the object fields. 171 | * @param {Parameter} parameter - parameter which will be renamed. 172 | * @param {String} newParameterName - new name of the parameter. 173 | * @param {CommandGroupCmd} cmdGroup - possible SetValue commands will be added to this command group. 174 | */ 175 | addCommandsForChangedParameterName(parameter, newParameterName, cmdGroup) { 176 | this.addCommandForChangedParameterName(parameter, newParameterName, 'source', cmdGroup); 177 | this.addCommandForChangedParameterName(parameter, newParameterName, 'printIf', cmdGroup); 178 | } 179 | 180 | /** 181 | * Returns class name. 182 | * This can be useful for introspection when the class names are mangled 183 | * due to the webpack uglification process. 184 | * @returns {string} 185 | */ 186 | getClassName() { 187 | return 'ImageElement'; 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /src/elements/LineElement.js: -------------------------------------------------------------------------------- 1 | import DocElement from './DocElement'; 2 | import * as utils from '../utils' 3 | 4 | /** 5 | * Line doc element. Currently only horizontal lines are supported. 6 | * @class 7 | */ 8 | export default class LineElement extends DocElement { 9 | constructor(id, initialData, rb) { 10 | super(rb.getLabel('docElementLine'), id, 100, 1, rb); 11 | this.color = '#000000'; 12 | this.setInitialData(initialData); 13 | } 14 | 15 | setup(openPanelItem) { 16 | super.setup(openPanelItem); 17 | this.createElement(); 18 | this.updateDisplay(); 19 | this.updateStyle(); 20 | } 21 | 22 | setValue(field, value) { 23 | super.setValue(field, value); 24 | if (field === 'color') { 25 | this.updateStyle(); 26 | } 27 | } 28 | 29 | /** 30 | * Returns all fields of this object that can be modified in the properties panel. 31 | * @returns {String[]} 32 | */ 33 | getProperties() { 34 | return ['x', 'y', 'width', 'height', 'styleId', 'color', 'printIf']; 35 | } 36 | 37 | getElementType() { 38 | return DocElement.type.line; 39 | } 40 | 41 | updateStyle() { 42 | this.el.style.backgroundColor = this.getValue('color'); 43 | } 44 | 45 | /** 46 | * Returns allowed sizers when element is selected. 47 | * @returns {String[]} 48 | */ 49 | getSizers() { 50 | return ['E', 'W']; 51 | } 52 | 53 | createElement() { 54 | this.el = utils.createElement('div', { id: `rbro_el${this.id}`, class: 'rbroDocElement rbroLineElement' }); 55 | this.appendToContainer(); 56 | super.registerEventHandlers(); 57 | } 58 | 59 | /** 60 | * Adds SetValue commands to command group parameter in case the specified parameter is used in any of 61 | * the object fields. 62 | * @param {Parameter} parameter - parameter which will be renamed. 63 | * @param {String} newParameterName - new name of the parameter. 64 | * @param {CommandGroupCmd} cmdGroup - possible SetValue commands will be added to this command group. 65 | */ 66 | addCommandsForChangedParameterName(parameter, newParameterName, cmdGroup) { 67 | this.addCommandForChangedParameterName(parameter, newParameterName, 'printIf', cmdGroup); 68 | } 69 | 70 | /** 71 | * Returns class name. 72 | * This can be useful for introspection when the class names are mangled 73 | * due to the webpack uglification process. 74 | * @returns {string} 75 | */ 76 | getClassName() { 77 | return 'LineElement'; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/elements/PageBreakElement.js: -------------------------------------------------------------------------------- 1 | import DocElement from './DocElement'; 2 | import * as utils from '../utils' 3 | 4 | /** 5 | * Page break doc element. A page break triggers a new page when the document is printed. 6 | * @class 7 | */ 8 | export default class PageBreakElement extends DocElement { 9 | constructor(id, initialData, rb) { 10 | super(rb.getLabel('docElementPageBreak'), id, -1, 1, rb); 11 | this.setInitialData(initialData); 12 | } 13 | 14 | setup(openPanelItem) { 15 | super.setup(openPanelItem); 16 | this.createElement(); 17 | this.updateDisplay(); 18 | this.updateStyle(); 19 | } 20 | 21 | setValue(field, value) { 22 | super.setValue(field, value); 23 | } 24 | 25 | /** 26 | * Returns all fields of this object that can be modified in the properties panel. 27 | * @returns {String[]} 28 | */ 29 | getProperties() { 30 | return ['y', 'printIf']; 31 | } 32 | 33 | getElementType() { 34 | return DocElement.type.pageBreak; 35 | } 36 | 37 | updateDisplayInternal(x, y, width, height) { 38 | if (this.el !== null) { 39 | this.el.style.left = this.rb.toPixel(0); 40 | this.el.style.top = this.rb.toPixel(y); 41 | this.el.style.width = '100%'; 42 | this.el.style.height = this.rb.toPixel(1); 43 | } 44 | } 45 | 46 | /** 47 | * Returns allowed sizers when element is selected. 48 | * @returns {String[]} 49 | */ 50 | getSizers() { 51 | return []; 52 | } 53 | 54 | createElement() { 55 | this.el = utils.createElement('div', { id: `rbro_el${this.id}`, class: 'rbroDocElement rbroPageBreakElement' }); 56 | this.appendToContainer(); 57 | super.registerEventHandlers(); 58 | } 59 | 60 | /** 61 | * Returns class name. 62 | * This can be useful for introspection when the class names are mangled 63 | * due to the webpack uglification process. 64 | * @returns {string} 65 | */ 66 | getClassName() { 67 | return 'PageBreakElement'; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/elements/SectionBandElement.js: -------------------------------------------------------------------------------- 1 | import DocElement from './DocElement'; 2 | import Band from '../container/Band'; 3 | import * as utils from '../utils'; 4 | 5 | /** 6 | * Section band doc element. This is the header, content or footer of a custom section. 7 | * All Elements inside the band are positioned relative. 8 | * @class 9 | */ 10 | export default class SectionBandElement extends DocElement { 11 | constructor(id, initialData, bandType, rb) { 12 | let name = (bandType === Band.bandType.header) ? 13 | rb.getLabel('bandHeader') : 14 | ((bandType === Band.bandType.footer) ? rb.getLabel('bandFooter') : rb.getLabel('bandContent')); 15 | super(name, id, 0, 100, rb); 16 | this.setupComplete = false; 17 | this.band = null; 18 | this.bandType = bandType; 19 | this.repeatHeader = false; 20 | this.backgroundColor = ''; 21 | this.alternateBackgroundColor = ''; 22 | this.alwaysPrintOnSamePage = true; 23 | this.shrinkToContentHeight = false; 24 | this.parentId = initialData.parentId; 25 | 26 | this.heightVal = 0; 27 | this.visible = (bandType === Band.bandType.content); 28 | 29 | this.setInitialData(initialData); 30 | } 31 | 32 | setup() { 33 | this.createElement(); 34 | this.updateDisplay(); 35 | this.updateStyle(); 36 | 37 | if (this.linkedContainerId === null) { 38 | this.linkedContainerId = this.rb.getUniqueId(); 39 | } 40 | this.band = new Band( 41 | this.bandType, true, this.linkedContainerId, 'section_' + this.bandType + '_' + this.linkedContainerId, 42 | this.rb); 43 | this.band.init(this); 44 | this.rb.addContainer(this.band); 45 | this.setupComplete = true; 46 | } 47 | 48 | /** 49 | * Do not register any event handlers so element cannot be selected. 50 | */ 51 | registerEventHandlers() { 52 | } 53 | 54 | /** 55 | * Returns highest id of this component, this is the id of the linked container because it is 56 | * created after the band element. 57 | * @returns {Number} 58 | */ 59 | getMaxId() { 60 | return this.linkedContainerId; 61 | } 62 | 63 | /** 64 | * Returns absolute position inside document. 65 | * @returns {Object} x and y coordinates. 66 | */ 67 | getAbsolutePosition() { 68 | let pos = { x: 0, y: 0 }; 69 | let parent = this.getParent(); 70 | if (parent !== null) { 71 | pos = parent.getAbsolutePosition(); 72 | } 73 | pos.y += this.yVal; 74 | return pos; 75 | } 76 | 77 | setValue(field, value) { 78 | super.setValue(field, value); 79 | 80 | if (field === 'height') { 81 | this[field + 'Val'] = utils.convertInputToNumber(value); 82 | let parent = this.getParent(); 83 | if (parent !== null) { 84 | parent.updateBands(this); 85 | } 86 | } else if (field === 'backgroundColor') { 87 | this.updateStyle(); 88 | } 89 | } 90 | 91 | /** 92 | * Returns all data fields of this object. The fields are used when serializing the object. 93 | * @returns {String[]} 94 | */ 95 | getFields() { 96 | let fields = this.getProperties(); 97 | fields.splice(0, 0, 'id', 'containerId', 'linkedContainerId'); 98 | return fields; 99 | } 100 | 101 | /** 102 | * Returns all fields of this object that can be modified in the properties panel. 103 | * @returns {String[]} 104 | */ 105 | getProperties() { 106 | let fields = ['height', 'styleId', 'backgroundColor', 'shrinkToContentHeight']; 107 | if (this.bandType === Band.bandType.header) { 108 | fields.push('repeatHeader'); 109 | } else { 110 | fields.push('alwaysPrintOnSamePage'); 111 | fields.push('shrinkToContentHeight'); 112 | if (this.bandType === Band.bandType.content) { 113 | fields.push('alternateBackgroundColor'); 114 | } 115 | } 116 | return fields; 117 | } 118 | 119 | updateDisplayInternal(x, y, width, height) { 120 | if (this.el !== null) { 121 | this.el.style.top = this.rb.toPixel(y); 122 | this.el.style.width = '100%'; 123 | this.el.style.height = this.rb.toPixel(height); 124 | if (this.setupComplete) { 125 | // update section element because section band dividers are contained in section 126 | let parent = this.getParent(); 127 | if (parent !== null) { 128 | parent.updateHeight(this, height); 129 | } 130 | } 131 | } 132 | } 133 | 134 | updateStyle() { 135 | this.el.style.backgroundColor = this.backgroundColor; 136 | } 137 | 138 | select() { 139 | super.select(); 140 | this.el.classList.add('rbroHighlightBandDescription'); 141 | } 142 | 143 | deselect() { 144 | super.deselect(); 145 | this.el.classList.remove('rbroHighlightBandDescription'); 146 | } 147 | 148 | /** 149 | * Returns allowed sizers when element is selected. 150 | * @returns {String[]} 151 | */ 152 | getSizers() { 153 | return ['S']; 154 | } 155 | 156 | getHeight() { 157 | return this.heightVal; 158 | } 159 | 160 | isAreaSelectionAllowed() { 161 | return false; 162 | } 163 | 164 | isDraggingAllowed() { 165 | return false; 166 | } 167 | 168 | createElement() { 169 | this.el = utils.createElement( 170 | 'div', { id: `rbro_el${this.id}`, class: 'rbroSectionBandElement rbroElementContainer' }); 171 | this.el.append(utils.createElement( 172 | 'div', { 173 | id: `rbro_el_band_description${this.id}`, class: 'rbroDocumentBandDescription' 174 | })); 175 | document.getElementById(`rbro_el${this.parentId}`).append(this.el); 176 | } 177 | 178 | getContentElement() { 179 | return this.el; 180 | } 181 | 182 | getParent() { 183 | return this.rb.getDataObject(this.parentId); 184 | } 185 | 186 | show(visible) { 187 | this.visible = visible; 188 | if (visible) { 189 | this.el.classList.remove('rbroHidden'); 190 | } else { 191 | this.el.classList.add('rbroHidden'); 192 | } 193 | } 194 | 195 | isVisible() { 196 | return this.visible; 197 | } 198 | 199 | /** 200 | * Returns class name. 201 | * This can be useful for introspection when the class names are mangled 202 | * due to the webpack uglification process. 203 | * @returns {string} 204 | */ 205 | getClassName() { 206 | return 'SectionBandElement'; 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /src/elements/SectionElement.js: -------------------------------------------------------------------------------- 1 | import DocElement from './DocElement'; 2 | import SectionBandElement from './SectionBandElement'; 3 | import Band from '../container/Band'; 4 | import MainPanelItem from '../menu/MainPanelItem'; 5 | import * as utils from '../utils'; 6 | 7 | /** 8 | * Section element. Sections can be added to the content band and contain a content band and optional 9 | * header/footer bands. 10 | * @class 11 | */ 12 | export default class SectionElement extends DocElement { 13 | constructor(id, initialData, rb) { 14 | super(rb.getLabel('docElementSection'), id, -1, 60, rb); 15 | this.setupComplete = false; 16 | this.elDividerHeader = null; 17 | this.elDividerFooter = null; 18 | this.elDividerBottom = null; 19 | this.dataSource = ''; 20 | this.label = ''; 21 | this.header = false; 22 | this.footer = false; 23 | this.headerData = null; 24 | this.contentData = null; 25 | this.footerData = null; 26 | 27 | this.setInitialData(initialData); 28 | } 29 | 30 | setup(openPanelItem) { 31 | super.setup(openPanelItem); 32 | this.createElement(); 33 | this.updateDisplay(); 34 | 35 | this.headerData = this.createBand(Band.bandType.header, null); 36 | this.contentData = this.createBand(Band.bandType.content, null); 37 | this.footerData = this.createBand(Band.bandType.footer, null); 38 | this.updateHeight(null, -1); 39 | 40 | this.setWidth(this.getContainerContentSize().width); 41 | 42 | this.setupComplete = true; 43 | this.updateName(); 44 | if (openPanelItem) { 45 | this.panelItem.open(); 46 | } 47 | } 48 | 49 | createBand(bandType, dataValues) { 50 | let data; 51 | let dataKey = bandType + 'Data'; 52 | let dataId; 53 | let panelItemProperties = { hasChildren: true, showDelete: false }; 54 | if (dataValues) { 55 | data = dataValues; 56 | } else if (this[dataKey]) { 57 | data = this[dataKey]; 58 | dataId = data.id; 59 | } else { 60 | data = {}; 61 | } 62 | data.parentId = this.id; 63 | data.containerId = this.containerId; 64 | if (!dataId) { 65 | dataId = this.rb.getUniqueId(); 66 | } 67 | let y = 0; 68 | if (bandType === Band.bandType.header) { 69 | data.y = '' + y; 70 | } else if (bandType === Band.bandType.content) { 71 | if (this.header && this.headerData !== null) { 72 | y += this.headerData.getValue('heightVal'); 73 | } 74 | data.y = '' + y; 75 | } else if (bandType === Band.bandType.footer) { 76 | if (this.header && this.headerData !== null) { 77 | y += this.headerData.getValue('heightVal'); 78 | } 79 | if (this.contentData !== null) { 80 | y += this.contentData.getValue('heightVal'); 81 | } 82 | data.y = '' + y; 83 | } 84 | if ((bandType === Band.bandType.header && !this.header) || 85 | (bandType === Band.bandType.footer && !this.footer)) { 86 | panelItemProperties.visible = false; 87 | } 88 | let bandElement = new SectionBandElement(dataId, data, bandType, this.rb); 89 | this.rb.addDataObject(bandElement); 90 | let panelItemBand = new MainPanelItem( 91 | 'sectionBand', this.panelItem, bandElement, panelItemProperties, this.rb); 92 | bandElement.setPanelItem(panelItemBand); 93 | this.panelItem.appendChild(panelItemBand); 94 | bandElement.setup(); 95 | 96 | if (bandType === Band.bandType.header) { 97 | bandElement.show(this.header); 98 | } else if (bandType === Band.bandType.footer) { 99 | bandElement.show(this.footer); 100 | } 101 | return bandElement; 102 | } 103 | 104 | /** 105 | * Register event handler for a container element so it can be dragged and 106 | * allow selection on double click. 107 | */ 108 | registerEventHandlers() { 109 | super.registerContainerEventHandlers(); 110 | } 111 | 112 | /** 113 | * Returns highest id of this component, this is the max id of the footer band because it is created last. 114 | * @returns {Number} 115 | */ 116 | getMaxId() { 117 | let id = this.id; 118 | if (this.footerData !== null) { 119 | id = this.footerData.getMaxId(); 120 | } 121 | return id; 122 | } 123 | 124 | appendContainerChildren(elements) { 125 | if (this.headerData !== null) { 126 | this.headerData.appendContainerChildren(elements); 127 | } 128 | if (this.contentData !== null) { 129 | this.contentData.appendContainerChildren(elements); 130 | } 131 | if (this.footerData !== null) { 132 | this.footerData.appendContainerChildren(elements); 133 | } 134 | } 135 | 136 | setValue(field, value) { 137 | super.setValue(field, value); 138 | 139 | if (field === 'label' || field === 'dataSource') { 140 | this.updateName(); 141 | } else if (field === 'header') { 142 | this.headerData.show(value); 143 | if (value) { 144 | this.headerData.getPanelItem().show(); 145 | } else { 146 | this.headerData.getPanelItem().hide(); 147 | } 148 | } else if (field === 'footer') { 149 | this.footerData.show(value); 150 | if (value) { 151 | this.footerData.getPanelItem().show(); 152 | } else { 153 | this.footerData.getPanelItem().hide(); 154 | } 155 | } else if (field === 'containerId') { 156 | this.headerData.containerId = value; 157 | this.contentData.containerId = value; 158 | this.footerData.containerId = value; 159 | } 160 | if (field === 'header' || field === 'footer') { 161 | this.updateBands(null); 162 | } 163 | } 164 | 165 | updateDisplayInternal(x, y, width, height) { 166 | if (this.el !== null) { 167 | this.el.style.top = this.rb.toPixel(y); 168 | this.el.style.width = '100%'; 169 | this.el.style.height = this.rb.toPixel(height); 170 | } 171 | } 172 | 173 | /** 174 | * Returns all fields of this object that can be modified in the properties panel. 175 | * @returns {String[]} 176 | */ 177 | getProperties() { 178 | return ['y', 'label', 'dataSource', 'header', 'footer', 'printIf']; 179 | } 180 | 181 | getElementType() { 182 | return DocElement.type.section; 183 | } 184 | 185 | select() { 186 | super.select(); 187 | let elSizerContainer = this.getSizerContainerElement(); 188 | // create sizers (to indicate selection) which do not support resizing 189 | for (let sizer of ['N', 'S']) { 190 | elSizerContainer.append( 191 | utils.createElement('div', { class: `rbroSizer rbroSizer${sizer} rbroSizerMove` })); 192 | } 193 | 194 | if (this.headerData !== null) { 195 | document.getElementById(`rbro_el${this.headerData.getId()}`).classList.add('rbroHighlightBandDescription'); 196 | } 197 | if (this.contentData !== null) { 198 | document.getElementById(`rbro_el${this.contentData.getId()}`).classList.add('rbroHighlightBandDescription'); 199 | } 200 | if (this.footerData !== null) { 201 | document.getElementById(`rbro_el${this.footerData.getId()}`).classList.add('rbroHighlightBandDescription'); 202 | } 203 | } 204 | 205 | deselect() { 206 | super.deselect(); 207 | const elBandDescriptions = this.el.querySelectorAll('.rbroSectionBandElement'); 208 | for (const elBandDescription of elBandDescriptions) { 209 | elBandDescription.classList.remove('rbroHighlightBandDescription'); 210 | } 211 | } 212 | 213 | /** 214 | * Returns allowed sizers when element is selected. 215 | * @returns {String[]} 216 | */ 217 | getSizers() { 218 | return []; 219 | } 220 | 221 | isAreaSelectionAllowed() { 222 | return false; 223 | } 224 | 225 | isDroppingAllowed() { 226 | return false; 227 | } 228 | 229 | createElement() { 230 | this.el = utils.createElement('div', { id: `rbro_el${this.id}`, class: 'rbroDocElement rbroSectionElement' }); 231 | this.el.append( 232 | utils.createElement('div', { 233 | id: `rbro_divider_section_top${this.id}`, 234 | class: 'rbroDivider rbroDividerSection', 235 | style: 'top: 0px' 236 | }) 237 | ); 238 | this.elDividerHeader = utils.createElement( 239 | 'div', { 240 | id: `rbro_divider_section_header${this.id}`, 241 | class: 'rbroDivider rbroDividerSectionBand rbroHidden' 242 | }); 243 | this.el.append(this.elDividerHeader); 244 | this.elDividerFooter = utils.createElement( 245 | 'div', { 246 | id: `rbro_divider_section_footer${this.id}`, 247 | class: 'rbroDivider rbroDividerSectionBand rbroHidden' 248 | }); 249 | this.el.append(this.elDividerFooter); 250 | this.elDividerBottom = utils.createElement( 251 | 'div', { 252 | id: `rbro_divider_section_bottom${this.id}`, 253 | class: 'rbroDivider rbroDividerSection' 254 | }); 255 | this.el.append(this.elDividerBottom); 256 | this.appendToContainer(); 257 | this.registerEventHandlers(); 258 | } 259 | 260 | remove() { 261 | super.remove(); 262 | // delete containers of section bands 263 | if (this.headerData !== null) { 264 | this.rb.deleteContainer(this.headerData.getLinkedContainer()); 265 | } 266 | if (this.contentData !== null) { 267 | this.rb.deleteContainer(this.contentData.getLinkedContainer()); 268 | } 269 | if (this.footerData !== null) { 270 | this.rb.deleteContainer(this.footerData.getLinkedContainer()); 271 | } 272 | } 273 | 274 | updateName() { 275 | if (this.label.trim() !== '') { 276 | this.name = this.label; 277 | } else { 278 | this.name = this.rb.getLabel('docElementSection'); 279 | if (this.dataSource.trim() !== '') { 280 | this.name += ' ' + this.dataSource; 281 | } 282 | } 283 | if (this.headerData !== null) { 284 | document.getElementById(`rbro_el_band_description${this.headerData.getId()}`).textContent = 285 | this.name + ' ' + this.headerData.getName(); 286 | } 287 | if (this.contentData !== null) { 288 | document.getElementById(`rbro_el_band_description${this.contentData.getId()}`).textContent = 289 | this.name + ' ' + this.contentData.getName(); 290 | } 291 | if (this.footerData !== null) { 292 | document.getElementById(`rbro_el_band_description${this.footerData.getId()}`).textContent = 293 | this.name + ' ' + this.footerData.getName(); 294 | } 295 | document.getElementById(`rbro_menu_item_name${this.id}`).textContent = this.name; 296 | } 297 | 298 | /** 299 | * Set internal width and width of all bands. Should be called whenever the document size changes. 300 | * @param {Number} width - total band width. 301 | */ 302 | setWidth(width) { 303 | this.widthVal = width; 304 | this.width = '' + width; 305 | if (this.headerData !== null) { 306 | this.headerData.widthVal = width; 307 | this.headerData.width = '' + width; 308 | } 309 | if (this.contentData !== null) { 310 | this.contentData.widthVal = width; 311 | this.contentData.width = '' + width; 312 | } 313 | if (this.footerData !== null) { 314 | this.footerData.widthVal = width; 315 | this.footerData.width = '' + width; 316 | } 317 | } 318 | 319 | /** 320 | * Update section element height and position, visibility of dividers for header/footer bands. 321 | * @param {SectionBandElement} band - if not null the bandHeight parameter will be used for band height 322 | * instead of the actual stored height value. This is needed to update the divider display during drag 323 | * of section band height. 324 | * @param {Number} bandHeight - used band height for given band parameter instead of stored height value. 325 | */ 326 | updateHeight(band, bandHeight) { 327 | let height = 0; 328 | if (this.header && this.headerData !== null) { 329 | if (band === this.headerData) { 330 | height += bandHeight; 331 | } else { 332 | height += this.headerData.getValue('heightVal'); 333 | } 334 | this.elDividerHeader.style.top = this.rb.toPixel(height); 335 | this.elDividerHeader.classList.remove('rbroHidden'); 336 | } else { 337 | this.elDividerHeader.classList.add('rbroHidden'); 338 | } 339 | if (this.contentData !== null) { 340 | if (band === this.contentData) { 341 | height += bandHeight; 342 | } else { 343 | height += this.contentData.getValue('heightVal'); 344 | } 345 | } 346 | if (this.footer && this.footerData !== null) { 347 | this.elDividerFooter.style.top = this.rb.toPixel(height); 348 | this.elDividerFooter.classList.remove('rbroHidden'); 349 | if (band === this.footerData) { 350 | height += bandHeight; 351 | } else { 352 | height += this.footerData.getValue('heightVal'); 353 | } 354 | } else { 355 | document.getElementById(`rbro_divider_section_footer${this.id}`).classList.add('rbroHidden'); 356 | } 357 | this.elDividerBottom.style.top = this.rb.toPixel(height); 358 | this.height = '' + height; 359 | this.heightVal = height; 360 | this.updateDisplay(); 361 | } 362 | 363 | /** 364 | * Update height and y-coordinate of all sub-bands (header, content, footer). 365 | */ 366 | updateBands(ignoreBandData) { 367 | if (this.setupComplete) { 368 | let y = 0; 369 | if (this.header) { 370 | if (this.headerData !== ignoreBandData) { 371 | this.headerData.setValue('y', '' + y); 372 | } 373 | y += this.headerData.getValue('heightVal'); 374 | } 375 | if (this.contentData !== ignoreBandData) { 376 | this.contentData.setValue('y', '' + y); 377 | } 378 | y += this.contentData.getValue('heightVal'); 379 | if (this.footer && this.footerData !== ignoreBandData) { 380 | this.footerData.setValue('y', '' + y); 381 | } 382 | } 383 | this.updateHeight(null, -1); 384 | } 385 | 386 | /** 387 | * Get linked containers of all bands. 388 | * @returns {Container[]} array with all linked containers of header/content/footer section bands. 389 | */ 390 | getLinkedContainers() { 391 | let containers = []; 392 | let container; 393 | for (let band of ['headerData', 'contentData', 'footerData']) { 394 | if (this[band] !== null) { 395 | container = this[band].getLinkedContainer(); 396 | if (container !== null) { 397 | containers.push(container); 398 | } 399 | } 400 | } 401 | return containers; 402 | } 403 | 404 | hasDataSource() { 405 | return true; 406 | } 407 | 408 | addChildren(docElements) { 409 | docElements.push(this.headerData); 410 | docElements.push(this.contentData); 411 | docElements.push(this.footerData); 412 | // children of section bands will be added through linked containers of section 413 | } 414 | 415 | /** 416 | * Adds SetValue commands to command group parameter in case the specified parameter is used in any of 417 | * the object fields. 418 | * @param {Parameter} parameter - parameter which will be renamed. 419 | * @param {String} newParameterName - new name of the parameter. 420 | * @param {CommandGroupCmd} cmdGroup - possible SetValue commands will be added to this command group. 421 | */ 422 | addCommandsForChangedParameterName(parameter, newParameterName, cmdGroup) { 423 | this.addCommandForChangedParameterName(parameter, newParameterName, 'dataSource', cmdGroup); 424 | this.addCommandForChangedParameterName(parameter, newParameterName, 'printIf', cmdGroup); 425 | } 426 | 427 | toJS() { 428 | const rv = super.toJS(); 429 | rv['headerData'] = this.headerData.toJS(); 430 | rv['contentData'] = this.contentData.toJS(); 431 | rv['footerData'] = this.footerData.toJS(); 432 | return rv; 433 | } 434 | 435 | /** 436 | * Returns class name. 437 | * This can be useful for introspection when the class names are mangled 438 | * due to the webpack uglification process. 439 | * @returns {string} 440 | */ 441 | getClassName() { 442 | return 'SectionElement'; 443 | } 444 | } 445 | -------------------------------------------------------------------------------- /src/elements/TextElement.js: -------------------------------------------------------------------------------- 1 | import DocElement from './DocElement'; 2 | import SetValueCmd from '../commands/SetValueCmd'; 3 | import Style from '../data/Style'; 4 | import * as utils from '../utils'; 5 | 6 | /** 7 | * Text doc element. 8 | * @class 9 | */ 10 | export default class TextElement extends DocElement { 11 | constructor(id, initialData, rb) { 12 | super(rb.getLabel('docElementText'), id, 100, 20, rb); 13 | this.elContent = null; 14 | this.elContentText = null; 15 | this.elContentTextData = null; 16 | 17 | this.content = ''; 18 | this.richText = false; 19 | this.richTextContent = null; 20 | this.richTextHtml = ''; 21 | this.eval = false; 22 | 23 | this.styleId = ''; 24 | this.bold = false; 25 | this.italic = false; 26 | this.underline = false; 27 | this.strikethrough = false; 28 | this.horizontalAlignment = Style.alignment.left; 29 | this.verticalAlignment = Style.alignment.top; 30 | this.textColor = '#000000'; 31 | this.backgroundColor = ''; 32 | this.font = rb.getProperty('defaultFont'); 33 | this.fontSize = 12; 34 | this.lineSpacing = 1; 35 | this.borderColor = '#000000'; 36 | this.borderWidth = '1'; 37 | this.borderAll = false; 38 | this.borderLeft = false; 39 | this.borderTop = false; 40 | this.borderRight = false; 41 | this.borderBottom = false; 42 | this.paddingLeft = '2'; 43 | this.paddingTop = '2'; 44 | this.paddingRight = '2'; 45 | this.paddingBottom = '2'; 46 | 47 | this.cs_condition = ''; 48 | this.cs_styleId = ''; 49 | this.cs_additionalRules = ''; 50 | this.cs_bold = false; 51 | this.cs_italic = false; 52 | this.cs_underline = false; 53 | this.cs_strikethrough = false; 54 | this.cs_horizontalAlignment = Style.alignment.left; 55 | this.cs_verticalAlignment = Style.alignment.top; 56 | this.cs_textColor = '#000000'; 57 | this.cs_backgroundColor = ''; 58 | this.cs_font = Style.font.helvetica; 59 | this.cs_fontSize = 12; 60 | this.cs_lineSpacing = 1; 61 | this.cs_borderColor = '#000000'; 62 | this.cs_borderWidth = '1'; 63 | this.cs_borderAll = false; 64 | this.cs_borderLeft = false; 65 | this.cs_borderTop = false; 66 | this.cs_borderRight = false; 67 | this.cs_borderBottom = false; 68 | this.cs_paddingLeft = '2'; 69 | this.cs_paddingTop = '2'; 70 | this.cs_paddingRight = '2'; 71 | this.cs_paddingBottom = '2'; 72 | 73 | this.alwaysPrintOnSamePage = true; 74 | this.pattern = ''; 75 | this.link = ''; 76 | 77 | this.spreadsheet_hide = false; 78 | this.spreadsheet_column = ''; 79 | this.spreadsheet_colspan = ''; 80 | this.spreadsheet_addEmptyRow = false; 81 | this.spreadsheet_type = ''; 82 | this.spreadsheet_pattern = ''; 83 | this.spreadsheet_textWrap = false; 84 | this.setInitialData(initialData); 85 | 86 | this.borderWidthVal = utils.convertInputToNumber(this.borderWidth); 87 | } 88 | 89 | setup(openPanelItem) { 90 | super.setup(openPanelItem); 91 | this.createElement(); 92 | this.updateDisplay(); 93 | this.updateStyle(); 94 | 95 | if (this.richText) { 96 | this.setupRichText(); 97 | } else { 98 | this.updateContent(this.content); 99 | } 100 | } 101 | 102 | handleDoubleClick(event) { 103 | super.handleDoubleClick(event); 104 | // focus text content input element and set caret at end of content 105 | let el = document.getElementById('rbro_doc_element_content'); 106 | el.focus(); 107 | if (typeof el.selectionStart === 'number') { 108 | el.selectionStart = el.selectionEnd = el.value.length; 109 | } 110 | } 111 | 112 | setValue(field, value) { 113 | if (field.indexOf('border') !== -1) { 114 | // Style.setBorderValue needs to be called before super.setValue 115 | // because it calls updateStyle() which expects the correct border settings 116 | this[field] = value; 117 | if (field.substring(0, 3) === 'cs_') { 118 | if (field === 'cs_borderWidth') { 119 | this.borderWidthVal = utils.convertInputToNumber(value); 120 | } 121 | Style.setBorderValue(this, field, 'cs_', value, this.rb); 122 | } else { 123 | if (field === 'borderWidth') { 124 | this.borderWidthVal = utils.convertInputToNumber(value); 125 | } 126 | Style.setBorderValue(this, field, '', value, this.rb); 127 | } 128 | } 129 | 130 | super.setValue(field, value); 131 | 132 | if (field === 'content') { 133 | this.updateContent(value); 134 | } else if (field === 'width' || field === 'height') { 135 | this.updateDisplay(); 136 | } else if (field === 'richText') { 137 | if (value) { 138 | this.setupRichText(); 139 | } else { 140 | this.updateContent(this.content); 141 | } 142 | this.updateStyle(); 143 | } else if (field === 'richTextContent') { 144 | this.updateRichTextContent(value); 145 | } else if (field === 'richTextHtml') { 146 | document.getElementById(`rbro_el_content_text_data${this.id}`).innerHTML = value; 147 | } 148 | } 149 | 150 | /** 151 | * Returns all fields of this object that can be modified in the properties panel. 152 | * @returns {String[]} 153 | */ 154 | getProperties() { 155 | return [ 156 | 'x', 'y', 'width', 'height', 'content', 'richText', 'richTextContent', 'richTextHtml', 'eval', 157 | 'styleId', 'bold', 'italic', 'underline', 'strikethrough', 158 | 'horizontalAlignment', 'verticalAlignment', 'textColor', 'backgroundColor', 'font', 'fontSize', 159 | 'lineSpacing', 'borderColor', 'borderWidth', 160 | 'borderAll', 'borderLeft', 'borderTop', 'borderRight', 'borderBottom', 161 | 'paddingLeft', 'paddingTop', 'paddingRight', 'paddingBottom', 162 | 'printIf', 'removeEmptyElement', 'alwaysPrintOnSamePage', 'pattern', 'link', 163 | 'cs_condition', 'cs_styleId', 'cs_additionalRules', 164 | 'cs_bold', 'cs_italic', 'cs_underline', 'cs_strikethrough', 165 | 'cs_horizontalAlignment', 'cs_verticalAlignment', 166 | 'cs_textColor', 'cs_backgroundColor', 'cs_font', 'cs_fontSize', 167 | 'cs_lineSpacing', 'cs_borderColor', 'cs_borderWidth', 168 | 'cs_borderAll', 'cs_borderLeft', 'cs_borderTop', 'cs_borderRight', 'cs_borderBottom', 169 | 'cs_paddingLeft', 'cs_paddingTop', 'cs_paddingRight', 'cs_paddingBottom', 170 | 'spreadsheet_hide', 'spreadsheet_column', 'spreadsheet_colspan', 171 | 'spreadsheet_addEmptyRow', 'spreadsheet_type', 'spreadsheet_pattern', 'spreadsheet_textWrap' 172 | ]; 173 | } 174 | 175 | getElementType() { 176 | return DocElement.type.text; 177 | } 178 | 179 | updateDisplayInternal(x, y, width, height) { 180 | if (this.el !== null) { 181 | this.el.style.left = this.rb.toPixel(x); 182 | this.el.style.top = this.rb.toPixel(y); 183 | this.el.style.width = this.rb.toPixel(width); 184 | this.el.style.height = this.rb.toPixel(height); 185 | } 186 | // update inner text element size 187 | let contentSize = this.getContentSize(width, height, this.getStyle()); 188 | this.elContentText.style.width = this.rb.toPixel(contentSize.width); 189 | this.elContentText.style.height = this.rb.toPixel(contentSize.height); 190 | } 191 | 192 | getContentSize(width, height, style) { 193 | let borderWidth = style.getValue('borderWidthVal'); 194 | width -= utils.convertInputToNumber(style.getValue('paddingLeft')) + utils.convertInputToNumber(style.getValue('paddingRight')); 195 | if (style.getValue('borderLeft')) { 196 | width -= borderWidth; 197 | } 198 | if (style.getValue('borderRight')) { 199 | width -= borderWidth; 200 | } 201 | height -= utils.convertInputToNumber(style.getValue('paddingTop')) + utils.convertInputToNumber(style.getValue('paddingBottom')); 202 | if (style.getValue('borderTop')) { 203 | height -= borderWidth; 204 | } 205 | if (style.getValue('borderBottom')) { 206 | height -= borderWidth; 207 | } 208 | return { width: width, height: height }; 209 | } 210 | 211 | updateStyle() { 212 | let styleProperties = {}; 213 | let style = this.getStyle(); 214 | let borderStyle, borderWidth = '', borderColor = ''; 215 | let contentSize = this.getContentSize(this.getDisplayWidth(), this.getDisplayHeight(), style); 216 | styleProperties['width'] = this.rb.toPixel(contentSize.width); 217 | styleProperties['height'] = this.rb.toPixel(contentSize.height); 218 | let horizontalAlignment = style.getValue('horizontalAlignment'); 219 | let verticalAlignment = style.getValue('verticalAlignment'); 220 | let alignClass = 'rbroDocElementAlign' + 221 | horizontalAlignment.charAt(0).toUpperCase() + horizontalAlignment.slice(1); 222 | let valignClass = 'rbroDocElementVAlign' + 223 | verticalAlignment.charAt(0).toUpperCase() + verticalAlignment.slice(1); 224 | styleProperties['vertical-align'] = verticalAlignment; 225 | styleProperties['background-color'] = style.getValue('backgroundColor'); 226 | if (!this.richText) { 227 | styleProperties['font-weight'] = style.getValue('bold') ? 'bold' : ''; 228 | styleProperties['font-style'] = style.getValue('italic') ? 'italic' : 'normal'; 229 | if (style.getValue('underline') && style.getValue('strikethrough')) { 230 | styleProperties['text-decoration'] = 'underline line-through'; 231 | } else if (style.getValue('underline')) { 232 | styleProperties['text-decoration'] = 'underline'; 233 | } else if (style.getValue('strikethrough')) { 234 | styleProperties['text-decoration'] = 'line-through'; 235 | } else { 236 | styleProperties['text-decoration'] = 'none'; 237 | } 238 | styleProperties['text-align'] = horizontalAlignment; 239 | styleProperties['color'] = style.getValue('textColor'); 240 | styleProperties['font-family'] = style.getValue('font'); 241 | styleProperties['font-size'] = style.getValue('fontSize') + 'px'; 242 | } else { 243 | // attributes are set by rich text content itself 244 | styleProperties['font-weight'] = 'unset'; 245 | styleProperties['font-style'] = 'normal'; 246 | styleProperties['text-decoration'] = 'none'; 247 | styleProperties['text-align'] = 'unset'; 248 | styleProperties['color'] = '#000000'; 249 | styleProperties['font-family'] = this.rb.getProperty('defaultFont'); 250 | styleProperties['font-size'] = '12px'; 251 | } 252 | styleProperties['line-height'] = style.getValue('lineSpacing'); 253 | if (style.getValue('borderLeft') || style.getValue('borderTop') || 254 | style.getValue('borderRight') || style.getValue('borderBottom')) { 255 | borderStyle = style.getValue('borderTop') ? 'solid' : 'none'; 256 | borderStyle += style.getValue('borderRight') ? ' solid' : ' none'; 257 | borderStyle += style.getValue('borderBottom') ? ' solid' : ' none'; 258 | borderStyle += style.getValue('borderLeft') ? ' solid' : ' none'; 259 | borderWidth = style.getValue('borderWidthVal') + 'px'; 260 | borderColor = style.getValue('borderColor'); 261 | } else { 262 | borderStyle = 'none'; 263 | } 264 | if (style.getValue('paddingLeft') !== '' || style.getValue('paddingTop') !== '' || 265 | style.getValue('paddingRight') !== '' || style.getValue('paddingBottom') !== '') { 266 | styleProperties['padding'] = this.rb.toPixel(style.getValue('paddingTop')); 267 | styleProperties['padding'] += ' ' + this.rb.toPixel(style.getValue('paddingRight')); 268 | styleProperties['padding'] += ' ' + this.rb.toPixel(style.getValue('paddingBottom')); 269 | styleProperties['padding'] += ' ' + this.rb.toPixel(style.getValue('paddingLeft')); 270 | } else { 271 | styleProperties['padding'] = ''; 272 | } 273 | this.elContent.style.borderStyle = borderStyle; 274 | this.elContent.style.borderWidth = borderWidth; 275 | this.elContent.style.borderColor = borderColor; 276 | this.elContent.className = ''; 277 | this.elContent.classList.add('rbroContentContainerHelper'); 278 | this.elContent.classList.add(alignClass); 279 | this.elContent.classList.add(valignClass); 280 | let cssText = ''; 281 | for (const styleName in styleProperties) { 282 | cssText += styleName + ': ' + styleProperties[styleName] + ';'; 283 | } 284 | this.elContentText.style.cssText = cssText; 285 | } 286 | 287 | hasBorderSettings() { 288 | return true; 289 | } 290 | 291 | createElement() { 292 | this.el = utils.createElement('div', { id: `rbro_el${this.id}`, class: 'rbroDocElement rbroTextElement' }); 293 | this.elContent = utils.createElement( 294 | 'div', { id: `rbro_el_content${this.id}`, class: 'rbroContentContainerHelper' }); 295 | this.elContentText = utils.createElement( 296 | 'div', { id: `rbro_el_content_text${this.id}`, class: 'rbroDocElementContentText' }); 297 | this.elContentTextData = utils.createElement('span', { id: `rbro_el_content_text_data${this.id}` }); 298 | 299 | // rbroContentContainerHelper contains border styles and alignment classes 300 | // rbroDocElementContentText contains specific styles 301 | // span is needed to preserve whitespaces and word-wrap of actual text content 302 | this.elContentText.append(this.elContentTextData); 303 | this.elContent.append(this.elContentText); 304 | this.el.append(this.elContent); 305 | 306 | this.appendToContainer(); 307 | this.elContentTextData.textContent = this.content; 308 | super.registerEventHandlers(); 309 | } 310 | 311 | updateContent(value) { 312 | if (value.trim() === '') { 313 | this.name = this.rb.getLabel('docElementText'); 314 | } else { 315 | this.name = value; 316 | } 317 | const elMenuItemName = document.getElementById(`rbro_menu_item_name${this.id}`); 318 | elMenuItemName.textContent = this.name; 319 | elMenuItemName.setAttribute('title', this.name); 320 | this.elContentTextData.textContent = value; 321 | } 322 | 323 | updateRichTextContent(delta) { 324 | let text = ''; 325 | if (delta && delta.ops) { 326 | for (let op of delta.ops) { 327 | if ('insert' in op) { 328 | text += op.insert; 329 | } 330 | } 331 | // remove line breaks 332 | text = text.replace(/(?:\r\n|\r|\n)/g, ' '); 333 | // truncate text if it is too long 334 | if (text.length > 80) { 335 | text = text.substring(0, 80); 336 | } 337 | text = text.trim(); 338 | } 339 | if (text === '') { 340 | this.name = this.rb.getLabel('docElementText'); 341 | } else { 342 | this.name = text; 343 | } 344 | const elMenuItemName = document.getElementById(`rbro_menu_item_name${this.id}`); 345 | elMenuItemName.textContent = this.name; 346 | elMenuItemName.setAttribute('title', this.name); 347 | } 348 | 349 | /** 350 | * Sets name of this element and html using rich text. 351 | * 352 | */ 353 | setupRichText() { 354 | this.updateRichTextContent(this.richTextContent); 355 | this.elContentTextData.innerHTML = this.richTextHtml; 356 | } 357 | 358 | /** 359 | * Adds SetValue commands to command group parameter in case the specified parameter is used in any of 360 | * the object fields. 361 | * @param {Parameter} parameter - parameter which will be renamed. 362 | * @param {String} newParameterName - new name of the parameter. 363 | * @param {CommandGroupCmd} cmdGroup - possible SetValue commands will be added to this command group. 364 | */ 365 | addCommandsForChangedParameterName(parameter, newParameterName, cmdGroup) { 366 | this.addCommandForChangedParameterName(parameter, newParameterName, 'content', cmdGroup); 367 | this.addCommandForChangedParameterName(parameter, newParameterName, 'richTextContent', cmdGroup); 368 | this.addCommandForChangedParameterName(parameter, newParameterName, 'richTextHtml', cmdGroup); 369 | this.addCommandForChangedParameterName(parameter, newParameterName, 'printIf', cmdGroup); 370 | this.addCommandForChangedParameterName(parameter, newParameterName, 'cs_condition', cmdGroup); 371 | } 372 | 373 | toJS() { 374 | const rv = super.toJS(); 375 | const numericFields = ['borderWidth', 'paddingLeft', 'paddingTop', 'paddingRight', 'paddingBottom']; 376 | // watermark text does not have conditional style properties 377 | if (this.getElementType() !== DocElement.type.watermarkText) { 378 | numericFields.push('cs_paddingLeft', 'cs_paddingTop', 'cs_paddingRight', 'cs_paddingBottom'); 379 | } 380 | for (const field of numericFields) { 381 | rv[field] = utils.convertInputToNumber(this.getValue(field)); 382 | } 383 | return rv; 384 | } 385 | 386 | /** 387 | * Returns class name. 388 | * This can be useful for introspection when the class names are mangled 389 | * due to the webpack uglification process. 390 | * @returns {string} 391 | */ 392 | getClassName() { 393 | return 'TextElement'; 394 | } 395 | } 396 | -------------------------------------------------------------------------------- /src/elements/WatermarkImageElement.js: -------------------------------------------------------------------------------- 1 | import ImageElement from './ImageElement'; 2 | import DocElement from './DocElement'; 3 | 4 | /** 5 | * Watermark image element, the image is displayed on the page background. 6 | * @class 7 | */ 8 | export default class WatermarkElement extends ImageElement { 9 | constructor(id, initialData, rb) { 10 | super(id, initialData, rb); 11 | this.rotateDeg = 0; 12 | this.opacity = 30; 13 | this.showInForeground = false; 14 | // watermark properties must be set explicitly because they did not exist in ImageElement constructor 15 | for (const key of ['rotateDeg', 'opacity', 'showInForeground']) { 16 | if (initialData.hasOwnProperty(key) && this.hasOwnProperty(key)) { 17 | this[key] = initialData[key]; 18 | } 19 | } 20 | } 21 | 22 | setValue(field, value) { 23 | super.setValue(field, value); 24 | 25 | if (field === 'rotateDeg' || field === 'opacity') { 26 | this.updateDisplay(); 27 | } 28 | } 29 | 30 | /** 31 | * Returns all fields of this object that can be modified in the properties panel. 32 | * @returns {String[]} 33 | */ 34 | getProperties() { 35 | return [ 36 | 'x', 'y', 'width', 'height', 'source', 'image', 'imageFilename', 37 | 'rotateDeg', 'opacity', 'showInForeground', 38 | 'styleId', 'horizontalAlignment', 'verticalAlignment', 'backgroundColor', 39 | 'printIf', 40 | ]; 41 | } 42 | 43 | getElementType() { 44 | return DocElement.type.watermarkImage; 45 | } 46 | 47 | updateDisplayInternal(x, y, width, height) { 48 | super.updateDisplayInternal(x, y, width, height); 49 | this.el.style.rotate = this.rotateDeg + 'deg'; 50 | this.elContent.style.opacity = this.opacity / 100.0; 51 | } 52 | 53 | /** 54 | * Watermark is only shown when element is selected. 55 | */ 56 | select() { 57 | super.select(); 58 | this.el.classList.remove('rbroHidden'); 59 | this.el.style.zIndex = ''; 60 | } 61 | 62 | deselect() { 63 | super.deselect(); 64 | this.el.classList.add('rbroHidden'); 65 | } 66 | 67 | createElement() { 68 | super.createElement(); 69 | this.el.classList.add('rbroHidden'); 70 | } 71 | 72 | /** 73 | * Returns true if element is restricted within container boundaries. 74 | * 75 | * Watermark elements are not restricted to any containers. 76 | * @returns {boolean} 77 | */ 78 | hasBoundaries() { 79 | return false; 80 | } 81 | 82 | /** 83 | * Returns class name. 84 | * This can be useful for introspection when the class names are mangled 85 | * due to the webpack uglification process. 86 | * @returns {string} 87 | */ 88 | getClassName() { 89 | return 'WatermarkImageElement'; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/elements/WatermarkTextElement.js: -------------------------------------------------------------------------------- 1 | import TextElement from './TextElement'; 2 | import DocElement from './DocElement'; 3 | 4 | /** 5 | * Watermark text element, the text is displayed on the page background. 6 | * @class 7 | */ 8 | export default class WatermarkTextElement extends TextElement { 9 | constructor(id, initialData, rb) { 10 | super(id, initialData, rb); 11 | this.rotateDeg = 0; 12 | this.opacity = 30; 13 | this.showInForeground = false; 14 | // watermark properties must be set explicitly because they did not exist in TextElement constructor 15 | for (const key of ['rotateDeg', 'opacity', 'showInForeground']) { 16 | if (initialData.hasOwnProperty(key) && this.hasOwnProperty(key)) { 17 | this[key] = initialData[key]; 18 | } 19 | } 20 | } 21 | 22 | setValue(field, value) { 23 | super.setValue(field, value); 24 | 25 | if (field === 'rotateDeg' || field === 'opacity') { 26 | this.updateDisplay(); 27 | } 28 | } 29 | 30 | /** 31 | * Returns all fields of this object that can be modified in the properties panel. 32 | * @returns {String[]} 33 | */ 34 | getProperties() { 35 | return [ 36 | 'x', 'y', 'width', 'height', 'content', 'eval', 'rotateDeg', 'opacity', 'showInForeground', 37 | 'styleId', 'bold', 'italic', 'underline', 'strikethrough', 38 | 'horizontalAlignment', 'verticalAlignment', 'textColor', 'backgroundColor', 'font', 'fontSize', 39 | 'lineSpacing', 'borderColor', 'borderWidth', 40 | 'borderAll', 'borderLeft', 'borderTop', 'borderRight', 'borderBottom', 41 | 'paddingLeft', 'paddingTop', 'paddingRight', 'paddingBottom', 42 | 'printIf', 43 | ]; 44 | } 45 | 46 | getElementType() { 47 | return DocElement.type.watermarkText; 48 | } 49 | 50 | updateDisplayInternal(x, y, width, height) { 51 | super.updateDisplayInternal(x, y, width, height); 52 | this.el.style.rotate = this.rotateDeg + 'deg'; 53 | this.elContent.style.opacity = this.opacity / 100.0; 54 | } 55 | 56 | /** 57 | * Watermark is only shown when element is selected. 58 | */ 59 | select() { 60 | super.select(); 61 | this.el.classList.remove('rbroHidden'); 62 | this.el.style.zIndex = ''; 63 | } 64 | 65 | deselect() { 66 | super.deselect(); 67 | this.el.classList.add('rbroHidden'); 68 | } 69 | 70 | createElement() { 71 | super.createElement(); 72 | this.el.classList.add('rbroHidden'); 73 | } 74 | 75 | /** 76 | * Returns true if element is restricted within container boundaries. 77 | * 78 | * Watermark elements are not restricted to any containers. 79 | * @returns {boolean} 80 | */ 81 | hasBoundaries() { 82 | return false; 83 | } 84 | 85 | /** 86 | * Returns class name. 87 | * This can be useful for introspection when the class names are mangled 88 | * due to the webpack uglification process. 89 | * @returns {string} 90 | */ 91 | getClassName() { 92 | return 'WatermarkTextElement'; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/fonts/font_style.css: -------------------------------------------------------------------------------- 1 | /* open-sans-300 - latin */ 2 | @font-face { 3 | font-family: 'Open Sans'; 4 | font-style: normal; 5 | font-weight: 300; 6 | src: local(''), 7 | url('open-sans-v34-latin-300.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */ 8 | url('open-sans-v34-latin-300.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ 9 | } 10 | 11 | /* open-sans-regular - latin */ 12 | @font-face { 13 | font-family: 'Open Sans'; 14 | font-style: normal; 15 | font-weight: 400; 16 | src: local(''), 17 | url('open-sans-v34-latin-regular.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */ 18 | url('open-sans-v34-latin-regular.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ 19 | } 20 | 21 | /* open-sans-600 - latin */ 22 | @font-face { 23 | font-family: 'Open Sans'; 24 | font-style: normal; 25 | font-weight: 600; 26 | src: local(''), 27 | url('open-sans-v34-latin-600.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */ 28 | url('open-sans-v34-latin-600.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ 29 | } 30 | 31 | /* open-sans-800 - latin */ 32 | @font-face { 33 | font-family: 'Open Sans'; 34 | font-style: normal; 35 | font-weight: 800; 36 | src: local(''), 37 | url('open-sans-v34-latin-800.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */ 38 | url('open-sans-v34-latin-800.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ 39 | } 40 | -------------------------------------------------------------------------------- /src/fonts/open-sans-v34-latin-300.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobsta/reportbro-designer/7f482651dd25bc6bb968c0402df6c492d03ecde1/src/fonts/open-sans-v34-latin-300.woff -------------------------------------------------------------------------------- /src/fonts/open-sans-v34-latin-300.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobsta/reportbro-designer/7f482651dd25bc6bb968c0402df6c492d03ecde1/src/fonts/open-sans-v34-latin-300.woff2 -------------------------------------------------------------------------------- /src/fonts/open-sans-v34-latin-600.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobsta/reportbro-designer/7f482651dd25bc6bb968c0402df6c492d03ecde1/src/fonts/open-sans-v34-latin-600.woff -------------------------------------------------------------------------------- /src/fonts/open-sans-v34-latin-600.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobsta/reportbro-designer/7f482651dd25bc6bb968c0402df6c492d03ecde1/src/fonts/open-sans-v34-latin-600.woff2 -------------------------------------------------------------------------------- /src/fonts/open-sans-v34-latin-800.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobsta/reportbro-designer/7f482651dd25bc6bb968c0402df6c492d03ecde1/src/fonts/open-sans-v34-latin-800.woff -------------------------------------------------------------------------------- /src/fonts/open-sans-v34-latin-800.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobsta/reportbro-designer/7f482651dd25bc6bb968c0402df6c492d03ecde1/src/fonts/open-sans-v34-latin-800.woff2 -------------------------------------------------------------------------------- /src/fonts/open-sans-v34-latin-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobsta/reportbro-designer/7f482651dd25bc6bb968c0402df6c492d03ecde1/src/fonts/open-sans-v34-latin-regular.woff -------------------------------------------------------------------------------- /src/fonts/open-sans-v34-latin-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobsta/reportbro-designer/7f482651dd25bc6bb968c0402df6c492d03ecde1/src/fonts/open-sans-v34-latin-regular.woff2 -------------------------------------------------------------------------------- /src/i18n/locale_de_de.js: -------------------------------------------------------------------------------- 1 | const locale_de = { 2 | bandContent: 'Inhalt', 3 | bandFooter: 'Fußzeile', 4 | bandHeader: 'Kopfzeile', 5 | clear: 'Löschen', 6 | contentHeight: 'Inhaltshöhe', 7 | contentHeightInfo: 'Höhe des Inhaltsbereichs, um Elemente zu platzieren (betrifft nicht die tatsächliche Seitengröße)', 8 | differentFiles: 'versch. Dateien...', 9 | differentValues: 'versch. Werte...', 10 | docElementAdditionalStyles: 'Zusätzliche Formatvorlagen', 11 | docElementAlignToPageBottom: 'Am unteren Seitenrand ausrichten', 12 | docElementAlternateBackgroundColor: 'Abwechselnde Hintergrundfarbe', 13 | docElementAlwaysPrintOnSamePage: 'Immer zusammen auf einer Seite', 14 | docElementBarCode: 'Barcode', 15 | docElementBarWidth: 'Balkenbreite', 16 | docElementBorderFrame: 'außen', 17 | docElementBorderFrameRow: 'Rahmen und Zeile', 18 | docElementBorderGrid: 'Alle Rahmenlinien', 19 | docElementBorderNone: 'Keiner', 20 | docElementBorderRow: 'Zeilen', 21 | docElementColor: 'Farbe', 22 | docElementColspan: 'Anz. verbundene Zellen', 23 | docElementColumns: 'Spalten', 24 | docElementConditionalStyle: 'Bedingte Formatierung', 25 | docElementConditionalStyleCondition: 'Bedingung', 26 | docElementContent: 'Text', 27 | docElementContentRows: 'Inhaltszeilen', 28 | docElementDataSource: 'Datenquelle', 29 | docElementDisplayValue: 'Wert anzeigen', 30 | docElementEditMultipleSelectionNotSupported: 'Bearbeiten von Mehrfachauswahl wird für diese Einstellung nicht unterstützt', 31 | docElementErrorCorrectionLevel: 'Fehlerkorrektur', 32 | docElementErrorCorrectionLevelHigh: 'Hoch', 33 | docElementErrorCorrectionLevelLow: 'Niedrig', 34 | docElementErrorCorrectionLevelMedium: 'Mittel', 35 | docElementErrorCorrectionLevelQuartile: 'Quartil', 36 | docElementEval: 'Auswerten', 37 | docElementFormat: 'Format', 38 | docElementFrame: 'Rahmen', 39 | docElementGroupExpression: 'Gruppen Ausdruck', 40 | docElementGrowWeight: 'Wachstums-Gewicht', 41 | docElementGrowWeightInfo: 'Wachstumsfaktor zur Verwendung des Platzes von ausgeblendeten Spalten', 42 | docElementGrowWeightHigh: 'hoch', 43 | docElementGrowWeightLow: 'niedrig', 44 | docElementGuardBar: 'Rand-/Trennzeichen', 45 | docElementHeight: 'Höhe', 46 | docElementImage: 'Bild', 47 | docElementImageCountExceeded: 'Es sind nur bis zu ${count} Bilder erlaubt', 48 | docElementImageFile: 'Bilddatei', 49 | docElementLabel: 'Bezeichnung', 50 | docElementLine: 'Linie', 51 | docElementLink: 'Link', 52 | docElementLoadImageErrorMsg: 'Bild laden fehlgeschlagen', 53 | docElementLoadImageWebPErrorMsg: 'Bild laden fehlgeschlagen, aktueller Browser benötigt', 54 | docElementOpacity: 'Deckkraft %', 55 | docElementPageBreak: 'Seitenumbruch', 56 | docElementPattern: 'Pattern', 57 | docElementPosition: 'Position (x, y)', 58 | docElementPositionX: 'Position (x)', 59 | docElementPositionY: 'Position (y)', 60 | docElementPrintIf: 'Anzeigen wenn', 61 | docElementPrintSettings: 'Anzeige', 62 | docElementRemoveEmptyElement: 'Entfernen wenn nicht vorhanden', 63 | docElementRepeatGroupHeader: 'Gruppe auf jeder Seite wiederholen', 64 | docElementRepeatHeader: 'Auf jeder Seite wiederholen', 65 | docElementRichText: 'Rich Text', 66 | docElementRoot: 'Dokument', 67 | docElementRotate: 'Drehen', 68 | docElementRotateDeg: 'Drehen °', 69 | docElementSection: 'Sektion', 70 | docElementShowInForeground: 'Im Vorderground anzeigen', 71 | docElementShrinkToContentHeight: 'Auf Inhaltshöhe reduzieren', 72 | docElementSize: 'Größe (Breite, Höhe)', 73 | docElementSource: 'Bildquelle', 74 | docElementSpreadsheet: 'Tabellenkalkulation', 75 | docElementSpreadsheetAddEmptyRow: 'Leere Zeile unterhalb einfügen', 76 | docElementSpreadsheetColspan: 'Anz. verbundene Zellen', 77 | docElementSpreadsheetColumn: 'Fixe Spalte', 78 | docElementSpreadsheetHide: 'Ausblenden', 79 | docElementSpreadsheetPattern: 'Pattern', 80 | docElementSpreadsheetTextWrap: 'Zeilenumbruch', 81 | docElementSpreadsheetType: 'Typ', 82 | docElementSpreadsheetTypeDate: 'Datum', 83 | docElementSpreadsheetTypeNone: 'Standard', 84 | docElementSpreadsheetTypeNumber: 'Number', 85 | docElementStyle: 'Formatvorlage', 86 | docElementTable: 'Tabelle', 87 | docElementTableBandPageBreak: 'Seitenumbruch mit jeder neuen Gruppe', 88 | docElementText: 'Text', 89 | docElementWidth: 'Breite', 90 | documentProperties: 'Dokumenteinstellungen', 91 | documentTabClose: 'Schließen', 92 | documentTabPdfLayout: 'PDF Layout', 93 | documentTabPdfPreview: 'PDF Vorschau', 94 | documentTabXlsxDownload: 'XLSX Download', 95 | editData: 'Bearbeiten', 96 | emptyPanel: 'Leer', 97 | errorMsgAddtionalRulesNoStyleSelected: 'Keine Formatvorlage für Regel {info} ausgewählt', 98 | errorMsgDuplicateParameter: 'Parameter existiert bereits', 99 | errorMsgDuplicateParameterField: 'Feld existiert bereits', 100 | errorMsgExternalImageNotAllowed: 'Server unterstützt das Laden von Bildern von externen Resourcen nicht', 101 | errorMsgFontNotAvailable: 'Schrift ist am Server nicht vorhanden', 102 | errorMsgGroupExpressionWithoutDataSource: 'Gruppen Ausdruck benötigt Tabelle mit Datenquelle', 103 | errorMsgLoadingImageFailed: 'Bild laden fehlgeschlagen: ${info}', 104 | errorMsgInvalidArray: 'Ungültige Liste', 105 | errorMsgInvalidAvgSumExpression: 'Ausdruck muss ein Zahl-Feld einer Liste enthalten', 106 | errorMsgInvalidBarCode: 'Ungültiger Barcode Inhalt', 107 | errorMsgInvalidDataSource: 'Ungültige Datenquelle', 108 | errorMsgInvalidDataSourceParameter: 'Parameter muss eine Liste sein', 109 | errorMsgInvalidDate: 'Ungültiges Datum, erwartetes Format ist JJJJ-MM-TT (bzw. JJJJ-MM-TT hh:mm für Datum mit Uhrzeit)', 110 | errorMsgInvalidStringData: 'Ungültiger Inhalt für Text Parameter ${info}', 111 | errorMsgInvalidExpression: 'Ungültiger Ausdruck: ${info}', 112 | errorMsgInvalidExpressionFuncNotDefined: 'Funktion ${info} ist nicht definiert', 113 | errorMsgInvalidExpressionNameNotDefined: 'Name ${info} ist nicht definiert', 114 | errorMsgInvalidExpressionType: 'Ausdruck liefert ungültigen Typ', 115 | errorMsgInvalidLink: 'Link muss mit http:// oder https:// beginnen', 116 | errorMsgInvalidImage: 'Ungültige Bilddaten, Bild muss base64 kodiert sein', 117 | errorMsgInvalidImageSource: 'Ungültige Bildquelle, Url beginnend mit http:// bzw. https:// erwartet', 118 | errorMsgInvalidImageSourceParameter: 'Parameter vom Typ Bild oder String (mit einer Url) notwendig', 119 | errorMsgInvalidMap: 'Ungültige Auflistung', 120 | errorMsgInvalidNumber: 'Ungültige Zahl', 121 | errorMsgInvalidPageSize: 'Ungültige Seitengröße', 122 | errorMsgInvalidParameterData: 'Daten stimmen nicht mit Parameter überein', 123 | errorMsgInvalidParameterName: 'Name muss mit einem Zeichen oder Unterstrich beginnen und darf nur Zeichen, Ziffern und Unterstriche enthalten', 124 | errorMsgInvalidPattern: 'Ungültiges Pattern', 125 | errorMsgInvalidPosition: 'Die Position ist außerhalb des Bereichs', 126 | errorMsgInvalidRichTextFontNotAvailable: 'Der Rich Text enthält eine Schrift, welche am Server nicht vorhanden ist', 127 | errorMsgInvalidRichTextParameter: 'Der Rich Text enthält ungültige Tags', 128 | errorMsgInvalidSize: 'Das Element ist außerhalb des Bereichs', 129 | errorMsgInvalidSpreadsheetDate: 'Inhalt kann nicht zu Datum konvertiert werden, erwartetes Format ist JJJJ-MM-TT (bzw. JJJJ-MM-TT hh:mm für Datum mit Uhrzeit)', 130 | errorMsgInvalidSpreadsheetNumber: 'Inhalt kann nicht zu Zahl konvertiert werden', 131 | errorMsgInvalidTestData: 'Ungültige Testdaten', 132 | errorMsgMissingData: 'Fehlende Daten', 133 | errorMsgMissingDataSourceParameter: 'Datenquelle Parameter nicht gefunden', 134 | errorMsgMissingExpression: 'Ausdruck muss gesetzt sein', 135 | errorMsgMissingGlyph: 'Text enthält Zeichen, die in der ausgewählten Schrift nicht verfügbar sind', 136 | errorMsgMissingImage: 'Fehlendes Bild: Keine Bildquelle oder -datei angegeben', 137 | errorMsgMissingParameter: 'Parameter nicht gefunden', 138 | errorMsgMissingParameterData: 'Daten für Parameter ${info} nicht gefunden', 139 | errorMsgRepeatGroupHeaderAfterContent: 'Nicht erlaubt für Gruppe nach Inhaltszeile', 140 | errorMsgPlusVersionRequired: 'Benötigt kommerzielle PLUS Version', 141 | errorMsgSectionBandNotOnSamePage: 'Abschnittsbereich passt nicht auf eine Seite', 142 | errorMsgSectionBandPageBreakNotAllowed: 'Manueller Seitenumbruch ist nicht erlaubt', 143 | errorMsgUnicodeEncodeError: 'Text enthält nicht druckbare Zeichen', 144 | errorMsgUnsupportedImageType: 'Bildtyp wird nicht unterstützt (nur .jpg und .png erlaubt)', 145 | footer: 'Fußzeile', 146 | footerDisplay: 'Anzeige', 147 | footerSize: 'Höhe Fußzeile', 148 | header: 'Kopfzeile', 149 | headerDisplay: 'Anzeige', 150 | headerFooterDisplayAlways: 'Immer', 151 | headerFooterDisplayNotOnFirstPage: 'Nicht auf erster Seite', 152 | headerSize: 'Höhe Kopfzeile', 153 | menuColumnAddLeft: 'Spalte links hinzufügen', 154 | menuColumnAddRight: 'Spalte rechts hinzufügen', 155 | menuColumnDelete: 'Spalte löschen', 156 | menuAlignBottom: 'Unten ausrichten', 157 | menuAlignCenter: 'Zentriert ausrichten', 158 | menuAlignLeft: 'Links ausrichten', 159 | menuAlignMiddle: 'Mittig ausrichten', 160 | menuAlignRight: 'Rechts ausrichten', 161 | menuAlignTop: 'Oben ausrichten', 162 | menuInsertReport: 'EINF.', 163 | menuInsertReportTip: 'Report-Vorlage einfügen', 164 | menuLogReport: 'LOG', 165 | menuLogReportTip: 'Report-Vorlage in Konsole ausgeben', 166 | menuPreview: 'VORSCHAU', 167 | menuPreviewTip: 'Report-Vorschau', 168 | menuRedo: 'WIEDERH.', 169 | menuRedoTip: 'Letzten rückgängig gemachten Befehl wiederholen', 170 | menuRowAddAbove: 'Zeile oberhalb hinzufügen', 171 | menuRowAddBelow: 'Zeile unterhalb hinzufügen', 172 | menuRowDelete: 'Zeile löschen', 173 | menuSave: 'SPEICHERN', 174 | menuSaveTip: 'Report speichern', 175 | menuToggleGrid: 'Raster ein-/ausblenden', 176 | menuUndo: 'RÜCKG.', 177 | menuUndoTip: 'Letzten Befehl rückgängig machen', 178 | menuZoomIn: 'Vergrößern', 179 | menuZoomOut: 'Verkleinern', 180 | nameCopySuffix: 'Kopie', 181 | orientation: 'Ausrichtung', 182 | orientationBottom: 'unten', 183 | orientationLandscape: 'Querformat', 184 | orientationLeft: 'links', 185 | orientationPortrait: 'Hochformat', 186 | orientationRight: 'rechts', 187 | orientationTop: 'oben', 188 | pageFormat: 'Seitenformat', 189 | pageFormatA4: 'DIN A4 (210 x 297 mm)', 190 | pageFormatA5: 'DIN A5 (148 x 210 mm)', 191 | pageFormatLetter: 'Brief (216 x 279 mm)', 192 | pageFormatUserDefined: 'Eigene Einstellung', 193 | pageHeight: 'Höhe', 194 | pageMargins: 'Seitenränder', 195 | pageWidth: 'Breite', 196 | parameter: 'Parameter', 197 | parameterArrayItemType: 'Listenelement-Typ', 198 | parameterEditTestData: 'Bearbeiten', 199 | parameterEditTestDataArrayNoFields: 'Keine Datenfelder für diese Liste definiert', 200 | parameterEditTestDataMapNoFields: 'Keine Datenfelder für diese Gruppierung definiert', 201 | parameterEval: 'Text auswerten', 202 | parameterExpression: 'Ausdruck', 203 | parameterListType: 'Listen-Typ', 204 | parameterName: 'Name', 205 | parameterNullable: 'NULL-Wert erlaubt', 206 | parameterPattern: 'Pattern', 207 | parameterRowParams: 'Parameter in Zeile', 208 | parameterSearchPlaceholder: 'Parameter durchsuchen...', 209 | parameterTestData: 'Testdaten', 210 | parameterTestDataDatePattern: 'JJJJ-MM-TT', 211 | parameterTestDataImageInfo: 'Bilder werden in der Berichtsvorlage gespeichert und können die Größe der Berichtsvorlage erheblich erhöhen', 212 | parameterTestDataRichTextInfo: 'Rich Text kann Html-Tags zur Formatierung enthalten. Siehe erlaubte Tags', 213 | parameterTestDataRowCount: '${count} Zeilen', 214 | parameterTestDataRowCountEmpty: 'leer', 215 | parameterTestDataRowCountOne: '1 Zeile', 216 | parameterType: 'Typ', 217 | parameterTypeArray: 'Liste', 218 | parameterTypeAverage: 'Durchschnitt', 219 | parameterTypeBoolean: 'Boolean', 220 | parameterTypeDate: 'Datum', 221 | parameterTypeImage: 'Bild', 222 | parameterTypeMap: 'Gruppierung', 223 | parameterTypeNumber: 'Zahl', 224 | parameterTypeRichText: 'Rich Text', 225 | parameterTypeSimpleArray: 'Einfache Liste', 226 | parameterTypeString: 'String', 227 | parameterTypeSum: 'Summe', 228 | parameters: 'Parameter', 229 | parametersDataSource: 'Datenquelle Parameter', 230 | parametersDataSourceName: '${name} Parameter', 231 | patternCurrencySymbol: 'Währungssymbol', 232 | patternDate1: 'Tag.Monat.Jahr, z.B. 1.6.1980', 233 | patternDate2: 'Tag.Monat.Jahr (2-stellig), Stunde(24h):Minute, z.B. 1.6.80, 14:30', 234 | patternDate3: 'Tag/Monat/Jahr (Monat abgekürzt), z.B. 1/Jun/1980', 235 | patternDate4: 'Monat/Tag/Jahr (Tag und Monat mit führender Null, falls einstellig), z.B. 06/01/1980', 236 | patternLocale: 'Pattern Locale', 237 | patternNumber1: 'Tausender-Trennzeichen', 238 | patternNumber2: 'Dezimalpunkt gefolgt von 3 Dezimalstellen', 239 | patternNumber3: 'Dezimalpunkt gefolgt von mind. 2 und max. 4 Dezimalstellen', 240 | patternNumber4: 'Tausender-Trennzeichen und Dezimalpunkt gefolgt von 2 Dezimalstellen', 241 | patternNumber5: 'Währungssymbol vor Zahl', 242 | patternNumberGroupSymbol: 'Tausender-Trennzeichen', 243 | patternNumberGroupSymbolInfo: 'leer lassen für Standardzeichen', 244 | patternSeparatorDates: '--- Datum Pattern ---', 245 | patternSeparatorNumbers: '--- Zahlen Pattern ---', 246 | plusFeatureInfo: 'Benötigt kommerzielle PLUS Version', 247 | plusFeatureInfoNestedParameter: 'Verschachtelter Parameter benötigt kommerzielle PLUS Version', 248 | popupWindowAddDataRow: 'Zeile hinzufügen', 249 | select: 'auswählen...', 250 | style: 'Formatvorlage', 251 | styleAlignment: 'Ausrichtung', 252 | styleBackgroundColor: 'Hintergrundfarbe', 253 | styleBold: 'Fett', 254 | styleBorder: 'Rahmen', 255 | styleBorderAll: 'vollständig', 256 | styleBorderColor: 'Rahmenfarbe', 257 | styleBorderWidth: 'Rahmenbreite', 258 | styleFont: 'Schrift', 259 | styleFontSizeUnit: 'pt', 260 | styleHAlignmentCenter: 'Zentriert', 261 | styleHAlignmentLeft: 'Links', 262 | styleHAlignmentJustify: 'Blocksatz', 263 | styleHAlignmentRight: 'Rechts', 264 | styleItalic: 'Kursiv', 265 | styleLineSpacing: 'Linienabstand', 266 | styleName: 'Name', 267 | styleNone: 'Keine', 268 | stylePadding: 'Innenabstand', 269 | styleStrikethrough: 'Durchgestrichen', 270 | styleTextColor: 'Textfarbe', 271 | styleTextStyle: 'Formatierung', 272 | styleType: 'Element', 273 | styleTypeFrame: 'Rahmen', 274 | styleTypeImage: 'Bild', 275 | styleTypeLine: 'Linie', 276 | styleTypeSectionBand: 'Sektionsbereich', 277 | styleTypeTable: 'Tabelle', 278 | styleTypeTableBand: 'Tabellenzeile', 279 | styleTypeText: 'Text', 280 | styleUnderline: 'Unterstreichen', 281 | styleVAlignmentBottom: 'Unten', 282 | styleVAlignmentMiddle: 'Mittig', 283 | styleVAlignmentTop: 'Oben', 284 | styles: 'Formatvorlagen', 285 | watermarkImages: 'Bild-Wasserzeichen', 286 | watermarkTexts: 'Text-Wasserzeichen', 287 | watermarks: 'Wasserzeichen', 288 | }; 289 | 290 | export default locale_de; 291 | -------------------------------------------------------------------------------- /src/i18n/locale_en_us.js: -------------------------------------------------------------------------------- 1 | const locale_en = { 2 | bandContent: 'Content', 3 | bandFooter: 'Footer', 4 | bandHeader: 'Header', 5 | clear: 'Clear', 6 | contentHeight: 'Content height', 7 | contentHeightInfo: 'affects only GUI size to place elements and not the real page size', 8 | differentFiles: 'different files...', 9 | differentValues: 'different values...', 10 | docElementAdditionalRules: 'Additional rules', 11 | docElementAlignToPageBottom: 'Align to bottom of page', 12 | docElementAlternateBackgroundColor: 'Alternate background color', 13 | docElementAlwaysPrintOnSamePage: 'Always on same page', 14 | docElementBarCode: 'Bar code', 15 | docElementBarWidth: 'Bar width', 16 | docElementBorderFrame: 'Frame', 17 | docElementBorderFrameRow: 'Frame and row', 18 | docElementBorderGrid: 'Grid', 19 | docElementBorderNone: 'None', 20 | docElementBorderRow: 'Row', 21 | docElementColor: 'Color', 22 | docElementColspan: 'Colspan', 23 | docElementColumns: 'Columns', 24 | docElementConditionalStyle: 'Conditional style', 25 | docElementConditionalStyleCondition: 'Condition', 26 | docElementContent: 'Text', 27 | docElementContentRows: 'Content rows', 28 | docElementDataSource: 'Data source', 29 | docElementDisplayValue: 'Display value', 30 | docElementEditMultipleSelectionNotSupported: 'Edit multiple selection is not supported for this setting', 31 | docElementErrorCorrectionLevel: 'Error correction', 32 | docElementErrorCorrectionLevelHigh: 'High', 33 | docElementErrorCorrectionLevelLow: 'Low', 34 | docElementErrorCorrectionLevelMedium: 'Medium', 35 | docElementErrorCorrectionLevelQuartile: 'Quartile', 36 | docElementEval: 'Evaluate', 37 | docElementFormat: 'Format', 38 | docElementFrame: 'Frame', 39 | docElementGroupExpression: 'Group expression', 40 | docElementGrowWeight: 'Grow weight', 41 | docElementGrowWeightInfo: 'Grow factor to take space of hidden columns', 42 | docElementGrowWeightHigh: 'high', 43 | docElementGrowWeightLow: 'low', 44 | docElementGuardBar: 'Guard bars', 45 | docElementHeight: 'Height', 46 | docElementImage: 'Image', 47 | docElementImageCountExceeded: 'Reached maximum number of ${count} allowed images', 48 | docElementImageFile: 'Image file', 49 | docElementLabel: 'Label', 50 | docElementLine: 'Line', 51 | docElementLink: 'Link', 52 | docElementLoadImageErrorMsg: 'Loading image failed', 53 | docElementLoadImageWebPErrorMsg: 'Loading image failed, upgrade browser to latest version', 54 | docElementOpacity: 'Opacity %', 55 | docElementPageBreak: 'Page break', 56 | docElementPattern: 'Pattern', 57 | docElementPosition: 'Position (x, y)', 58 | docElementPositionX: 'Position (x)', 59 | docElementPositionY: 'Position (y)', 60 | docElementPrintIf: 'Print if', 61 | docElementPrintSettings: 'Print settings', 62 | docElementRemoveEmptyElement: 'Remove when empty', 63 | docElementRepeatGroupHeader: 'Repeat group on each page', 64 | docElementRepeatHeader: 'Repeat header', 65 | docElementRichText: 'Rich text', 66 | docElementRoot: 'Document', 67 | docElementRotate: 'Rotate', 68 | docElementRotateDeg: 'Rotate °', 69 | docElementSection: 'Section', 70 | docElementShowInForeground: 'Show in foreground', 71 | docElementShrinkToContentHeight: 'Shrink to content height', 72 | docElementSize: 'Size (width, height)', 73 | docElementSource: 'Source', 74 | docElementSpreadsheet: 'Spreadsheet', 75 | docElementSpreadsheetAddEmptyRow: 'Add empty row below', 76 | docElementSpreadsheetColspan: 'Colspan', 77 | docElementSpreadsheetColumn: 'Fixed column', 78 | docElementSpreadsheetHide: 'Hide', 79 | docElementSpreadsheetPattern: 'Pattern', 80 | docElementSpreadsheetTextWrap: 'Text wrap', 81 | docElementSpreadsheetType: 'Type', 82 | docElementSpreadsheetTypeDate: 'Date', 83 | docElementSpreadsheetTypeNone: 'Default', 84 | docElementSpreadsheetTypeNumber: 'Number', 85 | docElementStyle: 'Style', 86 | docElementTable: 'Table', 87 | docElementTableBandPageBreak: 'Page break with each new group', 88 | docElementText: 'Text', 89 | docElementWidth: 'Width', 90 | documentProperties: 'Document properties', 91 | documentTabClose: 'Close', 92 | documentTabPdfLayout: 'PDF Layout', 93 | documentTabPdfPreview: 'PDF Preview', 94 | documentTabXlsxDownload: 'XLSX Download', 95 | editData: 'Edit', 96 | emptyPanel: 'Empty panel', 97 | errorMsgAddtionalRulesNoStyleSelected: 'No style selected for rule ${info}', 98 | errorMsgDuplicateParameter: 'Parameter already exists', 99 | errorMsgDuplicateParameterField: 'Field already exists', 100 | errorMsgExternalImageNotAllowed: 'Server does not support loading images from external resources', 101 | errorMsgFontNotAvailable: 'Font not available on server', 102 | errorMsgGroupExpressionWithoutDataSource: 'Group expression requires table with data source', 103 | errorMsgLoadingImageFailed: 'Loading image failed: ${info}', 104 | errorMsgInvalidArray: 'Invalid list', 105 | errorMsgInvalidAvgSumExpression: 'Expression must contain number field of a list parameter', 106 | errorMsgInvalidBarCode: 'Invalid bar code content', 107 | errorMsgInvalidDataSource: 'Invalid data source', 108 | errorMsgInvalidDataSourceParameter: 'Parameter must be a list', 109 | errorMsgInvalidDate: 'Invalid date, expected format is YYYY-MM-DD (or YYYY-MM-DD hh:mm for date with time)', 110 | errorMsgInvalidStringData: 'Invalid data for text parameter ${info}', 111 | errorMsgInvalidExpression: 'Invalid expression: ${info}', 112 | errorMsgInvalidExpressionFuncNotDefined: 'Function ${info} not defined', 113 | errorMsgInvalidExpressionNameNotDefined: 'Name ${info} not defined', 114 | errorMsgInvalidExpressionType: 'Expression returns invalid type', 115 | errorMsgInvalidLink: 'Link must start with http:// or https://', 116 | errorMsgInvalidImage: 'Invalid image data, image must be base64 encoded', 117 | errorMsgInvalidImageSource: 'Invalid source, expected url starting with http:// or https:// respectively', 118 | errorMsgInvalidImageSourceParameter: 'Parameter must be an image or string (containing a url)', 119 | errorMsgInvalidMap: 'Invalid collection', 120 | errorMsgInvalidNumber: 'Invalid number', 121 | errorMsgInvalidPageSize: 'Invalid page size', 122 | errorMsgInvalidParameterData: 'Data does not match parameter', 123 | errorMsgInvalidParameterName: 'Name must start with a character or underscore, and must only contain characters, digits and underscores (_)', 124 | errorMsgInvalidPattern: 'Invalid pattern', 125 | errorMsgInvalidPosition: 'The position is outside the area', 126 | errorMsgInvalidRichTextFontNotAvailable: 'The rich text contains a font which is not available on server', 127 | errorMsgInvalidRichTextParameter: 'The rich text contains invalid tags', 128 | errorMsgInvalidSize: 'The element is outside the area', 129 | errorMsgInvalidSpreadsheetDate: 'The data cannot be converted to date, expected format is YYYY-MM-DD (or YYYY-MM-DD hh:mm for date with time)', 130 | errorMsgInvalidSpreadsheetNumber: 'The data cannot be converted to number', 131 | errorMsgInvalidTestData: 'Invalid test data', 132 | errorMsgMissingData: 'Missing data', 133 | errorMsgMissingDataSourceParameter: 'Data source parameter not found', 134 | errorMsgMissingExpression: 'Expression must be set', 135 | errorMsgMissingGlyph: 'Text contains characters that are not available in the selected font', 136 | errorMsgMissingImage: 'Missing image, no source or image file specified', 137 | errorMsgMissingParameter: 'Parameter not found', 138 | errorMsgMissingParameterData: 'Data for parameter ${info} not found', 139 | errorMsgRepeatGroupHeaderAfterContent: 'Not allowed for group after content row', 140 | errorMsgPlusVersionRequired: 'Requires commerical PLUS version', 141 | errorMsgSectionBandNotOnSamePage: 'Section band does not fit on same page', 142 | errorMsgSectionBandPageBreakNotAllowed: 'Manual page break not allowed', 143 | errorMsgUnicodeEncodeError: 'Text contains non printable character', 144 | errorMsgUnsupportedImageType: 'Image does not have supported image type (.jpg, .png)', 145 | footer: 'Footer', 146 | footerDisplay: 'Display', 147 | footerSize: 'Footer size', 148 | header: 'Header', 149 | headerDisplay: 'Display', 150 | headerFooterDisplayAlways: 'Always', 151 | headerFooterDisplayNotOnFirstPage: 'Do not show on first page', 152 | headerSize: 'Header size', 153 | menuColumnAddLeft: 'Add column to the left', 154 | menuColumnAddRight: 'Add column to the right', 155 | menuColumnDelete: 'Delete column', 156 | menuAlignBottom: 'Align bottom', 157 | menuAlignCenter: 'Align center', 158 | menuAlignLeft: 'Align left', 159 | menuAlignMiddle: 'Align middle', 160 | menuAlignRight: 'Align right', 161 | menuAlignTop: 'Align top', 162 | menuInsertReport: 'INSERT', 163 | menuInsertReportTip: 'Insert report template', 164 | menuLogReport: 'LOG', 165 | menuLogReportTip: 'Log report template to console', 166 | menuPreview: 'PREVIEW', 167 | menuPreviewTip: 'Preview report', 168 | menuRedo: 'REDO', 169 | menuRedoTip: 'Repeat last undone command', 170 | menuRowAddAbove: 'Add row above', 171 | menuRowAddBelow: 'Add row below', 172 | menuRowDelete: 'Delete row', 173 | menuSave: 'SAVE', 174 | menuSaveTip: 'Save report', 175 | menuToggleGrid: 'Show/Hide grid', 176 | menuUndo: 'UNDO', 177 | menuUndoTip: 'Undo last command', 178 | menuZoomIn: 'Zoom in', 179 | menuZoomOut: 'Zoom out', 180 | nameCopySuffix: 'copy', 181 | orientation: 'Orientation', 182 | orientationBottom: 'bottom', 183 | orientationLandscape: 'Landscape', 184 | orientationLeft: 'left', 185 | orientationPortrait: 'Portrait', 186 | orientationRight: 'right', 187 | orientationTop: 'top', 188 | pageFormat: 'Page format', 189 | pageFormatA4: 'DIN A4 (210 x 297 mm)', 190 | pageFormatA5: 'DIN A5 (148 x 210 mm)', 191 | pageFormatLetter: 'Letter (8.5 x 11.0 inches)', 192 | pageFormatUserDefined: 'Own dimensions', 193 | pageHeight: 'height', 194 | pageMargins: 'Page margins', 195 | pageWidth: 'width', 196 | parameter: 'Parameter', 197 | parameterArrayItemType: 'List item type', 198 | parameterEditTestData: 'Edit', 199 | parameterEditTestDataArrayNoFields: 'No data fields defined for this list', 200 | parameterEditTestDataMapNoFields: 'No data fields defined for this collection', 201 | parameterEval: 'Evaluate text', 202 | parameterExpression: 'Expression', 203 | parameterListType: 'List type', 204 | parameterName: 'Name', 205 | parameterNullable: 'Nullable', 206 | parameterPattern: 'Pattern', 207 | parameterRowParams: 'Row parameters', 208 | parameterSearchPlaceholder: 'Search parameters...', 209 | parameterTestData: 'Test data', 210 | parameterTestDataDatePattern: 'YYYY-MM-DD', 211 | parameterTestDataImageInfo: 'Images are saved in the report template and could significantly increase report template size', 212 | parameterTestDataRichTextInfo: 'Rich text can contain html tags for formatting. See allowed tags', 213 | parameterTestDataRowCount: '${count} rows', 214 | parameterTestDataRowCountEmpty: 'empty', 215 | parameterTestDataRowCountOne: '1 row', 216 | parameterType: 'Type', 217 | parameterTypeArray: 'List', 218 | parameterTypeAverage: 'Average', 219 | parameterTypeBoolean: 'Boolean', 220 | parameterTypeDate: 'Date', 221 | parameterTypeImage: 'Image', 222 | parameterTypeMap: 'Collection', 223 | parameterTypeNumber: 'Number', 224 | parameterTypeRichText: 'Rich Text', 225 | parameterTypeSimpleArray: 'Simple List', 226 | parameterTypeString: 'Text', 227 | parameterTypeSum: 'Sum', 228 | parameters: 'Parameters', 229 | parametersDataSource: 'Data source parameters', 230 | parametersDataSourceName: '${name} parameters', 231 | patternCurrencySymbol: 'Pattern currency symbol', 232 | patternDate1: 'day.month.year, e.g. 1.6.1980', 233 | patternDate2: 'day.month.year (2-digit), hour(24h):minute, e.g. 1.6.80, 14:30', 234 | patternDate3: 'day/month/year (month abbreviation), e.g. 1/Jun/1980', 235 | patternDate4: 'month/day/year (day and month with leading zero if single digit), e.g. 06/01/1980', 236 | patternLocale: 'Pattern locale', 237 | patternNumber1: 'Show thousand separator', 238 | patternNumber2: 'Show decimal point followed by 3 decimal places', 239 | patternNumber3: 'Show decimal point followed by minimum of 2 and maximum of 4 decimal places', 240 | patternNumber4: 'Show thousand separator and decimal point followed by 2 decimal places', 241 | patternNumber5: 'Show currency symbol in front of number', 242 | patternNumberGroupSymbol: 'Thousands separator', 243 | patternNumberGroupSymbolInfo: 'leave empty for default', 244 | patternSeparatorDates: '--- Date patterns ---', 245 | patternSeparatorNumbers: '--- Number patterns ---', 246 | plusFeatureInfo: 'Requires commerical PLUS version', 247 | plusFeatureInfoNestedParameter: 'Nested parameter requires commerical PLUS version', 248 | popupWindowAddDataRow: 'Add row', 249 | select: 'select...', 250 | style: 'Style', 251 | styleAlignment: 'Alignment', 252 | styleBackgroundColor: 'Background color', 253 | styleBold: 'Bold', 254 | styleBorder: 'Border', 255 | styleBorderAll: 'borders', 256 | styleBorderColor: 'Border color', 257 | styleBorderWidth: 'Border width', 258 | styleFont: 'Font', 259 | styleFontSizeUnit: 'pt', 260 | styleHAlignmentCenter: 'Center', 261 | styleHAlignmentLeft: 'Left', 262 | styleHAlignmentJustify: 'Justify', 263 | styleHAlignmentRight: 'Right', 264 | styleItalic: 'Italic', 265 | styleLineSpacing: 'Line spacing', 266 | styleName: 'Name', 267 | styleNone: 'None', 268 | stylePadding: 'Padding', 269 | styleStrikethrough: 'Strikethrough', 270 | styleTextColor: 'Text color', 271 | styleTextStyle: 'Text style', 272 | styleType: 'Element', 273 | styleTypeFrame: 'Frame', 274 | styleTypeImage: 'Image', 275 | styleTypeLine: 'Line', 276 | styleTypeSectionBand: 'Section band', 277 | styleTypeTable: 'Table', 278 | styleTypeTableBand: 'Table band', 279 | styleTypeText: 'Text', 280 | styleUnderline: 'Underline', 281 | styleVAlignmentBottom: 'Bottom', 282 | styleVAlignmentMiddle: 'Middle', 283 | styleVAlignmentTop: 'Top', 284 | styles: 'Styles', 285 | watermarkImages: 'Image watermarks', 286 | watermarkTexts: 'Text watermarks', 287 | watermarks: 'Watermarks', 288 | }; 289 | 290 | export default locale_en; 291 | -------------------------------------------------------------------------------- /src/i18n/locales.js: -------------------------------------------------------------------------------- 1 | import locale_de_de from './locale_de_de'; 2 | import locale_en_us from './locale_en_us'; 3 | 4 | let locales = { 5 | de_de: locale_de_de, 6 | en_us: locale_en_us 7 | }; 8 | 9 | export default locales; 10 | -------------------------------------------------------------------------------- /src/iconfonts/reportbro.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobsta/reportbro-designer/7f482651dd25bc6bb968c0402df6c492d03ecde1/src/iconfonts/reportbro.ttf -------------------------------------------------------------------------------- /src/iconfonts/reportbro.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobsta/reportbro-designer/7f482651dd25bc6bb968c0402df6c492d03ecde1/src/iconfonts/reportbro.woff -------------------------------------------------------------------------------- /src/iconfonts/reportbro.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobsta/reportbro-designer/7f482651dd25bc6bb968c0402df6c492d03ecde1/src/iconfonts/reportbro.woff2 -------------------------------------------------------------------------------- /src/iconfonts/style.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'reportbro'; 3 | src: 4 | url('reportbro.woff2?aahybf') format('woff2'), 5 | url('reportbro.ttf?aahybf') format('truetype'), 6 | url('reportbro.woff?aahybf') format('woff'), 7 | url('reportbro.svg?aahybf#reportbro') format('svg'); 8 | font-weight: normal; 9 | font-style: normal; 10 | font-display: block; 11 | } 12 | 13 | [class^="rbroIcon-"], [class*=" rbroIcon-"] { 14 | /* use !important to prevent issues with browser extensions that change fonts */ 15 | font-family: 'reportbro' !important; 16 | speak: never; 17 | font-style: normal; 18 | font-weight: normal; 19 | font-variant: normal; 20 | text-transform: none; 21 | line-height: 1; 22 | 23 | /* Better Font Rendering =========== */ 24 | -webkit-font-smoothing: antialiased; 25 | -moz-osx-font-smoothing: grayscale; 26 | } 27 | 28 | .rbroIcon-info:before { 29 | content: "\e939"; 30 | } 31 | .rbroIcon-check:before { 32 | content: "\e938"; 33 | } 34 | .rbroIcon-delete:before { 35 | content: "\e937"; 36 | } 37 | .rbroIcon-insert-report:before { 38 | content: "\e936"; 39 | } 40 | .rbroIcon-row-delete:before { 41 | content: "\e92f"; 42 | } 43 | .rbroIcon-column-delete:before { 44 | content: "\e930"; 45 | } 46 | .rbroIcon-row-add-below:before { 47 | content: "\e931"; 48 | } 49 | .rbroIcon-row-add-above:before { 50 | content: "\e932"; 51 | } 52 | .rbroIcon-column-add-left:before { 53 | content: "\e933"; 54 | } 55 | .rbroIcon-column-add-right:before { 56 | content: "\e934"; 57 | } 58 | .rbroIcon-section:before { 59 | content: "\e92e"; 60 | } 61 | .rbroIcon-frame:before { 62 | content: "\e915"; 63 | } 64 | .rbroIcon-xlsx:before { 65 | content: "\e92d"; 66 | } 67 | .rbroIcon-arrow-line-up:before { 68 | content: "\e92c"; 69 | } 70 | .rbroIcon-barcode:before { 71 | content: "\e92a"; 72 | } 73 | .rbroIcon-download:before { 74 | content: "\e92b"; 75 | } 76 | .rbroIcon-edit:before { 77 | content: "\e924"; 78 | } 79 | .rbroIcon-magnifier:before { 80 | content: "\e926"; 81 | } 82 | .rbroIcon-play:before { 83 | content: "\e927"; 84 | } 85 | .rbroIcon-preview:before { 86 | content: "\e928"; 87 | } 88 | .rbroIcon-select:before { 89 | content: "\e925"; 90 | } 91 | .rbroIcon-page-break:before { 92 | content: "\e91f"; 93 | } 94 | .rbroIcon-line:before { 95 | content: "\e91e"; 96 | } 97 | .rbroIcon-border-table-frame:before { 98 | content: "\e920"; 99 | } 100 | .rbroIcon-border-table-frame-row:before { 101 | content: "\e921"; 102 | } 103 | .rbroIcon-border-table-row:before { 104 | content: "\e922"; 105 | } 106 | .rbroIcon-border-table-none:before { 107 | content: "\e914"; 108 | } 109 | .rbroIcon-border-table-grid:before { 110 | content: "\e929"; 111 | } 112 | .rbroIcon-border-all:before { 113 | content: "\e91a"; 114 | } 115 | .rbroIcon-border-right:before { 116 | content: "\e916"; 117 | } 118 | .rbroIcon-border-left:before { 119 | content: "\e917"; 120 | } 121 | .rbroIcon-border-top:before { 122 | content: "\e918"; 123 | } 124 | .rbroIcon-border-bottom:before { 125 | content: "\e919"; 126 | } 127 | .rbroIcon-grid:before { 128 | content: "\e910"; 129 | } 130 | .rbroIcon-console:before { 131 | content: "\e911"; 132 | } 133 | .rbroIcon-image:before { 134 | content: "\e912"; 135 | } 136 | .rbroIcon-text:before { 137 | content: "\e913"; 138 | } 139 | .rbroIcon-settings:before { 140 | content: "\e901"; 141 | } 142 | .rbroIcon-refresh:before { 143 | content: "\e923"; 144 | } 145 | .rbroIcon-save:before { 146 | content: "\e90a"; 147 | } 148 | .rbroIcon-undo:before { 149 | content: "\e90e"; 150 | } 151 | .rbroIcon-redo:before { 152 | content: "\e90f"; 153 | } 154 | .rbroIcon-align-center:before { 155 | content: "\e91b"; 156 | } 157 | .rbroIcon-align-middle:before { 158 | content: "\e902"; 159 | } 160 | .rbroIcon-align-bottom:before { 161 | content: "\e91c"; 162 | } 163 | .rbroIcon-align-left:before { 164 | content: "\e91d"; 165 | } 166 | .rbroIcon-align-right:before { 167 | content: "\e903"; 168 | } 169 | .rbroIcon-align-top:before { 170 | content: "\e904"; 171 | } 172 | .rbroIcon-table:before { 173 | content: "\e905"; 174 | } 175 | .rbroIcon-italic:before { 176 | content: "\e906"; 177 | } 178 | .rbroIcon-bold:before { 179 | content: "\e907"; 180 | } 181 | .rbroIcon-underline:before { 182 | content: "\e908"; 183 | } 184 | .rbroIcon-strikethrough:before { 185 | content: "\e900"; 186 | } 187 | .rbroIcon-text-align-left:before { 188 | content: "\e909"; 189 | } 190 | .rbroIcon-text-align-center:before { 191 | content: "\e90b"; 192 | } 193 | .rbroIcon-text-align-right:before { 194 | content: "\e90c"; 195 | } 196 | .rbroIcon-text-align-justify:before { 197 | content: "\e90d"; 198 | } 199 | .rbroIcon-minus:before { 200 | content: "\e805"; 201 | } 202 | .rbroIcon-arrow-right:before { 203 | content: "\e935"; 204 | } 205 | .rbroIcon-arrow-down:before { 206 | content: "\e60c"; 207 | } 208 | .rbroIcon-cancel:before { 209 | content: "\e604"; 210 | } 211 | .rbroIcon-plus:before { 212 | content: "\e611"; 213 | } 214 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (C) 2022 jobsta 3 | // 4 | // This file is part of ReportBro, a library to generate PDF and Excel reports. 5 | // Demos can be found at https://www.reportbro.com 6 | // 7 | // Dual licensed under AGPLv3 and ReportBro commercial license: 8 | // https://www.reportbro.com/license 9 | // 10 | // You should have received a copy of the GNU Affero General Public License 11 | // along with this program. If not, see https://www.gnu.org/licenses/ 12 | // 13 | // Details for ReportBro commercial license can be found at 14 | // https://www.reportbro.com/license/agreement 15 | // 16 | 17 | import ReportBro from './ReportBro'; 18 | 19 | (function(){ 20 | if (typeof exports !== 'undefined') exports.ReportBro = ReportBro; 21 | else window.ReportBro = ReportBro; 22 | })(); 23 | -------------------------------------------------------------------------------- /src/menu/MainPanel.js: -------------------------------------------------------------------------------- 1 | import MainPanelItem from './MainPanelItem'; 2 | import Container from '../container/Container'; 3 | 4 | /** 5 | * Main panel which contains all report elements like doc elements, parameters and styles. 6 | * The main panel shows the structure and all components of the report. 7 | * @class 8 | */ 9 | export default class MainPanel { 10 | constructor(rootElement, headerBand, contentBand, footerBand, parameterContainer, styleContainer, rb) { 11 | this.rootElement = rootElement; 12 | this.rb = rb; 13 | this.headerItem = new MainPanelItem( 14 | 'band', null, headerBand, { 15 | hasChildren: true, 16 | showAdd: false, 17 | showDelete: false, 18 | hasDetails: false, 19 | visible: this.rb.getDocumentProperties().getValue('header') 20 | }, rb); 21 | 22 | this.documentItem = new MainPanelItem( 23 | 'band', null, contentBand, { 24 | hasChildren: true, 25 | showAdd: false, 26 | showDelete: false, 27 | hasDetails: false 28 | }, rb); 29 | 30 | this.footerItem = new MainPanelItem( 31 | 'band', null, footerBand, { 32 | hasChildren: true, 33 | showAdd: false, 34 | showDelete: false, 35 | hasDetails: false, 36 | visible: this.rb.getDocumentProperties().getValue('footer') 37 | }, rb); 38 | 39 | this.parametersItem = new MainPanelItem( 40 | 'parameter', null, parameterContainer, { 41 | hasChildren: true, 42 | showAdd: rb.getProperty('adminMode'), 43 | showDelete: false, 44 | hasDetails: false 45 | }, rb); 46 | 47 | this.stylesItem = new MainPanelItem( 48 | 'style', null, styleContainer, { 49 | hasChildren: true, 50 | showAdd: true, 51 | showDelete: false, 52 | hasDetails: false 53 | }, rb); 54 | 55 | this.watermarksItem = new MainPanelItem( 56 | 'watermark', null, null, { 57 | hasChildren: true, 58 | showAdd: false, 59 | showDelete: false, 60 | hasDetails: false, 61 | id: '0_watermarks', 62 | name: rb.getLabel('watermarks'), 63 | // set static flag to prevent child nodes (sub categories) being cleared when report is loaded 64 | static: true, 65 | }, rb); 66 | 67 | this.documentPropertiesItem = new MainPanelItem( 68 | 'documentProperties', null, rb.getDocumentProperties(), { 69 | showDelete: false, 70 | hasDetails: true 71 | }, rb); 72 | 73 | this.items = [ 74 | this.headerItem, 75 | this.documentItem, 76 | this.footerItem, 77 | this.parametersItem, 78 | this.stylesItem, 79 | this.watermarksItem, 80 | this.documentPropertiesItem, 81 | ]; 82 | 83 | this.dragMainPanelSizer = false; 84 | this.dragMainPanelSizerStartX = 0; 85 | this.mainPanelWidth = 230; 86 | this.mainPanelSizerWidth = 3; 87 | 88 | headerBand.setPanelItem(this.headerItem); 89 | contentBand.setPanelItem(this.documentItem); 90 | footerBand.setPanelItem(this.footerItem); 91 | parameterContainer.setPanelItem(this.parametersItem); 92 | styleContainer.setPanelItem(this.stylesItem); 93 | } 94 | 95 | getHeaderItem() { 96 | return this.headerItem; 97 | } 98 | 99 | getDocumentItem() { 100 | return this.documentItem; 101 | } 102 | 103 | getFooterItem() { 104 | return this.footerItem; 105 | } 106 | 107 | getParametersItem() { 108 | return this.parametersItem; 109 | } 110 | 111 | getStylesItem() { 112 | return this.stylesItem; 113 | } 114 | 115 | getWatermarksItem() { 116 | return this.watermarksItem; 117 | } 118 | 119 | getContainerByItem(item) { 120 | while (item !== null) { 121 | if (item.getData() instanceof Container) { 122 | return item.getData(); 123 | } 124 | item = item.getParent(); 125 | } 126 | return null; 127 | } 128 | 129 | getDocumentPropertiesItem() { 130 | return this.documentPropertiesItem; 131 | } 132 | 133 | render() { 134 | let panel = document.getElementById('rbro_main_panel_list'); 135 | this.appendChildren(panel, this.items); 136 | 137 | document.getElementById('rbro_main_panel_sizer').addEventListener('mousedown', (event) => { 138 | this.dragMainPanelSizer = true; 139 | this.dragMainPanelSizerStartX = event.pageX; 140 | }); 141 | 142 | this.updateMainPanelWidth(this.mainPanelWidth); 143 | } 144 | 145 | appendChildren(el, items) { 146 | for (let item of items) { 147 | el.append(item.getElement()); 148 | let children = item.getChildren(); 149 | if (children.length > 0) { 150 | this.appendChildren(el, children); 151 | } 152 | } 153 | } 154 | 155 | processMouseMove(event) { 156 | if (this.dragMainPanelSizer) { 157 | let mainPanelWidth = this.mainPanelWidth + (event.pageX - this.dragMainPanelSizerStartX); 158 | mainPanelWidth = this.checkMainPanelWidth(mainPanelWidth); 159 | this.updateMainPanelWidth(mainPanelWidth); 160 | return true; 161 | } 162 | return false; 163 | } 164 | 165 | mouseUp(event) { 166 | if (this.dragMainPanelSizer) { 167 | this.dragMainPanelSizer = false; 168 | this.mainPanelWidth = this.mainPanelWidth + (event.pageX - this.dragMainPanelSizerStartX); 169 | this.mainPanelWidth = this.checkMainPanelWidth(this.mainPanelWidth); 170 | this.updateMainPanelWidth(this.mainPanelWidth); 171 | } 172 | } 173 | 174 | /** 175 | * Returns total panel width. This is the width of the main panel (containing the elements), 176 | * the property panel and an optional menu sidebar. 177 | * @returns {Number} 178 | */ 179 | getTotalPanelWidth() { 180 | let totalPanelWidth = this.mainPanelWidth + this.mainPanelSizerWidth + 390; 181 | if (this.rb.getProperty('menuSidebar')) { 182 | totalPanelWidth += 92; 183 | } 184 | return totalPanelWidth; 185 | } 186 | 187 | updateMainPanelWidth(mainPanelWidth) { 188 | document.getElementById('rbro_main_panel').style.width = mainPanelWidth + 'px'; 189 | document.getElementById('rbro_main_panel_sizer').style.left = mainPanelWidth + 'px'; 190 | document.getElementById('rbro_detail_panel').style.left = (mainPanelWidth + this.mainPanelSizerWidth) + 'px'; 191 | // calculate width of main panel, detail panel and sidebar (if available) 192 | let totalPanelWidth = mainPanelWidth + this.mainPanelSizerWidth + 390; 193 | if (this.rb.getProperty('menuSidebar')) { 194 | totalPanelWidth += 92; 195 | document.getElementById('reportbro').querySelector('.rbroLogo').style.width = mainPanelWidth + 'px'; 196 | } 197 | document.getElementById('rbro_document_panel').style.width = `calc(100% - ${totalPanelWidth}px)`; 198 | } 199 | 200 | checkMainPanelWidth(mainPanelWidth) { 201 | if (mainPanelWidth < 150) { 202 | return 150; 203 | } else if (mainPanelWidth > 500) { 204 | return 500; 205 | } 206 | return mainPanelWidth; 207 | } 208 | 209 | showHeader() { 210 | this.headerItem.show(); 211 | } 212 | 213 | hideHeader() { 214 | this.headerItem.hide(); 215 | } 216 | 217 | showFooter() { 218 | this.footerItem.show(); 219 | } 220 | 221 | hideFooter() { 222 | this.footerItem.hide(); 223 | } 224 | 225 | showWatermarks() { 226 | this.watermarksItem.show(); 227 | } 228 | 229 | hideWatermarks() { 230 | this.watermarksItem.hide(); 231 | } 232 | 233 | clearAll() { 234 | for (const item of this.items) { 235 | item.clear(); 236 | } 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /src/panels/EmptyDetailPanel.js: -------------------------------------------------------------------------------- 1 | import * as utils from '../utils'; 2 | 3 | /** 4 | * Empty panel which is shown when no data object is selected. 5 | * @class 6 | */ 7 | export default class EmptyDetailPanel { 8 | constructor(rootElement, rb) { 9 | this.rootElement = rootElement; 10 | this.rb = rb; 11 | } 12 | 13 | render() { 14 | const elDetailPanel = utils.createElement( 15 | 'div', { id: 'rbro_empty_detail_panel', class: 'rbroEmptyDetailPanel rbroHidden' }); 16 | elDetailPanel.append(utils.createElement('div', { class: 'rbroLogo' })); 17 | document.getElementById('rbro_detail_panel').append(elDetailPanel); 18 | } 19 | 20 | destroy() { 21 | } 22 | 23 | show(data) { 24 | document.getElementById('rbro_empty_detail_panel').classList.remove('rbroHidden'); 25 | } 26 | 27 | hide() { 28 | document.getElementById('rbro_empty_detail_panel').classList.add('rbroHidden'); 29 | } 30 | 31 | isKeyEventDisabled() { 32 | return false; 33 | } 34 | 35 | notifyEvent(obj, operation) { 36 | } 37 | 38 | updateErrors() { 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/rb_logo_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobsta/reportbro-designer/7f482651dd25bc6bb968c0402df6c492d03ecde1/src/rb_logo_dark.png -------------------------------------------------------------------------------- /src/rb_logo_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobsta/reportbro-designer/7f482651dd25bc6bb968c0402df6c492d03ecde1/src/rb_logo_white.png -------------------------------------------------------------------------------- /src/toggle-switch.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | /* 3 | * 4 | * Based on: 5 | * CSS TOGGLE SWITCH 6 | * 7 | * Ionuț Colceriu - ghinda.net 8 | * https://github.com/ghinda/css-toggle-switch 9 | * 10 | */ 11 | /* Hide by default 12 | */ 13 | .switch-toggle a, 14 | .switch-light span span { display: none; } 15 | 16 | /* We can't test for a specific feature, 17 | * so we only target browsers with support for media queries. 18 | */ 19 | @media only screen { 20 | /* Checkbox 21 | */ 22 | .switch-light { 23 | position: relative; 24 | display: block; 25 | /* simulate default browser focus outlines on the switch, 26 | * when the inputs are focused. 27 | */ } 28 | .switch-light::after { 29 | clear: both; 30 | content: ''; 31 | display: table; } 32 | .switch-light *, 33 | .switch-light *:before, 34 | .switch-light *:after { 35 | box-sizing: border-box; } 36 | .switch-light a { 37 | display: block; 38 | transition: all 0.2s ease-out; } 39 | .switch-light label, 40 | .switch-light input:focus ~ span a, 41 | .switch-light input:focus + label { 42 | outline:none; 43 | } 44 | } 45 | 46 | @media only screen { 47 | /* don't hide the input from screen-readers and keyboard access 48 | */ 49 | .switch-light input[type=checkbox] { 50 | position: absolute; 51 | opacity: 0; 52 | z-index: 3; 53 | } 54 | 55 | .switch-light input:checked ~ span a { 56 | right: 0; 57 | } 58 | 59 | /* inherit from label 60 | */ 61 | .switch-light strong { 62 | font-weight: inherit; 63 | } 64 | 65 | .switch-light > span { 66 | position: relative; 67 | overflow: hidden; 68 | display: block; 69 | padding: 0; 70 | text-align: left; 71 | } 72 | 73 | .switch-light span span { 74 | position: relative; 75 | z-index: 2; 76 | display: block; 77 | float: left; 78 | width: 50%; 79 | text-align: center; 80 | user-select: none; 81 | } 82 | 83 | .switch-light a { 84 | position: absolute; 85 | right: 50%; 86 | top: 0; 87 | z-index: 1; 88 | display: block; 89 | width: 50%; 90 | height: 100%; 91 | padding: 0; 92 | } 93 | } 94 | 95 | /* Material Theme 96 | */ 97 | /* switch-light 98 | */ 99 | @media only screen { 100 | .switch-light.switch-material a { 101 | top: -2px; 102 | width: 26px; 103 | height: 26px; 104 | border-radius: 50%; 105 | background: #fafafa; 106 | box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 3px 2px -2px rgba(0, 0, 0, 0.2), 0 2px 4px 0 rgba(0, 0, 0, 0.12); 107 | transition: right 0.28s cubic-bezier(0.4, 0, 0.2, 1); } 108 | .switch-material.switch-light { 109 | overflow: visible; } 110 | .switch-material.switch-light::after { 111 | clear: both; 112 | content: ''; 113 | display: table; } 114 | .switch-material.switch-light > span { 115 | overflow: visible; 116 | position: relative; 117 | top: 3px; 118 | width: 52px; 119 | height: 24px; 120 | border-radius: 16px; 121 | background: rgba(0, 0, 0, 0.26); 122 | } 123 | 124 | .switch-material.switch-light span span { 125 | position: absolute; 126 | clip: rect(0 0 0 0); 127 | } 128 | 129 | .switch-material.switch-light input:checked ~ span a { 130 | right: 0; 131 | background: #00ad69; 132 | box-shadow: 0 3px 4px 0 rgba(0, 0, 0, 0.14), 0 3px 3px -2px rgba(0, 0, 0, 0.2), 0 1px 6px 0 rgba(0, 0, 0, 0.12); 133 | } 134 | 135 | .switch-material.switch-light input:checked ~ span { 136 | background: rgba(0, 173, 105, 0.3); 137 | } 138 | } -------------------------------------------------------------------------------- /webpack.common.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 3 | 4 | 5 | module.exports = { 6 | entry: ['./src/main.js', './src/main.css', './src/fonts/font_style.css', './src/iconfonts/style.css', './src/toggle-switch.css', './src/quill.reportbro.css'], 7 | output: { 8 | filename: 'reportbro.js', 9 | path: path.resolve(__dirname, 'dist'), 10 | }, 11 | performance : { 12 | hints : false // disable warning for asset size limit (generated js file) 13 | }, 14 | module: { 15 | rules: [ 16 | { 17 | test: /\.css$/, use: [MiniCssExtractPlugin.loader, 'css-loader'], }, 18 | { 19 | test: /\.(png|gif)([\?]?.*)$/, 20 | type: 'asset/resource', 21 | generator: { 22 | filename: '[path][name][ext]' 23 | } 24 | }, 25 | { 26 | test: /\.(ttf|eot|svg|woff(2)?)(\?[a-z0-9=&.]+)?$/, 27 | type: 'asset/resource', 28 | generator: { 29 | filename: '[path][name][ext]?[hash]' 30 | } 31 | }, 32 | ] 33 | }, 34 | plugins: [ 35 | new MiniCssExtractPlugin({filename: "reportbro.css"}) 36 | ] 37 | }; 38 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const { merge } = require('webpack-merge'); 2 | const common = require('./webpack.common.js'); 3 | 4 | module.exports = merge(common, { 5 | mode: 'development', 6 | devtool: 'inline-source-map', 7 | }); 8 | -------------------------------------------------------------------------------- /webpack.config.prod.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | const { merge } = require('webpack-merge'); 4 | const common = require('./webpack.common.js'); 5 | 6 | const banner = 7 | `Copyright (C) 2023 jobsta 8 | 9 | This file is part of ReportBro, a library to generate PDF and Excel reports. 10 | Demos can be found at https://www.reportbro.com 11 | 12 | Dual licensed under AGPLv3 and ReportBro commercial license: 13 | https://www.reportbro.com/license 14 | 15 | You should have received a copy of the GNU Affero General Public License 16 | along with this program. If not, see https://www.gnu.org/licenses/ 17 | 18 | Details for ReportBro commercial license can be found at 19 | https://www.reportbro.com/license/agreement 20 | `; 21 | 22 | module.exports = merge(common, { 23 | mode: 'production', 24 | devtool: 'source-map', 25 | output: { 26 | clean: true, 27 | }, 28 | module: { 29 | rules: [ 30 | { 31 | test: /\.js$/, 32 | include: [path.resolve(__dirname, "src")], 33 | loader: "babel-loader", 34 | options: { 35 | plugins: ['@babel/plugin-transform-runtime'], 36 | presets: ['@babel/preset-env'] 37 | }, 38 | } 39 | ] 40 | }, 41 | plugins: [ 42 | new webpack.BannerPlugin({ banner: banner, test: 'reportbro.js' }), 43 | ] 44 | }); 45 | --------------------------------------------------------------------------------