Repository: born2net/studio-lite Branch: master Commit: 60ec84ad4c25 Files: 337 Total size: 4.8 MB Directory structure: gitextract_0m1po494/ ├── .angular-cli.json ├── .editorconfig ├── .gitignore ├── LICENSE ├── README.md ├── db-design.psd ├── dev/ │ └── notes.txt ├── e2e/ │ ├── app.e2e-spec.ts │ ├── app.po.ts │ └── tsconfig.json ├── examples/ │ ├── adStatsSample.xml │ ├── apps_table.txt │ ├── calendar_google.xml │ ├── collection.xml │ ├── collection_scene.xml │ ├── digg.xml │ ├── googlesheets.xml │ ├── instagram_feed.xml │ ├── jsonItem.xml │ ├── jsonPlayer.xml │ ├── json_as3_maps.xml │ ├── json_scenes.xml │ ├── jsonsheets.xml │ ├── location.xml │ ├── player_weather_scene.xml │ ├── reseller_sample.xml │ ├── sceneSample.xml │ ├── sceneSampleLayout.xml │ ├── twitterv3.xml │ └── worldweather.xml ├── notes.txt ├── package.json ├── src/ │ ├── Lib.ts │ ├── app/ │ │ ├── app-component.css │ │ ├── app-component.html │ │ ├── app-component.ts │ │ ├── app-module.ts │ │ ├── appwrap.ts │ │ ├── blocks/ │ │ │ ├── block-fabric-image.ts │ │ │ ├── block-fabric-josn-item.ts │ │ │ ├── block-fabric-label.ts │ │ │ ├── block-fabric-scene.ts │ │ │ ├── block-fabric-svg.ts │ │ │ ├── block-fabric.ts │ │ │ ├── block-prop-calendar.ts │ │ │ ├── block-prop-clock.ts │ │ │ ├── block-prop-collection.ts │ │ │ ├── block-prop-common.ts │ │ │ ├── block-prop-container.ts │ │ │ ├── block-prop-fasterq.ts │ │ │ ├── block-prop-html.ts │ │ │ ├── block-prop-image.ts │ │ │ ├── block-prop-instagram.ts │ │ │ ├── block-prop-json-item.ts │ │ │ ├── block-prop-json-player.ts │ │ │ ├── block-prop-label.ts │ │ │ ├── block-prop-location.ts │ │ │ ├── block-prop-mrss.ts │ │ │ ├── block-prop-position.ts │ │ │ ├── block-prop-qr.ts │ │ │ ├── block-prop-rss.ts │ │ │ ├── block-prop-scene.ts │ │ │ ├── block-prop-sheets.ts │ │ │ ├── block-prop-twitter.ts │ │ │ ├── block-prop-video.ts │ │ │ ├── block-prop-weather.ts │ │ │ ├── block-prop-youtube.ts │ │ │ ├── block-prop.digg.ts │ │ │ ├── block-service.ts │ │ │ └── json-event-grid.ts │ │ ├── campaigns/ │ │ │ ├── add-content.ts │ │ │ ├── campaign-channels.ts │ │ │ ├── campaign-duration.ts │ │ │ ├── campaign-editor-props.ts │ │ │ ├── campaign-editor.ts │ │ │ ├── campaign-editors.html │ │ │ ├── campaign-layout.ts │ │ │ ├── campaign-list.ts │ │ │ ├── campaign-manager.html │ │ │ ├── campaign-manager.ts │ │ │ ├── campaign-name.ts │ │ │ ├── campaign-orientation.ts │ │ │ ├── campaign-props-manager.ts │ │ │ ├── campaign-props.ts │ │ │ ├── campaign-resolution.ts │ │ │ ├── campaign-sched-props.css │ │ │ ├── campaign-sched-props.html │ │ │ ├── campaign-sched-props.ts │ │ │ ├── campaign-story-timeline.ts │ │ │ ├── campaigns-navigation.ts │ │ │ ├── campaigns.ts │ │ │ ├── channel-block-props.ts │ │ │ ├── channel-props.ts │ │ │ ├── dashboard-props.ts │ │ │ ├── index.ts │ │ │ ├── screen-layout-editor-props.ts │ │ │ ├── screen-layout-editor.ts │ │ │ ├── sequencer.ts │ │ │ ├── timeline/ │ │ │ │ ├── timeline.component.css │ │ │ │ ├── timeline.component.html │ │ │ │ ├── timeline.component.spec.ts │ │ │ │ └── timeline.component.ts │ │ │ ├── timeline-props.ts │ │ │ └── timeline-ruler/ │ │ │ ├── timeline-ruler.component.css │ │ │ ├── timeline-ruler.component.html │ │ │ ├── timeline-ruler.component.spec.ts │ │ │ └── timeline-ruler.component.ts │ │ ├── dashboard/ │ │ │ ├── dash-panel-mini.html │ │ │ ├── dash-panel-mini.ts │ │ │ ├── dash-panel.html │ │ │ ├── dash-panel.ts │ │ │ ├── dashboard-navigation.ts │ │ │ ├── server-avg.ts │ │ │ └── storage-used.ts │ │ ├── fasterq/ │ │ │ ├── fasterq-editor.html │ │ │ ├── fasterq-editor.ts │ │ │ ├── fasterq-line-props.ts │ │ │ ├── fasterq-manager.ts │ │ │ ├── fasterq-navigation.ts │ │ │ ├── fasterq-terminal.ts │ │ │ ├── fasterq.ts │ │ │ └── index.ts │ │ ├── help/ │ │ │ ├── help-navigation.ts │ │ │ └── index.ts │ │ ├── index.ts │ │ ├── install/ │ │ │ ├── index.ts │ │ │ └── install-navigation.ts │ │ ├── live-preview/ │ │ │ └── live-preview.ts │ │ ├── locale-selector/ │ │ │ ├── local-selector.css │ │ │ └── local-selector.ts │ │ ├── location/ │ │ │ ├── location-map.html │ │ │ └── location-map.ts │ │ ├── resources/ │ │ │ ├── index.ts │ │ │ ├── orders.ts │ │ │ ├── resource-props-manager.ts │ │ │ ├── resources-list.ts │ │ │ ├── resources-navigation.ts │ │ │ └── resources.ts │ │ ├── route-animation.ts │ │ ├── scenes/ │ │ │ ├── index.ts │ │ │ ├── scene-creator.css │ │ │ ├── scene-creator.ts │ │ │ ├── scene-editor.ts │ │ │ ├── scene-list.ts │ │ │ ├── scene-manager.ts │ │ │ ├── scene-props-manager.ts │ │ │ ├── scene-toolbar.ts │ │ │ ├── scenes-navigation.ts │ │ │ └── scenes.ts │ │ ├── settings/ │ │ │ ├── index.ts │ │ │ ├── settings-navigation.ts │ │ │ └── twofactor.ts │ │ ├── stations/ │ │ │ ├── index.ts │ │ │ ├── stations-list.ts │ │ │ ├── stations-navigation.ts │ │ │ ├── stations-props-manager.html │ │ │ ├── stations-props-manager.ts │ │ │ └── stations.ts │ │ └── studiopro/ │ │ ├── index.ts │ │ ├── pro-upgrade.ts │ │ ├── pro-upgrades.html │ │ └── studiopro-navigation.ts │ ├── app-routes.ts │ ├── assets/ │ │ └── geojson.json │ ├── comps/ │ │ ├── blurforwarder/ │ │ │ └── BlurForwarder.ts │ │ ├── connect-form/ │ │ │ └── connect-form.ts │ │ ├── contact-us/ │ │ │ └── contact-us.ts │ │ ├── disable-control/ │ │ │ └── disable-control.ts │ │ ├── draggable-list/ │ │ │ └── draggable-list.ts │ │ ├── duration-input/ │ │ │ ├── duration-input.component.css │ │ │ ├── duration-input.component.html │ │ │ ├── duration-input.component.spec.ts │ │ │ └── duration-input.component.ts │ │ ├── entry/ │ │ │ ├── AutoLogin.ts │ │ │ └── LoginPanel.ts │ │ ├── font-selector/ │ │ │ └── font-selector.ts │ │ ├── hour-counter/ │ │ │ └── hour-counter.ts │ │ ├── imgloader/ │ │ │ └── ImgLoader.ts │ │ ├── infobox/ │ │ │ └── Infobox.ts │ │ ├── lazy-image/ │ │ │ └── lazy-image.ts │ │ ├── limited-access/ │ │ │ └── limited-access.ts │ │ ├── loading/ │ │ │ └── loading.ts │ │ ├── logo/ │ │ │ ├── Logo.ts │ │ │ └── reseller-logo.ts │ │ ├── logout/ │ │ │ └── Logout.ts │ │ ├── match-body-height/ │ │ │ └── match-body-height.ts │ │ ├── media-player/ │ │ │ └── media-player.ts │ │ ├── ng-menu/ │ │ │ ├── ng-menu-item.ts │ │ │ └── ng-menu.ts │ │ ├── panel-split/ │ │ │ ├── panel-split-container.ts │ │ │ ├── panel-split-main.ts │ │ │ └── panel-split-side.ts │ │ ├── screen-template/ │ │ │ └── screen-template.ts │ │ ├── simple-grid-module/ │ │ │ ├── SimpleGrid.css │ │ │ ├── SimpleGrid.ts │ │ │ ├── SimpleGridData.ts │ │ │ ├── SimpleGridDataChecks.ts │ │ │ ├── SimpleGridDataCurrency.ts │ │ │ ├── SimpleGridDataDropdown.ts │ │ │ ├── SimpleGridDataImage.ts │ │ │ ├── SimpleGridDraggable.ts │ │ │ ├── SimpleGridExample.txt │ │ │ ├── SimpleGridModule.ts │ │ │ ├── SimpleGridRecord.ts │ │ │ ├── SimpleGridSortableHeader.ts │ │ │ └── SimpleGridTable.ts │ │ ├── sliderpanel/ │ │ │ ├── SliderItemContent.ts │ │ │ ├── Slideritem.ts │ │ │ └── Sliderpanel.ts │ │ ├── svg-icon/ │ │ │ └── svg-icon.ts │ │ └── tabs/ │ │ ├── tab.ts │ │ └── tabs.ts │ ├── create_reflection.html │ ├── decorators/ │ │ ├── once-decorator.ts │ │ └── timeout-decorator.ts │ ├── environments/ │ │ ├── environment.hmr.ts │ │ ├── environment.prod.ts │ │ └── environment.ts │ ├── filters/ │ │ └── filter-model-pipe.ts │ ├── hmr.ts │ ├── index.html │ ├── interfaces/ │ │ ├── BlockTypeEnum.ts │ │ ├── Consts.ts │ │ ├── IAddContent.ts │ │ ├── IRegisterCaller.ts │ │ └── IScreenTemplate.ts │ ├── libs/ │ │ ├── bootstrap-timepicker/ │ │ │ ├── CHANGELOG.md │ │ │ ├── README.md │ │ │ ├── bower.json │ │ │ ├── composer.json │ │ │ ├── css/ │ │ │ │ ├── bootstrap-timepicker.css │ │ │ │ ├── bootstrap-timepicker.min.css │ │ │ │ └── timepicker.less │ │ │ ├── js/ │ │ │ │ ├── bootstrap-timepicker.js │ │ │ │ └── bootstrap-timepicker.min.js │ │ │ └── package.json │ │ ├── contextmenu/ │ │ │ └── bootstrap-contextmenu.js │ │ ├── enjoyhint/ │ │ │ └── enjoyhint.js │ │ ├── fabric.require1-4-12.js │ │ ├── fabric.require1-4-12.min.js │ │ ├── fabric1-4-2.min.js │ │ ├── flashdetect/ │ │ │ └── flashdetect.js │ │ ├── gradient/ │ │ │ ├── colorpicker.js │ │ │ └── jquery.gradientpicker.js │ │ ├── jquery-ui.js │ │ ├── jquery.base64.js │ │ ├── jquery.knob.min.js │ │ ├── jquery.timepicker/ │ │ │ └── jquery.timepicker.min.js │ │ ├── minicolors/ │ │ │ ├── jquery.minicolors.css │ │ │ └── jquery.minicolors.js │ │ ├── qrcode/ │ │ │ └── qrcode.js │ │ ├── rc4v1/ │ │ │ └── rc4v1.js │ │ ├── rc4v2/ │ │ │ └── rc4v2.js │ │ ├── ruler/ │ │ │ ├── ruler.css │ │ │ ├── ruler.js │ │ │ ├── rulerConstructor.js │ │ │ ├── rulerGuideLine.js │ │ │ └── utils.js │ │ ├── screen-templates.json │ │ ├── stop-watch/ │ │ │ └── stop-watch.js │ │ └── xml2js/ │ │ └── xml2js.js │ ├── locale/ │ │ ├── ar.xtb │ │ ├── be.xtb │ │ ├── bn.xtb │ │ ├── cs.xtb │ │ ├── da.xtb │ │ ├── de.xtb │ │ ├── el.xtb │ │ ├── en.xtb │ │ ├── eo.xtb │ │ ├── es.xtb │ │ ├── fr.xtb │ │ ├── hi.xtb │ │ ├── it.xtb │ │ ├── iw.xtb │ │ ├── ja.xtb │ │ ├── ko.xtb │ │ ├── la.xtb │ │ ├── messages.xmb │ │ ├── pt.xtb │ │ ├── ru.xtb │ │ ├── template.xtb │ │ ├── th.xtb │ │ ├── tl.xtb │ │ └── zh-CN.xtb │ ├── main.ts │ ├── manifest.json │ ├── models/ │ │ ├── LocationMarkModel.ts │ │ ├── StationModel.ts │ │ ├── StoreModel.ts │ │ ├── StoreModelAbstract.ts │ │ ├── UserModel.ts │ │ ├── fasterq-analytics.ts │ │ ├── fasterq-line-model.ts │ │ ├── fasterq-queue-model.ts │ │ └── live-log-model.ts │ ├── modules/ │ │ ├── ngmslib-service.ts │ │ └── shared.module.ts │ ├── pipes/ │ │ ├── format-seconds-pipe.ts │ │ └── list-to-array-pipe.ts │ ├── polyfills.ts │ ├── print.html │ ├── service-worker.js │ ├── services/ │ │ ├── AuthService.ts │ │ ├── CommBroker.ts │ │ ├── CreditService.ts │ │ ├── LocalStorage.ts │ │ ├── StoreService.ts │ │ ├── block-factory-service.ts │ │ ├── font-loader-service.ts │ │ ├── global-error-handler.ts │ │ ├── redpepper.service.ts │ │ ├── wizard-service.ts │ │ └── yellowpepper.service.ts │ ├── store/ │ │ ├── actions/ │ │ │ ├── appdb.actions.ts │ │ │ └── msdb.actions.ts │ │ ├── application.state.ts │ │ ├── effects/ │ │ │ ├── appdb.effects.ts │ │ │ └── msdb.effects.ts │ │ ├── imsdb.interfaces.ts │ │ ├── imsdb.interfaces_auto.ts │ │ ├── model/ │ │ │ ├── StoreModel.ts │ │ │ └── msdb-models-extended.ts │ │ ├── reducers/ │ │ │ ├── appdb.reducer.ts │ │ │ └── msdb.reducer.ts │ │ ├── signage_sdk.js │ │ └── store.data.ts │ ├── styles/ │ │ ├── material-design/ │ │ │ └── css/ │ │ │ ├── bootstrap-material-design.css │ │ │ └── ripples.css │ │ ├── style.css │ │ └── style_dark.css │ ├── tsconfig.json │ ├── typings.d.ts │ └── validators/ │ ├── NameTakenValidator.ts │ └── StartCapValidator.ts ├── tslint.json └── typings.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .angular-cli.json ================================================ { "project": { "version": "1.0.0-beta.32.3", "name": "StudioWeb" }, "apps": [ { "root": "src", "outDir": "dist", "assets": [ "assets", "favicon.ico", "manifest.json", "service-worker.js" ], "index": "index.html", "main": "main.ts", "test": "test.ts", "tsconfig": "tsconfig.json", "prefix": "app", "mobile": false, "styles": [ "../node_modules/font-awesome/css/font-awesome.css", "../node_modules/bootstrap/dist/css/bootstrap.min.css", "../node_modules/ng2-toastr/ng2-toastr.css", "../node_modules/primeng/resources/themes/bootstrap/theme.css", "../node_modules/primeng/resources/primeng.min.css", "./libs/bootstrap-timepicker/css/bootstrap-timepicker.css", "./libs/minicolors/jquery.minicolors.css", "./libs/ruler/ruler.css", "./styles/style.css", "./styles/jquery.timepicker.min.css" ], "scripts": [ "../node_modules/jquery/dist/jquery.min.js", "./libs/jquery.base64.js", "./libs/jquery-ui.js", "./libs/jquery.knob.min.js", "./libs/flashdetect/flashdetect.js", "./libs/rc4v1/rc4v1.js", "./libs/rc4v2/rc4v2.js", "./libs/qrcode/qrcode.js", "./libs/bootstrap-timepicker/js/bootstrap-timepicker.min.js", "./libs/jquery.timepicker/jquery.timepicker.min.js", "./libs/contextmenu/bootstrap-contextmenu.js", "./libs/stop-watch/stop-watch.js", "./store/signage_sdk.js", "../node_modules/bootstrap/dist/js/bootstrap.js", "../node_modules/bootbox/bootbox.js", "../node_modules/platform/platform.js", "./libs/fabric.require1-4-12.js", "./libs/minicolors/jquery.minicolors.js", "./libs/gradient/colorpicker.js", "./libs/xml2js/xml2js.js", "./libs/gradient/jquery.gradientpicker.js", "./libs/enjoyhint/enjoyhint.js", "../node_modules/xdate/src/xdate.js", "./libs/ruler/ruler.js" ], "environmentSource": "environments/environment.ts", "environments": { "dev": "environments/environment.ts", "hmr": "environments/environment.hmr.ts", "prod": "environments/environment.prod.ts" } } ], "addons": [ "../node_modules/font-awesome/fonts/*.+(otf|eot|svg|ttf|woff|woff2)", "./styles/fonts//montserrat.woff2" ], "packages": [], "e2e": { "protractor": { "config": "./protractor.conf.js" } }, "test": { "karma": { "config": "./karma.conf.js" } }, "defaults": { "styleExt": "css", "prefixInterfaces": false, "inline": { "style": false, "template": false }, "spec": { "class": false, "component": true, "directive": true, "module": false, "pipe": true, "service": true } } } ================================================ FILE: .editorconfig ================================================ # Editor configuration, see http://editorconfig.org root = true [*] charset = utf-8 indent_style = space indent_size = 2 insert_final_newline = true trim_trailing_whitespace = true [*.md] max_line_length = off trim_trailing_whitespace = false ================================================ FILE: .gitignore ================================================ # See http://help.github.com/ignore-files/ for more about ignoring files. # compiled output /dist/*.js /dist/** /dist /tmp # dependencies /node_modules /bower_components # IDEs and editors /.idea /.idea/workspace.xml /.vscode .project .classpath .c9/ *.launch .settings/ # misc /.sass-cache /documentation /connect.lock /coverage/* /libpeerconnection.log npm-debug.log testem.log /typings *.log.* #include !index.js !/typings # js/map *.js src/**/*.js *prod.js src/**/*.map #System Files .DS_Store Thumbs.db workspace.xml !signageSDK.js !service-worker.js !src/store/signage_sdk.js !src/libs/* !src/libs/**/* !src/locale/**/* #src/store/application.state.js #src/store/imsdb.interfaces.js #src/store/imsdb.interfaces_auto.js #src/store/store.data.js #*.xlf #*.xmb !temp/*.js !temp/*.txt ================================================ FILE: LICENSE ================================================ ====================================================================== MediaSignage Add-on license to GNU GENERAL PUBLIC LICENSE V3 Jan 7, 2014 MediaSignage Inc SignagePlayer open source under GPL V3 modified license. GPL V3 add-on License for MediaSignage Copyright (c) SignagePlayer Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software with some restriction as described in exhibit (A). The SignagePlayer open source software is available without any warranty, documentation and support. The primary purpose of this software is: A) Educational: study of this product design and architecture B) Security: transparency into the inner workings of the mediaCLOUD and related apps C) All usage permitted only within the operation of the DigitalSignage.com mediaCLOUD and only under MediaSignage Inc related products and services. D) Customization and modification which are governed under items A-C Exhibit A: ========== Usage, modification, compilation and any other benefit of the SignagePlayer source code must come in a form of connection into the MediaSignage public mediaCLOUD service or to a private mediaSERVER purchased from MediaSignage Inc. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . ================================================ FILE: README.md ================================================ StudioLite, Digital Signage for the rest of us ---------------------------------------

------------------------------------------------------------------------ StudioLite is an open source, 100% FREE, Digital Signage platform that was designed with ease of use in mind. With StudioLite anyone can have a Digital Signage solution that is entirely customizable. Take the source code, modify it, brand it and build a product that's right for you and your customers. Best of all, you will take full advantage of the world's most popular Digital Signage cloud so you don't have to worry about backend programming or even setup a server, it's all done for you using the mediaCLOUD. - Based on the poplar SignageStudio Pro ( [MediaSignage]: http://www.DigitalSignage.com ) - Connected to a private mediaSERVER or the public free mediaCLOUD - 100% open source based on GNU V3 license - Contributors are welcome, fork, modify and send pull requests - Powered by Google's Angular Framework + TypeScript + ngrx Links: ------------------------------------------------------------------------ - StudioLite documentation: http://www.digitalsignage.com/lite_docs/ - Cloud web app: http://go.digitalsignage.com - Home: http://lite.digitalsignage.com - Docs: http://www.digitalsignage.com/msdocs/ - Support: http://script.digitalsignage.com/forum/index.php/board,9.0.html - Developer video tutorial: http://goo.gl/nkx7wr - StudioLite intro video: http://lite.digitalsignage.com/video1.html - StudioLite advanced video: http://lite.digitalsignage.com/video2.html - Developers page: http://www.digitalsignage.com/_html/open_source_digital_signage.html - Angular: https://angular.io/ Technical data: ------------------------------------------------------------------------ - Build on top the latest version Google's Angular framework with a clean MV* design - Developed using the latest version of TypeScript and ngrx store - Powered by Bootstrap using responsive design for phones, tablets and desktops - Lazy loaded modules for best user load experience - Driven using Soap API and includes Helper SDK - Uses a local msdb (database) through SDK for offline work - Support Angular AOT mode - Support available through the MediaSignage support forum Installation: ------------------------------------------------------------------------ StudioLite can be downloaded directly from GitHub With git you will be able to easily update to the latest version of StudioLite as well as take advantage Angular CLI which is included in the bundle currently the application should run under: - typescript 2.4 - node 6.x to install and host: ``` npm install -g npm``` (this will ensure npm 5.4.2 >) ``` git clone https://github.com/born2net/studio-lite.git cd studio-lite npm install -g @angular/cli@latest npm install open browser to: http://localhost:4208/ ``` Customization: ------------------------------------------------------------------------ Keep in mind the SignageStudio lite as well as its related SDK Pepper are often released with new updates, so you will lose any changes you make to your code if you overwrite it with our release builds. To overcome this you can follow these guidelines: 1. Always be sure to override files and not modify the original source file. This is true for both CSS and JavaScript code. Simply load your version of the CSS after ours to apply your latest changes. HTML files can be diffed (see below). 2. Our code base is modulated and uses 100% object oriented design pattern. This allows you to sub class (aka inherit) from our classes and make your applied changes (use _.extend to mixin). 3. You can also use pre-processor scripts which replace code segments automatically using directives. 4. And finally, even while following steps 1-3 you may find that your code is broken due to design changes in the original repository. That’s when GitHub comes to the rescue. When your fork the source repository, you can always merge the tree onto your forked project. Use source control diff tool to merge the changes into your code and resolve any conflicts. With the above steps you can ensure that your source code is fully customizable while still keeping it synchronized with our ongoing development efforts. And if you built something wonderful, just send us a pull request so we check it out. If we like what you did we we will merge it into our code base, so you will always receive it when you fetch our changes. If you are not a developer you can hire professional, inexpensive help from sites like oDesk and Freelancer. Since Angular is an opinionated framework, any developer who is verse in Angular, will be able to quickly customize a solution for your own business logic. Videos: ------------------------------------------------------------------------ [![Development with StudioLite](http://img.youtube.com/vi/Znti-QVDjvg/0.jpg)](https://www.youtube.com/watch?v=Znti-QVDjvg&feature=youtu.be "Advanced angular tips and tricks") previous version: ------------------------------------------------------------------------ If you are looking for the previous version of StudioLite which was developed using BackboneJS, go to the branch: - https://github.com/born2net/studio-lite/tree/studiolite-backbone License: ------------------------------------------------------------------------ The SignageStudio Web Lite and Pepper SDK are available under GPL - V3 https://github.com/born2net/studio-lite/blob/master/LICENSE ================================================ FILE: dev/notes.txt ================================================ ////////////// NOTES //////////////// need to find a way to remove core-js from videoangular definitions sed -i.bak '/core-js/d' './node_modules/videogular2/src/core/vg-media/i-playable.d.ts ; sed -i.bak '/core-js/d' './node_modules/videogular2/src/core/services/vg-api.d.ts' reload video: https://github.com/videogular/videogular2/issues/442 npm uninstall -g angular-cli @angular/cli npm cache clean npm install -g @angular/cli@latest OR npm install -g @angular/cli@1.2.6 npm install -g @angular/cli@8.0.2 Local project package: rm -rf node_modules dist # use rmdir on Windows npm install --save-dev @angular/cli@latest <<<<<<<<<<<<<<<< npm install ng init --ng4 ================================================================================================================== ------------------------------------ >>> switch to StudioLite global env: ------------------------------------ nvm use 6.16.0 ; node --version; rm -r -f /cygdrive/c/Program\ Files/nodejs/node_modules/typescript; cp -r -f /cygdrive/c/Program\ Files/nodejs/node_modules/typescript-2.4.2/ /cygdrive/c/Program\ Files/nodejs/node_modules/typescript; npm remove -g @angular/cli ; npm install -g @angular/cli@1.2.4; ------------------------------------ >>> switch to latest global env: ------------------------------------ nvm use 10.15.0 ; node --version; rm -r -f /cygdrive/c/Program\ Files/nodejs/node_modules/typescript; cp -r -f /cygdrive/c/Program\ Files/nodejs/node_modules/typescript-3.5.1/ /cygdrive/c/Program\ Files/nodejs/node_modules/typescript; npm remove -g @angular/cli ; npm install -g @angular/cli@8.0.2; ================================================================================================================== debug sessions: reflection: http://127.0.0.1:8080/src/create_reflection.html local: http://localhost:4208/ campaigns: http://localhost:4208/campaigns FasterqRemoteStatus (base64): http://localhost:4208/index.html?mode=remoteStatus¶m=eyJjYWxsX3R5cGUiOiJFTUFJTCIsInNlcnZpY2VfaWQiOjE5LCJ2ZXJpZmljYXRpb24iOjY5NSwibGluZV9pZCI6IjIxMTAiLCJsaW5lX25hbWUiOiIgbmV3IGxpbmUgMSIsImJ1c2luZXNzX2lkIjoiMzU4NjEzIiwic21zIjoiIiwiZW1haWwiOiJib3JuMm5ldEBnbWFpbC5jb20iLCJkYXRlIjoiNC83LzIwMTcifQ== FasterqTerminal (rc4): http://localhost:4208/index.html?data=5b6bcb27d4547657cfe4e021e1c6761fe8b4a33c3d62983ee8f6b0ccbded7b8a6578e47862f0aca0ca7ac070a1a27121fe17f5c496483ee08cf0a061440f277a3e49dcaaf3bb198c71033ed398d60166e3b82c9153f4c3a6d346f7d11b3016 auto-login local demo_lite http://localhost:4208/index.html?param=dXNlcj1kZW1vX2xpdGVAbXMuY29tLHBhc3M9cGFzc3dvcmQ= auto-login remote demo_lite https://secure.digitalsignage.com/studioweb/index.html?param=dXNlcj1kZW1vX2xpdGVAbXMuY29tLHBhc3M9cGFzc3dvcmQ= auto-login local lite22 http://localhost:4208/index.html?param=dXNlcj1saXRlMjJAbXMuY29tLHBhc3M9MTIzMTIz auto-login local lite90 http://localhost:4208/index.html?param=dXNlcj1saXRlOTBAbXMuY29tLHBhc3M9MTIzMTIz auto-login local lite28 http://localhost:4208/index.html?param=dXNlcj1saXRlMjhAbXMuY29tLHBhc3M9MTIzMTIz ================================================================================================================== revert webpack ng tools: npm install --save @ngtools/webpack@1.2.4 ================================================================================================================== clear cache: npm clear cache npm install @angular/cli@latest -g npm install -g @angular/cli ================================================================================================================== reflection Usage: to run this script and generate interface sdk / msdb: run live-server inside /cygdrive/c/msweb/studiolite open browser to: http://127.0.0.1:8080/src/create_reflection.html ================================================================================================================== Switching fabric versions: add/remove import "fabric"; from app-modules.ts add/remove form angular-cli.json: "../node_modules/fabric/dist/fabric.js" or angular-cli.json: "./libs/fabric.require1-4-12.js", or to load remotely: ================================================================================================================== ================================================================================================================== for translation: 1. release_aot > to generate all ./dist files in english 2. x_translate > to create an english version of ./src/local/messages.xmb 3. develop_new_script > to generate ./src/local/hebrew.xtb from messages.xmb (use server API) https://secure.digitalsignage.com/getLocal/he/xxx/go%20find%20me%20and%20bring%20me%20here 4. release_aot_hebrew > to create new StudioLite in hebrew and upload to specific local server dir ================================================ FILE: e2e/app.e2e-spec.ts ================================================ import { NgKitchenSinkPage } from './app.po'; describe('ng-kitchen-sink App', function() { let page: NgKitchenSinkPage; beforeEach(() => { page = new NgKitchenSinkPage(); }); it('should display message saying app works', () => { page.navigateTo(); expect(page.getParagraphText()).toEqual('app works!'); }); }); ================================================ FILE: e2e/app.po.ts ================================================ import { browser, element, by } from 'protractor'; export class NgKitchenSinkPage { navigateTo() { return browser.get('/'); } getParagraphText() { return element(by.css('app-root h1')).getText(); } } ================================================ FILE: e2e/tsconfig.json ================================================ { "compileOnSave": false, "compilerOptions": { "declaration": false, "emitDecoratorMetadata": true, "experimentalDecorators": true, "module": "commonjs", "moduleResolution": "node", "outDir": "../dist/out-tsc-e2e", "sourceMap": true, "target": "es5", "typeRoots": [ "../node_modules/@types" ] } } ================================================ FILE: examples/adStatsSample.xml ================================================ stationId,packageId,packageContentId,packageName,contentName,day,hour,counts,duration,mph,rate stationId,localContentId,day,hour,counts,duration,mph,rate 14,136,3,8,9,1214.9,0.221447,0 14,136,3,9,14,1889.8,0.442799,0 14,136,3,10,14,1856.2,0.383568,0 14,136,3,11,14,1890.1,0.456984,0 14,136,3,12,14,1848.5,0.41431,0 14,136,3,13,13,1754.9,0.39168,0 14,136,3,14,14,1890.2,0.457008,0 14,136,3,15,14,1890.2,0.457008,0 14,136,3,16,14,1890.1,0.456983,0 14,136,3,17,14,1890,0.45696,0 14,136,3,18,14,1890.2,0.457008,0 14,136,3,19,14,1890.1,0.456984,0 14,136,3,20,14,1890,0.45696,0 14,136,3,21,14,1890.2,0.457008,0 14,136,3,22,15,2025.3,0.489672,0 14,136,3,23,14,1889.9,0.456935,0 14,136,4,0,14,1890.3,0.457031,0 14,136,4,1,14,1890.2,0.457008,0 14,136,4,2,14,1890.2,0.457008,0 14,136,4,3,14,1890.2,0.457007,0 14,136,4,4,14,1890.1,0.456984,0 14,136,4,5,14,1889.9,0.42432,0 14,136,4,6,14,1889.9,0.456935,0 14,136,4,7,14,1889.9,0.456935,0 14,136,4,8,14,1890.2,0.457007,0 14,136,4,9,14,1890,0.45696,0 14,136,4,10,15,2024.9,0.489575,0 14,136,4,11,14,1889.9,0.456935,0 14,136,4,12,14,1889.7,0.42427,0 14,136,4,13,14,1889.6,0.45686,0 14,136,4,14,14,1890,0.456959,0 14,136,4,15,14,1889.7,0.456885,0 14,136,4,16,14,1889.9,0.456935,0 14,136,4,17,14,1889.9,0.456935,0 14,136,4,18,14,1890,0.45696,0 14,136,4,19,15,2024.9,0.489575,0 14,136,4,20,14,1889.9,0.456934,0 14,136,4,21,14,1889.9,0.456935,0 14,136,4,22,14,1889.8,0.45691,0 14,136,4,23,14,1889.9,0.456935,0 14,136,5,0,14,1889.9,0.456935,0 14,136,5,1,14,1890.2,0.457008,0 14,136,5,2,14,1890.2,0.457008,0 14,136,5,3,14,1890,0.456959,0 14,136,5,4,14,1889.7,0.456885,0 14,136,5,5,14,1890.1,0.424368,0 14,136,5,6,14,1890,0.45696,0 14,136,5,7,14,1890,0.45696,0 14,136,5,8,13,1716.2,0.349707,0 14,136,5,9,14,1890.2,0.457008,0 14,136,5,10,9,1215.3,0.293832,0 14,136,7,12,2,27.4,0,0 14,136,7,13,13,1756.2,0.391992,0 14,136,7,14,14,1891.8,0.457392,0 14,136,7,15,15,2026.4,0.489936,0 14,136,7,16,14,1892,0.45744,0 14,136,7,17,14,1892.3,0.457512,0 14,136,7,18,14,1891.5,0.45732,0 14,136,7,19,14,1891.2,0.457248,0 14,136,7,20,14,1891.2,0.457248,0 14,136,7,21,14,1891.5,0.45732,0 14,136,7,22,14,1891.4,0.457296,0 14,136,7,23,14,1891.5,0.45732,0 14,136,8,0,14,1891.7,0.457368,0 14,136,8,1,14,1891.8,0.457391,0 14,136,8,2,14,1891.1,0.457224,0 14,136,8,3,15,2026.7,0.490008,0 14,136,8,4,13,1756.8,0.424752,0 14,136,8,5,14,1889.9,0.424319,0 14,136,8,6,14,1889.7,0.456884,0 14,136,8,7,14,1890.1,0.456984,0 14,136,8,8,13,1755,0.391703,0 14,136,8,9,14,1889.8,0.45691,0 14,136,8,10,15,2025,0.489599,0 14,136,8,11,4,539.9,0.130535,0 14,136,8,12,9,1047.5,0.195815,0 14,136,8,13,2,515.1,0.124539,0 14,137,3,8,8,481,0.112702,0 14,137,3,9,15,901.5,0.21123,0 14,137,3,10,12,699.9,0.165785,0 14,137,3,11,14,841.9,0.203545,0 14,137,3,12,13,781.4,0.188915,0 14,137,3,13,13,781.4,0.188916,0 14,137,3,14,14,841.5,0.203446,0 14,137,3,15,14,841.4,0.203421,0 14,137,3,16,14,841.4,0.20342,0 14,137,3,17,15,901.8,0.218025,0 14,137,3,18,14,841.3,0.203397,0 14,137,3,19,14,841.3,0.203396,0 14,137,3,20,14,841.6,0.203471,0 14,137,3,21,14,841.3,0.203396,0 14,137,3,22,14,841.4,0.20342,0 14,137,3,23,14,841.4,0.203421,0 14,137,4,0,14,841.3,0.203397,0 14,137,4,1,14,841.5,0.203447,0 14,137,4,2,14,841.6,0.20347,0 14,137,4,3,15,901.6,0.217976,0 14,137,4,4,14,841.4,0.203421,0 14,137,4,5,14,841.7,0.203495,0 14,137,4,6,14,841.4,0.20342,0 14,137,4,7,14,841.4,0.20342,0 14,137,4,8,14,841.3,0.203396,0 14,137,4,9,14,841.3,0.203396,0 14,137,4,10,14,841.3,0.203396,0 14,137,4,11,14,841.4,0.20342,0 14,137,4,12,14,813.4,0.196651,0 14,137,4,13,14,841.3,0.203396,0 14,137,4,14,15,901.2,0.217878,0 14,137,4,15,14,841.3,0.203396,0 14,137,4,16,14,841.4,0.20342,0 14,137,4,17,14,841.3,0.203396,0 14,137,4,18,14,841.2,0.203372,0 14,137,4,19,14,841.4,0.20342,0 14,137,4,20,14,841.3,0.203396,0 14,137,4,21,14,841.2,0.203372,0 14,137,4,22,14,840.9,0.2033,0 14,137,4,23,14,841.3,0.203396,0 14,137,5,0,15,901.3,0.217902,0 14,137,5,1,14,841.4,0.20342,0 14,137,5,2,14,841.2,0.203372,0 14,137,5,3,14,841.4,0.203421,0 14,137,5,4,14,841.4,0.20342,0 14,137,5,5,13,781.3,0.18889,0 14,137,5,6,15,901.4,0.217926,0 14,137,5,7,14,841.2,0.203372,0 14,137,5,8,11,637.2,0.154052,0 14,137,5,9,14,841.3,0.203396,0 14,137,5,10,9,540.9,0.13077,0 14,137,7,13,13,782.8,0.18926,0 14,137,7,14,14,843.1,0.203841,0 14,137,7,15,14,842.7,0.203742,0 14,137,7,16,14,842.8,0.203765,0 14,137,7,17,14,842.7,0.203741,0 14,137,7,18,14,842.3,0.203643,0 14,137,7,19,14,842.8,0.203765,0 14,137,7,20,14,843.1,0.203839,0 14,137,7,21,15,904,0.218564,0 14,137,7,22,14,842.6,0.203717,0 14,137,7,23,14,842.7,0.203741,0 14,137,8,0,14,842.5,0.203693,0 14,137,8,1,14,842.9,0.203792,0 14,137,8,2,14,842.3,0.203642,0 14,137,8,3,14,842.8,0.203767,0 14,137,8,4,14,842.1,0.203595,0 14,137,8,5,14,841.4,0.20342,0 14,137,8,6,14,841.4,0.20342,0 14,137,8,7,14,841.3,0.203396,0 14,137,8,8,13,781.2,0.188866,0 14,137,8,9,14,841.4,0.20342,0 14,137,8,10,14,841.4,0.20342,0 14,137,8,11,5,300.5,0.07265,0 14,137,8,12,6,360.6,0.08718,0 14,137,8,13,1,60.1,0.01453,0 14,138,3,8,7,420.7,0.098574,0 14,138,3,9,14,841.3,0.197123,0 14,138,3,10,12,721.4,0.170824,0 14,138,3,11,14,841.6,0.20347,0 14,138,3,12,13,781.5,0.18894,0 14,138,3,13,13,781.3,0.18889,0 14,138,3,14,14,841.4,0.203421,0 14,138,3,15,14,841.6,0.20347,0 14,138,3,16,14,841.7,0.203495,0 14,138,3,17,14,841.4,0.20342,0 14,138,3,18,14,841.6,0.203471,0 14,138,3,19,15,901.8,0.218025,0 14,138,3,20,14,841.6,0.20347,0 14,138,3,21,14,841.5,0.203446,0 14,138,3,22,14,841.4,0.20342,0 14,138,3,23,14,841.4,0.203421,0 14,138,4,0,14,841.6,0.20347,0 14,138,4,1,14,841.3,0.203397,0 14,138,4,2,14,841.4,0.20342,0 14,138,4,3,14,841.3,0.203396,0 14,138,4,4,14,841.4,0.203421,0 14,138,4,5,13,781.4,0.188915,0 14,138,4,6,14,841.2,0.203373,0 14,138,4,7,14,841.5,0.203445,0 14,138,4,8,15,901.6,0.217975,0 14,138,4,9,14,841.4,0.20342,0 14,138,4,10,14,841.4,0.203421,0 14,138,4,11,14,841.4,0.20342,0 14,138,4,12,13,781.2,0.188866,0 14,138,4,13,14,841.3,0.203396,0 14,138,4,14,14,841.4,0.20342,0 14,138,4,15,14,841.3,0.203396,0 14,138,4,16,14,841.4,0.203421,0 14,138,4,17,15,901.7,0.218,0 14,138,4,18,14,841.4,0.20342,0 14,138,4,19,14,841.4,0.20342,0 14,138,4,20,14,841.4,0.20342,0 14,138,4,21,14,841.4,0.203421,0 14,138,4,22,14,841.4,0.20342,0 14,138,4,23,14,841.2,0.203372,0 14,138,5,0,14,841.6,0.20347,0 14,138,5,1,14,841.3,0.203396,0 14,138,5,2,14,841.4,0.20342,0 14,138,5,3,15,901.5,0.217952,0 14,138,5,4,14,841.5,0.203445,0 14,138,5,5,13,781.2,0.188866,0 14,138,5,6,14,841.4,0.20342,0 14,138,5,7,14,841.5,0.203445,0 14,138,5,8,11,661.2,0.159855,0 14,138,5,9,14,841.6,0.20347,0 14,138,5,10,9,541,0.130795,0 14,138,7,13,13,782.7,0.189236,0 14,138,7,14,14,842.8,0.203766,0 14,138,7,15,14,843.1,0.203838,0 14,138,7,16,14,842.9,0.20379,0 14,138,7,17,14,842.5,0.203693,0 14,138,7,18,14,843.3,0.203888,0 14,138,7,19,14,842.9,0.203791,0 14,138,7,20,14,843.1,0.203841,0 14,138,7,21,14,843,0.203814,0 14,138,7,22,14,843.2,0.203865,0 14,138,7,23,14,843,0.203815,0 14,138,8,0,15,902.7,0.218248,0 14,138,8,1,14,842.4,0.203669,0 14,138,8,2,14,843.1,0.203837,0 14,138,8,3,14,842.4,0.203667,0 14,138,8,4,14,842.8,0.203766,0 14,138,8,5,13,781.1,0.188842,0 14,138,8,6,14,841.4,0.20342,0 14,138,8,7,14,841.4,0.203421,0 14,138,8,8,14,793.3,0.191791,0 14,138,8,9,14,841.3,0.203397,0 14,138,8,10,14,841.5,0.203445,0 14,138,8,11,4,240.4,0.05812,0 14,138,8,12,6,360.8,0.08723,0 14,138,8,13,1,60.1,0.01453,0 18,136,2,15,2,269.9,0.06324,0 18,136,2,16,1,134.9,0.031608,0 18,137,2,15,1,60,0.014058,0 18,137,2,16,2,120.1,0.02814,0 18,138,2,15,2,120.1,0.02814,0 18,138,2,16,2,120,0.028116,0 19,136,3,12,1,134.9,0,0 19,136,3,13,14,1398.6,0.156452,0 19,136,3,16,2,270.3,0.032736,0 19,136,4,13,7,9960.4,2.301675,0 19,137,3,12,1,66.1,0.015981,0 19,137,3,13,5,304.1,0.073522,0 19,137,3,16,1,60.5,0.014627,0 19,137,4,13,4,244.5,0.059113,0 19,138,3,12,1,93.9,0.022702,0 19,138,3,13,5,303.3,0.073329,0 19,138,3,16,1,61.2,0.014796,0 19,138,4,13,4,185.6,0.044872,0 ================================================ FILE: examples/apps_table.txt ================================================ app_id app_name help_name description uninstallable hidden price min_version 10005 Embeded Resource "" Embeded Resource False True 0 4.12 10010 Scene "" Scene False True 0 4.12 10020 External Resource "" External Resource False False 0 4.12 10030 Label "" Label False False 0 4.12 10040 Html "" Html False False 0 4.12 10050 Rss Text "" Rss Text False False 0 4.12 10060 Custom Rss "" Custom Rss False False 0 4.12 10070 Media Rss/Podcast "" Media Rss/Podcast False False 0 4.12 10080 Weather "" Weather False False 0 4.12 10090 Stock "" Stock False False 0 4.12 10100 Catalog "" Catalog False False 0 4.12 10110 Capture/Camera "" Capture/Camera True False 0 4.12 10120 Clock "" Clock True False 0 4.12 10122 Countdown "" Countdown True False 0 4.12 10130 Grid/Chart "" Grid/Chart True False 0 4.12 10140 Ext Application "" Ext Application True False 0 4.12 10145 Webkit "" Webkit True False 0 4.33 10150 Advertising "" Advertising True False 0 4.12 10160 QR Code "" QR Code True False 0 4.12 10180 CollectionViewer "" CollectionViewer True False 0 4.12 10185 Location based "" Location based True False 0 4.33 10190 XML Player "" XML Player True False 0 4.12 10195 JSON Player "" JSON Player True False 0 4.33 10210 Twitter "" Twitter True False 0 4.12 10220 YouTube "" YouTube True False 0 4.12 10300 Form "" Form True False 0 4.12 10400 Message "" Message True False 0 4.12 10500 Label Queue "" Label Queue True False 0 4.15 11000 Browser "" Browser True False 0 4.33 12000 Digg "" Digg True False 0 4.33 12010 World weather "" Weather True False 0 4.33 12020 Facebook "" Facebook True False 0 4.33 12030 Google calendar "" Google calendar True False 0 4.33 12032 Google Sheets "" Google Sheets True False 0 4.33 12040 Google plus "" Google plus True False 0 4.33 12050 Picasa "" Picasa True False 0 4.33 12060 Instagram "" Instagram True False 0 4.33 12070 Google drive "" Google drive True False 0 4.33 12080 500px "" 500px True False 0 4.33 12090 Pinterest "" Pinterest True False 0 4.33 12100 FasterQ "" FasterQ True False 0 4.33 12200 Tumblr "" Tumblr True False 0 4.33 12210 Dropbox "" Dropbox True False 0 4.33 12220 Flickr "" Flickr True False 0 4.33 12230 Twitter "" Twitter True False 0 4.33 12240 Yelp "" Yelp True False 0 4.33 12250 Etsy "" Etsy True False 0 4.33 12260 Mashape "" Mashape True False 0 4.33 ================================================ FILE: examples/calendar_google.xml ================================================ scene Player player ================================================ FILE: examples/collection.xml ================================================ ================================================ FILE: examples/collection_scene.xml ================================================ ================================================ FILE: examples/digg.xml ================================================ Scene: Player: ================================================ FILE: examples/googlesheets.xml ================================================ ================================================ FILE: examples/instagram_feed.xml ================================================ scene: player: ================================================ FILE: examples/jsonItem.xml ================================================ ================================================ FILE: examples/jsonPlayer.xml ================================================ ================================================ FILE: examples/json_as3_maps.xml ================================================ 0) { for each(var item:Object in m_fieldCollection.source) { if (item.name==BlockJsonItemPlayer(m_playerLoader.player).fieldName) { fieldCombo.selectedItem = item; break; } } fieldComboForm.label = "Field name"; fieldComboForm.visible = true; fieldComboForm.height = NaN; spreadsheetFrame.visible = false; fieldPathForm.enabled = false; fieldTypeForm.enabled = false; fieldCombo.visible = true; } else { fieldComboForm.label = "Field name"; fieldComboForm.visible = false; fieldComboForm.height = 10; fieldPathForm.enabled = true; fieldTypeForm.enabled = true; fieldCombo.visible = true; } fontCtrl.fontItem = BlockJsonItemPlayer(m_playerLoader.player).fontItem; for each(var fieldType:Object in ArrayList(fieldTypeCombo.dataProvider).source) { if (fieldType.name==BlockJsonItemPlayer(m_playerLoader.player).fieldType) { fieldTypeCombo.selectedItem = fieldType; break; } } updateButtons(); if (BlockJsonItemPlayer(m_playerLoader.player).fieldType=="date") { dateFormat.text = BlockJsonItemPlayer(m_playerLoader.player).dateFormat; } else if (BlockJsonItemPlayer(m_playerLoader.player).fieldType=="resource" || BlockJsonItemPlayer(m_playerLoader.player).fieldType=="object") { maintainAspectRatio.selected = BlockJsonItemPlayer(m_playerLoader.player).maintainAspectRatio; } } private function onFieldChanged(event:Event):void { BlockJsonItemPlayer(m_playerLoader.player).fieldType= fieldCombo.selectedItem.type; fieldPath.text = BlockJsonItemPlayer(m_playerLoader.player).fieldName = fieldCombo.selectedItem.name; if (BlockJsonItemPlayer(m_playerLoader.player).fieldType=="date") { dateFormat.text = BlockJsonItemPlayer(m_playerLoader.player).dateFormat; } else if (BlockJsonItemPlayer(m_playerLoader.player).fieldType=="resource" || BlockJsonItemPlayer(m_playerLoader.player).fieldType=="object") { maintainAspectRatio.selected = BlockJsonItemPlayer(m_playerLoader.player).maintainAspectRatio; } updateButtons(); for each(var fieldType:Object in ArrayList(fieldTypeCombo.dataProvider).source) { if (fieldType.name==BlockJsonItemPlayer(m_playerLoader.player).fieldType) { fieldTypeCombo.selectedItem = fieldType; break; } } } private function onFieldPathChanged(event:Event):void { BlockJsonItemPlayer(m_playerLoader.player).fieldName = fieldPath.text; } private function onFieldType():void { BlockJsonItemPlayer(m_playerLoader.player).fieldType= fieldTypeCombo.selectedItem.name; updateButtons(); } private function onFontChanged(event:Event):void { BlockJsonItemPlayer(m_playerLoader.player).fontItem = fontCtrl.fontItem; } private function onMaintainAspectRatio():void { BlockJsonItemPlayer(m_playerLoader.player).maintainAspectRatio = maintainAspectRatio.selected; } private function onDateFormat():void { BlockJsonItemPlayer(m_playerLoader.player).dateFormat = dateFormat.text; } private function onSpreadsheetCell():void { BlockJsonItemPlayer(m_playerLoader.player).fieldName = fieldPath.text = "$cells." + row.value + "." + col.value + ".value"; } private function updateButtons():void { if (BlockJsonItemPlayer(m_playerLoader.player).fieldType=="text") { fontCtrl.visible = true; maintainAspectRatioForm.visible = false; dateFormatForm.visible = dateInfo.visible = false; } else if (BlockJsonItemPlayer(m_playerLoader.player).fieldType=="date") { fontCtrl.visible = true; maintainAspectRatioForm.visible = false; dateFormatForm.visible = dateInfo.visible = true; } else { fontCtrl.visible = false; maintainAspectRatioForm.visible = true; dateFormatForm.visible = dateInfo.visible = false; } } ]]> Y Year YY YYYY M Month in year M MM MMM MMM D Day in month D DD E Days in week E EE EEE EEEE A AM/PM AM or PM J Hour in Day 0-23 H Hour in Day 1-24 K Hour in am/pm 0-11 L Hour in am/pm 1-12 N Minute in hour N NN S Seconds in minute SS ================================================ FILE: examples/json_scenes.xml ================================================ ================================================ FILE: examples/jsonsheets.xml ================================================ ================================================ FILE: examples/location.xml ================================================ ================================================ FILE: examples/player_weather_scene.xml ================================================ ================================================ FILE: examples/reseller_sample.xml ================================================ ================================================ FILE: examples/sceneSample.xml ================================================ I am qr <Font fontSize="16" fontColor="102" fontFamily="Arial" fontWeight="bold" fontStyle="normal" textDecoration="none" textAlign="left"/> ================================================ FILE: examples/sceneSampleLayout.xml ================================================ ================================================ FILE: examples/twitterv3.xml ================================================ Scene Player ================================================ FILE: examples/worldweather.xml ================================================ ================================================ FILE: notes.txt ================================================ ////////////// NOTES //////////////// need to find a way to remove core-js from videoangular definitions sed -i.bak '/core-js/d' './node_modules/videogular2/src/core/vg-media/i-playable.d.ts ; sed -i.bak '/core-js/d' './node_modules/videogular2/src/core/services/vg-api.d.ts' reload video: https://github.com/videogular/videogular2/issues/442 npm uninstall -g angular-cli @angular/cli npm cache clean npm install -g @angular/cli@latest Local project package: rm -rf node_modules dist # use rmdir on Windows npm install --save-dev @angular/cli@latest <<<<<<<<<<<<<<<< npm install ng init --ng4 ================================================================================================================== debug sessions: reflection: http://127.0.0.1:8080/src/create_reflection.html local: http://localhost:4208/ campaigns: http://localhost:4208/campaigns FasterqRemoteStatus (base64): http://localhost:4208/index.html?mode=remoteStatus¶m=eyJjYWxsX3R5cGUiOiJFTUFJTCIsInNlcnZpY2VfaWQiOjE5LCJ2ZXJpZmljYXRpb24iOjY5NSwibGluZV9pZCI6IjIxMTAiLCJsaW5lX25hbWUiOiIgbmV3IGxpbmUgMSIsImJ1c2luZXNzX2lkIjoiMzU4NjEzIiwic21zIjoiIiwiZW1haWwiOiJib3JuMm5ldEBnbWFpbC5jb20iLCJkYXRlIjoiNC83LzIwMTcifQ== FasterqTerminal (rc4): http://localhost:4208/index.html?data=5b6bcb27d4547657cfe4e021e1c6761fe8b4a33c3d62983ee8f6b0ccbded7b8a6578e47862f0aca0ca7ac070a1a27121fe17f5c496483ee08cf0a061440f277a3e49dcaaf3bb198c71033ed398d60166e3b82c9153f4c3a6d346f7d11b3016 auto-login local demo_lite http://localhost:4208/index.html?param=dXNlcj1kZW1vX2xpdGVAbXMuY29tLHBhc3M9cGFzc3dvcmQ= auto-login remote demo_lite https://secure.digitalsignage.com/studioweb/index.html?param=dXNlcj1kZW1vX2xpdGVAbXMuY29tLHBhc3M9cGFzc3dvcmQ= auto-login remote demo_lite https://goo.gl/rru309 auto-login local lite22 http://localhost:4208/index.html?param=dXNlcj1saXRlMjJAbXMuY29tLHBhc3M9MTIzMTIz auto-login local lite90 http://localhost:4208/index.html?param=dXNlcj1saXRlOTBAbXMuY29tLHBhc3M9MTIzMTIz auto-login local lite28 http://localhost:4208/index.html?param=dXNlcj1saXRlMjhAbXMuY29tLHBhc3M9MTIzMTIz ================================================================================================================== revert webpack ng tools: npm install --save @ngtools/webpack@1.2.4 ================================================================================================================== clear cache: npm clear cache npm install @angular/cli@latest -g npm install -g @angular/cli ================================================================================================================== reflection Usage: to run this script and generate interface sdk / msdb: run live-server inside /cygdrive/c/msweb/studiolite open browser to: http://127.0.0.1:8080/src/create_reflection.html ================================================================================================================== Switching fabric versions: add/remove import "fabric"; from app-modules.ts add/remove form angular-cli.json: "../node_modules/fabric/dist/fabric.js" or angular-cli.json: "./libs/fabric.require1-4-12.js", or to load remotely: ================================================================================================================== ================================================ FILE: package.json ================================================ { "name": "studioweb", "version": "0.1.778", "license": "Modified GPL (see readme.md)", "angular-cli": {}, "scripts": { "postinstall": "npm run dev", "compodoc": "./node_modules/.bin/compodoc -p src/tsconfig.json", "x_disk-build": "ng build && npm run sw", "x_disk-serve": "cd dist && live-server --port=4208 --host=localhost --entry-file=/index.html", "x_reg_release": "rm -r -f ./dist && npm run x_bump && npm run x_prod && npm run x_rsync", "x_start": "ng serve", "x_lint": "tslint \"src/**/*.ts\"", "x_prod_jit": "ng build --target=production --base-href ./ --aot fiw.alse", "x_prod_aot": "ng build --target=production --base-href ./ --aot true", "x_prod_aot_hebrew": "ng build --locale iw -i18n-file src/locale/iw.xtb --i18n-format xtb --target=production --base-href ./ --aot true", "x_rsync": "rsync --progress --chmod=ug=rwx --chmod=o=rx -av --stats -e ssh /cygdrive/c/msweb/studiolite/dist/ Sean@secure.digitalsignage.com:/var/www/sites/dynasite/htdocs/_msportal/_js/_node/_studioweb", "x_rsync_docs": "rsync --progress --chmod=ug=rwx --chmod=o=rx -av --stats -e ssh /cygdrive/c/msweb/studiolite/documentation/ Sean@digitalsignage.com:/var/www/sites/mediasignage.com/htdocs/lite_docs", "x_rsync_iw": "rsync --progress --chmod=ug=rwx --chmod=o=rx -av --stats -e ssh /cygdrive/c/msweb/studiolite/dist/ Sean@digitalsignage.com:/var/www/sites/dynasite/htdocs/_msportal/_js/_node/_studioweb/locale/iw", "x_sw": "sw-precache --root=dist --config=sw-precache-config.js", "x_bump": "gulp x_bump", "x_cssDev": "gulp cssDev", "x_cssRelease": "gulp cssRelease", "x_translate": "node_modules/.bin/ng-xi18n -p src/tsconfig.json --i18nFormat=xmb", "x_node_copy": "cp -r -f ./node_modules/ ./dist/out-tsc/", "cp module": "cp ./node_modules/ng-mslib/src/myng-component.ts ./node_modules/ng-mslib/dist/", "generate_locale": "npm run x_node_copy && npm run x_translate", "hmr": "ng serve --port 4208 --aot false --hmr -e=hmr", "dev": "npm run x_cssDev && ng serve --port 4208 --aot false", "release_aot_hebrew": "npm run x_cssRelease && rm -r -f ./dist && npm run x_bump && npm run x_prod_aot_hebrew && npm run x_sw && npm run x_rsync_iw", "release_aot": "npm run x_cssRelease && rm -r -f ./dist && npm run x_bump && npm run x_prod_aot && npm run x_sw && npm run x_rsync", "release_aot_no_sync": "npm run x_cssRelease && rm -r -f ./dist && npm run x_bump && npm run x_prod_aot && npm run x_sw", "release_jit": "npm run x_cssRelease && rm -r -f ./dist && npm run x_bump && npm run x_prod_jit && npm run x_sw && npm run x_rsync" }, "dependencies": { "@angular/animations": "^4.3.1", "@angular/common": "^4.3.1", "@angular/compiler": "^4.3.1", "@angular/core": "^4.3.1", "@angular/forms": "^4.3.1", "@angular/http": "^4.3.1", "@angular/platform-browser": "^4.3.1", "@angular/platform-browser-dynamic": "^4.3.1", "@angular/router": "^4.3.1", "@ngrx/core": "^1.2.0", "@ngrx/effects": "^2.0.0", "@ngrx/store": "^2.2.1", "@ngrx/store-devtools": "^3.2.3", "@ngtools/webpack": "^1.2.4", "@types/bootbox": "^4.4.30", "@types/gsap": "1.19.0", "@types/lodash": "^4.14.52", "@types/xdate": "^0.8.27", "angular-pipes": "^5.4.0", "angular2-fontawesome": "~0.8.0", "angular2-google-maps": "^0.17.0", "angular2-highcharts": "^0.4.1", "angular2-redux-util": "^0.8.86", "angular2-uuid": "^1.1.0", "bootbox": "^4.4.0", "bootstrap": "^3.3.7", "core-js": "^2.4.1", "fabric": "git://github.com/born2net/fabric.js.git", "font-awesome": "~4.7.0", "gsap": "1.19.1", "gulp-bump": "^2.5.1", "hammerjs": "^2.0.8", "immutable": "^3.8.1", "jquery": "^3.1.1", "lodash": "^4.17.4", "moment": "^2.17.1", "ng-mslib": "^1.0.109", "ng-validators": "^0.2.1", "ng2-bs3-modal": "^0.10.4", "ng2-toastr": "^4.1.2", "ngrx-store-freeze": "^0.1.6", "ngx-bootstrap": "^1.7.1", "ngx-color-picker": "^4.0.0", "ngx-contextmenu": "^1.0.3", "platform": "^1.3.3", "primeng": "^4.0.0-rc.2", "print-js": "^1.0.34", "redux": "^3.6.0", "redux-thunk": "^2.1.0", "reselect": "^2.5.4", "rxjs": "^5.4.2", "stacktrace-js": "^1.3.1", "string": "^3.3.3", "ts-helpers": "^1.1.1", "typescript": "2.4.0", "videogular2": "^5.2.0", "x2js": "^3.1.0", "xdate": "^0.8.0", "xml2js": "^0.4.17", "zone.js": "^0.8.12" }, "devDependencies": { "@angular/cli": "^1.0.1", "@angular/compiler-cli": "^4.3.1", "@angular/language-service": "^4.3.1", "@angularclass/hmr": "^1.2.2", "@compodoc/compodoc": "^1.0.0-beta.9", "@types/core-js": "^0.9.41", "@types/hammerjs": "^2.0.33", "@types/immutable": "^3.8.6", "@types/jasmine": "2.5.38", "@types/jquery": "^2.0.34", "@types/node": "^6.0.42", "@types/x2js": "0.0.27", "autoprefixer": "^6.6.1", "co": "^4.6.0", "codelyzer": "~2.0.0", "fs-extra": "^2.1.2", "gulp": "^3.9.1", "gulp-comment-swap": "0.0.10", "gulp-concat": "^2.6.0", "gulp-git": "^1.6.0", "gulp-inject": "^1.3.1", "gulp-insert": "^0.5.0", "gulp-replace": "^0.5.4", "gulp-rimraf": "^0.2.0", "gulp-shell": "^0.5.2", "gulp-sourcemaps": "^1.6.0", "gulp-tslint": "^4.3.1", "gulp-tslint-stylish": "^1.1.1", "gulp-typedoc": "^1.2.1", "gulp-typescript": "^3.0.1", "gulp-uglify": "^1.2.0", "gulp-util": "^3.0.7", "gulp-watch": "^4.2.4", "node-fetch": "^1.6.3", "replace": "^0.3.0", "rsync": "^0.5.0", "run-sequence": "^1.2.2", "sw-precache": "4.2.1", "ts-node": "~2.0.0", "tslint": "~4.4.2", "webdriver-manager": "10.2.5", "xml2js": "^0.4.17" } } ================================================ FILE: src/Lib.ts ================================================ /** Common Library **/ import {Injectable} from "@angular/core"; import * as Immutable from "immutable"; import {List, Map} from "immutable"; import * as _ from "lodash"; import * as moment from 'moment' import {Observable} from "rxjs"; import {PartialObserver} from "rxjs/Observer"; import {AnonymousSubscription} from "rxjs/Subscription"; import {environment} from "./environments/environment"; import {FormGroup, ValidatorFn} from "@angular/forms"; export var simpleRegExp = '[\\[\\]\\-A-Za-z0-9_~=!:@\.|\ ]{3,50}'; //(https?:\/\/(?:www\.|(?!www))[^\s\.]+\.[^\s]{2,}|www\.[^\s]+\.[^\s]{2,}) export var urlRegExp = `(https?:\/\/(?:www\.|(?!www))\.*)` /** this control value must be equal to given control's value */ export function equalValueValidator(targetKey: string, toMatchKey: string): ValidatorFn { return (group: FormGroup): { [key: string]: any } => { const target = group.controls[targetKey]; const toMatch = group.controls[toMatchKey]; if (target.touched && toMatch.touched) { const isMatch = target.value === toMatch.value; // set equal value error on dirty controls if (!isMatch && target.valid && toMatch.valid) { toMatch.setErrors({equalValue: targetKey}); const message = targetKey + ' != ' + toMatchKey; return {'equalValue': message}; } if (isMatch && toMatch.hasError('equalValue')) { toMatch.setErrors(null); } } return null; }; } const rxjsDebugger = true; const rc4Key = '226a3a42f34ddd778ed2c3ba56644315'; Observable.prototype.sub = Observable.prototype.subscribe; window['con'] = (msg, stringify) => { if (Lib.DevMode()) { if (stringify) msg = JSON.stringify(msg); console.info(`${_.uniqueId()}:${new Date().toLocaleTimeString()} ${msg}`); } } declare module 'rxjs/Observable' { interface Observable { debug: (...any) => Observable } // interface Observable { // get(next?: (value: T) => void, error?: (error: any) => void, complete?: () => void): Subscription; // } } declare module "rxjs/Observable" { interface Observable { sub: (observerOrNext: PartialObserver | ((value: T) => void), error: (error: any) => void, complete?: () => void) => AnonymousSubscription; } } Observable.prototype.debug = function (message: string) { return this.do( nextValue => { if (rxjsDebugger) { console.debug('ObsDebug-I: ' + message, (nextValue.type || nextValue)) } }, error => { if (rxjsDebugger) { console.error('ObsDebug-E: ' + message, error) } }, () => { if (rxjsDebugger) { console.debug('ObsDebug-C: ' + message); /** for DevTools colors: **/ //console.log("%cObsDebug-C %s", "color: red", message); } } ); }; @Injectable() export class Lib { static Con(msg: any, stringify?: boolean) { con(msg, stringify) } /** * * @param dateString format of date + time: /Date(1469923200000+0000)/" * @returns {any} * @constructor */ static ProcessDateField(dateString: string, addDay: boolean = false): any { if (_.isUndefined(dateString)) return ''; var epoc = dateString.match(/Date\((.*)\)/) if (epoc[1]) { var date = epoc[1].split('+')[0] var time = epoc[1].split('+')[1] var result; //todo: adding +1 on save to server hack, need to ask Alon if (addDay) { result = moment(Number(date)).add(1, 'day'); } else { result = moment(Number(date)); } return moment(result).format('YYYY-MM-DD'); /** moment examples var a = moment().unix().format() console.log(moment.now()); console.log(moment().format('dddd')); console.log(moment().startOf('day').fromNow()); **/ } } static ToValidNumber(i_value): number { if (_.isNaN(i_value)) return 0; if (_.isNumber(i_value)) return i_value; return 0; } static Try(i_fn: () => void) { try { i_fn(); } catch (e) { if (Lib.DevMode()) console.error('Lib.Try exception in function: ' + i_fn + ' ' + e); } } /** deep compare two objects **/ static IsEqual(obj1, obj2) { function _equals(obj1, obj2) { var clone = $.extend(true, {}, obj1); var cloneStr = JSON.stringify(clone); return cloneStr === JSON.stringify($.extend(true, clone, obj2)); } return _equals(obj1, obj2) && _equals(obj2, obj1); } static GetThemeColor() { var light = true; if (light) return '#428ac9 '; return '#eb7c66'; } static EncryptUserPass(i_user, i_pass) { var rc4 = new RC4(rc4Key); var crumb = i_user + ':SignageStudioLite:' + i_pass + ':' + ' USER' return rc4.doEncrypt(crumb); } static AlertOnLeave() { if (!Lib.DevMode()) { window.onbeforeunload = function (e) { var message = "Did you save your changes?", e = e || window.event; // For IE and Firefox if (e) { e.returnValue = message; } // For Safari return message; }; } } /** Format a seconds value into an object broken into hours / minutes / seconds @method formatSecondsToObject @param {Number} i_totalSeconds @return {Object} **/ static FormatSecondsToObject(i_totalSeconds) { var seconds: any = 0; var minutes: any = 0; var hours: any = 0; var totalInSeconds = i_totalSeconds; if (i_totalSeconds >= 3600) { hours = Math.floor(i_totalSeconds / 3600); i_totalSeconds = i_totalSeconds - (hours * 3600); } if (i_totalSeconds >= 60) { minutes = Math.floor(i_totalSeconds / 60); seconds = i_totalSeconds - (minutes * 60); } if (hours == 0 && minutes == 0) seconds = i_totalSeconds; var playbackLength = { hours: parseInt(hours), minutes: parseInt(minutes), seconds: parseInt(seconds), totalInSeconds: parseInt(totalInSeconds) }; return playbackLength; } /** * * @param dateString format of date + time: /Date(1469923200000+0000)/" * @returns {any} * @constructor */ static ProcessDateFieldToUnix(dateString: string, addDay: boolean = false): any { if (_.isUndefined(dateString)) return ''; //todo: adding +1 on save to server hack, need to ask Alon if (addDay) { return moment(dateString, 'YYYY-MM-DD').add(0, 'day').valueOf(); } else { return moment(dateString, 'YYYY-MM-DD').valueOf(); } } /** Pad zeros @method padZeros @param {Number} n value @param {Number} width pre-pad width @param {Number} z negative as in '-' @return {Number} zero padded string **/ static PadZeros(n, width, z) { z = z || '0'; n = n + ''; return n.length >= width ? n : new Array(width - n.length + 1).join(z) + n; } /** Convert number or string to float with double precision @method parseToFloatDouble @param {Object} i_value @return {Number} **/ static ParseToFloatDouble(i_value: any): number { return parseFloat(parseFloat(i_value).toFixed(2)); } /** Capitilize first letter @method capitaliseFirst @param {String} string @return {String} string **/ static CapitaliseFirst(string) { return string.charAt(0).toUpperCase() + string.slice(1).toLowerCase(); } /** base64Encode @method base64Encode @param {String} @return {String} **/ static Base64Encode(str) { var c1, c2, c3; var Base64 = { _keyStr: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=", encode: function (e) { var t = ""; var n, r, i, s, o, u, a; var f = 0; e = Base64._utf8_encode(e); while (f < e.length) { n = e.charCodeAt(f++); r = e.charCodeAt(f++); i = e.charCodeAt(f++); s = n >> 2; o = (n & 3) << 4 | r >> 4; u = (r & 15) << 2 | i >> 6; a = i & 63; if (isNaN(r)) { u = a = 64 } else if (isNaN(i)) { a = 64 } t = t + this._keyStr.charAt(s) + this._keyStr.charAt(o) + this._keyStr.charAt(u) + this._keyStr.charAt(a) } return t }, decode: function (e) { var t = ""; var n, r, i; var s, o, u, a; var f = 0; e = e.replace(/[^A-Za-z0-9\+\/\=]/g, ""); while (f < e.length) { s = this._keyStr.indexOf(e.charAt(f++)); o = this._keyStr.indexOf(e.charAt(f++)); u = this._keyStr.indexOf(e.charAt(f++)); a = this._keyStr.indexOf(e.charAt(f++)); n = s << 2 | o >> 4; r = (o & 15) << 4 | u >> 2; i = (u & 3) << 6 | a; t = t + String.fromCharCode(n); if (u != 64) { t = t + String.fromCharCode(r) } if (a != 64) { t = t + String.fromCharCode(i) } } t = Base64._utf8_decode(t); return t }, _utf8_encode: function (e) { e = e.replace(/\r\n/g, "\n"); var t = ""; for (var n = 0; n < e.length; n++) { var r = e.charCodeAt(n); if (r < 128) { t += String.fromCharCode(r) } else if (r > 127 && r < 2048) { t += String.fromCharCode(r >> 6 | 192); t += String.fromCharCode(r & 63 | 128) } else { t += String.fromCharCode(r >> 12 | 224); t += String.fromCharCode(r >> 6 & 63 | 128); t += String.fromCharCode(r & 63 | 128) } } return t }, _utf8_decode: function (e) { var t = ""; var n = 0; var r = c1 = c2 = 0; while (n < e.length) { r = e.charCodeAt(n); if (r < 128) { t += String.fromCharCode(r); n++ } else if (r > 191 && r < 224) { c2 = e.charCodeAt(n + 1); t += String.fromCharCode((r & 31) << 6 | c2 & 63); n += 2 } else { c2 = e.charCodeAt(n + 1); c3 = e.charCodeAt(n + 2); t += String.fromCharCode((r & 15) << 12 | (c2 & 63) << 6 | c3 & 63); n += 3 } } return t } } return Base64.encode(str); } /** base64Decode @method base64Decode @param {String} @return {String} **/ static Base64Decode(str) { var c1, c2, c3; var Base64 = { _keyStr: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=", encode: function (e) { var t = ""; var n, r, i, s, o, u, a; var f = 0; e = Base64._utf8_encode(e); while (f < e.length) { n = e.charCodeAt(f++); r = e.charCodeAt(f++); i = e.charCodeAt(f++); s = n >> 2; o = (n & 3) << 4 | r >> 4; u = (r & 15) << 2 | i >> 6; a = i & 63; if (isNaN(r)) { u = a = 64 } else if (isNaN(i)) { a = 64 } t = t + this._keyStr.charAt(s) + this._keyStr.charAt(o) + this._keyStr.charAt(u) + this._keyStr.charAt(a) } return t }, decode: function (e) { var t = ""; var n, r, i; var s, o, u, a; var f = 0; e = e.replace(/[^A-Za-z0-9\+\/\=]/g, ""); while (f < e.length) { s = this._keyStr.indexOf(e.charAt(f++)); o = this._keyStr.indexOf(e.charAt(f++)); u = this._keyStr.indexOf(e.charAt(f++)); a = this._keyStr.indexOf(e.charAt(f++)); n = s << 2 | o >> 4; r = (o & 15) << 4 | u >> 2; i = (u & 3) << 6 | a; t = t + String.fromCharCode(n); if (u != 64) { t = t + String.fromCharCode(r) } if (a != 64) { t = t + String.fromCharCode(i) } } t = Base64._utf8_decode(t); return t }, _utf8_encode: function (e) { e = e.replace(/\r\n/g, "\n"); var t = ""; for (var n = 0; n < e.length; n++) { var r = e.charCodeAt(n); if (r < 128) { t += String.fromCharCode(r) } else if (r > 127 && r < 2048) { t += String.fromCharCode(r >> 6 | 192); t += String.fromCharCode(r & 63 | 128) } else { t += String.fromCharCode(r >> 12 | 224); t += String.fromCharCode(r >> 6 & 63 | 128); t += String.fromCharCode(r & 63 | 128) } } return t }, _utf8_decode: function (e) { var t = ""; var n = 0; var r = c1 = c2 = 0; while (n < e.length) { r = e.charCodeAt(n); if (r < 128) { t += String.fromCharCode(r); n++ } else if (r > 191 && r < 224) { c2 = e.charCodeAt(n + 1); t += String.fromCharCode((r & 31) << 6 | c2 & 63); n += 2 } else { c2 = e.charCodeAt(n + 1); c3 = e.charCodeAt(n + 2); t += String.fromCharCode((r & 15) << 12 | (c2 & 63) << 6 | c3 & 63); n += 3 } } return t } } return Base64.decode(str); } /** Simplify a string to basic character set @method cleanChar @param {String} value @return {String} cleaned string **/ static CleanChar(value) { if (value == null) value = ''; if ($.isNumeric(value)) return value; value = value.replace(/,/g, ' '); value = value.replace(/\\}/g, ' '); value = value.replace(/{/g, ' '); value = value.replace(/"/g, ' '); value = value.replace(/'/g, ' '); value = value.replace(/&/g, 'and'); value = value.replace(/>/g, ' '); value = value.replace(//ig, ")"); } case 3: { i_string = i_string.replace(/&/ig, "and"); } case 4: { i_string = i_string.replace(/"/ig, "`"); i_string = i_string.replace(/'/ig, "`"); } } return i_string; } static IsNumber(value) { if (_.isNaN(Number(value))) return false; return true; } static CleanCharForXml(value: any): any { var clean = function (value: string) { if (_.isUndefined(value)) return ''; if (_.isNull(value)) return ''; if (_.isNumber(value)) return value; if (_.isBoolean(value)) return value; value = value.replace(/\}/g, ' '); value = value.replace(/%/g, ' '); value = value.replace(/{/g, ' '); value = value.replace(/"/g, '`'); value = value.replace(/'/g, '`'); value = value.replace(/&/g, 'and'); value = value.replace(/>/g, ' '); value = value.replace(/ { // currently we don't support / clean arrays if (_.isArray(value[k])) return value[k] = v; value[k] = clean(v); }); return value; } static UnionList(a: List, b: List) { return a.toSet().union(b.toSet()).toList(); } static ProcessHourStartEnd(value: string, key: string): any { if (_.isUndefined(!value)) return ''; if (key == 'hourStart') return `${value}:00`; return `${value}:59`; } /** * CheckFoundIndex will check if a return value is -1 and error out if in dev mode (list.findIndex or indexOf for example) * @param i_value * @param i_message * @returns {number} * @constructor */ static CheckFoundIndex(i_value: number, i_message: string = 'CheckFoundIndex did not find index'): number { if (i_value === -1) { console.log(i_message); if (Lib.DevMode()) { alert(i_message); throw Error(i_message); } } return i_value; } // static GetCompSelector(i_constructor) { // return 'need to fix 2'; // if (!Lib.DevMode()) // return; // var annotations = Reflect.getMetadata('annotations', i_constructor); // var componentMetadata = annotations.find(annotation => { // return (annotation instanceof Component); // }); // return componentMetadata.selector; // } static BootboxHide(i_time = 1500) { setTimeout(() => { bootbox.hideAll(); }, i_time) } static DateToAbsolute(year, month) { return year * 12 + month; } static DateFromAbsolute(value: number) { var year = Math.floor(value / 12); var month = value % 12 + 1; return { year, month } } static MapOfIndex(map: Map, index: number, position: "first" | "last"): string { var mapJs = map.toJS(); var mapJsPairs = _.toPairs(mapJs); var offset = position == 'first' ? 0 : 1; if (mapJsPairs[index] == undefined) return "0" return mapJsPairs[index][offset]; } /** * PrivilegesXmlTemplate will generate a template for priveleges in 2 possible modes * * mode 1: just a raw template (we will ignore the values set) and this is the mode when * no selPrivName and appStore params are given * * mode 2: is when we actually serialize data to save to server and in this mode we do pass * in the selPrivName and appStore which we use to retrieve current values from user appStore * and generate the final XML to save to server * * @param selPrivName * @param appStore * @param callBack * @constructor */ static Base64() { var _PADCHAR = "=", _ALPHA = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", _VERSION = "1.0"; function _getbyte64(s, i) { // This is oddly fast, except on Chrome/V8. // Minimal or no improvement in performance by using a // object with properties mapping chars to value (eg. 'A': 0) var idx = _ALPHA.indexOf(s.charAt(i)); if (idx === -1) { throw "Cannot decode base64"; } return idx; } function _decode(s) { var pads = 0, i, b10, imax = s.length, x = []; s = String(s); if (imax === 0) { return s; } if (imax % 4 !== 0) { throw "Cannot decode base64"; } if (s.charAt(imax - 1) === _PADCHAR) { pads = 1; if (s.charAt(imax - 2) === _PADCHAR) { pads = 2; } // either way, we want to ignore this last block imax -= 4; } for (i = 0; i < imax; i += 4) { b10 = ( _getbyte64(s, i) << 18 ) | ( _getbyte64(s, i + 1) << 12 ) | ( _getbyte64(s, i + 2) << 6 ) | _getbyte64(s, i + 3); x.push(String.fromCharCode(b10 >> 16, ( b10 >> 8 ) & 0xff, b10 & 0xff)); } switch (pads) { case 1: b10 = ( _getbyte64(s, i) << 18 ) | ( _getbyte64(s, i + 1) << 12 ) | ( _getbyte64(s, i + 2) << 6 ); x.push(String.fromCharCode(b10 >> 16, ( b10 >> 8 ) & 0xff)); break; case 2: b10 = ( _getbyte64(s, i) << 18) | ( _getbyte64(s, i + 1) << 12 ); x.push(String.fromCharCode(b10 >> 16)); break; } return x.join(""); } function _getbyte(s, i) { var x = s.charCodeAt(i); if (x > 255) { throw "INVALID_CHARACTER_ERR: DOM Exception 5"; } return x; } function _encode(s) { if (arguments.length !== 1) { throw "SyntaxError: exactly one argument required"; } s = String(s); var i, b10, x = [], imax = s.length - s.length % 3; if (s.length === 0) { return s; } for (i = 0; i < imax; i += 3) { b10 = ( _getbyte(s, i) << 16 ) | ( _getbyte(s, i + 1) << 8 ) | _getbyte(s, i + 2); x.push(_ALPHA.charAt(b10 >> 18)); x.push(_ALPHA.charAt(( b10 >> 12 ) & 0x3F)); x.push(_ALPHA.charAt(( b10 >> 6 ) & 0x3f)); x.push(_ALPHA.charAt(b10 & 0x3f)); } switch (s.length - imax) { case 1: b10 = _getbyte(s, i) << 16; x.push(_ALPHA.charAt(b10 >> 18) + _ALPHA.charAt(( b10 >> 12 ) & 0x3F) + _PADCHAR + _PADCHAR); break; case 2: b10 = ( _getbyte(s, i) << 16 ) | ( _getbyte(s, i + 1) << 8 ); x.push(_ALPHA.charAt(b10 >> 18) + _ALPHA.charAt(( b10 >> 12 ) & 0x3F) + _ALPHA.charAt(( b10 >> 6 ) & 0x3f) + _PADCHAR); break; } return x.join(""); } return { decode: _decode, encode: _encode, VERSION: _VERSION }; } // static LoadComponentAsync(name: string, path: string) { // // return System.import(path).then(c => c[name]); // // //return System.import('/dist/public/out.js') // // .catch(function (e) { // // alert('prob loading out.js ' + e); // // }).then(function (e) { // // alert(e); // // alert(e[name]); // // alert(JSON.stringify(e)); // // return System.import('App1').then(c => c[name]); // // }); // } static ConstructImmutableFromTable(path): Array { var arr = []; path.forEach((member) => { var obj = {}; obj[member._attr.name] = { table: {} } for (var k in member._attr) { var value = member._attr[k] obj[member._attr.name][k] = value; for (var t in member.Tables["0"]._attr) { var value = member.Tables["0"]._attr[t] obj[member._attr.name]['table'][t] = value; } } arr.push(Immutable.fromJS(obj)); }); return arr; } static ComputeMask(accessMask): number { var bits = [1, 2, 4, 8, 16, 32, 64, 128]; var computedAccessMask = 0; accessMask.forEach(value => { var bit = bits.shift(); if (value) computedAccessMask = computedAccessMask + bit; }) return computedAccessMask; } static ValidateEmail(email) { var re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; return re.test(email); } static GetAccessMask(accessMask): List { var checks = List(); var bits = [1, 2, 4, 8, 16, 32, 64, 128]; for (var i = 0; i < bits.length; i++) { let checked = (bits[i] & accessMask) > 0 ? true : false; checks = checks.push(checked) } return checks; } static GetADaysMask(accessMask): List { var checks = List(); var bits = [1, 2, 4, 8, 16, 32, 64]; for (var i = 0; i < bits.length; i++) { let checked = (bits[i] & accessMask) > 0 ? true : false; checks = checks.push(checked) } return checks; } static log(msg) { console.log(new Date().toTimeString().replace(/.*(\d{2}:\d{2}:\d{2}).*/, "$1") + ': ' + msg); } static guid(): string { function s4() { return Math.floor((1 + Math.random()) * 0x10000) .toString(16) .substring(1); } return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4(); } /** Smart convert color (many) to decinal @method colorToDecimal @param {String} color @return {Number} decimal **/ static ColorToDecimal(color) { if (color.match('rgb')) { color = this.RgbToHex(color); return this.HexToDecimal(color) } return this.HexToDecimal(color); } static ColorToHex(color) { if (color.match('#')) { return color; } if (color.match('rgb')) { return '#' + this.RgbToHex(color); } return '#' + color; } /** Hex to decimal converter @method hexToDecimal @param {String} h @return {Number} decimal **/ static HexToDecimal(h) { function hexfix(str) { var v, w; v = parseInt(str, 16); // in rrggbb if (str.length == 3) { // nybble colors - fix to hex colors // 0x00000rgb -> 0x000r0g0b // 0x000r0g0b | 0x00r0g0b0 -> 0x00rrggbb w = ((v & 0xF00) << 8) | ((v & 0x0F0) << 4) | (v & 0x00F); v = w | (w << 4); } return v.toString(16).toUpperCase(); } var h = h.replace(/#/gi, ''); h = hexfix(h); return parseInt(h, 16); } /** RGB color to hex converter @method rgbToHex @param {Number} rgb @return {String} hex **/ static RgbToHex(rgb) { function componentFromStr(numStr, percent) { var num = Math.max(0, parseInt(numStr, 10)); return percent ? Math.floor(255 * Math.min(100, num) / 100) : Math.min(255, num); } var rgbRegex = /^rgb\(\s*(-?\d+)(%?)\s*,\s*(-?\d+)(%?)\s*,\s*(-?\d+)(%?)\s*\)$/; var result, r, g, b, hex = ""; if ((result = rgbRegex.exec(rgb))) { r = componentFromStr(result[1], result[2]); g = componentFromStr(result[3], result[4]); b = componentFromStr(result[5], result[6]); hex = (0x1000000 + (r << 16) + (g << 8) + b).toString(16).slice(1); } return hex; } /** Decimal to hex converter @method decimalToHex @param {Number} d @return {String} hex **/ static DecimalToHex(d) { var hex = Number(d).toString(16); hex = "000000".substr(0, 6 - hex.length) + hex; return hex; } static ReduxLoggerMiddleware = store => next => action => { // console.log("dispatching", action.type); let result = next(action); //console.log("next state", store.getState()); return result }; /** this.ngmslibService.inDevMode() uses url localhost (window.location.href.indexOf('localhost') > -1) while Lib.DevMode uses environment var */ static DevMode(): boolean { if (environment.production) { return false; } return true; } static GetSamples(): Object { return { 1019: 'Sushi Restaurant,pro', 1029: 'food menu board,pro', 1007: 'Home and Garden,pro', 1009: 'Hotel Lobby,pro', 1016: 'Coffee Shop,pro', 1011: 'Hobby Shop,pro', 1013: 'Sports Bar,pro', 1014: 'Museum,pro', 1017: 'Bank,pro', 1018: 'Gas Station,pro', 1020: 'Casino,pro', 1000: 'Travel,pro', 1021: 'Bicycle Shop,pro', 1022: 'Tanning Salon,pro', 1023: 'Pharmacy,pro', 1024: 'Laser Away,pro', 1025: 'Dentistry,pro', 1026: 'Clothing store,pro', 1027: 'Golf club,pro', 1028: 'RC Heli,pro', 1030: 'seven eleven,pro', 1031: 'Subway,pro', 1032: 'Super market,pro', 1033: 'Investment Group,pro', 1035: 'Synagogue,pro', 1036: 'Dry Cleaning,pro', 1037: 'Ice Cream Shop,pro', 1038: 'Real Estate office,pro', 1039: 'Night Club,pro', 1040: 'Hockey,pro', 1041: 'Train Station,pro', 1042: 'Realtor,pro', 1043: 'Toy Store,pro', 1044: 'Indian Restaurant,pro', 1045: 'Library,pro', 1046: 'Movie Theater,pro', 1047: 'Airport,pro', 1048: 'LAX,pro', 100310: 'Motel,pro', 100301: 'Parks and Recreations,pro', 100322: 'Corner Bakery,pro', 100331: 'Retirement home,pro', 100368: 'Navy recruiting office,pro', 100397: 'Martial arts school,pro', 100414: 'Supercuts,pro', 100432: 'The UPS Store,pro', 100438: 'Cruise One,pro', 100483: 'Car service,pro', 100503: 'fedex kinkos,pro', 100510: 'veterinarian,pro', 100556: 'YMCA,pro', 100574: 'Tax services,pro', 100589: 'Wedding planner,pro', 100590: 'Cleaning services,pro', 100620: 'Pet Training,pro', 100661: 'Gymboree Kids,pro', 100677: 'Trader Joes,pro', 100695: 'Men Haircuts,pro', 100722: 'Jiffy Lube,pro', 100738: 'Toyota car dealer,pro', 100747: 'Winery,pro', 100771: 'Savings and Loans,pro', 100805: 'Nail Salon,pro', 100822: 'Weight Watchers,pro', 100899: 'Dollar Tree,pro', 100938: 'Western Bagles,pro', 100959: 'Kaiser Permanente,pro', 300143: 'Funeral home,pro', 205734: 'Church,pro', 220354: 'College,pro', 206782: 'Dr Waiting Room,pro', 300769: 'NFL Stadium,pro', 301814: 'University Campus,pro', 303038: 'Day care,pro', 304430: 'GameStop,pro', 307713: 'Del Taco,pro', 305333: 'General Hospital,pro', 305206: 'Starbucks,pro', 308283: 'training and fitness,pro', 311519: 'High school hall,pro', 309365: 'Winery,pro', 310879: 'Law Firm,pro', 1001: 'Health Club,pro', 1002: 'Gym,pro', 1003: 'Flower Shop,pro', 1004: 'Car Dealership,pro', 1012: 'Pet Shop,pro', 1005: 'Hair Salon,pro', 1209: 'Motorcycle shop,lite', 1210: 'Sushi and Grill,lite', 1211: 'the Coffee Shop,lite', 1212: 'Pizzeria,lite', 1213: 'Music Store,lite', 1214: 'Diner,lite', 1215: 'the Hair Salon,lite', 1216: 'Dentist,lite', 1203: 'Jewelry,lite', 1217: 'Crossfit,lite', 1218: 'Copy and Print shop,lite', 1219: 'Antique Store,lite', 1220: 'Clock Repair Store,lite', 1221: 'Eastern Cuisine,lite', 1222: 'the Toy Store,lite', 1223: 'Pet Store Grooming,lite', 1224: 'the Veterinarian,lite', 1225: 'Tattoo Parlor,lite', 1226: 'Camera Store,lite', 1228: 'Bike shop,lite', 1229: 'Gun Shop,lite', 1230: 'Chiropractic Clinic,lite', 1231: 'French Restaurant,lite', 1233: 'Winery,lite', 1232: 'Mexican Taqueria,lite', 1234: 'Bistro Restaurant,lite', 1235: 'Vitamin Shop,lite', 1227: 'Tailor Shop,lite', 1236: 'Computer Repair,lite', 1237: 'Car Detail,lite', 1238: 'Asian Restaurants,lite', 1239: 'Marijuana Dispensary,lite', 1240: 'the Church,lite', 1241: 'Synagogue,lite', 1242: 'Frozen Yogurt Store,lite', 1244: 'Baby Day Care,lite', 1052: 'Car wash,lite', 1053: 'Smoke shop,lite', 1054: 'Yoga place,lite', 1055: 'Laundromat,lite', 1056: 'Baby clothes,lite', 1057: 'Travel agency,lite', 1058: 'Real Estate agent,lite' } } static Xml2Json() { //https://github.com/metatribal/xmlToJSON var xmlToJSON = (function () { this.version = "1.3"; var options = { // set up the default options mergeCDATA: true, // extract cdata and merge with text grokAttr: true, // convert truthy attributes to boolean, etc grokText: true, // convert truthy text/attr to boolean, etc normalize: true, // collapse multiple spaces to single space xmlns: true, // include namespaces as attribute in output namespaceKey: '_ns', // tag name for namespace objects textKey: '_text', // tag name for text nodes valueKey: '_value', // tag name for attribute values attrKey: '_attr', // tag for attr groups cdataKey: '_cdata', // tag for cdata nodes (ignored if mergeCDATA is true) attrsAsObject: true, // if false, key is used as prefix to name, set prefix to '' to merge children and attrs. stripAttrPrefix: true, // remove namespace prefixes from attributes stripElemPrefix: true, // for elements of same name in diff namespaces, you can enable namespaces and access the nskey property childrenAsArray: true // force children into arrays }; var prefixMatch: any = new RegExp('(?!xmlns)^.*:/'); var trimMatch: any = new RegExp('^\s+|\s+$g'); this.grokType = function (sValue) { if (/^\s*$/.test(sValue)) { return null; } if (/^(?:true|false)$/i.test(sValue)) { return sValue.toLowerCase() === "true"; } if (isFinite(sValue)) { return parseFloat(sValue); } return sValue; }; this.parseString = function (xmlString, opt) { return this.parseXML(this.stringToXML(xmlString), opt); } this.parseXML = function (oXMLParent, opt) { // initialize options for (var key in opt) { options[key] = opt[key]; } var vResult = {}, nLength = 0, sCollectedTxt = ""; // parse namespace information if (options.xmlns && oXMLParent.namespaceURI) { vResult[options.namespaceKey] = oXMLParent.namespaceURI; } // parse attributes // using attributes property instead of hasAttributes method to support older browsers if (oXMLParent.attributes && oXMLParent.attributes.length > 0) { var vAttribs = {}; for (nLength; nLength < oXMLParent.attributes.length; nLength++) { var oAttrib = oXMLParent.attributes.item(nLength); vContent = {}; var attribName = ''; if (options.stripAttrPrefix) { attribName = oAttrib.name.replace(prefixMatch, ''); } else { attribName = oAttrib.name; } if (options.grokAttr) { vContent[options.valueKey] = this.grokType(oAttrib.value.replace(trimMatch, '')); } else { vContent[options.valueKey] = oAttrib.value.replace(trimMatch, ''); } if (options.xmlns && oAttrib.namespaceURI) { vContent[options.namespaceKey] = oAttrib.namespaceURI; } if (options.attrsAsObject) { // attributes with same local name must enable prefixes vAttribs[attribName] = vContent; } else { vResult[options.attrKey + attribName] = vContent; } } if (options.attrsAsObject) { vResult[options.attrKey] = vAttribs; } else { } } // iterate over the children if (oXMLParent.hasChildNodes()) { for (var oNode, sProp, vContent, nItem = 0; nItem < oXMLParent.childNodes.length; nItem++) { oNode = oXMLParent.childNodes.item(nItem); if (oNode.nodeType === 4) { if (options.mergeCDATA) { sCollectedTxt += oNode.nodeValue; } else { if (vResult.hasOwnProperty(options.cdataKey)) { if (vResult[options.cdataKey].constructor !== Array) { vResult[options.cdataKey] = [vResult[options.cdataKey]]; } vResult[options.cdataKey].push(oNode.nodeValue); } else { if (options.childrenAsArray) { vResult[options.cdataKey] = []; vResult[options.cdataKey].push(oNode.nodeValue); } else { vResult[options.cdataKey] = oNode.nodeValue; } } } } /* nodeType is "CDATASection" (4) */ else if (oNode.nodeType === 3) { sCollectedTxt += oNode.nodeValue; } /* nodeType is "Text" (3) */ else if (oNode.nodeType === 1) { /* nodeType is "Element" (1) */ if (nLength === 0) { vResult = {}; } // using nodeName to support browser (IE) implementation with no 'localName' property if (options.stripElemPrefix) { sProp = oNode.nodeName.replace(prefixMatch, ''); } else { sProp = oNode.nodeName; } vContent = xmlToJSON.parseXML(oNode); if (vResult.hasOwnProperty(sProp)) { if (vResult[sProp].constructor !== Array) { vResult[sProp] = [vResult[sProp]]; } vResult[sProp].push(vContent); } else { if (options.childrenAsArray) { vResult[sProp] = []; vResult[sProp].push(vContent); } else { vResult[sProp] = vContent; } nLength++; } } } } else if (!sCollectedTxt) { // no children and no text, return null if (options.childrenAsArray) { vResult[options.textKey] = []; vResult[options.textKey].push(null); } else { vResult[options.textKey] = null; } } if (sCollectedTxt) { if (options.grokText) { var value = this.grokType(sCollectedTxt.replace(trimMatch, '')); if (value !== null && value !== undefined) { vResult[options.textKey] = value; } } else if (options.normalize) { vResult[options.textKey] = sCollectedTxt.replace(trimMatch, '').replace(/\s+/g, " "); } else { vResult[options.textKey] = sCollectedTxt.replace(trimMatch, ''); } } return vResult; } // Convert xmlDocument to a string // Returns null on failure this.xmlToString = function (xmlDoc) { try { var xmlString = xmlDoc.xml ? xmlDoc.xml : (new XMLSerializer()).serializeToString(xmlDoc); return xmlString; } catch (err) { console.log('error ' + err); return null; } } // Convert a string to XML Node Structure // Returns null on failure this.stringToXML = function (xmlString) { try { var xmlDoc = null; if (window['DOMParser']) { var parser = new DOMParser(); xmlDoc = parser.parseFromString(xmlString, "text/xml"); return xmlDoc; } else { xmlDoc = new ActiveXObject("Microsoft.XMLDOM"); xmlDoc.async = false; xmlDoc.loadXML(xmlString); return xmlDoc; } } catch (e) { console.log('error stringToXML ' + e); return null; } } return this; }).call({}); return xmlToJSON; } } /* tslint:disable */ // polyfill for Object.assign (not part of TS yet) // https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/assign if (!Object.assign) { Object.defineProperty(Object, "assign", { enumerable: false, configurable: true, writable: true, value: function (target) { "use strict"; if (target === undefined || target === null) { throw new TypeError("Cannot convert first argument to object"); } var to = Object(target); for (var i = 1; i < arguments.length; i++) { var nextSource = arguments[i]; if (nextSource === undefined || nextSource === null) { continue; } nextSource = Object(nextSource); var keysArray = Object.keys(nextSource); for (var nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex++) { var nextKey = keysArray[nextIndex]; var desc = Object.getOwnPropertyDescriptor(nextSource, nextKey); if (desc !== undefined && desc.enumerable) { to[nextKey] = nextSource[nextKey]; } } } return to; } }); } // window['StringJS'] = ss.default; // MyS.prototype = StringJS('') // MyS.prototype.constructor = MyS; // function MyS(val) { // this.setValue(val); // } // // var formatMoney = function(n, c, d, t){ // var c = isNaN(c = Math.abs(c)) ? 2 : c, // d = d == undefined ? "." : d, // t = t == undefined ? "," : t, // s = n < 0 ? "-" : "", // i:any = String(parseInt(n = Math.abs(Number(n) || 0).toFixed(c))), // j = (j = i.length) > 3 ? j % 3 : 0; // return s + (j ? i.substr(0, j) + t : "") + i.substr(j).replace(/(\d{3})(?=\d)/g, "$1" + t) + (c ? d + Math.abs(n - i).toFixed(c).slice(2) : ""); // }; // // MyS.prototype.isBlank = function () { // var value = this.s; // if (_.isNaN(value)) // return true; // if (_.isUndefined(value)) // return true; // if (_.isNull(value)) // return true; // if (_.isEmpty(String(value))) // return true; // return false; // } // // MyS.prototype.isNotBlank = function () { // var value = this.s; // if (_.isNaN(value)) // return false; // if (_.isUndefined(value)) // return false; // if (_.isNull(value)) // return false; // if (_.isEmpty(String(value))) // return false; // return true; // } // // /** // * booleanToNumber // * convert boolean to a number 0 or 1 // * if forceCast is true, it will always return a number, else it will alow strings to pass through it // * @param forceCast // * @returns {any} // */ // MyS.prototype.booleanToNumber = function (forceCasting: boolean = false) { // var value = this.s; // if (value == '') // return 0; // if (_.isUndefined(value) || _.isNull(value) || value == 'NaN' || value == 'null' || value == 'NULL') // return 0; // if (value === "0" || value === 'false' || value === "False" || value === false) // return 0; // if (value === 1 || value === "true" || value === "True" || value === true) // return 1; // if (forceCasting) { // return parseInt(value); // } else { // return value; // } // } // // MyS.prototype.toCurrency = function (format?: 'us'|'eu') { // // var value = StringJS(this.s).toFloat(2); // if (_.isNaN(value)) // value = 0; // switch (format) { // case 'eu': { // return '€' + formatMoney(value, 2, '.', ','); // } // case 'us': {} // default: { // return '$' + formatMoney(value, 2, '.', ','); // } // } // } // // MyS.prototype.toPercent = function () { // return StringJS(this.s).toFloat(2) + '%'; // } // // MyS.prototype.fileTailName = function (i_level) { // var fileName = this.s; // var arr = fileName.split('/'); // var size = arr.length; // var c = arr.slice(0 - i_level, size) // return new this.constructor(c.join('/')); // } // // MyS.prototype.cleanChar = function () { // var value = this.s; // if (_.isUndefined(value)) // return ''; // if (_.isNull(value)) // return ''; // if (_.isNumber(value)) // return value; // if (_.isBoolean(value)) // return value; // value = value.replace(/\}/g, ' '); // value = value.replace(/%/g, ' '); // value = value.replace(/{/g, ' '); // value = value.replace(/"/g, '`'); // value = value.replace(/'/g, '`'); // value = value.replace(/&/g, 'and'); // value = value.replace(/>/g, ' '); // value = value.replace(/
loading...

have a nice day

================================================ FILE: src/app/app-component.ts ================================================ import {AfterViewInit, Component, VERSION, ViewChild, ViewContainerRef} from "@angular/core"; import "rxjs/add/operator/catch"; import {ActivatedRoute, NavigationEnd, Router} from "@angular/router"; import {CommBroker} from "../services/CommBroker"; import {EventManager, Title} from "@angular/platform-browser"; import {ToastsManager} from "ng2-toastr"; import {Observable} from "rxjs"; import * as packageJson from "../../package.json"; import {AuthService} from "../services/AuthService"; import {LocalStorage} from "../services/LocalStorage"; import {YellowPepperService} from "../services/yellowpepper.service"; import {RedPepperService} from "../services/redpepper.service"; import {IUiState} from "../store/store.data"; import {ACTION_LIVELOG_UPDATE, ACTION_UISTATE_UPDATE} from "../store/actions/appdb.actions"; import {ModalComponent} from "ng2-bs3-modal/ng2-bs3-modal"; import {Map, List} from 'immutable'; import {Consts} from "../interfaces/Consts"; import {animate, state, style, transition, trigger} from "@angular/animations"; import * as moment from 'moment' import {LiveLogModel} from "../models/live-log-model"; import {LocaleSelector} from "./locale-selector/local-selector"; enum MainAppShowModeEnum { MAIN, SAVE, PREVIEW, LOGOUT } export enum MainAppShowStateEnum { INIT, NORMAL, SAVE, SAVING, SAVE_AND_PREVIEW, SAVED, GOODBYE } @Component({ selector: 'app-root', templateUrl: './app-component.html', animations: [ trigger('logoutState', [ state('active', style({ transform: 'scale(2)', alpha: 0 })), transition('* => active', animate('1000ms ease-out')) ]) ] }) export class AppComponent implements AfterViewInit { version: string; ngVersion: string; offlineDevMode: any = window['offlineDevMode']; m_ShowModeEnum = MainAppShowModeEnum; m_showMode: any = MainAppShowModeEnum.MAIN; m_hidden = false; m_localSelected; // isBrandingDisabled: Observable; syncOnSave = false; m_logoutState = ''; productName = 'Studio-Lite'; isBrandingDisabled: boolean = false; demoModeMsg = `Sorry cannot save while in demo mode, please open a new FREE account to be able to use all features...`; constructor(private router: Router, private localStorage: LocalStorage, private commBroker: CommBroker, private rp: RedPepperService, private authService: AuthService, private yp: YellowPepperService, private activatedRoute: ActivatedRoute, private vRef: ViewContainerRef, private titleService: Title, private eventManager: EventManager, private toastr: ToastsManager) { // this.version = packageJson.version; // this.ngVersion = VERSION.full // this.localStorage.removeItem('remember_me') // this.localStorage.removeItem('business_id') // this.localStorage.removeItem('no_show_limited') this.checkPlatform(); this.listenAppStateChange(); this.toastr.setRootViewContainerRef(vRef); this.listenRouterUpdateTitle(); this.listenUpgradeEnterpris(); this.listenSaves(); this.appResized(); Observable.fromEvent(window, 'resize').debounceTime(250) .subscribe(() => { this.appResized(); }, (e) => { console.error(e) }); } @ViewChild('modalProUpgrade') modal: ModalComponent; @ViewChild('modalLocale') modalLocale: ModalComponent; @ViewChild('localSelector') localSelector: LocaleSelector; ngOnInit() { this.yp.isBrandingDisabled() .subscribe((v) => { this.isBrandingDisabled = v; if (!this.isBrandingDisabled) this.productName = this.rp.getUserData().resellerName; }, (e) => console.error(e)); let s = this.router.events .subscribe((val) => { if (val instanceof NavigationEnd) { if (val.url.indexOf('data') > -1 || val.url.indexOf('remoteStatus') > -1) { this.router.navigate(['/FasterqTerminal', val.url.split('?')[1]]); } else { this.authService.start(); } s.unsubscribe(); } }, (e) => console.error(e)); } _onMenuIcon(icon, event) { event.stopImmediatePropagation(); event.preventDefault(); switch (icon) { case 'web': { window.open('http://www.digitalsignage.com', '_blank'); break; } case 'chat': { window.open('http://www.digitalsignage.com/_html/live_chat.html', '_blank'); break; } case 'upgrade': { this.modal.open(); break; } case 'save': { this.saveAndRestartPrompt(() => { }) break; } case 'locale': { this.modalLocale.open(); break; } } } /** Save and serialize configuration to remote mediaSERVER> Save and restart will check if the Stations module has been loaded and if no connected stations are present, it will NOT prompt for option to restart station on save, otherwise it will. @method saveAndRestartPrompt @param {Function} call back after save **/ saveAndRestartPrompt(i_callBack) { bootbox.dialog({ message: 'Restart connected stations and apply your saved work?', title: 'Save work to remote server', buttons: { success: { label: 'OK', className: "btn-success", callback: () => { let uiState: IUiState = {mainAppState: MainAppShowStateEnum.SAVE} this.yp.ngrxStore.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})) this.yp.dispatch(({type: ACTION_LIVELOG_UPDATE, payload: new LiveLogModel({event: 'app saved'})})); } }, danger: { label: 'Save & restart stations', className: "btn-success", callback: () => { // reboot will reboot the PC or exits presentation android // pepper.sendCommand('rebootStation', -1, function () {}); // reboot player exits player // pepper.sendCommand('rebootPlayer', -1, function () { // sync and restart does a fast / soft restart of player this.syncOnSave = true; let uiState: IUiState = {mainAppState: MainAppShowStateEnum.SAVE} this.yp.ngrxStore.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})) this.yp.dispatch(({type: ACTION_LIVELOG_UPDATE, payload: new LiveLogModel({event: 'app saved and restarting all stations'})})); } }, main: { label: 'Cancel', callback: () => { return; } } } }); } ngAfterViewInit() { let uiState: IUiState = {mainAppState: MainAppShowStateEnum.NORMAL} this.yp.ngrxStore.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})) } _onLocaleChanged(i_localSelected) { this.m_localSelected = i_localSelected; this.yp.ngrxStore.dispatch(({type: ACTION_UISTATE_UPDATE, payload: {mainAppState: MainAppShowStateEnum.SAVE}})); this.modalLocale.close(); } private listenAppStateChange() { this.yp.listenMainAppState() .subscribe((i_value: MainAppShowStateEnum) => { switch (i_value) { case MainAppShowStateEnum.SAVE_AND_PREVIEW: { if (this.rp.getUserData().businessID == 459848) { this.viewMode(MainAppShowModeEnum.MAIN); const uiState: IUiState = {mainAppState: MainAppShowStateEnum.NORMAL}; this.yp.ngrxStore.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})); return bootbox.alert(this.demoModeMsg); } this.save(() => { this.yp.dispatch(({type: ACTION_LIVELOG_UPDATE, payload: new LiveLogModel({event: 'loading preview'})})); this.viewMode(MainAppShowModeEnum.PREVIEW); }); break; } case MainAppShowStateEnum.NORMAL: { this.viewMode(MainAppShowModeEnum.MAIN); break; } case MainAppShowStateEnum.SAVED: { con('Saved to server'); if (this.syncOnSave) this.rp.sendCommand('syncAndStart', -1, () => { }); this.syncOnSave = false; const uiState: IUiState = {appSaved: moment().format('h:mm:ss')}; this.yp.ngrxStore.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})); if (this.m_localSelected) this.localSelector.redirect(this.m_localSelected) break; } case MainAppShowStateEnum.GOODBYE: { con('Goodbye'); this.m_logoutState = 'active' this.viewMode(MainAppShowModeEnum.LOGOUT); break; } case MainAppShowStateEnum.SAVE: { if (this.rp.getUserData().businessID == 459848) { this.viewMode(MainAppShowModeEnum.MAIN); const uiState: IUiState = {mainAppState: MainAppShowStateEnum.NORMAL}; this.yp.ngrxStore.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})); return bootbox.alert(this.demoModeMsg); } this.save(() => { this.viewMode(MainAppShowModeEnum.MAIN); const uiState: IUiState = {mainAppState: MainAppShowStateEnum.NORMAL}; this.yp.ngrxStore.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})); }); break; } } }, (e) => console.error(e)) } private save(i_cb: () => void) { this.viewMode(MainAppShowModeEnum.SAVE); let uiState: IUiState = {mainAppState: MainAppShowStateEnum.SAVING} this.yp.ngrxStore.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})) this.rp.save((result) => { if (result.status == true) { this.rp.reduxCommit(null, true) let uiState: IUiState = {mainAppState: MainAppShowStateEnum.SAVED} this.yp.ngrxStore.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})) i_cb(); } else { alert('error ' + JSON.stringify(result)); } }) } private viewMode(i_mode: MainAppShowModeEnum) { this.m_showMode = i_mode; switch (i_mode) { case MainAppShowModeEnum.MAIN: { this.m_hidden = false; break; } case MainAppShowModeEnum.PREVIEW: { this.m_hidden = true; break; } case MainAppShowModeEnum.SAVE: { this.m_hidden = true; break; } case MainAppShowModeEnum.LOGOUT: { this.m_hidden = true; break; } } } private checkPlatform() { switch (platform.name.toLowerCase()) { case 'microsoft edge': { // alert(`${platform.name} browser not supported at this time, please use Google Chrome`); break; } case 'chrome': { break; } default: { // alert('for best performance please use Google Chrome'); break; } } } private listenUpgradeEnterpris() { this.commBroker.onEvent(Consts.Events().UPGRADE_ENTERPRISE) .subscribe((v) => { this.modal.open(); }, (e) => console.error(e)); } private listenSaves() { this.eventManager.addGlobalEventListener('window', 'keydown.control.s', (event) => { event.preventDefault(); this.yp.ngrxStore.dispatch(({type: ACTION_UISTATE_UPDATE, payload: {mainAppState: MainAppShowStateEnum.SAVE}})); }) } public appResized(): void { var appHeight = document.body.clientHeight; var appWidth = document.body.clientWidth; var uiState: IUiState = {appSized: Map({width: appWidth, height: appHeight})} this.yp.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})) this.commBroker.setValue(Consts.Values().APP_SIZE, { height: appHeight, width: appWidth }); this.commBroker.fire({ fromInstance: self, event: Consts.Events().WIN_SIZED, context: '', message: { height: appHeight, width: appWidth } }) } private listenRouterUpdateTitle() { this.router.events .filter(event => event instanceof NavigationEnd) .map(() => this.activatedRoute) .map(route => { while (route.firstChild) { route = route.firstChild } return route; }).filter(route => route.outlet === 'primary') .mergeMap(route => route.data) .subscribe((event) => { this.titleService.setTitle(event['title']) }, (e) => console.error(e)); } } ================================================ FILE: src/app/app-module.ts ================================================ import {BrowserModule} from "@angular/platform-browser"; import {Compiler, NgModule, ErrorHandler} from "@angular/core"; import {FormsModule, ReactiveFormsModule} from "@angular/forms"; import {HttpModule, JsonpModule} from "@angular/http"; import {Ng2Bs3ModalModule} from "ng2-bs3-modal/ng2-bs3-modal"; import {AppComponent} from "./app-component"; import {LocalStorage} from "../services/LocalStorage"; import {RedPepperService} from "../services/redpepper.service"; import {YellowPepperService} from "../services/yellowpepper.service"; import {MsLibModule} from "ng-mslib/dist/mslib.module"; import {ToastModule, ToastOptions} from "ng2-toastr"; import {AccordionModule, AlertModule, ModalModule} from "ngx-bootstrap"; import {DropdownModule, DropdownModule as DropdownModulePrime, InputTextModule, SelectButtonModule, TreeModule} from "primeng/primeng"; import {routing} from "../app-routes"; import {LoginPanel} from "../comps/entry/LoginPanel"; import {Logout} from "../comps/logout/Logout"; import {AgmCoreModule} from "angular2-google-maps/core"; import {ImgLoader} from "../comps/imgloader/ImgLoader"; import {ChartModule} from "angular2-highcharts"; import {CommBroker} from "../services/CommBroker"; import {AUTH_PROVIDERS} from "../services/AuthService"; import {StoreService} from "../services/StoreService"; import {NgMenu} from "../comps/ng-menu/ng-menu"; import {NgMenuItem} from "../comps/ng-menu/ng-menu-item"; import {AutoLogin} from "../comps/entry/AutoLogin"; import {StoreModule} from "@ngrx/store"; import {INITIAL_APPLICATION_STATE} from "../store/application.state"; import {EffectsModule} from "@ngrx/effects"; import {StoreDevtoolsModule} from "@ngrx/store-devtools"; import {ACTION_LIVELOG_UPDATE, AppdbAction} from "../store/actions/appdb.actions"; import {AppDbEffects} from "../store/effects/appdb.effects"; import {MsdbEffects} from "../store/effects/msdb.effects"; import {environment} from "../environments/environment"; import {productionReducer} from "../store/store.data"; import {NgmslibService} from "ng-mslib"; import {SharedModule} from "../modules/shared.module"; import {Dashboard} from "./dashboard/dashboard-navigation"; import {Appwrap} from "./appwrap"; import "hammerjs"; import "print-js"; import "gsap"; import "gsap/CSSPlugin"; import "gsap/Draggable"; import "gsap/TweenLite"; import "gsap/ScrollToPlugin"; import {Lib} from "../Lib"; import {FontLoaderService} from "../services/font-loader-service"; import {SimpleGridModule} from "../comps/simple-grid-module/SimpleGridModule"; import {GlobalErrorHandler} from "../services/global-error-handler"; import {BrowserAnimationsModule} from "@angular/platform-browser/animations"; import {FasterqTerminal} from "./fasterq/fasterq-terminal"; import {WizardService} from "../services/wizard-service"; import {ResellerLogo} from "../comps/logo/reseller-logo"; import {DashPanel} from "./dashboard/dash-panel"; import {ServerAvg} from "./dashboard/server-avg"; import {StorageUsed} from "./dashboard/storage-used"; import {LiveLogModel} from "../models/live-log-model"; // import "fabric"; // need to remove if we import via cli // import {ScreenTemplate} from "../comps/screen-template/screen-template"; declare global { interface JQueryStatic { base64: any; knob: any; gradientPicker: any; timepicker: any; contextmenu: any; index: any; } } export class CustomToastOption extends ToastOptions { animate: 'flyRight'; positionClass: 'toast-bottom-right'; toastLife: 10000; showCloseButton: true; maxShown: 5; newestOnTop: true; enableHTML: true; dismiss: 'auto'; messageClass: ""; titleClass: "" } export const providing = [CommBroker, WizardService, AUTH_PROVIDERS, RedPepperService, YellowPepperService, LocalStorage, StoreService, FontLoaderService, AppdbAction, { provide: "OFFLINE_ENV", useValue: window['offlineDevMode'] }, { provide: "HYBRID_PRIVATE", useValue: false }, { provide: ErrorHandler, useClass: GlobalErrorHandler }, { provide: ToastOptions, useClass: CustomToastOption } ]; const decelerations = [AppComponent, AutoLogin, LoginPanel, Appwrap, Dashboard, Logout, NgMenu, NgMenuItem, ImgLoader, FasterqTerminal, DashPanel, ServerAvg, StorageUsed]; export function appReducer(state: any = INITIAL_APPLICATION_STATE, action: any) { if (environment.production) { return productionReducer(state, action); } else { return productionReducer(state, action); // return developmentReducer(state, action); } } @NgModule({ declarations: [decelerations], imports: [ BrowserModule, FormsModule, BrowserAnimationsModule, ReactiveFormsModule, Ng2Bs3ModalModule, HttpModule, ChartModule, StoreModule.provideStore(appReducer), EffectsModule.run(AppDbEffects), EffectsModule.run(MsdbEffects), environment.imports, // StoreDevtoolsModule.instrumentStore({maxAge: 2}), // StoreDevtoolsModule.instrumentOnlyWithExtension(), AgmCoreModule.forRoot({ apiKey: 'AIzaSyDKa8Z3QLtACfSfxF-S8A44gm5bkvNTmuM', libraries: ['places'] }), SimpleGridModule.forRoot(), SharedModule.forRoot(), ToastModule.forRoot(), AlertModule.forRoot(), MsLibModule.forRoot({a: 1}), ModalModule.forRoot(), DropdownModule, AccordionModule.forRoot(), JsonpModule, TreeModule, InputTextModule, SelectButtonModule, InputTextModule, DropdownModulePrime, routing, ], providers: [providing], bootstrap: [AppComponent] }) export class AppModule { constructor(private commBroker: CommBroker, private compiler: Compiler, private ngmslibService: NgmslibService, private yp: YellowPepperService, private fontLoaderService: FontLoaderService) { Lib.Con(`running in dev mode: ${Lib.DevMode()}`); Lib.Con(`App in ${(compiler instanceof Compiler) ? 'AOT' : 'JIT'} mode`); window['business_id'] = -1; window['jQueryAny'] = jQuery; window['jXML'] = jQuery; this.ngmslibService.globalizeStringJS(); Lib.Con(StringJS('app-loaded-and-ready').humanize().s); Lib.AlertOnLeave(); this.yp.dispatch(({type: ACTION_LIVELOG_UPDATE, payload: new LiveLogModel({event: 'app started'})})); } } ================================================ FILE: src/app/appwrap.ts ================================================ import {Component} from "@angular/core"; import {Router} from "@angular/router"; import {Compbaser} from "ng-mslib"; import {Observable} from "rxjs/Observable"; import {YellowPepperService} from "../services/yellowpepper.service"; @Component({ template: `
` }) export class Appwrap extends Compbaser { isBrandingDisabled: Observable constructor(private router: Router, private yp:YellowPepperService) { super(); jQuery(".navbar-header .navbar-toggle").trigger("click"); jQuery('.navbar-nav').css({ display: 'block' }); this.isBrandingDisabled = this.yp.isBrandingDisabled() } // public listenMenuChanges() { // var unsub = self.commBroker.onEvent(Consts.Events().MENU_SELECTION).subscribe((e: IMessage) => { // if (!this.routerActive) // return; // let screen = (e.message); // self.router.navigate([`/App1/${screen}`]); // }); // } } ================================================ FILE: src/app/blocks/block-fabric-image.ts ================================================ import {BlockFabric} from "./block-fabric"; import * as _ from "lodash"; import {BlockLabels} from "../../interfaces/Consts"; const blockType = BlockLabels.BLOCKCODE_IMAGE; export class BlockFabricImage extends BlockFabric { m_canvas; m_gridMagneticMode = 0; m_nativeID; m_fileFormat; constructor(options, i_blockService, i_pepper) { super(options, i_blockService, i_pepper, blockType) this.m_blockService = i_blockService; this.m_pepper = i_pepper; this.m_blockType = blockType; _.extend(options, {blockType: this.m_blockType}) this._initResourcesData(); } /** Set the instance resource data from msdb which includes resource_id (handle of a resource) as well as the description of the resource and icon. @method _initResourcesData **/ _initResourcesData() { var domPlayerData = this._getBlockPlayerData(); var xSnippet = $(domPlayerData).find('Resource'); this.m_resourceID = $(xSnippet).attr('hResource'); this.m_nativeID = this.m_pepper.getResourceNativeID(this.m_resourceID); if (_.isNull(this.m_nativeID)) { this._selfDestruct(); return; } this.m_blockName = this.m_pepper.getResourceRecord(this.m_resourceID).resource_name; this.m_blockDescription = this.m_pepper.getResourceName(this.m_resourceID); this.m_fileFormat = this.m_pepper.getResourceType(this.m_resourceID); this.m_blockFontAwesome = this.m_blockService.getFontAwesome(this.m_fileFormat); } /** Convert the block into a fabric js compatible object @Override @method fabricateBlock **/ fabricateBlock(i_canvasScale, i_callback) { var domPlayerData = this._getBlockPlayerData(); var layout = $(domPlayerData).find('Layout'); var businessID = this.m_pepper.getUserData().businessID; var elemID = _.uniqueId('imgElemrand') var imgPath; if (this.m_fileFormat == 'swf') { imgPath = './_assets/flash.png'; } else { /* if (platform.name == 'Chrome') { // CDN imgPath = window.g_protocol + 's3.signage.me/business' + this.m_pepper.getUserData().businessID + '/resources/'; imgPath = 'https://s3.signage.me/business' + this.m_pepper.getUserData().businessID + '/resources/'; imgPath += +this.m_nativeID + '.' + this.m_fileFormat; } else { // Legacy imgPath = window.g_protocol + this.m_pepper.getUserData().domain + '/Resources/business' + this.m_pepper.getUserData().businessID + '/resources/' + this.m_nativeID + '.' + this.m_fileFormat; } */ imgPath = window.g_protocol + this.m_pepper.getUserData().domain + '/Resources/business' + this.m_pepper.getUserData().businessID + '/resources/' + this.m_nativeID + '.' + this.m_fileFormat; // log('loading img from ' + imgPath); } var initImage = (i_image, i_passed) => { if (!i_passed) { i_callback(); return; } $(i_image).width(1000).height(800).appendTo('body'); var options = this._fabricateOptions(parseInt(layout.attr('y')), parseInt(layout.attr('x')), parseInt(layout.attr('width')), parseInt(layout.attr('height')), parseInt(layout.attr('rotation'))); var img = new fabric.Image(i_image, options); _.extend(this, img); this._fabricAlpha(domPlayerData); this._fabricLock(); this['canvasScale'] = i_canvasScale; i_callback(); }; // manage errors of resources which don't load $(``).on('load', function() { initImage(this, true); }).on('error', function() { initImage(this, false); }) } /** Get the resource id of the embedded resource **/ getResourceID() { return this.m_resourceID; } /** Delete this block @method deleteBlock @params {Boolean} i_memoryOnly if true only remove from existance but not from msdb **/ deleteBlock(i_memoryOnly) { // $(Elements.IMAGE_ASPECT_RATIO).off('change', this.m_inputChangeHandler); this._deleteBlock(i_memoryOnly); } } ================================================ FILE: src/app/blocks/block-fabric-josn-item.ts ================================================ import {BlockFabric} from "./block-fabric"; import * as _ from "lodash"; import {Lib} from "../../Lib"; import {BlockLabels} from "../../interfaces/Consts"; import {BlockFabricLabel} from "./block-fabric-label"; const blockType = BlockLabels.BLOCKCODE_JSON_ITEM; export class BlockFabricJsonItem extends BlockFabricLabel { protected m_options; protected m_selected; protected m_labelFontSelector:any; protected m_config:{}; protected m_sceneMime:string; constructor(options, i_blockService, i_pepper) { super(options, i_blockService, i_pepper) this.m_blockService = i_blockService; this.m_pepper = i_pepper; this.m_blockType = blockType; _.extend(options, {blockType: this.m_blockType}) this.m_sceneMime = this.m_pepper.getSceneMime(this.m_sceneID); this.m_config = { 'Json.instagram.feed': { title: 'Instagram', tabTitle: 'Posts', fields: { 1: { name: "title", type: "text", label: "title" }, 2: { name: "urlImage", type: "resource", label: "image" }, 3: { name: "video", type: "resource", label: "video" } } }, 'Json.twitter': { title: 'Twitter', tabTitle: 'Tweets', fields: { 1: { name: "name", type: "text", label: "name" }, 2: { name: "text", type: "text", label: "text" }, 3: { name: "screen_name", type: "text", label: "screen name" }, 4: { name: "created_at", type: "text", label: "created at" }, 5: { name: "profile_background_image_url", type: "resource", label: "Background image" }, 6: { name: "profile_image_url", type: "resource", label: "Image" } } }, 'Json.digg': { title: 'Digg', tabTitle: 'Posts', fields: { 1: { name: "title", type: "text", label: "title" }, 2: { name: "link", type: "resource", label: "image" } } }, 'Json.spreadsheet': { title: 'Spreadsheet', tabTitle: 'Cells', fields: { 1: { name: "$cells.1.1.value", type: "dual_numeric", label: "Sheet cell" } } }, 'Json.calendar': { title: 'Calendar', tabTitle: 'Date', fields: { 1: { name: "summary", type: "text", label: "summary" }, 2: { name: "description", type: "text", label: "description" }, 3: { name: "organizer", type: "text", label: "organizer" }, 4: { name: "organizerEmail", type: "text", label: "organizer email" }, 5: { name: "created", type: "text", label: "created" }, 6: { name: "startDateTime_time", type: "date", label: "start date time" }, 7: { name: "endDateTime_time", type: "date", label: "end date time" }, 8: { name: "updated", type: "text", label: "updated" } } }, 'Json.weather': { title: 'World weather', tabTitle: 'Conditions', fields: { 1: { name: "$[0].data.current_condition[0].iconPath", type: "resource", label: "current icon" }, 2: { name: "$[0].data.current_condition[0].temp_@", type: "text", label: "current temp" }, 3: { name: "$[0].data.current_condition[0].humidity", type: "text", label: "current humidity" }, 4: { name: "$[0].data.weather[0].iconPath", type: "resource", label: "today icon" }, 5: { name: "$[0].data.weather[0].mintemp@", type: "text", label: "today min temp" }, 6: { name: "$[0].data.weather[0].maxtemp@", type: "text", label: "today max temp" }, 7: { name: "$[0].data.weather[0].day", type: "text", label: "today label" }, 8: { name: "$[0].data.weather[1].iconPath", type: "resource", label: "today+1 icon" }, 9: { name: "$[0].data.weather[1].mintemp@", type: "text", label: "today+1 min temp" }, 10: { name: "$[0].data.weather[1].maxtemp@", type: "text", label: "today+1 max temp" }, 11: { name: "$[0].data.weather[1].day", type: "text", label: "today+1 label" }, 12: { name: "$[0].data.weather[2].iconPath", type: "resource", label: "today+2 icon" }, 13: { name: "$[0].data.weather[2].mintemp@", type: "text", label: "today+2 min temp" }, 14: { name: "$[0].data.weather[2].maxtemp@", type: "text", label: "today+2 max temp" }, 15: { name: "$[0].data.weather[2].day", type: "text", label: "today+2 label" }, 16: { name: "$[0].data.weather[3].iconPath", type: "resource", label: "today+3 icon" }, 17: { name: "$[0].data.weather[3].mintemp@", type: "text", label: "today+3 min temp" }, 18: { name: "$[0].data.weather[3].maxtemp@", type: "text", label: "today+3 max temp" }, 19: { name: "$[0].data.weather[3].day", type: "text", label: "today+3 label" }, 20: { name: "$[0].data.weather[4].iconPath", type: "resource", label: "today+4 icon" }, 21: { name: "$[0].data.weather[4].mintemp@", type: "text", label: "today+4 min temp" }, 22: { name: "$[0].data.weather[4].maxtemp@", type: "text", label: "today+4 max temp" }, 23: { name: "$[0].data.weather[4].day", type: "text", label: "today+4 label" }, 24: { name: "$[0].data.weather[5].iconPath", type: "resource", label: "today+5 icon" }, 25: { name: "$[0].data.weather[5].mintemp@", type: "text", label: "today+5 min temp" }, 26: { name: "$[0].data.weather[5].maxtemp@", type: "text", label: "today+5 max temp" }, 27: { name: "$[0].data.weather[5].day", type: "text", label: "today+5 label" }, 28: { name: "$[0].data.weather[6].iconPath", type: "resource", label: "today+6 icon" }, 29: { name: "$[0].data.weather[6].mintemp@", type: "text", label: "today+6 min temp" }, 30: { name: "$[0].data.weather[6].maxtemp@", type: "text", label: "today+6 max temp" }, 31: { name: "$[0].data.weather[6].day", type: "text", label: "today+6 label" } } } }; } /** translate a json item path such as $[0].data.weather... to it's label @method _translateToLabel @param {Number} i_playerData @return {Number} Unique clientId. **/ private _translateToLabel(i_jsonPath:string):string { var self = this; // no mime configured in scnene so return same label if (_.isUndefined(self.m_sceneMime)) return i_jsonPath; switch (self.m_sceneMime) { case 'Json.spreadsheet': { // lookup up label in m_config for spreadsheet return self.m_config['Json.spreadsheet'].fields['1'].label; } default: { // look up label in m_config db for everything else var fields:any = self.m_config[self.m_sceneMime].fields; for (var item in fields) { if (fields[item].name == i_jsonPath) return fields[item].label; } } } return i_jsonPath; } /** Some json item field names need to be muated into something else. For example, the default fieldName of text needs to be changed into '$cells.1.1.value' when used in a scene of mimeType @method _mutateCustomFieldName **/ private _mutateCustomFieldName():void { var self = this; switch (self.m_sceneMime) { case 'Json.spreadsheet': { var domPlayerData = self._getBlockPlayerData(); var xSnippet = $(domPlayerData).find('XmlItem'); var fieldName = $(xSnippet).attr('fieldName'); if (fieldName == 'text') { var value = self.m_config['Json.spreadsheet'].fields['1'].name; $(xSnippet).attr('fieldName', value); // self._setBlockPlayerData(domPlayerData, BB.CONSTS.NO_NOTIFICATION); } break; } default: { } } } /** Convert the block into a fabric js compatible object @Override @method fabricateBlock @param {number} i_canvasScale @param {function} i_callback **/ fabricateBlock(i_canvasScale, i_callback) { var self= this; self._mutateCustomFieldName(); var domPlayerData = self._getBlockPlayerData(); var layout = $(domPlayerData).find('Layout'); var xSnippet = $(domPlayerData).find('XmlItem'); var fieldName = $(xSnippet).attr('fieldName'); var text = self._translateToLabel(fieldName); var font = $(xSnippet).find('Font'); var link = '://ajax.googleapis.com/ajax/libs/webfont/1/webfont.js'; var url = ('https:' === document.location.protocol ? 'https' : 'http') + link; //$.getScript(src, function (data) { // console.log(data); //}); var t = new fabric.IText(text, { fontSize: Number($(font).attr('fontSize')), //fontFamily: 'Graduate', //fontFamily: 'Jolly Lodger', //fontFamily: 'Arial', fontFamily: $(font).attr('fontFamily'), fill: '#' + Lib.DecimalToHex($(font).attr('fontColor')), textDecoration: $(font).attr('textDecoration'), fontWeight: $(font).attr('fontWeight'), fontStyle: $(font).attr('fontStyle'), textAlign: $(font).attr('textAlign'), top: 5, left: 5 }); // calculate block so it can always contain the text it holds and doesn't bleed //self.m_minSize.w = t.width < 50 ? 50 : t.width * 1.2; //self.m_minSize.h = t.height < 50 ? 50 : t.height * 1.2; //var w = parseInt(layout.attr('width')) < self.m_minSize.w ? self.m_minSize.w : parseInt(layout.attr('width')); //var h = parseInt(layout.attr('height')) < self.m_minSize.h ? self.m_minSize.h : parseInt(layout.attr('height')); var w = parseInt(layout.attr('width')); var textWidth = t.width * 1.2; if (textWidth > w) { t.setText('...'); } var h = parseInt(layout.attr('height')); var textHeight = t.height * 1.2; if (textHeight > h) { t.setText('...'); } var rec = self._fabricRect(w, h, domPlayerData); var o = self._fabricateOptions(parseInt(layout.attr('y')), parseInt(layout.attr('x')), w, h, parseInt(layout.attr('rotation'))); rec.originX = 'center'; rec.originY = 'center'; t.top = 0 - (rec.height / 2); t.left = 0 - (rec.width / 2); _.extend(self, o); self.add(rec); self.add(t); self._fabricAlpha(domPlayerData); self._fabricLock(); self['canvasScale'] = i_canvasScale; //$.ajax({ // url: url, // async: false, // dataType: 'script', // complete: function (e) { // setTimeout(i_callback, 1); // } //}); setTimeout(i_callback, 1); var direction = $(font).attr('textAlign'); switch (direction) { case 'left': { break; } case 'center': { t.set({ textAlign: direction, originX: direction, left: 0 }); break; } case 'right': { t.set({ textAlign: direction, originX: direction, left: rec.width / 2 }); break; } } } } ================================================ FILE: src/app/blocks/block-fabric-label.ts ================================================ import {BlockFabric} from "./block-fabric"; import * as _ from "lodash"; import {Lib} from "../../Lib"; import {BlockLabels} from "../../interfaces/Consts"; const blockType = BlockLabels.LABEL; export class BlockFabricLabel extends BlockFabric { constructor(options, i_blockService, i_pepper) { super(options, i_blockService, i_pepper, blockType) this.m_blockService = i_blockService; this.m_pepper = i_pepper; this.m_blockType = blockType; _.extend(options, {blockType: this.m_blockType}) } /** Convert the block into a fabric js compatible object @Override @method fabricateBlock @param {number} i_canvasScale @param {function} i_callback **/ fabricateBlock(i_canvasScale, i_callback) { var domPlayerData = this._getBlockPlayerData(); var layout = $(domPlayerData).find('Layout'); var label = $(domPlayerData).find('Label'); var text = $(label).find('Text').text(); var font = $(label).find('Font'); var url = ('https:' === document.location.protocol ? 'https' : 'http') + '://ajax.googleapis.com/ajax/libs/webfont/1/webfont.js'; //$.getScript(src, function (data) { // console.log(data); //}); var t = new fabric.IText(text, { fontSize: Number($(font).attr('fontSize')), //fontFamily: 'Graduate', //fontFamily: 'Jolly Lodger', //fontFamily: 'Arial', fontFamily: $(font).attr('fontFamily'), fill: '#' + Lib.DecimalToHex($(font).attr('fontColor')), textDecoration: $(font).attr('textDecoration'), fontWeight: $(font).attr('fontWeight'), fontStyle: $(font).attr('fontStyle'), textAlign: $(font).attr('textAlign'), top: 5, left: 5 }); // calculate block so it can always contain the text it holds and doesn't bleed this.m_minSize.w = t.width < 50 ? 50 : t.width * 1.2; this.m_minSize.h = t.height < 50 ? 50 : t.height * 1.2; var w = parseInt(layout.attr('width')) < this.m_minSize.w ? this.m_minSize.w : parseInt(layout.attr('width')); var h = parseInt(layout.attr('height')) < this.m_minSize.h ? this.m_minSize.h : parseInt(layout.attr('height')); var rec = this._fabricRect(w, h, domPlayerData); var o = this._fabricateOptions(parseInt(layout.attr('y')), parseInt(layout.attr('x')), w, h, parseInt(layout.attr('rotation'))); //var group = new fabric.Group([ rec, t ], o); //_.extend(this, group); rec.originX = 'center'; rec.originY = 'center'; t.top = 0 - (rec.height / 2); t.left = 0 - (rec.width / 2); _.extend(this, o); this.add(rec); this.add(t); this._fabricAlpha(domPlayerData); this._fabricLock(); this['canvasScale'] = i_canvasScale; //$.ajax({ // url: url, // async: false, // dataType: "script", // complete: function(e){ // setTimeout(i_callback,1) // } //}); setTimeout(i_callback,1); var direction = $(font).attr('textAlign'); switch (direction) { case 'left': { break; } case 'center': { t.set({ textAlign: direction, originX: direction, left: 0 }); break; } case 'right': { t.set({ textAlign: direction, originX: direction, left: rec.width / 2 }); break; } } // todo: add shadow //http://jsfiddle.net/Kienz/fgWNL/ //http://jsfiddle.net/fabricjs/7gvJG/ //http://jsfiddle.net/5KKQ2/ //t.set('shadow', { // color: 'black', // blur: 1, // offsetX:3, // offsetY: 1 //});({ color: 'rgba(12,12,12,1)' }); //rec.set('shadow', { // color: 'black', // blur: 1, // offsetX:3, // offsetY: 1 //}); } /** Delete this block @method deleteBlock @params {Boolean} i_memoryOnly if true only remove from existance but not from msdb **/ deleteBlock(i_memoryOnly) { // $(Elements.IMAGE_ASPECT_RATIO).off('change', this.m_inputChangeHandler); this._deleteBlock(i_memoryOnly); } } ================================================ FILE: src/app/blocks/block-fabric-scene.ts ================================================ import {BlockFabric} from "./block-fabric"; import * as _ from 'lodash'; import {Lib} from "../../Lib"; import {BlockLabels} from "../../interfaces/Consts"; const blockType = BlockLabels.BLOCKCODE_SCENE; export class BlockFabricScene extends BlockFabric { m_canvas; m_gridMagneticMode = 0; constructor(options, i_blockService, i_pepper) { super(options, i_blockService, i_pepper, blockType) this.m_blockService = i_blockService; this.m_pepper = i_pepper; this.m_blockType = blockType; _.extend(options, {blockType: this.m_blockType}) } /** get player data for a scene @Override **/ getBlockData() { var data = BlockFabric.prototype.getBlockData.call(this); var domPlayerData = this._getBlockPlayerData(); data.blockName = $(domPlayerData).find('Player').eq(0).attr('label'); return data; } /** Update the msdb for the block with new values inside its player_data @Override **/ _setBlockPlayerData(i_xmlDoc, i_noNotify) { // var player_data = (new XMLSerializer()).serializeToString(i_xmlDoc); // switch (this.m_placement) { // case BB.CONSTS.PLACEMENT_CHANNEL: { // var recBlock = pepper.getCampaignTimelineChannelPlayerRecord(this.m_block_id); // var domPlayerData = $.parseXML(recBlock['player_data']); // var scene_id = $(domPlayerData).find('Player').attr('hDataSrc'); // var player_data = (new XMLSerializer()).serializeToString(i_xmlDoc); // pepper.setScenePlayerData(scene_id, player_data); // break; // } // // case BB.CONSTS.PLACEMENT_IS_SCENE: { // pepper.setScenePlayerData(this.m_block_id, player_data); // //if (!i_noNotify) // // this._announceBlockChanged(); // break; // } // } } /** Get the XML player data of a block, depending where its placed @Override **/ _getBlockPlayerData(): any { var blockID = this.m_pepper.getSceneIdFromPseudoId(this.m_block_id); var recPlayerData = this.m_pepper.getScenePlayerRecord(blockID); var xPlayerdata = recPlayerData['player_data_value']; return $.parseXML(xPlayerdata); // var recBlock = undefined; // // switch (this.m_placement) { // // case BB.CONSTS.PLACEMENT_CHANNEL: { // recBlock = pepper.getCampaignTimelineChannelPlayerRecord(this.m_block_id); // var domPlayerData = $.parseXML(recBlock['player_data']); // var sceneHandle = $(domPlayerData).find('Player').attr('hDataSrc'); // return pepper.getScenePlayerdataDom(sceneHandle); // break; // } // // case BB.CONSTS.PLACEMENT_IS_SCENE: { // var blockID = pepper.getSceneIdFromPseudoId(this.m_block_id); // var recPlayerData = BB.Pepper.getScenePlayerRecord(blockID); // var xPlayerdata = recPlayerData['player_data_value']; // return $.parseXML(xPlayerdata); // break; // } // } } /** Find the border section in player_data for selected block **/ // _findBorder(i_domPlayerData) { // var xSnippet = $(i_domPlayerData).find('Layout').eq(0).siblings().filter('Border'); // return xSnippet; // } /** Find the background section in player_data for selected block **/ // _findBackground(i_domPlayerData) { // var xSnippet = $(i_domPlayerData).find('Layout').eq(0).siblings().filter('Background'); // return xSnippet; // } /** Find the gradient blocks in player_data for selected scene block **/ _findGradientPoints(i_domPlayerData) { var xBackground = $(i_domPlayerData).find('Layout').eq(0).siblings().filter('Background'); var xSnippet = $(xBackground).find('GradientPoints').eq(0); return xSnippet; } /** Add the checkers background to a scene **/ _fabricApplySceneBgImage(i_image) { this.m_canvas.setBackgroundColor('', this.m_canvas.renderAll.bind(this.m_canvas)); $('#sceneCanvasContainer').find('.canvas-container').removeClass('checkers').removeClass('grid25').removeClass('grid50').addClass(i_image); this.m_canvas.renderAll(); } /** Set a scene's background color or image @method fabricSceneBg **/ fabricSceneBg() { var domPlayerData = this._getBlockPlayerData(); var colorPoints = this._findGradientPoints(domPlayerData) var color = $(colorPoints).find('Point').attr('color'); switch (this.m_gridMagneticMode) { case 0: { if (_.isUndefined(color)) { this._fabricApplySceneBgImage('checkers'); return; } color = '#' + Lib.DecimalToHex(color); if (this.m_canvas.backgroundColor == color) return; this.m_canvas.setBackgroundColor(color, function () { }); this.m_canvas.renderAll(); break; } case 1: { this._fabricApplySceneBgImage('grid25'); break; } case 2: { this._fabricApplySceneBgImage('grid50'); break; } } } /** Set reference to managed canvas **/ setCanvas(i_canvas, i_magneticGridMode) { this.m_canvas = i_canvas; this.m_gridMagneticMode = i_magneticGridMode; this.fabricSceneBg(); } /** Get the scene id that's associated with this block given that it resides in a timeline > channel **/ // getChannelBlockSceneID() { // var recBlock = pepper.getCampaignTimelineChannelPlayerRecord(this.m_block_id); // var domPlayerData = $.parseXML(recBlock['player_data']); // var scene_id = $(domPlayerData).find('Player').attr('hDataSrc'); // return scene_id; // } /** Delete this block @method deleteBlock @params {Boolean} i_memoryOnly if true only remove from existance but not from msdb **/ deleteBlock(i_memoryOnly) { // BB.comBroker.stopListenWithNamespace(BB.EVENTS.SCENE_BG_COLOR_CHANGED, this); // $(Elements.SCENE_NAME_INPUT).off("input", this.m_inputNameChangeHandler); // $(Elements.SCENE_WIDTH_INPUT).off("input", this.m_inputWidthChangeHandler); // $(Elements.SCENE_WIDTH_INPUT).off("blur", this.m_inputWidthChangeHandler); // $(Elements.SCENE_HEIGHT_INPUT).off("blur", this.m_inputWidthChangeHandler); // $(Elements.SCENE_HEIGHT_INPUT).off("input", this.m_inputHeightChangeHandler); // this._deleteBlock(i_memoryOnly); } } ================================================ FILE: src/app/blocks/block-fabric-svg.ts ================================================ import {BlockFabric} from "./block-fabric"; import * as _ from "lodash"; import {BlockLabels} from "../../interfaces/Consts"; const blockType = BlockLabels.BLOCKCODE_SVG; export class BlockFabricSvg extends BlockFabric { m_nativeID; m_fileFormat; constructor(options, i_blockService, i_pepper) { super(options, i_blockService, i_pepper, blockType) this.m_blockService = i_blockService; this.m_pepper = i_pepper; this.m_blockType = blockType; _.extend(options, {blockType: this.m_blockType}) this._initResourcesData(); } /** Set the instance resource data from msdb which includes resource_id (handle of a resource) as well as the description of the resource and icon. @method _initResourcesData **/ _initResourcesData() { var domPlayerData = this._getBlockPlayerData(); var xSnippet = $(domPlayerData).find('Resource'); this.m_resourceID = $(xSnippet).attr('hResource'); this.m_nativeID = this.m_pepper.getResourceNativeID(this.m_resourceID); if (_.isNull(this.m_nativeID)) { this._selfDestruct(); return; } this.m_blockName = this.m_pepper.getResourceRecord(this.m_resourceID).resource_name; this.m_blockDescription = this.m_pepper.getResourceName(this.m_resourceID); this.m_fileFormat = this.m_pepper.getResourceType(this.m_resourceID); this.m_blockFontAwesome = this.m_blockService.getFontAwesome(this.m_fileFormat); } // /var/www/sites/dynasite/htdocs/_msportal/_js/_node/public/assets/14.svg /** Convert the block into a fabric js compatible object, called externally on creation of block @Override @method fabricateBlock **/ fabricateBlock(i_canvasScale, i_callback) { var self = this; var domPlayerData = self._getBlockPlayerData(); var layout = $(domPlayerData).find('Layout'); var w = parseInt(layout.attr('width')); var h = parseInt(layout.attr('height')); var rec = self._fabricRect(w, h, domPlayerData); var svgPath = window.g_protocol + self.m_pepper.getUserData().domain + '/Resources/business' + self.m_pepper.getUserData().businessID + '/resources/' + self.m_nativeID + '.' + self.m_fileFormat; var urlPath = jQuery.base64.encode(svgPath); var srvPath = 'https://secure.digitalsignage.com/proxyRequest/' + urlPath; //svgPath = 'https://secure.digitalsignage.com/_public/assets/15.svg'; //svgPath = 'https://ida.signage.me/Test/14.svg'; //svgPath = 'https://ida.signage.me/code/14.svg'; //svgPath = "https://s3-us-west-2.amazonaws.com/oregon-signage-resources/business372844/resources/14.svg"; //svgPath = 'https://ida2.signage.me/14.svg'; $.get(srvPath, function (svg) { var hh, ww, svgHeight, svgWidth, re; // set new height in SVG per current selection box height hh = layout.attr('height'); // catch load errors svgHeight = svg.match(/(height=")([^\"]*)/) if (_.isNull(svgHeight)) { i_callback(); return; } svgHeight = svgHeight[2]; re = new RegExp('height="' + svgHeight + '"', "ig"); svg = svg.replace(re, 'height="' + hh + '"'); // set new width in SVG per current selection box width ww = layout.attr('width'); svgWidth = svg.match(/(width=")([^\"]*)/)[2]; re = new RegExp('width="' + svgWidth + '"', "ig"); svg = svg.replace(re, 'width="' + ww + '"'); fabric.loadSVGFromString(svg, function (objects:any, options) { objects[0].heightAttr = hh; objects[0].widthAttr = ww; objects[0].height = hh; objects[0].width = ww; var groupSvg = fabric.util.groupSVGElements(objects, options); rec.originX = 'center'; rec.originY = 'center'; groupSvg.originX = 'center'; groupSvg.originY = 'center'; var o = { left: parseInt(layout.attr('x')), top: parseInt(layout.attr('y')), width: parseInt(layout.attr('width')), height: parseInt(layout.attr('height')), angle: parseInt(layout.attr('rotation')), hasRotatingPoint: false, stroke: 'transparent', cornerColor: 'black', cornerSize: 5, lockRotation: true, transparentCorners: false }; _.extend(self, o); self.add(rec); self.add(groupSvg); self._fabricAlpha(domPlayerData); self._fabricLock(); self['canvasScale'] = i_canvasScale; i_callback(); }); }, 'text'); } /** Get the resource id of the embedded resource @method getResourceID @return {Number} resource_id; **/ getResourceID() { return this.m_resourceID; } /** Delete this block @method deleteBlock @params {Boolean} i_memoryOnly if true only remove from existance but not from msdb **/ deleteBlock(i_memoryOnly) { // $(Elements.IMAGE_ASPECT_RATIO).off('change', this.m_inputChangeHandler); this._deleteBlock(i_memoryOnly); } } ================================================ FILE: src/app/blocks/block-fabric.ts ================================================ import * as _ from 'lodash'; import {Lib} from "../../Lib"; import {BlockService} from "./block-service"; import {RedPepperService} from "../../services/redpepper.service"; export class BlockFabric extends fabric.Group { m_block_id; m_sceneID; m_blockType; m_zIndex = -1; m_minSize = {w: 50, h: 50}; m_blockName; m_blockAcronym; m_blockDescription; m_blockIcon; m_blockFontAwesome; m_blockSvg; m_resourceID; m_blockProperty; m_blockService: BlockService; m_pepper: RedPepperService; constructor(options, i_blockService, i_pepper, i_blockType) { super() this.m_blockType = i_blockType this.m_blockService = i_blockService; this.m_pepper = i_pepper; this.m_block_id = options.i_block_id; this.m_sceneID = options.i_scene_player_data_id; this.m_zIndex = -1; this.m_minSize = {w: 50, h: 50}; this.m_blockName = i_blockService.getBlockBoilerplate(this.m_blockType).name; this.m_blockAcronym = i_blockService.getBlockBoilerplate(this.m_blockType).acronym; this.m_blockDescription = i_blockService.getBlockBoilerplate(this.m_blockType).description; this.m_blockIcon = i_blockService.getBlockBoilerplate(this.m_blockType).icon; this.m_blockFontAwesome = i_blockService.getBlockBoilerplate(this.m_blockType).fontAwesome; this.m_blockSvg = i_blockService.getBlockBoilerplate(this.m_blockType).svg; this.m_resourceID = undefined; } // _setBlockPlayerData(i_xmlDoc, i_noNotify, i_xmlIsString) { // var player_data; // if (i_xmlIsString == true) { // player_data = i_xmlDoc; // } else { // player_data = (new XMLSerializer()).serializeToString(i_xmlDoc); // } // this.m_pepper.setScenePlayerdataBlock(this.m_sceneID, this.m_block_id, player_data); // this.m_pepper.reduxCommit(); // // switch (this.m_placement) { // // case PLACEMENT_SCENE: { // // this.m_pepper.setScenePlayerdataBlock(this.m_sceneID, this.m_block_id, player_data); // // break; // // } // // case PLACEMENT_IS_SCENE: { // // this.m_pepper.setScenePlayerData(this.m_block_id, player_data); // // break; // // } // // } // } /** Get the XML player data of a block, depending where its placed If you like to view XML raw data, be sure to debug domPlayerData.children[0].outerHTML **/ _getBlockPlayerData(): any { return this.m_pepper.getScenePlayerdataBlock(this.m_sceneID, this.m_block_id); // to view data debug domPlayerData.children[0].outerHTML } /** Find the border section in player_data for selected block **/ _findBorder(i_domPlayerData) { return $(i_domPlayerData).find('Border'); } /** Fabricate alpha to canvas **/ _fabricAlpha(i_domPlayerData) { var appearance = $(i_domPlayerData).find('Appearance'); var opacity: any = $(appearance).attr('alpha'); this.setOpacity(opacity); } /** Fabricate color points to canvas **/ _fabricColorPoints(i_domPlayerData) { var gradientPoints = $(i_domPlayerData).find('GradientPoints'); var points = $(gradientPoints).find('Point'); var colorStops = {} _.each(points, function (point) { var color = '#' + Lib.DecimalToHex(($(point).attr('color'))); var offset: any = $(point).attr('midpoint'); offset = offset / 250; colorStops[offset] = color; }); return colorStops; } /** Config the fabric block border **/ _fabricateBorder(i_options) { var domPlayerData = this._getBlockPlayerData() var border = this._findBorder(domPlayerData); var color = border.length == 0 ? 'transparent' : '#' + Lib.DecimalToHex($(border).attr('borderColor')); return _.extend({ // borderColor: '#5d5d5d', stroke: color, strokeWidth: 1 }, i_options); } /** Build the options injected into a newly created fabric object **/ _fabricateOptions(i_top, i_left, i_width, i_height, i_angle) { var options = { top: i_top, left: i_left, width: i_width, height: i_height, angle: i_angle, fill: '#ececec', hasRotatingPoint: false, transparentCorners: false, cornerColor: 'black', cornerSize: 5, lockRotation: true, lineWidth: 1 }; return this._fabricateBorder(options); } /** Fabricate color points to canvas **/ _fabricRect(i_width, i_height, i_domPlayerData) { var options = this._fabricateOptions(0, 0, i_width, i_height, 0); var r = new fabric.Rect(options); r.setGradient('fill', { x1: 0 - (i_width / 2), y1: 0, x2: (i_width / 2), y2: 0, colorStops: this._fabricColorPoints(i_domPlayerData) }); return r; } /** Convert the block into a fabric js compatible object, called externally on creation of block @Override @method fabricateBlock **/ fabricateBlock(i_canvasScale, i_callback) { var domPlayerData = this._getBlockPlayerData(); var layout = $(domPlayerData).find('Layout'); var w = parseInt(layout.attr('width')); var h = parseInt(layout.attr('height')); var rec = this._fabricRect(w, h, domPlayerData); fabric.loadSVGFromString(this.m_blockSvg, (objects, options) => { var groupSvg = fabric.util.groupSVGElements(objects, options); rec.originX = 'center'; rec.originY = 'center'; groupSvg.originX = 'center'; groupSvg.originY = 'center'; var o = { left: parseInt(layout.attr('x')), top: parseInt(layout.attr('y')), width: parseInt(layout.attr('width')), height: parseInt(layout.attr('height')), angle: parseInt(layout.attr('rotation')), hasRotatingPoint: false, stroke: 'transparent', cornerColor: 'black', cornerSize: 5, lockRotation: true, transparentCorners: false }; _.extend(this, o); this.add(rec); this.add(groupSvg); this._fabricAlpha(domPlayerData); this._fabricLock(); this['canvasScale'] = i_canvasScale; i_callback(); }); } /** On changes in msdb model updated UI common lock properties **/ _fabricLock() { var domPlayerData = this._getBlockPlayerData(); var locked: any = $(domPlayerData).attr('locked'); if (_.isUndefined(locked) || locked == '0') { locked = false; } else { locked = true; } this.lockMovementX = locked; this.lockMovementY = locked; //self.lockScalingX = locked; self.lockScalingY = locked; self.lockUniScaling = locked; self.lockRotation = locked; /////var dimensionProps = BB.comBroker.getService(BB.SERVICES['DIMENSION_PROPS_LAYOUT']); /////if (_.isUndefined(dimensionProps)) ////// return; //////dimensionProps.setLock(locked); } /** Get block data as a json formatted object literal and return to caller **/ getBlockData() { var data = { blockID: this.m_block_id, blockType: this.m_blockType, blockName: this.m_blockName, blockDescription: this.m_blockDescription, blockIcon: this.m_blockIcon, blockFontAwesome: this.m_blockFontAwesome, blockAcronym: this.m_blockAcronym, blockMinWidth: this.m_minSize.w, blockMinHeight: this.m_minSize.h, blockData: this._getBlockPlayerData() }; return data; } /** Set a block's z-index in case we know it (i.e.: it is going to be a re-render of a previous block that was removed from the canvas) **/ setZindex(i_zIndex) { this.m_zIndex = i_zIndex; } /** Get a block's z-index @method getZindex @param {Number} i_index **/ getZindex(i_zIndex) { return this.m_zIndex; } /** Delete block is a public method used as fall back method, if not overridden by inherited instance. It is also a semi abstract method, all implementations should go into _deleteBlock(); @method deleteBlock @params {Boolean} i_memoryOnly if true only remove from existance but not from msdb @return none **/ deleteBlock(i_memoryOnly) { /* semi-abstract, overridden, do not modify */ this._deleteBlock(i_memoryOnly); } /** bug fix: backward comparability with player_data that includes deleted resources this was already fixed but we live _selfDestruct for backwards compatability @method _selfDestruct **/ _selfDestruct() { setTimeout(() => { //todo: ??? // var sceneEditView = BB.comBroker.getService(BB.SERVICES['SCENE_EDIT_VIEW']); // if (!_.isUndefined(sceneEditView)) { // var selectedSceneID = sceneEditView.getSelectedSceneID(); // pepper.removeScenePlayer(selectedSceneID, self.m_block_id); // BB.comBroker.fire(BB.EVENTS.LOAD_SCENE, this, null, selectedSceneID); // } }, 2000); } /** Delete block is a private method that is always called regardless if instance has been inherited or not. Used for releasing memory for garbage collector. @method _deleteBlock @params {Boolean} i_memoryOnly if true only remove from existance but not from msdb @return none **/ _deleteBlock(i_memoryOnly) { // if (!i_memoryOnly) // pepper.removeBlockFromTimelineChannel(this.m_block_id); // BB.comBroker.stopListenWithNamespace(BB.EVENTS.BLOCK_SELECTED, this); // BB.comBroker.stopListenWithNamespace(BB.EVENTS.BLOCK_LENGTH_CHANGING, this); // BB.comBroker.stopListenWithNamespace(BB.EVENTS.GRADIENT_COLOR_CHANGED, this); // BB.comBroker.stopListenWithNamespace(BB.EVENTS.GRADIENT_COLOR_CLOSED, this); // BB.comBroker.stopListenWithNamespace(BB.EVENTS.BLOCK_BORDER_CHANGE, this); // BB.comBroker.stopListenWithNamespace(BB.EVENTS.ALPHA_CHANGED, this); // BB.comBroker.stopListenWithNamespace(BB.EVENTS.LOCK_CHANGED, this); // $(Elements.SHOW_BACKGROUND).off(this.m_proxyToggleBgKey, this.m_proxyToggleBg); // $(Elements.SHOW_BORDER).off(this.m_proxyToggleBorderKey, this.m_proxyToggleBorder); // if (this.off != undefined) // this.off('modified'); // // if (this.m_sceneSelectedHandler) // this.m_canvas.off('object:selected', this.m_sceneSelectedHandler); // // $.each(this, function (k) { // this[k] = undefined; // }); } } ================================================ FILE: src/app/blocks/block-prop-calendar.ts ================================================ import {AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, Input} from "@angular/core"; import {FormBuilder, FormControl, FormGroup, Validators} from "@angular/forms"; import {BlockService, IBlockData} from "./block-service"; import {Compbaser, NgmslibService} from "ng-mslib"; import * as _ from "lodash"; import * as moment from 'moment' @Component({ selector: 'block-prop-calendar', host: { '(input-blur)': 'saveToStore($event)' }, changeDetection: ChangeDetectionStrategy.OnPush, template: ` {{me}}
  • token
  • Load with calendar
  • offset range mode




` }) export class BlockPropCalendar extends Compbaser implements AfterViewInit { m_formInputs = {}; m_contGroup: FormGroup; m_blockData: IBlockData; m_calList = []; m_calSeleced: any = {}; m_mode = false; constructor(private fb: FormBuilder, private cd: ChangeDetectorRef, private bs: BlockService, private ngmslibService: NgmslibService) { super(); this.m_contGroup = fb.group({ 'token': [''], 'mode': [''], 'startDate': ['1/1/2020'], 'endDate': ['1/1/2020'], 'before': [], 'after': [], 'calSelection': [''] }); _.forEach(this.m_contGroup.controls, (value, key: string) => { this.m_formInputs[key] = this.m_contGroup.controls[key] as FormControl; }) } @Input() jsonMode: boolean; @Input() set setBlockData(i_blockData) { if (this.m_blockData && this.m_blockData.blockID != i_blockData.blockID) { this.m_blockData = i_blockData; this._render(); } else { this.m_blockData = i_blockData; } } _saveDates(range) { } _onCalSelected(event) { var calId = event.target.value; var domPlayerData: XMLDocument = this.m_blockData.playerDataDom; var item = jXML(domPlayerData).find('Json').find('Data'); jXML(item).attr('id', calId); this.bs.setBlockPlayerData(this.m_blockData, domPlayerData); } private _render() { var domPlayerData: XMLDocument = this.m_blockData.playerDataDom var $data = jXML(domPlayerData).find('Json').find('Data'); var mode = $data.attr('mode'); this.m_mode = (mode == 'fixed') ? false : true; var before = $data.attr('before'); var after = $data.attr('after'); this.m_formInputs['mode'].setValue(this.m_mode); this.m_formInputs['after'].setValue(after); this.m_formInputs['before'].setValue(before); this.m_formInputs['token'].setValue($data.attr('token')); this._getGoogleCalendars(); this._populateStartEndDates(); } /** Populate the start and end dates for Google calendar date range selection If first time date component is used, set startDate and endDate where startDate is relative to today and endDate for a week from now @method _populateStartEndDates **/ _populateStartEndDates(): void { var startDate = this._getRangeDate('startDate'); var endDate = this._getRangeDate('endDate'); this.m_formInputs['startDate'].setValue(startDate); this.m_formInputs['endDate'].setValue(endDate); } _setRangeDate(i_field: 'startDate' | 'endDate', i_value): void { var domPlayerData = this.m_blockData.playerDataDom; var xSnippet = jXML(domPlayerData).find('Json').find('Data'); var value = moment(i_value).unix() + '000'; jXML(xSnippet).attr(i_field, value); this.bs.setBlockPlayerData(this.m_blockData, domPlayerData); } _getRangeDate(i_field): string { var domPlayerData: XMLDocument = this.m_blockData.playerDataDom; var item = jXML(domPlayerData).find('Json').find('Data'); var value: any = jXML(item).attr(i_field); if (_.isEmpty(value)) { var date = new Date(); var lastWeek: number = date.setDate(new Date().getDate() - 7); var newDate = moment(lastWeek).format('YYYY-MM-DD'); return newDate; } value = value.slice(0, -3); value = moment.unix(parseInt(value)).format('YYYY-MM-DD'); return value; } _onModeChange(i_value) { var mode = i_value == true ? 'offset' : 'fixed'; this.m_mode = i_value; var domPlayerData = this.m_blockData.playerDataDom; var xSnippet = jXML(domPlayerData).find('Json').find('Data'); jXML(xSnippet).attr('mode', mode); this.bs.setBlockPlayerData(this.m_blockData, domPlayerData); } _getGoogleCalendars() { var self = this; try { jQuery.ajax({ url: `https://secure.digitalsignage.com/GoogleCalendarList/${self.m_contGroup.value.token}/100`, dataType: "json", type: "post", complete: function (response, status) { self.m_calSeleced = {}; self.m_calList = []; if (_.isUndefined(response.responseText) || response.responseText.length == 0) return; if (response.responseText.indexOf('Cannot')>-1) return; var jData = JSON.parse(response.responseText); _.forEach(jData, function (k: any) { self.m_calList.push({ id: k.id, label: k.summary }) }); var id = self._getFileId(); if (id && id.length > 10) self.m_calSeleced = self.m_calList.find(item => item.id == id); self.m_formInputs['calSelection'].setValue(self.m_calSeleced.id); self.cd.markForCheck() }, error: function (jqXHR, exception) { console.log('ajax req:' + jqXHR, exception); } }); } catch (e) { console.error('error on ajax' + e); } } private _getFileId(): string { var domPlayerData: XMLDocument = this.m_blockData.playerDataDom; var item = jXML(domPlayerData).find('Json').find('Data'); return jXML(item).attr('id'); } ngAfterViewInit() { this._render(); } _onCreateToken() { var win = window.open('http://google.signage.me', '_blank'); if (win) { win.focus(); } else { bootbox.alert('Browser popups are blocked, please enable and try again'); } } private saveToStore() { // Lib.Con(this.m_contGroup.status + ' ' + JSON.stringify(this.ngmslibService.cleanCharForXml(this.m_contGroup.value))); if (this.m_contGroup.status != 'VALID') return; var domPlayerData: XMLDocument = this.m_blockData.playerDataDom; var item = jXML(domPlayerData).find('Json').find('Data'); jXML(item).attr('before', this.m_contGroup.value.before); jXML(item).attr('after', this.m_contGroup.value.after); jXML(item).attr('token', this.m_contGroup.value.token); this._setRangeDate('startDate',this.m_contGroup.value.startDate) this._setRangeDate('endDate',this.m_contGroup.value.endDate) this.bs.setBlockPlayerData(this.m_blockData, domPlayerData); } destroy() { } } ================================================ FILE: src/app/blocks/block-prop-clock.ts ================================================ import {AfterViewInit, ChangeDetectionStrategy, Component, Input} from "@angular/core"; import {FormBuilder} from "@angular/forms"; import {BlockService, IBlockData} from "./block-service"; import {RedPepperService} from "../../services/redpepper.service"; import {Compbaser} from "ng-mslib"; import {IFontSelector} from "../../comps/font-selector/font-selector"; import {Lib} from "../../Lib"; @Component({ selector: 'block-prop-clock', changeDetection: ChangeDetectionStrategy.OnPush, template: ` {{me}}
  • Choose format
` }) export class BlockPropClock extends Compbaser implements AfterViewInit { m_blockData: IBlockData; m_fontConfig: IFontSelector; m_clockFormats = [{ type: 'longDateAndTime', format: 'Friday, Mar 21 2018 at 8:59AM' }, { type: 'longDate', format: 'Friday, Mar 21 2018' }, { type: 'shortDayTime', format: 'Friday 9:10 AM' }, { type: 'date', format: '3/21/18' }, { type: 'time', format: '9:00:39 AM' }]; m_model = { options: this.m_clockFormats[0] }; constructor(private fb: FormBuilder, private rp: RedPepperService, private bs: BlockService) { super(); } @Input() set setBlockData(i_blockData) { if (this.m_blockData && this.m_blockData.blockID != i_blockData.blockID) { this.m_blockData = i_blockData; this._render(); } else { this.m_blockData = i_blockData; } } _onFontChanged(config: IFontSelector) { var domPlayerData = this.m_blockData.playerDataDom; var xSnippet = jXML(domPlayerData).find('Clock'); var xSnippetFont = jXML(xSnippet).find('Font'); config.bold == true ? xSnippetFont.attr('fontWeight', 'bold') : xSnippetFont.attr('fontWeight', 'normal'); config.italic == true ? xSnippetFont.attr('fontStyle', 'italic') : xSnippetFont.attr('fontStyle', 'normal'); config.underline == true ? xSnippetFont.attr('textDecoration', 'underline') : xSnippetFont.attr('textDecoration', 'none'); xSnippetFont.attr('fontColor', Lib.ColorToDecimal(config.color)); xSnippetFont.attr('fontSize', config.size); xSnippetFont.attr('fontFamily', config.font); xSnippetFont.attr('textAlign', config.alignment); this.bs.setBlockPlayerData(this.m_blockData, domPlayerData); } _onFormatChanged(e) { var mask = this.bs.getBlockBoilerplate(this.m_blockData.blockCode).getDateTimeMask(e.type); var domPlayerData = this.m_blockData.playerDataDom; var xSnippet = jXML(domPlayerData).find('Clock'); xSnippet.attr('clockMask', mask); this.bs.setBlockPlayerData(this.m_blockData, domPlayerData); } ngAfterViewInit() { this._render(); } _render() { var self = this; var domPlayerData = this.m_blockData.playerDataDom; var xSnippet = jXML(domPlayerData).find('Clock'); var mask = jXML(xSnippet).attr('clockMask'); var xSnippetFont = jXML(xSnippet).find('Font'); this.m_clockFormats.forEach(i_clockFormat => { var currMask = self.bs.getBlockBoilerplate(self.m_blockData.blockCode).getDateTimeMask(i_clockFormat.type); if (mask == currMask) { this.m_model = {options: i_clockFormat}; } }); this.m_fontConfig = { size: Number(xSnippetFont.attr('fontSize')), alignment: xSnippetFont.attr('textAlign'), bold: xSnippetFont.attr('fontWeight') == 'bold' ? true : false, italic: xSnippetFont.attr('fontStyle') == 'italic' ? true : false, font: xSnippetFont.attr('fontFamily'), underline: xSnippetFont.attr('textDecoration') == 'underline' ? true : false, color: Lib.ColorToHex(Lib.DecimalToHex(xSnippetFont.attr('fontColor'))), } } destroy() { // console.log('destroy html component'); } } ================================================ FILE: src/app/blocks/block-prop-collection.ts ================================================ import {AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, Inject, Input, ViewChild} from "@angular/core"; import {FormBuilder, FormControl, FormGroup} from "@angular/forms"; import {BlockService, IBlockData} from "./block-service"; import {Compbaser, NgmslibService} from "ng-mslib"; import * as _ from "lodash"; import {List} from "immutable"; import {ISimpleGridEdit} from "../../comps/simple-grid-module/SimpleGrid"; import {StoreModel} from "../../store/model/StoreModel"; import {SimpleGridRecord} from "../../comps/simple-grid-module/SimpleGridRecord"; import {SimpleGridTable} from "../../comps/simple-grid-module/SimpleGridTable"; import {ISimpleGridDraggedData} from "../../comps/simple-grid-module/SimpleGridDraggable"; import {JsonEventResourceModel} from "./json-event-grid"; import {Lib} from "../../Lib"; import {ModalComponent} from "ng2-bs3-modal/ng2-bs3-modal"; import {IAddContents} from "../../interfaces/IAddContent"; import {BlockLabels, PLACEMENT_LISTS, PLACEMENT_SCENE} from "../../interfaces/Consts"; import {RedPepperService} from "../../services/redpepper.service"; import {IUiState} from "../../store/store.data"; import {ACTION_UISTATE_UPDATE, SideProps} from "../../store/actions/appdb.actions"; import {YellowPepperService} from "../../services/yellowpepper.service"; @Component({ selector: 'block-prop-collection', host: { '(input-blur)': 'saveToStore($event)' }, styles: [` /* walk up the ancestor tree and if darkTheme is found, apply style */ /*:host-context(.darkTheme) * {*/ /*background-color: #1e1e1e;*/ /*}*/ `], changeDetection: ChangeDetectionStrategy.OnPush, template: ` {{me}}
  • Kiosk mode
  • name seconds order
  • ` }) export class BlockPropCollection extends Compbaser implements AfterViewInit { formInputs = {}; m_contGroup: FormGroup; m_blockData: IBlockData; m_PLACEMENT_LISTS = PLACEMENT_LISTS; m_collectionList: List; m_jsonEventResources: Array; constructor(private fb: FormBuilder, private yp:YellowPepperService, private cd: ChangeDetectorRef, private bs: BlockService, @Inject('BLOCK_PLACEMENT') private blockPlacement: string, private rp: RedPepperService) { super(); this.m_contGroup = fb.group({ 'mode': [0] }); _.forEach(this.m_contGroup.controls, (value, key: string) => { this.formInputs[key] = this.m_contGroup.controls[key] as FormControl; }) } @ViewChild('simpleGrid') simpleGrid: SimpleGridTable; @ViewChild(ModalComponent) modal: ModalComponent; @Input() set setBlockData(i_blockData) { this.m_blockData = i_blockData; this._render(); } ngAfterViewInit() { this._render(); } _onAddNewBlock() { this.modal.open() } _onClosed(){ var uiState: IUiState = {uiSideProps: SideProps.miniDashboard} this.yp.ngrxStore.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})) } // _onAddedNewBlock(i_addContents:IAddContents) { // console.log('added ' + i_addContents.name); // this.modal.close() // } _onDragComplete(dragData: ISimpleGridDraggedData) { // dragData.items.forEach((item: StoreModel, i) => con(i + ' ' + item.getKey('name')) ); var currentIndex = dragData.currentIndex; var newIndex = dragData.newIndex; var domPlayerData = this.m_blockData.playerDataDom; var target = jXML(domPlayerData).find('Collection').children().get(newIndex); var source = jXML(domPlayerData).find('Collection').children().get(currentIndex); newIndex > currentIndex ? jXML(target).after(source) : jXML(target).before(source); this.bs.setBlockPlayerData(this.m_blockData, domPlayerData); } _onRemoveCollectionItem() { var record: SimpleGridRecord = this.simpleGrid.getSelected(); if (_.isUndefined(record)) return; var rowIndex = this.simpleGrid.getSelected().index; var domPlayerData = this.m_blockData.playerDataDom; jXML(domPlayerData).find('Collection').children().eq(rowIndex).remove(); // self._populateTableCollection(domPlayerData); this._populateTableEvents(); this.bs.setBlockPlayerData(this.m_blockData, domPlayerData) } _onAddNewCollectionItem() { this._onAddNewBlock() } _onDurationEdited(event: ISimpleGridEdit, index) { var value = event.value; if (!Lib.IsNumber(value)) return; var domPlayerData = this.m_blockData.playerDataDom; var item = jXML(domPlayerData).find('Collection').children().get(index); jXML(item).attr('duration', value); this.bs.setBlockPlayerData(this.m_blockData, domPlayerData) } _onPageNameEdited(event: ISimpleGridEdit, index) { var value = event.value; var domPlayerData = this.m_blockData.playerDataDom; var item = jXML(domPlayerData).find('Collection').children().get(index); jXML(item).attr('page', value); this.bs.setBlockPlayerData(this.m_blockData, domPlayerData) } _populateTableCollection() { this.m_collectionList = List([]); var domPlayerData = this.m_blockData.playerDataDom as any var rowIndex = 0; jXML(domPlayerData).find('Collection').children().each((k, page) => { var resource_hResource, scene_hDataSrc; var type = jXML(page).attr('type'); if (type == 'resource') { resource_hResource = jXML(page).find('Resource').attr('hResource'); } else { scene_hDataSrc = jXML(page).find('Player').attr('hDataSrc'); } //con('populating ' + resource_hResource); var storeModel = new StoreModel({ rowIndex: rowIndex, checkbox: true, name: jXML(page).attr('page'), duration: jXML(page).attr('duration'), type: type, resource_hResource: resource_hResource, scene_hDataSrc: scene_hDataSrc }); this.m_collectionList = this.m_collectionList.push(storeModel); rowIndex++; }); this.m_collectionList = this._sortCollection(this.m_collectionList); } /** Load event list to block props UI @method _populateTableEvents **/ _populateTableEvents() { var data: Array = [], rowIndex = 0; var domPlayerData = this.m_blockData.playerDataDom; // self.m_collectionEventTable.bootstrapTable('removeAll'); jXML(domPlayerData).find('EventCommands').children().each(function (k, eventCommand) { var pageName = ''; if (jXML(eventCommand).attr('command') == 'selectPage') pageName = jXML(eventCommand).find('Page').attr('name'); var storeModel = new JsonEventResourceModel({ rowIndex: rowIndex, checkbox: true, event: jXML(eventCommand).attr('from'), pageName: pageName, action: jXML(eventCommand).attr('command') } ) data.push(storeModel) rowIndex++; }); this.m_jsonEventResources = data; } _sortCollection(i_collection: List): List { var sorted = i_collection.sort((a, b) => { if (a.getKey('rowIndex') > b.getKey('rowIndex')) return 1; if (a.getKey('rowIndex') < b.getKey('rowIndex')) return -1; return 0; }) return sorted as List; } _render() { this.m_contGroup.reset(); var domPlayerData = this.m_blockData.playerDataDom var xSnippetCollection = jXML(domPlayerData).find('Collection'); var mode = jXML(xSnippetCollection).attr('mode') == 'kiosk' ? 1 : 0; this.formInputs['mode'].setValue(mode); this._populateTableCollection(); this._populateTableEvents(); this.cd.markForCheck(); } _toggleKioskMode(i_value) { i_value = StringJS(i_value).booleanToNumber() var domPlayerData = this.m_blockData.playerDataDom; jXML(domPlayerData).find('Collection').attr('mode', i_value ? 'kiosk' : 'slideshow'); this.bs.setBlockPlayerData(this.m_blockData, domPlayerData) } /** Add a new collection item which can include a Scene or a resource (not a component) @method _onAddedContent @param {Event} e **/ _onAddedContent(i_addContents: IAddContents) { var domPlayerData = this.m_blockData.playerDataDom; var xSnippetCollection = jXML(domPlayerData).find('Collection'); var buff = ''; if (Number(i_addContents.blockCode) == BlockLabels.BLOCKCODE_SCENE) { // add scene to collection, if block resides in scene don't allow cyclic reference to collection scene inside current scene if (this.blockPlacement == PLACEMENT_SCENE && this.m_blockData.scene.handle == i_addContents.sceneData.scene_id) { return bootbox.alert('You cannot display a scene in a collection that refers to itself, that is just weird'); } var sceneName = i_addContents.sceneData.domPlayerDataJson.Player._label; var nativeId = i_addContents.sceneData.scene_native_id; buff = ` `; } else { // Add resources to collection var resourceName = this.rp.getResourceRecord(i_addContents.resourceId).resource_name; buff = ` ` } jXML(xSnippetCollection).append(jXML(buff)); this.bs.setBlockPlayerData(this.m_blockData, domPlayerData) } private saveToStore() { // console.log(this.m_contGroup.status + ' ' + JSON.stringify(this.ngmslibService.cleanCharForXml(this.m_contGroup.value))); if (this.m_contGroup.status != 'VALID') return; // var domPlayerData = this.m_blockData.playerDataDom; // var xSnippet = jXML(domPlayerData).find('HTML'); // xSnippet.attr('src', this.m_contGroup.value.url); // this.bs.setBlockPlayerData(this.m_blockData, domPlayerData); } destroy() { } } ================================================ FILE: src/app/blocks/block-prop-common.ts ================================================ import {AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, Inject, Input} from "@angular/core"; import {FormBuilder, FormControl, FormGroup} from "@angular/forms"; import {BlockService, IBlockData} from "./block-service"; import {timeout} from "../../decorators/timeout-decorator"; import {Subject} from "rxjs"; import {RedPepperService} from "../../services/redpepper.service"; import {Compbaser} from "ng-mslib"; import {Lib} from "../../Lib"; import * as _ from "lodash"; import {BlockLabels} from "../../interfaces/Consts"; @Component({ selector: 'block-prop-common', changeDetection: ChangeDetectionStrategy.OnPush, template: ` {{me}}
    common properties
    • alpha
    • background
    • scene background color
    • border color
    ` }) export class BlockPropCommon extends Compbaser implements AfterViewInit { private formInputs = {}; contGroup: FormGroup; m_blockData: IBlockData; m_isPropsForScene: boolean = false; m_borderColorChanged = new Subject(); m_sceneBackgroundColorChanged = new Subject(); m_color; m_sceneBackgroundColor; constructor(@Inject('BLOCK_PLACEMENT') private blockPlacement: string, private cd: ChangeDetectorRef, private fb: FormBuilder, private rp: RedPepperService, private bs: BlockService, private el: ElementRef) { super(); this.contGroup = fb.group({ 'alpha': [0], 'borderColor': [], 'border': [0], 'sceneBackgroundColor': [], 'sceneBackground': [0] }); _.forEach(this.contGroup.controls, (value, key: string) => { this.formInputs[key] = this.contGroup.controls[key] as FormControl; }) this._listenBorderChanged(); } /** * set block data for the component. * on first selection we just set the blockData, in subsequent calls we only * re-render if we are dealing with a new block id, note that on componet creation * the rendering is done via ngAfterViewInit * @param i_blockData */ @Input() set setBlockData(i_blockData) { if (this.m_blockData && this.m_blockData.blockID != i_blockData.blockID) { this.m_blockData = i_blockData; this._render(); } else { this.m_blockData = i_blockData; } } ngAfterViewInit() { this._listenBorderChanged(); this._bgGradientWidgetInit(); this._render(); } /** * Render the component with latest data from BlockData */ _render() { this.m_isPropsForScene = parseInt(this.m_blockData.blockCode) == BlockLabels.BLOCKCODE_SCENE ? true : false; this._alphaPopulate(); this._gradientPopulate(); this._sceneBackgroundPopulate(); this._borderPropsPopulate(); } _listenBorderChanged() { this.cancelOnDestroy( // this.m_borderColorChanged .debounceTime(500) .filter(v => v != '#123') .subscribe((i_color: any) => { var domPlayerData = this.bs.getBlockPlayerData(this.m_blockData) var border = this._findBorder(domPlayerData); jXML(border).attr('borderColor', Lib.HexToDecimal(i_color)); this.bs.setBlockPlayerData(this.m_blockData, domPlayerData); }, (e) => console.error(e)) ) this.cancelOnDestroy( // this.m_sceneBackgroundColorChanged .debounceTime(500) .filter(v => v != '#123') .subscribe((i_color: any) => { var domPlayerData = this.bs.getBlockPlayerData(this.m_blockData) var xPoints = this._findGradientPoints(domPlayerData); jXML(xPoints).find('Point').attr('color', Lib.HexToDecimal(i_color)); this.bs.setBlockPlayerData(this.m_blockData, domPlayerData); this.bs.notifySceneBgChanged(); }, (e) => console.error(e)) ) } /** Toggle block background on UI checkbox selection @method _toggleBorder @param {event} e **/ _toggleSceneBackground(i_checked: boolean) { var domPlayerData = this.bs.getBlockPlayerData(this.m_blockData) var checked = i_checked == true ? 1 : 0; if (checked) { var xBgSnippet = this.bs.getCommonBackgroundXML(); var data = jXML(domPlayerData).find('Data').eq(0); jXML(data).find('Background').remove(); jXML(data).append(jXML(xBgSnippet)); this.bs.setBlockPlayerData(this.m_blockData, domPlayerData); this._sceneBackgroundPopulate(); } else { var xSnippet = this._findGradientPointsScene(domPlayerData); jXML(xSnippet).empty(); this.bs.setBlockPlayerData(this.m_blockData, domPlayerData); } this.bs.notifySceneBgChanged() } /** Toggle block background on UI checkbox selection @method _toggleBorder @param {event} e **/ _toggleBorder(i_checked: boolean) { var domPlayerData = this.bs.getBlockPlayerData(this.m_blockData) var checked = i_checked == true ? 1 : 0; if (checked) { var xBgSnippet = this.bs.getCommonBorderXML(); var data = jXML(domPlayerData).find('Data').eq(0); var bgData: any = this._findBorder(data); if (bgData.length > 0 && !_.isUndefined(bgData.replace)) { // ie bug workaround bgData.replace(jXML(xBgSnippet)); } else { jXML(data).append(jXML(xBgSnippet)); } this.bs.setBlockPlayerData(this.m_blockData, domPlayerData); } else { var xSnippet = this._findBorder(domPlayerData); jXML(xSnippet).remove(); this.bs.setBlockPlayerData(this.m_blockData, domPlayerData); } } /** On changes in msdb model updated UI common alpha properties @method _alphaPopulate **/ _alphaPopulate() { var domPlayerData = this.bs.getBlockPlayerData(this.m_blockData); var data = jXML(domPlayerData).find('Data').eq(0); var xSnippet = jXML(data).find('Appearance').eq(0); var a1: any = jXML(xSnippet).attr('alpha'); if (_.isUndefined(a1)) a1 = 1; this.formInputs['alpha'].setValue(a1) } _onAlphaChange(event) { var domPlayerData = this.bs.getBlockPlayerData(this.m_blockData); var data = jXML(domPlayerData).find('Data').eq(0); var xSnippet = jXML(data).find('Appearance').eq(0); jXML(xSnippet).attr('alpha', event.target.value); this.bs.setBlockPlayerData(this.m_blockData, domPlayerData); this.bs.notifySceneBlockChanged(this.m_blockData) } _sceneBackgroundPropsPopulate() { var self = this; var domPlayerData = this.bs.getBlockPlayerData(this.m_blockData) var xSnippet = self._findBorder(domPlayerData); if (xSnippet.length > 0) { var color = jXML(xSnippet).attr('borderColor'); this._updateBorderColor(true, color) } else { this._updateBorderColor(false, '16777215') } } /** On changes in msdb model updated UI common border properties @method _borderPropsPopulate **/ _borderPropsPopulate() { var self = this; var domPlayerData = this.bs.getBlockPlayerData(this.m_blockData) var xSnippet = self._findBorder(domPlayerData); if (xSnippet.length > 0) { var color = jXML(xSnippet).attr('borderColor'); this._updateBorderColor(true, color) } else { this._updateBorderColor(false, '16777215') } } @timeout(50) _sceneBackgroundPopulate() { if (!this.m_isPropsForScene) return; var domPlayerData = this.bs.getBlockPlayerData(this.m_blockData) var xPoints = this._findGradientPointsScene(domPlayerData); var color = jXML(xPoints).find('Point').attr('color'); if (_.isUndefined(color)) return this.formInputs['sceneBackground'].setValue(false) this.formInputs['sceneBackground'].setValue(true); color = '#' + Lib.DecimalToHex(color); this.m_sceneBackgroundColor = color; this.cd.markForCheck(); } @timeout(50) _updateBorderColor(i_value, i_color) { this.formInputs['border'].setValue(i_value); this.m_color = '#' + Lib.DecimalToHex(i_color); // this.formInputs['border_input'].setValue(this.m_color); this.cd.markForCheck(); } @timeout(50) _updateSceneBackgroundColor(i_value, i_color) { this.formInputs['sceneBackground'].setValue(i_value); this.m_color = '#' + Lib.DecimalToHex(i_color); this.cd.markForCheck(); } /** Find the border section in player_data for selected block @method _findBorder @param {object} i_domPlayerData @return {Xml} xSnippet **/ _findBorder(i_domPlayerData) { return jXML(i_domPlayerData).find('Border'); } /** Load jXML gradient component once @method _bgGradientWidgetInit **/ _bgGradientWidgetInit() { var self = this; var lazyUpdateBgColor = _.debounce(function (points, styles) { if (points.length == 0) return; self._gradientChanged({points: points, styles: styles}) // console.log('gradient 1...' + Math.random()); }, 50); var gradientColorPickerClosed = function () { // console.log('gradient 2'); }; jQuery('#bgColorGradientSelector', self.el.nativeElement).gradientPicker({ change: lazyUpdateBgColor, closed: gradientColorPickerClosed, fillDirection: "90deg" }); // always close gradient color picker on mouseout // jXML('.colorpicker').on('mouseleave', function (e) { // jXML(document).trigger('mousedown'); // console.log('gradient 3'); // }); } /** On changes in msdb model updated UI common gradient background properties @method _gradientPopulate **/ _gradientPopulate() { var self = this; var gradient = jXML('#bgColorGradientSelector', self.el.nativeElement).data("gradientPicker-sel"); // gradient.changeFillDirection("top"); /* change direction future support */ this._bgGradientWidgetClear(); var domPlayerData = self.m_blockData.playerDataDom; var xSnippet = self._findGradientPoints(domPlayerData); if (xSnippet.length > 0) { var points = jXML(xSnippet).find('Point'); $.each(points, function (i, point) { var pointColor = Lib.DecimalToHex(jXML(point).attr('color')); var pointMidpoint = (parseInt(jXML(point).attr('midpoint')) / 250); gradient.addPoint(pointMidpoint, pointColor, true); }); } } _bgGradientWidgetClear() { var gradient = jQuery('#bgColorGradientSelector', this.el.nativeElement).data("gradientPicker-sel"); gradient.removeAllPoints(); gradient.clear(); } _gradientChanged(e) { var self = this; var points: any = e.points; var styles = e.styles; if (points.length == 0) return; var domPlayerData = self.m_blockData.playerDataDom; jXML(domPlayerData).find('Background').remove(); var pointsXML = ""; for (var i = 0; i < points.length; ++i) { var pointMidpoint: any = (points[i].position * 250); var color = Lib.ColorToDecimal(points[i].color); var xPoint = ''; // log(xPoint); // jXML(gradientPoints).append(xPoint); pointsXML += xPoint; } var xPointsSnippet = jXML.parseXML(this.bs.getCommonBackgroundXML()); jXML(xPointsSnippet).find('GradientPoints').empty().append(pointsXML); var newGradientPoints = (new XMLSerializer()).serializeToString(xPointsSnippet); jXML(domPlayerData).find('Data').append(newGradientPoints) this.bs.setBlockPlayerData(this.m_blockData, domPlayerData); } _findGradientPoints(i_domPlayerData) { var xSnippet = jXML(i_domPlayerData).find('GradientPoints'); return xSnippet; } _findGradientPointsScene(i_domPlayerData) { var xBackground = jXML(i_domPlayerData).find('Layout').eq(0).siblings().filter('Background'); var xSnippet = jXML(xBackground).find('GradientPoints').eq(0); return xSnippet; } _onRemoveBackgroundClicked() { this._bgGradientWidgetClear(); var domPlayerData = this.m_blockData.playerDataDom; var gradientPoints = this._findGradientPoints(domPlayerData); jXML(gradientPoints).empty(); this.bs.setBlockPlayerData(this.m_blockData, domPlayerData); } _findBackground(i_domPlayerData) { var xSnippet = jXML(i_domPlayerData).find('Background'); return xSnippet; } destroy() { var gradient = jXML('#bgColorGradientSelector', this.el.nativeElement).data("gradientPicker-sel"); gradient.destroyed(); } } ================================================ FILE: src/app/blocks/block-prop-container.ts ================================================ import {AfterViewInit, ChangeDetectorRef, Component, Inject, ViewChild} from "@angular/core"; import {Compbaser} from "ng-mslib"; import {YellowPepperService} from "../../services/yellowpepper.service"; import {BlockService, IBlockData, ISceneData} from "./block-service"; import {CampaignTimelineChanelPlayersModel} from "../../store/imsdb.interfaces_auto"; import {ColorPickerService} from "ngx-color-picker"; import {Tab} from "../../comps/tabs/tab"; import {BlockLabels, PLACEMENT_CHANNEL, PLACEMENT_SCENE} from "../../interfaces/Consts"; @Component({ selector: 'block-prop-container', template: ` {{me}}

    no block prop found, new?

    {{m_blockTypeSelected}}
    `, }) export class BlockPropContainer extends Compbaser implements AfterViewInit { m_blockTypeSelected: string = 'none'; m_blockLabels = BlockLabels; m_blockData: IBlockData; m_tabTitle: string = 'none'; m_showSettingsTab = true; constructor(@Inject('BLOCK_PLACEMENT') private blockPlacement: string, private yp: YellowPepperService, private bs: BlockService, private cpService: ColorPickerService, private cd: ChangeDetectorRef) { super(); // console.log(blockPlacement); if (this.blockPlacement == PLACEMENT_CHANNEL) this._listenOnChannels(); if (this.blockPlacement == PLACEMENT_SCENE) this._listenOnScenes(); } @ViewChild('settings') settings: Tab; ngAfterViewInit() { this.toggleSettingsTab(); } private _listenOnChannels() { this.cancelOnDestroy( // this.yp.listenBlockChannelSelectedOrChanged() .mergeMap((i_campaignTimelineChanelPlayersModel: CampaignTimelineChanelPlayersModel) => { return this.bs.getBlockData(i_campaignTimelineChanelPlayersModel.getCampaignTimelineChanelPlayerId()) }) .subscribe((blockData: IBlockData) => { this.m_blockTypeSelected = blockData.blockCode; this.m_tabTitle = blockData.blockAcronym; this.m_blockData = blockData; // for json based scenes show the settings, unless its the actual Json Player which we don't if (blockData.playerMimeScene && this.m_blockTypeSelected != '4300') { this.m_showSettingsTab = true; this.toggleSettingsTab(); } else { this.m_showSettingsTab = false; this.toggleSettingsTab(); } }, (e) => console.error(e)) ) } private _listenOnScenes() { this.cancelOnDestroy( // this.yp.listenSceneOrBlockSelectedChanged() .mergeMap((i_sceneData: ISceneData) => { return this.bs.getBlockDataInScene(i_sceneData) }) .subscribe((blockData: IBlockData) => { this.m_blockTypeSelected = blockData.blockCode; this.m_tabTitle = blockData.blockAcronym; this.m_blockData = blockData; this.m_showSettingsTab = false; this.toggleSettingsTab(); this.cd.markForCheck(); }, (e) => console.error(e)) ) } // private getTabTitle(i_blockData: IBlockData): string { // // this.bs.getCommonBorderXML()if (i_blockData.blockAcronym.indexOf('JSON')) > -1 ? blockData.playerMimeScene : blockData.blockAcronym // } private toggleSettingsTab() { if (!this.settings) return; this.settings.show = this.m_showSettingsTab; } ngOnInit() { } destroy() { } } ================================================ FILE: src/app/blocks/block-prop-fasterq.ts ================================================ import {AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, Input} from "@angular/core"; import {FormBuilder, FormControl, FormGroup} from "@angular/forms"; import {BlockService, IBlockData} from "./block-service"; import {Compbaser} from "ng-mslib"; import * as _ from "lodash"; import {Subject} from "rxjs"; import {timeout} from "../../decorators/timeout-decorator"; import {Lib} from "../../Lib"; @Component({ selector: 'block-prop-fasterq', host: { '(input-blur)': 'saveToStore($event)' }, styles: [` .offSet { position: relative; top: 5px; } button { margin: 5px; height: 30px; } .colorPicker { width: 20px; float: left; display: inline-block; margin: 0 10px 0 0; padding: 15px 45px; border-radius: 0; border: 1px solid gray; padding: 10px 20px 10px 20px; background: #21ff2e; text-decoration: none; } `], changeDetection: ChangeDetectionStrategy.OnPush, template: ` {{me}}
    customer lines


    ` }) export class BlockPropFasterQ extends Compbaser implements AfterViewInit { formInputs = {}; m_contGroup: FormGroup; m_blockData: IBlockData; m_borderColorChanged = new Subject(); m_moveColorPickerOnce = false; m_color = '#ffffff'; constructor(private fb: FormBuilder, private el: ElementRef, private bs: BlockService, private cd: ChangeDetectorRef) { super(); this.m_contGroup = fb.group({ 'lineID1': [''], 'lineID2': [''], 'lineID3': [''], 'lineID4': [''], 'lineID5': [''] }); _.forEach(this.m_contGroup.controls, (value, key: string) => { this.formInputs[key] = this.m_contGroup.controls[key] as FormControl; }) this._listenColorChanged(); } @Input() set setBlockData(i_blockData) { if (this.m_blockData && this.m_blockData.blockID != i_blockData.blockID) { this.m_blockData = i_blockData; this._render(); } else { this.m_blockData = i_blockData; } } _listenColorChanged() { this.cancelOnDestroy( // this.m_borderColorChanged .debounceTime(500) .distinct() .skip(1) .subscribe((i_color) => { this.m_color = String(i_color); this.saveToStore(); }, (e) => console.error(e)) ) } ngAfterViewInit() { this._render(); } _render() { var domPlayerData = this.m_blockData.playerDataDom var xWebKit = jXML(domPlayerData).find('Webkit'); var xWebKitData = jXML(xWebKit).find('Data'); this.formInputs['lineID1'].setValue(jXML(xWebKitData).attr('lineID1')); this.formInputs['lineID2'].setValue(jXML(xWebKitData).attr('lineID2')); this.formInputs['lineID3'].setValue(jXML(xWebKitData).attr('lineID3')); this.formInputs['lineID4'].setValue(jXML(xWebKitData).attr('lineID4')); this.formInputs['lineID5'].setValue(jXML(xWebKitData).attr('lineID5')); this.setNewColor(jXML(xWebKitData).attr('bgColor')); // this.m_color = Lib.ColorToHex(Lib.DecimalToHex(jXML(xWebKitData).attr('bgColor'))); } @timeout() _moveColorPicker() { console.log(this.m_color); if (this.m_moveColorPickerOnce) return; this.m_moveColorPickerOnce = true; jXML(".color-picker", this.el.nativeElement).css("left", "+=100"); } @timeout(50) private setNewColor(i_color) { if (_.isNaN(Lib.ColorToDecimal(i_color))) i_color = '#ffffff'; this.m_color = i_color this.cd.markForCheck(); } private saveToStore() { // console.log(this.m_contGroup.status + ' ' + JSON.stringify(this.ngmslibService.cleanCharForXml(this.m_contGroup.value))); if (this.m_contGroup.status != 'VALID') return; var domPlayerData = this.m_blockData.playerDataDom; var xWebKit = jXML(domPlayerData).find('Webkit'); var xWebKitData = jXML(xWebKit).find('Data'); jXML(xWebKitData).attr('lineID1', this.formInputs['lineID1'].value); jXML(xWebKitData).attr('lineID2', this.formInputs['lineID2'].value); jXML(xWebKitData).attr('lineID3', this.formInputs['lineID3'].value); jXML(xWebKitData).attr('lineID4', this.formInputs['lineID4'].value); jXML(xWebKitData).attr('lineID5', this.formInputs['lineID5'].value); jXML(xWebKitData).attr('bgColor', this.m_color); this.bs.setBlockPlayerData(this.m_blockData, domPlayerData); } destroy() { } } ================================================ FILE: src/app/blocks/block-prop-html.ts ================================================ import {AfterViewInit, ChangeDetectionStrategy, Component, Input} from "@angular/core"; import {FormBuilder, FormControl, FormGroup, Validators} from "@angular/forms"; import {BlockService, IBlockData} from "./block-service"; import {RedPepperService} from "../../services/redpepper.service"; import {Compbaser, NgmslibService} from "ng-mslib"; import {urlRegExp} from "../../Lib"; import * as _ from "lodash"; @Component({ selector: 'block-prop-html', host: { '(input-blur)': 'saveToStore($event)' }, changeDetection: ChangeDetectionStrategy.OnPush, template: ` {{me}}
    • url
    ` }) export class BlockPropHtml extends Compbaser implements AfterViewInit { private formInputs = {}; m_contGroup: FormGroup; private m_blockData: IBlockData; constructor(private fb: FormBuilder, private rp: RedPepperService, private bs: BlockService, private ngmslibService: NgmslibService) { super(); this.m_contGroup = fb.group({ 'url': ['', [Validators.pattern(urlRegExp)]] }); _.forEach(this.m_contGroup.controls, (value, key: string) => { this.formInputs[key] = this.m_contGroup.controls[key] as FormControl; }) } @Input() set setBlockData(i_blockData) { if (this.m_blockData && this.m_blockData.blockID != i_blockData.blockID) { this.m_blockData = i_blockData; this._render(); } else { this.m_blockData = i_blockData; } } ngAfterViewInit() { this._render(); } _render() { this.m_contGroup.reset(); var domPlayerData = this.m_blockData.playerDataDom var xSnippet = jXML(domPlayerData).find('HTML'); this.formInputs['url'].setValue(xSnippet.attr('src')); } private saveToStore() { // console.log(this.m_contGroup.status + ' ' + JSON.stringify(this.ngmslibService.cleanCharForXml(this.m_contGroup.value))); if (this.m_contGroup.status != 'VALID') return; var domPlayerData = this.m_blockData.playerDataDom; var xSnippet = jXML(domPlayerData).find('HTML'); xSnippet.attr('src', this.m_contGroup.value.url); this.bs.setBlockPlayerData(this.m_blockData, domPlayerData); } destroy() { } } ================================================ FILE: src/app/blocks/block-prop-image.ts ================================================ import {AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, Input} from "@angular/core"; import {FormBuilder, FormControl, FormGroup, Validators} from "@angular/forms"; import {BlockService, IBlockData} from "./block-service"; import {Compbaser, NgmslibService} from "ng-mslib"; import * as _ from "lodash"; import {urlRegExp} from "../../Lib"; @Component({ selector: 'block-prop-image', host: {'(input-blur)': 'saveToStore($event)'}, changeDetection: ChangeDetectionStrategy.OnPush, template: ` {{me}}
    • url
    • maintain aspect ratio
    ` }) export class BlockPropImage extends Compbaser implements AfterViewInit { m_formInputs = {}; m_contGroup: FormGroup; m_blockData: IBlockData; constructor(private fb: FormBuilder, private cd: ChangeDetectorRef, private bs: BlockService, private ngmslibService: NgmslibService) { super(); this.m_contGroup = fb.group({ 'url': ['', [Validators.pattern(urlRegExp)]], 'maintain': [] }); _.forEach(this.m_contGroup.controls, (value, key: string) => { this.m_formInputs[key] = this.m_contGroup.controls[key] as FormControl; }) } @Input() set setBlockData(i_blockData) { if (this.m_blockData && this.m_blockData.blockID != i_blockData.blockID) { this.m_blockData = i_blockData; this._render(); } else { this.m_blockData = i_blockData; } } @Input() external: boolean = false; /** Toggle maintain aspect ratio **/ _toggleAspectRatio(i_value) { i_value = StringJS(i_value).booleanToNumber() var domPlayerData = this.m_blockData.playerDataDom; var xSnippet = jXML(domPlayerData).find('AspectRatio'); jXML(xSnippet).attr('maintain', i_value); this.bs.setBlockPlayerData(this.m_blockData, domPlayerData) } private _render() { var domPlayerData: XMLDocument = this.m_blockData.playerDataDom if (this.external) { var xSnippet = jXML(domPlayerData).find('LINK'); this.m_formInputs['url'].setValue(xSnippet.attr('src')); } else { var xSnippet = jXML(domPlayerData).find('AspectRatio'); var maintain = StringJS(jXML(xSnippet).attr('maintain')).booleanToNumber(); this.m_formInputs['maintain'].setValue(maintain); } } ngAfterViewInit() { this._render(); } private saveToStore() { con(this.m_contGroup.status + ' ' + JSON.stringify(this.ngmslibService.cleanCharForXml(this.m_contGroup.value))); if (this.m_contGroup.status != 'VALID') return; var domPlayerData: XMLDocument = this.m_blockData.playerDataDom; var xSnippet = jXML(domPlayerData).find('LINK'); xSnippet.attr('src', this.m_contGroup.value.url); this.bs.setBlockPlayerData(this.m_blockData, domPlayerData); } destroy() { } } ================================================ FILE: src/app/blocks/block-prop-instagram.ts ================================================ import {AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, Input} from "@angular/core"; import {FormBuilder, FormControl, FormGroup, Validators} from "@angular/forms"; import {BlockService, IBlockData} from "./block-service"; import {Compbaser, NgmslibService} from "ng-mslib"; import * as _ from "lodash"; @Component({ selector: 'block-prop-instagram', host: {'(input-blur)': 'saveToStore($event)'}, changeDetection: ChangeDetectionStrategy.OnPush, template: ` {{me}}
    • token
    ` }) export class BlockPropInstagram extends Compbaser implements AfterViewInit { m_formInputs = {}; m_contGroup: FormGroup; m_blockData: IBlockData; constructor(private fb: FormBuilder, private cd: ChangeDetectorRef, private bs: BlockService, private ngmslibService: NgmslibService) { super(); this.m_contGroup = fb.group({ 'token': [''] }); _.forEach(this.m_contGroup.controls, (value, key: string) => { this.m_formInputs[key] = this.m_contGroup.controls[key] as FormControl; }) } @Input() jsonMode: boolean; @Input() set setBlockData(i_blockData) { if (this.m_blockData && this.m_blockData.blockID != i_blockData.blockID) { this.m_blockData = i_blockData; this._render(); } else { this.m_blockData = i_blockData; } } private _render() { var domPlayerData: XMLDocument = this.m_blockData.playerDataDom var jXMLdata = jXML(domPlayerData).find('Json').find('Data'); this.m_formInputs['token'].setValue(jXMLdata.attr('token')); // this.cd.markForCheck(); } ngAfterViewInit() { this._render(); } _onCreateToken() { var win = window.open('http://instagram.signage.me', '_blank'); if (win) { win.focus(); } else { bootbox.alert('Browser popups are blocked, please enable and try again'); } } private saveToStore() { con(this.m_contGroup.status + ' ' + JSON.stringify(this.ngmslibService.cleanCharForXml(this.m_contGroup.value))); if (this.m_contGroup.status != 'VALID') return; var domPlayerData: XMLDocument = this.m_blockData.playerDataDom; var item = jXML(domPlayerData).find('Json').find('Data'); jXML(item).attr('token', this.m_contGroup.value.token); this.bs.setBlockPlayerData(this.m_blockData, domPlayerData); } destroy() { } } ================================================ FILE: src/app/blocks/block-prop-json-item.ts ================================================ import {AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, Input} from "@angular/core"; import {FormBuilder, FormControl, FormGroup} from "@angular/forms"; import {BlockService, IBlockData} from "./block-service"; import {Compbaser, NgmslibService} from "ng-mslib"; import * as _ from "lodash"; import {IFontSelector} from "../../comps/font-selector/font-selector"; import {Lib} from "../../Lib"; @Component({ selector: 'block-prop-json-item', // changeDetection: ChangeDetectionStrategy.OnPush, template: ` {{me}}
    Json path notation

    maintain aspect ratio
    ` }) export class BlockPropJsonItem extends Compbaser implements AfterViewInit { m_formInputs = {}; m_contGroup: FormGroup; m_blockData: IBlockData; m_sheetList = []; m_sheetSeleced: any = {}; m_fields = []; m_dateFields = []; m_fontConfig: IFontSelector; jsonItemFieldContainer; jsonItemTextFieldsContainer; jsonItemDualNumericSettings; jsonItemIconSettings; jsonItemDateSettings; jsonItemFontSettings; constructor(private fb: FormBuilder, private cd: ChangeDetectorRef, private bs: BlockService, private ngmslibService: NgmslibService) { super(); this.m_contGroup = fb.group({ 'jsonItemField': [''], 'jsonItemTextFields': [''], 'jsonItemDualNumeric1': [''], 'jsonItemDualNumeric2': [''], 'jsonItemDateFormat': [''], 'jsonItemMaintainAspectRatio': [''] }); _.forEach(this.m_contGroup.controls, (value, key: string) => { this.m_formInputs[key] = this.m_contGroup.controls[key] as FormControl; }) } @Input() set setBlockData(i_blockData) { if (this.m_blockData && this.m_blockData.blockID != i_blockData.blockID) { this.m_blockData = i_blockData; this._render(); } else { this.m_blockData = i_blockData; } } private _render() { var domPlayerData: XMLDocument = this.m_blockData.playerDataDom var jXMLdata = jXML(domPlayerData).find('Json').find('Data'); var mode = jXMLdata.attr('mode'); // JSON item (no mime) if (_.isUndefined(this.m_blockData.playerMimeScene)) { var domPlayerData = this.m_blockData.playerDataDom; var xSnippet = jXML(domPlayerData).find('XmlItem'); var xSnippetFont = jXML(xSnippet).find('Font'); var fieldName = jXML(xSnippet).attr('fieldName'); this.jsonItemFieldContainer = true; this.jsonItemTextFieldsContainer = false; this.jsonItemDualNumericSettings = false; this.jsonItemIconSettings = false; this.jsonItemDateSettings = false; this.jsonItemFontSettings = true; this.m_formInputs['jsonItemField'].setValue(fieldName); this._populateFonts(xSnippetFont) } else { // Json mime this._populateMimeType(); } } _toggleAspectRatio(i_value) { i_value = StringJS(i_value).booleanToNumber() var domPlayerData = this.m_blockData.playerDataDom; var xSnippet = jXML(domPlayerData).find('XmlItem'); jXML(xSnippet).attr('maintainAspectRatio', i_value); this.bs.setBlockPlayerData(this.m_blockData, domPlayerData) } _onJsonItemTextFieldChanged(i_event) { var domPlayerData = this.m_blockData.playerDataDom; var xSnippet = jXML(domPlayerData).find('XmlItem'); jXML(xSnippet).attr('fieldName', i_event.target.value); this.bs.setBlockPlayerData(this.m_blockData, domPlayerData) } _onJsonItemTextFieldsChanged(event) { var name = event.target.value; var mime = this.m_blockData.playerMimeScene; var domPlayerData: XMLDocument = this.m_blockData.playerDataDom; var xSnippet = jXML(domPlayerData).find('XmlItem'); _.forEach(this.m_config[mime].fields, (k:any) => { if (k.name == name) { jXML(xSnippet).attr('fieldType', k.type); jXML(xSnippet).attr('fieldName', k.name); this.bs.setBlockPlayerData(this.m_blockData, domPlayerData); this._populateMimeType(); } }) } _onJsonItemDateFieldsChanged(event) { var value = event.target.value; var domPlayerData: XMLDocument = this.m_blockData.playerDataDom; var xSnippet = jXML(domPlayerData).find('XmlItem'); jXML(xSnippet).attr('dateFormat', value); this.bs.setBlockPlayerData(this.m_blockData, domPlayerData); this._populateMimeType(); } _onDualNumericChanged(){ var domPlayerData: XMLDocument = this.m_blockData.playerDataDom; var xSnippet = jXML(domPlayerData).find('XmlItem'); var row = this.m_contGroup.value.jsonItemDualNumeric1; var column = this.m_contGroup.value.jsonItemDualNumeric2; var fieldName = `$cells.${row}.${column}.value`; jXML(xSnippet).attr('fieldName', fieldName); this.bs.setBlockPlayerData(this.m_blockData, domPlayerData); } _onFontChanged(config: IFontSelector) { var domPlayerData = this.m_blockData.playerDataDom; var xSnippet = jXML(domPlayerData).find('XmlItem'); var xSnippetFont = jXML(xSnippet).find('Font'); config.bold == true ? xSnippetFont.attr('fontWeight', 'bold') : xSnippetFont.attr('fontWeight', 'normal'); config.italic == true ? xSnippetFont.attr('fontStyle', 'italic') : xSnippetFont.attr('fontStyle', 'normal'); config.underline == true ? xSnippetFont.attr('textDecoration', 'underline') : xSnippetFont.attr('textDecoration', 'none'); xSnippetFont.attr('fontColor', Lib.ColorToDecimal(config.color)); xSnippetFont.attr('fontSize', config.size); xSnippetFont.attr('fontFamily', config.font); xSnippetFont.attr('textAlign', config.alignment); this.bs.setBlockPlayerData(this.m_blockData, domPlayerData); } /** The component is a subclass of JSON item (i.e.: it has a mimetype) so we need to populate it according to its mimetype config options **/ private _populateMimeType() { var self = this; var domPlayerData = this.m_blockData.playerDataDom; var xSnippet = jXML(domPlayerData).find('XmlItem'); var xSnippetFont = jXML(xSnippet).find('Font'); var fieldType = jXML(xSnippet).attr('fieldType'); var fieldName = jXML(xSnippet).attr('fieldName'); var maintainAspectRatio = jXML(xSnippet).attr('maintainAspectRatio'); var dateFormat = jXML(xSnippet).attr('dateFormat'); this.jsonItemFieldContainer = false; this.jsonItemTextFieldsContainer = true; this.m_fields = [{type: '', value: '', label: 'no field selected'}]; var fields: any = self.m_config[this.m_blockData.playerMimeScene].fields; _.each(fields, (k: any) => this.m_fields.push({type: k.type, value: k.name, label: k.label})); this.m_formInputs['jsonItemTextFields'].setValue(fieldName) // populate according to filed type (text/resource) switch (fieldType) { case 'resource': { this.jsonItemFontSettings = false; this.jsonItemDateSettings = false; this.jsonItemIconSettings = true; self._populateAspectRatio(maintainAspectRatio); break; } case 'date': { self._populateDateFormat(dateFormat); this._populateFonts(xSnippetFont); this.jsonItemDateSettings = true; break; } case 'dual_numeric': { } case 'text': { this.jsonItemIconSettings = false; this.jsonItemDateSettings = false; this.jsonItemFontSettings = true; this._populateFonts(xSnippetFont); break; } } // populate according to mimetype exception or default behavior switch (this.m_blockData.playerMimeScene) { case 'Json.spreadsheet': { this.jsonItemDualNumericSettings = true; self._populateDualNumeric(); break; } default: { this.jsonItemDualNumericSettings = false; } } this.cd.markForCheck(); this.cd.detectChanges(); } private _populateFonts(xSnippetFont) { this.m_fontConfig = { bold: xSnippetFont.attr('fontWeight') === 'bold' ? true : false, italic: xSnippetFont.attr('fontStyle') === 'italic' ? true : false, underline: xSnippetFont.attr('textDecoration') === 'underline' ? true : false, alignment: xSnippetFont.attr('textAlign'), font: xSnippetFont.attr('fontFamily'), color: Lib.ColorToHex(Lib.DecimalToHex(xSnippetFont.attr('fontColor'))), size: Number(xSnippetFont.attr('fontSize')) }; // this.cd.markForCheck(); } /** Populate date format for common types of date styles on dropdown selection @method _populateDateFormat @param {string} i_selectedFormat **/ private _populateDateFormat(i_selectedFormat) { var formats = [ 'D/M/Y', 'DD/MM/YY', 'DD/MM/YYYY', 'DD/MMM/YY', 'MM/DD/YY', 'MM/DD/YYYY', 'MMM/DD/YYYY ', 'D/M/Y J:NN:SS', 'DD/MM/YY J:NN:SS', 'DD/MM/YYYY J:NN:SS', 'DD/MMM/YY J:NN:SS', 'MM/DD/YY J:NN:SS', 'MM/DD/YYYY J:NN:SS', 'MMM/DD/YYYY J:NN:SS', 'J:NN:SS', 'J:NN:SS A', 'J:NN:SS A', 'J:NN' ]; this.m_dateFields = [{value: 'select format'}]; for (var i = 0; i < formats.length; i++) { this.m_dateFields.push({value: formats[i]}); } this.m_formInputs['jsonItemDateFormat'].setValue(i_selectedFormat) } /** Populate aspect ratio switch button **/ private _populateAspectRatio(i_aspectRatio) { var value = StringJS(i_aspectRatio).booleanToNumber(); this.m_formInputs['jsonItemMaintainAspectRatio'].setValue(value) } /** Populate the dual numeric steppers that are used in components like the google sheets @method _populateDualNumeric **/ private _populateDualNumeric() { var row: string = '1'; var column: string = '1'; var domPlayerData = this.m_blockData.playerDataDom; var xSnippet = jXML(domPlayerData).find('XmlItem'); var fieldName = jXML(xSnippet).attr('fieldName'); var re = /cells.([0-9]+).([0-9]+).value/i; var match = fieldName.match(re); if (!_.isNull(match)) { row = String(match[1]); column = String(match[2]); } this.m_formInputs['jsonItemDualNumeric1'].setValue(row) this.m_formInputs['jsonItemDualNumeric2'].setValue(column) // var spinners = jXML('.spinner', Elements.JSON_ITEM_DUAL_NUMERIC_SETTINGS); // jXML(spinners[0]).spinner('value', row); // jXML(spinners[2]).spinner('value', column); } ngAfterViewInit() { this._render(); } m_config = { 'Json.instagram.feed': { title: 'Instagram', tabTitle: 'Posts', fields: { 1: { name: "title", type: "text", label: "title" }, 2: { name: "urlImage", type: "resource", label: "image" }, 3: { name: "video", type: "resource", label: "video" } } }, 'Json.twitter': { title: 'Twitter', tabTitle: 'Tweets', fields: { 1: { name: "name", type: "text", label: "name" }, 2: { name: "text", type: "text", label: "text" }, 3: { name: "screen_name", type: "text", label: "screen name" }, 4: { name: "created_at", type: "text", label: "created at" }, 5: { name: "profile_background_image_url", type: "resource", label: "Background image" }, 6: { name: "profile_image_url", type: "resource", label: "Image" } } }, 'Json.digg': { title: 'Digg', tabTitle: 'Posts', fields: { 1: { name: "title", type: "text", label: "title" }, 2: { name: "link", type: "resource", label: "image" } } }, 'Json.spreadsheet': { title: 'Spreadsheet', tabTitle: 'Cells', fields: { 1: { name: "$cells.1.1.value", type: "dual_numeric", label: "Sheet cell" } } }, 'Json.calendar': { title: 'Calendar', tabTitle: 'Date', fields: { 1: { name: "summary", type: "text", label: "summary" }, 2: { name: "description", type: "text", label: "description" }, 3: { name: "organizer", type: "text", label: "organizer" }, 4: { name: "organizerEmail", type: "text", label: "organizer email" }, 5: { name: "created", type: "text", label: "created" }, 6: { name: "startDateTime_time", type: "date", label: "start date time" }, 7: { name: "endDateTime_time", type: "date", label: "end date time" }, 8: { name: "updated", type: "text", label: "updated" } } }, 'Json.weather': { title: 'World weather', tabTitle: 'Conditions', fields: { 1: { name: "$[0].data.current_condition[0].iconPath", type: "resource", label: "current icon" }, 2: { name: "$[0].data.current_condition[0].temp_@", type: "text", label: "current temp" }, 3: { name: "$[0].data.current_condition[0].humidity", type: "text", label: "current humidity" }, 4: { name: "$[0].data.weather[0].iconPath", type: "resource", label: "today icon" }, 5: { name: "$[0].data.weather[0].mintemp@", type: "text", label: "today min temp" }, 6: { name: "$[0].data.weather[0].maxtemp@", type: "text", label: "today max temp" }, 7: { name: "$[0].data.weather[0].day", type: "text", label: "today label" }, 8: { name: "$[0].data.weather[1].iconPath", type: "resource", label: "today+1 icon" }, 9: { name: "$[0].data.weather[1].mintemp@", type: "text", label: "today+1 min temp" }, 10: { name: "$[0].data.weather[1].maxtemp@", type: "text", label: "today+1 max temp" }, 11: { name: "$[0].data.weather[1].day", type: "text", label: "today+1 label" }, 12: { name: "$[0].data.weather[2].iconPath", type: "resource", label: "today+2 icon" }, 13: { name: "$[0].data.weather[2].mintemp@", type: "text", label: "today+2 min temp" }, 14: { name: "$[0].data.weather[2].maxtemp@", type: "text", label: "today+2 max temp" }, 15: { name: "$[0].data.weather[2].day", type: "text", label: "today+2 label" }, 16: { name: "$[0].data.weather[3].iconPath", type: "resource", label: "today+3 icon" }, 17: { name: "$[0].data.weather[3].mintemp@", type: "text", label: "today+3 min temp" }, 18: { name: "$[0].data.weather[3].maxtemp@", type: "text", label: "today+3 max temp" }, 19: { name: "$[0].data.weather[3].day", type: "text", label: "today+3 label" }, 20: { name: "$[0].data.weather[4].iconPath", type: "resource", label: "today+4 icon" }, 21: { name: "$[0].data.weather[4].mintemp@", type: "text", label: "today+4 min temp" }, 22: { name: "$[0].data.weather[4].maxtemp@", type: "text", label: "today+4 max temp" }, 23: { name: "$[0].data.weather[4].day", type: "text", label: "today+4 label" }, 24: { name: "$[0].data.weather[5].iconPath", type: "resource", label: "today+5 icon" }, 25: { name: "$[0].data.weather[5].mintemp@", type: "text", label: "today+5 min temp" }, 26: { name: "$[0].data.weather[5].maxtemp@", type: "text", label: "today+5 max temp" }, 27: { name: "$[0].data.weather[5].day", type: "text", label: "today+5 label" }, 28: { name: "$[0].data.weather[6].iconPath", type: "resource", label: "today+6 icon" }, 29: { name: "$[0].data.weather[6].mintemp@", type: "text", label: "today+6 min temp" }, 30: { name: "$[0].data.weather[6].maxtemp@", type: "text", label: "today+6 max temp" }, 31: { name: "$[0].data.weather[6].day", type: "text", label: "today+6 label" } } } } destroy() { } } ================================================ FILE: src/app/blocks/block-prop-json-player.ts ================================================ import {AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, ViewChild} from "@angular/core"; import {FormBuilder, FormControl, FormGroup, Validators} from "@angular/forms"; import {BlockService, IBlockData} from "./block-service"; import {RedPepperService} from "../../services/redpepper.service"; import {Compbaser} from "ng-mslib"; import {urlRegExp} from "../../Lib"; import * as _ from "lodash"; import {YellowPepperService} from "../../services/yellowpepper.service"; import {Once} from "../../decorators/once-decorator"; import {SimpleGridTable} from "../../comps/simple-grid-module/SimpleGridTable"; @Component({ selector: 'block-prop-json-player', changeDetection: ChangeDetectionStrategy.OnPush, host: {'(input-blur)': 'saveToStore($event)'}, template: ` {{me}}
    • load with scene

    • play video to completion
    • random playback
    • slideshow
    ` }) export class BlockPropJsonPlayer extends Compbaser implements AfterViewInit { formInputs = {}; m_contGroup: FormGroup; m_blockData: IBlockData; m_sceneSelection = []; m_sceneSeleced: any = {}; m_slideShowMode = 0; constructor(private fb: FormBuilder, private yp: YellowPepperService, private rp: RedPepperService, private bs: BlockService, private cd: ChangeDetectorRef) { super(); this.m_contGroup = fb.group({ 'sceneSelection': [], 'randomOrder': [], 'slideShow': [], 'playVideoInFull': [], 'itemInterval': [], 'itemsPath': [], 'itemsUrl': ['', [Validators.pattern(urlRegExp)]], 'url': ['', [Validators.pattern(urlRegExp)]] }); _.forEach(this.m_contGroup.controls, (value, key: string) => { this.formInputs[key] = this.m_contGroup.controls[key] as FormControl; }) } @ViewChild('simpleGrid') simpleGrid: SimpleGridTable; @Input() standAlone: boolean = false; @Input() set setBlockData(i_blockData) { /** Disabled as in this component we wish to always update UI on block changes since we are addinf and removing elements to event grid and need to be updated if (this.m_blockData && this.m_blockData.blockID != i_blockData.blockID) { this.m_blockData = i_blockData; this._render(); } else { this.m_blockData = i_blockData; } **/ this.m_blockData = i_blockData; this._render(); } ngAfterViewInit() { this._render(); } _onPlayVideoInFull(i_value) { i_value = StringJS(i_value).booleanToNumber() var domPlayerData = this.m_blockData.playerDataDom; var xSnippet = jXML(domPlayerData).find('Json'); jXML(xSnippet).attr('playVideoInFull', i_value); this.bs.setBlockPlayerData(this.m_blockData, domPlayerData) } _onRandomPlay(i_value) { i_value = StringJS(i_value).booleanToNumber() var domPlayerData = this.m_blockData.playerDataDom; var xSnippet = jXML(domPlayerData).find('Json'); jXML(xSnippet).attr('randomOrder', i_value); this.bs.setBlockPlayerData(this.m_blockData, domPlayerData) } _onSlideShow(i_value) { i_value = StringJS(i_value).booleanToNumber() this.m_slideShowMode = i_value; var domPlayerData = this.m_blockData.playerDataDom; var xSnippet = jXML(domPlayerData).find('Json'); jXML(xSnippet).attr('slideShow', i_value); this.bs.setBlockPlayerData(this.m_blockData, domPlayerData) } _onSceneSelectionChanged(i_scene_id) { var domPlayerData = this.m_blockData.playerDataDom; var xSnippet = jXML(domPlayerData).find('Json'); var xSnippetPlayer = jXML(xSnippet).find('Player'); jXML(xSnippetPlayer).attr('hDataSrc', i_scene_id.value.id); this.bs.setBlockPlayerData(this.m_blockData, domPlayerData) } /** Populate the LI with all available scenes from msdb if the mimetype is empty (used for this class) we show all scenes in dropdown, but if mimetype exists (used by subclasses of this class) we filter dropdown list by matching mimetypes @method _populateSceneDropdown **/ @Once() private _initSceneDropdown() { var self = this; return this.yp.getSceneNames() .subscribe((scenes) => { this.m_sceneSelection = []; var domPlayerData = this.m_blockData.playerDataDom; var xSnippet = jXML(domPlayerData).find('Json'); var xSnippetPlayer = jXML(xSnippet).find('Player'); var selectedSceneID = jXML(xSnippetPlayer).attr('hDataSrc'); for (var scene in scenes) { var mimeType = scenes[scene].mimeType; var label = scenes[scene].label; var sceneId = scenes[scene].id; if (sceneId == selectedSceneID) { this.m_sceneSeleced = scenes[scene]; } // if this component is used as a standalone Json Player, include in drop down all possible scenes if (self.m_blockData.playerMimeScene == mimeType || this.standAlone) { this.m_sceneSelection.push({ sceneId, label, mimeType, value: scenes[scene] }) } } }, (e) => console.error(e)) } _render() { this._initSceneDropdown(); // this._initEventTable(); var domPlayerData = this.m_blockData.playerDataDom var xSnippet = jXML(domPlayerData).find('Json'); var playVideoInFull = StringJS(jXML(xSnippet).attr('playVideoInFull')).booleanToNumber(); this.formInputs['playVideoInFull'].setValue(playVideoInFull); var randomOrder = StringJS(jXML(xSnippet).attr('randomOrder')).booleanToNumber(); this.formInputs['randomOrder'].setValue(randomOrder); this.m_slideShowMode = StringJS(jXML(xSnippet).attr('slideShow')).booleanToNumber(true) as number; this.formInputs['slideShow'].setValue(this.m_slideShowMode); this.formInputs['itemsPath'].setValue(jXML(xSnippet).attr('itemsPath')); this.formInputs['itemInterval'].setValue(jXML(xSnippet).attr('itemInterval')); this.formInputs['itemsUrl'].setValue(jXML(xSnippet).attr('url')); } private saveToStore() { if (this.m_contGroup.status != 'VALID') return; var domPlayerData: XMLDocument = this.m_blockData.playerDataDom; var xSnippet = jXML(domPlayerData).find('Json'); xSnippet.attr('itemsPath', this.m_contGroup.value.itemsPath); xSnippet.attr('url', this.m_contGroup.value.itemsUrl); xSnippet.attr('itemInterval', this.m_contGroup.value.itemInterval); this.bs.setBlockPlayerData(this.m_blockData, domPlayerData); } destroy() { } } ================================================ FILE: src/app/blocks/block-prop-label.ts ================================================ import {AfterViewInit, ChangeDetectionStrategy, Component, Input} from "@angular/core"; import {FormBuilder, FormControl, FormGroup, Validators} from "@angular/forms"; import {BlockService, IBlockData} from "./block-service"; import {RedPepperService} from "../../services/redpepper.service"; import {Compbaser, NgmslibService} from "ng-mslib"; import {Lib, urlRegExp} from "../../Lib"; import * as _ from "lodash"; import {IFontSelector} from "../../comps/font-selector/font-selector"; @Component({ selector: 'block-prop-label', host: { '(input-blur)': 'saveToStore($event)' }, changeDetection: ChangeDetectionStrategy.OnPush, template: ` {{me}}
    ` }) export class BlockPropLabel extends Compbaser implements AfterViewInit { private formInputs = {}; m_fontConfig: IFontSelector; m_contGroup: FormGroup; m_blockData: IBlockData; constructor(private fb: FormBuilder, private rp: RedPepperService, private bs: BlockService, private ngmslibService: NgmslibService) { super(); this.m_contGroup = fb.group({ 'text': [''] }); _.forEach(this.m_contGroup.controls, (value, key: string) => { this.formInputs[key] = this.m_contGroup.controls[key] as FormControl; }) } @Input() set setBlockData(i_blockData) { if (this.m_blockData && this.m_blockData.blockID != i_blockData.blockID) { this.m_blockData = i_blockData; this._render(); } else { this.m_blockData = i_blockData; } } ngAfterViewInit() { this._render(); } _render() { var domPlayerData = this.m_blockData.playerDataDom var xSnippetLabel = jXML(domPlayerData).find('Label'); var xSnippetText = jXML(xSnippetLabel).find('Text'); var xSnippetFont = jXML(xSnippetLabel).find('Font'); this.m_fontConfig = { bold: xSnippetFont.attr('fontWeight') == 'bold' ? true : false, italic: xSnippetFont.attr('fontStyle') == 'italic' ? true : false, underline: xSnippetFont.attr('textDecoration') == 'underline' ? true : false, alignment: xSnippetFont.attr('textAlign') as any, font: xSnippetFont.attr('fontFamily'), color: Lib.ColorToHex(Lib.DecimalToHex(xSnippetFont.attr('fontColor'))), size: parseInt(xSnippetFont.attr('fontSize')) }; this.formInputs['text'].setValue(xSnippetText.text()); } _onFontChanged(config: IFontSelector) { var domPlayerData = this.m_blockData.playerDataDom; var xSnippet = jXML(domPlayerData).find('Label'); var xSnippetFont = jXML(xSnippet).find('Font'); config.bold == true ? xSnippetFont.attr('fontWeight', 'bold') : xSnippetFont.attr('fontWeight', 'normal'); config.italic == true ? xSnippetFont.attr('fontStyle', 'italic') : xSnippetFont.attr('fontStyle', 'normal'); config.underline == true ? xSnippetFont.attr('textDecoration', 'underline') : xSnippetFont.attr('textDecoration', 'none'); xSnippetFont.attr('fontColor', Lib.ColorToDecimal(config.color)); xSnippetFont.attr('fontSize', config.size); xSnippetFont.attr('fontFamily', config.font); xSnippetFont.attr('textAlign', config.alignment); this.bs.setBlockPlayerData(this.m_blockData, domPlayerData); } private saveToStore() { // console.log(this.m_contGroup.status + ' ' + JSON.stringify(this.ngmslibService.cleanCharForXml(this.m_contGroup.value))); if (this.m_contGroup.status != 'VALID') return; var text = this.formInputs['text'].value; text = Lib.CleanProbCharacters(text, 1); var domPlayerData = this.m_blockData.playerDataDom; var xSnippet = jXML(domPlayerData).find('Label'); var xSnippetText = jXML(xSnippet).find('Text'); if (text != xSnippetText.text()){ jXML(xSnippetText).text(text); } var xSnippet = jXML(domPlayerData).find('HTML'); xSnippet.attr('src', this.formInputs['text'].value); this.bs.setBlockPlayerData(this.m_blockData, domPlayerData); } destroy() { } } ================================================ FILE: src/app/blocks/block-prop-location.ts ================================================ import {AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, Inject, Input, ViewChild} from "@angular/core"; import {FormBuilder, FormControl, FormGroup} from "@angular/forms"; import {BlockService, IBlockData} from "./block-service"; import {Compbaser} from "ng-mslib"; import * as _ from "lodash"; import {List} from "immutable"; import {ISimpleGridEdit} from "../../comps/simple-grid-module/SimpleGrid"; import {StoreModel} from "../../store/model/StoreModel"; import {SimpleGridRecord} from "../../comps/simple-grid-module/SimpleGridRecord"; import {SimpleGridTable} from "../../comps/simple-grid-module/SimpleGridTable"; import {ISimpleGridDraggedData} from "../../comps/simple-grid-module/SimpleGridDraggable"; import {Lib} from "../../Lib"; import {ModalComponent} from "ng2-bs3-modal/ng2-bs3-modal"; import {IAddContents} from "../../interfaces/IAddContent"; import {BlockLabels, PLACEMENT_LISTS, PLACEMENT_SCENE} from "../../interfaces/Consts"; import {RedPepperService} from "../../services/redpepper.service"; import {IUiState} from "../../store/store.data"; import {ACTION_UISTATE_UPDATE} from "../../store/actions/appdb.actions"; import {YellowPepperService} from "../../services/yellowpepper.service"; import {LocationMarkModel} from "../../models/LocationMarkModel"; @Component({ selector: 'block-prop-location', host: { '(input-blur)': 'saveToStore($event)' }, changeDetection: ChangeDetectionStrategy.OnPush, template: ` {{me}}
  • name seconds order


    • name
    • latitude
    • longitude
    • duration
    • radius range {{m_radius}} kilometers
    • conflict priority
    ` }) export class BlockPropLocation extends Compbaser implements AfterViewInit { m_formInputs = {}; m_currentIndex = 0; m_radius = 0; m_totalLocations = 0; m_contGroup: FormGroup; m_blockData: IBlockData; m_pendingBlocAddition: { type: string, content: IAddContents, xmlSnippet: string }; m_showMap = false; m_PLACEMENT_LISTS = PLACEMENT_LISTS; m_collectionList: List; constructor(private fb: FormBuilder, private yp: YellowPepperService, private cd: ChangeDetectorRef, private bs: BlockService, @Inject('BLOCK_PLACEMENT') private blockPlacement: string, private rp: RedPepperService) { super(); this.m_contGroup = fb.group({ 'mode': [0], 'label': [0], 'lng': [0], 'lat': [0], 'duration': [0], 'priority': [0], 'radius': [0] }); _.forEach(this.m_contGroup.controls, (value, key: string) => { this.m_formInputs[key] = this.m_contGroup.controls[key] as FormControl; }) this.cancelOnDestroy( // this.yp.listenLocationMarkerSelected() .filter((i_LocationMarkModel: LocationMarkModel) => !_.isEmpty(this.m_pendingBlocAddition)) .subscribe((i_LocationMarkModel: LocationMarkModel) => { var domPlayerData = this.m_blockData.playerDataDom; var reLat = new RegExp(":LAT:", "ig"); var reLng = new RegExp(":LNG:", "ig"); this.m_pendingBlocAddition.xmlSnippet = this.m_pendingBlocAddition.xmlSnippet.replace(reLat, i_LocationMarkModel.lat); this.m_pendingBlocAddition.xmlSnippet = this.m_pendingBlocAddition.xmlSnippet.replace(reLng, i_LocationMarkModel.lng); var xSnippetLocation = jXML(domPlayerData).find('GPS'); jXML(xSnippetLocation).append(jXML(this.m_pendingBlocAddition.xmlSnippet)); this.bs.setBlockPlayerData(this.m_blockData, domPlayerData); this.m_pendingBlocAddition = null; this._jumpToLocation('last') }, (e) => console.error(e)) ) this.cancelOnDestroy( // this.yp.listenLocationMapLoad() .pairwise() .filter(v => v[0] == true && v[1] == false && this.m_pendingBlocAddition && this.m_pendingBlocAddition.xmlSnippet != '') .do(() => { console.log(1); }) .combineLatest(this.yp.ngrxStore.select(store => store.appDb.uiState.locationMap.locationMarkerSelected)) .subscribe((v) => { console.log(v); }, (e) => console.error(e)) ) } @ViewChild('simpleGrid') simpleGrid: SimpleGridTable; @ViewChild('modalAddContent') modalAddContent: ModalComponent; // @ViewChild('modalMap') // modalMap: ModalComponent; @Input() set setBlockData(i_blockData) { this.m_blockData = i_blockData; this._render(); } ngAfterViewInit() { this._render(); this._jumpToLocation('first'); } _onModelMapClosed() { this.m_showMap = false; } _onDragComplete(dragData: ISimpleGridDraggedData) { // dragData.items.forEach((item: StoreModel, i) => con(i + ' ' + item.getKey('name')) ); var currentIndex = dragData.currentIndex; var newIndex = dragData.newIndex; var domPlayerData = this.m_blockData.playerDataDom; var target = jXML(domPlayerData).find('Fixed').children().get(newIndex); var source = jXML(domPlayerData).find('Fixed').children().get(currentIndex); newIndex > currentIndex ? jXML(target).after(source) : jXML(target).before(source); this.bs.setBlockPlayerData(this.m_blockData, domPlayerData); } _onRemoveCollectionItem() { var record: SimpleGridRecord = this.simpleGrid.getSelected(); if (_.isUndefined(record)) return; var rowIndex = this.simpleGrid.getSelected().index; var domPlayerData = this.m_blockData.playerDataDom; jXML(domPlayerData).find('Fixed').children().eq(rowIndex).remove(); // self._populateTableCollection(domPlayerData); // this._populateTableEvents(); this.bs.setBlockPlayerData(this.m_blockData, domPlayerData) } _onClosed() { this.modalAddContent.close() } _onAddNewBlock(type: string) { this.m_pendingBlocAddition = {type: type, content: null, xmlSnippet: ''} this.modalAddContent.open() } /** Add a new collection item which can include a Scene or a resource (not a component) @method _onAddedContent @param {Event} e **/ _onAddedContent(i_addContents: IAddContents) { var domPlayerData = this.m_blockData.playerDataDom; var buff = ''; var locationBuff; var xSnippetLocation; switch (this.m_pendingBlocAddition.type) { case 'Fixed': { xSnippetLocation = jXML(domPlayerData).find('Fixed'); locationBuff = '>'; break; } case 'GPS': { locationBuff = 'lat=":LAT:" lng=":LNG:" radios="4" duration="5" priority="1">'; xSnippetLocation = jXML(domPlayerData).find('GPS'); break; } } if (Number(i_addContents.blockCode) == BlockLabels.BLOCKCODE_SCENE) { // add scene to collection, if block resides in scene don't allow cyclic reference to collection scene inside current scene if (this.blockPlacement == PLACEMENT_SCENE && this.m_blockData.scene.handle == i_addContents.sceneData.scene_id) { return bootbox.alert('You cannot display a scene in a collection that refers to itself, that is just weird'); } var sceneName = i_addContents.sceneData.domPlayerDataJson.Player._label; var nativeId = i_addContents.sceneData.scene_native_id; buff = ` `; } else { var resourceName = this.rp.getResourceRecord(i_addContents.resourceId).resource_name; buff = ` ` } // if default item, just add it. if location item, remember it and only add it once user select // a location for it in google the map as we need to wait for the coordinates. switch (this.m_pendingBlocAddition.type) { case 'Fixed': { jXML(xSnippetLocation).append(jXML(buff)); this.bs.setBlockPlayerData(this.m_blockData, domPlayerData); this.m_pendingBlocAddition = null; break; } case 'GPS': { this.m_pendingBlocAddition.content = i_addContents; this.m_pendingBlocAddition.xmlSnippet = buff; this._openMap(); break; } } } _removeLocation() { var domPlayerData = this.m_blockData.playerDataDom; jXML(domPlayerData).find('GPS').children().eq(this.m_currentIndex).remove(); this.bs.setBlockPlayerData(this.m_blockData, domPlayerData); this._jumpToLocation('first'); } /** Populate the total map locations set @method _populateTotalMapLocations @param {Object} domPlayerData **/ _populateTotalMapLocations() { var domPlayerData = this.m_blockData.playerDataDom this.m_totalLocations = jXML(domPlayerData).find('GPS').children().length; if (this.m_totalLocations == 0) { this.m_currentIndex = 0; } else { // jXML(Elements.LOCATION_SELECTED).show(); } // jXML(Elements.TOTAL_MAP_LOCATIONS).text(total); } _openMap() { var uiState: IUiState = {locationMap: {loadLocationMap: true}} this.yp.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})) } /** Select specific location and populate both the UI as well scroll map to coordinates **/ _jumpToLocation(i_item?) { var domPlayerData = this.m_blockData.playerDataDom; var total = jXML(domPlayerData).find('GPS').children().length; var item; if (total == 0) { this._populateTotalMapLocations(); var uiState: IUiState = {locationMap: {locationMarkerSelected: null}} this.yp.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})) return; } if (this.m_currentIndex > total - 1) i_item = 'first'; switch (i_item) { case 'first': { this.m_currentIndex = 0; item = jXML(domPlayerData).find('GPS').children().first(); break; } case 'last': { this.m_currentIndex = total - 1; item = jXML(domPlayerData).find('GPS').children().last(); break; } case 'next': { if (this.m_currentIndex == (total - 1)) { item = jXML(domPlayerData).find('GPS').children().last(); } else { this.m_currentIndex++; item = jXML(domPlayerData).find('GPS').children().get(this.m_currentIndex); } break; } case 'prev': { if (this.m_currentIndex == 0) { item = jXML(domPlayerData).find('GPS').children().first(); } else { this.m_currentIndex--; item = jXML(domPlayerData).find('GPS').children().get(this.m_currentIndex); } break; } default: { item = jXML(domPlayerData).find('GPS').children().get(this.m_currentIndex); } } this.m_radius = parseFloat(jXML(item).attr('radios')); var lat = parseFloat(jXML(item).attr('lat')); var lng = parseFloat(jXML(item).attr('lng')); var duration = parseFloat(jXML(item).attr('duration')); var marker: LocationMarkModel = new LocationMarkModel({ id: Math.random(), lat: lat, lng: lng, radius: this.m_radius, new: false, label: '', draggable: true }) this.m_formInputs['label'].setValue(jXML(item).attr('page')); this.m_formInputs['priority'].setValue(jXML(item).attr('priority')); this.m_formInputs['lat'].setValue(lat); this.m_formInputs['lng'].setValue(lng); this.m_formInputs['duration'].setValue(duration); this.m_formInputs['radius'].setValue(this.m_radius); var uiState: IUiState = {locationMap: {locationMarkerSelected: marker}} this.yp.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})) // this.m_addBlockLocationView.panToPoint(jXML(item).attr('lat'), jXML(item).attr('lng')); } _onDurationEdited(event: ISimpleGridEdit, index) { var value = event.value; if (!Lib.IsNumber(value)) return; var domPlayerData = this.m_blockData.playerDataDom; var item = jXML(domPlayerData).find('Fixed').children().get(index); jXML(item).attr('duration', value); this.bs.setBlockPlayerData(this.m_blockData, domPlayerData) } _onPageNameEdited(event: ISimpleGridEdit, index) { var value = event.value; var domPlayerData = this.m_blockData.playerDataDom; var item = jXML(domPlayerData).find('Fixed').children().get(index); jXML(item).attr('page', value); this.bs.setBlockPlayerData(this.m_blockData, domPlayerData) } _populateTableCollection() { this.m_collectionList = List([]); var domPlayerData = this.m_blockData.playerDataDom as any var rowIndex = 0; jXML(domPlayerData).find('Fixed').children().each((k, page) => { var resource_hResource, scene_hDataSrc; var type = jXML(page).attr('type'); if (type == 'resource') { resource_hResource = jXML(page).find('Resource').attr('hResource'); } else { scene_hDataSrc = jXML(page).find('Player').attr('hDataSrc'); } //con('populating ' + resource_hResource); var storeModel = new StoreModel({ rowIndex: rowIndex, checkbox: true, name: jXML(page).attr('page'), duration: jXML(page).attr('duration'), type: type, resource_hResource: resource_hResource, scene_hDataSrc: scene_hDataSrc }); this.m_collectionList = this.m_collectionList.push(storeModel); rowIndex++; }); this.m_collectionList = this._sortCollection(this.m_collectionList); } // /** // Load event list to block props UI // @method _populateTableEvents // **/ // _populateTableEvents() { // var data: Array = [], rowIndex = 0; // var domPlayerData = this.m_blockData.playerDataDom; // // self.m_collectionEventTable.bootstrapTable('removeAll'); // jXML(domPlayerData).find('EventCommands').children().each(function (k, eventCommand) { // var pageName = ''; // if (jXML(eventCommand).attr('command') == 'selectPage') // pageName = jXML(eventCommand).find('Page').attr('name'); // var storeModel = new JsonEventResourceModel({ // rowIndex: rowIndex, // checkbox: true, // event: jXML(eventCommand).attr('from'), // pageName: pageName, // action: jXML(eventCommand).attr('command') // } // ) // data.push(storeModel) // rowIndex++; // }); // this.m_jsonEventResources = data; // } _sortCollection(i_collection: List): List { var sorted = i_collection.sort((a, b) => { if (a.getKey('rowIndex') > b.getKey('rowIndex')) return 1; if (a.getKey('rowIndex') < b.getKey('rowIndex')) return -1; return 0; }) return sorted as List; } _render() { this.m_contGroup.reset(); this._populateTableCollection(); this._populateTotalMapLocations(); this._jumpToLocation(); this.cd.markForCheck(); } private saveToStore() { // console.log(this.m_contGroup.status + ' ' + JSON.stringify(this.ngmslibService.cleanCharForXml(this.m_contGroup.value))); if (this.m_contGroup.status != 'VALID') return; var domPlayerData = this.m_blockData.playerDataDom; var total = jXML(domPlayerData).find('GPS').children().length; if (total == 0) return; var item = jXML(domPlayerData).find('GPS').children().get(this.m_currentIndex); jXML(item).attr('radios', this.m_contGroup.value.radius); jXML(item).attr('page', this.m_contGroup.value.label); jXML(item).attr('lat', this.m_contGroup.value.lat); jXML(item).attr('lng', this.m_contGroup.value.lng); jXML(item).attr('duration', this.m_contGroup.value.duration); jXML(item).attr('priority', this.m_contGroup.value.priority); this.bs.setBlockPlayerData(this.m_blockData, domPlayerData) // var xSnippet = jXML(domPlayerData).find('HTML'); // xSnippet.attr('src', this.m_contGroup.value.url); // this.bs.setBlockPlayerData(this.m_blockData, domPlayerData); } destroy() { } } ================================================ FILE: src/app/blocks/block-prop-mrss.ts ================================================ import {AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, Input} from "@angular/core"; import {FormBuilder, FormControl, FormGroup, Validators} from "@angular/forms"; import {BlockService, IBlockData} from "./block-service"; import {Compbaser, NgmslibService} from "ng-mslib"; import {urlRegExp} from "../../Lib"; import * as _ from "lodash"; @Component({ selector: 'block-prop-mrss', host: {'(input-blur)': 'saveToStore($event)'}, changeDetection: ChangeDetectionStrategy.OnPush, template: ` {{me}}
    • maintain aspect ratio
    ` }) export class BlockPropMrss extends Compbaser implements AfterViewInit { m_formInputs = {}; m_contGroup: FormGroup; m_blockData: IBlockData; m_showCustomUrl = false; m_mrssLinksData = []; m_mrssLinks = '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' constructor(private fb: FormBuilder, private cd: ChangeDetectorRef, private bs: BlockService, private ngmslibService: NgmslibService) { super(); this.m_contGroup = fb.group({ 'url': ['', [Validators.pattern(urlRegExp)]], 'rssSelection': [], 'maintain': [] }); _.forEach(this.m_contGroup.controls, (value, key: string) => { this.m_formInputs[key] = this.m_contGroup.controls[key] as FormControl; }) var links = jXML(jXML.parseXML(this.m_mrssLinks)).find('Rss'); _.forEach(links, (k, v) => { this.m_mrssLinksData.push({ url: jXML(k).attr('url'), label: jXML(k).attr('label') }) }); } @Input() set setBlockData(i_blockData) { if (this.m_blockData && this.m_blockData.blockID != i_blockData.blockID) { this.m_blockData = i_blockData; this._render(); } else { this.m_blockData = i_blockData; } } @Input() external: boolean = false; /** Toggle maintain aspect ratio **/ _toggleAspectRatio(i_value) { i_value = StringJS(i_value).booleanToNumber() var domPlayerData = this.m_blockData.playerDataDom; var xSnippet = jXML(domPlayerData).find('Rss'); jXML(xSnippet).attr('maintainAspectRatio', i_value); this.bs.setBlockPlayerData(this.m_blockData, domPlayerData) } _isUrlCustom(i_url): boolean { var feed = this.m_mrssLinksData.find(o => o.url == i_url); if (feed && feed.label == 'Custom') return true; if (feed) return false; return true; } _onRssSelected(event) { this.m_showCustomUrl = this._isUrlCustom(event.target.value); } private _render() { var domPlayerData: XMLDocument = this.m_blockData.playerDataDom var xSnippet = jXML(domPlayerData).find('Rss'); var url = xSnippet.attr('url'); var maintain = StringJS(jXML(xSnippet).attr('maintainAspectRatio')).booleanToNumber(); this.m_formInputs['maintain'].setValue(maintain); if (this._isUrlCustom(url)) { this.m_showCustomUrl = true; this.m_formInputs['rssSelection'].setValue(''); this.m_formInputs['url'].setValue(url); } else { this.m_showCustomUrl = false; this.m_formInputs['rssSelection'].setValue(url); } this.cd.markForCheck(); } ngAfterViewInit() { this._render(); } private saveToStore() { // con(this.m_contGroup.status + ' ' + JSON.stringify(this.ngmslibService.cleanCharForXml(this.m_contGroup.value))); if (this.m_contGroup.status != 'VALID') return; var domPlayerData: XMLDocument = this.m_blockData.playerDataDom; var xSnippet = jXML(domPlayerData).find('Rss'); if (this.m_contGroup.value.rssSelection == ''){ jXML(xSnippet).attr('url', this.m_contGroup.value.url); } else { jXML(xSnippet).attr('url', this.m_contGroup.value.rssSelection); } this.bs.setBlockPlayerData(this.m_blockData, domPlayerData); } destroy() { } } ================================================ FILE: src/app/blocks/block-prop-position.ts ================================================ import {ChangeDetectionStrategy, ChangeDetectorRef, Component} from "@angular/core"; import {FormBuilder, FormControl, FormGroup, Validators} from "@angular/forms"; import {Compbaser} from "ng-mslib"; import {YellowPepperService} from "../../services/yellowpepper.service"; import * as _ from "lodash"; import {BlockService, IBlockData, ISceneData} from "./block-service"; import {RedPepperService} from "../../services/redpepper.service"; import {timeout} from "../../decorators/timeout-decorator"; import {BlockLabels} from "../../interfaces/Consts"; @Component({ selector: 'block-prop-position', changeDetection: ChangeDetectionStrategy.OnPush, styles: [` li { padding-top: 3px; padding-bottom: 3px; } form { padding: 20px; } .inliner { display: inline-block; width: 60px; } input.ng-invalid { border-right: 10px solid red; } .material-switch { position: relative; padding-top: 10px; } .input-group { padding-top: 10px; } i { width: 20px; } `], template: `
    • top
    • left
    • width
    • height
    • rotation


    • locked
    ` }) export class BlockPropPosition extends Compbaser { m_formInputs = {}; m_contGroup: FormGroup; m_blockData: IBlockData; m_canvasScale = -1; m_blockIsScene = false; constructor(private fb: FormBuilder, private rp: RedPepperService, private yp: YellowPepperService, private bs: BlockService, private cd: ChangeDetectorRef) { super(); this.m_contGroup = fb.group({ 'pixel_y': [0], 'pixel_x': [0], 'pixel_width': [0], 'pixel_height': [0], 'rotation': [0], 'locked': [], 'x1_check': [{value: '', disabled: true}], 'x2_check': [{value: '', disabled: true}], 'x3_check': [{value: '', disabled: true}], 'y1_check': [{value: '', disabled: true}], 'y2_check': [{value: '', disabled: true}], 'y3_check': [{value: '', disabled: true}], 'x1_text': [{value: '', disabled: true}], 'x2_text': [{value: '', disabled: true}], 'x3_text': [{value: '', disabled: true}], 'y1_text': [{value: '', disabled: true}], 'y2_text': [{value: '', disabled: true}], 'y3_text': [{value: '', disabled: true}] }); _.forEach(this.m_contGroup.controls, (value, key: string) => { this.m_formInputs[key] = this.m_contGroup.controls[key] as FormControl; }) // this.m_formInputs['x1_check'].disable(); this.cancelOnDestroy( // this.yp.listenSceneOrBlockSelectedChanged() .combineLatest(this.yp.listenFabricSceneScaled(), (i_sceneData: ISceneData, i_scale: number) => { this.m_canvasScale = i_scale; return i_sceneData }) .mergeMap((i_sceneData: ISceneData) => { return this.bs.getBlockDataInScene(i_sceneData) }) .subscribe((blockData: IBlockData) => { this.m_blockIsScene = parseInt(blockData.blockCode) == BlockLabels.BLOCKCODE_SCENE ? true : false; this.m_blockData = blockData; this.m_formInputs['locked'].setValue(StringJS(blockData.playerDataJson.Player._locked).booleanToNumber()); this.m_formInputs['rotation'].setValue(blockData.playerDataJson.Player.Data.Layout._rotation); this.m_formInputs['pixel_height'].setValue(blockData.playerDataJson.Player.Data.Layout._height); this.m_formInputs['pixel_width'].setValue(blockData.playerDataJson.Player.Data.Layout._width); this.m_formInputs['pixel_x'].setValue(blockData.playerDataJson.Player.Data.Layout._x); this.m_formInputs['pixel_y'].setValue(blockData.playerDataJson.Player.Data.Layout._y); this.cd.markForCheck(); }, (e) => console.error(e)) ) } ngAfterViewInit() { // this.cancelOnDestroy( // this.m_contGroup.valueChanges // .map(v => { // for (var z in v) // v[z] = parseInt(v[z]) // return v; // }) // .filter(v => !_.some(v, (o) => _.isNaN(o))) // .startWith({}) // .pairwise() // .filter(v => !_.isEqual(v[0], v[1])) // .debounceTime(1000) // .subscribe((v) => { // this.saveToStoreLayout(); // }, (e) => console.error(e)) //cancelOnDestroy please // ) } saveToStoreLayout() { if (this.m_contGroup.status != 'VALID') return; var domPlayerData: XMLDocument = this.m_blockData.playerDataDom; var x = this.m_contGroup.value.pixel_x; var y = this.m_contGroup.value.pixel_y; var w = this.m_contGroup.value.pixel_width; var h = this.m_contGroup.value.pixel_height; var r = this.m_contGroup.value.rotation; var blockMinWidth = 50; var blockMinHeight = 50; if (h < blockMinHeight) h = blockMinHeight; if (w < blockMinWidth) w = blockMinWidth; if (this.m_blockIsScene) { $(domPlayerData).find('Layout').eq(0).attr('width', w); $(domPlayerData).find('Layout').eq(0).attr('height', h); this.bs.setBlockPlayerData(this.m_blockData, domPlayerData); this.bs.notifyReloadScene(this.m_blockData.scene.handle); } else { var layout = $(domPlayerData).find('Layout'); layout.attr('rotation', parseInt(r)); layout.attr('x', parseInt(x)); layout.attr('y', parseInt(y)); layout.attr('width', parseInt(w)); layout.attr('height', parseInt(h)); this.bs.setBlockPlayerData(this.m_blockData, domPlayerData); this.bs.notifySceneBlockChanged(this.m_blockData); } } @timeout(250) private saveToStoreLock(v) { var domPlayerData: XMLDocument = this.m_blockData.playerDataDom; var r = v == true ? 1 : 0; jXML(domPlayerData).find('Player').attr('locked', r); this.bs.setBlockPlayerData(this.m_blockData, domPlayerData); this.bs.notifySceneBlockChanged(this.m_blockData); } destroy() { } } ================================================ FILE: src/app/blocks/block-prop-qr.ts ================================================ import {AfterViewInit, ChangeDetectionStrategy, Component, Input} from "@angular/core"; import {FormBuilder, FormControl, FormGroup, Validators} from "@angular/forms"; import {BlockService, IBlockData} from "./block-service"; import {RedPepperService} from "../../services/redpepper.service"; import {Compbaser, NgmslibService} from "ng-mslib"; import {urlRegExp} from "../../Lib"; import * as _ from "lodash"; @Component({ selector: 'block-prop-qr', host: { '(input-blur)': 'saveToStore($event)' }, changeDetection: ChangeDetectionStrategy.OnPush, template: ` {{me}}
    • QR code
    ` }) export class BlockPropQR extends Compbaser implements AfterViewInit { private formInputs = {}; m_contGroup: FormGroup; m_blockData: IBlockData; constructor(private fb: FormBuilder, private bs: BlockService) { super(); this.m_contGroup = fb.group({ 'text': [] }); _.forEach(this.m_contGroup.controls, (value, key: string) => { this.formInputs[key] = this.m_contGroup.controls[key] as FormControl; }) } @Input() set setBlockData(i_blockData) { if (this.m_blockData && this.m_blockData.blockID != i_blockData.blockID) { this.m_blockData = i_blockData; this._render(); } else { this.m_blockData = i_blockData; } } ngAfterViewInit() { this._render(); } _render() { var domPlayerData = this.m_blockData.playerDataDom var xSnippet = jXML(domPlayerData).find('Text'); this.formInputs['text'].setValue(xSnippet.text()); } private saveToStore() { if (this.m_contGroup.status != 'VALID') return; var domPlayerData = this.m_blockData.playerDataDom; var xSnippet = jXML(domPlayerData).find('Text'); jXML(xSnippet).text(this.m_contGroup.value.text); this.bs.setBlockPlayerData(this.m_blockData, domPlayerData); } destroy() { } } ================================================ FILE: src/app/blocks/block-prop-rss.ts ================================================ import {AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, Input} from "@angular/core"; import {FormBuilder, FormControl, FormGroup, Validators} from "@angular/forms"; import {BlockService, IBlockData} from "./block-service"; import {Compbaser, NgmslibService} from "ng-mslib"; import {Lib, urlRegExp} from "../../Lib"; import * as _ from "lodash"; import {IFontSelector} from "../../comps/font-selector/font-selector"; @Component({ selector: 'block-prop-rss', host: {'(input-blur)': 'saveToStore($event)'}, changeDetection: ChangeDetectionStrategy.OnPush, template: ` {{me}}


    • Horizontal
      Vertical

    • slow
      medium
      fast
    ` }) export class BlockPropRss extends Compbaser implements AfterViewInit { m_formInputs = {}; m_contGroup: FormGroup; m_fontConfig: IFontSelector; m_blockData: IBlockData; m_showCustomUrl = false; m_mrssLinksData = []; m_mrssLinks = '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' constructor(private fb: FormBuilder, private cd: ChangeDetectorRef, private bs: BlockService, private ngmslibService: NgmslibService) { super(); this.m_contGroup = fb.group({ 'url': ['', [Validators.pattern(urlRegExp)]], 'minRefreshTime': [1], 'vertical': [0], 'speed': [50], 'rssSelection': [] }); _.forEach(this.m_contGroup.controls, (value, key: string) => { this.m_formInputs[key] = this.m_contGroup.controls[key] as FormControl; }) var links = jXML(jXML.parseXML(this.m_mrssLinks)).find('Rss'); _.forEach(links, (k, v) => { this.m_mrssLinksData.push({ url: jXML(k).attr('url'), label: jXML(k).attr('label') }) }); } @Input() set setBlockData(i_blockData) { if (this.m_blockData && this.m_blockData.blockID != i_blockData.blockID) { this.m_blockData = i_blockData; this._render(); } else { this.m_blockData = i_blockData; } } @Input() external: boolean = false; _isUrlCustom(i_url): boolean { var feed = this.m_mrssLinksData.find(o => o.url == i_url); if (feed && feed.label == 'Custom') return true; if (feed) return false; return true; } _onRssSelected(event) { this.m_showCustomUrl = this._isUrlCustom(event.target.value); } _onFontChanged(config: IFontSelector) { var domPlayerData = this.m_blockData.playerDataDom; var xSnippet = jXML(domPlayerData).find('Rss'); var xSnippetFont = jXML(xSnippet).find('Font').eq(0); config.bold == true ? xSnippetFont.attr('fontWeight', 'bold') : xSnippetFont.attr('fontWeight', 'normal'); config.italic == true ? xSnippetFont.attr('fontStyle', 'italic') : xSnippetFont.attr('fontStyle', 'normal'); config.underline == true ? xSnippetFont.attr('textDecoration', 'underline') : xSnippetFont.attr('textDecoration', 'none'); xSnippetFont.attr('fontColor', Lib.ColorToDecimal(config.color)); xSnippetFont.attr('fontSize', config.size); xSnippetFont.attr('fontFamily', config.font); xSnippetFont.attr('textAlign', config.alignment); this.bs.setBlockPlayerData(this.m_blockData, domPlayerData); } private _render() { var domPlayerData: XMLDocument = this.m_blockData.playerDataDom var xSnippet = jXML(domPlayerData).find('Rss'); var xSnippetFont = jXML(xSnippet).find('Font').eq(0); var url = xSnippet.attr('url'); var vertical = jXML(xSnippet).attr('vertical'); var minRefreshTime = jXML(xSnippet).attr('minRefreshTime') var speed = jXML(xSnippet).attr('speed') if (this._isUrlCustom(url)) { this.m_showCustomUrl = true; this.m_formInputs['rssSelection'].setValue(''); this.m_formInputs['url'].setValue(url); } else { this.m_showCustomUrl = false; this.m_formInputs['rssSelection'].setValue(url); } this.m_formInputs['minRefreshTime'].setValue(minRefreshTime); this.m_formInputs['speed'].setValue(speed); this.m_formInputs['vertical'].setValue(vertical); this.m_fontConfig = { size: Number(xSnippetFont.attr('fontSize')), alignment: xSnippetFont.attr('textAlign'), bold: xSnippetFont.attr('fontWeight') == 'bold' ? true : false, italic: xSnippetFont.attr('fontStyle') == 'italic' ? true : false, font: xSnippetFont.attr('fontFamily'), underline: xSnippetFont.attr('textDecoration') == 'underline' ? true : false, color: Lib.ColorToHex(Lib.DecimalToHex(xSnippetFont.attr('fontColor'))), } } ngAfterViewInit() { this._render(); } private saveToStore() { // con(this.m_contGroup.status + ' ' + JSON.stringify(this.ngmslibService.cleanCharForXml(this.m_contGroup.value))); if (this.m_contGroup.status != 'VALID') return; var domPlayerData: XMLDocument = this.m_blockData.playerDataDom; var xSnippet = jXML(domPlayerData).find('Rss'); if (this.m_contGroup.value.rssSelection == ''){ jXML(xSnippet).attr('url', this.m_contGroup.value.url); } else { jXML(xSnippet).attr('url', this.m_contGroup.value.rssSelection); } jXML(xSnippet).attr('minRefreshTime', this.m_contGroup.value.minRefreshTime); jXML(xSnippet).attr('speed', this.m_contGroup.value.speed); jXML(xSnippet).attr('vertical', this.m_contGroup.value.vertical); this.bs.setBlockPlayerData(this.m_blockData, domPlayerData); } destroy() { } } ================================================ FILE: src/app/blocks/block-prop-scene.ts ================================================ import {AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, Inject, Input} from "@angular/core"; import {FormBuilder, FormControl, FormGroup, Validators} from "@angular/forms"; import {BlockService, IBlockData} from "./block-service"; import {RedPepperService} from "../../services/redpepper.service"; import {Compbaser, NgmslibService} from "ng-mslib"; import {Lib, urlRegExp} from "../../Lib"; import * as _ from "lodash"; @Component({ selector: 'block-prop-scene', host: { '(input-blur)': 'saveToStore($event)' }, changeDetection: ChangeDetectionStrategy.OnPush, template: ` {{me}}
    • scene name
    ` }) export class BlockPropScene extends Compbaser implements AfterViewInit { private formInputs = {}; m_contGroup: FormGroup; m_blockData: IBlockData; constructor(@Inject('BLOCK_PLACEMENT') private blockPlacement, private fb: FormBuilder, private cd: ChangeDetectorRef, private rp: RedPepperService, private bs: BlockService, private ngmslibService: NgmslibService) { super(); this.m_contGroup = fb.group({ 'name': [] }); _.forEach(this.m_contGroup.controls, (value, key: string) => { this.formInputs[key] = this.m_contGroup.controls[key] as FormControl; }) } @Input() set setBlockData(i_blockData) { this.m_blockData = i_blockData; this._render(); } ngAfterViewInit() { this._render(); } _render() { var domPlayerData = this.bs.getBlockPlayerData(this.m_blockData); var name = jXML(domPlayerData).find('Player').eq(0).attr('label'); this.formInputs['name'].setValue(name); } private saveToStore() { if (this.m_contGroup.status != 'VALID') return; var domPlayerData = this.bs.getBlockPlayerData(this.m_blockData); var name = Lib.CleanProbCharacters(this.m_contGroup.value.name, 1); jXML(domPlayerData).find('Player').eq(0).attr('label', name); this.bs.setBlockPlayerData(this.m_blockData, domPlayerData); } destroy() { } } ================================================ FILE: src/app/blocks/block-prop-sheets.ts ================================================ import {AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, Input} from "@angular/core"; import {FormBuilder, FormControl, FormGroup, Validators} from "@angular/forms"; import {BlockService, IBlockData} from "./block-service"; import {Compbaser, NgmslibService} from "ng-mslib"; import * as _ from "lodash"; import * as moment from 'moment' @Component({ selector: 'block-prop-sheets', host: { '(input-blur)': 'saveToStore($event)' }, changeDetection: ChangeDetectionStrategy.OnPush, template: ` {{me}}
    • token
    • Load with sheet
    ` }) export class BlockPropSheets extends Compbaser implements AfterViewInit { m_formInputs = {}; m_contGroup: FormGroup; m_blockData: IBlockData; m_sheetList = []; m_sheetSeleced: any = {}; m_mode = false; constructor(private fb: FormBuilder, private cd: ChangeDetectorRef, private bs: BlockService, private ngmslibService: NgmslibService) { super(); this.m_contGroup = fb.group({ 'token': [''], 'sheetSelection': [''] }); _.forEach(this.m_contGroup.controls, (value, key: string) => { this.m_formInputs[key] = this.m_contGroup.controls[key] as FormControl; }) } @Input() jsonMode: boolean; @Input() set setBlockData(i_blockData) { if (this.m_blockData && this.m_blockData.blockID != i_blockData.blockID) { this.m_blockData = i_blockData; this._render(); } else { this.m_blockData = i_blockData; } } _onSheetSelected(event) { var calId = event.target.value; var domPlayerData: XMLDocument = this.m_blockData.playerDataDom; var item = jXML(domPlayerData).find('Json').find('Data'); jXML(item).attr('id', calId); this.bs.setBlockPlayerData(this.m_blockData, domPlayerData); } private _render() { var domPlayerData: XMLDocument = this.m_blockData.playerDataDom var jXMLdata = jXML(domPlayerData).find('Json').find('Data'); var mode = jXMLdata.attr('mode'); this.m_formInputs['token'].setValue(jXMLdata.attr('token')); this._getGoogleSheets(); } _getGoogleSheets() { var self = this; try { jXML.ajax({ url: `https://secure.digitalsignage.com/GoogleSheetsList/${self.m_contGroup.value.token}`, dataType: "json", type: "post", complete: function (response, status) { self.m_sheetSeleced = {}; self.m_sheetList = []; if (_.isUndefined(response.responseText) || response.responseText.length == 0) return; if (response.responseText.indexOf('Cannot')>-1) return; var jData = JSON.parse(response.responseText); _.forEach(jData, function (k: any) { self.m_sheetList.push({ id: k.id, label: k.title, mimeType: k.mimeType }) }); var id = self._getFileId(); if (id && id.length > 10) self.m_sheetSeleced = self.m_sheetList.find(item => item.id == id); self.m_formInputs['sheetSelection'].setValue(self.m_sheetSeleced.id); self.cd.markForCheck() }, error: function (jqXHR, exception) { console.log('ajax req:' + jqXHR, exception); } }); } catch (e) { console.error('error on ajax' + e); } } private _getFileId(): string { var domPlayerData: XMLDocument = this.m_blockData.playerDataDom; var item = jXML(domPlayerData).find('Json').find('Data'); return jXML(item).attr('id'); } ngAfterViewInit() { this._render(); } _onCreateToken() { var win = window.open('http://google.signage.me', '_blank'); if (win) { win.focus(); } else { bootbox.alert('Browser popups are blocked, please enable and try again'); } } private saveToStore() { // Lib.Con(this.m_contGroup.status + ' ' + JSON.stringify(this.ngmslibService.cleanCharForXml(this.m_contGroup.value))); if (this.m_contGroup.status != 'VALID') return; var domPlayerData: XMLDocument = this.m_blockData.playerDataDom; var item = jXML(domPlayerData).find('Json').find('Data'); jXML(item).attr('token', this.m_contGroup.value.token); this.bs.setBlockPlayerData(this.m_blockData, domPlayerData); } destroy() { } } ================================================ FILE: src/app/blocks/block-prop-twitter.ts ================================================ import {AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, Input} from "@angular/core"; import {FormBuilder, FormControl, FormGroup} from "@angular/forms"; import {BlockService, IBlockData} from "./block-service"; import {Compbaser, NgmslibService} from "ng-mslib"; import * as _ from "lodash"; @Component({ selector: 'block-prop-twitter', host: { '(input-blur)': 'saveToStore($event)' }, changeDetection: ChangeDetectionStrategy.OnPush, template: ` {{me}}
    • screen name
    • token
    ` }) export class BlockPropTwitter extends Compbaser implements AfterViewInit { m_formInputs = {}; m_contGroup: FormGroup; m_blockData: IBlockData; constructor(private fb: FormBuilder, private cd: ChangeDetectorRef, private bs: BlockService, private ngmslibService: NgmslibService) { super(); this.m_contGroup = fb.group({ 'screenName': [''], 'token': [''] }); _.forEach(this.m_contGroup.controls, (value, key: string) => { this.m_formInputs[key] = this.m_contGroup.controls[key] as FormControl; }) } @Input() jsonMode: boolean; @Input() set setBlockData(i_blockData) { if (this.m_blockData && this.m_blockData.blockID != i_blockData.blockID) { this.m_blockData = i_blockData; this._render(); } else { this.m_blockData = i_blockData; } } private _render() { var domPlayerData: XMLDocument = this.m_blockData.playerDataDom var jXMLdata = jXML(domPlayerData).find('Json').find('Data'); this.m_formInputs['token'].setValue(jXMLdata.attr('token')); this.m_formInputs['screenName'].setValue(jXMLdata.attr('screenName')); } ngAfterViewInit() { this._render(); } _onCreateToken() { var win = window.open('http://twitter.signage.me', '_blank'); if (win) { win.focus(); } else { bootbox.alert('Browser popups are blocked, please enable and try again'); } } private saveToStore() { con(this.m_contGroup.status + ' ' + JSON.stringify(this.ngmslibService.cleanCharForXml(this.m_contGroup.value))); if (this.m_contGroup.status != 'VALID') return; var domPlayerData: XMLDocument = this.m_blockData.playerDataDom; var item = jXML(domPlayerData).find('Json').find('Data'); jXML(item).attr('screenName', this.m_contGroup.value.screenName); jXML(item).attr('token', this.m_contGroup.value.token); this.bs.setBlockPlayerData(this.m_blockData, domPlayerData); } destroy() { } } ================================================ FILE: src/app/blocks/block-prop-video.ts ================================================ import {AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, Input} from "@angular/core"; import {FormBuilder, FormControl, FormGroup, Validators} from "@angular/forms"; import {BlockService, IBlockData} from "./block-service"; import {Compbaser, NgmslibService} from "ng-mslib"; import * as _ from "lodash"; import {urlRegExp} from "../../Lib"; @Component({ selector: 'block-prop-video', host: {'(input-blur)': 'saveToStore($event)'}, changeDetection: ChangeDetectionStrategy.OnPush, template: ` {{me}}
    • url
    • maintain aspect ratio
    ` }) export class BlockPropVideo extends Compbaser implements AfterViewInit { m_formInputs = {}; m_contGroup: FormGroup; m_blockData: IBlockData; constructor(private fb: FormBuilder, private cd: ChangeDetectorRef, private bs: BlockService, private ngmslibService: NgmslibService) { super(); this.m_contGroup = fb.group({ 'url': ['', [Validators.pattern(urlRegExp)]], 'maintain': [] }); _.forEach(this.m_contGroup.controls, (value, key: string) => { this.m_formInputs[key] = this.m_contGroup.controls[key] as FormControl; }) } @Input() external: boolean = false; @Input() set setBlockData(i_blockData) { if (this.m_blockData && this.m_blockData.blockID != i_blockData.blockID) { this.m_blockData = i_blockData; this._render(); } else { this.m_blockData = i_blockData; } } /** Toggle maintain aspect ratio **/ _toggleAspectRatio(i_value) { i_value = StringJS(i_value).booleanToNumber() var domPlayerData = this.m_blockData.playerDataDom; var xSnippet = jXML(domPlayerData).find('AspectRatio'); jXML(xSnippet).attr('maintain', i_value); this.bs.setBlockPlayerData(this.m_blockData, domPlayerData) } private _render() { var domPlayerData: XMLDocument = this.m_blockData.playerDataDom if (this.external) { var xSnippet = jXML(domPlayerData).find('LINK'); this.m_formInputs['url'].setValue(xSnippet.attr('src')); } else { var xSnippet = jXML(domPlayerData).find('AspectRatio'); var maintain = StringJS(jXML(xSnippet).attr('maintain')).booleanToNumber(); this.m_formInputs['maintain'].setValue(maintain); } } ngAfterViewInit() { this._render(); } private saveToStore() { con(this.m_contGroup.status + ' ' + JSON.stringify(this.ngmslibService.cleanCharForXml(this.m_contGroup.value))); if (this.m_contGroup.status != 'VALID') return; var domPlayerData: XMLDocument = this.m_blockData.playerDataDom; var xSnippet = jXML(domPlayerData).find('LINK'); xSnippet.attr('src', this.m_contGroup.value.url); this.bs.setBlockPlayerData(this.m_blockData, domPlayerData); } destroy() { } } ================================================ FILE: src/app/blocks/block-prop-weather.ts ================================================ import {AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, Input} from "@angular/core"; import {FormBuilder, FormControl, FormGroup, Validators} from "@angular/forms"; import {BlockService, IBlockData} from "./block-service"; import {Compbaser, NgmslibService} from "ng-mslib"; import {simpleRegExp} from "../../Lib"; import * as _ from "lodash"; @Component({ selector: 'block-prop-weather', host: { '(input-blur)': 'saveToStore($event)' }, changeDetection: ChangeDetectionStrategy.OnPush, template: ` {{me}}

    • Fahrenheit
      Celsius

    • Black
      White
      Color
    • address / zip code
    ` }) export class BlockPropWeather extends Compbaser implements AfterViewInit { m_formInputs = {}; m_contGroup: FormGroup; m_blockData: IBlockData; constructor(private fb: FormBuilder, private cd: ChangeDetectorRef, private bs: BlockService, private ngmslibService: NgmslibService) { super(); this.m_contGroup = fb.group({ 'style': "", 'unit': "", 'address': ['', [Validators.pattern(simpleRegExp)]] }); _.forEach(this.m_contGroup.controls, (value, key: string) => { this.m_formInputs[key] = this.m_contGroup.controls[key] as FormControl; }) } @Input() jsonMode: boolean; @Input() set setBlockData(i_blockData) { if (this.m_blockData && this.m_blockData.blockID != i_blockData.blockID) { this.m_blockData = i_blockData; this._render(); } else { this.m_blockData = i_blockData; } } private _render() { var domPlayerData: XMLDocument = this.m_blockData.playerDataDom var jXMLdata = jXML(domPlayerData).find('Json').find('Data'); this.m_formInputs['unit'].setValue(jXMLdata.attr('unit')); this.m_formInputs['style'].setValue(jXMLdata.attr('style')); this.m_formInputs['address'].setValue(jXMLdata.attr('address')); //this.cd.markForCheck(); } ngAfterViewInit() { this._render(); } private saveToStore() { // console.log(this.m_contGroup.status + ' ' + JSON.stringify(this.ngmslibService.cleanCharForXml(this.m_contGroup.value))); if (this.m_contGroup.status != 'VALID') return; var domPlayerData: XMLDocument = this.m_blockData.playerDataDom; var item = jXML(domPlayerData).find('Json').find('Data'); jXML(item).attr('unit', this.m_contGroup.value.unit); jXML(item).attr('style', this.m_contGroup.value.style); jXML(item).attr('address', this.m_contGroup.value.address); this.bs.setBlockPlayerData(this.m_blockData, domPlayerData); } destroy() { } } ================================================ FILE: src/app/blocks/block-prop-youtube.ts ================================================ import {AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, Input} from "@angular/core"; import {FormBuilder, FormControl, FormGroup, Validators} from "@angular/forms"; import {BlockService, IBlockData} from "./block-service"; import {RedPepperService} from "../../services/redpepper.service"; import {Compbaser, NgmslibService} from "ng-mslib"; import {urlRegExp} from "../../Lib"; import * as _ from "lodash"; @Component({ selector: 'block-prop-youtube', host: { '(input-blur)': 'saveToStore($event)' }, changeDetection: ChangeDetectionStrategy.OnPush, template: ` {{me}}
    • volume
    • video quality
    • region
    • most viewed
      custom list
    • video ids
    ` }) export class BlockPropYouTube extends Compbaser implements AfterViewInit { formInputs = {}; m_contGroup: FormGroup; m_blockData: IBlockData; m_regions = ['US', 'AR', 'AU', 'AT', 'BE', 'BR', 'CA', 'CL', 'CO', 'CZ', 'EG', 'FR', 'DE', 'GB', 'HK', 'HU', 'IN', 'IE', 'IL', 'IT', 'JP', 'JO', 'MY', 'MX', 'MA', 'NL', 'NZ', 'PE', 'PH', 'PL', 'RU', 'SA', 'SG', 'ZA', 'KR', 'ES', 'SE', 'CH', 'TW', 'AE']; constructor(private fb: FormBuilder, private bs: BlockService, private cd: ChangeDetectorRef) { super(); this.m_contGroup = fb.group({ 'volume': [100], 'listType': [0], 'quality': [5], 'region': ['US'], 'customList': [''], 'text': [] }); _.forEach(this.m_contGroup.controls, (value, key: string) => { this.formInputs[key] = this.m_contGroup.controls[key] as FormControl; }) } @Input() set setBlockData(i_blockData) { if (this.m_blockData && this.m_blockData.blockID != i_blockData.blockID) { this.m_blockData = i_blockData; this._render(); } else { this.m_blockData = i_blockData; } } ngAfterViewInit() { this._render(); } _render() { var domPlayerData = this.m_blockData.playerDataDom var xSnippetYouTube = jXML(domPlayerData).find('YouTube'); var xSnippetYouTubeManualList = jXML(domPlayerData).find('VideoIdList'); var videoIDs = jXML(xSnippetYouTubeManualList).text(); var listType = jXML(xSnippetYouTube).attr('listType'); //manually most_viewed var region = jXML(xSnippetYouTube).attr('listRegion'); var volume = parseFloat(xSnippetYouTube.attr('volume')); var quality = jXML(xSnippetYouTube).attr('quality'); this.formInputs['volume'].setValue(volume); this.formInputs['listType'].setValue(listType); this.formInputs['quality'].setValue(quality); this.formInputs['region'].setValue(region); this.formInputs['customList'].setValue(videoIDs); this.cd.markForCheck(); } private saveToStore() { if (this.m_contGroup.status != 'VALID') return; var domPlayerData = this.m_blockData.playerDataDom; var xSnippet = jXML(domPlayerData).find('YouTube'); jXML(xSnippet).attr('volume', this.m_contGroup.value.volume); jXML(xSnippet).attr('quality', this.m_contGroup.value.quality); jXML(xSnippet).attr('listRegion', this.m_contGroup.value.region); jXML(xSnippet).attr('listType', this.m_contGroup.value.listType); jXML(xSnippet).find('VideoIdList').remove(); if (this.m_contGroup.value.listType == 'manually') { jXML(xSnippet).append(jXML(`${this.m_contGroup.value.customList}`)); } else { jXML(xSnippet).find('VideoIdList').remove(); } this.bs.setBlockPlayerData(this.m_blockData, domPlayerData); } destroy() { } } ================================================ FILE: src/app/blocks/block-prop.digg.ts ================================================ import {AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, Input} from "@angular/core"; import {FormBuilder, FormControl, FormGroup, Validators} from "@angular/forms"; import {BlockService, IBlockData} from "./block-service"; import {Compbaser, NgmslibService} from "ng-mslib"; import * as _ from "lodash"; @Component({ selector: 'block-prop-digg', changeDetection: ChangeDetectionStrategy.OnPush, template: ` {{me}}
    Digg, it's what's new on the web...
    ` }) export class BlockPropDigg extends Compbaser implements AfterViewInit { m_formInputs = {}; m_contGroup: FormGroup; m_blockData: IBlockData; constructor(private fb: FormBuilder) { super(); this.m_contGroup = fb.group({ 'token': [''] }); _.forEach(this.m_contGroup.controls, (value, key: string) => { this.m_formInputs[key] = this.m_contGroup.controls[key] as FormControl; }) } @Input() jsonMode: boolean; @Input() set setBlockData(i_blockData) { if (this.m_blockData && this.m_blockData.blockID != i_blockData.blockID) { this.m_blockData = i_blockData; this._render(); } else { this.m_blockData = i_blockData; } } private _render() { var domPlayerData: XMLDocument = this.m_blockData.playerDataDom jXML(domPlayerData).find('Json').find('Data'); } ngAfterViewInit() { this._render(); } destroy() { } } ================================================ FILE: src/app/blocks/block-service.ts ================================================ /** * Block service is responsible only for procedures related to channel and scene blocks */ import {Inject, Injectable} from "@angular/core"; import {YellowPepperService} from "../../services/yellowpepper.service"; import * as X2JS from "x2js"; import * as _ from "lodash"; import {Observable} from "rxjs"; import {CampaignTimelineChanelPlayersModelExt, PlayerDataModelExt} from "../../store/model/msdb-models-extended"; import {RedPepperService} from "../../services/redpepper.service"; import {ResourcesModel} from "../../store/imsdb.interfaces_auto"; import {BLOCK_SERVICE, BlockLabels, PLACEMENT_CHANNEL, PLACEMENT_SCENE} from "../../interfaces/Consts"; import {CommBroker} from "../../services/CommBroker"; import {IUiState} from "../../store/store.data"; import {ACTION_UISTATE_UPDATE} from "../../store/actions/appdb.actions"; //// import X2JS from "x2js"; interface IDomPlayerDataJson { Player: { Data: { Appearance: { _alpha: string; _blendMode: string; } Background: { GradientPoints: { Point: { _color: string; _opacity: string; _midpoint: string; } } _style: string; _gradientType: string; _angle: string; _offsetX: string; _offsetY: string; }; Border: { _borderThickness: string; _borderColor: string; _cornerRadius: string; }; Layout: { _rotation: string; _x: string; _y: string; _width: string; _height: string; }; Resource: { AspectRatio: { _maintain: string; }; Image: string; _resource: string; _hResource: string; } }; _player: string; _label: string; _interactive: string; _locked: string; _id: string; } } export interface ISceneData { scene_id: number; scene_id_pseudo_id: string; scene_native_id: number; playerDataModel: PlayerDataModelExt; domPlayerData: XMLDocument; domPlayerDataJson: IDomPlayerDataJson; domPlayerDataXml: string; block_pseudo_id: any; mimeType?; } export interface IBlockData { blockID: number; blockType: string; blockCode: string; blockName: string; blockDescription: string; blockIcon: string; blockFontAwesome: string; blockAcronym: string; blockMinWidth: number; blockMinHeight: number; playerDataJson: any; playerDataDom: XMLDocument, playerMimeScene: string; playerDataJsonHandle: number; campaignTimelineChanelPlayersModelExt: CampaignTimelineChanelPlayersModelExt, duration: number; offset: number; scene?: { name: string, handle: any, playerDataJson: IDomPlayerDataJson; playerDataDom: XMLDocument }, resource?: { name: string, handle: string, type: string, } } @Injectable() export class BlockService { parser; private m_zIndex = -1; private m_minSize = {w: 50, h: 50}; private m_components = {}; private m_fontAwesome = {}; private m_blockCodes = {}; private m_commBroker; constructor(private commBroker: CommBroker, @Inject('BLOCK_PLACEMENT') private blockPlacement: string, private yp: YellowPepperService, private rp: RedPepperService) { this.parser = new X2JS({ escapeMode: true, attributePrefix: "_", arrayAccessForm: "none", emptyNodeForm: "text", enableToStringFunc: true, arrayAccessFormPaths: [], skipEmptyTextNodesForObj: true }); var self = this; this.m_blockCodes = _.invert(BlockLabels); this.m_fontAwesome = { 'scene': {image: 'fa-pencil-square-o'}, 'qr': {image: 'fa-qrcode'}, 'youtube': {image: 'fa-youtube'}, 'collection': {image: 'fa-stack-exchange'}, 'location': {image: 'fa-map-marker'}, 'fasterq': {image: 'fa-male'}, 'twitter': {image: 'fa-twitter'}, 'twitteritem': {image: 'fa-twitter'}, 'instagram': {image: 'fa-instagram'}, 'json': {image: 'fa-cubes'}, 'jsonitem': {image: 'fa-cubes'}, 'worldweather': {image: 'fa-sun-o'}, 'googlesheets': {image: 'fa-table'}, 'googlecalendar': {image: 'fa-calendar'}, 'digg': {image: 'fa-digg'}, 'rss': {image: 'fa-rss'}, 'mrss': {image: 'fa-rss-square'}, 'flv': {image: 'fa-file-video-o'}, 'mp4': {image: 'fa-video-camera'}, 'png': {image: 'fa-picture-o'}, 'swf': {image: 'fa-bolt'}, 'jpg': {image: 'fa-file-image-o'}, 'svg': {image: 'fa-file-image-o'}, 'html5': {image: 'fa-html5'}, 'clock': {image: 'fa-clock-o'}, 'label': {image: 'fa-file-text-o'}, 'extvideo': {image: 'fa-share-square-o'}, 'extimage': {image: 'fa-share-square '} }; this.m_components = { 3100: { name: 'Video', //color: '#D50D36', color: '#E5E5E5', acronym: 'video', description: 'Movie file', svg: new String('backgroundLayer 1'), fontAwesome: self.getFontAwesome('mp4'), getDefaultPlayerData: function (i_placement, i_resourceID) { return '' + '' + self.getCommonDefaultXML() + self.getCommonSceneLayout(i_placement) + '' + '' + '' + '' + ''; }, ext: [ 0, 'flv', 1, 'mp4' ] }, 3130: { name: 'Image', //color: '#24870D', color: '#E5E5E5', acronym: 'Image', description: 'Bitmap file', fontAwesome: self.getFontAwesome('jpg'), svg: new String('background Layer 1'), getDefaultPlayerData: function (i_placement, i_resourceID) { return '' + '' + self.getCommonDefaultXML() + self.getCommonSceneLayout(i_placement) + '' + '' + '' + '' + '' + ''; }, ext: [ 0, 'png', 1, 'jpg', 2, 'swf' ] }, 3140: { name: 'SVG', //color: '#24870D', color: '#E5E5E5', acronym: 'SVG', description: 'SVG file', svg: new String('background Layer 1'), getDefaultPlayerData: function (i_placement, i_resourceID) { return '' + '' + self.getCommonDefaultXML() + self.getCommonSceneLayout(i_placement) + '' + '' + '' + '' + '' + ''; }, ext: [ 0, 'svg' ] }, 3160: { name: 'External image', //color: '#B6B5A1', color: '#E5E5E5', acronym: 'ext image', description: 'Download images and Flash swf from external web links', svg: new String('background Layer 1 '), getDefaultPlayerData: function (i_placement) { return '' + '' + self.getCommonDefaultXML() + self.getCommonSceneLayout(i_placement) + '' + '' + '' }, fontAwesome: self.getFontAwesome('extimage') }, 3150: { name: 'External video', //color: '#5BB5E8', color: '#E5E5E5', acronym: 'ext video', description: 'Download video from external web links', svg: new String('background Layer 1 '), getDefaultPlayerData: function (i_placement) { return '' + '' + self.getCommonDefaultXML() + self.getCommonSceneLayout(i_placement) + '' + '' + '' }, fontAwesome: self.getFontAwesome('extvideo') }, 3430: { name: 'QR', app_id: '10160', color: '#E5E5E5', acronym: 'qr', description: 'QR code for mobile device integration', svg: new String('backgroundLayer 1'), getDefaultPlayerData: function (i_placement) { return '' + '' + self.getCommonDefaultXML() + self.getCommonSceneLayout(i_placement) + '' + '' + '' }, fontAwesome: self.getFontAwesome('qr') }, 4600: { name: 'YouTube', app_id: '10220', color: '#E5E5E5', acronym: 'YouTube', description: 'YouTube - broadcast yourself', svg: new String(''), getDefaultPlayerData: function (i_placement) { return '' + '' + self.getCommonDefaultXML() + self.getCommonSceneLayout(i_placement) + '' + '' + '' }, fontAwesome: self.getFontAwesome('youtube') }, 4100: { name: 'Collection', app_id: '10180', color: '#E5E5E5', acronym: 'Collection', description: 'Collection - just list your content', svg: new String(' '), getDefaultPlayerData: function (i_placement) { return '' + '' + self.getCommonDefaultXML() + self.getCommonSceneLayout(i_placement) + '' + '' + '' + '' + '' }, fontAwesome: self.getFontAwesome('collection') }, 4105: { name: 'Location', app_id: '10185', color: '#E5E5E5', acronym: 'Location', description: 'Location - content by GPS coordinates', svg: new String(' '), getDefaultPlayerData: function (i_placement) { return '' + '' + self.getCommonDefaultXML() + self.getCommonSceneLayout(i_placement) + '' + '' + '' + '' + '' + '' }, fontAwesome: self.getFontAwesome('location') }, 6100: { name: 'FasterQ', app_id: '12100', color: '#E5E5E5', acronym: 'FasterQ', description: 'FasterQ - smart lines', svg: new String(' '), getDefaultPlayerData: function (i_placement) { return '' + '' + self.getCommonDefaultXML() + self.getCommonSceneLayout(i_placement) + '' + '' + '' + '' + '' }, fontAwesome: self.getFontAwesome('fasterq') }, 4500: { name: 'Twitter', app_id: '10210', color: '#E5E5E5', acronym: 'Twitter', description: 'Twitter', svg: '', getDefaultPlayerData: function (i_placement) { return '' + '' + self.getCommonDefaultXML() + self.getCommonSceneLayout(i_placement) + '' + '' + '' + '' + '' }, fontAwesome: self.getFontAwesome('twitter') }, 4505: { name: 'Twitter Item', app_id: '10210', color: '#E5E5E5', acronym: 'Twitter', description: 'Twitter', svg: new String(' '), getDefaultPlayerData: function (i_placement) { return '' + '' + self.getCommonDefaultXML() + self.getCommonSceneLayout(i_placement) + '' + '' + '' + '' + '' }, fontAwesome: self.getFontAwesome('twitteritem') }, 4300: { name: 'JS Object player', app_id: '10195', color: '#E5E5E5', acronym: 'JSON', description: 'JSON', svg: '', getDefaultPlayerData: function (i_placement) { return '' + '' + self.getCommonDefaultXML() + self.getCommonSceneLayout(i_placement) + '' + '' + '' + '' + '' + '' + '' }, fontAwesome: self.getFontAwesome('json') }, 4310: { name: 'JS Object item', app_id: '10195', color: '#E5E5E5', acronym: 'JSON item', description: 'JSON item', svg: new String(' '), getDefaultPlayerData: function (i_placement) { return '' + '' + self.getCommonDefaultXML() + self.getCommonSceneLayout(i_placement) + '' + '' + '' + '' + '' }, fontAwesome: self.getFontAwesome('jsonitem') }, 6010: { name: 'Weather', app_id: '12010', color: '#E5E5E5', acronym: 'weather', description: 'World Weather', mimeType: 'Json.weather', jsonItemLongDescription: 'Include a seven day extended weather forecast in your presentation, including highs, lows, humidity, wind conditions and more. Pick from white, black or color icons', svg: new String(' '), getDefaultPlayerData: function (i_placement) { return '' + '' + self.getCommonDefaultXML() + self.getCommonSceneLayout(i_placement) + '' + '' + '' + '' + '' + '' + '' }, fontAwesome: self.getFontAwesome('worldweather') }, 6022: { name: 'Sheets', app_id: '12032', color: '#E5E5E5', acronym: 'Sheets', description: 'Google Sheets', mimeType: 'Json.spreadsheet', jsonItemLongDescription: 'Easily link Scene labels to Google spreadsheet cells simply by specifying the column and row you want to link to, on the Google cloud spreadsheet. Very flexible, easy to add and even easier to edit', svg: '', getDefaultPlayerData: function (i_placement) { return '' + '' + self.getCommonDefaultXML() + self.getCommonSceneLayout(i_placement) + '' + '' + '' + '' + '' + '' + '' }, fontAwesome: self.getFontAwesome('googlesheets') }, 6020: { name: 'Calendar', app_id: '12030', color: '#E5E5E5', acronym: 'Calendar', description: 'Google Calendar', mimeType: 'Json.calendar', jsonItemLongDescription: 'With the Google Calendar component you can simply select only the Calendar you want and set a specific date range relative to today, allowing your content to always stay relevant', svg: '', getDefaultPlayerData: function (i_placement) { return '' + '' + self.getCommonDefaultXML() + self.getCommonSceneLayout(i_placement) + '' + '' + '' + '' + '' + '' + '' }, fontAwesome: self.getFontAwesome('googlecalendar') }, 6230: { name: 'Twitter V3', app_id: '12230', color: '#E5E5E5', acronym: 'Twitter V3', description: 'Twitter V3', mimeType: 'Json.twitter', jsonItemLongDescription: 'Get live tweets from your favorite twitter account, filter by a twitter user name and mix images and other fields in your Twitter scene', svg: new String(' '), getDefaultPlayerData: function (i_placement) { return '' + '' + self.getCommonDefaultXML() + self.getCommonSceneLayout(i_placement) + '' + '' + '' + '' + '' + '' + '' }, fontAwesome: self.getFontAwesome('twitter') }, 6050: { name: 'Instagram', app_id: '12060', color: '#E5E5E5', acronym: 'Instagram', description: 'Instagram', mimeType: 'Json.instagram.feed', jsonItemLongDescription: 'Get a live feed from your favorite Instagram account. Load data in real time and watch your posts come to life inside your digital signage presentation', svg: new String(''), getDefaultPlayerData: function (i_placement) { return '' + '' + self.getCommonDefaultXML() + self.getCommonSceneLayout(i_placement) + '' + '' + '' + '' + '' + '' + '' }, fontAwesome: self.getFontAwesome('instagram') }, 6000: { name: 'Digg', app_id: '12000', color: '#E5E5E5', acronym: 'Digg', description: 'Digg', mimeType: 'Json.digg', jsonItemLongDescription: 'In one click add live content from Digg.com and keep your screen always fresh with live a feed that includes images and news', svg: new String(' '), getDefaultPlayerData: function (i_placement) { return '' + '' + self.getCommonDefaultXML() + self.getCommonSceneLayout(i_placement) + '' + '' + '' + '' + '' + '' + '' }, fontAwesome: self.getFontAwesome('digg') }, 3340: { name: 'Multimedia RSS', //color: '#AAE4E0', color: '#E5E5E5', acronym: 'mrss', description: 'multimedia video stream', svg: new String('background Layer 1 '), getDefaultPlayerData: function (i_placement) { return '' + '' + self.getCommonDefaultXML() + self.getCommonSceneLayout(i_placement) + '' + '' + '' }, fontAwesome: self.getFontAwesome('mrss') }, 3235: { name: 'HTML Website content', //color: '#FD6060', color: '#E5E5E5', acronym: 'web', description: 'HTML5 web integration', svg: new String('background Layer 1 '), getDefaultPlayerData: function (i_placement) { return '' + '' + self.getCommonDefaultXML() + self.getCommonSceneLayout(i_placement) + '' + '' + '' }, fontAwesome: self.getFontAwesome('html5') }, 3320: { name: 'Clock Date/Time', //color: '#8AC697', color: '#E5E5E5', acronym: 'clock', description: 'Set live local date and time', svg: new String('backgroundLayer 1'), getDefaultPlayerData: function (i_placement) { return '' + '' + self.getCommonDefaultXML() + self.getCommonSceneLayout(i_placement) + '' + '' + '' + '' + '' }, fontAwesome: self.getFontAwesome('clock'), getDateTimeMask: function (i_mask) { switch (i_mask) { case 'longDateAndTime': { return 'EEEE, MMM. D, YYYY at L:NN A'; } case 'longDate': { return 'EEEE, MMM. D, YYYY'; } case 'shortDayTime': { return 'EEEE L:NN A'; } case 'date': { return 'MM/DD/YY'; } case 'time': { return 'J:NN:SS A'; } } } }, 3241: { name: 'Label text', //color: '#B3F26A', color: '#E5E5E5', acronym: 'text', description: 'Label editor with custom text properties', svg: new String('backgroundLayer 1'), getDefaultPlayerData: function (i_placement) { return '' + '' + self.getCommonDefaultXML() + self.getCommonSceneLayout(i_placement) + '' + '' + '' }, fontAwesome: self.getFontAwesome('label') }, 3510: { name: 'Scene', //color: '#BCDEB1', color: '#E5E5E5', acronym: 'scene', description: 'A Scene editor', svg: new String('backgroundLayer 1'), getDefaultPlayerData: function (i_placement) { return '' + '' + self.getCommonSceneDefaultXML() + self.getCommonSceneLayout(i_placement, 600, 400) + '' + '' + '' + '' + '' + '' + '' + '' + '' }, fontAwesome: self.getFontAwesome('scene') }, 3345: { name: 'Really Simple Syndication', //color: '#FDA401', color: '#E5E5E5', acronym: 'rss', description: 'RSS for daily fresh scrolling news feed', svg: new String('background Layer 1 '), getDefaultPlayerData: function (i_placement) { return '' + '' + self.getCommonDefaultXML() + self.getCommonSceneLayout(i_placement) + '' + '' + '<Font fontSize="16" fontColor="65280" fontFamily="Arial" fontWeight="normal" fontStyle="normal" textDecoration="none" textAlign="left" />' + '' + '' + '' + '' + '' + '' + '' }, fontAwesome: self.getFontAwesome('rss') } }; this.commBroker.setService(BLOCK_SERVICE, this) } getBlockNameByCode(i_blockCode: number): string { return this.m_blockCodes[i_blockCode]; } /** Get the common layout which only applies when block is inside a scene @method getCommonSceneLayout @param {string} i_placement @return {String} common xml **/ getCommonSceneLayout(i_placement, w ?, h ?) { w = w == undefined ? 100 : w; h = h == undefined ? 100 : h; if (i_placement == PLACEMENT_CHANNEL) return ''; return ''; } /** Get the common properties XML with all default values @method getCommonDefaultXML @return {String} common xml **/ getCommonDefaultXML() { var self = this; var common = '' + self.getCommonBackgroundXML() + self.getCommonBorderXML(); return common; } /** Get the common properties Scene XML with all default values @method getCommonSceneDefaultXML @return {String} common xml **/ getCommonSceneDefaultXML() { var self = this; var common = '' + self.getCommonBorderXML(); return common; } /** Get the common border XML with all default values @method getCommonBorderXML @return {String} common xml **/ getCommonBorderXML() { return ''; } /** Get the common properties XML with all default values @method getCommonBackgroundXML @return {String} common xml **/ getCommonBackgroundXML() { var common = ` ` return common; } /** Get a component data structure and properties for a particular component id. @method getBlockBoilerplate @param {Number} i_blockID @return {Object} return the data structure of a specific component **/ getBlockBoilerplate(i_blockID) { var self = this; return self.m_components[i_blockID]; } /** Translate a mimeType to a font-awesome icon of generic icons if does not exist @method getIconFromMimeType @param {Number} i_playerData @return {String} foundMimeIcon **/ getIconFromMimeType(i_mimeType) { var self = this; var foundMimeIcon = 'fa-star'; var blocks = self.getBlocks(); _.forEach(blocks, function (block: any) { if (block.mimeType && block.mimeType == i_mimeType) foundMimeIcon = block.fontAwesome; }); return foundMimeIcon } /** Get the entire set data structure for all components. @method getBlocks @return {Object} return all data structure **/ getBlocks() { var self = this; return self.m_components; } /** Retrieve a component code from a file extension type (i.e.: flv > 3100). @method getBlockCodeFromFileExt @param {String} i_fileExtension @return {Number} return component code **/ getBlockCodeFromFileExt(i_fileExtension) { var self = this; for (var code in self.m_components) { if (self.m_components[code]['ext'] != undefined) { for (var i = 0; i < self.m_components[code]['ext'].length; i++) { if (self.m_components[code]['ext'][i] == i_fileExtension) { return code; } } } } return -1; } /** Get the font awesome path @method getFontAwesome @param {String} i_fontName @return {String} url path **/ getFontAwesome(i_fontName) { var self = this; if (_.isUndefined((self.m_fontAwesome[i_fontName]))) return undefined; return self.m_fontAwesome[i_fontName]['image']; } /** Get the entire font awesome set @method getFontsAwesome @return {Object} data set **/ getFontsAwesome() { var self = this; return self.m_fontAwesome; } /** Convert player data to json format @method playerDataTojson @param {String} i_playerData @return {Json} jPlayerData **/ playerDataTojson(i_playerData) { // var x2js = BB.comBroker.getService('compX2JS'); // var jPlayerData = x2js.xml_str2json(i_playerData); // return jPlayerData; } /** Convert player data to xml format @method playerDataToxml @param {String} i_playerData @return {XML} xml data **/ playerDataToxml(i_playerData) { // var x2js = BB.comBroker.getService('compX2JS'); // var xPlayerData = x2js.json2xml_str(i_playerData); // return xPlayerData; } public getServiceType(): string { return this.blockPlacement; } public getBlockDataInScene(i_sceneData: ISceneData): Observable { let domPlayerData = i_sceneData.domPlayerData let playerMimeScene = i_sceneData.mimeType; let playerDataJson = this.parser.xml2js(i_sceneData.domPlayerDataXml); let code = playerDataJson['Player']['_player']; let blockType: any = this.getBlockNameByCode(code) var sceneData = { name: '', handle: i_sceneData.scene_id, playerDataJson: null, playerDataDom: null } var data: IBlockData = { blockID: i_sceneData.block_pseudo_id, blockType: blockType, blockCode: code, blockName: this.getBlockBoilerplate(code).name, blockDescription: this.getBlockBoilerplate(code).description, blockIcon: this.getBlockBoilerplate(code).icon, blockFontAwesome: this.getBlockBoilerplate(code).fontAwesome, blockAcronym: this.getBlockBoilerplate(code).acronym, blockMinWidth: this.m_minSize.w, blockMinHeight: this.m_minSize.h, playerDataDom: domPlayerData, playerDataJson: playerDataJson, playerMimeScene: playerMimeScene, playerDataJsonHandle: null, duration: -1, offset: -1, campaignTimelineChanelPlayersModelExt: null, scene: sceneData }; return Observable.of(data); } public getBlockData(blockId): Observable { return this.yp.getChannelBlockRecord(blockId) .mergeMap((i_campaignTimelineChanelPlayersModel: CampaignTimelineChanelPlayersModelExt) => { // var t0 = performance.now(); var xml = i_campaignTimelineChanelPlayersModel.getPlayerData(); var code: any; var playerMimeScene; var playerDataJsonHandle; var playerDataDom = $.parseXML(xml); let playerDataJson = this.parser.xml2js(xml); if (playerDataJson['Player']['_player']) { /************************************ * Block ************************************/ code = playerDataJson['Player']['_player']; var blockType = this.getBlockNameByCode(code) playerMimeScene = playerDataJson.Player.Data.Json ? `Json.${playerDataJson.Player.Data.Json._providerType}` : null; playerDataJsonHandle = (playerDataJson.Player.Data.Json && playerDataJson.Player.Data.Json.Player) ? playerDataJson.Player.Data.Json.Player : null; if (_.isUndefined(blockType)) { var e = `Panic using a component / block which is not supported yet ${code} ${blockType}`; throw new Error(e) } // console.log(`Serialization of block ${code} took ${(performance.now() - t0)} milliseconds`) } else { /************************************ * Block is a scene ************************************/ code = BlockLabels['BLOCKCODE_SCENE']; var blockType = this.getBlockNameByCode(code) var sceneData = { name: '', handle: jXML(playerDataDom).find('Player').attr('hDataSrc'), playerDataJson: null, playerDataDom: null } } var data: IBlockData = { blockID: blockId, blockType: blockType, blockCode: code, blockName: this.getBlockBoilerplate(code).name, blockDescription: this.getBlockBoilerplate(code).description, blockIcon: this.getBlockBoilerplate(code).icon, blockFontAwesome: this.getBlockBoilerplate(code).fontAwesome, blockAcronym: this.getBlockBoilerplate(code).acronym, blockMinWidth: this.m_minSize.w, blockMinHeight: this.m_minSize.h, playerDataDom: playerDataDom, playerDataJson: playerDataJson, playerMimeScene: playerMimeScene, playerDataJsonHandle: playerDataJsonHandle, duration: i_campaignTimelineChanelPlayersModel.getPlayerDurationInt(), offset: i_campaignTimelineChanelPlayersModel.getPlayerOffsetTimeInt(), campaignTimelineChanelPlayersModelExt: i_campaignTimelineChanelPlayersModel, scene: sceneData }; return Observable.of(data) }).mergeMap((blockData: any) => { /** additional data for: scenes **/ if (blockData.scene) { return this.yp.getScenePlayerdataDom(blockData.scene.handle) .map((xml: string) => { var domPlayerData = $.parseXML(xml) blockData.scene.name = jXML(domPlayerData).find('Player').eq(0).attr('label'); blockData.scene.playerDataDom = domPlayerData; blockData.scene.playerDataJson = this.parser.xml2js(xml); return blockData; }) } else { return Observable.of(blockData); } /** additional data for: resources (images, videos, svg ) fill additional data (make exception for collection | location) **/ }).mergeMap((blockData: IBlockData) => { if (Number(blockData.blockCode) == BlockLabels.BLOCKCODE_COLLECTION || Number(blockData.blockCode) == BlockLabels.LOCATION) return Observable.of(blockData); var domPlayerData = blockData.playerDataDom; var xSnippet = jXML(domPlayerData).find('Resource'); if (xSnippet.length > 0) { blockData.resource = { handle: jXML(xSnippet).attr('hResource'), name: '', type: '' }; return this.yp.getResourceRecord(blockData.resource.handle) .map((i_resourcesModel: ResourcesModel) => { // todo: temp hack till resolved if (_.isUndefined(i_resourcesModel)) { blockData.resource.name = '???'; blockData.resource.type = ''; } else { blockData.resource.name = i_resourcesModel.getResourceName() blockData.resource.type = i_resourcesModel.getResourceType() } return blockData; }) } return Observable.of(blockData); }) } /** Help method to get the proper playerDataDom depending of we are dealing with a regular block or a scene block **/ getBlockPlayerData(i_blockData: IBlockData): XMLDocument { switch (this.blockPlacement) { case PLACEMENT_CHANNEL: { switch (Number(i_blockData.blockCode)) { case BlockLabels.BLOCKCODE_SCENE: { return i_blockData.scene.playerDataDom; } default: { return i_blockData.playerDataDom; } } } case PLACEMENT_SCENE: { switch (Number(i_blockData.blockCode)) { case BlockLabels.BLOCKCODE_SCENE: { return i_blockData.playerDataDom; } default: { return i_blockData.playerDataDom; } } } } } notifySceneBgChanged() { this.commBroker.fire({event: 'SCENE_CHANGE', fromInstance: this, message: []}); } notifySceneBlockChanged(i_block: string | IBlockData) { if (i_block['blockID']) i_block = i_block['blockID']; this.commBroker.fire({event: 'SCENE_BLOCK_CHANGE', fromInstance: this, message: [i_block]}); } notifyReloadScene(i_sceneId) { var uiState: IUiState = {scene: {sceneSelected: -1, blockSelected: -1}} this.yp.ngrxStore.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})) var uiState: IUiState = {scene: {sceneSelected: i_sceneId, blockSelected: -1}} this.yp.ngrxStore.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})) // if (i_block['blockID']) i_block = i_block['blockID']; // this.commBroker.fire({event: 'LOAD_SCENE', fromInstance: this, message: [i_block]}); } /** Update the msdb for the block with new values inside its player_data **/ setBlockPlayerData(blockData: IBlockData, i_xmlDoc: XMLDocument | string) { var player_data: string, player_data_json; if (i_xmlDoc instanceof XMLDocument) { player_data = (new XMLSerializer()).serializeToString(i_xmlDoc as XMLDocument); } else { player_data = i_xmlDoc as string; } player_data_json = this.parser.xml2js(player_data); player_data = this.rp.ieFixEscaped(player_data); var playerCode = Number(player_data_json.Player._player); switch (this.blockPlacement) { case PLACEMENT_CHANNEL: { switch (playerCode) { /***** scene *****/ case BlockLabels.BLOCKCODE_SCENE: { this.yp.getChannelBlockRecord(blockData.blockID) .subscribe((i_campaignTimelineChanelPlayersModel: CampaignTimelineChanelPlayersModelExt) => { var domPlayerData = $.parseXML(i_campaignTimelineChanelPlayersModel.getPlayerData()); var scene_id = jXML(domPlayerData).find('Player').attr('hDataSrc'); this.rp.setScenePlayerData(scene_id, player_data); }) break; } /***** all block *****/ default: { this.rp.setCampaignTimelineChannelPlayerRecord(blockData.blockID, 'player_data', player_data); } } break; } case PLACEMENT_SCENE: { switch (playerCode) { /***** scene *****/ case BlockLabels.BLOCKCODE_SCENE: { this.rp.setScenePlayerData(blockData.blockID, player_data); break; } /***** all blocks *****/ default: { this.rp.setScenePlayerdataBlock(blockData.scene.handle, blockData.blockID, player_data); } } break; } } this.rp.reduxCommit(); } } // export const MapMimeProviders = { // 'weather': 'Json.weather', // 'instagram.feed': 'Json.instagram.feed', // } //instagram.media ================================================ FILE: src/app/blocks/json-event-grid.ts ================================================ import {AfterViewInit, ChangeDetectionStrategy, Component, Input, ViewChild} from "@angular/core"; import {Compbaser} from "ng-mslib"; import {YellowPepperService} from "../../services/yellowpepper.service"; import {ISimpleGridEdit} from "../../comps/simple-grid-module/SimpleGrid"; import {StoreModel} from "../../store/model/StoreModel"; import {BlockService, IBlockData} from "./block-service"; import {SimpleGridRecord} from "../../comps/simple-grid-module/SimpleGridRecord"; import {SimpleGridTable} from "../../comps/simple-grid-module/SimpleGridTable"; import {List} from "immutable"; import * as _ from "lodash"; export class JsonEventResourceModel extends StoreModel { constructor(data: { rowIndex: number; checkbox: boolean; event: string; pageName: string; action: string; }) { super(data); } } @Component({ selector: 'json-event-grid', changeDetection: ChangeDetectionStrategy.OnPush, template: ` {{me}}

    event action go to
    ` }) export class JsonEventGrid extends Compbaser implements AfterViewInit { m_blockData: IBlockData; m_events: List; m_actions: List; m_collectionList: List; m_mode: 'url' | 'page'; m_jsonEventResources: Array; constructor(private yp: YellowPepperService, private bs: BlockService) { super(); this.m_actions = List([ new StoreModel({name: 'firstPage'}), new StoreModel({name: 'nextPage'}), new StoreModel({name: 'prevPage'}), new StoreModel({name: 'lastPage'}), new StoreModel({name: 'loadUrl'}), new StoreModel({name: 'selectPage'}) ]); } @ViewChild('simpleGrid') simpleGrid: SimpleGridTable; @Input() set setBlockData(i_blockData) { this.m_blockData = i_blockData; this._render(); } @Input() set resources(i_jsonEventResources: Array) { this.m_jsonEventResources = i_jsonEventResources; } @Input() set collectionList(i_collectionList) { this.m_collectionList = i_collectionList; } @Input() set showOption(i_value: 'url' | 'page') { this.m_mode = i_value; if (i_value == 'url') { this.m_actions = this.m_actions.filter((v: StoreModel) => { return v.getKey('name') != 'selectPage'; }) as List; return; } if (i_value == 'page') { this.m_actions = this.m_actions.filter((v: StoreModel) => { return v.getKey('name') != 'loadUrl'; }) as List; return; } } _render() { this._initEventTable(); } /** Load event list to block props UI @method _initEventTable **/ _initEventTable() { var rowIndex = 0; var domPlayerData = this.m_blockData.playerDataDom; var events = []; jXML(domPlayerData).find('EventCommands').children().each((k, eventCommand) => { var url = ''; if (jXML(eventCommand).attr('command') == 'loadUrl') url = jXML(eventCommand).find('Url').attr('name'); if (jXML(eventCommand).attr('command') == 'selectPage') url = jXML(eventCommand).find('Page').attr('name'); if (_.isUndefined(url) || _.isEmpty(url)) url = '---'; var storeModel = new StoreModel({ id: rowIndex, event: jXML(eventCommand).attr('from'), url: url, action: jXML(eventCommand).attr('command') }); events.push(storeModel) rowIndex++; }); this.m_events = List(events) } _setAction(event: ISimpleGridEdit, index: number) { var domPlayerData = this.m_blockData.playerDataDom; var target = jXML(domPlayerData).find('EventCommands').children().get(index); jXML(target).attr('command', event.value); this.bs.setBlockPlayerData(this.m_blockData, domPlayerData); } _onRemoveEvent() { var record: SimpleGridRecord = this.simpleGrid.getSelected(); if (_.isUndefined(record)) return; var domPlayerData = this.m_blockData.playerDataDom; jXML(domPlayerData).find('EventCommands').children().eq(record.index).remove(); this.bs.setBlockPlayerData(this.m_blockData, domPlayerData) } _onAddNewEvent() { var domPlayerData = this.m_blockData.playerDataDom; var buff = ''; jXML(domPlayerData).find('EventCommands').append(jXML(buff)); // domPlayerData = this.rp.xmlToStringIEfix(domPlayerData) this.bs.setBlockPlayerData(this.m_blockData, domPlayerData); } _selectedAction() { return (a: StoreModel, b: StoreModel) => { return a.getKey('name') == b.getKey('action') ? 'selected' : ''; } } _selectedResource() { return (a: StoreModel, b: StoreModel) => { return a.getKey('name') == b.getKey('url') ? 'selected' : ''; } } _onUrlEdited(event: ISimpleGridEdit, index) { var url = event.value; var domPlayerData = this.m_blockData.playerDataDom; var target = jXML(domPlayerData).find('EventCommands').children().get(parseInt(index)); jXML(target).find('Params').remove(); jXML(target).append(' '); this.bs.setBlockPlayerData(this.m_blockData, domPlayerData); } _onPageEdited(event: ISimpleGridEdit, index) { var page = event.value; var domPlayerData = this.m_blockData.playerDataDom; var target = jXML(domPlayerData).find('EventCommands').children().get(parseInt(index)); jXML(target).find('Params').remove(); jXML(target).append(''); this.bs.setBlockPlayerData(this.m_blockData, domPlayerData); } _onLabelEdited(event: ISimpleGridEdit, index) { var domPlayerData = this.m_blockData.playerDataDom; var target = jXML(domPlayerData).find('EventCommands').children().get(index); jXML(target).attr('from', event.value); this.bs.setBlockPlayerData(this.m_blockData, domPlayerData); } ngAfterViewInit() { } ngOnInit() { } destroy() { } } ================================================ FILE: src/app/campaigns/add-content.ts ================================================ import {AfterViewInit, Component, EventEmitter, Inject, Input, Output} from "@angular/core"; import {Compbaser} from "ng-mslib"; import {YellowPepperService} from "../../services/yellowpepper.service"; import {IUiState} from "../../store/store.data"; import {ACTION_UISTATE_UPDATE, SideProps} from "../../store/actions/appdb.actions"; import {BlockService, ISceneData} from "../blocks/block-service"; import {UserModel} from "../../models/UserModel"; import {ResourcesModel} from "../../store/imsdb.interfaces_auto"; import {IAddContents} from "../../interfaces/IAddContent"; import {BlockTypeEnum} from "../../interfaces/BlockTypeEnum"; import {BlockLabels, Consts, PLACEMENT_CHANNEL, PLACEMENT_LISTS, PLACEMENT_SCENE} from "../../interfaces/Consts"; import {CommBroker} from "../../services/CommBroker"; import {ADD_NEW_BLOCK_SCENE} from "../scenes/scene-editor"; import {Lib} from "../../Lib"; import {List} from "immutable"; import * as _ from "lodash"; import {BehaviorSubject} from "rxjs/BehaviorSubject"; import {Subject} from "rxjs/Subject"; import {Observer} from "rxjs/Observer"; import {Observable} from "rxjs/Observable"; @Component({ selector: 'add-content', styles: [` .nowAllowed { opacity: 0.4; } .btn-primary { position: relative; top: -45px } li:hover { background-color: #dadada; cursor: pointer; } `], template: ` {{me}}
    • {{component.name}}

      {{component.description}}
    • {{resource.name}}

      {{resource.description}}
    • {{scene.name}}

    ` }) export class AddContent extends Compbaser implements AfterViewInit { m_placement; m_sceneMime; m_filter; m_userModel: UserModel; m_resourceModels: List; m_sceneDatas: Array; m_componentList: Array = []; m_resourceList: Array = []; m_sceneList: Array = []; m_PLACEMENT_SCENE = PLACEMENT_SCENE; m_PLACEMENT_LISTS = PLACEMENT_LISTS; m_PLACEMENT_CHANNEL = PLACEMENT_CHANNEL; constructor(@Inject('HYBRID_PRIVATE') private hybrid_private: boolean, private commBroker: CommBroker, private yp: YellowPepperService, private bs: BlockService) { super(); this.cancelOnDestroy( // this.yp.getUserModel() .subscribe((i_userModel: UserModel) => { this.m_userModel = i_userModel; }, (e) => console.error(e)) ) this.cancelOnDestroy( // this.yp.listenResources() .subscribe((i_resources: List) => { this.m_resourceModels = i_resources; }, (e) => console.error(e)) ) this.cancelOnDestroy( // this.yp.getScenes() .subscribe((i_playerDatas: Array) => { this.m_sceneDatas = i_playerDatas; }, (e) => console.error(e)) ) } @Input() set placement(i_value) { this.m_placement = i_value; switch (i_value) { case PLACEMENT_LISTS: { break; } case PLACEMENT_CHANNEL: { var uiState: IUiState = {uiSideProps: SideProps.miniDashboard} this.yp.ngrxStore.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})) break; } case PLACEMENT_SCENE: { var uiState: IUiState = {uiSideProps: SideProps.miniDashboard} this.yp.ngrxStore.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})) break; } } this._render(); } // @Output() // onAddContentSelected: EventEmitter = new EventEmitter(); // @Output() // onClosed: EventEmitter = new EventEmitter(); @Output() onClosed: Observable = new Subject().debounceTime(200).delay(333); @Output() onAddContentSelected: Observable = new Subject().debounceTime(200); _addBlock(i_addContents: IAddContents) { switch (this.m_placement) { case PLACEMENT_CHANNEL: { (this.onAddContentSelected as Subject).next(i_addContents) // this.onAddContentSelected.emit(i_addContents) break; } case PLACEMENT_LISTS: { (this.onAddContentSelected as Subject).next(i_addContents) // this.onAddContentSelected.emit(i_addContents) break; } case PLACEMENT_SCENE: { this.commBroker.fire({event: ADD_NEW_BLOCK_SCENE, fromInstance: this, message: i_addContents}); break; } } } ngAfterViewInit() { this._render(); } _onUpgEnterprise(event: MouseEvent) { event.stopImmediatePropagation(); event.preventDefault(); this.commBroker.fire({event: Consts.Events().UPGRADE_ENTERPRISE, fromInstance: this, message: ''}); } _onComponentSelected(i_component) { if (!i_component.allow) return bootbox.alert('Please upgrade to the Enterprise edition') this._addBlock(i_component); this._close(); } _onResourceSelected(i_resource) { this._addBlock(i_resource); this._close(); } _onSceneSelected(i_scene) { this._addBlock(i_scene); this._close(); } _close() { (this.onClosed as Subject).next(); } /** Build lists of components, resources and scenes (respectively showing what's needed per placement mode) Once an LI is selected proper event fired to announce block is added. @method _render @return none **/ _render() { this.m_componentList = []; this.m_sceneList = [] ///////////////////////////////////////////////////////// // component selection list ///////////////////////////////////////////////////////// var components = this.bs.getBlocks(); var specialJsonItemName = ''; var specialJsonItemColor = ''; //var sceneHasMimeType = ''; for (var i_componentID in components) { var componentID: any = i_componentID; if (componentID == BlockLabels.BLOCKCODE_IMAGE || componentID == BlockLabels.BLOCKCODE_SVG || componentID == BlockLabels.BLOCKCODE_TWITTER || componentID == BlockLabels.BLOCKCODE_TWITTER_ITEM || componentID == BlockLabels.BLOCKCODE_VIDEO || componentID == BlockLabels.BLOCKCODE_SCENE || (this.m_placement == PLACEMENT_CHANNEL && componentID == BlockLabels.BLOCKCODE_JSON_ITEM) || (this.m_placement == PLACEMENT_CHANNEL && componentID == BlockLabels.BLOCKCODE_TWITTER_ITEM) || (this.m_placement == PLACEMENT_SCENE && componentID == BlockLabels.BLOCKCODE_JSON) || (this.m_placement == PLACEMENT_SCENE && componentID == BlockLabels.BLOCKCODE_WORLD_WEATHER) || (this.m_placement == PLACEMENT_SCENE && componentID == BlockLabels.BLOCKCODE_GOOGLE_SHEETS) || (this.m_placement == PLACEMENT_SCENE && componentID == BlockLabels.BLOCKCODE_TWITTER)) { continue; } // if PLACEMENT_SCENE and mimetype is set to specific, don't show any JSON based players if (this.m_sceneMime && this.m_placement == PLACEMENT_SCENE) { var jsonBasedPlayerXML = this.bs.getBlockBoilerplate(componentID).getDefaultPlayerData(PLACEMENT_SCENE); jsonBasedPlayerXML = $.parseXML(jsonBasedPlayerXML); if ($(jsonBasedPlayerXML).find('Json').length > 0) continue; } // if PLACEMENT_SCENE and mimetype is set on scene, give special attention to JSON_ITEM component since it will often be the one user needs if (this.m_sceneMime && this.m_placement == PLACEMENT_SCENE && componentID == BlockLabels.BLOCKCODE_JSON_ITEM) { specialJsonItemName = Lib.CapitaliseFirst(this.m_sceneMime.split('.')[1]); // specialJsonItemColor = BB.CONSTS['THEME'] === 'light' ? '#A9CFFA' : '#262627'; } else { specialJsonItemName = ''; specialJsonItemColor = ''; } // check if and how to render components depending on user account type var status = this._checkAllowedComponent(componentID) switch (status) { case 0: { continue; } case 1: case 2: { this.m_componentList.push({ blockId: componentID, type: BlockTypeEnum.COMPONENT, allow: status == 1 ? true : false, blockCode: componentID, name: components[componentID].name, fa: components[componentID].fontAwesome, specialJsonItemName: specialJsonItemName, specialJsonItemColor: specialJsonItemColor, description: components[componentID].description }) break; } } } ///////////////////////////////////////////////////////// // show resource selection list ///////////////////////////////////////////////////////// // var recResources = pepper.listenResources(); this.m_resourceModels.forEach((i_resourcesModel: ResourcesModel) => { var size = (i_resourcesModel.getResourceBytesTotal() / 1000).toFixed(2); var resourceDescription = 'size: ' + size; this.m_resourceList.push({ resourceId: i_resourcesModel.getResourceId(), type: BlockTypeEnum.RESOURCE, name: i_resourcesModel.getResourceName(), blockCode: this.bs.getBlockCodeFromFileExt(i_resourcesModel.getResourceType()) as number, size: size, allow: true, fa: this.bs.getFontAwesome(i_resourcesModel.getResourceType()), description: resourceDescription }) }) ///////////////////////////////////////////////////////// // show scene selection list in Scene or block list modes ///////////////////////////////////////////////////////// if (this.m_placement == PLACEMENT_CHANNEL || this.m_placement == PLACEMENT_LISTS) { this.m_sceneDatas.forEach((i_sceneData: ISceneData) => { var label = $(i_sceneData.domPlayerData).find('Player').eq(0).attr('label'); var mimeType = $(i_sceneData.domPlayerData).find('Player').eq(0).attr('mimeType'); // don't allow adding mimetype scenes to channels directly as needs to be added via Player block if (this.m_placement == PLACEMENT_CHANNEL) { if (!_.isUndefined(mimeType)) return; } this.m_sceneList.push({ sceneData: i_sceneData, type: BlockTypeEnum.SCENE, blockCode: 3510, name: label, fa: this.bs.getFontAwesome('scene'), allow: true, description: 'scene' }) }) } //reset mimetype this.m_sceneMime = undefined; } /** Check if component is allowed under enterprise / prime membership Note that if running under Hybrid or Private server default is to always allow all components @method _checkAllowedComponent @param {Number} i_componentID @return {Number} 0 = hide, 1 = show, 2 = upgradable **/ _checkAllowedComponent(i_componentID) { // include all if (this.hybrid_private) return 1; // FasterQ, open to all if (i_componentID == 6100) { return 1; } var appID = this.bs.getBlockBoilerplate(i_componentID).app_id; if (_.isUndefined(appID)) return 1; // component is prime, account is free type, upgradable if (this.m_userModel.getKey('resellerId') == 1) return 2; // account is under a reseller and component not available, hide it if (this.m_userModel.getKey('resellerId') != 1 && _.isUndefined(this.m_userModel.getKey('components')[appID])) return 0; // account is under a reseller and component is available, show it return 1; } destroy() { } } ================================================ FILE: src/app/campaigns/campaign-channels.ts ================================================ import {AfterViewInit, ChangeDetectorRef, Component, Input, ViewChild} from "@angular/core"; import {Compbaser} from "ng-mslib"; import {CampaignTimelineBoardViewerChanelsModel, CampaignTimelineChanelPlayersModel, CampaignTimelineChanelsModel, CampaignTimelinesModel} from "../../store/imsdb.interfaces_auto"; import {BlockService, IBlockData} from "../blocks/block-service"; import {Observable, Subject} from "rxjs"; import {RedPepperService} from "../../services/redpepper.service"; import {YellowPepperService} from "../../services/yellowpepper.service"; import {Once} from "../../decorators/once-decorator"; import {List} from "immutable"; import {IUiState} from "../../store/store.data"; import {ACTION_UISTATE_UPDATE, SideProps} from "../../store/actions/appdb.actions"; import {DraggableList} from "../../comps/draggable-list/draggable-list"; import {IAddContents} from "../../interfaces/IAddContent"; import {timeout} from "../../decorators/timeout-decorator"; import {Lib} from "../../Lib"; @Component({ selector: 'campaign-channels', styles: [` * { font-size: 1.1em !important; } .dragch { float: right; padding-right: 10px; position: relative; top: 5px; } .lengthTimer { float: right; padding-right: 10px; } .listItems { cursor: pointer; } .listItems a i { display: inline; font-size: 40px; padding-right: 20px; } .listItems a span { display: inline; font-size: 1.5em; position: relative; top: -12px; } `], template: ` {{me}} ` }) export class CampaignChannels extends Compbaser implements AfterViewInit { private selected_campaign_timeline_id: number = -1; private selected_campaign_timeline_chanel_id: number = -1; private durationChanged$ = new Subject(); m_blockList: List = List([]); constructor(private yp: YellowPepperService, private rp: RedPepperService, private bs: BlockService, private cd:ChangeDetectorRef) { super(); } @ViewChild(DraggableList) draggableList: DraggableList; ngAfterViewInit() { this.listenChannelSelected(); this.preventRedirect(true); } @timeout() private listenChannelSelected() { this.cd.markForCheck(); this.cancelOnDestroy( this.yp.listenCampaignTimelineBoardViewerSelected(true) .skip(1) .distinctUntilChanged() .subscribe(() => { this.draggableList.deselect(); }) ) this.cancelOnDestroy( this.yp.listenCampaignTimelineBoardViewerSelected(true) .combineLatest( this.durationChanged$, this.yp.ngrxStore.select(store => store.msDatabase.sdk.table_campaign_timeline_chanel_players) ) .filter((v) => { var campaignTimelineBoardViewerChanelsModel: CampaignTimelineBoardViewerChanelsModel = v[0]; var totalDuration = v[1]; var campaignTimelineChanelPlayersModel = v[2]; if (campaignTimelineBoardViewerChanelsModel == null) this.m_blockList = List([]); return campaignTimelineBoardViewerChanelsModel != null; }) .withLatestFrom(this.yp.listenTimelineSelected(), (i_channelModel: CampaignTimelineBoardViewerChanelsModel, i_timelinesModel: CampaignTimelinesModel) => { this.selected_campaign_timeline_chanel_id = i_channelModel[0].getCampaignTimelineChanelId(); this.selected_campaign_timeline_id = i_timelinesModel.getCampaignTimelineId(); return i_channelModel[0].getCampaignTimelineBoardViewerChanelId() }) .mergeMap(i_boardViewerChanelId => { return this.yp.getChannelFromCampaignTimelineBoardViewer(i_boardViewerChanelId) }) .mergeMap((i_campaignTimelineChanelsModel: CampaignTimelineChanelsModel) => { return this.yp.getChannelBlocks(i_campaignTimelineChanelsModel.getCampaignTimelineChanelId()) }) .mergeMap(blockIds => { if (blockIds.length == 0) return Observable.of([]) return Observable.from(blockIds) .map((blockId) => this.bs.getBlockData(blockId)) .combineAll() }) .subscribe((i_blockList: Array) => { this.m_blockList = List(this._sortBlock(i_blockList)); // this.draggableList.createSortable() }, e => console.error(e)) ) this.cancelOnDestroy( this.yp.listenTimelineDurationChanged() .subscribe((totalDuration) => { this.durationChanged$.next(totalDuration); }) ) } _onItemSelected(event) { var blockData: IBlockData = event.item; var uiState: IUiState = { campaign: { blockChannelSelected: blockData.blockID }, uiSideProps: SideProps.channelBlock } this.yp.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})) } _onDragComplete(i_blocks) { this._reOrderChannelBlocks(i_blocks); } private _sortBlock(i_blockList: Array): Array { var sorted = i_blockList.sort((a, b) => { if (a.offset < b.offset) return -1; if (a.offset > b.offset) return 1; if (a.offset === b.offset) return 0; }) return sorted; } /** Update the blocks offset times according to current order of LI elements and reorder accordingly in msdb. @method _reOrderChannelBlocks @return none **/ _reOrderChannelBlocks(i_blocks) { var self = this var blocks = i_blocks; var playerOffsetTime: any = 0; jQuery(blocks).each(function (i) { var block_id = jQuery('[data-block_id]', this).data('block_id'); self._getBlockRecord(block_id, (i_campaignTimelineChanelPlayersModel: CampaignTimelineChanelPlayersModel) => { var playerDuration = i_campaignTimelineChanelPlayersModel.getPlayerDuration(); self.rp.setBlockRecord(block_id, 'player_offset_time', Lib.ToValidNumber(playerOffsetTime)); // console.log('player ' + block_id + ' offset ' + playerOffsetTime + ' playerDuration ' + playerDuration); playerOffsetTime = parseFloat(playerOffsetTime) + parseFloat(playerDuration); }) }); self.rp.updateTotalTimelineDuration(this.selected_campaign_timeline_id); self.rp.reduxCommit(); } @Once() private _getBlockRecord(i_blockId, i_cb: (i_blockId: CampaignTimelineChanelPlayersModel) => void) { return this.yp.getChannelBlockRecord(i_blockId) .subscribe((block: CampaignTimelineChanelPlayersModel) => { i_cb(block); }, (e) => console.error(e)); } ngOnInit() { } destroy() { this.selected_campaign_timeline_chanel_id = -1; this.selected_campaign_timeline_id = -1; } } // var campaign_timeline_chanel_player_id = jData['campaign_timeline_chanel_player_id']; // var campaign_timeline_chanel_player_data = jData['campaign_timeline_chanel_player_data']; // var timeline = BB.comBroker.getService(BB.SERVICES.CAMPAIGN_VIEW).getTimelineInstance(self.selected_campaign_timeline_id); // var channel = timeline.getChannelInstance(self.selected_campaign_timeline_chanel_id); // channel.createChannelBlock(campaign_timeline_chanel_player_id, campaign_timeline_chanel_player_data); // // var campaign_timeline_board_viewer_id = self.selected_campaign_timeline_board_viewer_id; // var campaign_timeline_id = self.selected_campaign_timeline_id; // var campaign_timeline_chanel_id = self.selected_campaign_timeline_chanel_id; // // // self._resetChannel(); // $(Elements.SORTABLE).empty(); // self._loadChannelBlocks(campaign_timeline_id, campaign_timeline_chanel_id); // self._listenBlockSelected(); // // self._deselectBlocksFromChannel(); // self._selectLastBlockOnChannel(); // self._reOrderChannelBlocks(); // var blocksSorted = {}; // _.forEach(i_blockList, (i_block: IBlockData) => { // var player_data = i_block.campaignTimelineChanelPlayersModelExt.getPlayerData(); // var domPlayerData = $.parseXML(player_data); // var sceneHandle = jQuery(domPlayerData).find('Player').attr('player'); // // workaround to remove scenes listed inside table campaign_timeline_chanel_players // if (sceneHandle == '3510') // return; // var a = i_block.campaignTimelineChanelPlayersModelExt.getKey('player_offset_time'); // var offsetTime = i_block.campaignTimelineChanelPlayersModelExt.getPlayerOffsetTimeInt(); // console.log(i_block.blockName + ' duration: ' + i_block.length + ' offset: ' + offsetTime); // blocksSorted[offsetTime] = i_block; // }); // return _.values(blocksSorted) as Array; ================================================ FILE: src/app/campaigns/campaign-duration.ts ================================================ import {AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component} from "@angular/core"; import {Compbaser} from "ng-mslib"; import {YellowPepperService} from "../../services/yellowpepper.service"; import {Observable} from "rxjs"; @Component({ selector: 'campaign-duration', changeDetection: ChangeDetectionStrategy.OnPush, template: ` {{me}} campaign length: {{m_duration$ | async | FormatSecondsPipe}} ` }) export class CampaignDuration extends Compbaser implements AfterViewInit { m_duration$:Observable; constructor(private yp: YellowPepperService, private cd:ChangeDetectorRef) { super(); } ngAfterViewInit() { this.m_duration$ = this.yp.listenTimelineDurationChanged() this.cd.markForCheck() } ngOnInit() { } destroy() { } } ================================================ FILE: src/app/campaigns/campaign-editor-props.ts ================================================ import {Component} from "@angular/core"; import {Compbaser} from "ng-mslib"; import {YellowPepperService} from "../../services/yellowpepper.service"; import {CampaignsModelExt} from "../../store/model/msdb-models-extended"; import {Observable} from "rxjs"; @Component({ selector: 'campaign-editor-props', host: { '(input-blur)': 'onFormChange($event)' }, template: `
    editor properties {{me}}
    • {{(m_campaignModel$ | async)?.getCampaignName() }}

    • playback mode: scheduler

      playback mode: sequencer

    `, styles: [` i { width: 20px; } `] }) export class CampaignEditorProps extends Compbaser { m_campaignModel$: Observable; constructor(private yp: YellowPepperService) { super(); this.m_campaignModel$ = this.yp.listenCampaignValueChanged() } destroy() { } } ================================================ FILE: src/app/campaigns/campaign-editor.ts ================================================ import {ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Output, ViewChild} from "@angular/core"; import {animate, state, style, transition, trigger} from "@angular/animations"; import {Compbaser} from "ng-mslib"; import {CampaignsModelExt, CampaignTimelineChanelPlayersModelExt} from "../../store/model/msdb-models-extended"; import {YellowPepperService} from "../../services/yellowpepper.service"; import {CampaignTimelineChanelsModel, CampaignTimelinesModel} from "../../store/imsdb.interfaces_auto"; import {List} from "immutable"; import {ACTION_UISTATE_UPDATE, AppdbAction, SideProps} from "../../store/actions/appdb.actions"; import {IUiState, StoryBoardListViewModeEnum} from "../../store/store.data"; import {PreviewModeEnum} from "../live-preview/live-preview"; import * as _ from "lodash"; import {RedPepperService} from "../../services/redpepper.service"; import {MainAppShowStateEnum} from "../app-component"; import {Lib} from "../../Lib"; import {CampaignStoryTimeline, ITimelineState} from "./campaign-story-timeline"; // https://github.com/AlexWD/ds-timeline-widget @Component({ selector: 'campaign-editor', templateUrl: './campaign-editors.html', changeDetection: ChangeDetectionStrategy.OnPush, styles: [` .btn.active.focus, .btn.active:focus, .btn.focus, .btn:active.focus, .btn:active:focus, .btn:focus { outline: 0; } label { border-radius: 0px; } `], animations: [ trigger('visibilityChanged', [ state('on', style({transform: 'rotate(0deg)'})), state('off', style({transform: 'rotate(180deg)'})), transition('* => *', animate('300ms')) ]), trigger('fadeInOut', [ transition(':enter', [ style({opacity: 0}), animate('400ms', style({opacity: 1})) ]), transition(':leave', [ style({opacity: 1}), animate('200ms', style({opacity: 0})) ]) ]) ] }) export class CampaignEditor extends Compbaser { campaignModel: CampaignsModelExt; campaignTimelinesModel: CampaignTimelinesModel; channelModel: CampaignTimelineChanelsModel; m_campaignTimelinesModels: List; m_campaignTimelineChanelPlayersModel: CampaignTimelineChanelPlayersModelExt; m_isVisible1 = 'off'; zoom = 1; loginState: string = ''; m_inDevMode = Lib.DevMode(); m_storyBoardListViewModeEnum = StoryBoardListViewModeEnum; m_storyBoardListViewModeSelection = StoryBoardListViewModeEnum.ListMode; m_switchMode = false; m_duration:number = 0; constructor(private yp: YellowPepperService, private actions: AppdbAction, private rp: RedPepperService, private cd: ChangeDetectorRef) { super(); this.cancelOnDestroy( this.yp.listenStoryBoardListViewModeSelected() .subscribe((v) => { this._onTimelineViewMode(v); }, (e) => console.error(e)) ); this.cancelOnDestroy( this.yp.listenCampaignSelected() .switchMap((i_campaignsModelExt: CampaignsModelExt) => { this.campaignModel = i_campaignsModelExt; return this.yp.listenCampaignTimelines(i_campaignsModelExt.getCampaignId()) }) .subscribe((i_campaignTimelinesModel: List) => { this.m_campaignTimelinesModels = i_campaignTimelinesModel; }, (e) => console.error(e)) ); this.cancelOnDestroy( this.yp.listenTimelineSelected(true) .subscribe((i_campaignTimelinesModel: CampaignTimelinesModel) => { this.campaignTimelinesModel = i_campaignTimelinesModel; if (this.campaignTimelinesModel){ this.m_duration = this.campaignTimelinesModel.getTimelineDuration(); console.log('duration ' + this.m_duration); //todo: error when enabled but need for Duration component // this.cd.detectChanges(); } }, (e) => console.error(e)) ); this.cancelOnDestroy( this.yp.listenChannelSelected(true) .subscribe((channel: CampaignTimelineChanelsModel) => { this.channelModel = channel; }, (e) => { console.error(e) }) ); this.cancelOnDestroy( this.yp.listenBlockChannelSelected(true) .subscribe((i_campaignTimelineChanelPlayersModel: CampaignTimelineChanelPlayersModelExt) => { this.m_campaignTimelineChanelPlayersModel = i_campaignTimelineChanelPlayersModel; }, (e) => console.error(e)) ) } @ViewChild(CampaignStoryTimeline) campaignStoryTimeline:CampaignStoryTimeline; @Output() onToScreenLayoutEditor: EventEmitter = new EventEmitter(); @Output() onToAddContent: EventEmitter = new EventEmitter(); @Output() onToAddTimeline: EventEmitter = new EventEmitter(); @Output() onGoBack: EventEmitter = new EventEmitter(); _onAddContent() { if (!this.channelModel) return bootbox.alert('Select channel to add content to. First be sure to select a timeline and next, click the [Next Channel] button'); this.onToAddContent.emit(); } _timelineDurationChange(i_duration) { this.m_duration = i_duration; console.log('CampaignEditor db total new duration ' + i_duration + ' ' + this.campaignTimelinesModel.getCampaignTimelineId()); this.rp.setTimelineTotalDuration(this.campaignTimelinesModel.getCampaignTimelineId(), i_duration); this.rp.reduxCommit(); this.cd.markForCheck(); } _campaignStoryTimelineCmd(i_cmd){ this.campaignStoryTimeline[i_cmd](); } _onStateChanged(state:ITimelineState){ // this.m_duration = state.duration; } _onRemoveTimeline() { if (!this.campaignTimelinesModel) return bootbox.alert('you must first select a timeline to remove'); if (this.rp.getCampaignTimelines(this.campaignTimelinesModel.getCampaignId()).length == 1) return bootbox.alert('you must keep at least one Timeline') bootbox.confirm('are you sure you want to remove the selected timeline?', (i_result) => { if (i_result == true) { var boardTemplateID = this.rp.getGlobalTemplateIdOfTimeline(this.campaignTimelinesModel.getCampaignTimelineId()); this.rp.removeTimelineFromCampaign(this.campaignTimelinesModel.getCampaignTimelineId()); this.rp.removeSchedulerFromTime(this.campaignTimelinesModel.getCampaignTimelineId()); var campaignTimelineBoardTemplateID = this.rp.removeBoardTemplateFromTimeline(this.campaignTimelinesModel.getCampaignTimelineId()); this.rp.removeBoardTemplate(boardTemplateID); this.rp.removeTimelineBoardViewerChannels(campaignTimelineBoardTemplateID); this.rp.removeBoardTemplateViewers(boardTemplateID); this.rp.getChannelsOfTimeline(this.campaignTimelinesModel.getCampaignTimelineId()).forEach(i_campaign_timeline_chanel_id => { this.rp.removeChannelFromTimeline(i_campaign_timeline_chanel_id); this.rp.getChannelBlocks(i_campaign_timeline_chanel_id).forEach((i_block_id) => { this.rp.removeBlockFromTimelineChannel(i_block_id); }) }); var uiState: IUiState = { uiSideProps: SideProps.miniDashboard, campaign: { timelineSelected: -1, campaignTimelineChannelSelected: -1, campaignTimelineBoardViewerSelected: -1 } } this.yp.ngrxStore.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})) this.rp.reduxCommit(); } }); } _changeZoom(value) { // console.log(value); } _onTimelineViewMode(i_mode: StoryBoardListViewModeEnum) { var uiState: IUiState = { campaign: { storyBoardListViewModeSelected: i_mode } } this.yp.ngrxStore.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})) this.m_storyBoardListViewModeSelection = i_mode; } _onAddTimeline() { this.onToAddTimeline.emit(); } _onEditScreenLayout() { if (!this.campaignTimelinesModel) return bootbox.alert('no timeline selected') var uiState: IUiState = {uiSideProps: SideProps.screenLayoutEditor} this.yp.ngrxStore.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})) this.onToScreenLayoutEditor.emit(); } /** Delete the selected block from the channel @method _deleteChannelBlock @return none **/ _onRemoveContent() { if (!this.m_campaignTimelineChanelPlayersModel) return bootbox.alert('No item selected'); this.rp.removeBlockFromTimelineChannel(this.m_campaignTimelineChanelPlayersModel.getCampaignTimelineChanelPlayerId()); this.rp.reduxCommit(); let uiState: IUiState = { uiSideProps: SideProps.miniDashboard, campaign: { blockChannelSelected: -1 } } this.yp.ngrxStore.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})) } _onCampaignPreview() { return bootbox.alert('HTML preview coming soon! for now preview using Desktop Signage Player'); // let uiState: IUiState = {mainAppState: MainAppShowStateEnum.SAVE_AND_PREVIEW, previewMode: PreviewModeEnum.CAMPAIGN} // this.yp.ngrxStore.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})) } _onTimelinePreview() { return bootbox.alert('HTML preview coming soon! for now preview using Desktop Signage Player'); // if (_.isUndefined(this.campaignTimelinesModel)) // return bootbox.alert('No timeline selected'); // let uiState: IUiState = {mainAppState: MainAppShowStateEnum.SAVE_AND_PREVIEW, previewMode: PreviewModeEnum.TIMELINE} // this.yp.ngrxStore.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})) } _onGoBack() { this.actions.resetCampaignSelection(); this.onGoBack.emit() } ngOnInit() { } destroy() { } } ================================================ FILE: src/app/campaigns/campaign-editors.html ================================================ {{me}}

    campaign editor

    Timeline

    ================================================ FILE: src/app/campaigns/campaign-layout.ts ================================================ import {Component, EventEmitter, Input, Output} from "@angular/core"; import {Compbaser} from "ng-mslib"; import * as screenTemplates from "../../libs/screen-templates.json"; import * as _ from "lodash"; import {OrientationEnum} from "./campaign-orientation"; import {Observable, Observer} from "rxjs"; import {Once} from "../../decorators/once-decorator"; import {IUiStateCampaign} from "../../store/store.data"; import {YellowPepperService} from "../../services/yellowpepper.service"; import {RedPepperService} from "../../services/redpepper.service"; import {IScreenTemplateData} from "../../interfaces/IScreenTemplate"; import {ACTION_LIVELOG_UPDATE} from "../../store/actions/appdb.actions"; import {LiveLogModel} from "../../models/live-log-model"; @Component({ selector: 'campaign-layout', styles: [` :host /deep/ .svgSD { cursor: pointer; } `], template: ` {{me}}

    screen layout

    ` }) export class CampaignLayout extends Compbaser { private m_resolution: string; private m_screenTemplateData: IScreenTemplateData; private m_orientation: OrientationEnum; _nextClick: Observer; m_addToExistingCampaignMode = false; m_screenLayouts: Array; m_campaignName: string; m_onNewCampaignMode: boolean; m_mouseHoverEffect:boolean = false; constructor(private yp: YellowPepperService, private rp: RedPepperService) { super(); } ngAfterViewInit() { this.getNewCampaignParams(); this.cancelOnDestroy( Observable.create(observer => { this._nextClick = observer }).map((i_screenTemplateData: IScreenTemplateData) => { this.m_screenTemplateData = i_screenTemplateData; return i_screenTemplateData; }) .debounceTime(200) .do(() => { this.onSelection.emit(this.m_screenTemplateData) }).subscribe(() => { }, (e) => console.error(e)) ) } @Input() set mouseHoverEffect(i_value) { this.m_mouseHoverEffect = i_value; } @Input() set onNewCampaignMode(i_value: boolean) { this.m_onNewCampaignMode = i_value; } @Once() private getNewCampaignParams() { return this.yp.getNewCampaignParmas() .subscribe((value: IUiStateCampaign) => { if (this.m_onNewCampaignMode) { this.m_addToExistingCampaignMode = false; this.m_resolution = value.campaignCreateResolution; this.m_orientation = value.campaignCreateOrientation; this.m_campaignName = value.campaignCreateName; this.yp.dispatch(({type: ACTION_LIVELOG_UPDATE, payload: new LiveLogModel({event: 'campaign created ' + this.m_campaignName})})); } else { this.m_addToExistingCampaignMode = true; var recBoard = this.rp.getGlobalBoardFromCampaignId(value.campaignSelected) var h = parseInt(recBoard.board_pixel_height); var w = parseInt(recBoard.board_pixel_width); this.m_resolution = `${w}x${h}`; this.m_orientation = w > h ? OrientationEnum.HORIZONTAL : OrientationEnum.VERTICAL; this.m_campaignName = ''; } this._render(); }, (e) => console.error(e)) } @Output() onSelection: EventEmitter = new EventEmitter(); private _render() { if (_.isUndefined(this.m_orientation) || _.isUndefined(this.m_resolution)) return; this.m_screenLayouts = []; for (var screenType in screenTemplates[this.m_orientation][this.m_resolution]) { var screenTemplateData: IScreenTemplateData = { orientation: this.m_orientation, resolution: this.m_resolution, screenType: screenType, screenProps: screenTemplates[this.m_orientation][this.m_resolution][screenType], scale: 14, name: this.m_campaignName }; this.m_screenLayouts.push(screenTemplateData); } } ngOnInit() { } destroy() { } } ================================================ FILE: src/app/campaigns/campaign-list.ts ================================================ import {Component, ChangeDetectionStrategy, Input, Output, EventEmitter} from "@angular/core"; import {Compbaser} from "ng-mslib"; import {CampaignsModelExt} from "../../store/model/msdb-models-extended"; import {List} from "immutable"; import {IUiState} from "../../store/store.data"; import {SideProps} from "../../store/actions/appdb.actions"; @Component({ selector: 'campaign-list', template: ` {{me}} `, }) export class CampaignList extends Compbaser { selectedIdx = -1; m_campaigns: List; m_selectedCampaign: CampaignsModelExt; constructor() { super(); } @Input() set campaigns(i_campaigns: List) { this.m_campaigns = i_campaigns; } @Output() slideToCampaignEditor: EventEmitter = new EventEmitter(); @Output() slideToCampaignName: EventEmitter = new EventEmitter(); @Output() onCampaignSelected: EventEmitter = new EventEmitter(); _onCampaignSelected(event: MouseEvent, campaign: CampaignsModelExt, index) { // event.stopPropagation(); // event.preventDefault(); this.selectedIdx = index; let uiState: IUiState; if (jQuery(event.target).hasClass('props')) { uiState = { uiSideProps: SideProps.campaignProps, campaign: { campaignSelected: campaign.getCampaignId() } } this.onCampaignSelected.emit(uiState) } else { uiState = { uiSideProps: SideProps.campaignEditor, campaign: { campaignSelected: campaign.getCampaignId() } } this.slideToCampaignEditor.emit(); this.onCampaignSelected.emit(uiState) } this.m_selectedCampaign = campaign; } ngOnInit() { } destroy() { } } ================================================ FILE: src/app/campaigns/campaign-manager.html ================================================ {{me}}
    campaign selection

    ================================================ FILE: src/app/campaigns/campaign-manager.ts ================================================ import {ChangeDetectionStrategy, Component, ElementRef, EventEmitter, HostListener, Output} from "@angular/core"; import {Observable} from "rxjs"; import {List} from "immutable"; import {Compbaser} from "ng-mslib"; import {Router} from "@angular/router"; import {UserModel} from "../../models/UserModel"; import {RedPepperService} from "../../services/redpepper.service"; import {CampaignsModelExt} from "../../store/model/msdb-models-extended"; import {ACTION_UISTATE_UPDATE, SideProps} from "../../store/actions/appdb.actions"; import {IUiState} from "../../store/store.data"; import {YellowPepperService} from "../../services/yellowpepper.service"; import {MainAppShowStateEnum} from "../app-component"; import {WizardService} from "../../services/wizard-service"; @Component({ // changeDetection: ChangeDetectionStrategy.OnPush, selector: 'campaign-manager', styles: [` button { width: 160px; } /*.selectedItem {*/ /*background-color: green !important*/ /*}*/ /*a.list-group-item:focus, button.list-group-item:focus {*/ /*background-color: pink !important;*/ /*}*/ `], templateUrl: './campaign-manager.html' }) export class CampaignManager extends Compbaser { // public userModel$: Observable; public campaigns$: Observable>; public timelineSelected$: Observable; constructor(private el: ElementRef, private yp: YellowPepperService, private redPepperService: RedPepperService, private router: Router, private wizardService: WizardService) { super(); this.preventRedirect(true); this.timelineSelected$ = this.yp.ngrxStore.select(store => store.appDb.uiState.campaign.timelineSelected).map(v => v); // this.userModel$ = this.yp.ngrxStore.select(store => store.appDb.userModel); this.campaigns$ = this.yp.ngrxStore.select(store => store.msDatabase.sdk.table_campaigns) .map((list: List) => list); // this.yp.ngrxStore.select(store => store.msDatabase.sdk.table_resources).subscribe((resourceModels: List) => { // // console.log(resourceModels.first().getResourceName()); // // console.log(resourceModels.first().getResourceBytesTotal()); // }) } // @once(6000) // private testListen() { // return this.yp.ngrxStore.select(store => store.appDb.uiState.uiSideProps).map((v) => { // console.log(v); // }).subscribe((e) => { // console.log(e); // }); // } @Output() slideToCampaignEditor: EventEmitter = new EventEmitter(); @Output() slideToCampaignName: EventEmitter = new EventEmitter(); // m_selectedCampaign: CampaignsModelExt; // _onCampaignSelected(event: MouseEvent, campaign: CampaignsModelExt) { // let uiState: IUiState; // if (jQuery(event.target).hasClass('props')) { // uiState = { // uiSideProps: SideProps.campaignProps, // campaign: { // campaignSelected: campaign.getCampaignId() // } // } // this.yp.ngrxStore.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})) // } else { // uiState = { // uiSideProps: SideProps.campaignEditor, // campaign: { // campaignSelected: campaign.getCampaignId() // } // } // this.slideToCampaignEditor.emit(); // this.yp.ngrxStore.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})) // } // this.m_selectedCampaign = campaign; // } onRoute1() { this.router.navigate(['/App1/Campaigns']) } onRoute2() { this.router.navigate(['/App1/Fasterq']) } onRoute3() { this.router.navigate(['/App1/Resources']) } onRoute4() { this.router.navigate(['/App1/Settings']) } onRoute5() { this.router.navigate(['/App1/Stations']) } onRoute6() { this.router.navigate(['/App1/StudioPro']) } // save() { // let uiState: IUiState = {mainAppState: MainAppShowStateEnum.SAVE} // this.yp.ngrxStore.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})) // } _onCampaignSelected(i_uiState: IUiState) { this.yp.ngrxStore.dispatch(({type: ACTION_UISTATE_UPDATE, payload: i_uiState})) } _createCampaign() { var uiState: IUiState = {uiSideProps: SideProps.miniDashboard} this.yp.ngrxStore.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})) this.slideToCampaignName.emit(); } _onWizard() { this.wizardService.start(); } destroy() { // var uiState: IUiState = {uiSideProps: SideProps.none} // this.yp.ngrxStore.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})) } } // let uiState: IUiState = { // campaign: { // campaignSelected: 123 // } // }; // uiState.campaign.campaignSelected = _.random(1,1999); // this.yp.ngrxStore.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})) // var b: IUiState = { // uiSideProps: _.random(1,1222) // } // this.yp.ngrxStore.dispatch(({type: ACTION_UISTATE_UPDATE, payload: b})) ================================================ FILE: src/app/campaigns/campaign-name.ts ================================================ import {Component, ChangeDetectionStrategy, Output, EventEmitter} from "@angular/core"; import {Compbaser} from "ng-mslib"; import {YellowPepperService} from "../../services/yellowpepper.service"; import {IUiState} from "../../store/store.data"; import {ACTION_UISTATE_UPDATE} from "../../store/actions/appdb.actions"; @Component({ selector: 'campaign-name', // changeDetection: ChangeDetectionStrategy.OnPush, template: ` {{me}}

    Select your campaign name

    `, }) export class CampaignName extends Compbaser { constructor(private yp: YellowPepperService) { super(); } @Output() onNext:EventEmitter = new EventEmitter(); m_campaignName: string = ''; public get getCampaignNameChanged(): string { return this.m_campaignName; } _onKeyDown(event:KeyboardEvent){ if (event.keyCode==13){ this.onNext.emit(); } } ngOnInit() { } destroy() { var uiState:IUiState = { campaign: { campaignCreateName: this.m_campaignName } } this.yp.ngrxStore.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})) } } ================================================ FILE: src/app/campaigns/campaign-orientation.ts ================================================ import {Component, EventEmitter, forwardRef, Inject, Output} from "@angular/core"; import {Compbaser} from "ng-mslib"; import {Observable, Observer} from "rxjs"; import {IUiState} from "../../store/store.data"; import {ACTION_UISTATE_UPDATE} from "../../store/actions/appdb.actions"; import {YellowPepperService} from "../../services/yellowpepper.service"; export enum OrientationEnum { HORIZONTAL, VERTICAL } // export type OrientationConst = "HORIZONTAL" | "VERTICAL"; @Component({ selector: 'campaign-orientation', // changeDetection: ChangeDetectionStrategy.OnPush, styles: [` .defaultOpacity { opacity: 0.6; cursor: pointer; } .selectedOrientation { opacity: 1 !important; } `], template: ` {{me}}

    screen orientation

    ` }) export class CampaignOrientation extends Compbaser { m_orientation: OrientationEnum; OrientationEnum = OrientationEnum; _nextClick: Observer; constructor(@Inject(forwardRef(() => YellowPepperService)) private yp: YellowPepperService) { super(); this.cancelOnDestroy( Observable.create(observer => { this._nextClick = observer }).map((i_orientation) => { this.m_orientation = i_orientation; return i_orientation; }).debounceTime(100) .subscribe(() => { var uiState: IUiState = { campaign: { campaignCreateOrientation: this.m_orientation } } this.yp.ngrxStore.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})) this.onSelection.emit(this.m_orientation) }, (e) => { console.error(e) }) ) } @Output() onSelection: EventEmitter = new EventEmitter(); ngOnInit() { } destroy() { } } ================================================ FILE: src/app/campaigns/campaign-props-manager.ts ================================================ import {Component} from "@angular/core"; import {Compbaser} from "ng-mslib"; import {Observable} from "rxjs"; import {SideProps} from "../../store/actions/appdb.actions"; import {YellowPepperService} from "../../services/yellowpepper.service"; @Component({ selector: 'campaign-props-manager', styles: [` ul { padding: 0 } `], template: ` {{me}}
    `, }) export class CampaignPropsManager extends Compbaser { constructor(private yp: YellowPepperService) { super(); this.m_uiUserFocusItem$ = this.yp.ngrxStore.select(store => store.appDb.uiState.uiSideProps); } m_uiUserFocusItemEnum = SideProps; m_uiUserFocusItem$: Observable; ngOnInit() { } destroy() { } } ================================================ FILE: src/app/campaigns/campaign-props.ts ================================================ import {Component, Input} from "@angular/core"; import {FormBuilder, FormControl, FormGroup, Validators} from "@angular/forms"; import {Compbaser, NgmslibService} from "ng-mslib"; import {CampaignsModelExt} from "../../store/model/msdb-models-extended"; import {YellowPepperService} from "../../services/yellowpepper.service"; import {RedPepperService} from "../../services/redpepper.service"; import {timeout} from "../../decorators/timeout-decorator"; import {Observable} from "rxjs"; import {IUiState} from "../../store/store.data"; import {ACTION_LIVELOG_UPDATE, ACTION_UISTATE_UPDATE, SideProps} from "../../store/actions/appdb.actions"; import * as _ from "lodash"; import {simpleRegExp} from "../../Lib"; import {LiveLogModel} from "../../models/live-log-model"; enum CampaignPlaylistModeEnum { SEQUENCER, SCHEDULER } @Component({ selector: 'campaign-props', host: { '(input-blur)': 'listenUpdatedFormBlur($event)' }, template: `
    campaign properties {{me}}
    • campaign id: {{m_campaignModel?.getCampaignId()}}
    • kiosk mode
    • Campaign playback mode:

      Sequencer (simple mode):

      Play timelines for this campaign in a continuous loop. It is easy to setup and simple to use

      Scheduler (advanced mode):

      Play timelines for this campaign only on specific times. For example, play Timeline A in the morning and Timeline B at night.

    `, styles: [` .faded { opacity: 0.4; } .campaignPlayMode { font-size: 4em; width: 118px; } input.ng-invalid { border-right: 10px solid red; } .material-switch { position: relative; padding-top: 10px; } .input-group { padding-top: 10px; } i { width: 20px; } `] }) export class CampaignProps extends Compbaser { /** * In this example we demonstrate two ways we can bind to the store values: * * 1. m_campaignModel$ via Observable subscription using async into the template: [ngClass]="{faded: ((m_campaignModel$ | async)?.getCampaignPlaylistMode() == 1)}" ... * 2. campaignModel direct grabbing the campaignModel from the store and doing a loop over keys: _.forEach(this.formInputs, (value, key: string) => { ... * * We also demoing here two ways of upding store: * 1. reacting to input changes both via blur * 2. reacting to the Observable of statusChanges **/ m_campaignModel: CampaignsModelExt; m_campaignModel$: Observable; private formInputs = {}; m_contGroup: FormGroup; CampaignPlaylistModeEnum = CampaignPlaylistModeEnum; constructor(private fb: FormBuilder, private ngmslibService: NgmslibService, private yp: YellowPepperService, private rp: RedPepperService) { super(); this.m_contGroup = fb.group({ 'campaign_name': ['', [Validators.required, Validators.pattern(simpleRegExp)]], 'campaign_playlist_mode': [0], 'kiosk_mode': [0] }); _.forEach(this.m_contGroup.controls, (value, key: string) => { this.formInputs[key] = this.m_contGroup.controls[key] as FormControl; }) this.listenUpdatedFormReactive(); // example 1: subscribe to store slice via subscription and and set to local member campaignModel this.cancelOnDestroy( this.yp.listenCampaignSelected() .subscribe((campaign: CampaignsModelExt) => { this.m_campaignModel = campaign; this.renderFormInputs(); this.renderFormInputsReactive(); }, (e) => { console.error(e) }) ); // example 2: hook to store slice observable and pipe to async in template this.m_campaignModel$ = this.yp.listenCampaignValueChanged() } _onChangePlaylistMode(mode: number) { switch (mode) { case CampaignPlaylistModeEnum.SEQUENCER: { this.rp.setCampaignRecord(this.m_campaignModel.getCampaignId(), 'campaign_playlist_mode', String(mode)); break; } case CampaignPlaylistModeEnum.SCHEDULER: { this.rp.setCampaignRecord(this.m_campaignModel.getCampaignId(), 'campaign_playlist_mode', String(mode)); this.rp.checkAndCreateCampaignTimelineScheduler(this.m_campaignModel.getCampaignId()); break; } } this.rp.reduxCommit(); } // example 1 on input update via manually for looping private renderFormInputs() { if (!this.m_campaignModel) return; _.forEach(this.formInputs, (value, key: string) => { let data = this.m_campaignModel.getKey(key); data = StringJS(data).booleanToNumber(); this.formInputs[key].setValue(data) }); }; // example 2 on input update via observable and patch value private renderFormInputsReactive() { this.cancelOnDestroy( this.yp.listenCampaignSelected() .subscribe((i_campaignModel: CampaignsModelExt) => { this.m_campaignModel = i_campaignModel; // var bb = this.m_campaignModel.toPureJs(); // this.m_contGroup.patchValue(bb); }, (e) => console.error(e)) ); }; // example on changes 1 blur listenUpdatedFormBlur(event) { this.saveToStore(); } // example 2 on changes observable private listenUpdatedFormReactive() { this.cancelOnDestroy( this.m_contGroup.statusChanges .filter(valid => valid === 'VALID') .withLatestFrom(this.m_contGroup.valueChanges, (valid, value) => value) .debounceTime(100) .subscribe(value => { // console.log('res ' + JSON.stringify(value) + ' ' + Math.random()) this.saveToStore(); }, (e) => console.error(e)) ) } @timeout() private saveToStore() { // console.log(this.m_contGroup.status + ' ' + JSON.stringify(this.ngmslibService.cleanCharForXml(this.m_contGroup.value))); if (this.m_contGroup.status != 'VALID') return; this.rp.setCampaignRecord(this.m_campaignModel.getCampaignId(), 'campaign_name', this.m_contGroup.value.campaign_name); this.rp.setCampaignRecord(this.m_campaignModel.getCampaignId(), 'campaign_playlist_mode', this.m_contGroup.value.campaign_playlist_mode); this.rp.setCampaignRecord(this.m_campaignModel.getCampaignId(), 'kiosk_timeline_id', 0); //todo: you need to fix this as zero is arbitrary number right now this.rp.setCampaignRecord(this.m_campaignModel.getCampaignId(), 'kiosk_mode', this.m_contGroup.value.kiosk_mode); this.rp.reduxCommit() } removeCampaign() { var campaignId = this.m_campaignModel.getCampaignId(); var allCampaignIDs = this.rp.getStationCampaignIDs(); if (_.indexOf(allCampaignIDs, campaignId) > -1) return bootbox.alert('Cannot remove this campaign as one or more stations are associated with it, be sure to remove them first.'); bootbox.confirm({ message: "Are you sure you want to delete the campaign, there is NO WAY BACK?", buttons: { confirm: { label: 'Yes', className: 'btn-success' }, cancel: { label: 'No', className: 'btn-danger' } }, callback: (result) => { if (result == true) { var campaignId = this.m_campaignModel.getCampaignId(); this.rp.removeCampaignKeepBoards(campaignId); this.rp.reduxCommit(); var uiState: IUiState = {uiSideProps: SideProps.miniDashboard} this.yp.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})) this.yp.dispatch(({type: ACTION_LIVELOG_UPDATE, payload: new LiveLogModel({event: 'campaign deleted ' + campaignId})})); } } }); } destroy() { } } ================================================ FILE: src/app/campaigns/campaign-resolution.ts ================================================ import {Component, ChangeDetectionStrategy, Input, Output, EventEmitter, ViewChild, ChangeDetectorRef} from "@angular/core"; import {Compbaser} from "ng-mslib"; import * as screenTemplates from "../../libs/screen-templates.json"; import {OrientationEnum} from "./campaign-orientation"; import {timeout} from "../../decorators/timeout-decorator"; import {Observable, Observer} from "rxjs"; import {YellowPepperService} from "../../services/yellowpepper.service"; import {Once} from "../../decorators/once-decorator"; import {IUiState, IUiStateCampaign} from "../../store/store.data"; import {ACTION_UISTATE_UPDATE} from "../../store/actions/appdb.actions"; @Component({ selector: 'campaign-resolution', // changeDetection: ChangeDetectionStrategy.OnPush, template: ` {{me}}

    screen resolution

    `, }) export class CampaignResolution extends Compbaser { m_screens: Array = []; private m_resolution: string; _nextClick: Observer; constructor(private yp: YellowPepperService) { super(); this.getNewCampaignParams(); this.cancelOnDestroy( Observable.create(observer => { this._nextClick = observer }).map((i_resolution) => { this.m_resolution = i_resolution; return i_resolution; }).debounceTime(100) .subscribe(() => { var uiState: IUiState = { campaign: { campaignCreateResolution: this.m_resolution } } this.yp.ngrxStore.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})) this.onSelection.emit(this.m_resolution); }, (e) => { console.error(e) }) ) } @Once() private getNewCampaignParams() { return this.yp.getNewCampaignParmas() .subscribe((value: IUiStateCampaign) => { this.m_screens = []; var orientation:OrientationEnum = value.campaignCreateOrientation; for (var screenResolution in screenTemplates[orientation]) { this.m_screens.push(screenResolution) } }, (e) => { console.error(e) }) } @Output() onSelection: EventEmitter = new EventEmitter(); ngOnInit() { } destroy() { } } ================================================ FILE: src/app/campaigns/campaign-sched-props.css ================================================ .carousel-control.left, .carousel-control.right { background-image: none; } #schedulePriority img:first-child { position: relative; top: 5px; left: 1px; } #schedulePriority img:last-child { position: relative; top: -4px; left: -2px; } #schedulePriority img:hover { cursor: pointer; } .offsetDurations { float: right; position: relative; top: -10px; } .carousel-control.left span, .carousel-control.right span { background-image: none; color: gray; font-size: 0.8em; } .carousel-control { height: 30px; } .carousel-control.left { position: absolute; left: -15px; top: 0px; } .carousel-control.right { position: absolute; top: 0px; } input.ng-invalid { border-right: 10px solid red; } .material-switch { position: relative; padding-top: 10px; } .input-group { padding-top: 10px; } i { width: 20px; } ================================================ FILE: src/app/campaigns/campaign-sched-props.html ================================================
    scheduler properties {{me}}
    Conflict priority:

    Duration:
    Start time:


    ================================================ FILE: src/app/campaigns/campaign-sched-props.ts ================================================ import {AfterViewInit, ChangeDetectorRef, Component, ElementRef, ViewChild} from "@angular/core"; import {FormBuilder, FormControl, FormGroup} from "@angular/forms"; import {Compbaser, NgmslibService} from "ng-mslib"; import {YellowPepperService} from "../../services/yellowpepper.service"; import {RedPepperService} from "../../services/redpepper.service"; import {timeout} from "../../decorators/timeout-decorator"; import * as _ from "lodash"; import {CampaignTimelineSchedulesModel} from "../../store/imsdb.interfaces_auto"; @Component({ selector: 'campaign-sched-props', //changeDetection: ChangeDetectionStrategy.OnPush, // host: {'(input-blur)': '_saveToStore($event)'}, templateUrl: './campaign-sched-props.html', styleUrls: ['./campaign-sched-props.css'] }) export class CampaignSchedProps extends Compbaser implements AfterViewInit { private m_campaignTimelineSchedulesModel: CampaignTimelineSchedulesModel; m_days: Array = []; m_startTime = 0; m_duration = 0; private formInputs = {}; contGroup: FormGroup; private m_ONCE = '0'; private m_DAILY = '1'; private m_WEEKLY = '2'; private m_PRIORITY_LOW = 2; private m_PRIORITY_MEDIUM = 1; private m_PRIORITY_HIGH = 0; private m_WEEKDAYS = [1, 2, 4, 8, 16, 32, 64]; private m_WEEKDAYS_NAME = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']; constructor(private fb: FormBuilder, private el: ElementRef, private yp: YellowPepperService, private rp: RedPepperService, private cd: ChangeDetectorRef, private ngmslibService: NgmslibService) { super(); this.contGroup = this.fb.group({ 'once': [], 'weekly_start': ['1/1/2020'], 'weekly_end': ['1/1/2020'], 'daily_start': ['1/1/2020'], 'daily_end': ['1/1/2020'] }); _.forEach(this.contGroup.controls, (value, key: string) => { this.formInputs[key] = this.contGroup.controls[key] as FormControl; }) } ngAfterViewInit() { this.cancelOnDestroy( this.yp.listenSchedulerValueChanged() .subscribe(i_campaignTimelineSchedulesModel => { this.m_campaignTimelineSchedulesModel = i_campaignTimelineSchedulesModel; this._renderConflictPriority(); this._renderCarouselPosition(); this._initTimePicker(); this._initDays(); this._renderFormInputs(); }, (e) => { console.error(e) }) ) } _setPriority(i_value: number) { this.rp.setCampaignsSchedule(this.m_campaignTimelineSchedulesModel.getCampaignTimelineId(), 'priorty', i_value); this.rp.reduxCommit(); } _onDurationChanged(i_value) { this.rp.setCampaignsSchedule(this.m_campaignTimelineSchedulesModel.getCampaignTimelineId(), 'duration', i_value); this.rp.reduxCommit(); } _onStartTimeChanged(i_value) { this.rp.setCampaignsSchedule(this.m_campaignTimelineSchedulesModel.getCampaignTimelineId(), 'start_time', i_value); this.rp.reduxCommit(); } private _renderCarouselPosition() { jQuery('#schedulerRepeatMode', this.el.nativeElement).carousel(Number(this.m_campaignTimelineSchedulesModel.getRepeatType())); } private _renderConflictPriority() { if (this.m_campaignTimelineSchedulesModel.getPriorty() == this.m_PRIORITY_LOW) { jQuery('#schedulePriority', this.el.nativeElement).find('img').eq(1).fadeTo('fast', 0.5).end().eq(2).fadeTo('fast', 0.5); } else if (this.m_campaignTimelineSchedulesModel.getPriorty() == this.m_PRIORITY_MEDIUM) { jQuery('#schedulePriority', this.el.nativeElement).find('img').eq(1).fadeTo('fast', 1).end().eq(2).fadeTo('fast', 0.5); } else { jQuery('#schedulePriority', this.el.nativeElement).find('img').eq(1).fadeTo('fast', 1).end().eq(2).fadeTo('fast', 1); } } private _renderFormInputs() { this.m_startTime = this.m_campaignTimelineSchedulesModel.getStartTime(); this.m_duration = this.m_campaignTimelineSchedulesModel.getDuration(); _.forEach(this.formInputs, (value, key: string) => { switch (key) { case 'once': { break; } case 'daily_start': { } case 'daily_end': { } case 'weekly_start': { } case 'weekly_end': { var startDate = this.m_campaignTimelineSchedulesModel.getStartDate().split(' ')[0]; var endDate = this.m_campaignTimelineSchedulesModel.getEndDate().split(' ')[0]; var xStart = new XDate(startDate).toString('yyyy-MM-dd'); var xEnd = new XDate(endDate).toString('yyyy-MM-dd'); this.formInputs['weekly_start'].setValue(xStart) this.formInputs['weekly_end'].setValue(xEnd) this.formInputs['daily_start'].setValue(xStart) this.formInputs['daily_end'].setValue(xEnd) this.formInputs['once'].setValue(xStart) return; } default: { } } let data = this.m_campaignTimelineSchedulesModel.getKey(key); data = StringJS(data).booleanToNumber(); this.formInputs[key].setValue(data) }); }; private _initDays() { this.m_days = []; var weekDays = this.m_campaignTimelineSchedulesModel.getWeekDays(); this.m_WEEKDAYS.forEach((v, i) => { var n = weekDays & v; this.m_days.push({ day: this.m_WEEKDAYS_NAME[i], checked: n == v ? true : false }) }); this.cd.detectChanges() } private _initTimePicker() { jQuery('#timepickerDurationInput', this.el.nativeElement).timepicker({ showSeconds: true, showMeridian: false, defaultTime: false, minuteStep: 1, secondStep: 1 }); jQuery('#timepickerTimeInput', this.el.nativeElement).timepicker({ showSeconds: true, showMeridian: false, defaultTime: false, minuteStep: 1, secondStep: 1 }); } _onDaysChanged(checked, day: {}, i: number) { var weekBitsTotal = 0; this.m_days[i] = { day: this.m_WEEKDAYS_NAME[i], checked: checked } this.m_days.forEach((day, i) => { if (day.checked) weekBitsTotal = weekBitsTotal + this.m_WEEKDAYS[i] }); this.rp.setCampaignsSchedule(this.m_campaignTimelineSchedulesModel.getCampaignTimelineId(), 'week_days', weekBitsTotal); this.rp.reduxCommit() } _saveDates(key, event: MouseEvent) { switch (key) { case 'daily_start': case 'weekly_start': case 'once': { var value = event.target['value']; var date = new XDate(value).toString('MM/dd/yyyy') + ' 12:00:00 AM' this.rp.setCampaignsSchedule(this.m_campaignTimelineSchedulesModel.getCampaignTimelineId(), 'start_date', date); return; } case 'weekly_end': case 'daily_end': { var value = event.target['value']; var date = new XDate(value).toString('MM/dd/yyyy') + ' 12:00:00 AM' this.rp.setCampaignsSchedule(this.m_campaignTimelineSchedulesModel.getCampaignTimelineId(), 'end_date', date); return; } } this.rp.reduxCommit() } @timeout(1000) _saveRepeat() { this._saveToStore(); } @timeout() private _saveToStore(key?: string, event?: MouseEvent) { var carouselIndex = jQueryAny('#schedulerRepeatMode .active', this.el.nativeElement).index('#schedulerRepeatMode .item', this.el.nativeElement); this.rp.setCampaignsSchedule(this.m_campaignTimelineSchedulesModel.getCampaignTimelineId(), 'repeat_type', carouselIndex); this.rp.reduxCommit() } destroy() { jQuery('#timepickerDurationInput', this.el.nativeElement).off("hide.timepicker"); jQuery('#timepickerTimeInput', this.el.nativeElement).off("hide.timepicker"); } } // _listenTimepickerChanges() { // jQuery('#timepickerDurationInput', this.el.nativeElement).on("hide.timepicker", (e:any) => { // var totalSeconds = this.rp.formatObjectToSeconds({ // hours: e.time.hours, // minutes: e.time.minutes, // seconds: e.time.seconds // }); // this.rp.setCampaignsSchedule(this.m_campaignTimelineSchedulesModel.getCampaignTimelineId(), 'duration', totalSeconds); // this.rp.reduxCommit(); // }); // jQuery('#timepickerTimeInput', this.el.nativeElement).on("hide.timepicker", (e:any) => { // var totalSeconds = this.rp.formatObjectToSeconds({ // hours: e.time.hours, // minutes: e.time.minutes, // seconds: e.time.seconds // }); // this.rp.setCampaignsSchedule(this.m_campaignTimelineSchedulesModel.getCampaignTimelineId(), 'start_time', totalSeconds); // this.rp.reduxCommit(); // }); // } // var startTime = this.rp.formatSecondsToObject(this.m_campaignTimelineSchedulesModel.getStartTime()); // var startTimeFormatted = `${startTime.hours}:${startTime.minutes}:${startTime.seconds}`; // this.formInputs['start_time'].setValue(startTimeFormatted); // jQuery('#timepickerTimeInput', this.el.nativeElement).timepicker('setTime', startTimeFormatted); // var duration = this.rp.formatSecondsToObject(this.m_campaignTimelineSchedulesModel.getDuration()); // var durationFormatted = `${duration.hours}:${duration.minutes}:${duration.seconds}`; // this.formInputs['duration'].setValue(durationFormatted); // jQuery('#timepickerDurationInput', this.el.nativeElement).timepicker('setTime', durationFormatted); ================================================ FILE: src/app/campaigns/campaign-story-timeline.ts ================================================ /** Github repo: https://github.com/AlexWD/ds-timeline-widget **/ import {AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, Output, ViewChild} from "@angular/core"; import {Compbaser} from "ng-mslib"; import {YellowPepperService} from "../../services/yellowpepper.service"; import {CampaignTimelineBoardViewerChanelsModel, CampaignTimelineChanelsModel, CampaignTimelinesModel} from "../../store/imsdb.interfaces_auto"; import {RedPepperService} from "../../services/redpepper.service"; import {Observable} from "rxjs/Observable"; import {BlockService, IBlockData} from "../blocks/block-service"; import {IUiState} from "../../store/store.data"; import {ACTION_UISTATE_UPDATE, SideProps} from "../../store/actions/appdb.actions"; import {TimelineComponent} from "./timeline/timeline.component"; import {EventManager} from "@angular/platform-browser"; import {Lib} from "../../Lib"; import * as _ from "lodash"; import {List, Map} from "immutable"; interface IChannelCollection { blocks: Array; channelId: number; } interface IOutputs { id: number; name: string; color: string; selected: boolean; } interface IChannels { id: number; viewerId: number; name: string; type: 'common' | 'normal'; color: string; selected: boolean; } interface IItem { id: number; type: 'output' | 'channel'; resource: Object; title: string; start: number; duration: number; channel: number; selected: boolean; } export interface ITimelineState { zoom: number; switch: boolean; duration: number; channels: Array; outputs: Array; items: Array; } @Component({ selector: 'campaign-story-timeline', changeDetection: ChangeDetectionStrategy.OnPush, host: { '(document:keyup)': 'handleKeyboardEvents($event,"up")', '(document:keydown)': 'handleKeyboardEvents($event,"down")', '(window:mouseup)': 'handleMouseEvents($event,"up")', '(window:mousedown)': 'handleMouseEvents($event,"down")' }, template: ` {{me}}
    ` }) export class CampaignStoryTimeline extends Compbaser implements AfterViewInit { m_campaignTimelinesModels: List; campaignTimelinesModel: CampaignTimelinesModel; m_contPressed: 'down' | 'up' = 'up'; m_selectedItems: Array = []; m_zoom = 1; // @Output() // stateChanged:EventEmitter = new EventEmitter(); resources = { items: [ { id: 1, name: 'logo', type: 'png', time: '0', size: '110KB', src: 'assets/img/doc-13-128.png' }, { id: 2, name: 'samplesvg', type: 'svg', time: '0', size: '110KB', src: 'assets/img/svgexample.svg' } ], outputs: [ { id: 1, name: 'logo', type: 'png', time: '0', size: '110KB', src: 'assets/img/doc-13-128.png' }, { id: 2, name: 'samplesvg', type: 'svg', time: '0', size: '110KB', src: 'assets/img/svgexample.svg' } ] }; state: Map; // = Map({ // zoom: 1, // duration: -1, // channels: [], // outputs: [], // items: [] // }); stateTemp: ITimelineState = { zoom: 1, switch: false, duration: -1, channels: [], outputs: [], items: [] } // id = 0 constructor(private yp: YellowPepperService, private rp: RedPepperService, private cd: ChangeDetectorRef, private bs: BlockService, private eventManager: EventManager) { super(); this.cancelOnDestroy( this.yp.listenTimelineSelected() .map((i_campaignTimelinesModel: CampaignTimelinesModel) => { this.campaignTimelinesModel = i_campaignTimelinesModel; console.log('selected timeline ' + i_campaignTimelinesModel.getCampaignTimelineId()); return i_campaignTimelinesModel; }) .mergeMap((i_campaignTimelinesModel: CampaignTimelinesModel) => { return this.yp.listenChannelsOfTimeline(i_campaignTimelinesModel.getCampaignTimelineId()) }) .do((i_channels: List) => { return this.updateStateChannels(i_channels); }) .combineLatest( this.yp.listenCampaignTimelineBoardViewerSelected(true), this.yp.listenSelectedTimelineChanged(), this.yp.ngrxStore.select(store => store.msDatabase.sdk.table_campaign_timeline_chanel_players)) .map((i_data): List => { var i_campaignTimelinesModel = i_data[2]; this.updateStateDuration(i_campaignTimelinesModel.getTimelineDuration()); var i_campaignTimelineBoardViewerChanelsModel: CampaignTimelineBoardViewerChanelsModel = i_data["1"] if (i_campaignTimelineBoardViewerChanelsModel) this.updateStateChannelSelection(i_campaignTimelineBoardViewerChanelsModel.getCampaignTimelineChanelId()); return i_data[0]; }) .mergeMap((i_campaignTimelineChanelModels: List) => { var channelIds = []; i_campaignTimelineChanelModels.forEach((i_campaignTimelineChanelModel: CampaignTimelineChanelsModel) => { channelIds.push(i_campaignTimelineChanelModel.getCampaignTimelineChanelId()); }) return Observable.from(channelIds) .map((channelId) => { return this.yp.getChannelBlocks(channelId) .map((blocks) => { if (blocks.length > 0) { return {channelId, blocks}; } else { return {channelId, blocks: [-1]}; } }) }) .combineAll() }) .mergeMap((i_channelArray: Array) => { return Observable.from(i_channelArray) .map((i_channelCollection: IChannelCollection) => { return Observable.from(i_channelCollection.blocks) .map((i_block) => { if (i_block == -1) return Observable.of(-1); return this.bs.getBlockData(i_block).map((block) => { return {block, channelId: i_channelCollection.channelId} }) }) .combineAll() }) .combineAll() }) .withLatestFrom( this.yp.ngrxStore.select(store => store.appDb.uiState.campaign.blockChannelSelected), (i_channels, i_blockIdSelected) => ({i_channels, i_blockIdSelected}) ) .subscribe(({i_channels, i_blockIdSelected}) => { this.updateStateBlocks(i_channels, i_blockIdSelected); this.applyState(); this.cd.markForCheck(); }, e => console.error(e)) ); } @ViewChild(TimelineComponent) timelineComponent: TimelineComponent; // @Input() // set duration(i_duration:number) { // this.stateTemp.duration = i_duration; // this.applyState(); // } @Input() set zoom(i_zoom: number) { this.stateTemp.zoom = i_zoom; if (!this.timelineComponent) return; this.applyState(); this.cd.detectChanges(); this.timelineComponent.changeZoom(null); } @Input() set switchMode(i_mode: boolean) { this.stateTemp.switch = i_mode; this.applyState(); } private updateStateChannelSelection(i_channelSelectedId: number) { this.stateTemp.channels.forEach((ch, index) => { if (ch.id == i_channelSelectedId) { ch.selected = true; } else { ch.selected = false; } this.stateTemp.channels[index] = ch; }) } private updateStateDuration(i_duration: number) { console.log('CampaignStoryTimeline: upd duration ' + i_duration); this.stateTemp.duration = i_duration; } private updateStateChannels(i_channels: List) { var channels = [] i_channels.forEach((i_channel: CampaignTimelineChanelsModel) => { var channel: IChannels = { id: i_channel.getCampaignTimelineChanelId(), viewerId: this.rp.getAssignedViewerIdFromChannelId(i_channel.getCampaignTimelineChanelId()), name: i_channel.getChanelName(), color: '#' + Lib.DecimalToHex(i_channel.getChanelColor()), type: 'normal', selected: false } channels.push(channel); }); this.stateTemp.channels = channels; } private updateStateBlocks(i_channels, i_blockIdSelected) { var items = [] _.forEach(i_channels, (i_channel) => { var channelId = i_channel["0"].channelId; var blockList = this._sortBlock(i_channel); blockList.forEach((i_block) => { if (i_block == -1) return; var name; if (i_block.block.scene) { name = i_block.block.scene.name; } else if (i_block.block.resource) { name = i_block.block.resource.name; } else { name = i_block.block.blockName; } var block: IBlockData = i_block.block var item: IItem = { id: block.blockID, type: 'channel', channel: channelId, duration: block.duration, selected: i_blockIdSelected == block.blockID, title: name, start: block.offset, // uncomment for svg or png support // resource: { // type: 'svg', // src: "assets/sample3.svg" // } resource: { type: 'fa', src: i_block.block.blockFontAwesome } } items.push(item); }); this.stateTemp.items = items; }) } private applyState() { // if (this.stateTemp.duration == -1) // return; if (!this.state) return this.state = Map(this.stateTemp); const currentState = this.state.toJS(); var equal = _.isEqual(currentState, this.stateTemp); if (equal) return; this.state = Map(this.stateTemp); // this.stateChanged.emit(currentState); } private _sortBlock(i_blockList) { var sorted = i_blockList.sort((a, b) => { if (a.block.offset < b.block.offset) return -1; if (a.block.offset > b.block.offset) return 1; if (a.block.offset === b.block.offset) return 0; }) return sorted; } public closedGaps(){ this.timelineComponent.closeGaps(); } public resizeToLargest(){ this.timelineComponent.resizeToLargest(); } public alignLeft(){ this.timelineComponent.alignLeft(); } public alignRight(){ this.timelineComponent.alignRight(); } remove(id) { // let index = this.items.findIndex(item => item.id === id) // this.items.splice(index, 1) } reset() { // this.items = [] } add() { // this.items.unshift({id: this.id++, name: 'item'}) } itemsMoved(event) { event.forEach((item) => { // console.log("Item moved", item); this.rp.setBlockTimelineChannelBlockNewPosition(item.channel, item.id, "player_offset_time", item.start); }) this.rp.reduxCommit(); } channelAdded(event) { // console.log("Channel added", event); } onChannelClicked(event) { var uiState: IUiState = { campaign: { campaignTimelineChannelSelected: event.id, campaignTimelineBoardViewerSelected: event.id } } this.yp.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})) } handleMouseEvents(event: KeyboardEvent, direction) { // con(event + ' ' + direction); } handleKeyboardEvents(event: KeyboardEvent, direction) { var key = event.which || event.keyCode; if (key != 17) return; this.m_contPressed = direction; return true; } itemsClicked(event) { // con('Total items clicks ' + event.length); if (this.m_contPressed == 'down' || event.length == 0) return; const item = event[event.length - 1]; var uiState: IUiState = { campaign: { campaignTimelineBoardViewerSelected: item.channel, campaignTimelineChannelSelected: item.channel, blockChannelSelected: item.id }, uiSideProps: SideProps.channelBlock } this.yp.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})) } itemsChanged(event) { event.forEach((item) => { this.rp.setBlockTimelineChannelBlockNewPosition(item.channel, item.id, "player_offset_time", Math.round(item.start)); this.rp.setBlockTimelineChannelBlockNewPosition(item.channel, item.id, "player_duration", Math.round(item.duration)); }) this.rp.reduxCommit(); } itemAdded(event) { // console.log("Item Added", event); } ngAfterViewInit() { } ngOnInit() { } destroy() { } } ================================================ FILE: src/app/campaigns/campaigns-navigation.ts ================================================ import {ChangeDetectionStrategy, Component} from "@angular/core"; import {animate, state, style, transition, trigger} from "@angular/animations"; import {Compbaser} from "ng-mslib"; import {BlockService} from "../blocks/block-service"; import {AppdbAction, AuthenticateFlags} from "../../store/actions/appdb.actions"; import {PLACEMENT_CHANNEL} from "../../interfaces/Consts"; import {YellowPepperService} from "../../services/yellowpepper.service"; import {UserModel} from "../../models/UserModel"; @Component({ changeDetection: ChangeDetectionStrategy.OnPush, // new animation, can't add due to aot limitation and angular language service bug // host: { // '[@routerTransition]': '', // '[style.display]': "'block'" // }, // animations: [routerTransition()], providers: [BlockService, { provide: "BLOCK_PLACEMENT", useValue: PLACEMENT_CHANNEL }], host: { '[@routeAnimation]': 'true', '[style.display]': "'block'" }, animations: [ trigger('routeAnimation', [ state('*', style({opacity: 1})), transition('void => *', [ style({opacity: 0}), animate(333) ]), transition('* => void', animate(333, style({opacity: 0}))) ]) ], template: ` campaigns-navigation
    ` }) export class CampaignsNavigation extends Compbaser { userModel$; m_AuthenticateFlags = AuthenticateFlags; constructor(private actions: AppdbAction, private yp: YellowPepperService) { super(); this.userModel$ = this.yp.listenUserModel(); } destroy() { this.actions.resetCampaignSelection(); } } ================================================ FILE: src/app/campaigns/campaigns.ts ================================================ import {ChangeDetectionStrategy, Component, ViewChild} from "@angular/core"; import {Compbaser} from "ng-mslib"; import {ISliderItemData, Slideritem} from "../../comps/sliderpanel/Slideritem"; import {ACTION_UISTATE_UPDATE, SideProps} from "../../store/actions/appdb.actions"; import {IUiState, IUiStateCampaign} from "../../store/store.data"; import {YellowPepperService} from "../../services/yellowpepper.service"; import {RedPepperService} from "../../services/redpepper.service"; import {Once} from "../../decorators/once-decorator"; import {PLACEMENT_CHANNEL} from "../../interfaces/Consts"; import {IAddContents} from "../../interfaces/IAddContent"; import {CampaignTimelineBoardViewerChanelsModel, CampaignTimelinesModel} from "../../store/imsdb.interfaces_auto"; import {BlockService} from "../blocks/block-service"; import {IScreenTemplateData} from "../../interfaces/IScreenTemplate"; @Component({ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'campaigns', template: ` {{me}} ` }) export class Campaigns extends Compbaser { m_PLACEMENT_CHANNEL = PLACEMENT_CHANNEL; private m_selected_campaign_timeline_chanel_id = -1; private m_selected_campaign_timeline_id = -1; constructor(private yp: YellowPepperService, private rp: RedPepperService, private bs: BlockService) { super(); this.cancelOnDestroy( // this.yp.listenTimelineSelected(true) .subscribe((i_campaignTimelinesModel: CampaignTimelinesModel) => { if (!i_campaignTimelinesModel) return this.m_selected_campaign_timeline_id = -1; this.m_selected_campaign_timeline_id = i_campaignTimelinesModel.getCampaignTimelineId(); }, (e) => console.error(e)) ) this.cancelOnDestroy( // this.yp.listenCampaignTimelineBoardViewerSelected() .subscribe((i_campaignTimelineBoardViewerChanelsModel: CampaignTimelineBoardViewerChanelsModel) => { this.m_selected_campaign_timeline_chanel_id = i_campaignTimelineBoardViewerChanelsModel.getCampaignTimelineChanelId(); }, (e) => console.error(e)) ) this.cancelOnDestroy( // this.yp.listenLocationMapLoad() .subscribe((v) => { if (v && this.sliderItemCampaignEditor){ this.sliderItemCampaignEditor.slideTo('locationMap','right'); } }, (e) => console.error(e)) ) var uiState: IUiState = {uiSideProps: SideProps.miniDashboard} this.yp.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})) } @ViewChild('sliderItemCampaignEditor') sliderItemCampaignEditor:Slideritem; _onOpenScreenLayoutEditor() { } _onAddedContent(i_addContents: IAddContents) { this.cancelOnDestroy( // this.yp.getTotalDurationChannel(this.m_selected_campaign_timeline_chanel_id) .subscribe((i_totalChannelLength) => { var boilerPlate = this.bs.getBlockBoilerplate(i_addContents.blockCode); this.rp.createNewChannelPlayer(this.m_selected_campaign_timeline_chanel_id, i_addContents, boilerPlate, i_totalChannelLength); this.rp.updateTotalTimelineDuration(this.m_selected_campaign_timeline_id); this.rp.reduxCommit(); }, (e) => console.error(e)) ) } _onLocationMapClosed(){ this.sliderItemCampaignEditor.slideTo('campaignEditor','left') } _onAddedContentClosed(){ var uiState: IUiState = {uiSideProps: SideProps.miniDashboard} this.yp.ngrxStore.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})) } _onSlideChange(event: ISliderItemData) { if (event.direction == 'left' && event.to == 'campaignList') { var uiState: IUiState = {uiSideProps: SideProps.miniDashboard} return this.yp.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})) } // if (event.direction == 'right' && event.to == 'campaignEditor') // return this._createCampaign(); } @Once() private _addTimelineToCampaign(i_screenTemplateData: IScreenTemplateData) { return this.yp.getNewCampaignParmas() .subscribe((value: IUiStateCampaign) => { var campaign_board_id = this.rp.getFirstBoardIDofCampaign(value.campaignSelected); var board_id = this.rp.getBoardFromCampaignBoard(campaign_board_id); var newTemplateData = this.rp.createNewTemplate(board_id, i_screenTemplateData.screenProps); var board_template_id = newTemplateData['board_template_id'] var viewers = newTemplateData['viewers']; var campaign_timeline_id = this.rp.createNewTimeline(value.campaignSelected); this.rp.setCampaignTimelineSequencerIndex(value.campaignSelected, campaign_timeline_id, 0); this.rp.setTimelineTotalDuration(campaign_timeline_id, '0'); this.rp.createCampaignTimelineScheduler(value.campaignSelected, campaign_timeline_id); var campaign_timeline_board_template_id = this.rp.assignTemplateToTimeline(campaign_timeline_id, board_template_id, campaign_board_id); var channels = this.rp.createTimelineChannels(campaign_timeline_id, viewers); this.rp.assignViewersToTimelineChannels(campaign_timeline_board_template_id, viewers, channels); this.rp.reduxCommit(); }, (e) => { console.error(e) }) } @Once() private _createCampaign(i_screenTemplateData: IScreenTemplateData) { return this.yp.getNewCampaignParmas() .subscribe((value: IUiStateCampaign) => { var campaignId = this.rp.createCampaignEntire(i_screenTemplateData.screenProps, i_screenTemplateData.name, value.campaignCreateResolution); var uiState: IUiState = {campaign: {campaignSelected: campaignId}} this.yp.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})) }, (e) => { console.error(e) }) } } ================================================ FILE: src/app/campaigns/channel-block-props.ts ================================================ import {AfterViewInit, Component, ElementRef} from "@angular/core"; import {Compbaser} from "ng-mslib"; import {YellowPepperService} from "../../services/yellowpepper.service"; import {CampaignTimelineChanelPlayersModel, CampaignTimelinesModel} from "../../store/imsdb.interfaces_auto"; import {Lib} from "../../Lib"; import {RedPepperService} from "../../services/redpepper.service"; import {List} from "immutable"; import {CampaignTimelineChanelPlayersModelExt} from "../../store/model/msdb-models-extended"; import {StoryBoardListViewModeEnum} from "../../store/store.data"; @Component({ selector: 'channel-block-props', template: ` {{me}}
    hours / minutes /seconds
    `, }) export class ChannelBlockProps extends Compbaser implements AfterViewInit { m_blockLengthHours = 0; m_blockLengthMinutes = 0; m_blockLengthSeconds = 0; private m_selectedCampaignTimelinesModel: CampaignTimelinesModel; private m_campaignTimelineChanelPlayersModel: CampaignTimelineChanelPlayersModel constructor(private rp: RedPepperService, private yp: YellowPepperService, private el: ElementRef) { super(); } ngAfterViewInit() { this.cancelOnDestroy( this.yp.listenTimelineSelected() .subscribe((i_selectedCampaignTimelinesModel) => { this.m_selectedCampaignTimelinesModel = i_selectedCampaignTimelinesModel; }, (e) => console.error(e)) ) this.cancelOnDestroy( this.yp.listenBlockChannelSelected() .subscribe((i_campaignTimelineChanelPlayersModel: CampaignTimelineChanelPlayersModel) => { this.m_campaignTimelineChanelPlayersModel = i_campaignTimelineChanelPlayersModel; var totalSeconds = this.m_campaignTimelineChanelPlayersModel.getPlayerDuration() var totalSecondsObj = Lib.FormatSecondsToObject(totalSeconds); this.m_blockLengthHours = totalSecondsObj.hours; this.m_blockLengthMinutes = totalSecondsObj.minutes; this.m_blockLengthSeconds = totalSecondsObj.seconds; jQuery('#blockLengthHours', this.el.nativeElement).val(this.m_blockLengthHours).trigger('change'); jQuery('#blockLengthMinutes', this.el.nativeElement).val(this.m_blockLengthMinutes).trigger('change'); jQuery('#blockLengthSeconds', this.el.nativeElement).val(this.m_blockLengthSeconds).trigger('change'); }, (e) => console.error(e)) ) this._propLengthKnobsInit() } /** Update the blocks offset times according to current order of LI elements and reorder accordingly in msdb. @method _reOrderChannelBlocks @return none **/ _reOrderChannelBlocks() { var self = this this.cancelOnDestroy( this.yp.getChannelBlockModels(this.m_campaignTimelineChanelPlayersModel.getCampaignTimelineChanelId()) .withLatestFrom(this.yp.ngrxStore.select(store => store.appDb.uiState.campaign.storyBoardListViewModeSelected)) .filter(v => v[1] == StoryBoardListViewModeEnum.ListMode) .subscribe((v: any) => { var i_campaignTimelineChanelPlayersModels: List = v[0]; var sorted = i_campaignTimelineChanelPlayersModels.sort((a, b) => { if (a.getPlayerOffsetTimeInt() < b.getPlayerOffsetTimeInt()) return -1; if (a.getPlayerOffsetTimeInt() > b.getPlayerOffsetTimeInt()) return 1; if (a.getPlayerOffsetTimeInt() === b.getPlayerOffsetTimeInt()) return 0; }) var playerOffsetTime: any = 0; sorted.forEach((i_campaignTimelineChanelPlayersModel) => { console.log(i_campaignTimelineChanelPlayersModel.getPlayerDuration() + ' ' + i_campaignTimelineChanelPlayersModel.getPlayerOffsetTime()); var playerDuration = i_campaignTimelineChanelPlayersModel.getPlayerDuration(); self.rp.setBlockRecord(i_campaignTimelineChanelPlayersModel.getCampaignTimelineChanelPlayerId(), 'player_offset_time', Lib.ToValidNumber(playerOffsetTime)); console.log('player ' + i_campaignTimelineChanelPlayersModel.getCampaignTimelineChanelPlayerId() + ' offset ' + playerOffsetTime + ' playerDuration ' + playerDuration); playerOffsetTime = parseFloat(playerOffsetTime) + parseFloat(playerDuration); }) self.rp.updateTotalTimelineDuration(this.m_selectedCampaignTimelinesModel.getCampaignTimelineId()); self.rp.reduxCommit(); }, (e) => console.error(e)) ) } /** Create the block length knobs so a user can set the length of the block with respect to timeline_channel @method _propLengthKnobsInit @return none **/ _propLengthKnobsInit() { var self = this; jQuery('.knob', this.el.nativeElement).knob({ /*change: function (value) { console.log("change : " + value); var caller = this['i'][0].id; },*/ release: function (value) { // console.log(this.$.attr('value')); // console.log("release : " + value + ' ' + this['i'][0].id); var caller = this['i'][0].id; switch (caller) { case 'blockLengthHours': { self.m_blockLengthHours = parseInt(value) break; } case 'blockLengthMinutes': { self.m_blockLengthMinutes = parseInt(value) break; } case 'blockLengthSeconds': { self.m_blockLengthSeconds = parseInt(value) break; } } // log('upd: ' + self.m_block_id + ' ' + hours + ' ' + minutes + ' ' + seconds); if (self.m_blockLengthHours == 0 && self.m_blockLengthMinutes == 0 && self.m_blockLengthSeconds < 5) return; self.rp.setBlockTimelineChannelBlockLength(self.m_campaignTimelineChanelPlayersModel.getCampaignTimelineChanelPlayerId(), self.m_blockLengthHours, self.m_blockLengthMinutes, self.m_blockLengthSeconds); self.rp.reduxCommit(); self._reOrderChannelBlocks(); }, /*cancel: function () { console.log("cancel : ", this); },*/ draw: function () { if (this.$.data('skin') == 'tron') { var a = this.angle(this.cv) // Angle , sa = this.startAngle // Previous start angle , sat = this.startAngle // Start angle , ea // Previous end angle , eat = sat + a // End angle , r = 1; this.g.lineWidth = this.lineWidth; this.o.cursor && (sat = eat - 0.3) && (eat = eat + 0.3); if (this.o.displayPrevious) { ea = this.startAngle + this.angle(this.v); this.o.cursor && (sa = ea - 0.3) && (ea = ea + 0.3); this.g.beginPath(); this.g.strokeStyle = this.pColor; this.g.arc(this.xy, this.xy, this.radius - this.lineWidth, sa, ea, false); this.g.stroke(); } this.g.beginPath(); this.g.strokeStyle = r ? this.o.fgColor : this.fgColor; this.g.arc(this.xy, this.xy, this.radius - this.lineWidth, sat, eat, false); this.g.stroke(); this.g.lineWidth = 2; this.g.beginPath(); this.g.strokeStyle = this.o.fgColor; this.g.arc(this.xy, this.xy, this.radius - this.lineWidth + 1 + this.lineWidth * 2 / 3, 0, 2 * Math.PI, false); this.g.stroke(); return false; } } }); } ngOnInit() { } destroy() { } } ================================================ FILE: src/app/campaigns/channel-props.ts ================================================ import {Component} from "@angular/core"; import {FormBuilder, FormControl, FormGroup} from "@angular/forms"; import {Compbaser} from "ng-mslib"; import {YellowPepperService} from "../../services/yellowpepper.service"; import {RedPepperService} from "../../services/redpepper.service"; import {timeout} from "../../decorators/timeout-decorator"; import * as _ from "lodash"; import {CampaignTimelineChanelsModel} from "../../store/imsdb.interfaces_auto"; import {Lib} from "../../Lib"; import {Subject} from "rxjs/Subject"; @Component({ selector: 'channel-props', host: { '(input-blur)': 'onFormChange($event)' }, template: `
    channel properties {{me}}
    • Name:
    • Channel Color:
    • repeat to fit
    • random order
    `, styles: [` input.ng-invalid { border-right: 10px solid red; } .material-switch { position: relative; padding-top: 10px; } .input-group { padding-top: 10px; } i { width: 20px; } `] }) export class ChannelProps extends Compbaser { private channelModel: CampaignTimelineChanelsModel; private formInputs = {}; // m_channel$: Observable; m_contGroup: FormGroup; m_color; m_channelColorChanged = new Subject(); constructor(private fb: FormBuilder, private yp: YellowPepperService, private rp: RedPepperService) { super(); this.m_contGroup = fb.group({ 'repeat_to_fit': [0], 'chanel_name': [''], 'chanel_color': ['#000'], 'random_order': [0] }); _.forEach(this.m_contGroup.controls, (value, key: string) => { this.formInputs[key] = this.m_contGroup.controls[key] as FormControl; }) this.cancelOnDestroy( this.yp.listenChannelSelected() .subscribe((channel: CampaignTimelineChanelsModel) => { this.channelModel = channel; this.renderFormInputs(); }, (e) => { console.error(e) }) ); this.cancelOnDestroy( // this.m_channelColorChanged .debounceTime(1000) .filter(v => v != '#123') .subscribe((i_color: any) => { this.updateSore() }, (e) => console.error(e)) ) } onFormChange(event) { this.updateSore(); } @timeout() private updateSore() { // console.log(this.m_contGroup.status + ' ' + JSON.stringify(this.ngmslibService.cleanCharForXml(this.m_contGroup.value))); this.rp.setCampaignTimelineChannelRecord(this.channelModel.getCampaignTimelineChanelId(), 'random_order', this.m_contGroup.value.random_order); this.rp.setCampaignTimelineChannelRecord(this.channelModel.getCampaignTimelineChanelId(), 'repeat_to_fit', this.m_contGroup.value.repeat_to_fit); this.rp.setCampaignTimelineChannelRecord(this.channelModel.getCampaignTimelineChanelId(), 'chanel_name', this.m_contGroup.value.chanel_name); this.rp.setCampaignTimelineChannelRecord(this.channelModel.getCampaignTimelineChanelId(), 'chanel_color', Lib.ColorToDecimal(this.m_color)); this.rp.reduxCommit() } private renderFormInputs() { if (!this.channelModel) return; _.forEach(this.formInputs, (value, key: string) => { let data = this.channelModel.getKey(key); data = StringJS(data).booleanToNumber(); if (key == 'chanel_color') this.m_color = '#' + Lib.DecimalToHex(data); this.formInputs[key].setValue(data) }); }; destroy() { } } ================================================ FILE: src/app/campaigns/dashboard-props.ts ================================================ import {Component, Input, ChangeDetectionStrategy} from "@angular/core"; import {FormControl, FormGroup, FormBuilder} from "@angular/forms"; import {Compbaser, NgmslibService} from "ng-mslib"; import {CampaignsModelExt} from "../../store/model/msdb-models-extended"; import {YellowPepperService} from "../../services/yellowpepper.service"; import {RedPepperService} from "../../services/redpepper.service"; import {timeout} from "../../decorators/timeout-decorator"; import * as _ from "lodash"; import {Observable} from "rxjs"; import {UserModel} from "../../models/UserModel"; @Component({ selector: 'dashboard-props', host: { '(input-blur)': 'onFormChange($event)' }, template: ` ` }) export class DashboardProps extends Compbaser { // options: Object; // public userModel$: Observable; constructor(private yp: YellowPepperService) { super(); // this.userModel$ = this.yp.ngrxStore.select(store => store.appDb.userModel); // this.options = { // chart: { // plotBackgroundColor: null, // plotBorderWidth: null, // plotShadow: false, // type: 'pie', // height: '250' // }, // title : { text : 'simple chart' }, // series: [{ // data: [29.9, 71.5, 106.4, 129.2], // }] // }; } destroy() { } } ================================================ FILE: src/app/campaigns/index.ts ================================================ import {NgModule} from "@angular/core"; import {CommonModule} from "@angular/common"; import {RouterModule} from "@angular/router"; import {CampaignsNavigation} from "./campaigns-navigation"; import {DropdownModule, DropdownModule as DropdownModulePrime} from "primeng/primeng"; import {SharedModule} from "../../modules/shared.module"; import {Campaigns} from "./campaigns"; import {OrderListModule} from "primeng/components/orderlist/orderlist"; import {CampaignManager} from "./campaign-manager"; import {CampaignName} from "./campaign-name"; import {CampaignOrientation} from "./campaign-orientation"; import {CampaignLayout} from "./campaign-layout"; import {CampaignEditor} from "./campaign-editor"; import {CampaignResolution} from "./campaign-resolution"; import {CampaignList} from "./campaign-list"; import {Sequencer} from "./sequencer"; import {ScreenLayoutEditor} from "./screen-layout-editor"; import {ScreenLayoutEditorProps} from "./screen-layout-editor-props"; import {CampaignProps} from "./campaign-props"; import {TimelineProps} from "./timeline-props"; import {ChannelProps} from "./channel-props"; import {DashboardProps} from "./dashboard-props"; import {CampaignEditorProps} from "./campaign-editor-props"; import {CampaignSchedProps} from "./campaign-sched-props"; import {CampaignPropsManager} from "./campaign-props-manager"; import {CampaignChannels} from "./campaign-channels"; import {ChannelBlockProps} from "./channel-block-props"; import {CampaignDuration} from "./campaign-duration"; import {Ng2Bs3ModalModule} from "ng2-bs3-modal/ng2-bs3-modal"; import {TimelineComponent} from "./timeline/timeline.component"; import {TimelineRulerComponent} from "./timeline-ruler/timeline-ruler.component"; import {CampaignStoryTimeline} from "./campaign-story-timeline"; import {HourCounter} from "../../comps/hour-counter/hour-counter"; import {DurationInputComponent} from "../../comps/duration-input/duration-input.component"; export const LAZY_ROUTES = [ {path: ':folder', component: CampaignsNavigation}, {path: ':folder/:id', component: CampaignsNavigation}, {path: '**', component: CampaignsNavigation} ]; @NgModule({ imports: [DropdownModulePrime, SharedModule, CommonModule, DropdownModule, OrderListModule, Ng2Bs3ModalModule, RouterModule.forChild(LAZY_ROUTES)], declarations: [CampaignsNavigation, Campaigns, CampaignManager, CampaignName, CampaignOrientation, CampaignLayout, TimelineComponent, TimelineRulerComponent, HourCounter, CampaignEditor, CampaignResolution, CampaignList, Sequencer, ScreenLayoutEditor, ScreenLayoutEditorProps, CampaignChannels, CampaignDuration, CampaignStoryTimeline, DurationInputComponent, CampaignPropsManager, CampaignProps, TimelineProps, ChannelProps, DashboardProps, CampaignEditorProps, CampaignSchedProps, ChannelBlockProps] }) export class CampaignsLazyModule { } ================================================ FILE: src/app/campaigns/screen-layout-editor-props.ts ================================================ import {Component} from "@angular/core"; import {FormBuilder, FormControl, FormGroup} from "@angular/forms"; import {Compbaser} from "ng-mslib"; import {YellowPepperService} from "../../services/yellowpepper.service"; import {RedPepperService} from "../../services/redpepper.service"; import * as _ from "lodash"; import {BoardTemplateViewersModel} from "../../store/imsdb.interfaces_auto"; @Component({ selector: 'screen-layout-editor-props', //changeDetection: ChangeDetectionStrategy.OnPush, host: { '(input-blur)': '_saveToStore($event)' }, styles: [` /*:host > > > .ui-spinner-input {*/ /*width: 60px;*/ /*}*/ input { width: 70px; } .spinLabel { display: inline-block; width: 70px; } `], template: `
    layout properties {{me}}
    • top:
    • left:
    • width:
    • height:
    ` }) export class ScreenLayoutEditorProps extends Compbaser { private boardTemplateModel: BoardTemplateViewersModel; private formInputs = {}; contGroup: FormGroup; constructor(private fb: FormBuilder, private yp: YellowPepperService, private rp: RedPepperService) { super(); this.contGroup = fb.group({ 'pixel_height': [0], 'pixel_width': [0], 'pixel_x': [0], 'pixel_y': [0] }); _.forEach(this.contGroup.controls, (value, key: string) => { this.formInputs[key] = this.contGroup.controls[key] as FormControl; }) this.cancelOnDestroy( this.yp.listenGlobalBoardSelectedChanged(true) .subscribe((boardTemplateModel: BoardTemplateViewersModel) => { if (!boardTemplateModel) { _.forEach(this.formInputs, (value, key: string) => { this.formInputs[key].disable(); this.formInputs[key].setValue(0); }); return; } _.forEach(this.formInputs, (value, key: string) => { this.formInputs[key].enable(); }); this.boardTemplateModel = boardTemplateModel; this.renderFormInputs(); }, (e) => console.error(e)) ) } _saveToStore() { // console.log(this.contGroup.status + ' ' + JSON.stringify(this.contGroup.value)); if (this.contGroup.status != 'VALID' || !this.boardTemplateModel) return; var props = { x: this.contGroup.value.pixel_x, y: this.contGroup.value.pixel_y, w: this.contGroup.value.pixel_width, h: this.contGroup.value.pixel_height } this.rp.setBoardTemplateViewer(-1, this.boardTemplateModel.getBoardTemplateViewerId(), props); this.rp.reduxCommit() } private renderFormInputs() { _.forEach(this.formInputs, (value, key: string) => { let data = this.boardTemplateModel.getKey(key); data = StringJS(data).booleanToNumber(); this.formInputs[key].setValue(data) }); }; destroy() { } } ================================================ FILE: src/app/campaigns/screen-layout-editor.ts ================================================ import {AfterViewInit, Component, ComponentFactoryResolver, ComponentRef, ElementRef, EventEmitter, Output, ViewChild, ViewContainerRef} from "@angular/core"; import {Compbaser} from "ng-mslib"; import {YellowPepperService} from "../../services/yellowpepper.service"; import {BoardTemplateViewersModel, CampaignTimelinesModel} from "../../store/imsdb.interfaces_auto"; import {Observable} from "rxjs"; import {OrientationEnum} from "./campaign-orientation"; import {ScreenTemplate} from "../../comps/screen-template/screen-template"; import * as _ from "lodash"; import {Lib} from "../../Lib"; import {RedPepperService} from "../../services/redpepper.service"; import {ACTION_UISTATE_UPDATE, SideProps} from "../../store/actions/appdb.actions"; import {IUiState} from "../../store/store.data"; import Any = jasmine.Any; import {IScreenTemplateData} from "../../interfaces/IScreenTemplate"; interface selectTimelineBoardIdResult { campaignTimelinesModel: CampaignTimelinesModel, campaign_timeline_board_template_ids: number[] } @Component({ selector: 'screen-layout-editor', template: ` {{me}}

    Edit screen layout

    `, }) export class ScreenLayoutEditor extends Compbaser implements AfterViewInit { RATIO = 4; m_canvas; m_canvasID; m_onOverlap; m_selectedViewerID; m_bgSelectedHandler; m_orientation: OrientationEnum; m_objectMovingHandler; m_resolution: string; m_screenTemplateData: IScreenTemplateData; m_global_board_template_id: number = -1; m_campaign_timeline_id: number = -1; m_campaign_timeline_board_template_id: number = -1; private boardTemplateModel: BoardTemplateViewersModel; private componentRef: ComponentRef; constructor(private yp: YellowPepperService, private componentFactoryResolver: ComponentFactoryResolver, private rp: RedPepperService, private el: ElementRef) { super(); } @ViewChild('container', {read: ViewContainerRef}) container: ViewContainerRef; /** Constructor @method initialize **/ ngAfterViewInit() { this.cancelOnDestroy( this.yp.listenTimelineSelected() .concatMap((i_campaignTimelinesModel: CampaignTimelinesModel) => { return this.yp.getTemplatesOfTimeline(i_campaignTimelinesModel.getCampaignTimelineId()) .flatMap((i_campaign_timeline_board_template_ids) => { return Observable.of({ campaign_timeline_board_template_ids: i_campaign_timeline_board_template_ids, campaignTimelinesModel: i_campaignTimelinesModel }) }) }).subscribe((result: selectTimelineBoardIdResult) => { this.m_campaign_timeline_id = result.campaignTimelinesModel.getCampaignTimelineId(); this.m_campaign_timeline_board_template_id = result.campaign_timeline_board_template_ids[0]; this.selectView(result.campaignTimelinesModel.getCampaignTimelineId(), this.m_campaign_timeline_board_template_id); }, (e) => { console.error(e) }) ) this.cancelOnDestroy( this.yp.listenGlobalBoardSelectedChanged() .subscribe((boardTemplateModel: BoardTemplateViewersModel) => { this.boardTemplateModel = boardTemplateModel; var props = { h: boardTemplateModel.getPixelHeight(), w: boardTemplateModel.getPixelWidth(), x: boardTemplateModel.getPixelX(), y: boardTemplateModel.getPixelY() } this._moveViewer(props) }, (e) => { console.error(e) }) ) } /** Move the object / viewer to new set of coords @method _moveViewer @param {Object} i_props **/ _moveViewer(i_props) { var self = this; var viewer = self.m_canvas.getActiveObject(); if (viewer) { viewer.setWidth(i_props.w / self.RATIO); viewer.setHeight(i_props.h / self.RATIO); viewer.set('left', i_props.x / self.RATIO); viewer.set('top', i_props.y / self.RATIO); self._enforceViewerMinimums(viewer); self._enforceViewerVisible(); viewer.setCoords(); self.m_canvas.renderAll(); } } /** Listen to the removal of an existing screen division @method _listenRemoveDivision **/ _onRemoveDivision() { var self = this; if (_.isUndefined(self.m_canvas)) return; var totalViews = self.m_canvas.getObjects().length; if (totalViews < 2) { bootbox.alert('you must keep at least one viewable screen division'); return; } var campaign_timeline_chanel_id = this.rp.removeTimelineBoardViewerChannel(self.m_selectedViewerID); this.rp.removeBoardTemplateViewer(self.m_campaign_timeline_board_template_id, self.m_selectedViewerID); this.rp.removeChannelFromTimeline(campaign_timeline_chanel_id); this.rp.removeBlocksFromTimelineChannel(campaign_timeline_chanel_id); self.m_canvas.remove(self.m_canvas.getActiveObject()); self.m_canvas.renderAll(); this.rp.reduxCommit(); /*var viewer = self.m_canvas.item(0); var props = { y: viewer.get('top'), x: viewer.get('left'), w: viewer.get('width') * self.RATIO, h: viewer.get('height') * self.RATIO }; self._saveToStore(viewer, props); BB.comBroker.fire(BB.EVENTS.VIEWER_REMOVED, this, this, { campaign_timeline_chanel_id: campaign_timeline_chanel_id }); pepper.announceTemplateViewerEdited(self.m_campaign_timeline_board_template_id); */ } /** Listen to the addition of a new viewer @method (totalViews - i) **/ _onAddDivision() { var self = this; this.m_selectedViewerID = -1; self.m_canvas.deactivateAll().renderAll(); var props = { x: 0, y: 0, w: 100, h: 100 } var board_viewer_id = this.rp.createViewer(self.m_global_board_template_id, props); var campaign_timeline_chanel_id = this.rp.createTimelineChannel(self.m_campaign_timeline_id); this.rp.assignViewerToTimelineChannel(self.m_campaign_timeline_board_template_id, board_viewer_id, campaign_timeline_chanel_id); var viewer = new fabric.Rect({ left: 0, top: 0, fill: '#ececec', id: board_viewer_id, hasRotatingPoint: false, borderColor: '#5d5d5d', stroke: 'black', strokeWidth: 1, borderScaleFactor: 0, lineWidth: 1, width: 100, height: 100, cornerColor: 'black', cornerSize: 5, lockRotation: true, transparentCorners: false }); self.m_canvas.add(viewer); var props = { x: 0, y: 0, w: viewer.get('width') * self.RATIO, h: viewer.get('height') * self.RATIO } self._saveToStore(viewer, props); self.rp.reduxCommit(); } /** Load the editor into DOM using the StackView using animation slider @method selectView **/ selectView(i_campaign_timeline_id, i_campaign_timeline_board_template_id) { this.cancelOnDestroy( this.yp.getGlobalTemplateIdOfTimeline(i_campaign_timeline_board_template_id) .concatMap((i_board_template_id) => { this.m_global_board_template_id = i_board_template_id[0]; return this.yp.getTemplateViewersScreenProps(i_campaign_timeline_id, i_campaign_timeline_board_template_id) }) .subscribe((screenTemplateData: IScreenTemplateData) => { this.m_orientation = screenTemplateData.orientation; this.m_resolution = screenTemplateData.resolution; this.m_screenTemplateData = screenTemplateData; var w = parseInt(this.m_resolution.split('x')[0]) / this.RATIO; var h = parseInt(this.m_resolution.split('x')[1]) / this.RATIO; this._canvasFactory(w, h); this._listenObjectChanged(); this._listenObjectsOverlap(); this._listenBackgroundSelected(); }, (e) => console.error(e)) ) } /** Listen to re-order of screen division, putting selected on top @method _listenPushToTopDivision **/ _onPushTop() { var self = this; var viewer = self.m_canvas.getActiveObject(); if (!viewer) return; self.m_canvas.bringToFront(viewer); self._updateZorder(); } /** Change the z-order of viewers in pepper @method _updateZorder **/ _updateZorder() { var self = this; var viewers = self.m_canvas.getObjects(); for (var i in viewers) { // log(viewers[i].get('id') + ' ' + i); this.rp.updateTemplateViewerOrder(viewers[i].get('id'), i); } var active = self.m_canvas.getActiveObject(); self.m_canvas.setActiveObject(active); this.rp.reduxCommit(); } /** Listen to re-order of screen division, putting selected at bottom @method _listenPushToBottomDivision **/ _onPushBottom() { var self = this; var viewer = self.m_canvas.getActiveObject(); if (!viewer) return; self.m_canvas.sendToBack(viewer); self._updateZorder(); } /** Listen to changes on selecting the background canvas @method _listenBackgroundSelected **/ _listenBackgroundSelected() { var self = this; self.m_bgSelectedHandler = function (e) { var uiState: IUiState = {campaign: {campaignTimelineBoardViewerSelected: -1}} self.yp.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})) }; self.m_canvas.on('selection:cleared', self.m_bgSelectedHandler); } /** Listen to changes in viewer overlaps @method _listenObjectsOverlap **/ _listenObjectsOverlap() { var self = this; self.m_onOverlap = function (options) { options.target.setCoords(); self.m_canvas.forEachObject(function (obj) { if (obj === options.target) return; obj.setOpacity(options.target.intersectsWithObject(obj) ? 0.5 : 1); }); } self.m_canvas.on({ 'object:moving': self.m_onOverlap, 'object:scaling': self.m_onOverlap, 'object:rotating': self.m_onOverlap }); } /** Create the canvas to render the screen division @method _canvasFactory @param {Number} i_width @param {Number} i_height **/ _canvasFactory(i_width, i_height) { var self = this; var offsetH = i_height / 2; var offsetW = (i_width / 2) + 30; this.m_canvasID = _.uniqueId('screenLayoutEditorCanvas'); jQuery('#screenLayoutEditorCanvasWrap', this.el.nativeElement).append(`
    ${this.m_resolution.split('x')[0]}px x ${this.m_resolution.split('x')[1]}px
    `); this.m_canvas = new fabric.Canvas(this.m_canvasID); this.m_canvas.selection = false; let factory = this.componentFactoryResolver.resolveComponentFactory(ScreenTemplate); this.componentRef = this.container.createComponent(factory); this.m_screenTemplateData.scale = 4; this.componentRef.instance.setTemplate = this.m_screenTemplateData; var rects = this.componentRef.instance.getDivisions(); for (var i = 0; i < rects.length; i++) { var rectProperties: any = rects[i]; var rect: any = new fabric.Rect({ left: rectProperties.x.baseVal.value, top: rectProperties.y.baseVal.value, fill: '#ececec', id: jQuery(rectProperties).data('campaign_timeline_board_viewer_id'), hasRotatingPoint: false, borderColor: '#5d5d5d', stroke: 'black', strokeWidth: 1, borderScaleFactor: 0, width: rectProperties.width.baseVal.value, height: rectProperties.height.baseVal.value, cornerColor: 'black', cornerSize: 5, lockRotation: true, transparentCorners: false }); this.m_canvas.add(rect); //rect.on('selected', function () { // log('object selected a rectangle'); //}); } //this.m_canvas.on('object:moving', function (e) { // log('savings: ' + this.m_global_board_template_id); //}); setTimeout(function () { if (!self.m_canvas) return; self.m_canvas.setHeight(i_height); self.m_canvas.setWidth(i_width); self.m_canvas.renderAll(); }, 500); } /** Make sure that at least one screen division is visible within the canvas @method _enforceViewerVisible **/ _enforceViewerVisible() { var self = this; var pass = 0; var viewer; self.m_canvas.forEachObject(function (o) { viewer = o; if (pass) return; if (o.get('left') < (0 - o.get('width')) + 20) { } else if (o.get('left') > self.m_canvas.getWidth() - 20) { } else if (o.get('top') < (0 - o.get('height') + 20)) { } else if (o.get('top') > self.m_canvas.getHeight() - 20) { } else { pass = 1; } }); if (!pass && viewer) { viewer.set({left: 0, top: 0}).setCoords(); viewer.setCoords(); self.m_canvas.renderAll(); bootbox.alert({ message: "you must keep at least one viewable screen division", title: "screen division position reset" }); var props = { x: viewer.get('top'), y: viewer.get('left'), w: viewer.get('width') * self.RATIO, h: viewer.get('height') * self.RATIO } self._saveToStore(viewer, props); } } /** Enforce minimum x y w h props @method this._enforceViewerMinimums(i_viewer); @param {Object} i_rect **/ _enforceViewerMinimums(i_viewer) { var MIN_SIZE = 50; if ((i_viewer.width * this.RATIO) < MIN_SIZE || (i_viewer.height * this.RATIO) < MIN_SIZE) { i_viewer.width = MIN_SIZE / this.RATIO; i_viewer.height = MIN_SIZE / this.RATIO; var props = { x: i_viewer.get('top'), y: i_viewer.get('left'), w: MIN_SIZE, h: MIN_SIZE } this._saveToStore(i_viewer, props); } } /** Listen to changes in a viewer changes in cords and update pepper @method i_props **/ _listenObjectChanged() { var self = this; self.m_objectMovingHandler = function (e) { var o = e.target; if (o.width != o.currentWidth || o.height != o.currentHeight) { o.width = o.currentWidth; o.height = o.currentHeight; o.scaleX = 1; o.scaleY = 1; } self._enforceViewerMinimums(o); self._enforceViewerVisible(); var x = Lib.ParseToFloatDouble(o.left) * self.RATIO; var y = Lib.ParseToFloatDouble(o.top) * self.RATIO; var w = Lib.ParseToFloatDouble(o.currentWidth) * self.RATIO; var h = Lib.ParseToFloatDouble(o.currentHeight) * self.RATIO; // var a = o.get('angle'); var props = { w: w, h: h, x: x, y: y }; // self.m_property.viewPanel(Elements.VIEWER_EDIT_PROPERTIES); //todo: props //self.m_dimensionProps.setValues(props); self.m_selectedViewerID = o.id; self._saveToStore(o, props); }; // old code was wrapped in debouncer // self.m_objectMovingHandler = _.debounce(function (e) { // ... // }, 2000); self.m_canvas.on({ // 'object:moving': self.m_objectMovingHandler, // 'object:scaling': self.m_objectMovingHandler, 'object:selected': self.m_objectMovingHandler, 'object:modified': self.m_objectMovingHandler }); } /** Update Pepper with latest object dimensions @method _saveToStore @param {Object} i_props **/ _saveToStore(i_viewer, i_props) { var uiState: IUiState = {campaign: {campaignTimelineBoardViewerSelected: i_viewer.get('id')}} this.yp.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})) // console.log('Pepper ' + i_viewer.get('id') + ' ' + JSON.stringify(i_props)); this.rp.setBoardTemplateViewer(this.m_campaign_timeline_board_template_id, i_viewer.get('id'), i_props); i_viewer.setCoords(); this.m_canvas.renderAll(); this.rp.reduxCommit(); } /** Listen to selection of next viewer @method _listenSelectNextDivision **/ _selectNextDivision() { var self = this; var viewer = self.m_canvas.getActiveObject(); var viewIndex = self.m_canvas.getObjects().indexOf(viewer); var totalViews = self.m_canvas.getObjects().length; if (viewIndex == totalViews - 1) { self.m_canvas.setActiveObject(self.m_canvas.item(0)); } else { self.m_canvas.setActiveObject(self.m_canvas.item(viewIndex + 1)); } } @Output() onGoBack: EventEmitter = new EventEmitter(); _goBack() { var uiState: IUiState = { uiSideProps: SideProps.miniDashboard, campaign: { campaignTimelineBoardViewerSelected: -1 } } this.yp.ngrxStore.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})) this.onGoBack.emit(); } ngOnInit() { } destroy() { console.log('dest screen-layout-editor'); var self = this; if (!_.isUndefined(self.m_canvas)) { self.m_canvas.off('selection:cleared', self.m_bgSelectedHandler); self.m_canvas.off({ 'object:moving': self.m_objectMovingHandler, 'object:scaling': self.m_objectMovingHandler, 'object:selected': self.m_objectMovingHandler, 'object:modified': self.m_objectMovingHandler }); self.m_canvas.off({ 'object:moving': self.m_onOverlap, 'object:scaling': self.m_onOverlap, 'object:rotating': self.m_onOverlap }); self.m_canvas.clear().renderAll(); } // $('#screenLayoutEditorCanvasWrap').empty() self.m_canvasID = undefined; self.m_canvas = undefined; self.m_campaign_timeline_id = undefined; self.m_campaign_timeline_board_template_id = undefined; self.m_orientation = undefined; self.m_resolution = undefined; self.m_global_board_template_id = undefined; self.m_selectedViewerID = undefined; } } // this._listenPushToTopDivision(); // this._listenPushToBottomDivision(); // this._listenSelectNextDivision(); // this.listenTo(this.options.stackView, BB.EVENTS.SELECTED_STACK_VIEW, function (e) { // if (e == this) { // if (this.m_dimensionProps == undefined) { // require(['DimensionProps'], function (DimensionProps) { // this.m_dimensionProps = new DimensionProps({ // appendTo: Elements.VIEWER_DIMENSIONS, // showAngle: false // }); // $(this.m_dimensionProps).on('changed', function (e) { // var props = e.target.getValues(); // this._saveToStore(this.m_canvas.getActiveObject(), props); // this._moveViewer(props); // }); // this._render(); // }); // } else { // this._render(); // } // } // }); ================================================ FILE: src/app/campaigns/sequencer.ts ================================================ import {ChangeDetectionStrategy, Component, ElementRef, EventEmitter, Input, Output, QueryList, ViewChildren} from "@angular/core"; import {RedPepperService} from "../../services/redpepper.service"; import {YellowPepperService} from "../../services/yellowpepper.service"; import {CampaignTimelineBoardViewerChanelsModel, CampaignTimelinesModel} from "../../store/imsdb.interfaces_auto"; import {ScreenTemplate} from "../../comps/screen-template/screen-template"; import {Observable, Subscriber, Subscription} from "rxjs"; import {IUiState, IUiStateCampaign} from "../../store/store.data"; import {ACTION_UISTATE_UPDATE, SideProps} from "../../store/actions/appdb.actions"; import {List} from "immutable"; import * as _ from "lodash"; import {ContextMenuService} from "ngx-contextmenu"; import {Once} from "../../decorators/once-decorator"; import {IScreenTemplateData} from "../../interfaces/IScreenTemplate"; import {Compbaser} from "ng-mslib"; // import TweenLite = gsap.TweenLite; @Component({ selector: 'sequencer', styles: [` #dragcontainer { padding-left: 0; margin-left: 0; vertical-align: middle; width: 2500px; overflow-y: hidden; } .dottedHR { height: 6px; width: 2500px; opacity: 0.6; position: relative; border-top: 12px dotted #c1c1c1; padding-bottom: 7px; top: 20px; } .draggableTimeline { float: left; margin: 10px; overflow-y: hidden; } `], changeDetection: ChangeDetectionStrategy.OnPush, template: `
    {{me}}

    total timelines: {{m_campaignTimelinesModels?.size}}


    timeline properties for {{item?.name}} edit layout next channel
    ` }) export class Sequencer extends Compbaser { m_campaignTimelinesModels: List; m_screenTemplates$: Observable; private m_draggables; private m_thumbsContainer; private target; private x: number; private m_selectedScreenTemplate: ScreenTemplate; private m_selectedTimelineId: number; private m_campaignTimelineBoardViewerSelected: number = -1; private m_campaignTimelineChannelSelected: number = -1; private m_selectedCampaignId: number = -1; constructor(private el: ElementRef, private yp: YellowPepperService, private pepper: RedPepperService, private contextMenuService: ContextMenuService) { super(); this.m_thumbsContainer = el.nativeElement; } @ViewChildren(ScreenTemplate) screenTemplatesQueryList: QueryList; trackByFn(index, item) { return item.campaignTimelineId; } ngAfterViewInit() { // auto select the timeline / division on component creation if need to this.cancelOnDestroy( this.yp.ngrxStore.select(store => store.appDb.uiState.campaign.campaignTimelineChannelSelected) .filter(channelId => channelId != -1 && !_.isUndefined(this.m_selectedScreenTemplate)) .subscribe((i_channelId) => { this._onDivisionDoubleClicked(i_channelId); }) ) this.cancelOnDestroy( this.yp.ngrxStore.select(store => store.appDb.uiState.campaign) .take(1) .subscribe((i_campaign: IUiStateCampaign) => { if (i_campaign.timelineSelected != -1 && i_campaign.campaignTimelineBoardViewerSelected != -1) { this.tmpScreenTemplates.forEach((i_screenTemplate) => { if (i_screenTemplate.campaignTimelineId == i_campaign.timelineSelected) { this.m_selectedScreenTemplate = i_screenTemplate; this.m_selectedTimelineId = i_campaign.timelineSelected; this.m_campaignTimelineBoardViewerSelected = i_campaign.campaignTimelineBoardViewerSelected; this.m_campaignTimelineChannelSelected = i_campaign.campaignTimelineChannelSelected; this.m_selectedCampaignId = i_campaign.campaignSelected; i_screenTemplate.selectFrame(); i_screenTemplate.selectDivison(i_campaign.campaignTimelineBoardViewerSelected) } }) } }, (e) => console.error(e)) ) } _onContextClicked(cmd: string, screenTemplateData: IScreenTemplateData) { switch (cmd) { case 'edit': { this.onEditLayout.emit(); break; } case 'nextch': { this.onSelectNextChannel() break; } } } public onContextMenu($event: MouseEvent, item: any): void { this.contextMenuService.show.next({ event: $event, item: item, }); $event.preventDefault(); $event.stopPropagation(); } @ViewChildren(ScreenTemplate) tmpScreenTemplates: QueryList; @Input() set setCampaignTimelinesModels(i_campaignTimelinesModels: List) { if (!i_campaignTimelinesModels) return; this.m_campaignTimelinesModels = i_campaignTimelinesModels; this.m_selectedCampaignId = this.m_campaignTimelinesModels.first().getCampaignId(); this._sortTimelines((sortedTimelines: Array) => { this.m_screenTemplates$ = Observable.from(sortedTimelines) .map(i_campaignTimelinesModelsOrdered => { return this._getScreenTemplate(i_campaignTimelinesModelsOrdered) }).combineAll(); setTimeout(() => { this._createSortable('#dragcontainer'); }, 300) }); } @Output() onEditLayout: EventEmitter = new EventEmitter(); @Once() private _sortTimelines(i_cb: (sortedTimelines: Array) => void) { return Observable.from(this.m_campaignTimelinesModels.toArray()) .switchMap((i_campaignTimelinesModel: CampaignTimelinesModel) => { return this.yp.getCampaignTimelineSequencerIndex(i_campaignTimelinesModel.getCampaignTimelineId()) .filter(v => v != -1) .map((index) => { return Observable.of({ index: index, campaign: i_campaignTimelinesModel }) }) }) .combineAll() .subscribe((i_orderedTimelines: any) => { var orderedTimelines = _.sortBy(i_orderedTimelines, [function (o) { return o.index; }]); i_cb( _.toArray(_.map(orderedTimelines, function (o) { return o['campaign']; })) ); }, (e) => { console.error(e) }) } _getScreenTemplate(i_campaignTimelinesModel: CampaignTimelinesModel): Observable { return this.yp.getTemplatesOfTimeline(i_campaignTimelinesModel.getCampaignTimelineId()) .map((campaignTimelineBoardTemplateIds: Array) => { // for now return zero as we don't support multiple divisions per single timeline, yet return campaignTimelineBoardTemplateIds[0]; }).switchMap((campaignTimelineBoardTemplateId) => { return this.yp.getTemplateViewersScreenProps( i_campaignTimelinesModel.getCampaignTimelineId(), campaignTimelineBoardTemplateId, i_campaignTimelinesModel.getTimelineName() ); }) } _onScreenTemplateSelected(event, screenTemplate: ScreenTemplate) { this.tmpScreenTemplates.forEach((i_screenTemplate) => { if (i_screenTemplate == screenTemplate) { if (this.m_selectedTimelineId != i_screenTemplate.m_screenTemplateData.campaignTimelineId) { i_screenTemplate.selectFrame(); this.m_selectedScreenTemplate = i_screenTemplate; this.m_selectedTimelineId = i_screenTemplate.m_screenTemplateData.campaignTimelineId; this.m_campaignTimelineChannelSelected = -1; this.m_campaignTimelineBoardViewerSelected = -1; this._setAndNotifyIds(); } else { i_screenTemplate.deselectDivisons(); } var uiState: IUiState = { campaign: { campaignTimelineChannelSelected: -1, campaignTimelineBoardViewerSelected: -1 } } this.yp.ngrxStore.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})) this._notifyPropertySelect(SideProps.timeline); } else { i_screenTemplate.deSelectFrame(); i_screenTemplate.deselectDivisons(); } }) } private _setAndNotifyIds() { var uiState: IUiState = { campaign: { campaignTimelineChannelSelected: this.m_campaignTimelineChannelSelected, campaignTimelineBoardViewerSelected: this.m_campaignTimelineBoardViewerSelected, timelineSelected: this.m_selectedTimelineId } } this.yp.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})) } /** Create a sortable channel list @method _createSortable @param {Element} i_selector **/ private _createSortable(i_selector) { var self = this; if (jQuery(i_selector).children().length == 0) return; var sortable = document.querySelector(i_selector); self.m_draggables = Draggable.create(sortable.children, { type: "x", bounds: sortable, edgeResistance: 1, dragResistance: 0, onPress: self._sortablePress, onDragStart: self._sortableDragStart, onDrag: self._sortableDrag, liveSnap: self._sortableSnap, zIndexBoost: true, onDragEnd () { var t = this.target, max = t.kids.length - 1, newIndex = Math.round(this.x / t.currentWidth); //newIndex += (newIndex < 0 ? -1 : 0) + t.currentIndex; var preIndex = newIndex; //alert(this.x); newIndex += t.originalIndex; if (newIndex === max) { t.parentNode.appendChild(t); } else { if (preIndex >= 0) t.parentNode.insertBefore(t, t.kids[newIndex + 1]); else t.parentNode.insertBefore(t, t.kids[newIndex]); } TweenLite.set(t.kids, {xPercent: 0, overwrite: "all"}); TweenLite.set(t, {x: 0, color: ""}); var orderedTimelines = self.reSequenceTimelines(); // jQuery(self.m_thumbsContainer).empty(); // BB.comBroker.getService(BB.SERVICES.CAMPAIGN_VIEW).populateTimelines(orderedTimelines); // var campaign_timeline_id = BB.comBroker.getService(BB.SERVICES.CAMPAIGN_VIEW).getSelectedTimeline(); // self.selectTimeline(campaign_timeline_id); } } ); } /** Reorder the timeline in the local msdb to match the UI order of the timeline thumbnails in the Sequencer @method reSequenceTimelines @return {Array} order of timelines ids **/ reSequenceTimelines() { var self = this; var order = []; var timelines = jQuery('#dragcontainer', self.el.nativeElement).children().each(function (sequenceIndex) { var element = jQuery(this).find('[data-campaign_timeline_id]').eq(0); var campaign_timeline_id = jQuery(element).data('campaign_timeline_id'); order.push(campaign_timeline_id); self.pepper.setCampaignTimelineSequencerIndex(self.m_selectedCampaignId, campaign_timeline_id, sequenceIndex); }); this.pepper.reduxCommit(); return order; } /** Sortable channel list on press @method _sortablePress **/ private _sortablePress() { var t = this.target, i = 0, child = t; while (child = child.previousSibling) if (child.nodeType === 1) i++; t.originalIndex = i; t.currentWidth = jQuery(t).outerWidth(); t.kids = [].slice.call(t.parentNode.children); // convert to array } /** Sortable drag channel list on press @method _sortableDragStart **/ private _sortableDragStart() { TweenLite.set(this.target, {color: "#88CE02"}); } /** Sortable drag channel list @method _sortableDrag **/ private _sortableDrag() { var t = this.target, elements = t.kids.slice(), // clone // indexChange = Math.round(this.x / t.currentWidth), // round flawed on large values indexChange = Math.ceil(this.x / t.currentWidth), srcIndex = t.originalIndex, dstIndex = srcIndex + indexChange; // console.log('k ' + t.kids.length + ' s:' + srcIndex + ' d:' + indexChange + ' t:' + (dstIndex - srcIndex)); if (srcIndex < dstIndex) { // moved right TweenLite.to(elements.splice(srcIndex + 1, dstIndex - srcIndex), 0.15, {xPercent: -140}); // 140 = width of screen layout widget TweenLite.to(elements, 0.15, {xPercent: 0}); } else if (srcIndex === dstIndex) { elements.splice(srcIndex, 1); TweenLite.to(elements, 0.15, {xPercent: 0}); } else { // moved left // ignore if destination > source index if ((indexChange < 0 ? indexChange * -1 : indexChange) > srcIndex) return; TweenLite.to(elements.splice(dstIndex, srcIndex - dstIndex), 0.15, {xPercent: 140}); // 140 = width of screen layout widget TweenLite.to(elements, 0.15, {xPercent: -10}); } } /** snap channels to set rounder values @method _sortableSnap **/ private _sortableSnap(y) { return y; /* enable code below to use live drag snapping */ // var h = this.target.currentHeight; // return Math.round(y / h) * h; } @Once() _onDivisionDoubleClicked(i_campaign_timeline_board_viewer_id) { this.m_campaignTimelineBoardViewerSelected = i_campaign_timeline_board_viewer_id; this.m_selectedScreenTemplate.selectDivison(i_campaign_timeline_board_viewer_id) return this.yp.getChannelFromViewer(this.m_selectedTimelineId, i_campaign_timeline_board_viewer_id) .subscribe((result: any) => { this.m_campaignTimelineChannelSelected = result.channel; this._setAndNotifyIds() this._notifyPropertySelect(SideProps.channel); }, (e) => { console.error(e) }) } private _notifyPropertySelect(i_type) { var uiState: IUiState = {uiSideProps: i_type} this.yp.ngrxStore.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})) } /** Select next channel @method selectNextChannel **/ @Once() public onSelectNextChannel(): Subscription { if (!this.m_selectedScreenTemplate && this.screenTemplatesQueryList) this._onScreenTemplateSelected(null, this.screenTemplatesQueryList.first) var timeline_channel_id; return this.yp.getChannelsOfTimeline(this.m_selectedTimelineId) .subscribe((channelsIDs) => { if (this.m_campaignTimelineChannelSelected == -1) { timeline_channel_id = channelsIDs[0]; } else { for (var ch in channelsIDs) { if (channelsIDs[ch] == this.m_campaignTimelineChannelSelected) { if (_.isUndefined(channelsIDs[parseInt(ch) + 1])) { timeline_channel_id = channelsIDs[0]; } else { timeline_channel_id = channelsIDs[parseInt(ch) + 1]; } } } } this.m_campaignTimelineChannelSelected = timeline_channel_id; this.getAssignedViewerIdFromChannelId(timeline_channel_id); }, (e) => { console.error(e) }); } @Once() private getAssignedViewerIdFromChannelId(timeline_channel_id) { return this.yp.getAssignedViewerIdFromChannelId(timeline_channel_id) .subscribe((i_campaign_timeline_board_viewer_id) => { // note: workaround for when viewer is unassigned, need to investigate if (_.isUndefined(i_campaign_timeline_board_viewer_id)) return; this.m_campaignTimelineBoardViewerSelected = i_campaign_timeline_board_viewer_id; this.m_selectedScreenTemplate.selectDivison(i_campaign_timeline_board_viewer_id) this._setAndNotifyIds(); this._notifyPropertySelect(SideProps.channel); // self._removeBlockSelection(); // self._addChannelSelection(timeline_channel_id); // BB.comBroker.getService(BB.SERVICES['SEQUENCER_VIEW']).selectViewer(screenData.campaign_timeline_id, screenData.campaign_timeline_board_viewer_id); // BB.comBroker.fire(BB.EVENTS.ON_VIEWER_SELECTED, this, screenData); // BB.comBroker.fire(BB.EVENTS.CAMPAIGN_TIMELINE_CHANNEL_SELECTED, this, null, self.m_selectedChannel); }, (e) => console.error(e)); } ngOnInit() { } destroy() { } } ================================================ FILE: src/app/campaigns/timeline/timeline.component.css ================================================ #container { height: 801px; overflow: visible; padding: 0; position: relative; } :host /deep/ .box { background-color: #a9a9a9; text-align: center; font-family: Asap, Avenir, Arial, sans-serif; width: 196px; height: 50px; line-height: 50px; color: black; position: absolute; top: 0; border: 1px solid black; z-index: 1000; list-style: none; display: flex; align-items: center; } :host /deep/ .fa-item { float: left; padding: 0 16px 0 10px; opacity: 0.8; } .box-image { user-drag: none; user-select: none; -moz-user-select: none; -webkit-user-drag: none; -webkit-user-select: none; -ms-user-select: none; } .item-title { white-space: nowrap; overflow: hidden; } :host /deep/ .ui-selected { background: #c7c7c7; } .ruler { height: 50px; } .ruler-container { padding-left: 0px; } .channel-titles { padding-right: 0px; /*width: 97px;*/ } .channel-title { height: 50px; border: 1px solid black; background-color: #2d2d2d; color: #e0e0e0; font-weight: bold; line-height: 50px; padding-left: 10px; cursor: pointer; text-overflow: clip; white-space: nowrap; overflow: hidden; } .common-channel { text-decoration: underline; } .channel-color { width: 10px; height: 10px; display: inline-block; } .channel-selected { background: #656565; } .timeline { padding-left: 0px; /*max-width: 1200px;*/ overflow-x: auto; } :host /deep/ .timeline-row { position: absolute; border: 1px solid #454545; width: 100%; } :host /deep/ .output-row { background-color: #adadad; border: 1px solid #454545; width: 100%; } .timeline-container { } ================================================ FILE: src/app/campaigns/timeline/timeline.component.html ================================================
    Magnetic Switch Frozen
    {{ output.name }}
    {{ channel.name }}
  • {{ item.title }}
  • {{ item.title }}
  • Resources

    Name Type Total time size
    {{ resource.name }} {{ resource.type }} {{ resource.time }} {{ resource.size }}

    Outputs

    Name Type Total time size
    {{ resource.name }} {{ resource.type }} {{ resource.time }} {{ resource.size }}
    ================================================ FILE: src/app/campaigns/timeline/timeline.component.spec.ts ================================================ /* tslint:disable:no-unused-variable */ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { DebugElement } from '@angular/core'; import { TimelineComponent } from './timeline.component'; describe('TimelineComponent', () => { let component: TimelineComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [ TimelineComponent ] }) .compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(TimelineComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: src/app/campaigns/timeline/timeline.component.ts ================================================ import { Component, OnInit, Input, AfterViewChecked, OnChanges, EventEmitter, Output } from '@angular/core'; declare let $: any; declare let Draggable: any; declare let TweenLite: any; @Component({ selector: 'app-timeline', templateUrl: './timeline.component.html', styleUrls: ['./timeline.component.css'] }) export class TimelineComponent implements OnInit, AfterViewChecked, OnChanges { $container; ruler = undefined; dev = false; draggingItem; scrollPosition = 0; defaultState = { duration: 3600, gridWidth: 36000, gridHeight: 50, items: [], channels: [], outputs: [], selectedChannel: undefined, selectedItem: undefined, selectedOutput: undefined, frozen: false, magnetic: false, switch: false, zoom: 1 }; @Input() resources; @Input() state; @Output() itemsMoved = new EventEmitter(); @Output() itemAdded = new EventEmitter(); @Output() itemsClicked = new EventEmitter(); @Output() itemsResized = new EventEmitter(); @Output() channelAdded = new EventEmitter(); @Output() channelClicked = new EventEmitter(); @Output() outputAdded = new EventEmitter(); @Output() outputClicked = new EventEmitter(); @Output() closedGaps = new EventEmitter(); @Output() resizedToLargest = new EventEmitter(); @Output() alignedLeft = new EventEmitter(); @Output() alignedRight = new EventEmitter(); constructor() { } ngOnInit() { // reset item selection when the container is clicked this.$container.click((e) => { if (!$(e.target).hasClass('box') && !$(e.target).hasClass('box-image') && !$(e.target).hasClass('item-title')) { this.resetSelection(); } }); } ngOnChanges() { // ngOnChanges gets called first, so initialize here this.$container = $('#container'); this.state = Object.assign({}, this.defaultState, this.state.toJS()); this.initializeStateChanges(); } initializeStateChanges() { // draw channels this.drawChannels(); // initialize item positions this.state.items.map((item) => { item.left = item.start * (10 / this.state.zoom); item.width = item.duration * (10 / this.state.zoom); item.top = this.getChannelById(item.channel).top; item.overlapsLeft = []; item.overlapsRight = []; }); } ngAfterViewChecked() { var self = this; this.state.items.map((item, i) => { if (item.$el === undefined) { var startX, startY; var companions; var lastOverlap = 0; item.$el = $("li[data-bid='" + i + "']"); if (item.selected) { item.$el.addClass('ui-selected'); } var container = this.$container; item.draggable = Draggable.create(item.$el, { bounds: self.$container, edgeResistance: 1.0, type: "x,y", throwProps: false, lockAxis: false, x: 0, y: 0, liveSnap: { y: function(endValue) { return Math.round(endValue / self.state.gridHeight) * self.state.gridHeight; }, x: function(endValue) { // magnetic snapping if (self.state.magnetic) { self.state.items.filter(filterItem => filterItem != item && filterItem.channel == item.channel).map((otherItem) => { if (endValue >= otherItem.left + otherItem.width - 5 && endValue <= otherItem.left + otherItem.width + 5) { endValue = otherItem.left + otherItem.width; } if (endValue + item.width >= otherItem.left - 5 && endValue + item.width <= otherItem.left + 5) { endValue = otherItem.left - item.width; } }); } // only allow objects to stop at positions that represent whole numbers of seconds var timeValue = Math.round(endValue * self.state.zoom / 10); return timeValue * (10 / self.state.zoom); } }, onRelease: function () { // emit click event for selected items on release var selectedItems = self.state.items.filter(i => i.selected); self.itemsClicked.emit(selectedItems); // console.log("Item clicked: ", selectedItems); }, onPress: function(e) { // mutli-select functionality if (!e.ctrlKey && $(".box.ui-selected").length == 1) { self.resetSelection(); } self.selectItem(item); e.stopPropagation(); //when the user presses, we'll create an array ("companions") and populate it with all the OTHER elements that have the ".ui-selected" class applied (excluding the one that's being dragged). We also record their x and y position so that we can apply the delta to it in the onDrag. var boxes = $(".box.ui-selected"), i = boxes.length; companions = []; startX = this.x; startY = this.y; while (--i > -1) { var boxId = $(boxes[i]).data('bid'); if (boxes[i] !== this.target) { companions.push({ e: e, selfId: self.state.items.indexOf(item), item: self.state.items[boxId], itemId: boxId, element: boxes[i], x: boxes[i]._gsTransform.x, y: boxes[i]._gsTransform.y, lastX: boxes[i]._gsTransform.x, lastY: boxes[i]._gsTransform.y }); } else { self.state.items[boxId].selected = true; } } TweenLite.killTweensOf(".box"); }, onDrag: function() { // mutliselect movement var i = companions.length, deltaX = this.x - startX, deltaY = this.y - startY, companion; while (--i > -1) { companion = companions[i]; self.moveItem(companion.item, companion.x + deltaX, companion.y + deltaY); if (!companion.item.draggable.hitTest('#container', "100%")) { // if the item is moved outside of the bounds, move it back self.moveItem(companion.item, companion.lastX, companion.lastY); } else { companion.lastX = companion.x + deltaX; companion.lastY = companion.y + deltaY; } // start the companion dragging with the original event self.state.items[companion.selfId].draggable.startDrag(companion.e); } // switch if (self.state.switch) { // self.state.items.filter(item => item.selected).map((selectedItem) => { // self.state.items.filter(item => !item.selected).map((otherItem) => { // if (selectedItem.channel == otherItem.channel) { // var selectedItemId = self.state.items.indexOf(selectedItem); // // var min = 0, // max = (selectedItem.channel.type == "common" ? 13000 : self.state.gridWidth) - otherItem.width; // // if (selectedItem.left >= otherItem.left + otherItem.width / 2 - 10 && // selectedItem.left <= otherItem.left + otherItem.width / 2 + 10) { // self.moveItem(otherItem, Math.min(max, Math.max(min, selectedItem.left + selectedItem.width)), otherItem.top, 100); // } // // if (selectedItem.left + selectedItem.width >= otherItem.left + otherItem.width / 2 - 10 && // selectedItem.left + selectedItem.width <= otherItem.left + otherItem.width / 2 + 10) { // self.moveItem(otherItem, Math.min(max, Math.max(min, selectedItem.left - otherItem.width)), otherItem.top, 100); // } // } // }); // }); self.state.items.filter(item => item.selected).map((selectedItem) => { self.state.items.filter(item => !item.selected).map((otherItem) => { if (selectedItem.channel == otherItem.channel) { if (selectedItem.overlapsLeft == undefined) { selectedItem.overlapsLeft = []; } if (selectedItem.overlapsRight == undefined) { selectedItem.overlapsRight = []; } if (selectedItem.draggable.hitTest(otherItem.$el)) { if (selectedItem.left <= otherItem.left && selectedItem.overlapsLeft.indexOf(otherItem) === -1 && selectedItem.overlapsRight.indexOf(otherItem) === -1 ) { selectedItem.overlapsLeft.push(otherItem); } if (selectedItem.left > otherItem.left && selectedItem.overlapsRight.indexOf(otherItem) === -1 && selectedItem.overlapsLeft.indexOf(otherItem) === -1) { selectedItem.overlapsRight.push(otherItem); } } } }); selectedItem.overlapsLeft.map((otherItem, idx) => { if (!selectedItem.draggable.hitTest(otherItem.$el, "5%")) { selectedItem.overlapsLeft.splice(idx, 1); } }); selectedItem.overlapsRight.map((otherItem, idx) => { if (!selectedItem.draggable.hitTest(otherItem.$el, "5%")) { selectedItem.overlapsRight.splice(idx, 1); } }); }); self.state.items.filter(item => item.selected).map((selectedItem) => { selectedItem.overlapsLeft.map(otherItem => { var min = 0, max = (self.getChannelById(selectedItem.channel).type == "common" ? 13000 : self.state.gridWidth) - otherItem.width; if (selectedItem.left + selectedItem.width >= otherItem.left + otherItem.width - 10 && selectedItem.left + selectedItem.width <= otherItem.left + otherItem.width + 10) { self.moveItem(otherItem, Math.min(max, Math.max(min, otherItem.left - selectedItem.width)), otherItem.top, 100); selectedItem.overlapsLeft = []; } }); selectedItem.overlapsRight.map(otherItem => { var min = 0, max = (self.getChannelById(selectedItem.channel).type == "common" ? 13000 : self.state.gridWidth) - otherItem.width; if (selectedItem.left >= otherItem.left - 10 && selectedItem.left <= otherItem.left + 10) { var newPos = otherItem.left + selectedItem.width; self.moveItem(otherItem, Math.min(max, Math.max(min, newPos)), otherItem.top, 100); selectedItem.overlapsRight = []; } }); }); } } })[0]; //connect object to drag event listener to update position item.draggable.addEventListener("drag", function() { item.left = this._gsTransform.x; item.start = item.left * self.state.zoom / 10; item.top = this._gsTransform.y; self.updateItemChannel(item); }); item.draggable.addEventListener("dragend", function() { var selectedItems = self.state.items.filter(i => i.selected); self.itemsMoved.emit(selectedItems); // console.log("Item Moved: ", selectedItems); }); // set item initial position this.moveItem(item, item.left, item.top); // resize functionality var startWidth; // define a start width to calculate deltas for multi-resize $('.resizable').resizable({ handles: 'e, w', start: function (event, ui) { var id = ui.originalElement.data('bid'); var resizingItem = self.state.items[id]; startWidth = resizingItem.width; // console.log('start: ' + startWidth); }, resize: function (event, ui) { // var newWidth = Math.round(ui.size.width / 10) * 10; // $(this).width(newWidth); var id = ui.originalElement.data('bid'); var resizingItem = self.state.items[id]; resizingItem.width = ui.size.width; resizingItem.duration = ui.size.width * self.state.zoom / 10; var widthDelta = resizingItem.width - startWidth; // resizingItem.width = newWidth; // resizingItem.duration = newWidth * (self.state.zoom / 10); // var widthDelta = newWidth - startWidth; // move any companions self.state.items.filter((item) => item.selected).map((selectedItem) => { if (selectedItem != resizingItem) { selectedItem.width += widthDelta; selectedItem.duration = selectedItem.width * self.state.zoom / 10; } }); // console.log('newWidth: ' + resizingItem.width); startWidth = resizingItem.width; }, stop: function (event, ui) { // resizable modifies the left which messes with things, so we undo it and calculate the offsets var left = parseInt($(this).css('left')); var id = ui.originalElement.data('bid'); var resizingItem = self.state.items[id]; $(this).css({ left: 0 }); self.moveItem(resizingItem, resizingItem.left + left, resizingItem.top); var selectedItems = self.state.items.filter(i => i.selected); self.itemsResized.emit(selectedItems); // console.log("Item resized: " + selectedItems); } }); // makes GSAP Draggable avoid clicks on the resize handles $('.ui-resizable-handle').attr('data-clickable', true); this.applyItemBounds(); } }); } timelineDurationChange(dur) { this.state.duration = dur; // console.log('new timeline duration: ', dur); this.updateContainerSize(); } resetSelection() { $('.resizable').removeClass('ui-selected'); this.state.items.map((item) => { item.selected = false; }) } drawChannels() { this.$container.find(".timeline-row").remove(); this.state.outputs.concat(this.state.channels).map((channel, i) => { var type = this.state.channels.indexOf(channel) !== -1 ? "channel" : "output"; var resources; if (type == "channel") { resources = this.resources.items; } else { resources = this.resources.outputs; } // create element for channel and append it to the container channel.top = i * this.state.gridHeight; channel.$el = $("
    ").css({ height: this.state.gridHeight - 1, top: channel.top, left: 0 }).addClass('timeline-row').appendTo(this.$container); //add ondrop event listener for accepting item drops channel.$el[0].ondrop = (e) => { e.preventDefault(); var data = resources[e.dataTransfer.getData("text")]; var offset = this.$container.offset(); var left = e.x - offset.left; var top = i * this.state.gridHeight; this.addItem({ resource: { src: data.src, type: data.type }, title: data.name, type: type, left: left, width: 60 * (10 / this.state.zoom), channel: channel.id, top: top, draggable: undefined, $el: undefined, selected: false }); }; channel.$el[0].ondragover = (e) => { if (type == this.draggingItem) { e.preventDefault(); } }; }); //set the container's size to match the grid, and ensure that the box widths/heights reflect the variables above this.updateContainerSize(); this.applyItemBounds(); } applyItemBounds() { this.state.items.map((item) => { if (item.draggable !== undefined) { if (item.type == 'output') { var bounds = { left: 0, top: 0, width: this.state.gridWidth, height: this.state.outputs.length * this.state.gridHeight }; item.draggable.applyBounds(bounds); } else if (item.type == 'channel') { if (this.getChannelById(item.channel).type == "common") { item.draggable.applyBounds({ top: 0, left: 0, width: 13000, height: this.$container.height() }); } else { var bounds = { left: 0, top: this.state.outputs.length * this.state.gridHeight, width: this.state.gridWidth, height: this.state.channels.length * this.state.gridHeight }; item.draggable.applyBounds(bounds); } } } }); } onScroll(e) { this.scrollPosition = e.target.scrollLeft; } drag(e, type, resourceIndex) { e.dataTransfer.setData("text", resourceIndex); this.draggingItem = type; } resizeToLargest() { var largest = this.state.items.reduce((accum, item) => { return Math.max(accum, item.width); }, 0); var resizedItems = this.state.items.filter(item => item.selected) .map((item) => { item.width = largest; item.duration = largest * this.state.zoom / 10; item.$el.css({ width: largest }); return item; }); this.resizedToLargest.emit(resizedItems); // console.log("Resized to largest", resizedItems); } closeGaps() { var channel; this.state.channels.concat(this.state.outputs).map((c) => { if (c.selected) { channel = c.id; } }); for (var i = 0; i < this.state.items.length; ++i) { var item = this.state.items[i]; if (item.selected) { channel = item.channel; } } if (channel !== undefined) { // get the items in this channel and sort them left to right var groupedItems = this.state.items.filter((item) => { return item.channel == channel; }).sort((a, b) => { return a.left - b.left; }); // place the channels var nextStartPos = 0; groupedItems.map((item, i) => { this.moveItem(item, nextStartPos, item.top); nextStartPos += item.width; }); this.closedGaps.emit(groupedItems); // console.log("Closed gaps", groupedItems); } } alignLeft() { var leftAlign = this.state.items.filter((item) => item.selected) .reduce((accum, item) => Math.min(accum, item.left), Infinity); var alignedItems = this.state.items.filter(item => item.selected) .map((item, i) => { this.moveItem(item, leftAlign, item.top); return item; }); this.alignedLeft.emit(alignedItems); // console.log("Aligned Left", alignedItems); } alignRight() { var rightAlign = this.state.items.filter((item) => item.selected) .reduce((accum, item) => Math.max(accum, item.left + item.width), 0); var alignedItems = this.state.items.filter(item => item.selected) .map((item, i) => { this.moveItem(item, rightAlign - item.width, item.top); return item; }); this.alignedRight.emit(alignedItems); // console.log("Aligned Right", alignedItems); } changeZoom(e) { if (!this.state) return; var zoomFactor = 10 / this.state.zoom; this.state.items.map((item) => { item.width = item.duration * zoomFactor; this.moveItem(item, item.start * zoomFactor, item.top); }); this.updateContainerSize(); this.applyItemBounds(); } toggleFrozen(e) { this.state.frozen = !this.state.frozen; if (this.state.frozen) { this.state.items.map((item) => { item.draggable.disable(); }); } else { this.state.items.map((item) => { item.draggable.enable(); }); } } updateItemChannel(item) { var newChannel = this.state.channels.filter((c) => c.top == item.top)[0]; if (newChannel) { item.channel = newChannel.id; this.applyItemBounds(); } } moveItem(item, x, y, dur = 0) { if (item) { x = (x === undefined) ? item.left : x; y = (y === undefined) ? item.top : y; TweenLite.to(item.$el[0], dur / 1000, { x: x, y: y }); item.draggable.update(); // update the draggable position item.left = x; item.start = item.left * this.state.zoom / 10; item.top = y; this.updateItemChannel(item); } } addChannel(channel) { var newChannel = Object.assign( {}, { id: -1, $el: undefined, name: "CH" + this.state.channels.length, type: "normal", color: '#00FFFF' }, channel ); this.state.channels.push(newChannel); this.drawChannels(); this.channelAdded.emit(newChannel); // console.log("Channel Added: " + newChannel); } addCommonChannel(channel) { var newChannel = Object.assign( {}, { id: -1, $el: undefined, name: "CH" + this.state.channels.length, type: "common", color: '#0000FF' }, channel ); this.state.channels.push(newChannel); this.drawChannels(); this.channelAdded.emit(newChannel); // console.log("Channel added: " + newChannel); } addOutput(output) { var newOutput = Object.assign( {}, { id: -1, name: "Output", color: "#000" }, output ); this.state.outputs.push(newOutput); // move all existing items on channels down one row this.state.items.filter((item) => item.type == 'channel').map((item) => { this.moveItem(item, item.left, item.top + this.state.gridHeight); }); this.drawChannels(); this.outputAdded.emit(newOutput); // console.log("Output added: " + newOutput); } addItem(item) { var newItem = Object.assign( {}, { id: -1, duration: item.width, start: item.left, overlapsLeft: [], overlapsRight: [] }, item ); this.state.items.push(newItem); this.itemAdded.emit(newItem); // console.log("Item added: " + newItem); } getChannelById(id) { for (var i = 0; i < this.state.channels.length; ++i) { if (this.state.channels[i].id == id) return this.state.channels[i]; } return false; } updateContainerSize() { this.state.gridWidth = this.state.duration * (10 / this.state.zoom); TweenLite.set( this.$container, { height: (this.state.channels.length + this.state.outputs.length) * this.state.gridHeight + 1, width: this.state.gridWidth } ); } selectChannel(i) { var channel = this.state.channels[i]; this.resetObjectSelection(); channel.selected = true; this.channelClicked.emit(channel); // console.log("Channel clicked: " + channel); } selectItem(item) { //this.resetObjectSelection(); this.state.channels .concat(this.state.outputs).map((o) => { o.selected = false; }); item.selected = true; item.$el.addClass('ui-selected'); } selectOutput(i) { var output = this.state.outputs[i]; this.resetObjectSelection(); output.selected = true; this.outputClicked.emit(output); // console.log("Output clicked: " + output); } resetObjectSelection() { this.state.channels .concat(this.state.outputs) .map((o) => { o.selected = false; }); this.resetSelection(); } deleteChannel(i) { var deleted = this.state.channels.splice(i, 1)[0]; deleted.$el.remove(); this.drawChannels(); } deleteOutput(i) { var deleted = this.state.outputs.splice(i, 1)[0]; deleted.$el.remove(); // move all existing items on channels up one row this.state.items.filter((item) => item.type == 'channel').map((item) => { this.moveItem(item, item.left, item.top - this.state.gridHeight); }); this.drawChannels(); } deleteItem(i) { var deleted = this.state.items.splice(i, 1)[0]; } } ================================================ FILE: src/app/campaigns/timeline-props.ts ================================================ import {ChangeDetectorRef, Component} from "@angular/core"; import {FormBuilder, FormControl, FormGroup, Validators} from "@angular/forms"; import {Compbaser, NgmslibService} from "ng-mslib"; import {YellowPepperService} from "../../services/yellowpepper.service"; import {RedPepperService} from "../../services/redpepper.service"; import {timeout} from "../../decorators/timeout-decorator"; import * as _ from "lodash"; import {CampaignTimelinesModel} from "../../store/imsdb.interfaces_auto"; import {Observable} from "rxjs"; import {CampaignsModelExt} from "../../store/model/msdb-models-extended"; import {simpleRegExp} from "../../Lib"; @Component({ selector: 'timeline-props', host: { '(input-blur)': 'onFormChange($event)' }, template: `
    timeline properties {{me}}
    • playback mode: scheduler

      playback mode: sequencer

    • timeline length: {{m_duration}}


    `, styles: [` input.ng-invalid { border-right: 10px solid red; } .material-switch { position: relative; padding-top: 10px; } .input-group { padding-top: 10px; } i { width: 20px; } `] }) export class TimelineProps extends Compbaser { private timelineModel: CampaignTimelinesModel; private formInputs = {}; m_duration: string = '00:00:00' m_contGroup: FormGroup; m_campaignModel$: Observable; constructor(private fb: FormBuilder, private ngmslibService: NgmslibService, private yp: YellowPepperService, private rp: RedPepperService, private cd: ChangeDetectorRef) { super(); this.m_contGroup = fb.group({ 'timeline_name': ['', [Validators.required, Validators.pattern(simpleRegExp)]], }); _.forEach(this.m_contGroup.controls, (value, key: string) => { this.formInputs[key] = this.m_contGroup.controls[key] as FormControl; }) this.m_campaignModel$ = this.yp.listenCampaignValueChanged() this.cancelOnDestroy( this.yp.listenTimelineSelected() .subscribe((i_timelineModel: CampaignTimelinesModel) => { this.timelineModel = i_timelineModel; var totalDuration = parseInt(i_timelineModel.getTimelineDuration()) var xdate = new XDate(); this.m_duration = xdate.clearTime().addSeconds(totalDuration).toString('HH:mm:ss'); this.renderFormInputs(); this.cd.markForCheck(); }, (e) => console.error(e)) ); } private onFormChange(event) { this.updateSore(); } @timeout() private updateSore() { // con(this.m_contGroup.status + ' ' + JSON.stringify(this.ngmslibService.cleanCharForXml(this.m_contGroup.value))); this.rp.setCampaignTimelineRecord(this.timelineModel.getCampaignTimelineId(), 'timeline_name', this.m_contGroup.value.timeline_name); this.rp.reduxCommit() } private renderFormInputs() { if (!this.timelineModel) return; _.forEach(this.formInputs, (value, key: string) => { let data = this.timelineModel.getKey(key); data = StringJS(data).booleanToNumber(); this.formInputs[key].setValue(data) }); }; destroy() { } } ================================================ FILE: src/app/campaigns/timeline-ruler/timeline-ruler.component.css ================================================ #ruler { border: 1px solid black; } #ruler-container { height: 50px; } ================================================ FILE: src/app/campaigns/timeline-ruler/timeline-ruler.component.html ================================================
    ================================================ FILE: src/app/campaigns/timeline-ruler/timeline-ruler.component.spec.ts ================================================ /* tslint:disable:no-unused-variable */ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { DebugElement } from '@angular/core'; import { TimelineRulerComponent } from './timeline-ruler.component'; describe('TimelineRulerComponent', () => { let component: TimelineRulerComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [ TimelineRulerComponent ] }) .compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(TimelineRulerComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: src/app/campaigns/timeline-ruler/timeline-ruler.component.ts ================================================ import { Component, OnInit, Input, OnChanges } from '@angular/core'; @Component({ selector: 'app-timeline-ruler', templateUrl: './timeline-ruler.component.html', styleUrls: ['./timeline-ruler.component.css'] }) export class TimelineRulerComponent implements OnInit, OnChanges { @Input() width; @Input() height; @Input() scale : number = 1.0 / 10; @Input() position : number; canvas : any; ctx; constructor() { } ngOnInit() { this.drawScale(); window.onresize = () => this.drawScale(); } ngOnChanges() { this.drawScale(); } drawScale() { this.canvas = document.getElementById('ruler'); this.ctx = this.canvas.getContext('2d'); this.width = parseInt(this.width); this.canvas.width = Math.min(document.getElementById("ruler-container").offsetWidth, this.width); this.canvas.height = this.height; this.ctx.fillStyle = 'rgb(50, 50, 50)'; this.ctx.fillRect(0, 0, this.width, this.height); var minorStep = 10; var majorStep = 100; var minorLineHeight = 10; var majorLineHeight = 25; var pos = Number(this.position); var startPos; for (let i = -100; i <= 0;++i) { if ((pos + i) % majorStep == 0 || (pos + i) % minorStep == 0) { startPos = i; break; } } this.ctx.fillStyle = 'rgb(250, 250, 250)'; this.ctx.font = '12px verdana'; for (let i = startPos; i <= startPos + this.width + 100; i += minorStep) { var realPosition = pos + i; if (realPosition % majorStep == 0) { // draw major step this.ctx.fillRect(i, 50 - majorLineHeight, 1, majorLineHeight); var totalSeconds = realPosition * this.scale; var hours = Math.floor(totalSeconds / 60 / 60); totalSeconds -= hours * 3600; var minutes = Math.floor(totalSeconds / 60); totalSeconds -= minutes * 60; var seconds = totalSeconds; var displayHours = hours, displayMinutes = minutes < 10 ? "0" + +minutes : minutes, displaySeconds = seconds < 10 ? "0" + +seconds : seconds; // draw text this.ctx.fillText(`${displayHours}:${displayMinutes}:${displaySeconds}`, i, 15); } else if (realPosition % minorStep == 0) { // draw minor step this.ctx.fillRect(i, 50 - minorLineHeight, 1, minorLineHeight); } } } } ================================================ FILE: src/app/dashboard/dash-panel-mini.html ================================================ {{me}}
    {{resellerName}}

    {{clock$ | async | date:'medium'}}


    mode: online


    User: {{(m_userModel$ | async).getUser()}}
    Business id: {{(m_userModel$ | async).businessId()}}
    server : {{(m_userModel$ | async).getPartialDomain()}}
    app version: {{version}}
    framework version: {{ngVersion}}

    ================================================ FILE: src/app/dashboard/dash-panel-mini.ts ================================================ import {AfterViewInit, Component, VERSION} from "@angular/core"; import {Compbaser} from "ng-mslib"; import {YellowPepperService} from "../../services/yellowpepper.service"; import {RedPepperService} from "../../services/redpepper.service"; import {Observable} from "rxjs/Observable"; import * as packageJson from "../../../package.json"; @Component({ selector: 'dash-panel-mini', styles: [` a { font-size: 1.1em; } li { list-style-type: none; } `], templateUrl: './dash-panel-mini.html' }) export class DashPanelMini extends Compbaser implements AfterViewInit { clock$; m_userModel$; offlineDevMode: any = window['offlineDevMode']; version = packageJson.version; isBrandingDisabled: boolean = false; ngVersion = VERSION.full; resellerName = 'MediasSignage Inc.'; constructor(private yp: YellowPepperService, private rp: RedPepperService) { super(); this.clock$ = Observable .interval(300) .startWith(1) .map(() => new Date()); this.m_userModel$ = this.yp.listenUserModel(); this.cancelOnDestroy( this.yp.isBrandingDisabled() .subscribe((v) => { this.isBrandingDisabled = v; if (!this.isBrandingDisabled) this.resellerName = this.rp.getUserData().resellerName; }, (e) => console.error(e)) ) } ngAfterViewInit() { } destroy() { } } ================================================ FILE: src/app/dashboard/dash-panel.html ================================================ {{me}}


    ================================================ FILE: src/app/dashboard/dash-panel.ts ================================================ import {AfterViewInit, Component} from "@angular/core"; import {Compbaser} from "ng-mslib"; import {YellowPepperService} from "../../services/yellowpepper.service"; import {EFFECT_LOAD_FASTERQ_LINES, EFFECT_LOAD_STATIONS} from "../../store/effects/appdb.effects"; import {RedPepperService} from "../../services/redpepper.service"; import {List} from "immutable"; import {StationModel} from "../../models/StationModel"; import {Observable} from "rxjs/Observable"; import {LiveLogModel} from "../../models/live-log-model"; import {ACTION_LIVELOG_UPDATE} from "../../store/actions/appdb.actions"; @Component({ selector: 'dash-panel', styles: [` .twitter-timeline { width: 100% !important; } iframe { width: 100% !important; } `], templateUrl: './dash-panel.html' }) export class DashPanel extends Compbaser implements AfterViewInit { m_totalStationsConnected = 0; m_totalStationsDisconnected = 0; m_lastSave$; m_userModel$; m_scenes$; m_campaigns$; m_resources$; m_lines$; m_timelines$; m_liveLog:List; isBrandingDisabled: Observable; constructor(private yp: YellowPepperService, private rp: RedPepperService) { super(); this.m_lastSave$ = this.yp.ngrxStore.select(store => store.appDb.uiState.appSaved) this.m_userModel$ = this.yp.listenUserModel(); this.m_scenes$ = this.yp.getScenes(); this.m_campaigns$ = this.yp.getCampaigns(); this.m_resources$ = this.yp.getResources(); this.m_lines$ = this.yp.listenFasterqLines(); this.m_timelines$ = this.yp.getTimelines(); this.isBrandingDisabled = this.yp.isBrandingDisabled(); this._listenStationsConnection(); this._listenLoadLines(); this._listenLiveLog(); } _listenLiveLog() { this.cancelOnDestroy( this.yp.ngrxStore.select(store => store.appDb.liveLog) .subscribe((i_liveLog) => { this.m_liveLog = i_liveLog; }, (e) => console.error(e)) ) } _listenLoadLines() { this.yp.ngrxStore.dispatch({type: EFFECT_LOAD_FASTERQ_LINES, payload: {}}) this.yp.dispatch(({type: ACTION_LIVELOG_UPDATE, payload: new LiveLogModel({event: 'updating remote stations status'})})); } _listenStationsConnection() { this.cancelOnDestroy( this.yp.listenStations() .map((i_stationModels: List) => { i_stationModels.forEach(i_stationModel => { if (i_stationModel.connection == 0) { this.m_totalStationsDisconnected++; } else { this.m_totalStationsConnected++; } }); return i_stationModels; }).subscribe(() => { }, (e) => console.error(e)) ); this._loadStationData(); } ngAfterViewInit() { var twitter = function (d: any, s, id) { var js, fjs = d.getElementsByTagName(s)[0], p = /^http:/.test(d.location) ? 'http' : 'https'; // if (!d.getElementById(id)) { js = d.createElement(s); js.id = id; js.src = p + "://platform.twitter.com/widgets.js"; js.setAttribute('onload', "twttr.events.bind('rendered',function(e) {});"); fjs.parentNode.insertBefore(js, fjs); // } }(document, "script", "twitter-wjs"); this.setTwitterWidth(); } _loadStationData() { this.yp.ngrxStore.dispatch({type: EFFECT_LOAD_STATIONS, payload: {userData: this.rp.getUserData()}}); } setTwitterWidth() { Observable.interval(400) .take(5) .subscribe(() => { jQuery('.twitter-timeline').css({width: '100%'}); }) } destroy() { } } ================================================ FILE: src/app/dashboard/dashboard-navigation.ts ================================================ import {AfterViewInit, ChangeDetectionStrategy, Component} from "@angular/core"; import {Compbaser} from "ng-mslib"; import {animate, state, style, transition, trigger} from "@angular/animations"; import {ToastsManager} from "ng2-toastr"; import {timeout} from "../../decorators/timeout-decorator"; @Component({ selector: 'Dashboard', changeDetection: ChangeDetectionStrategy.OnPush, host: { '[@routeAnimation]': 'true', '[style.display]': "'block'" }, animations: [ trigger('routeAnimation', [ state('*', style({opacity: 1})), transition('void => *', [ style({opacity: 0}), animate(333) ]), transition('* => void', animate(333, style({opacity: 0}))) ]) ], template: `

    account dashboard

    ` }) export class Dashboard implements AfterViewInit { constructor(private toastr: ToastsManager) { } ngAfterViewInit(){ this.clearToasts(); } @timeout(1000) clearToasts(){ this.toastr.clearAllToasts(); } } ================================================ FILE: src/app/dashboard/server-avg.ts ================================================ import {ChangeDetectorRef, Component, Input} from "@angular/core"; import {Http} from "@angular/http"; import * as _ from 'lodash'; import {Observable} from "rxjs/Observable"; import {Compbaser} from "ng-mslib"; @Component({ selector: 'server-avg', styles: [` chart { width: 100% !important; margin: 0 auto; display: block; } `], template: `
    >
    ` }) export class ServerAvg extends Compbaser { _options; _chart: any; _ready = false; constructor(private _http: Http, private cd:ChangeDetectorRef) { super(); this.serverStatus(); this._options = { chart: { type: 'spline', height: 228, borderColor: '#d9d9d9', borderWidth: 1, marginRight: 10, }, responsive: { rules: [{ condition: { maxWidth: 500 }, chartOptions: { legend: { align: 'center', verticalAlign: 'bottom', layout: 'horizontal' }, yAxis: { labels: { align: 'left', x: 0, y: -5 }, title: { text: null } }, subtitle: { text: null }, credits: { enabled: false } } }] }, title: { text: '' }, xAxis: { labels: { enabled: false }, categories: [] }, credits: { enabled: false }, yAxis: [{ min: 0, title: { text: 'average cloud response' } }, { title: { text: 'measured in milliseconds' }, opposite: true }], legend: { enabled: false, shadow: false }, tooltip: { shared: true }, plotOptions: { column: { grouping: false, shadow: false, borderWidth: 0 } }, series: [{data: [0]}] }; } saveInstance(chartInstance) { this._chart = chartInstance; } public serverStatus() { this.cancelOnDestroy( Observable.interval(2000) .startWith(0) .switchMap(() => this._http.get(`https://secure.digitalsignage.com/msPingServersGuest`) .map(result => { this._ready = true; this.cd.markForCheck(); result = result.json(); var avg = 0, t = 0; _.forEach(result, (stats) => { t++; avg = Number(stats) + avg; }); if (!this._chart) return 0; return avg / t; }) ) .subscribe((value) => { if (!this._chart) return; var series = this._chart.series[0], shift = series.data.length > 20; this._chart.series[0].addPoint(value, true, shift); }, (e) => console.error(e)) ); } } ================================================ FILE: src/app/dashboard/storage-used.ts ================================================ import {ChangeDetectorRef, Component, Input} from "@angular/core"; import {Http} from "@angular/http"; import * as _ from 'lodash'; import {Observable} from "rxjs/Observable"; import {Compbaser} from "ng-mslib"; import {RedPepperService} from "../../services/redpepper.service"; import {Lib} from "../../Lib"; import {LiveLogModel} from "../../models/live-log-model"; import {ACTION_LIVELOG_UPDATE} from "../../store/actions/appdb.actions"; import {YellowPepperService} from "../../services/yellowpepper.service"; @Component({ selector: 'storage-used', styles: [` chart { width: 100% !important; margin: 0 auto; display: block; } `], template: `
    >
    ` }) export class StorageUsed extends Compbaser { _options; _chart: any; _ready = false; constructor(private yp: YellowPepperService, private cd:ChangeDetectorRef, private rp:RedPepperService) { super(); var totalCapacity = this.rp.getUserData().resellerID == 1 ? 1000 : 25000; var gigs = totalCapacity / 1000 + 'GB'; var bytesTotal = 0; this.rp.getResources().forEach((recResources)=>{ if (recResources['change_type'] != 3) bytesTotal = bytesTotal + parseInt(recResources['resource_bytes_total']); }); var used = Lib.ParseToFloatDouble((Math.ceil(bytesTotal / 1000000) / totalCapacity) * 100); var free = 100 - used; // if (String(mbTotalPercentRounded).length == 1 && String(mbTotalPercentRounded) != '0') // mbTotalPercentRounded = '0' + mbTotalPercentRounded; // console.log(`Used %${mbTotalPercentRounded} of ${gigs}gb` ); this._options = { chart: { type: 'pie', height: 228, borderColor: '#d9d9d9', borderWidth: 1, marginRight: 10, }, responsive: { rules: [{ condition: { maxWidth: 500 }, chartOptions: { legend: { align: 'center', verticalAlign: 'bottom', layout: 'horizontal' }, yAxis: { labels: { align: 'left', x: 0, y: -5 }, title: { text: null } }, subtitle: { text: null }, credits: { enabled: false } } }] }, title: { text: '' }, xAxis: { labels: { enabled: false }, categories: [] }, credits: { enabled: false }, yAxis: [{ min: 0, title: { text: 'average response time' } }, { title: { text: 'measured in milliseconds' }, opposite: true }], legend: { enabled: false, shadow: false }, tooltip: { shared: true }, plotOptions: { column: { grouping: false, shadow: false, borderWidth: 0 } }, series: [{ name: 'cloud storage', colorByPoint: true, data: [{ name: '% storage used', y: used }, { name: '% storage free', y: free, sliced: true, selected: true }] }] }; this._ready = true; } saveInstance(chartInstance) { this._chart = chartInstance; this.cd.markForCheck(); } // public serverStatus() { // this.cancelOnDestroy( // Observable.interval(2000) // .startWith(0) // .switchMap(() => // this._http.get(`https://secure.digitalsignage.com/msPingServersGuest`) // .map(result => { // this._ready = true; // this.cd.markForCheck(); // result = result.json(); // var avg = 0, t = 0; // _.forEach(result, (stats) => { // t++; // avg = Number(stats) + avg; // }); // if (!this._chart) // return 0; // return avg / t; // }) // ) // .subscribe((value) => { // if (!this._chart) // return; // var series = this._chart.series[0], // shift = series.data.length > 20; // this._chart.series[0].addPoint(value, true, shift); // }, (e) => console.error(e)) // ); // } } ================================================ FILE: src/app/fasterq/fasterq-editor.html ================================================ {{me}}
    Line: {{m_fasterqLineModel?.lineName}}
    Service id: {{m_selectedServiceId > 0 ? m_selectedServiceId : 'none'}}



    called customer:
    {{m_stopTimer}}
    last you serviced
    {{m_fqLastServiced}}
    last you called
    {{m_lastCalled}}
    total to be serviced
    {{m_totalToBeServiced}}
    avg customer service time (minutes):

    {{m_avgServiceTimeCalc}}

    avg customer wait time (minutes):

    {{m_avgCalledTimeCalc}}

    now servicing:

    Latest: {{m_nowServicing}}


    {{queue.serviceId}}

    ================================================ FILE: src/app/fasterq/fasterq-editor.ts ================================================ import {AfterViewInit, ChangeDetectorRef, Component, ElementRef} from "@angular/core"; import {Compbaser} from "ng-mslib"; import {YellowPepperService} from "../../services/yellowpepper.service"; import {FasterqQueueModel} from "../../models/fasterq-queue-model"; import {FasterqLineModel} from "../../models/fasterq-line-model"; import {FasterqAnalyticsModel} from "../../models/fasterq-analytics"; import {RedPepperService} from "../../services/redpepper.service"; import {EFFECT_LOAD_FASTERQ_ANALYTICS, EFFECT_LOAD_FASTERQ_QUEUES, EFFECT_QUEUE_CALL_SAVE, EFFECT_QUEUE_POLL_SERVICE, EFFECT_QUEUE_SERVICE_SAVE} from "../../store/effects/appdb.effects"; import {CommBroker} from "../../services/CommBroker"; import {FASTERQ_QUEUE_CALL_CANCLED} from "../../interfaces/Consts"; import {IUiState} from "../../store/store.data"; import {ACTION_UISTATE_UPDATE, SideProps} from "../../store/actions/appdb.actions"; import {List} from "immutable"; import {Lib} from "../../Lib"; import * as _ from "lodash"; import {timeout} from "../../decorators/timeout-decorator"; export interface IQueueSave { queue_id: number; queue: FasterqQueueModel; serviced?: string; called?: string; called_by?: string; called_by_override?: boolean; } @Component({ selector: 'fasterq-editor', styles: [` .personInLine { margin: 10px; padding: 0; float: left; width: 40px; height: 100px; cursor: pointer; color: #D0D0D0; } .called { color: #BE6734; } .serviced { color: #ACFD89; } `], templateUrl: './fasterq-editor.html' }) export class FasterqEditor extends Compbaser { QUEUE_OFFSET = 8; m_gotoModel = 0; m_stopWatchHandle: any = new Stopwatch(); m_stopTimer = '00:00:00'; m_fasterqLineModel: FasterqLineModel; m_selectedServiceId = -1; m_queues: List = List([]); m_analytics: List = List([]); m_offsetPosition = 0; m_avgServiceTimeCalc: any = '00:00:00'; m_avgCalledTimeCalc: any = '00:00:00'; m_lastCalled: any = 0; m_nowServicing: any = 0; m_totalToBeServiced = 0; m_fqLastServiced = ''; m_liveUpdateHandler; appBaseUrlServices; constructor(private yp: YellowPepperService, private rp: RedPepperService, private commBroker: CommBroker, private el: ElementRef, private cd: ChangeDetectorRef) { super(); this._pollServices(); this.cancelOnDestroy( this.yp.ngrxStore.select(store => store.appDb.appBaseUrlServices) .subscribe((i_appBaseUrlServices) => { this.appBaseUrlServices = i_appBaseUrlServices; }) ) this.cancelOnDestroy( this.commBroker.onEvent(FASTERQ_QUEUE_CALL_CANCLED) .subscribe((data: any) => { bootbox.confirm('Customer already called by user' + data.message.called_by + '

    Would you like to call the customer again?', (result) => { if (result) { data.message['called_by_override'] = true; this.yp.ngrxStore.dispatch({type: EFFECT_QUEUE_CALL_SAVE, payload: data.message}) } }); }, (e) => console.error(e)) ) this.cancelOnDestroy( this.yp.listenFasterqQueueLastServicedPolled() .subscribe((i_fasterqNowServicing) => { this.m_nowServicing = i_fasterqNowServicing; this.cd.markForCheck(); }, (e) => console.error(e)) ) this.cancelOnDestroy( this.yp.listenFasterqQueueSelected() .subscribe((i_serviceId) => { this.m_selectedServiceId = i_serviceId this.cd.markForCheck(); }, (e) => console.error(e)) ) this.cancelOnDestroy( this.yp.listenFasterqLineSelected() .subscribe((i_fasterqLineModel) => { this.m_fasterqLineModel = i_fasterqLineModel; this.cd.markForCheck(); }, (e) => console.error(e)) ) this.cancelOnDestroy( this.yp.listenFasterqQueues() .subscribe((i_queues: List) => { this.m_queues = List([]); for (var i = (0 - this.QUEUE_OFFSET); i < 0; i++) { i_queues = i_queues.unshift(new FasterqQueueModel({line_id: -1})) } this.m_queues = i_queues; this._selectFirst(); this.cd.markForCheck(); }, (e) => console.error(e)) ) this.cancelOnDestroy( this.yp.listenFasterqAnalytics() .subscribe((i_analytics: List) => { this.m_analytics = i_analytics; this._calcAverages(); this.cd.markForCheck(); }, (e) => console.error(e)) ) this._selectFirst(); } _openRemoteStatus() { window.open(this._buildURL(), "_blank", "toolbar=yes, scrollbars=yes, resizable=yes, top=10, left=10, width=400, height=400"); } /** Create URL string to load customer terminal UI for FasterQ queue generation @method _buildURL @return {String} URL **/ _buildURL() { var data = { line_id: this.m_fasterqLineModel.lineId, business_id: this.m_fasterqLineModel.businessId, call_type: 'QR' }; data = $.base64.encode(JSON.stringify(data)); return `${this.appBaseUrlServices}/studioweb/index.html?mode=remoteStatus¶m=${data}`; } @timeout(1000) _selectFirst() { if (this.m_queues.size != this.QUEUE_OFFSET + 1) return; this.m_selectedServiceId = this.m_queues.get(this.QUEUE_OFFSET).serviceId; this._onQueueSelected(this.m_queues.get(this.QUEUE_OFFSET)); } _pollServices() { this.m_liveUpdateHandler = setInterval(() => { this.yp.ngrxStore.dispatch({type: EFFECT_QUEUE_POLL_SERVICE, payload: {business_id: this.rp.getUserData().businessID, line_id: this.m_fasterqLineModel.lineId}}) this.yp.ngrxStore.dispatch(({type: EFFECT_LOAD_FASTERQ_QUEUES, payload: {line_id: this.m_fasterqLineModel.lineId}})) this.yp.ngrxStore.dispatch(({type: EFFECT_LOAD_FASTERQ_ANALYTICS, payload: {line_id: this.m_fasterqLineModel.lineId}})) this._updateTotalToBeServiced(); // todo: increase from 5 sec to 25sec 7-6-2018 }, 25000); } /** Update the total number of queues left to be serviced @method _updateTotalToBeServiced **/ _updateTotalToBeServiced() { var total = 0; this.m_queues.forEach((i_asterqQueueModel: FasterqQueueModel) => { if (_.isNull(i_asterqQueueModel.serviced)) total++; this.m_totalToBeServiced = total; }); } _onQueueSelected(i_queue: FasterqQueueModel) { this.m_selectedServiceId = i_queue.serviceId; var index = this._getQueueIndexByServiceId(); var uiState: IUiState = { uiSideProps: SideProps.fasterqQueueProps, fasterq: { fasterqQueueSelected: this.m_selectedServiceId } } this.yp.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})) this._scrollTo(index); } _getQueueIndexByServiceId(): number { if (this.m_selectedServiceId == -1) return this.m_selectedServiceId; return this.m_queues.findIndex((i_fasterqQueueModel) => { return i_fasterqQueueModel.serviceId == this.m_selectedServiceId; }) } /** Scroll to position of selected queue / UI person @method _scrollTo @param {Element} i_element **/ _scrollTo(i_index) { this._watchStop(); var el = $('#fqLineQueueComponent', this.el.nativeElement).children().eq(i_index); var scrollXPos = $(el).position().left; // console.log('current offset ' + scrollXPos + ' ' + 'going to index ' + $(i_element).index() + ' service_id ' + i_serviceId); this.m_offsetPosition = $('#fqLineQueueComponentContainer', this.el.nativeElement).scrollLeft(); scrollXPos += this.m_offsetPosition; var final = scrollXPos - 480; TweenLite.to('#fqLineQueueComponentContainer', 2, { scrollTo: {x: final, y: 0}, ease: Power4['easeOut'] }); } _getQueueFromSelectedIndex(): FasterqQueueModel { return this.m_queues.find(i_fasterqQueueModel => { return i_fasterqQueueModel.serviceId == this.m_selectedServiceId; }) } _selectIfDefault() { if (this.m_queues.size >= this.QUEUE_OFFSET + 1 && this.m_selectedServiceId == -1) this.m_selectedServiceId = this.m_queues.get(this.QUEUE_OFFSET).serviceId; } /** Listen to queue being called, mark on UI and post to server @method _listenCalled **/ _onCall() { this._selectIfDefault(); if (this.m_queues.size == this.QUEUE_OFFSET) return; if (!_.isNull(this._getQueueFromSelectedIndex().serviced)) return bootbox.alert('customer has already been serviced'); this._watchStart(); this.m_lastCalled = this._getQueueFromSelectedIndex().serviceId; var d = new XDate(); var payload: IQueueSave = { queue_id: this._getQueueFromSelectedIndex().queueId, called: d.toString('M/d/yyyy hh:mm:ss TT'), called_by: this.rp.getUserData().userName, called_by_override: false, queue: this._getQueueFromSelectedIndex() } this.yp.ngrxStore.dispatch({type: EFFECT_QUEUE_CALL_SAVE, payload: payload}) } /** Listen to queue being serviced, mark on UI and post to server @method _listenServiced **/ _onService() { this._selectIfDefault(); if (this.m_queues.size == this.QUEUE_OFFSET) return; this._watchStop(); if (_.isNull(this._getQueueFromSelectedIndex().called)) { bootbox.alert('customer has not been called yet'); return; } if (!_.isNull(this._getQueueFromSelectedIndex().serviced)) { bootbox.alert('customer has already been serviced'); return; } this.m_fqLastServiced = this._getQueueFromSelectedIndex().serviceId; var d = new XDate(); var payload: IQueueSave = { queue_id: this._getQueueFromSelectedIndex().queueId, serviced: d.toString('M/d/yyyy hh:mm:ss TT'), queue: this._getQueueFromSelectedIndex() } this.yp.ngrxStore.dispatch({type: EFFECT_QUEUE_SERVICE_SAVE, payload: payload}) } /** Calculate average respond and service line times @method _calcAverages **/ _calcAverages() { var avgServiceTime = []; var avgCalledTime = []; this.m_analytics.forEach((i_model: FasterqAnalyticsModel) => { var entered = i_model.entered; var serviced = i_model.serviced; var called = i_model.called; if (_.isNull(called)) { // customer not called, do nothing } else if (!_.isNull(serviced)) { // customer called & serviced var xEntered = new XDate(entered); var minFromEnteredToCalled = xEntered.diffMinutes(called); if (minFromEnteredToCalled < 0) minFromEnteredToCalled = 1; avgCalledTime.push(minFromEnteredToCalled); var xCalled = new XDate(called); var minFromCalledToServiced = xCalled.diffMinutes(serviced); avgServiceTime.push(minFromCalledToServiced); } else { // customer called not serviced var xEntered = new XDate(entered); var minFromEnteredToCalled = xEntered.diffMinutes(called); if (minFromEnteredToCalled < 0) minFromEnteredToCalled = 1; avgCalledTime.push(minFromEnteredToCalled); } }); this.m_avgServiceTimeCalc = _.reduce(avgServiceTime, function (memo, num) { return memo + num; }, 0) / (avgServiceTime.length === 0 ? 1 : avgServiceTime.length); this.m_avgServiceTimeCalc = Lib.ParseToFloatDouble(this.m_avgServiceTimeCalc); this.m_avgCalledTimeCalc = _.reduce(avgCalledTime, function (memo, num) { return memo + num; }, 0) / (avgCalledTime.length === 0 ? 1 : avgCalledTime.length); this.m_avgCalledTimeCalc = Lib.ParseToFloatDouble(this.m_avgCalledTimeCalc) } _onPrev() { if (this._getQueueIndexByServiceId() == this.QUEUE_OFFSET) return; this._onQueueSelected(this.m_queues.get(this._getQueueIndexByServiceId() - 1)); } _onNext() { if (this._getQueueIndexByServiceId() + 1 == this.m_queues.size || this.m_queues.size <= this.QUEUE_OFFSET + 1) return; if (this._getQueueIndexByServiceId() == -1) return this._onQueueSelected(this.m_queues.get(this.QUEUE_OFFSET)); this._onQueueSelected(this.m_queues.get(this._getQueueIndexByServiceId() + 1)); } _onGoTo() { var queue = this.m_queues.find((i_fasterqQueueModel: FasterqQueueModel) => { var serviceId = i_fasterqQueueModel.serviceId; var selectedId = Lib.PadZeros(this.m_gotoModel, 3, 0); return serviceId == selectedId; }) if (queue) this._onQueueSelected(queue); } /** Start the stop watch UI @method _watchStart **/ _watchStart() { this.m_stopWatchHandle.setListener((e) => { this.m_stopTimer = this.m_stopWatchHandle.toString(); this.cd.markForCheck(); }); this.m_stopWatchHandle.start(); } /** Stop the stop watch UI @method _watchStop **/ _watchStop() { this.m_stopWatchHandle.stop(); this.m_stopWatchHandle.reset(); this.m_stopTimer = '00:00:00'; } destroy() { clearInterval(this.m_liveUpdateHandler); var uiState: IUiState = { uiSideProps: SideProps.miniDashboard, fasterq: { fasterqQueueSelected: -1 } } this.yp.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})) } } ================================================ FILE: src/app/fasterq/fasterq-line-props.ts ================================================ import {Component, ChangeDetectionStrategy, AfterViewInit} from "@angular/core"; import {Compbaser} from "ng-mslib"; import {YellowPepperService} from "../../services/yellowpepper.service"; import {FasterqLineModel} from "../../models/fasterq-line-model"; import {FormBuilder, FormGroup} from "@angular/forms"; import {SideProps} from "../../store/actions/appdb.actions"; import {Observable} from "rxjs/Observable"; import {EFFECT_RESET_FASTERQ_LINE, EFFECT_UPDATE_FASTERQ_LINE} from "../../store/effects/appdb.effects"; import * as _ from 'lodash'; import {FasterqQueueModel} from "../../models/fasterq-queue-model"; import {RedPepperService} from "../../services/redpepper.service"; import {Lib} from "../../Lib"; @Component({ selector: 'fasterq-line-props', host: { '(input-blur)': 'saveToStore($event)' }, styles: [` input.ng-invalid { border-right: 10px solid red; } .material-switch { position: relative; padding-top: 10px; } .input-group { padding-top: 10px; } `], template: ` {{me}}
    • selected line
    • reminder ahead of people

    Queue properties

    • selected customer: {{m_customer}}
    • verification: {{m_verification}}
    • called by: {{m_calledBy}}
    `, }) export class FasterqLineProps extends Compbaser implements AfterViewInit { m_selectedLine: FasterqLineModel; m_selectedQueue: FasterqQueueModel; m_contGroup: FormGroup; m_sideProps$: Observable; m_sidePropsEnum = SideProps; m_customer = ''; m_verification = ''; m_calledBy: '' appBaseUrlServices constructor(private fb: FormBuilder, private yp: YellowPepperService, private rp: RedPepperService) { super(); this.m_sideProps$ = this.yp.ngrxStore.select(store => store.appDb.uiState.uiSideProps); this.m_contGroup = fb.group({ 'line_name': [''], 'reminder': [0] }); this.cancelOnDestroy( this.yp.ngrxStore.select(store => store.appDb.appBaseUrlServices) .subscribe((i_appBaseUrlServices) => { this.appBaseUrlServices = i_appBaseUrlServices; }) ) this.cancelOnDestroy( this.yp.listenFasterqLineSelected() .subscribe((i_lineSelected: FasterqLineModel) => { this.m_selectedLine = i_lineSelected; this.m_contGroup.controls.line_name.setValue(this.m_selectedLine.lineName) this.m_contGroup.controls.reminder.setValue(this.m_selectedLine.reminder) }, (e) => console.error(e)) ) this.cancelOnDestroy( this.yp.listenFasterqQueueModelSelected() .subscribe((i_queueSelected: FasterqQueueModel) => { this.m_selectedQueue = i_queueSelected; this.m_customer = this.m_selectedQueue.serviceId; this.m_verification = this.m_selectedQueue.verification == -1 ? 'print out' : this.m_selectedQueue.verification; this.m_calledBy = (_.isNull(this.m_selectedQueue.calledBy) ? 'none' : this.m_selectedQueue.calledBy); }, (e) => console.error(e)) ) } saveToStore() { this.yp.ngrxStore.dispatch({ type: EFFECT_UPDATE_FASTERQ_LINE, payload: { id: this.m_selectedLine.lineId, name: this.m_contGroup.controls.line_name.value, reminder: _.isNumber(this.m_contGroup.controls.reminder.value) ? this.m_contGroup.controls.reminder.value : 1 } }) } _onResetLine() { bootbox.prompt('are you sure you want to reset the counter? (enter YES)', (i_password) => { if (i_password != 'YES') return; this.yp.ngrxStore.dispatch({ type: EFFECT_RESET_FASTERQ_LINE, payload: { business_id: this.rp.getUserData().businessID, line_id: this.m_selectedLine.lineId, counter: 1 } }); }); } /** Listen to open customer terminal @method _listenOpenCustomerTerminal **/ _onOpenTerminal() { var data = { call_type: 'CUSTOMER_TERMINAL', business_id: this.rp.getUserData().businessID, line_id: this.m_selectedLine.lineId, line_name: this.m_selectedLine.lineName }; var rc4v2 = new RC4V2(); var rcData: any = rc4v2.encrypt(JSON.stringify(data), '8547963624824263'); var url; if (Lib.DevMode()) { url = `http://localhost:4208/index.html?data=${rcData}`; } else { url = `${this.appBaseUrlServices}/studioweb/index.html?data=${rcData}`; } window.open(url, '_blank'); } ngAfterViewInit() { } ngOnInit() { } destroy() { } } ================================================ FILE: src/app/fasterq/fasterq-manager.ts ================================================ import {Component, EventEmitter, Output} from "@angular/core"; import {Observable} from "rxjs"; import {Compbaser} from "ng-mslib"; import {RedPepperService} from "../../services/redpepper.service"; import {ACTION_LIVELOG_UPDATE, ACTION_UISTATE_UPDATE, SideProps} from "../../store/actions/appdb.actions"; import {IUiState} from "../../store/store.data"; import {YellowPepperService} from "../../services/yellowpepper.service"; import {ToastsManager} from "ng2-toastr"; import {EFFECT_ADD_FASTERQ_LINE, EFFECT_LOAD_FASTERQ_ANALYTICS, EFFECT_LOAD_FASTERQ_LINES, EFFECT_LOAD_FASTERQ_QUEUES, EFFECT_REMOVE_FASTERQ_LINE} from "../../store/effects/appdb.effects"; import {FasterqLineModel} from "../../models/fasterq-line-model"; import {List} from "immutable"; import {LiveLogModel} from "../../models/live-log-model"; @Component({ selector: 'fasterq-manager', template: ` {{me}}
    scene selection
    ` }) export class FasterqManager extends Compbaser { selectedIdx = -1; m_selectedLine: FasterqLineModel; lines$: Observable> constructor(private yp: YellowPepperService, private rp: RedPepperService, private toastr: ToastsManager) { super(); this.preventRedirect(true); this.yp.ngrxStore.dispatch({type: EFFECT_LOAD_FASTERQ_LINES, payload: {}}) this.lines$ = this.yp.listenFasterqLines(); var uiState: IUiState = {fasterq: {fasterqLineSelected: -1}}; this.yp.ngrxStore.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})) } @Output() slideToFasterqEditor: EventEmitter = new EventEmitter(); @Output() onLineSelected: EventEmitter = new EventEmitter(); _onLineSelected(event: MouseEvent, i_fasterqLineModel: FasterqLineModel, index) { // event.stopPropagation(); // event.preventDefault(); this.selectedIdx = index; let uiState: IUiState; if (jQuery(event.target).hasClass('props')) { uiState = { uiSideProps: SideProps.fasterqLineProps, fasterq: { fasterqLineSelected: i_fasterqLineModel.lineId } } this.onLineSelected.emit(uiState) } else { uiState = { fasterq: { fasterqLineSelected: i_fasterqLineModel.lineId } } this.slideToFasterqEditor.emit(); // this.onCampaignSelected.emit(uiState) } this.m_selectedLine = i_fasterqLineModel; this.yp.ngrxStore.dispatch(({type: EFFECT_LOAD_FASTERQ_QUEUES, payload: {line_id: i_fasterqLineModel.lineId}})) this.yp.ngrxStore.dispatch(({type: EFFECT_LOAD_FASTERQ_ANALYTICS, payload: {line_id: i_fasterqLineModel.lineId}})) this.yp.ngrxStore.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})) } _createNew() { this.yp.ngrxStore.dispatch({ type: EFFECT_ADD_FASTERQ_LINE, payload: {name: 'new line', business_id: this.rp.getUserData().businessID} }) this.yp.dispatch(({type: ACTION_LIVELOG_UPDATE, payload: new LiveLogModel({event: 'created new fasterq line'})})); } _remove() { bootbox.confirm("Are you sure you want to remove the Line and associated queues?", (result) => { if (!result) return; this.yp.ngrxStore.dispatch({ type: EFFECT_REMOVE_FASTERQ_LINE, payload: {id: this.m_selectedLine.lineId} }) var uiState: IUiState = {uiSideProps: SideProps.miniDashboard} this.yp.ngrxStore.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})) this.m_selectedLine = null; this.selectedIdx = -1; this.yp.dispatch(({type: ACTION_LIVELOG_UPDATE, payload: new LiveLogModel({event: 'removed fasterq line id: ' + this.m_selectedLine.lineId})})); }); } destroy() { var uiState: IUiState = {uiSideProps: SideProps.miniDashboard} this.yp.ngrxStore.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})) } } ================================================ FILE: src/app/fasterq/fasterq-navigation.ts ================================================ import {ChangeDetectionStrategy, Component} from "@angular/core"; import {Compbaser} from "ng-mslib"; import {animate, state, style, transition, trigger} from "@angular/animations"; @Component({ changeDetection: ChangeDetectionStrategy.OnPush, host: { '[@routeAnimation]': 'true', '[style.display]': "'block'" }, providers: [], animations: [ trigger('routeAnimation', [ state('*', style({opacity: 1})), transition('void => *', [ style({opacity: 0}), animate(333) ]), transition('* => void', animate(333, style({opacity: 0}))) ]) ], template: ` scene-navigation ` }) export class FasterqNavigation extends Compbaser { } ================================================ FILE: src/app/fasterq/fasterq-terminal.ts ================================================ import {AfterViewInit, Component, ElementRef, NgZone} from "@angular/core"; import {Compbaser} from "ng-mslib"; import {YellowPepperService} from "../../services/yellowpepper.service"; import {ActivatedRoute} from "@angular/router"; import {FasterqLineModel} from "../../models/fasterq-line-model"; import {EFFECT_LOAD_FASTERQ_LINE, EFFECT_LOADED_FASTERQ_LINE, EFFECT_QUEUE_CALL_SAVE} from "../../store/effects/appdb.effects"; import * as _ from 'lodash'; import {timeout} from "../../decorators/timeout-decorator"; import {Http, RequestMethod, RequestOptionsArgs, Response} from "@angular/http"; import {Lib} from "../../Lib"; import {ToastsManager} from "ng2-toastr"; import {LocalStorage} from "../../services/LocalStorage"; @Component({ selector: 'fasterq-terminal', styles: [` .largeFont2em { font-size: 2em; } .lPad { padding-left: 20px; } .carousel-inner { height: 400px; } `], template: ` {{me}}

    now serving {{m_currentlyServing}}

    you are {{m_fasterqLineModel.serviceId}} in line

    line name: {{m_fasterqLineModel?.lineName}}

    your verification number is {{m_fasterqLineModel?.verification}}

    {{m_fasterqLineModel?.lineName}}



    ` }) export class FasterqTerminal extends Compbaser implements AfterViewInit { m_fasterqLineModel: FasterqLineModel; m_displayServiceId = ''; appBaseUrlServices; emailAddress = '' m_appMode = ''; m_baseUrl sms = '' m_remoteStatusServiceId; m_remoteStatusVerification; m_statusHandler; m_currentlyServing = 0; m_printing = false; m_serviceId = 0; m_lineName = ''; constructor(private toastr: ToastsManager, private http: Http, private yp: YellowPepperService, private router: ActivatedRoute, private el: ElementRef, private zone: NgZone, private simplestorage: LocalStorage) { super(); // this.preventRedirect(true); this.cancelOnDestroy( this.yp.ngrxStore.select(store => store.appDb.fasterq.terminal) .filter(v => !_.isNull(v)) .subscribe((i_fasterqLineModel: FasterqLineModel) => { this.m_fasterqLineModel = i_fasterqLineModel; switch (this.m_appMode) { case 'CUSTOMER_TERMINAL': { this._createQRcode(); break; } case 'REMOTE_STATUS': { this.m_baseUrl = `${this.appBaseUrlServices}?mode=remoteStatus¶m=`; this._getServerDateTime(this._initServices); // this._releaseSpot(); // this._getNewSpot(); break; } default: { console.log('fasterq problem, did not get line id'); } } }, (e) => console.error(e)) ) this.cancelOnDestroy( this.yp.ngrxStore.select(store => store.appDb.appBaseUrlServices) .subscribe((i_appBaseUrlServices) => { this.appBaseUrlServices = i_appBaseUrlServices; }) ) } ngOnInit() { this.cancelOnDestroy( this.router.params .take(1) .subscribe(i_params => { if (i_params.id.indexOf('data') > -1) { /** Terminal mode, coming from Studio link **/ var params = i_params['id'].split('=')[1] + '=='; var rc4v2 = new RC4V2(); var rcData: any = rc4v2.decrypt(params.replace(/=/ig, ''), '8547963624824263'); var data = JSON.parse(rcData); this.m_appMode = 'CUSTOMER_TERMINAL'; this.yp.ngrxStore.dispatch({type: EFFECT_LOAD_FASTERQ_LINE, payload: {lineId: data.line_id, businessId: data.business_id}}) } else { /** Remote status mode, coming server short url **/ var params: string = i_params['id'].split('=')[2].replace(/%3D/ig, '='); var rcData = $.base64.decode(params); var data = JSON.parse(rcData); this.m_appMode = 'REMOTE_STATUS'; this.yp.ngrxStore.dispatch({type: EFFECT_LOADED_FASTERQ_LINE, payload: new FasterqLineModel(data)}) } }, (e) => console.error(e)) ) } ngAfterViewInit() { } /** Get current server date / time @method _getServerDateTime server:getDateTime @param {Function} i_cb **/ _getServerDateTime(i_cb) { var self = this; $.ajax({ url: `${self.appBaseUrlServices}/GetDateTime`, success: function (dateTime) { $.proxy(i_cb, self)(dateTime); }, error: function (e) { console.log('error ajax ' + e); }, dataType: 'json' }); } /** Check if service id exists in local storage, if not get one from server @method _initServices **/ _initServices(i_dateTime) { // STORAGE FIRST //debug; //this.simplestorage.removeItem('data'); var storage = this.simplestorage.getItem('data'); if (!_.isUndefined(storage)) { var storedDate = storage.date; if (storedDate != i_dateTime.date) { this.simplestorage.removeItem('data'); } else { this.m_remoteStatusServiceId = storage.service_id; this.m_remoteStatusVerification = storage.verification; this._pollNowServicing(); return; } } // EMAIL OR SMS var call_type = this.m_fasterqLineModel.callType; if (call_type == 'SMS' || call_type == 'EMAIL') { if (i_dateTime.date != this.m_fasterqLineModel.date) { bootbox.alert('your number expired on ' + this.m_fasterqLineModel.date + ', so we generated a new number for you...'); this._getServiceID(); return; } this._createStorage(this.m_fasterqLineModel.serviceId, this.m_fasterqLineModel.verification, this.m_fasterqLineModel.date); this._pollNowServicing(); } // QR if (call_type == 'QR') { this._getServiceID(); } } _createStorage(i_service_id, i_verification, i_date) { this.simplestorage.setItem('data', { service_id: i_service_id, date: i_date, verification: i_verification }); } /** Forget spot in line @method _getNewSpot **/ _getNewSpot() { bootbox.prompt('are you sure you want to get a new number (type yes or no)?', (i_answer) => { if (i_answer) { if (i_answer.toLowerCase() == 'yes') { this.simplestorage.removeItem('data'); window.clearInterval(this.m_statusHandler); var url = this._buildURL(); jQuery(location).attr('href', url); } } }) } /** Forget spot in line @method _releaseSpot **/ _releaseSpot() { bootbox.prompt('are you sure you want to let go of your spot (type yes or no)?', (i_answer) => { if (i_answer) { if (i_answer.toLowerCase() == 'yes') { jQuery('#appEntry').html('

    have a nice day

    '); this.simplestorage.removeItem('data'); window.clearInterval(this.m_statusHandler); } } }) } /** Create queue in table as well as matching data in analytics @method _getServiceID server:setQueue **/ _getServiceID() { var options: RequestOptionsArgs = this.createOptionArgs('/Queue', RequestMethod.Post, { line_id: this.m_fasterqLineModel.lineId, business_id: this.m_fasterqLineModel.businessId, email: this.m_fasterqLineModel.email, call_type: this.m_fasterqLineModel.callType, }) return this.http.get(options.url, options) .catch((err) => { // return Observable.throw(err); return err; }) .finally(() => { }).take(1) .subscribe((i_response: Response) => { var jData = i_response.json() var fasterqLineModel: FasterqLineModel = new FasterqLineModel(jData) this._createStorage(fasterqLineModel.serviceId, fasterqLineModel.verification, fasterqLineModel.date); this.yp.ngrxStore.dispatch({type: EFFECT_LOADED_FASTERQ_LINE, payload: fasterqLineModel}) // this._populateCustomerInfo(); this._pollNowServicing(); }, (e) => console.error(e)); } /** Get the last called service_id for line @method _pollNowServicing server:LastCalledQueue **/ _pollNowServicing() { var self = this; var lastCalledQueue = () => { $.ajax({ url: `${this.appBaseUrlServices}/LastCalledQueue`, data: { business_id: this.m_fasterqLineModel.businessId, line_id: this.m_fasterqLineModel.lineId }, success: function (i_model) { self.m_currentlyServing = i_model.service_id; }, error: function (e) { console.log('error ajax ' + e); }, dataType: 'json' }); }; this.m_statusHandler = setInterval(function () { lastCalledQueue(); }, 5000); lastCalledQueue(); } _isValidEmail(email) { var re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; return re.test(email); } /** Listen to custom selection on queue id creator via QR scan @method _createQRcode **/ @timeout(1000) _createQRcode() { jQuery('.carousel').carousel() var q: any = jQuery("#qrcode", this.el.nativeElement); q = q[0]; var qrcode = new QRCode(q, {width: 200, height: 200}); var url = this._buildURL(); qrcode.makeCode(url); } _onCall($event) { event.stopImmediatePropagation(); event.preventDefault(); const baseUrl = `${this.appBaseUrlServices}/studioweb/index.html?mode=remoteStatus¶m=`; if (this.sms.length < 6) return this.toastr.error('The phone number entered is invalid'); $.ajax({ url: `${this.appBaseUrlServices}/SendQueueSMSEmail`, data: { business_id: this.m_fasterqLineModel.businessId, line_id: this.m_fasterqLineModel.lineId, line_name: this.m_fasterqLineModel.lineName, sms: this.sms, call_type: 'SMS', url: baseUrl }, success: (e) => { this.toastr.info('Thank you') setTimeout(() => { this.sms = '' }, 4000) }, error: (e) => { console.log('error ajax ' + e); }, dataType: 'json' }); } _onSend($event) { event.stopImmediatePropagation(); event.preventDefault(); const baseUrl = `${this.appBaseUrlServices}/studioweb/index.html?mode=remoteStatus¶m=`; if (!this._isValidEmail(this.emailAddress)) return this.toastr.error('The email address entered is invalid'); $.ajax({ url: `${this.appBaseUrlServices}/SendQueueSMSEmail`, data: { business_id: this.m_fasterqLineModel.businessId, line_id: this.m_fasterqLineModel.lineId, line_name: this.m_fasterqLineModel.lineName, email: this.emailAddress, call_type: 'EMAIL', url: baseUrl }, success: (e) => { this.toastr.info('Thank you') setTimeout(() => { this.emailAddress = '' }, 4000) }, error: (e) => { console.log('error ajax ' + e); }, dataType: 'json' }); } _onPrint(event) { event.stopImmediatePropagation(); event.preventDefault(); // this.printToCart('printSectionId'); // urlRoot: BB.CONSTS.ROOT_URL + '/', // idAttribute: 'queue_id' // var url = `${this.appBaseUrlServices}/`; var options: RequestOptionsArgs = this.createOptionArgs('/Queue', RequestMethod.Post, {line_id: this.m_fasterqLineModel.lineId, business_id: this.m_fasterqLineModel.businessId}) return this.http.get(options.url, options) .catch((err) => { // return Observable.throw(err); return err; }) .finally(() => { }).take(1) .subscribe((i_response: Response) => { var jData = i_response.json() this.m_displayServiceId = jData.service_id; this.m_serviceId = jData.service_id; this.m_lineName = jData.name; this.m_printing = true; setTimeout(()=>{ printJS('printArea', 'html') },1000); setTimeout(()=>{ this.m_printing = false; },2000) return; // this._printNumber(jData.service_id, jData.name); }, (e) => console.error(e)); } /** Print current customer service id @method _printNumber @param {Number} i_service_id **/ _printNumber(i_service_id, name) { this.zone.runOutsideAngular(() => { var $printDiag = jQuery('#printDiag'); //var div = document.getElementById("printerDiv"); var p = function () { jQuery('body').append('

    ') } var arg = Lib.Base64Encode(i_service_id + ':_:' + name) // $printDiag.html(''); $printDiag.html(''); }) // $printDiag.find('h1').text('your number is ' + i_service_id); // $printDiag.find('h3').text('created on ' + moment().format('MMMM Do YYYY, h:mm:ss a')); // var divContents = jQuery(Elements.PRINT_DIAG).html(); // var printWindow = window.open('', '', 'height=250,width=450'); // printWindow.document.write('' + self.model.get('name') + ''); // printWindow.document.write('
    '); // printWindow.document.write(divContents); // printWindow.document.write('
    '); // printWindow.document.close(); // printWindow.print(); } private createOptionArgs(i_urlEndPoint, i_method, i_body): RequestOptionsArgs { var url = `${this.appBaseUrlServices}${i_urlEndPoint}`; return { url: url, method: i_method, body: i_body }; } /** Create URL string to load customer terminal UI for FasterQ queue generation @method _buildURL @return {String} URL **/ _buildURL() { var data = { line_id: this.m_fasterqLineModel.lineId, business_id: this.m_fasterqLineModel.businessId, call_type: 'QR' }; data = $.base64.encode(JSON.stringify(data)); return `${this.appBaseUrlServices}/studioweb/index.html?mode=remoteStatus¶m=${data}`; } } ================================================ FILE: src/app/fasterq/fasterq.ts ================================================ import {ChangeDetectionStrategy, Component, ViewChild} from "@angular/core"; import {Compbaser} from "ng-mslib"; import {ACTION_UISTATE_UPDATE, SideProps} from "../../store/actions/appdb.actions"; import {IUiState} from "../../store/store.data"; import {YellowPepperService} from "../../services/yellowpepper.service"; import {RedPepperService} from "../../services/redpepper.service"; import {PLACEMENT_SCENE} from "../../interfaces/Consts"; import {ISliderItemData, Slideritem} from "../../comps/sliderpanel/Slideritem"; @Component({ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'fasterq', template: ` {{me}} ` }) export class Fasterq extends Compbaser { private m_placement = PLACEMENT_SCENE; constructor(private yp: YellowPepperService, private rp: RedPepperService) { super(); var uiState: IUiState = {uiSideProps: SideProps.miniDashboard} this.yp.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})) this.cancelOnDestroy( // this.yp.listenLocationMapLoad() .subscribe((v) => { if (v){ this.sliderSceneCreator.slideTo('locationMap','right') } }, (e) => console.error(e)) ) } @ViewChild('sliderSceneCreator') sliderSceneCreator:Slideritem; _onLocationMapClosed(){ this.sliderSceneCreator.slideTo('fqEditor','left') } _onSlideChange(event: ISliderItemData) { if (event.direction == 'left' && event.to == 'fqList') { var uiState:IUiState = { uiSideProps: SideProps.miniDashboard, scene: {sceneSelected: -1} } return this.yp.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})) } // if (event.direction == 'right' && event.to == 'campaignEditor') // return this._createCampaign(); } } ================================================ FILE: src/app/fasterq/index.ts ================================================ import {NgModule} from "@angular/core"; import {CommonModule} from "@angular/common"; import {RouterModule} from "@angular/router"; import {FasterqNavigation} from "./fasterq-navigation"; import {DropdownModule as DropdownModulePrime} from "primeng/primeng"; import {SharedModule} from "../../modules/shared.module"; import {Fasterq} from "./fasterq"; import {FasterqManager} from "./fasterq-manager"; import {FasterqEditor} from "./fasterq-editor"; import {FasterqLineProps} from "./fasterq-line-props"; export const LAZY_ROUTES = [ {path: ':folder', component: FasterqNavigation}, {path: ':folder/:id', component: FasterqNavigation}, {path: '**', component: FasterqNavigation} ]; @NgModule({ imports: [DropdownModulePrime, SharedModule, CommonModule, RouterModule.forChild(LAZY_ROUTES)], declarations: [FasterqNavigation, Fasterq, FasterqManager, FasterqEditor, FasterqLineProps] }) export class FasterqLazyModule { } ================================================ FILE: src/app/help/help-navigation.ts ================================================ import {ChangeDetectionStrategy, Component, ViewChild} from "@angular/core"; import {Compbaser} from "ng-mslib"; import {animate, state, style, transition, trigger} from "@angular/animations"; import {ModalComponent} from "ng2-bs3-modal/ng2-bs3-modal"; import {MediaPlayer} from "../../comps/media-player/media-player"; import {YellowPepperService} from "../../services/yellowpepper.service"; import {Observable} from "rxjs/Observable"; @Component({ changeDetection: ChangeDetectionStrategy.OnPush, styles: [` button { padding: 8px; margin: 8px; width: 200px } `], host: { '[@routeAnimation]': 'true', '[style.display]': "'block'" }, animations: [ trigger('routeAnimation', [ state('*', style({opacity: 1})), transition('void => *', [ style({opacity: 0}), animate(333) ]), transition('* => void', animate(333, style({opacity: 0}))) ]) ], template: `

    Video tutorial



    Powered by Google's Angular framework
    ` }) export class HelpNavigation extends Compbaser { m_playResource m_playing = false; isBrandingDisabled: Observable; @ViewChild('modal') modal: ModalComponent; @ViewChild('mediaPlayer') media: MediaPlayer; constructor(private yp: YellowPepperService) { super(); this.isBrandingDisabled = this.yp.isBrandingDisabled() } _onClose() { this.m_playing = false; } _onPlay(i_path) { this.m_playResource = i_path; this.modal.open('lg') this.m_playing = true; } destroy() { } } ================================================ FILE: src/app/help/index.ts ================================================ import {NgModule} from "@angular/core"; import {CommonModule} from "@angular/common"; import {RouterModule} from "@angular/router"; import {HelpNavigation} from "./help-navigation"; import {DropdownModule as DropdownModulePrime} from "primeng/primeng"; import {SharedModule} from "../../modules/shared.module"; import {ContactUs} from "../../comps/contact-us/contact-us"; export const LAZY_ROUTES = [ {path: ':folder', component: HelpNavigation}, {path: ':folder/:id', component: HelpNavigation}, {path: '**', component: HelpNavigation} ]; @NgModule({ imports: [DropdownModulePrime, SharedModule, CommonModule, RouterModule.forChild(LAZY_ROUTES)], declarations: [HelpNavigation, ContactUs] }) export class HelpLazyModule { } ================================================ FILE: src/app/index.ts ================================================ export * from './app-component'; export * from './app-module'; ================================================ FILE: src/app/install/index.ts ================================================ import {NgModule} from "@angular/core"; import {CommonModule} from "@angular/common"; import {RouterModule} from "@angular/router"; import {SharedModule} from "../../modules/shared.module"; import {InstallNavigation} from "./install-navigation"; export const LAZY_ROUTES = [ {path: ':folder', component: InstallNavigation}, {path: ':folder/:id', component: InstallNavigation}, {path: '**', component: InstallNavigation} ]; @NgModule({ imports: [SharedModule, CommonModule, RouterModule.forChild(LAZY_ROUTES)], declarations: [InstallNavigation] }) export class InstallLazyModule { } ================================================ FILE: src/app/install/install-navigation.ts ================================================ import {ChangeDetectionStrategy, Component} from "@angular/core"; import {Compbaser} from "ng-mslib"; import {BlockService} from "../blocks/block-service"; import {BlockFactoryService} from "../../services/block-factory-service"; import {PLACEMENT_SCENE} from "../../interfaces/Consts"; import {animate, state, style, transition, trigger} from "@angular/animations"; @Component({ changeDetection: ChangeDetectionStrategy.OnPush, host: { '[@routeAnimation]': 'true', '[style.display]': "'block'" }, providers: [BlockService, BlockFactoryService, { provide: "BLOCK_PLACEMENT", useValue: PLACEMENT_SCENE }], animations: [ trigger('routeAnimation', [ state('*', style({opacity: 1})), transition('void => *', [ style({opacity: 0}), animate(333) ]), transition('* => void', animate(333, style({opacity: 0}))) ]) ], template: `

    Install the Signage Player

    Choose which version of the SignagePlayer you would like to get
    We recommend that you purchase our device as it is optimized for the signage software. It also has a built in watch-dog and the software comes pre-installed, so you will not need to download anything at all.

    • Step 1: purchase one of our devices:
    • Step 2: register the device with your user    and password 
    • Step 3: begin remote managing your device from the   Stations   section of this application
    • Step 1: download player from Google play store:
    • Step 2: register the device with your user    and password 
    • Step 3: begin remote managing your device from the   Stations   section of this application
    • Step 1: download the SignagePlayer EXE app:
    • Step 2: register the device with your user    and password 
    • Step 3: begin remote managing your device from the   Stations   section of this application
    • Step 1: download Adobe AIR runtime:
    • Step 2: download the SignagePlayer AIR app:
    • Step 3: register the device with your user    and password 
    • Step 4: begin remote managing your device from the   Stations   section of this application
    • Step 1: download player from the App store:
    • Step 2: register the device with your user    and password 
    • Step 3: begin remote managing your device from the   Stations   section of this application
    ` }) export class InstallNavigation extends Compbaser { _openInNewTab(url, event?:MouseEvent) { if (event){ event.stopImmediatePropagation(); event.preventDefault(); } var win = window.open(url, '_blank'); win.focus(); } } ================================================ FILE: src/app/live-preview/live-preview.ts ================================================ import {AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef} from "@angular/core"; import {Compbaser} from "ng-mslib"; import {YellowPepperService} from "../../services/yellowpepper.service"; import {ACTION_UISTATE_UPDATE} from "../../store/actions/appdb.actions"; import {IUiState} from "../../store/store.data"; import {RedPepperService} from "../../services/redpepper.service"; import {CampaignTimelinesModel} from "../../store/imsdb.interfaces_auto"; import {ISceneData} from "../blocks/block-service"; import {CampaignsModelExt} from "../../store/model/msdb-models-extended"; import {MainAppShowStateEnum} from "../app-component"; export enum PreviewModeEnum { NONE, SCENE, CAMPAIGN, TIMELINE } @Component({ selector: 'live-preview', changeDetection: ChangeDetectionStrategy.OnPush, styles: [` .btn-group { position: relative; top: -25px; left: 20px; } #iFramePreview { width: 100%; height: 100%; float: left; text-align: center; background-color: #222222; } #iFrameEmbedded { width: 1920px; height: 1080px; margin: 0 auto; } `], template: `
    `, }) export class LivePreview extends Compbaser implements AfterViewInit { constructor(private cd:ChangeDetectorRef, private yp: YellowPepperService, private rp: RedPepperService, private el: ElementRef) { super(); cd.detach(); } ngAfterViewInit() { if (!this.checkFlash()) return; this.yp.getPreviewMode() .mergeMap((i_previewMode: PreviewModeEnum) => { switch (i_previewMode) { case PreviewModeEnum.SCENE: { return this.yp.listenSceneSelected() .map((i_sceneData: ISceneData) => { return this.launchScene(i_sceneData.scene_id); }) } case PreviewModeEnum.TIMELINE: { return this.yp.listenTimelineSelected() .take(1) .map((i_campaignTimelinesModel: CampaignTimelinesModel) => { return this.launchTimeline(i_campaignTimelinesModel.getCampaignId(), i_campaignTimelinesModel.getCampaignTimelineId()); }) } case PreviewModeEnum.CAMPAIGN: { return this.yp.listenCampaignSelected() .take(1) .map((i_campaignsModelExt: CampaignsModelExt) => { return this.launchCampaign(i_campaignsModelExt.getCampaignId()); }) } } }).subscribe((v) => { console.log(v); }, (e) => console.error(e)) } _onExit() { let uiState: IUiState = {mainAppState: MainAppShowStateEnum.NORMAL, previewMode: PreviewModeEnum.NONE} this.yp.ngrxStore.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})) } checkFlash() { if (!FlashDetect.installed || !FlashDetect.versionAtLeast(13)) { bootbox.alert({ message: 'Flash is not enabled, in Chrome go to the URL: chrome://settings/content to enable' }); return false; } else { return true; } } /** Listen to live preview launch @method launch **/ launchScene(i_sceneId) { var url = this.rp.livePreviewScene(i_sceneId, 0); $('#iFrameEmbedded').attr('src', url); } /** Listen to live preview launch @method launch i_campaignTimelineNativeID **/ launchTimeline(i_campaignID, i_campaignTimelineID) { var url = this.rp.livePreviewTimeline(i_campaignID, i_campaignTimelineID, 0); $('#iFrameEmbedded').attr('src', url); } /** Listen to live view launch @method launch **/ launchCampaign(i_campaignID) { var url = this.rp.livePreviewCampaign(i_campaignID, 0); $('#iFrameEmbedded').attr('src', url); } ngOnInit() { } destroy() { } } ================================================ FILE: src/app/locale-selector/local-selector.css ================================================ ul { width: 290px; padding-left: 0px; padding-top: 50px; } tr { cursor: pointer; opacity: 1; } tr:hover { opacity: 0.8; } .f32 { cursor: pointer; } li { opacity: 0.3; } li:hover { opacity: 1; } .f32 .flag { display: inline-block; height: 32px; width: 32px; vertical-align: text-top; line-height: 32px; background: url(../../assets/flags32.png) no-repeat; } .f32 .flag2 { height: 32px; width: 32px; vertical-align: text-top; line-height: 32px; background: url(../../assets/flags32.png) no-repeat; } .f32 ._African_Union { background-position: 0 -32px; } .f32 ._Arab_League { background-position: 0 -64px; } .f32 ._ASEAN { background-position: 0 -96px; } .f32 ._CARICOM { background-position: 0 -128px; } .f32 ._CIS { background-position: 0 -160px; } .f32 ._Commonwealth { background-position: 0 -192px; } .f32 ._England { background-position: 0 -224px; } .f32 ._European_Union, .f32 .eu { background-position: 0 -256px; } .f32 ._Islamic_Conference { background-position: 0 -288px; } .f32 ._Kosovo { background-position: 0 -320px; } .f32 ._NATO { background-position: 0 -352px; } .f32 ._Northern_Cyprus { background-position: 0 -384px; } .f32 ._Northern_Ireland { background-position: 0 -416px; } .f32 ._Olimpic_Movement { background-position: 0 -448px; } .f32 ._OPEC { background-position: 0 -480px; } .f32 ._Red_Cross { background-position: 0 -512px; } .f32 ._Scotland { background-position: 0 -544px; } .f32 ._Somaliland { background-position: 0 -576px; } .f32 ._Tibet { background-position: 0 -608px; } .f32 ._United_Nations { background-position: 0 -640px; } .f32 ._Wales { background-position: 0 -672px; } .f32 .ad { background-position: 0 -704px; } .f32 .ae { background-position: 0 -736px; } .f32 .af { background-position: 0 -768px; } .f32 .ag { background-position: 0 -800px; } .f32 .ai { background-position: 0 -832px; } .f32 .al { background-position: 0 -864px; } .f32 .am { background-position: 0 -896px; } .f32 .ao { background-position: 0 -928px; } .f32 .aq { background-position: 0 -960px; } .f32 .ar { background-position: 0 -992px; } .f32 .as { background-position: 0 -1024px; } .f32 .at { background-position: 0 -1056px; } .f32 .au { background-position: 0 -1088px; } .f32 .aw { background-position: 0 -1120px; } .f32 .ax { background-position: 0 -1152px; } .f32 .az { background-position: 0 -1184px; } .f32 .ba { background-position: 0 -1216px; } .f32 .bb { background-position: 0 -1248px; } .f32 .bd { background-position: 0 -1280px; } .f32 .be { background-position: 0 -1312px; } .f32 .bf { background-position: 0 -1344px; } .f32 .bg { background-position: 0 -1376px; } .f32 .bh { background-position: 0 -1408px; } .f32 .bi { background-position: 0 -1440px; } .f32 .bj { background-position: 0 -1472px; } .f32 .bm { background-position: 0 -1504px; } .f32 .bn { background-position: 0 -1536px; } .f32 .bo { background-position: 0 -1568px; } .f32 .br { background-position: 0 -1600px; } .f32 .bs { background-position: 0 -1632px; } .f32 .bt { background-position: 0 -1664px; } .f32 .bw { background-position: 0 -1696px; } .f32 .by { background-position: 0 -1728px; } .f32 .bz { background-position: 0 -1760px; } .f32 .ca { background-position: 0 -1792px; } .f32 .cd { background-position: 0 -1824px; } .f32 .cf { background-position: 0 -1856px; } .f32 .cg { background-position: 0 -1888px; } .f32 .ch { background-position: 0 -1920px; } .f32 .ci { background-position: 0 -1952px; } .f32 .ck { background-position: 0 -1984px; } .f32 .cl { background-position: 0 -2016px; } .f32 .cm { background-position: 0 -2048px; } .f32 .cn { background-position: 0 -2080px; } .f32 .co { background-position: 0 -2112px; } .f32 .cr { background-position: 0 -2144px; } .f32 .cu { background-position: 0 -2176px; } .f32 .cv { background-position: 0 -2208px; } .f32 .cy { background-position: 0 -2240px; } .f32 .cz { background-position: 0 -2272px; } .f32 .de { background-position: 0 -2304px; } .f32 .dj { background-position: 0 -2336px; } .f32 .dk { background-position: 0 -2368px; } .f32 .dm { background-position: 0 -2400px; } .f32 .do { background-position: 0 -2432px; } .f32 .dz { background-position: 0 -2464px; } .f32 .ec { background-position: 0 -2496px; } .f32 .ee { background-position: 0 -2528px; } .f32 .eg { background-position: 0 -2560px; } .f32 .eh { background-position: 0 -2592px; } .f32 .er { background-position: 0 -2624px; } .f32 .es { background-position: 0 -2656px; } .f32 .et { background-position: 0 -2688px; } .f32 .fi { background-position: 0 -2720px; } .f32 .fj { background-position: 0 -2752px; } .f32 .fm { background-position: 0 -2784px; } .f32 .fo { background-position: 0 -2816px; } .f32 .fr { background-position: 0 -2848px; } .f32 .bl, .f32 .cp, .f32 .mf, .f32 .yt { background-position: 0 -2848px; } .f32 .ga { background-position: 0 -2880px; } .f32 .gb { background-position: 0 -2912px; } .f32 .sh { background-position: 0 -2912px; } .f32 .gd { background-position: 0 -2944px; } .f32 .ge { background-position: 0 -2976px; } .f32 .gg { background-position: 0 -3008px; } .f32 .gh { background-position: 0 -3040px; } .f32 .gi { background-position: 0 -3072px; } .f32 .gl { background-position: 0 -3104px; } .f32 .gm { background-position: 0 -3136px; } .f32 .gn { background-position: 0 -3168px; } .f32 .gp { background-position: 0 -3200px; } .f32 .gq { background-position: 0 -3232px; } .f32 .gr { background-position: 0 -3264px; } .f32 .gt { background-position: 0 -3296px; } .f32 .gu { background-position: 0 -3328px; } .f32 .gw { background-position: 0 -3360px; } .f32 .gy { background-position: 0 -3392px; } .f32 .hk { background-position: 0 -3424px; } .f32 .hn { background-position: 0 -3456px; } .f32 .hr { background-position: 0 -3488px; } .f32 .ht { background-position: 0 -3520px; } .f32 .hu { background-position: 0 -3552px; } .f32 .id { background-position: 0 -3584px; } .f32 .mc { background-position: 0 -3584px; } .f32 .ie { background-position: 0 -3616px; } .f32 .il { background-position: 0 -3648px; } .f32 .im { background-position: 0 -3680px; } .f32 .in { background-position: 0 -3712px; } .f32 .iq { background-position: 0 -3744px; } .f32 .ir { background-position: 0 -3776px; } .f32 .is { background-position: 0 -3808px; } .f32 .it { background-position: 0 -3840px; } .f32 .je { background-position: 0 -3872px; } .f32 .jm { background-position: 0 -3904px; } .f32 .jo { background-position: 0 -3936px; } .f32 .jp { background-position: 0 -3968px; } .f32 .ke { background-position: 0 -4000px; } .f32 .kg { background-position: 0 -4032px; } .f32 .kh { background-position: 0 -4064px; } .f32 .ki { background-position: 0 -4096px; } .f32 .km { background-position: 0 -4128px; } .f32 .kn { background-position: 0 -4160px; } .f32 .kp { background-position: 0 -4192px; } .f32 .kr { background-position: 0 -4224px; } .f32 .kw { background-position: 0 -4256px; } .f32 .ky { background-position: 0 -4288px; } .f32 .kz { background-position: 0 -4320px; } .f32 .la { background-position: 0 -4352px; } .f32 .lb { background-position: 0 -4384px; } .f32 .lc { background-position: 0 -4416px; } .f32 .li { background-position: 0 -4448px; } .f32 .lk { background-position: 0 -4480px; } .f32 .lr { background-position: 0 -4512px; } .f32 .ls { background-position: 0 -4544px; } .f32 .lt { background-position: 0 -4576px; } .f32 .lu { background-position: 0 -4608px; } .f32 .lv { background-position: 0 -4640px; } .f32 .ly { background-position: 0 -4672px; } .f32 .ma { background-position: 0 -4704px; } .f32 .md { background-position: 0 -4736px; } .f32 .me { background-position: 0 -4768px; } .f32 .mg { background-position: 0 -4800px; } .f32 .mh { background-position: 0 -4832px; } .f32 .mk { background-position: 0 -4864px; } .f32 .ml { background-position: 0 -4896px; } .f32 .mm { background-position: 0 -4928px; } .f32 .mn { background-position: 0 -4960px; } .f32 .mo { background-position: 0 -4992px; } .f32 .mq { background-position: 0 -5024px; } .f32 .mr { background-position: 0 -5056px; } .f32 .ms { background-position: 0 -5088px; } .f32 .mt { background-position: 0 -5120px; } .f32 .mu { background-position: 0 -5152px; } .f32 .mv { background-position: 0 -5184px; } .f32 .mw { background-position: 0 -5216px; } .f32 .mx { background-position: 0 -5248px; } .f32 .my { background-position: 0 -5280px; } .f32 .mz { background-position: 0 -5312px; } .f32 .na { background-position: 0 -5344px; } .f32 .nc { background-position: 0 -5376px; } .f32 .ne { background-position: 0 -5408px; } .f32 .ng { background-position: 0 -5440px; } .f32 .ni { background-position: 0 -5472px; } .f32 .nl { background-position: 0 -5504px; } .f32 .bq { background-position: 0 -5504px; } .f32 .no { background-position: 0 -5536px; } .f32 .bv, .f32 .nq, .f32 .sj { background-position: 0 -5536px; } .f32 .np { background-position: 0 -5568px; } .f32 .nr { background-position: 0 -5600px; } .f32 .nz { background-position: 0 -5632px; } .f32 .om { background-position: 0 -5664px; } .f32 .pa { background-position: 0 -5696px; } .f32 .pe { background-position: 0 -5728px; } .f32 .pf { background-position: 0 -5760px; } .f32 .pg { background-position: 0 -5792px; } .f32 .ph { background-position: 0 -5824px; } .f32 .pk { background-position: 0 -5856px; } .f32 .pl { background-position: 0 -5888px; } .f32 .pr { background-position: 0 -5920px; } .f32 .ps { background-position: 0 -5952px; } .f32 .pt { background-position: 0 -5984px; } .f32 .pw { background-position: 0 -6016px; } .f32 .py { background-position: 0 -6048px; } .f32 .qa { background-position: 0 -6080px; } .f32 .re { background-position: 0 -6112px; } .f32 .ro { background-position: 0 -6144px; } .f32 .rs { background-position: 0 -6176px; } .f32 .ru { background-position: 0 -6208px; } .f32 .rw { background-position: 0 -6240px; } .f32 .sa { background-position: 0 -6272px; } .f32 .sb { background-position: 0 -6304px; } .f32 .sc { background-position: 0 -6336px; } .f32 .sd { background-position: 0 -6368px; } .f32 .se { background-position: 0 -6400px; } .f32 .sg { background-position: 0 -6432px; } .f32 .si { background-position: 0 -6464px; } .f32 .sk { background-position: 0 -6496px; } .f32 .sl { background-position: 0 -6528px; } .f32 .sm { background-position: 0 -6560px; } .f32 .sn { background-position: 0 -6592px; } .f32 .so { background-position: 0 -6624px; } .f32 .sr { background-position: 0 -6656px; } .f32 .st { background-position: 0 -6688px; } .f32 .sv { background-position: 0 -6720px; } .f32 .sy { background-position: 0 -6752px; } .f32 .sz { background-position: 0 -6784px; } .f32 .tc { background-position: 0 -6816px; } .f32 .td { background-position: 0 -6848px; } .f32 .tg { background-position: 0 -6880px; } .f32 .th { background-position: 0 -6912px; } .f32 .tj { background-position: 0 -6944px; } .f32 .tl { background-position: 0 -6976px; } .f32 .tm { background-position: 0 -7008px; } .f32 .tn { background-position: 0 -7040px; } .f32 .to { background-position: 0 -7072px; } .f32 .tr { background-position: 0 -7104px; } .f32 .tt { background-position: 0 -7136px; } .f32 .tv { background-position: 0 -7168px; } .f32 .tw { background-position: 0 -7200px; } .f32 .tz { background-position: 0 -7232px; } .f32 .ua { background-position: 0 -7264px; } .f32 .ug { background-position: 0 -7296px; } .f32 .us { background-position: 0 -7328px; } .f32 .uy { background-position: 0 -7360px; } .f32 .uz { background-position: 0 -7392px; } .f32 .va { background-position: 0 -7424px; } .f32 .vc { background-position: 0 -7456px; } .f32 .ve { background-position: 0 -7488px; } .f32 .vg { background-position: 0 -7520px; } .f32 .vi { background-position: 0 -7552px; } .f32 .vn { background-position: 0 -7584px; } .f32 .vu { background-position: 0 -7616px; } .f32 .ws { background-position: 0 -7648px; } .f32 .ye { background-position: 0 -7680px; } .f32 .za { background-position: 0 -7712px; } .f32 .zm { background-position: 0 -7744px; } .f32 .zw { background-position: 0 -7776px; } .f32 .sx { background-position: 0 -7808px; } .f32 .cw { background-position: 0 -7840px; } .f32 .ss { background-position: 0 -7872px; } .f32 .nu { background-position: 0 -7904px; } ================================================ FILE: src/app/locale-selector/local-selector.ts ================================================ import {ChangeDetectionStrategy, EventEmitter, Component, Input, Output} from "@angular/core"; import {Compbaser} from "ng-mslib"; import {YellowPepperService} from "../../services/yellowpepper.service"; @Component({ selector: 'locale-selector', styleUrls: ['./local-selector.css'], changeDetection: ChangeDetectionStrategy.OnPush, template: ` {{me}}

    Selected language: {{m_selectedLocale?.name}}

    {{locale.name}}
    ` }) export class LocaleSelector extends Compbaser { m_selectedLocale; m_orientation; /** locale info: project: https://github.com/lafeber/world-flags-sprite flags codes: https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2 google codes: https://cloud.google.com/translate/docs/languages docs: https://angular.io/docs/ts/latest/cookbook/i18n.html **/ locales = [ {flag: 'us', locale: 'en', name: 'English'}, {flag: 'cn', locale: 'zh-CN', name: 'Chinese'}, {flag: 'in', locale: 'bn', name: 'Bengali'}, {flag: 'es', locale: 'es', name: 'Spanish'}, {flag: 'in', locale: 'hi', name: 'Hindi'}, {flag: 'jo', locale: 'ar', name: 'Arabic'}, {flag: 'il', locale: 'iw', name: 'Hebrew'}, {flag: 'br', locale: 'pt', name: 'Portuguese'}, {flag: 'de', locale: 'de', name: 'German'}, {flag: 'jp', locale: 'ja', name: 'Japanese'}, {flag: 'ru', locale: 'ru', name: 'Russian'}, {flag: 'ph', locale: 'tl', name: 'Filipino'}, {flag: 'fr', locale: 'fr', name: 'French'}, {flag: 'gr', locale: 'el', name: 'Greek'}, {flag: 'kr', locale: 'ko', name: 'Korean'}, {flag: 'th', locale: 'th', name: 'Thai'}, {flag: 'my', locale: 'ms', name: 'Malay'}, {flag: 'it', locale: 'it', name: 'Italian'} ] constructor(private yp: YellowPepperService) { super(); } @Input() set orientation(i_orientation: 'inline' | 'modal') { this.m_orientation = i_orientation; } @Output() onLocaleChanged: EventEmitter = new EventEmitter(); _saveAndReload() { this.onLocaleChanged.emit(this.m_selectedLocale); } _onSelected(i_locale) { this.m_selectedLocale = i_locale; } public redirect(i_locale) { window.onbeforeunload = () => { }; window.location.replace(`https://secure.digitalsignage.com/studioweb/locale/${i_locale.locale}/`); } destroy() { } } ================================================ FILE: src/app/location/location-map.html ================================================ {{me}}

    simulation mode
    {{m_simStatus}}
    Latitude: {{m_simulatedLat}}
    longitude: {{m_simulatedLng}}
    {{m_simUrl}}
    ================================================ FILE: src/app/location/location-map.ts ================================================ import {AfterViewInit, ChangeDetectorRef, Component, EventEmitter, Inject, NgZone, Output, ViewChild} from "@angular/core"; import {Compbaser} from "ng-mslib"; import {YellowPepperService} from "../../services/yellowpepper.service"; import {SebmGoogleMap} from "angular2-google-maps/esm/core"; import {IUiState} from "../../store/store.data"; import {ACTION_UISTATE_UPDATE} from "../../store/actions/appdb.actions"; import {MapsAPILoader} from "angular2-google-maps/core"; import {BlockService, IBlockData, ISceneData} from "../blocks/block-service"; import {RedPepperService} from "../../services/redpepper.service"; import {PLACEMENT_CHANNEL, PLACEMENT_SCENE} from "../../interfaces/Consts"; import {CampaignTimelineChanelPlayersModel} from "../../store/imsdb.interfaces_auto"; import {LocationMarkModel} from "../../models/LocationMarkModel"; import {FormBuilder, FormGroup} from "@angular/forms"; import * as _ from 'lodash'; export declare var google: any; /** * examples: * http://embed.plnkr.co/YX7W20/ * http://plnkr.co/edit/NtfCPol50mlwGoiB8UZu?p=preview * * **/ @Component({ selector: 'location-map', styles: [` .sebm-google-map-container { height: 700px; width: 1700px; } .green { background-color: green; color: white; } .locationSimulationProps { width: 420px; height: 160px; border: 1px dotted gray; padding: 6px; } #simModeContainer { padding: 0 20px 20px 0; position: relative; top: -20px; } #addressLookup { z-index: 1; position: relative; top: 50px; width: 300px; } #refresh { position: relative; top: -1px; height: 31px; } `], templateUrl: './location-map.html' }) export class LocationMap extends Compbaser implements AfterViewInit { zoom: any = 8; lat: any = 51.673858; lng: any = 7.815982; markers: LocationMarkModel[] = []; m_stations = []; inSimMode = false; m_inRange = false; m_simStatus = 'status: waiting...' m_simulatedLat = 0; m_simulatedLng = 0; m_simUrl = ''; m_blockData: IBlockData; contGroup: FormGroup; constructor(private yp: YellowPepperService, private cd: ChangeDetectorRef, private m_mapsAPILoader: MapsAPILoader, private fb: FormBuilder, private zone: NgZone, private bs: BlockService, @Inject('BLOCK_PLACEMENT') private blockPlacement: string, private rp: RedPepperService) { super(); this.contGroup = fb.group({ 'stations': [], 'postMode': [] }); } @ViewChild('address') address; @ViewChild('googleMaps') googleMaps: SebmGoogleMap; @Output() onClose: EventEmitter = new EventEmitter(); ngAfterViewInit() { // Google Place Autocomplete var autocomplete: any; // var inputAddress = document.getElementById("address"); this.m_mapsAPILoader.load().then(() => { console.log('google script loaded'); autocomplete = new google.maps.places.Autocomplete(this.address.nativeElement, {}); google.maps.event.addListener(autocomplete, 'place_changed', () => { this.zone.run(() => { /* "Zone.run" allows the map to be immediately updated. Without it, you would have to click the map to observe the new lat and lng */ var place = autocomplete.getPlace(); if (!place || !place.geometry) return; var lat = place.geometry.location.lat(); var lng = place.geometry.location.lng(); this.setCenter(lat, lng) }); }); // var geocoder = new google.maps.Geocoder(); }); this.cancelOnDestroy( // this.yp.listenLocationMarkerSelected() .subscribe((i_marker: LocationMarkModel) => { this.setCenter(i_marker.lat, i_marker.lng) }, (e) => console.error(e)) ) if (this.blockPlacement == PLACEMENT_CHANNEL) this._listenOnChannels(); if (this.blockPlacement == PLACEMENT_SCENE) this._listenOnScenes(); } _toggleSimMode() { this.inSimMode = !this.inSimMode; if (!this.inSimMode) { this.m_simulatedLat = 0; this.m_simulatedLng = 0; this.m_inRange = false; } else { this._loadStationList(); this.contGroup.controls.postMode.setValue('local'); } this.cd.markForCheck(); } /** Load and refresh the station list so we can pull station id for simulation @method _loadStationList **/ _loadStationList() { var self = this; this.m_stations = []; this.contGroup.controls.stations.setValue(''); var userData = this.rp.getUserData(); var url = window.g_protocol + userData.domain + '/WebService/getStatus.ashx?user=' + userData.userName + '&password=' + userData.userPass + '&callback=?'; $.getJSON(url, (data) => { var s64 = data['ret']; var str = jQuery.base64.decode(s64); var xml = jXML.parseXML(str); $(xml).find('Station').each((key, value) => { var stationId = $(value).attr('id'); var stationName = $(value).attr('name'); var stationPort = $(value).attr('localPort') || 9999; var stationIp = $(value).attr('localAddress'); self.m_stations.push({stationIp, stationId, stationName, stationPort}); }); self.cd.markForCheck(); }); } _onStationSelected(event) { console.log(this.contGroup.value); } /** Simulate a trigger event of GPS coordinates by user clicks within the google map @method _simulateEvent @param {Number} lat @param {Number} lng @param {Boolean} inRange true if clicked within a marked circle radius **/ private _simulateEvent(i_marker: LocationMarkModel, i_inRange) { this.m_inRange = i_inRange; this.m_simulatedLat = i_marker.lat; this.m_simulatedLng = i_marker.lng; var selected = this.contGroup.value.stations; if (_.isNull(selected) || _.isEmpty(selected)) return bootbox.alert('no station selected...'); var postMode = this.contGroup.value.postMode; var msg = (postMode == 'local') ? 'click link below to send post...' : 'sending post...'; var id = this.contGroup.value.stations.stationId; var ip = this.contGroup.value.stations.stationIp; var stationRecord = this.rp.getStationRecord(id); var port = stationRecord.lan_server_port; var url = this.rp.sendLocalEventGPS(postMode, this.m_simulatedLat, this.m_simulatedLng, id, ip, port, function (e) { console.log(e); }); this.m_simStatus = msg; this.m_simUrl = url; } _openSimUrl() { if (this.contGroup.value.postMode != 'local') return; var id = this.contGroup.value.stations.stationId; var ip = this.contGroup.value.stations.stationIp; var stationRecord = this.rp.getStationRecord(id); var port = stationRecord.lan_server_port; var url = this.rp.sendLocalEventGPS('local', this.m_simulatedLat, this.m_simulatedLng, id, ip, port, function (e) { console.log(e); }); window.open(url, '_blank'); } private _listenOnChannels() { this.cancelOnDestroy( // this.yp.listenBlockChannelSelectedOrChanged() .mergeMap((i_campaignTimelineChanelPlayersModel: CampaignTimelineChanelPlayersModel) => { return this.bs.getBlockData(i_campaignTimelineChanelPlayersModel.getCampaignTimelineChanelPlayerId()) }) .subscribe((blockData: IBlockData) => { this.m_blockData = blockData; this.render(); }, (e) => console.error(e)) ) } private _listenOnScenes() { this.cancelOnDestroy( // this.yp.listenSceneOrBlockSelectedChanged() .mergeMap((i_sceneData: ISceneData) => { return this.bs.getBlockDataInScene(i_sceneData) }) .subscribe((blockData: IBlockData) => { this.m_blockData = blockData; this.render(); }, (e) => console.error(e)) ) } render() { this.markers = []; var domPlayerData = this.bs.getBlockPlayerData(this.m_blockData) $(domPlayerData).find('GPS').children().each((k, v) => { var marker: LocationMarkModel = new LocationMarkModel({ draggable: false, label: '', lat: parseFloat(jXML(v).attr('lat')), lng: parseFloat(jXML(v).attr('lng')), radius: parseFloat(jXML(v).attr('radios')) * 1000, // convert to meters }); this.markers.push(marker); //map.points.push(point); }); if (this.markers.length > 0) this.setCenter(this.markers[0].lat, this.markers[0].lng); this.forceUpdateUi() } clickedMarker(i_marker: LocationMarkModel, index: number) { if (this.inSimMode) return this._simulateEvent(i_marker, true); // con(`clicked the marker: ${i_marker.label || index}`) var uiState: IUiState = {locationMap: {locationMarkerSelected: i_marker}} this.yp.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})) } mapClicked($event: MouseEvent) { let marker: LocationMarkModel = new LocationMarkModel({ id: Math.random(), radius: 10000, lat: $event['coords'].lat, lng: $event['coords'].lng, draggable: true, new: true }); if (this.inSimMode) return this._simulateEvent(marker, false); // enable code below if you wish to add new marker and not through reactive // this.markers.push(marker); // this.forceUpdateUi(); var uiState: IUiState = {locationMap: {locationMarkerSelected: marker}} this.yp.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})) } markerDragEnd(m: LocationMarkModel, $event: MouseEvent) { console.log('dragEnd', m, $event); } _close() { var uiState: IUiState = { locationMap: { loadLocationMap: false } } this.yp.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})) this.onClose.emit() } public forceUpdateUi() { this.cd.detach(); setTimeout(() => { this.cd.reattach(); this.googleMaps.triggerResize(); this.cd.markForCheck(); }, 300) } public setCenter(lat, lng) { // for private access to all APIs do: this.googleMaps['_mapsWrapper'].setCenter({ lat: lat, lng: lng, }); this.forceUpdateUi(); } destroy() { } } /** Create the google map and listen to corresponding events such map clicks (not within a circle or marker) as well as the Search box find input etc @method _createMap **/ // _createMap() { // var self = this; // google.maps.LatLng.prototype.destinationPoint = function (brng, dist) { // dist = dist / 6371; // brng = brng.toRad(); // // var lat1 = this.lat().toRad(), lon1 = this.lng().toRad(); // // var lat2:any = Math.asin(Math.sin(lat1) * Math.cos(dist) + // Math.cos(lat1) * Math.sin(dist) * Math.cos(brng)); // // var lon2 = lon1 + Math.atan2(Math.sin(brng) * Math.sin(dist) * // Math.cos(lat1), // Math.cos(dist) - Math.sin(lat1) * // Math.sin(lat2)); // // if (isNaN(lat2) || isNaN(lon2)) return null; // // return new google.maps.LatLng(lat2.toDeg(), lon2.toDeg()); // }; // // var pointA = new google.maps.LatLng(34.155260, -118.787163); // Circle center // var radius = 1; // 10km // // var mapOpt = { // mapTypeId: google.maps.MapTypeId.TERRAIN, // center: pointA, // zoom: 10 // }; // var map = $('.map', self.el.nativeElement); // // self.m_map = new google.maps.Map(map[0], mapOpt); // console.log(this.googleMaps); /* // Create the search box and link it to the UI element. //var input = $('#pac-input', self.el)[0]; var c = $('.inputPlacement', self.el); $(c).append(''); var input = $(c).find('input')[0]; var searchBox = new google.maps.places.SearchBox(input); self.m_map.controls[google.maps.ControlPosition.TOP_LEFT].push(input); // Bias the SearchBox results towards current map's viewport. self.m_map.addListener('bounds_changed', function () { searchBox.setBounds(self.m_map.getBounds()); }); var markers = []; // Listen for the event fired when the user selects a prediction and retrieve details for location searchBox.addListener('places_changed', function () { var places = searchBox.getPlaces(); if (places.length == 0) { return; } // Clear out the old markers. markers.forEach(function (marker) { marker.setMap(null); }); markers = []; // For each place, get the icon, name and location. var bounds = new google.maps.LatLngBounds(); places.forEach(function (place) { var icon = { url: place.icon, size: new google.maps.Size(71, 71), origin: new google.maps.Point(0, 0), anchor: new google.maps.Point(17, 34), scaledSize: new google.maps.Size(25, 25) }; // Create a marker for each place. markers.push(new google.maps.Marker({ map: self.m_map, icon: icon, title: place.name, position: place.geometry.location })); if (place.geometry.viewport) { // Only geocodes have viewport. bounds.union(place.geometry.viewport); } else { bounds.extend(place.geometry.location); } }); self.m_map.fitBounds(bounds); }); google.maps.event.addListener(self.m_map, 'click', function (event) { var lat = event.latLng.lat(); var lng = event.latLng.lng(); if (self._getSimulationMode()) { console.log('out of range ' + lat + ' ' + lng); self._simulateEvent(lat, lng, false); return; } if (self.m_markerOnClick) { self.addPoint(event.latLng, 0.10); self.m_markerOnClick = false; BB.comBroker.fire(BB.EVENTS.ADD_LOCATION_POINT, self, null, {lat: lat, lng: lng}); } }); */ //} // export interface IMarker { // lat: number; // lng: number; // radius: number; // draggable: boolean; // new:boolean; // } ================================================ FILE: src/app/resources/index.ts ================================================ import {NgModule} from "@angular/core"; import {CommonModule} from "@angular/common"; import {RouterModule} from "@angular/router"; import {ResourcesNavigation} from "./resources-navigation"; import {DropdownModule as DropdownModulePrime} from "primeng/primeng"; import {SharedModule} from "../../modules/shared.module"; import {Orders} from "./orders"; import {ResourcePropsManager} from "./resource-props-manager"; import {Resources} from "./resources"; import {ResourcesList} from "./resources-list"; export const LAZY_ROUTES = [ {path: ':folder', component: ResourcesNavigation}, {path: ':folder/:id', component: ResourcesNavigation}, {path: '**', component: ResourcesNavigation} ]; @NgModule({ imports: [DropdownModulePrime, SharedModule, CommonModule, RouterModule.forChild(LAZY_ROUTES)], declarations: [ResourcesNavigation, Orders, ResourcePropsManager, Resources, ResourcesList] }) export class ResourcesLazyModule { } ================================================ FILE: src/app/resources/orders.ts ================================================ import {Component, ChangeDetectionStrategy} from "@angular/core"; @Component({ selector: 'orders', styles: [` .page { padding-left: 100px; padding-top: 40px; } `], template: `

    Order 1

    Order 1

    Order 1

    Order 1

    Order 1

    Order 1

    Order 1

    Order 1

    Order 1

    Order 1

    Order 1

    Order 1

    Order 1

    Order 1

    Order 1

    Order 1

    Order 1

    Order 1

    Order 1

    Order 1

    Order 1

    Order 1

    Order 1

    Order 1

    Order 1

    Order 2

    Order 3

    `, changeDetection: ChangeDetectionStrategy.OnPush }) export class Orders { } ================================================ FILE: src/app/resources/resource-props-manager.ts ================================================ import {Component} from "@angular/core"; import {Compbaser} from "ng-mslib"; import {Observable} from "rxjs"; import {SideProps} from "../../store/actions/appdb.actions"; import {YellowPepperService} from "../../services/yellowpepper.service"; import {ResourcesModel} from "../../store/imsdb.interfaces_auto"; import {Lib} from "../../Lib"; import {RedPepperService} from "../../services/redpepper.service"; @Component({ selector: 'resource-props-manager', styles: [` ul { padding: 0 } `], template: ` {{me}}
      resource property

      Flash swf content

    `, }) export class ResourcePropsManager extends Compbaser { m_resourceType; m_sideProps$: Observable; m_sidePropsEnum = SideProps; m_uiUserFocusItem$: Observable; m_formatIcon = ''; m_resourceName = ''; m_selectedResource: ResourcesModel; m_playResource = ''; m_svgPath = ''; m_imagePath = ''; constructor(private yp: YellowPepperService, private rp: RedPepperService) { super(); this.m_uiUserFocusItem$ = this.yp.ngrxStore.select(store => store.appDb.uiState.uiSideProps); this.m_sideProps$ = this.yp.ngrxStore.select(store => store.appDb.uiState.uiSideProps); this.cancelOnDestroy( // this.yp.listenResourceSelected() .subscribe((i_resource: ResourcesModel) => { this.m_selectedResource = i_resource; this.m_resourceName = this.m_selectedResource.getResourceName(); switch (this.m_selectedResource.getResourceType()) { case 'svg':{ this.m_formatIcon = 'spinner'; this.m_resourceType = 'svg'; path = window['g_protocol'] + this.rp.getUserData().domain + '/Resources/business' + this.rp.getUserData().businessID + '/resources/' + this.rp.getResourceNativeID(this.m_selectedResource.getResourceId()) + '.' + 'svg'; path = $.base64.encode(path); // var path = window['g_protocol'] + 's3.signage.me/business' + this.rp.getUserData().businessID + '/resources/' + this.rp.getResourceNativeID(this.m_selectedResource.getResourceId()) + '.' + this.m_selectedResource.getResourceType(); // var urlPath = $.base64.encode(path); // this.m_svgPath = 'https://secure.digitalsignage.com/proxyRequest/' + path; // console.log(this.m_svgPath); this.m_svgPath = 'https://secure.digitalsignage.com/proxyRequest/' + path; // this.m_svgPath = 'https://secure.digitalsignage.com/proxyRequest/aHR0cHM6Ly9wbHV0by5zaWduYWdlLm1lL1Jlc291cmNlcy9idXNpbmVzczM1ODYxMy9yZXNvdXJjZXMvMzQuc3Zn'; break; } case 'jpg': case 'png': { this.m_formatIcon = 'image'; this.m_resourceType = 'image'; var path = 'http://' + 's3.signage.me/business' + this.rp.getUserData().businessID + '/resources/' + this.rp.getResourceNativeID(this.m_selectedResource.getResourceId()) + '.' + this.m_selectedResource.getResourceType(); this.m_imagePath = path; break; } case 'm4v': case 'mp4': case 'flv': { this.m_formatIcon = 'video-camera'; this.m_resourceType = 'video'; var path = 'http://' + 's3.signage.me/business' + this.rp.getUserData().businessID + '/resources/' + this.rp.getResourceNativeID(this.m_selectedResource.getResourceId()) + '.' + this.m_selectedResource.getResourceType(); this.m_playResource = path; // path = window['g_protocol'] + BB.Pepper.getUserData().domain + '/Resources/business' + BB.Pepper.getUserData().businessID + '/resources/' + BB.Pepper.getResourceNativeID(i_recResource['resource_id']) + '.' + ext; console.log(path); break; } case 'swf': { this.m_formatIcon = 'bolt'; this.m_resourceType = 'swf'; break; } } }, (e) => console.error(e)) ) } _onUpdateResourceName(event) { var text = Lib.CleanProbCharacters(this.m_resourceName, 1); this.rp.setResourceRecord(this.m_selectedResource.getResourceId(), 'resource_name', text); this.rp.reduxCommit(); // var elem = self.$el.find('[data-resource_id="' + this.m_selectedResource.getResourceId() + '"]'); // elem.find('span').text(text); } ngOnInit() { } destroy() { } } ================================================ FILE: src/app/resources/resources-list.ts ================================================ import {ChangeDetectionStrategy, Component, ElementRef, EventEmitter, Input, Output} from "@angular/core"; import {Compbaser} from "ng-mslib"; import {ResourcesModel} from "../../store/imsdb.interfaces_auto"; import {List} from "immutable"; import {BlockService} from "../blocks/block-service"; import {IUiState} from "../../store/store.data"; import {SideProps} from "../../store/actions/appdb.actions"; @Component({ selector: 'resources-list', changeDetection: ChangeDetectionStrategy.OnPush, template: ` {{me}} `, }) export class ResourcesList extends Compbaser { selectedIdx = -1; m_resources: List; m_selected; constructor(public bs: BlockService, private el: ElementRef) { super(); } @Input() set resources(i_resources: List) { this.m_resources = i_resources; } @Input() filter; @Input() set setViewMode(i_viewMode: 'grid' | 'list') { if (i_viewMode == 'list') { var query = $('.resourcesListItems', this.el.nativeElement); $(query).addClass('col-xs-12'); $(query).removeClass('col-xs-3'); } if (i_viewMode == 'grid') { var query = $('.resourcesListItems', this.el.nativeElement); $(query).addClass('col-xs-3'); $(query).removeClass('col-xs-12'); } } @Output() onSelected: EventEmitter = new EventEmitter(); _onSelected(event: MouseEvent, i_resource: ResourcesModel, index) { this.selectedIdx = index; this.onSelected.emit(i_resource) this.m_selected = i_resource; } resetSelection() { this.selectedIdx = -1; } ngOnInit() { } destroy() { } } ================================================ FILE: src/app/resources/resources-navigation.ts ================================================ import {ChangeDetectionStrategy, Component} from "@angular/core"; import {animate, state, style, transition, trigger} from "@angular/animations"; import {Compbaser} from "ng-mslib"; import {BlockService} from "../blocks/block-service"; @Component({ changeDetection: ChangeDetectionStrategy.OnPush, host: { '[@routeAnimation]': 'true', '[style.display]': "'block'" }, providers: [BlockService, { provide: "BLOCK_PLACEMENT", useValue: '' } ], animations: [ trigger('routeAnimation', [ state('*', style({opacity: 1})), transition('void => *', [ style({opacity: 0}), animate(333) ]), transition('* => void', animate(333, style({opacity: 0}))) ]) ], template: ` resource-navigation `, }) export class ResourcesNavigation extends Compbaser { constructor() { super(); } destroy() { } } ================================================ FILE: src/app/resources/resources.ts ================================================ import {ChangeDetectionStrategy, Component} from "@angular/core"; import {YellowPepperService} from "../../services/yellowpepper.service"; import {RedPepperService} from "../../services/redpepper.service"; import {ResourcesModel} from "../../store/imsdb.interfaces_auto"; import {List} from "immutable"; import {Observable} from "rxjs"; import {Compbaser} from "ng-mslib"; import {IUiState} from "../../store/store.data"; import {ACTION_LIVELOG_UPDATE, ACTION_UISTATE_UPDATE, SideProps} from "../../store/actions/appdb.actions"; import {BlockService} from "../blocks/block-service"; import {MainAppShowStateEnum} from "../app-component"; import {LiveLogModel} from "../../models/live-log-model"; @Component({ // changeDetection: ChangeDetectionStrategy.OnPush, selector: 'resources', template: ` {{me}}
    supported files: flv, mp4, jpg, png, swf and svg
      `, styles: [` /*:host /deep/ vg-player {*/ /*background-color: transparent;*/ /*margin: 30px;*/ /*width: 30%;*/ /*height: calc(100% - 60px);*/ /*}*/ /*:host /deep/ vg-player {*/ /*background-color: transparent;*/ /*margin: 30px;*/ /*width: 30%;*/ /*height: calc(100% - 60px);*/ /*}*/ * { border-radius: 0 !important; } vg-player { background-color: transparent; margin: 30px; width: 30%; height: calc(100% - 60px); } vg-controls { padding: 30px; transition: all 1s; } #resourcesPanel { padding: 10px; } .myFile { position: relative; overflow: hidden; float: left; clear: left; } .myFile input[type="file"] { display: block; position: absolute; top: 0; right: 0; opacity: 0; font-size: 100px; filter: alpha(opacity=0); cursor: pointer; } `] }) export class Resources extends Compbaser { m_filter; m_resourceModel: ResourcesModel; m_viewMode = 'list'; m_resourceModels$: Observable>; constructor(private yp: YellowPepperService, private rp: RedPepperService, private bs: BlockService) { super(); this.m_resourceModels$ = this.yp.listenResources(); this.cancelOnDestroy( // this.yp.listenResourceSelected() .subscribe((i_resources: ResourcesModel) => { this.m_resourceModel = i_resources; }, (e) => console.error(e)) ) } _onRemove() { bootbox.confirm(`are you sure you want to remove ${this.m_resourceModel.getResourceName()}`, (i_result) => { if (!i_result) return; this.rp.removeResource(this.m_resourceModel.getResourceId()); this.rp.removeBlocksWithResourceID(this.m_resourceModel.getResourceId()); this.rp.removeResourceFromBlockCollectionInScenes(this.m_resourceModel.getResourceId()); this.rp.removeResourceFromBlockLocationInScenes(this.m_resourceModel.getResourceId()); this.rp.removeResourceFromBlockCollectionsInChannel(this.m_resourceModel.getResourceId()); this.rp.removeResourceFromBlockLocationInChannel(this.m_resourceModel.getResourceId()); this.rp.removeAllScenePlayersWithResource(this.m_resourceModel.getResourceId()); this.rp.reduxCommit(); let uiState: IUiState = { uiSideProps: SideProps.miniDashboard, resources: {resourceSelected: -1} } this.yp.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})) }); } _onFileUpload(event) { var status: any = this.rp.uploadResources('file', this.bs); if (status.length == 0) { bootbox.alert('The file format is not supported'); return -1; } let uiState: IUiState = {mainAppState: MainAppShowStateEnum.SAVE} this.yp.ngrxStore.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})) this.yp.dispatch(({type: ACTION_LIVELOG_UPDATE, payload: new LiveLogModel({event: 'uploaded resource'})})); } _onSelected(i_resource: ResourcesModel) { let uiState: IUiState = { uiSideProps: SideProps.resourceProps, resources: {resourceSelected: i_resource.getResourceId()} } this.yp.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})) } } ================================================ FILE: src/app/route-animation.ts ================================================ // import {trigger, state, animate, style, transition} from '@angular/core'; // // export function routerTransition() { // return animateRoute(); // } // // function animateRoute() { // return trigger('routerTransition', [ // state('*', style({opacity: 1})), // transition('void => *', [ // style({opacity: 0}), // animate(333) // ]), // transition('* => void', animate(333, style({opacity: 0}))) // ]); // } ================================================ FILE: src/app/scenes/index.ts ================================================ import {NgModule} from "@angular/core"; import {CommonModule} from "@angular/common"; import {RouterModule} from "@angular/router"; import {ScenesNavigation} from "./scenes-navigation"; import {DropdownModule as DropdownModulePrime} from "primeng/primeng"; import {SharedModule} from "../../modules/shared.module"; import {ScenePropsManager} from "./scene-props-manager"; import {SceneList} from "./scene-list"; import {SceneManager} from "./scene-manager"; import {Scenes} from "./scenes"; import {SceneEditor} from "./scene-editor"; import {SceneToolbar} from "./scene-toolbar"; import {BlockPropPosition} from "../blocks/block-prop-position"; import {Ng2Bs3ModalModule} from "ng2-bs3-modal/ng2-bs3-modal"; import {SceneCreator} from "./scene-creator"; export const LAZY_ROUTES = [ {path: ':folder', component: ScenesNavigation}, {path: ':folder/:id', component: ScenesNavigation}, {path: '**', component: ScenesNavigation} ]; @NgModule({ imports: [DropdownModulePrime, SharedModule, CommonModule, Ng2Bs3ModalModule, RouterModule.forChild(LAZY_ROUTES)], declarations: [Scenes, SceneEditor, SceneCreator, SceneToolbar, ScenesNavigation, ScenePropsManager, SceneManager, SceneList, BlockPropPosition] }) export class ScenesLazyModule { } ================================================ FILE: src/app/scenes/scene-creator.css ================================================ .profileCard { margin-bottom: 20px; } .profileCard { border-radius: 2px; box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.16), 0 2px 10px 0 rgba(0, 0, 0, 0.12); padding: 15px; margin-top: 60px; float: left; height: 333px; background-color: #FFF; cursor: pointer; position: relative; top:0; left: 0; /*transition-property: top, left, box-shadow;*/ transition-property: top; transition-duration: 0.3s; } .sceneImportThumb { width: 139px; height: 78px; border: solid 2px gray; cursor: pointer; transition: all .2s ease-in-out; } .sceneImportThumb:hover { transform: scale(1.1); } .profileCard:hover { top: -7px; /*left: 4px;*/ /*z-index: 200;*/ /*-webkit-box-shadow: 18px 24px 16px -3px rgba(0,0,0,0.25);*/ /*-moz-box-shadow: 18px 24px 16px -3px rgba(0,0,0,0.25);*/ /*box-shadow: 18px 24px 16px -3px rgba(0,0,0,0.25);*/ } .profileCard .pImg { background-color: #40a5ec; border-radius: 50% !important; display: table; height: 100px; margin: 0 auto; width: 100px; margin-top: -60px; } .profileCard .pImg span { color: #fff; display: table-cell; text-align: center; vertical-align: middle; } .profileCard1 .pDes > p { color: #717171; font-size: 13px; padding-top: 10px; text-align: justify; } .profileCard1 .pDes > a { background-color: #03a9f4; border-radius: 2px; box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.16), 0 2px 10px 0 rgba(0, 0, 0, 0.12); color: #fff; transition: all 0.5s ease 0s; position: absolute; top: 285px; opacity: 0.3; } .profileCard1 .pDes > a { background-color: #03a9f4; border-radius: 2px; box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.16), 0 2px 10px 0 rgba(0, 0, 0, 0.12); color: #fff; transition: all 0.5s ease 0s; } .profileCard1 .pDes > a:hover { background-color: #0288d1; box-shadow: 0 2px 3px 0 rgba(0, 0, 0, 0.16), 0 2px 5px 0 rgba(0, 0, 0, 0.12); color: #fff; transition: all 0.5s ease 0s; } ================================================ FILE: src/app/scenes/scene-creator.ts ================================================ import {Component, ChangeDetectionStrategy, AfterViewInit, Output, EventEmitter, ViewChild} from "@angular/core"; import {Compbaser} from "ng-mslib"; import {YellowPepperService} from "../../services/yellowpepper.service"; import {BlockService} from "../blocks/block-service"; import * as _ from 'lodash'; import {Lib} from "../../Lib"; import {RedPepperService} from "../../services/redpepper.service"; import {ModalComponent} from "ng2-bs3-modal/ng2-bs3-modal"; import {IUiState} from "../../store/store.data"; import {ACTION_LIVELOG_UPDATE, ACTION_UISTATE_UPDATE} from "../../store/actions/appdb.actions"; import {ToastsManager} from "ng2-toastr"; import {PLACEMENT_IS_SCENE} from "../../interfaces/Consts"; import {MainAppShowStateEnum} from "../app-component"; import {LiveLogModel} from "../../models/live-log-model"; @Component({ selector: 'scene-creator', styleUrls: ['./scene-creator.css'], // changeDetection: ChangeDetectionStrategy.OnPush, template: ` {{me}}

      {{category.name}}

      {{category.description}}

      `, }) export class SceneCreator extends Compbaser implements AfterViewInit { m_selectedAccount; m_largePreview = ''; m_sceneCategories = []; m_sceneAccounts = []; m_selectTypeMode = true; m_sceneTemplates; m_selectedSceneMime: string; constructor(private yp: YellowPepperService, private bs: BlockService, private rp: RedPepperService, private toastr: ToastsManager) { super(); this._initSceneTemplates(); } @ViewChild(ModalComponent) modal: ModalComponent; @Output() onGoBack: EventEmitter = new EventEmitter(); ngAfterViewInit() { this.m_sceneCategories = [ { name: 'from empty', mimeType: 'blank', icon: 'fa-star', description: 'Create your own design, simply start with a blank scene and mix in your favorite images, videos, SVG graphics and even smart components. Get all the power to design your own custom scene.' }, { name: 'from template', mimeType: 'template', icon: 'fa-paint-brush', description: 'With hundreds of beautiful pre-made designs you are sure to find something you like. The scene templates are preloaded with images and labels so its a great way to get started' } ]; var blocks = (this.bs.getBlocks()); _.forEach(blocks, (block: any) => { if (block.mimeType) { this.m_sceneCategories.push({ name: block.description, mimeType: block.mimeType, icon: block.fontAwesome, description: block.jsonItemLongDescription }); } }); this.cancelOnDestroy( this.yp.listenMainAppState() .skip(1) .subscribe(i_status => { if (i_status == MainAppShowStateEnum.SAVED) { this.toastr.info('scene imported and is available in scene list'); this._goBack(); } }) ) } _onSceneImport() { this.rp.loaderManager.importScene(this.m_selectedAccount.businessId, this.m_selectedAccount.nativeId, (i_SceneId) => { this.rp.injectPseudoScenePlayersIDs(i_SceneId); let uiState: IUiState = {mainAppState: MainAppShowStateEnum.SAVE} this.yp.ngrxStore.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})) this.yp.dispatch(({type: ACTION_LIVELOG_UPDATE, payload: new LiveLogModel({event: 'scene imported id: ' + i_SceneId})})); }); } _onTemplateSelected(account) { this.m_selectedAccount = account; this.m_largePreview = `http://s3.signage.me/business1000/resources/scenes_snaps/${account.sceneName}.png` this.modal.open(); } private _getTemplates() { this.m_sceneAccounts = []; for (var sceneName in this.m_sceneTemplates) { var sceneConfig = this.m_sceneTemplates[sceneName]; switch (this.m_selectedSceneMime) { case 'Json.digg': { if (sceneName.indexOf('Digg') == -1) continue; break; } case 'Json.twitter': { if (sceneName.indexOf('Twitter') == -1) continue; break; } case 'Json.instagram': { if (sceneName.indexOf('Instagram') == -1) continue; break; } case 'Json.calendar': { if (sceneName.indexOf('Calendar') == -1) continue; break; } case 'Json.spreadsheet': { if (sceneName.indexOf('Sheet') == -1) continue; break; } case 'Json.weather': { if (sceneName.indexOf('Weather') == -1) continue; break; } default: { } } this.m_sceneAccounts.push({ nativeId: sceneConfig[2], sceneName: sceneName, businessId: sceneConfig[0], src: `_assets/scenes/'${sceneName}.jpg` }) } this.m_sceneAccounts.pop(); } _onAddScene(category) { switch (category.mimeType) { case 'blank': { this._nameScene((i_name) => { if (_.isUndefined(i_name) || i_name.length == 0) return; var player_data = this.bs.getBlockBoilerplate('3510').getDefaultPlayerData(PLACEMENT_IS_SCENE); var sceneId = this.rp.createScene(player_data, '', i_name); this.rp.reduxCommit(); this.toastr.info('scene imported and is available in scene list'); this._goBack(); }); return; } case 'template': { this.m_selectedSceneMime = 'all'; break; } case 'Json.digg': { this.m_selectedSceneMime = 'Json.digg'; break; } case 'Json.twitter': { this.m_selectedSceneMime = 'Json.twitter'; break; } case 'Json.instagram.feed': { this.m_selectedSceneMime = 'Json.instagram'; break; } case 'Json.calendar': { this.m_selectedSceneMime = 'Json.calendar'; break; } case 'Json.weather': { this.m_selectedSceneMime = 'Json.weather'; break; } case 'Json.spreadsheet': { this.m_selectedSceneMime = 'Json.spreadsheet'; break; } } this._getTemplates(); this.m_selectTypeMode = false; } _goBack() { this.onGoBack.emit() } private _nameScene(i_cb) { bootbox.prompt("Give your scene a name:", (result) => { if (result === null) { i_cb(); } else { result = Lib.CleanChar(result); i_cb(result); } }); } ngOnInit() { } destroy() { } private _initSceneTemplates() { this.m_sceneTemplates = { Template21: [396594, 'ida.signage.me', 50], Template18: [396594, 'ida.signage.me', 47], Template23: [396594, 'ida.signage.me', 52], Template24: [396594, 'ida.signage.me', 53], Template22: [396594, 'ida.signage.me', 51], Template6: [396594, 'ida.signage.me', 34], Template13: [396594, 'ida.signage.me', 42], Template8: [396594, 'ida.signage.me', 36], Template10: [396594, 'ida.signage.me', 39], Template12: [396594, 'ida.signage.me', 41], Template1: [396594, 'ida.signage.me', 29], Template4: [396594, 'ida.signage.me', 32], Template5: [396594, 'ida.signage.me', 33], Template7: [396594, 'ida.signage.me', 35], Template3: [396594, 'ida.signage.me', 31], Template9: [396594, 'ida.signage.me', 38], Template2: [396594, 'ida.signage.me', 30], Template11: [396594, 'ida.signage.me', 40], Template15: [396594, 'ida.signage.me', 44], Template16: [396594, 'ida.signage.me', 45], Template14: [396594, 'ida.signage.me', 43], Template17: [396594, 'ida.signage.me', 46], Template19: [396594, 'ida.signage.me', 48], Template20: [396594, 'ida.signage.me', 49], Template26: [396595, 'ida.signage.me', 4], Template40: [396595, 'ida.signage.me', 18], Template42: [396595, 'ida.signage.me', 21], Template34: [396595, 'ida.signage.me', 12], Template27: [396595, 'ida.signage.me', 5], Template38: [396595, 'ida.signage.me', 16], Template35: [396595, 'ida.signage.me', 13], Template37: [396595, 'ida.signage.me', 15], Template39: [396595, 'ida.signage.me', 17], Template36: [396595, 'ida.signage.me', 14], Template29: [396595, 'ida.signage.me', 7], Template44: [396595, 'ida.signage.me', 23], Template43: [396595, 'ida.signage.me', 22], Template32: [396595, 'ida.signage.me', 10], Template46: [396595, 'ida.signage.me', 24], Template41: [396595, 'ida.signage.me', 19], Template45: [396595, 'ida.signage.me', 25], Template47: [396595, 'ida.signage.me', 20], Template48: [396595, 'ida.signage.me', 26], Template25: [396595, 'ida.signage.me', 3], Template28: [396595, 'ida.signage.me', 6], Template30: [396595, 'ida.signage.me', 8], Template31: [396595, 'ida.signage.me', 9], Template33: [396595, 'ida.signage.me', 11], Template57: [396596, 'ida.signage.me', 14], Template59: [396596, 'ida.signage.me', 16], Template58: [396596, 'ida.signage.me', 15], Template62: [396596, 'ida.signage.me', 19], Template61: [396596, 'ida.signage.me', 18], Template53: [396596, 'ida.signage.me', 10], Template65: [396596, 'ida.signage.me', 22], Template63: [396596, 'ida.signage.me', 20], Template66: [396596, 'ida.signage.me', 23], Template67: [396596, 'ida.signage.me', 24], Template64: [396596, 'ida.signage.me', 21], Template68: [396596, 'ida.signage.me', 25], Template70: [396596, 'ida.signage.me', 27], Template72: [396596, 'ida.signage.me', 29], Template51: [396596, 'ida.signage.me', 8], Template52: [396596, 'ida.signage.me', 9], Template50: [396596, 'ida.signage.me', 7], Template71: [396596, 'ida.signage.me', 28], Template49: [396596, 'ida.signage.me', 6], Template54: [396596, 'ida.signage.me', 11], Template55: [396596, 'ida.signage.me', 12], Template56: [396596, 'ida.signage.me', 13], Template60: [396596, 'ida.signage.me', 17], Template69: [396596, 'ida.signage.me', 26], Template81: [396597, 'ida.signage.me', 25], Template73: [396597, 'ida.signage.me', 17], Template78: [396597, 'ida.signage.me', 22], Template79: [396597, 'ida.signage.me', 23], Template84: [396597, 'ida.signage.me', 28], Template76: [396597, 'ida.signage.me', 20], Template77: [396597, 'ida.signage.me', 21], Template86: [396597, 'ida.signage.me', 30], Template87: [396597, 'ida.signage.me', 31], Template85: [396597, 'ida.signage.me', 29], Template83: [396597, 'ida.signage.me', 27], Template90: [396597, 'ida.signage.me', 34], Template91: [396597, 'ida.signage.me', 35], Template92: [396597, 'ida.signage.me', 36], Template89: [396597, 'ida.signage.me', 33], Template95: [396597, 'ida.signage.me', 40], Template94: [396597, 'ida.signage.me', 38], Template88: [396597, 'ida.signage.me', 32], Template75: [396597, 'ida.signage.me', 19], Template93: [396597, 'ida.signage.me', 37], Template74: [396597, 'ida.signage.me', 18], Template82: [396597, 'ida.signage.me', 26], Template80: [396597, 'ida.signage.me', 24], Template96: [396598, 'ida.signage.me', 12], Template98: [396598, 'ida.signage.me', 14], Template99: [396598, 'ida.signage.me', 15], Template100: [396598, 'ida.signage.me', 16], Template97: [396598, 'ida.signage.me', 13], Template116: [401213, 'ida.signage.me', 21], Template117: [401213, 'ida.signage.me', 22], Template102: [401213, 'ida.signage.me', 6], Template105: [401213, 'ida.signage.me', 9], Template109: [401213, 'ida.signage.me', 13], Template101: [401213, 'ida.signage.me', 5], Template108: [401213, 'ida.signage.me', 12], Template111: [401213, 'ida.signage.me', 15], Template110: [401213, 'ida.signage.me', 14], Template113: [401213, 'ida.signage.me', 17], Template112: [401213, 'ida.signage.me', 16], Template115: [401213, 'ida.signage.me', 20], Template119: [401213, 'ida.signage.me', 24], Template114: [401213, 'ida.signage.me', 19], Template106: [401213, 'ida.signage.me', 10], Template103: [401213, 'ida.signage.me', 7], Template104: [401213, 'ida.signage.me', 8], Template107: [401213, 'ida.signage.me', 11], Template120: [401213, 'ida.signage.me', 25], Template118: [401213, 'ida.signage.me', 23], Template127: [402393, 'ida.signage.me', 13], Template123: [402393, 'ida.signage.me', 9], Template122: [402393, 'ida.signage.me', 8], Template121: [402393, 'ida.signage.me', 7], Template124: [402393, 'ida.signage.me', 10], Template125: [402393, 'ida.signage.me', 11], Template126: [402393, 'ida.signage.me', 12], Template128: [402393, 'ida.signage.me', 14], Template130: [402393, 'ida.signage.me', 16], Template129: [402393, 'ida.signage.me', 15], Template140: [402394, 'ida.signage.me', 13], Template132: [402394, 'ida.signage.me', 5], Template137: [402394, 'ida.signage.me', 10], Template138: [402394, 'ida.signage.me', 11], Template135: [402394, 'ida.signage.me', 8], Template136: [402394, 'ida.signage.me', 9], Template131: [402394, 'ida.signage.me', 4], Template133: [402394, 'ida.signage.me', 6], Template134: [402394, 'ida.signage.me', 7], Template139: [402394, 'ida.signage.me', 12], Template142: [402395, 'ida.signage.me', 10], Template146: [402395, 'ida.signage.me', 14], Template141: [402395, 'ida.signage.me', 9], Template144: [402395, 'ida.signage.me', 12], Template145: [402395, 'ida.signage.me', 13], Template143: [402395, 'ida.signage.me', 11], Template148: [402395, 'ida.signage.me', 16], Template147: [402395, 'ida.signage.me', 15], Template149: [402395, 'ida.signage.me', 17], Template150: [402395, 'ida.signage.me', 18], Template155: [402397, 'ida.signage.me', 22], Template154: [402397, 'ida.signage.me', 21], Template151: [402397, 'ida.signage.me', 18], Template152: [402397, 'ida.signage.me', 19], Template153: [402397, 'ida.signage.me', 20], Template156: [402397, 'ida.signage.me', 23], Template157: [402397, 'ida.signage.me', 24], Template158: [402397, 'ida.signage.me', 25], Template159: [402397, 'ida.signage.me', 26], Template160: [402397, 'ida.signage.me', 27], Template168: [402398, 'ida.signage.me', 25], Template169: [402398, 'ida.signage.me', 26], Template161: [402398, 'ida.signage.me', 18], Template162: [402398, 'ida.signage.me', 19], Template164: [402398, 'ida.signage.me', 20], Template163: [402398, 'ida.signage.me', 21], Template165: [402398, 'ida.signage.me', 22], Template167: [402398, 'ida.signage.me', 24], Template166: [402398, 'ida.signage.me', 23], Template170: [402398, 'ida.signage.me', 27], Template171: [402399, 'ida.signage.me', 12], Template174: [402399, 'ida.signage.me', 15], Template172: [402399, 'ida.signage.me', 13], Template176: [402399, 'ida.signage.me', 17], Template173: [402399, 'ida.signage.me', 14], Template178: [402399, 'ida.signage.me', 19], Template177: [402399, 'ida.signage.me', 18], Template179: [402399, 'ida.signage.me', 20], Template180: [402399, 'ida.signage.me', 21], Template175: [402399, 'ida.signage.me', 16], Template187: [402400, 'ida.signage.me', 16], Template188: [402400, 'ida.signage.me', 17], Template181: [402400, 'ida.signage.me', 10], Template182: [402400, 'ida.signage.me', 11], Template183: [402400, 'ida.signage.me', 12], Template184: [402400, 'ida.signage.me', 13], Template185: [402400, 'ida.signage.me', 14], Template186: [402400, 'ida.signage.me', 15], Template190: [402400, 'ida.signage.me', 19], Template189: [402400, 'ida.signage.me', 18], Template192: [402401, 'ida.signage.me', 9], Template193: [402401, 'ida.signage.me', 11], Template194: [402401, 'ida.signage.me', 12], Template197: [402401, 'ida.signage.me', 16], Template198: [402401, 'ida.signage.me', 17], Template199: [402401, 'ida.signage.me', 18], Template200: [402401, 'ida.signage.me', 19], Template191: [402401, 'ida.signage.me', 8], Template195: [402401, 'ida.signage.me', 13], Template196: [402401, 'ida.signage.me', 14], Template204: [402402, 'ida.signage.me', 20], Template206: [402402, 'ida.signage.me', 22], Template201: [402402, 'ida.signage.me', 17], Template202: [402402, 'ida.signage.me', 18], Template203: [402402, 'ida.signage.me', 19], Template205: [402402, 'ida.signage.me', 21], Template207: [402402, 'ida.signage.me', 23], Template208: [402402, 'ida.signage.me', 24], Template209: [402402, 'ida.signage.me', 25], Template210: [402402, 'ida.signage.me', 26], Template214: [402444, 'ida.signage.me', 13], Template213: [402444, 'ida.signage.me', 12], Template212: [402444, 'ida.signage.me', 11], Template219: [402444, 'ida.signage.me', 18], Template211: [402444, 'ida.signage.me', 10], Template215: [402444, 'ida.signage.me', 14], Template216: [402444, 'ida.signage.me', 15], Template217: [402444, 'ida.signage.me', 16], Template218: [402444, 'ida.signage.me', 17], Template220: [402444, 'ida.signage.me', 19], Template227: [402446, 'ida.signage.me', 16], Template222: [402446, 'ida.signage.me', 11], Template224: [402446, 'ida.signage.me', 13], Template230: [402446, 'ida.signage.me', 19], Template221: [402446, 'ida.signage.me', 10], Template223: [402446, 'ida.signage.me', 12], Template225: [402446, 'ida.signage.me', 14], Template226: [402446, 'ida.signage.me', 15], Template228: [402446, 'ida.signage.me', 17], Template229: [402446, 'ida.signage.me', 18], Template238: [405214, 'ida.signage.me', 10], Template231: [405214, 'ida.signage.me', 3], Template232: [405214, 'ida.signage.me', 4], Template233: [405214, 'ida.signage.me', 5], Template236: [405214, 'ida.signage.me', 8], Template237: [405214, 'ida.signage.me', 9], Template239: [405214, 'ida.signage.me', 11], Template240: [405214, 'ida.signage.me', 12], Template235: [405214, 'ida.signage.me', 7], Template234: [405214, 'ida.signage.me', 6], Template241: [405215, 'ida.signage.me', 10], Template247: [405215, 'ida.signage.me', 16], Template248: [405215, 'ida.signage.me', 17], Template242: [405215, 'ida.signage.me', 11], Template243: [405215, 'ida.signage.me', 12], Template244: [405215, 'ida.signage.me', 13], Template245: [405215, 'ida.signage.me', 14], Template246: [405215, 'ida.signage.me', 15], Template249: [405215, 'ida.signage.me', 18], Template250: [405215, 'ida.signage.me', 19], Template258: [405216, 'ida.signage.me', 14], Template253: [405216, 'ida.signage.me', 9], Template256: [405216, 'ida.signage.me', 12], Template257: [405216, 'ida.signage.me', 13], Template251: [405216, 'ida.signage.me', 7], Template254: [405216, 'ida.signage.me', 10], Template255: [405216, 'ida.signage.me', 11], Template259: [405216, 'ida.signage.me', 15], Template252: [405216, 'ida.signage.me', 8], Template260: [405216, 'ida.signage.me', 16], Template267: [405217, 'ida.signage.me', 20], Template261: [405217, 'ida.signage.me', 14], Template262: [405217, 'ida.signage.me', 15], Template268: [405217, 'ida.signage.me', 21], Template266: [405217, 'ida.signage.me', 19], Template265: [405217, 'ida.signage.me', 18], Template270: [405217, 'ida.signage.me', 23], Template263: [405217, 'ida.signage.me', 16], Template264: [405217, 'ida.signage.me', 17], Template269: [405217, 'ida.signage.me', 22], Template273: [405218, 'ida.signage.me', 20], Template274: [405218, 'ida.signage.me', 21], Template275: [405218, 'ida.signage.me', 22], Template279: [405218, 'ida.signage.me', 26], Template271: [405218, 'ida.signage.me', 18], Template272: [405218, 'ida.signage.me', 19], Template278: [405218, 'ida.signage.me', 25], Template276: [405218, 'ida.signage.me', 23], Template277: [405218, 'ida.signage.me', 24], Template280: [405218, 'ida.signage.me', 27], Template283: [405219, 'ida.signage.me', 19], Template284: [405219, 'ida.signage.me', 20], Template286: [405219, 'ida.signage.me', 22], Template281: [405219, 'ida.signage.me', 17], Template282: [405219, 'ida.signage.me', 18], Template285: [405219, 'ida.signage.me', 21], Template287: [405219, 'ida.signage.me', 23], Template288: [405219, 'ida.signage.me', 24], Template289: [405219, 'ida.signage.me', 25], Template290: [405219, 'ida.signage.me', 26], Template294: [405220, 'ida.signage.me', 15], Template295: [405220, 'ida.signage.me', 16], Template293: [405220, 'ida.signage.me', 14], Template297: [405220, 'ida.signage.me', 18], Template291: [405220, 'ida.signage.me', 12], Template292: [405220, 'ida.signage.me', 13], Template296: [405220, 'ida.signage.me', 17], Template298: [405220, 'ida.signage.me', 19], Template299: [405220, 'ida.signage.me', 20], Template300: [405220, 'ida.signage.me', 21], Template306: [405221, 'ida.signage.me', 10], Template307: [405221, 'ida.signage.me', 11], Template309: [405221, 'ida.signage.me', 13], Template301: [405221, 'ida.signage.me', 5], Template302: [405221, 'ida.signage.me', 6], Template303: [405221, 'ida.signage.me', 7], Template304: [405221, 'ida.signage.me', 8], Template305: [405221, 'ida.signage.me', 9], Template308: [405221, 'ida.signage.me', 12], Template310: [405221, 'ida.signage.me', 14], Template311: [405222, 'ida.signage.me', 24], Template313: [405222, 'ida.signage.me', 26], Template314: [405222, 'ida.signage.me', 27], Template315: [405222, 'ida.signage.me', 28], Template312: [405222, 'ida.signage.me', 25], Template317: [405222, 'ida.signage.me', 30], Template316: [405222, 'ida.signage.me', 29], Template319: [405222, 'ida.signage.me', 32], Template318: [405222, 'ida.signage.me', 31], Template320: [405222, 'ida.signage.me', 34], Template321: [405223, 'ida.signage.me', 21], Template326: [405223, 'ida.signage.me', 26], Template325: [405223, 'ida.signage.me', 25], Template329: [405223, 'ida.signage.me', 30], Template324: [405223, 'ida.signage.me', 24], Template328: [405223, 'ida.signage.me', 29], Template322: [405223, 'ida.signage.me', 22], Template323: [405223, 'ida.signage.me', 23], Template327: [405223, 'ida.signage.me', 28], //Template340: [405223,'ida.signage.me',32], Template331: [405224, 'ida.signage.me', 27], Template332: [405224, 'ida.signage.me', 28], Template338: [405224, 'ida.signage.me', 35], Template339: [405224, 'ida.signage.me', 36], Template335: [405224, 'ida.signage.me', 32], Template337: [405224, 'ida.signage.me', 34], Template334: [405224, 'ida.signage.me', 31], Template340: [405224, 'ida.signage.me', 37], Template336: [405224, 'ida.signage.me', 33], Template333: [405224, 'ida.signage.me', 30], Template341: [407718, 'ida.signage.me', 4], Template344: [407718, 'ida.signage.me', 7], Template345: [407718, 'ida.signage.me', 8], Template346: [407718, 'ida.signage.me', 9], Template348: [407718, 'ida.signage.me', 11], Template343: [407718, 'ida.signage.me', 6], Template342: [407718, 'ida.signage.me', 5], Template347: [407718, 'ida.signage.me', 10], Template349: [407718, 'ida.signage.me', 12], Template350: [407718, 'ida.signage.me', 13], Template351: [407720, 'ida.signage.me', 5], Template353: [407720, 'ida.signage.me', 7], Template352: [407720, 'ida.signage.me', 6], Template355: [407720, 'ida.signage.me', 9], Template356: [407720, 'ida.signage.me', 11], Template354: [407720, 'ida.signage.me', 10], Template357: [407720, 'ida.signage.me', 12], Template360: [407720, 'ida.signage.me', 15], Template358: [407720, 'ida.signage.me', 13], Template359: [407720, 'ida.signage.me', 14], Template362: [407721, 'ida.signage.me', 6], Template364: [407721, 'ida.signage.me', 8], Template367: [407721, 'ida.signage.me', 11], Template361: [407721, 'ida.signage.me', 5], Template368: [407721, 'ida.signage.me', 12], Template370: [407721, 'ida.signage.me', 14], Template365: [407721, 'ida.signage.me', 9], Template369: [407721, 'ida.signage.me', 13], Template363: [407721, 'ida.signage.me', 7], Template366: [407721, 'ida.signage.me', 10], Template371: [407722, 'ida.signage.me', 15], Template373: [407722, 'ida.signage.me', 16], Template372: [407722, 'ida.signage.me', 17], Template374: [407722, 'ida.signage.me', 18], Template375: [407722, 'ida.signage.me', 19], Template378: [407722, 'ida.signage.me', 21], Template376: [407722, 'ida.signage.me', 20], Template379: [407722, 'ida.signage.me', 23], Template377: [407722, 'ida.signage.me', 22], Template380: [407722, 'ida.signage.me', 24], Template382: [407723, 'ida.signage.me', 28], Template384: [407723, 'ida.signage.me', 30], Template385: [407723, 'ida.signage.me', 31], Template381: [407723, 'ida.signage.me', 27], Template388: [407723, 'ida.signage.me', 34], Template387: [407723, 'ida.signage.me', 33], Template386: [407723, 'ida.signage.me', 32], Template389: [407723, 'ida.signage.me', 35], Template390: [407723, 'ida.signage.me', 36], Template383: [407723, 'ida.signage.me', 29], Template392: [407725, 'ida.signage.me', 17], Template397: [407725, 'ida.signage.me', 22], Template400: [407725, 'ida.signage.me', 25], Template398: [407725, 'ida.signage.me', 23], Template399: [407725, 'ida.signage.me', 24], Template391: [407725, 'ida.signage.me', 16], Template393: [407725, 'ida.signage.me', 18], Template394: [407725, 'ida.signage.me', 19], Template395: [407725, 'ida.signage.me', 20], Template396: [407725, 'ida.signage.me', 21], Template401: [407726, 'ida.signage.me', 18], Template404: [407726, 'ida.signage.me', 21], Template405: [407726, 'ida.signage.me', 22], Template406: [407726, 'ida.signage.me', 23], Template403: [407726, 'ida.signage.me', 20], Template407: [407726, 'ida.signage.me', 24], Template408: [407726, 'ida.signage.me', 25], Template402: [407726, 'ida.signage.me', 19], Template409: [407726, 'ida.signage.me', 26], Template410: [407726, 'ida.signage.me', 27], Template411: [407727, 'ida.signage.me', 8], Template413: [407727, 'ida.signage.me', 10], Template415: [407727, 'ida.signage.me', 12], Template414: [407727, 'ida.signage.me', 11], Template416: [407727, 'ida.signage.me', 13], Template417: [407727, 'ida.signage.me', 14], Template419: [407727, 'ida.signage.me', 16], Template418: [407727, 'ida.signage.me', 15], Template412: [407727, 'ida.signage.me', 9], Template420: [407727, 'ida.signage.me', 17], Template428: [407728, 'ida.signage.me', 17], Template429: [407728, 'ida.signage.me', 18], Template421: [407728, 'ida.signage.me', 10], Template422: [407728, 'ida.signage.me', 11], Template423: [407728, 'ida.signage.me', 12], Template424: [407728, 'ida.signage.me', 13], Template425: [407728, 'ida.signage.me', 14], Template426: [407728, 'ida.signage.me', 15], Template427: [407728, 'ida.signage.me', 16], Template430: [407728, 'ida.signage.me', 19], Template434: [407729, 'ida.signage.me', 22], Template439: [407729, 'ida.signage.me', 27], Template433: [407729, 'ida.signage.me', 21], Template438: [407729, 'ida.signage.me', 26], Template431: [407729, 'ida.signage.me', 19], Template432: [407729, 'ida.signage.me', 20], Template435: [407729, 'ida.signage.me', 23], Template436: [407729, 'ida.signage.me', 24], Template437: [407729, 'ida.signage.me', 25], Template440: [407729, 'ida.signage.me', 28], Template442: [407730, 'ida.signage.me', 13], Template443: [407730, 'ida.signage.me', 14], Template444: [407730, 'ida.signage.me', 15], Template446: [407730, 'ida.signage.me', 17], Template445: [407730, 'ida.signage.me', 16], Template448: [407730, 'ida.signage.me', 19], Template449: [407730, 'ida.signage.me', 20], Template441: [407730, 'ida.signage.me', 12], Template447: [407730, 'ida.signage.me', 18], Template450: [407730, 'ida.signage.me', 21], Template454: [411108, 'ida.signage.me', 7], Template455: [411108, 'ida.signage.me', 8], Template453: [411108, 'ida.signage.me', 6], Template456: [411108, 'ida.signage.me', 9], Template457: [411108, 'ida.signage.me', 10], Template458: [411108, 'ida.signage.me', 11], Template459: [411108, 'ida.signage.me', 12], Template452: [411108, 'ida.signage.me', 5], Template460: [411108, 'ida.signage.me', 13], Template451: [411108, 'ida.signage.me', 4], Template461: [411110, 'ida.signage.me', 12], Template463: [411110, 'ida.signage.me', 14], Template462: [411110, 'ida.signage.me', 13], Template465: [411110, 'ida.signage.me', 16], Template466: [411110, 'ida.signage.me', 17], Template467: [411110, 'ida.signage.me', 18], Template464: [411110, 'ida.signage.me', 15], Template469: [411110, 'ida.signage.me', 20], Template470: [411110, 'ida.signage.me', 21], Template468: [411110, 'ida.signage.me', 19], Template471: [411111, 'ida.signage.me', 10], Template473: [411111, 'ida.signage.me', 12], Template474: [411111, 'ida.signage.me', 13], Template475: [411111, 'ida.signage.me', 14], Template476: [411111, 'ida.signage.me', 15], Template472: [411111, 'ida.signage.me', 11], Template479: [411111, 'ida.signage.me', 18], Template480: [411111, 'ida.signage.me', 19], Template478: [411111, 'ida.signage.me', 17], Template477: [411111, 'ida.signage.me', 16], Template483: [411112, 'ida.signage.me', 7], Template481: [411112, 'ida.signage.me', 5], Template484: [411112, 'ida.signage.me', 8], Template486: [411112, 'ida.signage.me', 10], Template482: [411112, 'ida.signage.me', 6], Template485: [411112, 'ida.signage.me', 9], Template488: [411112, 'ida.signage.me', 12], Template489: [411112, 'ida.signage.me', 13], Template487: [411112, 'ida.signage.me', 11], Template490: [411112, 'ida.signage.me', 14], Template491: [411113, 'ida.signage.me', 17], Template493: [411113, 'ida.signage.me', 19], Template494: [411113, 'ida.signage.me', 20], Template497: [411113, 'ida.signage.me', 23], Template492: [411113, 'ida.signage.me', 18], Template495: [411113, 'ida.signage.me', 21], Template496: [411113, 'ida.signage.me', 22], Template498: [411113, 'ida.signage.me', 24], Template499: [411113, 'ida.signage.me', 25], Template500: [411113, 'ida.signage.me', 26], Digg9: [411115, 'ida.signage.me', 21], Digg6: [411115, 'ida.signage.me', 18], Digg10: [411115, 'ida.signage.me', 22], Digg11: [411115, 'ida.signage.me', 23], Digg12: [411115, 'ida.signage.me', 24], Digg8: [411115, 'ida.signage.me', 20], Digg14: [411115, 'ida.signage.me', 26], Digg13: [411115, 'ida.signage.me', 25], Digg15: [411115, 'ida.signage.me', 27], Digg16: [411115, 'ida.signage.me', 28], Digg17: [411115, 'ida.signage.me', 29], Digg18: [411115, 'ida.signage.me', 30], Digg19: [411115, 'ida.signage.me', 31], Digg20: [411115, 'ida.signage.me', 32], Digg1: [411115, 'ida.signage.me', 12], Digg2: [411115, 'ida.signage.me', 14], Digg3: [411115, 'ida.signage.me', 15], Digg4: [411115, 'ida.signage.me', 16], Digg5: [411115, 'ida.signage.me', 17], Digg7: [411115, 'ida.signage.me', 19], Digg21: [434454, 'jupiter.signage.me', 6], Sheet6: [411116, 'ida.signage.me', 24], Sheet7: [411116, 'ida.signage.me', 25], Sheet5: [411116, 'ida.signage.me', 23], Sheet12: [411116, 'ida.signage.me', 30], Sheet13: [411116, 'ida.signage.me', 31], Sheet15: [411116, 'ida.signage.me', 33], Sheet9: [411116, 'ida.signage.me', 27], Sheet16: [411116, 'ida.signage.me', 34], Sheet10: [411116, 'ida.signage.me', 28], Sheet17: [411116, 'ida.signage.me', 35], Sheet19: [411116, 'ida.signage.me', 37], Sheet14: [411116, 'ida.signage.me', 32], Sheet18: [411116, 'ida.signage.me', 36], Sheet11: [411116, 'ida.signage.me', 29], Sheet8: [411116, 'ida.signage.me', 26], Sheet1: [411116, 'ida.signage.me', 19], //Calendar1: [411116,'ida.signage.me',17], Sheet2: [411116, 'ida.signage.me', 20], Sheet3: [411116, 'ida.signage.me', 21], Sheet4: [411116, 'ida.signage.me', 22], Sheet20: [411116, 'ida.signage.me', 38], Sheet21: [434454, 'jupiter.signage.me', 8], Calendar11: [411117, 'ida.signage.me', 22], Calendar10: [411117, 'ida.signage.me', 24], Calendar12: [411117, 'ida.signage.me', 25], //Twitter1: [411117,'ida.signage.me',12], Calendar3: [411117, 'ida.signage.me', 15], Calendar13: [411117, 'ida.signage.me', 26], Calendar15: [411117, 'ida.signage.me', 28], Calendar5: [411117, 'ida.signage.me', 17], Calendar9: [411117, 'ida.signage.me', 21], Calendar14: [411117, 'ida.signage.me', 27], Calendar6: [411117, 'ida.signage.me', 18], Calendar16: [411117, 'ida.signage.me', 29], Calendar4: [411117, 'ida.signage.me', 16], Calendar17: [411117, 'ida.signage.me', 30], Calendar7: [411117, 'ida.signage.me', 19], Calendar18: [411117, 'ida.signage.me', 31], Calendar8: [411117, 'ida.signage.me', 20], Calendar1: [411117, 'ida.signage.me', 13], Calendar2: [411117, 'ida.signage.me', 14], Calendar19: [411117, 'ida.signage.me', 32], Calendar20: [411117, 'ida.signage.me', 33], Calendar21: [434454, 'jupiter.signage.me', 5], Twitter1: [411119, 'ida.signage.me', 24], Twitter2: [411119, 'ida.signage.me', 25], Twitter4: [411119, 'ida.signage.me', 27], Twitter3: [411119, 'ida.signage.me', 26], Twitter6: [411119, 'ida.signage.me', 29], Twitter7: [411119, 'ida.signage.me', 30], Twitter5: [411119, 'ida.signage.me', 28], Twitter8: [411119, 'ida.signage.me', 31], Twitter9: [411119, 'ida.signage.me', 32], Twitter10: [411119, 'ida.signage.me', 33], Twitter12: [411119, 'ida.signage.me', 35], Twitter11: [411119, 'ida.signage.me', 34], Twitter13: [411119, 'ida.signage.me', 36], Twitter14: [411119, 'ida.signage.me', 37], Twitter16: [411119, 'ida.signage.me', 39], Twitter17: [411119, 'ida.signage.me', 40], Twitter19: [411119, 'ida.signage.me', 42], Twitter15: [411119, 'ida.signage.me', 38], Twitter20: [411119, 'ida.signage.me', 43], Twitter18: [411119, 'ida.signage.me', 41], Weather1: [411114, 'ida.signage.me', 12], Weather2: [411114, 'ida.signage.me', 13], Weather3: [411114, 'ida.signage.me', 14], Weather13: [411114, 'ida.signage.me', 24], Weather10: [411114, 'ida.signage.me', 21], Weather11: [411114, 'ida.signage.me', 22], Weather16: [411114, 'ida.signage.me', 27], Weather17: [411114, 'ida.signage.me', 28], Weather18: [411114, 'ida.signage.me', 29], Weather19: [411114, 'ida.signage.me', 30], Weather4: [411114, 'ida.signage.me', 15], Weather20: [411114, 'ida.signage.me', 31], Weather5: [411114, 'ida.signage.me', 16], Weather12: [411114, 'ida.signage.me', 23], Weather14: [411114, 'ida.signage.me', 25], Weather15: [411114, 'ida.signage.me', 26], Weather6: [411114, 'ida.signage.me', 17], Weather7: [411114, 'ida.signage.me', 18], Weather8: [411114, 'ida.signage.me', 19], Weather9: [411114, 'ida.signage.me', 20], Weather23: [411120, 'ida.signage.me', 20], Weather22: [411120, 'ida.signage.me', 19], Weather26: [411120, 'ida.signage.me', 23], Weather21: [411120, 'ida.signage.me', 18], Weather27: [411120, 'ida.signage.me', 24], Weather29: [411120, 'ida.signage.me', 26], Weather24: [411120, 'ida.signage.me', 21], Weather25: [411120, 'ida.signage.me', 22], Weather28: [411120, 'ida.signage.me', 25], Weather30: [411120, 'ida.signage.me', 27], Weather31: [411120, 'ida.signage.me', 28], Weather32: [411120, 'ida.signage.me', 30], Weather33: [411120, 'ida.signage.me', 31], Weather34: [411120, 'ida.signage.me', 32], Weather35: [411120, 'ida.signage.me', 33], Weather36: [411120, 'ida.signage.me', 34], Weather37: [411120, 'ida.signage.me', 35], Weather38: [411120, 'ida.signage.me', 36], Weather39: [411120, 'ida.signage.me', 38], Weather40: [411120, 'ida.signage.me', 39], Weather41: [411120, 'ida.signage.me', 40], Weather42: [411120, 'ida.signage.me', 42], Weather43: [411120, 'ida.signage.me', 41], Weather44: [411120, 'ida.signage.me', 43], Weather46: [434454, 'jupiter.signage.me', 7] } } } ================================================ FILE: src/app/scenes/scene-editor.ts ================================================ import {AfterViewInit, ChangeDetectorRef, Component, ElementRef, EventEmitter, Input, NgZone, Output, ViewChild} from "@angular/core"; import {Compbaser} from "ng-mslib"; import {YellowPepperService} from "../../services/yellowpepper.service"; import {BlockService, ISceneData} from "../blocks/block-service"; import {CommBroker, IMessage} from "../../services/CommBroker"; import {RedPepperService} from "../../services/redpepper.service"; import {Consts, PLACEMENT_IS_SCENE, PLACEMENT_SCENE} from "../../interfaces/Consts"; import {BlockFactoryService} from "../../services/block-factory-service"; import {BlockFabric} from "../blocks/block-fabric"; import * as _ from "lodash"; import {PlayerDataModelExt} from "../../store/model/msdb-models-extended"; import {timeout} from "../../decorators/timeout-decorator"; import {IUiState} from "../../store/store.data"; import {ACTION_UISTATE_UPDATE, SideProps} from "../../store/actions/appdb.actions"; import {ModalComponent} from "ng2-bs3-modal/ng2-bs3-modal"; import {IAddContents} from "../../interfaces/IAddContent"; import {PreviewModeEnum} from "../live-preview/live-preview"; import {AddContent} from "../campaigns/add-content"; import {Lib} from "../../Lib"; import {MainAppShowStateEnum} from "../app-component"; import {PlayerDataModel} from "../../store/imsdb.interfaces_auto"; export const ADD_NEW_BLOCK_SCENE = 'ADD_NEW_BLOCK_SCENE'; export const SCENE_BLOCK_CHANGE = 'SCENE_BLOCK_CHANGE'; export const SCENE_CHANGE = 'SCENE_CHANGE'; const JSON_EVENT_ROW_CHANGED = 'JSON_EVENT_ROW_CHANGED'; const STATIONS_POLL_TIME_CHANGED = 'STATIONS_POLL_TIME_CHANGED'; const THEME_CHANGED = 'THEME_CHANGED'; const SELECTED_STACK_VIEW = 'SELECTED_STACK_VIEW'; const BLOCK_SELECTED = 'BLOCK_SELECTED'; const SCENE_BLOCKS_RENDERED = 'SCENE_BLOCKS_RENDERED'; const SCENE_EDITOR_REMOVE = 'SCENE_EDITOR_REMOVE'; const SCENE_ITEM_REMOVE = 'SCENE_ITEM_REMOVE'; const SCENE_CANVAS_SELECTED = 'SCENE_CANVAS_SELECTED'; const SCENE_SELECT_NEXT = 'SCENE_SELECT_NEXT'; const WIZARD_EXIT = 'WIZARD_EXIT'; const NEW_SCENE_ADD = 'NEW_SCENE_ADD'; const SCENE_LIST_UPDATED = 'SCENE_LIST_UPDATED'; const SCENE_UNDO = 'SCENE_UNDO'; const SCENE_REDO = 'SCENE_REDO'; const REMOVING_SCENE = 'REMOVING_SCENE'; const REMOVED_SCENE = 'REMOVED_SCENE'; const REMOVING_RESOURCE = 'REMOVING_RESOURCE'; const REMOVED_RESOURCE = 'REMOVED_RESOURCE'; const ADDED_RESOURCE = 'ADDED_RESOURCE'; const MOUSE_ENTERS_CANVAS = 'MOUSE_ENTERS_CANVAS'; const FONT_SELECTION_CHANGED = 'FONT_SELECTION_CHANGED'; const CAMPAIGN_LIST_LOADING = 'CAMPAIGN_LIST_LOADED'; @Component({ selector: 'scene-editor', styles: [` a { outline: none; } `], // changeDetection: ChangeDetectionStrategy.OnPush, template: ` {{me}}
      ` }) export class SceneEditor extends Compbaser implements AfterViewInit { m_PLACEMENT_SCENE = PLACEMENT_SCENE; m_activeAddContent = false; m_isLoading = true; m_selectedSceneID = undefined; m_sceneScrollTop = 0; m_sceneScrollLeft = 0; m_objectScaling = 0; m_mouseX: any = 0; m_mouseY: any = 0; m_gridMagneticMode = 0; m_rendering = false; m_memento = {}; m_canvas: any; m_canvasBlocks = []; _sceneBlockModified; m_sceneBlock; m_canvasMouseState = 0; m_copiesObjects = []; m_canvasScale = 1; SCALE_FACTOR = 1.2; PUSH_TOP = 1; PUSH_BOTTOM = 0; m_blocks = { blocksPre: [], blocksPost: {}, blockSelected: undefined }; constructor(private zone: NgZone, private blockFactory: BlockFactoryService, private rp: RedPepperService, private el: ElementRef, private yp: YellowPepperService, private cd: ChangeDetectorRef, private bs: BlockService, private commBroker: CommBroker) { super(); // this.cd.detach(); } @ViewChild(ModalComponent) modal: ModalComponent; @ViewChild('addContent') addContent: AddContent; ngAfterViewInit() { this.m_selectedSceneID = undefined; this.m_sceneScrollTop = 0; this.m_sceneScrollLeft = 0; this.m_objectScaling = 0; this.m_mouseX = 0; this.m_mouseY = 0; this.m_gridMagneticMode = 0; this.m_rendering = false; this.m_memento = {}; this.m_canvasMouseState = 0; this.m_copiesObjects = []; this.PUSH_TOP = 1; this.PUSH_BOTTOM = 0; this.m_blocks = { blocksPre: [], blocksPost: {}, blockSelected: undefined }; this.m_canvas = undefined; this.m_canvasScale = 1; this.SCALE_FACTOR = 1.2; this._listenSceneSelection(); this._listenTotalBlocksModified(); this._listenAddBlockWizard(); this._listenToCanvasScroll(); this._listenSceneChanged(); this._listenContextMenu(); this._listenSelectNextBlock(); // this._listenSceneBlockRemove(); this._listenSceneNew(); this._listenAppResized(); this._listenBlockSelected(); this._delegateSceneBlockModified(); this._notifyScaleChange(); // this.addContent.setPlacement(1); } ngOnInit() { } @Output() onGoBack: EventEmitter = new EventEmitter(); _onClosed() { var uiState: IUiState = {uiSideProps: SideProps.miniDashboard} this.yp.ngrxStore.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})) } _notifyScaleChange() { let uiState: IUiState = {scene: {fabric: {scale: this.m_canvasScale}}} this.yp.ngrxStore.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})) } _onToolbarAction(event) { con('toolbar ' + event); switch (event) { case 'back': { let uiState: IUiState = {uiSideProps: SideProps.miniDashboard, scene: {blockSelected: -1}} this.yp.ngrxStore.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})) this.onGoBack.emit(); break; } case 'add': { this.m_activeAddContent = true; this.modal.open(); break; } case 'removeItem': { this._onContentMenuSelection('remove'); break; } case 'playPreview': { let uiState: IUiState = {mainAppState: MainAppShowStateEnum.SAVE_AND_PREVIEW, previewMode: PreviewModeEnum.SCENE} this.yp.ngrxStore.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})) break; } case 'pushItemToTopButtonToolTip': { if (_.isUndefined(this.m_selectedSceneID)) return; var block = this.m_canvas.getActiveObject(); if (_.isNull(block)) { this._discardSelections(); return; } this.m_canvas.bringToFront(block); this._updateZorder(this.PUSH_TOP, block); this._mementoAddState(); break; } case 'pushItemToBottomButtonToolTip': { if (_.isUndefined(this.m_selectedSceneID)) return; var block = this.m_canvas.getActiveObject(); if (_.isNull(block)) { this._discardSelections(); return; } this.m_canvas.sendToBack(block); this._updateZorder(this.PUSH_BOTTOM, block); this._mementoAddState(); break; } case 'sceneZoomIn': { this._zoomIn(); this._discardSelections(); this._resetAllObjectScale(); this.m_canvas.renderAll(); break; } case 'sceneZoomOut': { this._zoomOut(); this._discardSelections(); this._resetAllObjectScale(); this.m_canvas.renderAll(); break; } case 'sceneZoomReset': { this._zoomReset(); this._resetAllObjectScale(); this.m_canvas.renderAll(); break; } case 'undo': { if (this.m_rendering) return; this.m_blocks.blockSelected = undefined; this._discardSelections(); this._mementoLoadState('undo'); break; } case 'redo': { if (this.m_rendering) return; this.m_blocks.blockSelected = undefined; this._discardSelections(); this._mementoLoadState('redo'); break; } case 'magneticGrid': { if (this.m_rendering || _.isUndefined(this.m_canvas)) return; switch (this.m_gridMagneticMode) { case 0: { this.m_gridMagneticMode = 1; break; } case 1: { this.m_gridMagneticMode = 2; break; } case 2: { this.m_gridMagneticMode = 0; break; } } this.m_sceneBlock.setCanvas(this.m_canvas, this.m_gridMagneticMode); break; } } } /** Listen to changes in a new scene selection @method _listenSceneSelection **/ _listenSceneSelection() { this.cancelOnDestroy( // this.yp.listenSceneSelected(true) .delay(1000) .subscribe((sceneData: ISceneData) => { if (!sceneData) { this.m_isLoading = true; this.cd.markForCheck(); return; } this.m_selectedSceneID = sceneData.scene_id_pseudo_id; this._loadScene(); this._sceneCanvasSelected(); if (this._mementoInit()) this._mementoAddState(); }, (e) => console.error(e)) ) } /** Listen to changes in a new scene selection @method _listenSceneSelection **/ _listenBlockSelected() { var sceneData: ISceneData = { scene_id: null, scene_id_pseudo_id: null, scene_native_id: null, block_pseudo_id: null, playerDataModel: null, domPlayerData: null, domPlayerDataJson: null, domPlayerDataXml: null } this.cancelOnDestroy( this.yp.listenSceneOrBlockSelectedChanged() .startWith(sceneData) .pairwise() .filter((v: Array) => { if (v[0].scene_id == null) return false; if (v[0].block_pseudo_id != v[1].block_pseudo_id) return false; if (Lib.IsEqual(v[0].domPlayerDataJson, v[1].domPlayerDataJson)) return false; delete v["0"].domPlayerDataJson.Player.Data.Layout; delete v["1"].domPlayerDataJson.Player.Data.Layout; if (Lib.IsEqual(v[0].domPlayerDataJson, v[1].domPlayerDataJson)) return false; return true; }) .subscribe((v) => { this.commBroker.fire({event: SCENE_BLOCK_CHANGE, fromInstance: this, message: [v[1].block_pseudo_id]}) }, (e) => console.error(e)) ) this.cancelOnDestroy( this.commBroker.onEvent(BLOCK_SELECTED) .skip(1) .subscribe((e: IMessage) => { let uiState: IUiState = {uiSideProps: SideProps.sceneBlock, scene: {blockSelected: e.message}}; this.yp.ngrxStore.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})) }, (e) => console.error(e)) ) } @timeout(500) _updateBlocksCount() { if (!this.m_canvas) return; this.m_canvasBlocks = [{id: 'SCENE', name: '[Scene canvas]'}]; this.m_canvas.getObjects().forEach((block: BlockFabric) => { this.m_canvasBlocks.push({ id: block.getBlockData().blockID, name: block.getBlockData().blockName, }) }) this.cd.markForCheck(); } /** Bring the scene into view @method _sceneActive **/ _sceneActive() { // $('#sceneToolbar').fadeIn(); // $('#sceneToolbar').fadeTo(500, 1); } /** Init a new canvas and listen to even changes on that new canvas @method _initializeCanvas @param {Number} w width @param {Number} h height **/ _initializeCanvas(w, h) { $('#sceneCanvasContainer', this.el.nativeElement).empty(); $('#sceneCanvasContainer', this.el.nativeElement).append(``); this.m_canvas = new fabric.Canvas('sceneCanvas'); this.m_canvas.renderOnAddRemove = false; $('#sceneCanvas', this.el.nativeElement).addClass('basicBorder'); this._listenBlockModified(); this._listenCanvasSelections(); this._listenKeyboard(); } /** Init a new scene and subclass off a standard Block @method _initializeScene **/ _initializeScene(i_selectedSceneID) { var scene_player_data = this.rp.getScenePlayerdata(i_selectedSceneID); this.m_sceneBlock = this.blockFactory.createBlock(i_selectedSceneID, scene_player_data); this.m_sceneBlock.setCanvas(this.m_canvas, this.m_gridMagneticMode); ////_.extend(this.m_canvas, this.m_sceneBlock); } /** Load a new scene and dispose of any previous ones @return {Number} Unique clientId. **/ _loadScene() { if (_.isUndefined(this.m_selectedSceneID)) return -1; this.m_isLoading = true; this._disposeBlocks(); this.disposeScene(); this._zoomReset(); // this.m_property.resetPropertiesView(); var domPlayerData = this.rp.getScenePlayerdataDom(this.m_selectedSceneID); var l = $(domPlayerData).find('Layout').eq(0); var w = $(l).attr('width'); var h = $(l).attr('height'); this._initializeCanvas(w, h); this._initializeScene(this.m_selectedSceneID); this._preRender(domPlayerData); } /** Listen to selection of next block @method _listenSelectNextDivision **/ _listenSelectNextBlock() { this.cancelOnDestroy( // this.commBroker.onEvent(SCENE_SELECT_NEXT) .subscribe((e: IMessage) => { if (_.isUndefined(this.m_selectedSceneID)) return; var viewer = this.m_canvas.getActiveObject(); var viewIndex = this.m_canvas.getObjects().indexOf(viewer); var totalViews = this.m_canvas.getObjects().length; var blockID = undefined; if (viewIndex == totalViews - 1) { this.m_canvas.setActiveObject(this.m_canvas.item(0)); blockID = this.m_canvas.getActiveObject().getBlockData().blockID; this.commBroker.fire({event: BLOCK_SELECTED, fromInstance: this, message: blockID}); } else { this.m_canvas.setActiveObject(this.m_canvas.item(viewIndex + 1)); blockID = this.m_canvas.getActiveObject().getBlockData().blockID; this.commBroker.fire({event: BLOCK_SELECTED, fromInstance: this, message: blockID}); } }, (e) => console.error(e)) ) } /** Listen to when a user selects to delete a block @method _listenSceneBlockRemove **/ // _listenSceneBlockRemove() { // this.cancelOnDestroy( // // // this.commBroker.onEvent(SCENE_ITEM_REMOVE) // .subscribe((msg: IMessage) => { // this._onContentMenuSelection('remove'); // }, (e) => console.error(e)) // ) // } /** Listen to keyboard events @method _listenKeyboard **/ _listenKeyboard() { // if (_.isUndefined(this.m_canvas)) // return; // $('canvas',this.el.nativeElement).attr('tabindex', '1'); // var keyDown = _.debounce( (e) => { // if (this.m_objectScaling) // return; // if (this.m_canvasMouseState) // return; // var block = this.m_canvas.getActiveObject(); // if (_.isNull(block)) // return; // var dimensionProps = BB.comBroker.getService(BB.SERVICES['DIMENSION_PROPS_LAYOUT']); // var values = dimensionProps.getValues(); // var val = e.shiftKey ? 25 : 1; // switch (e.keyCode) { // case 38: { // values.y = values.y - val; // break; // } // case 40: { // values.y = values.y + val; // break; // } // case 37: { // values.x = values.x - val; // break; // } // case 39: { // values.x = values.x + val; // break; // } // } // dimensionProps.setValues(values, true); // return false; // }, 100); // $('.upper-canvas').keydown(keyDown); } /** Listen to user selection of new scene @method _listenSceneNew **/ _listenSceneNew() { this.cancelOnDestroy( // this.commBroker.onEvent(NEW_SCENE_ADD) .subscribe((msg: IMessage) => { var player_data = this.bs.getBlockBoilerplate('3510').getDefaultPlayerData(PLACEMENT_IS_SCENE); this.createScene(player_data, false, true, msg.message.mimeType, msg.message.name); }, (e) => console.error(e)) ) } // /** // Listen when mouse enters canvas wrapper and announce it // @method _listenMouseEnterCanvas // **/ // _listenMouseEnterCanvas() { // $('#sceneCanvasContainer', this.el.nativeElement).on("mouseover", (e) => { // this.commBroker.fire({event: MOUSE_ENTERS_CANVAS, fromInstance: this}); // }); // } _listenTotalBlocksModified() { this.cancelOnDestroy( this.yp.listenSelectedSceneChanged() .pairwise() .map((i_playerDataModelsExt: Array) => { var a0 = i_playerDataModelsExt[0].getPlayerDataValue(); var a1 = $.parseXML(a0); var a2 = $(a1).find('Players').children('Player') .map((i, player) => $(player).attr('id')) var previousBlockIds = $.makeArray(a2); var b0 = i_playerDataModelsExt[1].getPlayerDataValue(); var b1 = $.parseXML(b0); var b2 = $(b1).find('Players').children('Player') .map((i, player) => $(player).attr('id')) var currentBlockIds = $.makeArray(b2); return !_.isEqual(currentBlockIds, previousBlockIds); }).filter(v => v) .subscribe(() => { this._updateBlocksCount(); }, (e) => console.error(e)) ) } /** Listen to the event of scene changes which normally comes from a block that modified its data and re-render all scene content @method _listenSceneChanged **/ _listenSceneChanged() { var message: IMessage = { event: '', fromInstance: null } this.cancelOnDestroy( // this.commBroker.onEvent(SCENE_BLOCK_CHANGE) .subscribe((msg: IMessage) => { if (this.m_rendering) return; var blockIDs = msg.message; con('block(s) edited ' + blockIDs); var domPlayerData = this.rp.getScenePlayerdataDom(this.m_selectedSceneID); this.m_blocks.blockSelected = blockIDs[0]; this._preRender(domPlayerData, blockIDs); this._mementoAddState(); }, (e) => console.error(e)) ) this.cancelOnDestroy( // this.commBroker.onEvent(SCENE_CHANGE) .subscribe((msg: IMessage) => { if (this.m_rendering) return; this.m_sceneBlock.fabricSceneBg(msg); this._mementoAddState(); }, (e) => console.error(e)) ) } /** Listen to any canvas right click @method _listenContextMenu **/ /** Listen to any canvas right click @method _listenContextMenu **/ _listenContextMenu() { var self = this; jQueryAny('#sceneCanvasContainer', this.el.nativeElement).contextmenu({ target: '#sceneContextMenu', before: function (e, element, target) { e.preventDefault(); // no canvas if (_.isUndefined(self.m_canvas)) { this.closemenu(); return false; } // remember right click position for pasting self.m_mouseX = e.offsetX; self.m_mouseY = e.offsetY; // group selected var active = self.m_canvas.getActiveGroup(); if (active) { $('.blocksOnly', '#sceneContextMenu').show(); return true; } // scene selected var block = self.m_canvas.getActiveObject(); if (_.isNull(block)) { $('.blocksOnly', '#sceneContextMenu').hide(); return true; } // object selected $('.blocksOnly', '#sceneContextMenu').show(); return true; }, onItem: function (context, e) { self._onContentMenuSelection($(e.target).attr('name')) } }); } /** On Scene right click context menu selection command @method _onContentMenuSelection @param {String} i_command **/ _onContentMenuSelection(i_command) { var blocks = []; var contextCmd = (i_blocks) => { switch (i_command) { case 'copy': { this.m_copiesObjects = []; _.each(i_blocks, (selectedObject:any) => { var blockPlayerData = selectedObject.getBlockData().blockData; blockPlayerData = this.rp.stripPlayersID(blockPlayerData); this.m_copiesObjects.push(blockPlayerData); }); break; } case 'cut': { let uiState: IUiState = {scene: {blockSelected: -1}} this.yp.ngrxStore.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})) this.m_copiesObjects = []; _.each(i_blocks, (selectedObject:any) => { var blockData = selectedObject.getBlockData(); var blockPlayerData = blockData.blockData; this._discardSelections(); this.rp.removeScenePlayer(this.m_selectedSceneID, blockData.blockID); this._disposeBlocks(blockData.blockID); blockPlayerData = this.rp.stripPlayersID(blockPlayerData); this.m_copiesObjects.push(blockPlayerData); }); this.m_canvas.renderAll(); this.commBroker.fire({event: SCENE_ITEM_REMOVE, fromInstance: this}) // this._updateBlockCount(); this.rp.reduxCommit(); break; } case 'remove': { let uiState: IUiState = {scene: {blockSelected: -1}} this.yp.ngrxStore.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})) _.each(i_blocks, (selectedObject:any) => { var blockData = selectedObject.getBlockData(); this._discardSelections(); this.rp.removeScenePlayer(this.m_selectedSceneID, blockData.blockID); this._disposeBlocks(blockData.blockID); }); this.m_canvas.renderAll(); this.commBroker.fire({event: SCENE_ITEM_REMOVE, fromInstance: this}) // this._updateBlockCount(); this.rp.reduxCommit(); break; } case 'paste': { if (this.m_copiesObjects.length == 0) return; var x: any, y: any, blockID, origX: any, origY: any, blockIDs = []; _.each(this.m_copiesObjects, (domPlayerData, i: any) => { blockID = this.rp.generateSceneId(); $(domPlayerData).attr('id', blockID); blockIDs.push(blockID); var layout: any = $(domPlayerData).find('Layout'); if (i == 0) { origX = parseInt(layout.attr('x')); origY = parseInt(layout.attr('y')); x = this.m_mouseX; y = this.m_mouseY; } else { var a: any = layout.attr('x') - origX; var b: any = layout.attr('y') - origY; x = this.m_mouseX + a; y = this.m_mouseY + b; } layout.attr('x', x); layout.attr('y', y); var player_data = (new XMLSerializer()).serializeToString(domPlayerData); this.rp.appendScenePlayerBlock(this.m_selectedSceneID, player_data); }); this._discardSelections(); if (this.m_copiesObjects.length == 1) { this.commBroker.fire({event: SCENE_BLOCK_CHANGE, fromInstance: this, message: [blockID]}); } else { this.commBroker.fire({event: SCENE_BLOCK_CHANGE, fromInstance: this, message: blockIDs}); } // this._updateBlockCount(); this.rp.reduxCommit(); break; } } }; // no canvas if (_.isUndefined(this.m_canvas)) { return; } // group selected var group = this.m_canvas.getActiveGroup(); if (group) { con(i_command + ' on group'); blocks = []; _.each(group.objects, (selectedObject) => { blocks.push(selectedObject); }); contextCmd(blocks); return; } // scene selected var block = this.m_canvas.getActiveObject(); if (_.isNull(block)) { con(i_command + ' on scene'); contextCmd(null); return; } // object selected con(i_command + ' on object'); blocks = []; blocks.push(block); contextCmd(blocks); return true; } /** Listen to canvas scrolling @method _listenToCanvasScroll **/ _listenToCanvasScroll() { var self = this; var sceneScrolling = _.debounce(() => { $('#sceneCanvasContainer', this.el.nativeElement).scroll((e) => { self.m_sceneScrollTop = $('#scenesPanel').scrollTop(); self.m_sceneScrollLeft = $('#scenesPanel').scrollLeft(); self.m_canvas.calcOffset(); }); }, 500); $('#sceneCanvasContainer', this.el.nativeElement).scroll(sceneScrolling); } /** Listen to and add new component / resources to scene @method _listenAddBlockWizard @param {event} e **/ _listenAddBlockWizard() { this.cancelOnDestroy( // this.commBroker.onEvent(ADD_NEW_BLOCK_SCENE) .subscribe((msg: IMessage) => { var addContents: IAddContents = msg.message; var blockID = this.rp.generateSceneId(); var player_data = this.bs.getBlockBoilerplate(addContents.blockCode).getDefaultPlayerData(PLACEMENT_SCENE, addContents.resourceId); var domPlayerData = $.parseXML(player_data); $(domPlayerData).find('Player').attr('id', blockID); player_data = (new XMLSerializer()).serializeToString(domPlayerData); this.rp.appendScenePlayerBlock(this.m_selectedSceneID, player_data); this.rp.reduxCommit(); this.commBroker.fire({event: SCENE_BLOCK_CHANGE, fromInstance: this, message: [blockID]}); }, (e) => console.error(e)) ) } _onAddedNewBlock() { this.modal.close() this.m_activeAddContent = false; } /** @method _sceneProcessing **/ _sceneProcessing(i_status, i_callBack, from ?) { if (i_status) { $('#sceneProcessing', this.el.nativeElement).css({ width: $('#scenePanelWrap').width(), height: $('#scenePanelWrap').height() }) $('#sceneProcessing', this.el.nativeElement).fadeTo('fast', 0.7, i_callBack); } else { this.m_isLoading = false; this.cd.markForCheck(); $('#sceneProcessing', this.el.nativeElement).fadeOut('slow', i_callBack); } } /** Init a undo / redo via memento pattern @method _mementoInit @return {Boolean} return true if memento created false if one already existed **/ _mementoInit() { if (_.isUndefined(this.m_memento[this.m_selectedSceneID])) { this.m_memento[this.m_selectedSceneID] = { playerData: [], cursor: -1 }; return true; } return false; } /** Remember current memento state @method _mementoAddState **/ _mementoAddState() { const MAX = 100; if (_.isUndefined(this.m_selectedSceneID)) return; this._mementoInit(); // maintain memento to stack MAX value if (this.m_memento[this.m_selectedSceneID].playerData.length > MAX) this.m_memento[this.m_selectedSceneID].playerData.shift(); // if undo / redo was executed, remove ahead mementos if (this.m_memento[this.m_selectedSceneID].cursor != this.m_memento[this.m_selectedSceneID].playerData.length - 1) this.m_memento[this.m_selectedSceneID].playerData.splice(this.m_memento[this.m_selectedSceneID].cursor + 1); var player_data = this.rp.getScenePlayerdata(this.m_selectedSceneID); this.m_memento[this.m_selectedSceneID].playerData.push(player_data); this.m_memento[this.m_selectedSceneID].cursor = this.m_memento[this.m_selectedSceneID].playerData.length - 1; } /** Remember current memento state @method _mementoLoadState **/ _mementoLoadState(i_direction) { if (_.isUndefined(this.m_selectedSceneID)) return; this._mementoInit(); if (this.m_memento[this.m_selectedSceneID].playerData.length == 0) return; switch (i_direction) { case 'undo': { var cursor = this.m_memento[this.m_selectedSceneID].cursor; if (cursor == 0) return; this.m_memento[this.m_selectedSceneID].cursor--; cursor = this.m_memento[this.m_selectedSceneID].cursor; break; } case 'redo': { var cursor = this.m_memento[this.m_selectedSceneID].cursor; if (cursor == this.m_memento[this.m_selectedSceneID].playerData.length - 1) return; this.m_memento[this.m_selectedSceneID].cursor++; cursor = this.m_memento[this.m_selectedSceneID].cursor; break; } } var player_data = this.m_memento[this.m_selectedSceneID].playerData[cursor]; this.rp.setScenePlayerData(this.m_selectedSceneID, player_data); this._loadScene(); this.rp.reduxCommit(); this.commBroker.fire({event: SCENE_LIST_UPDATED, fromInstance: this}); } /** Update the z-order index of an object @method _updateZorder @param {String} i_pushDirection @param {Object} i_block **/ _updateZorder(i_pushDirection, i_block) { if (_.isUndefined(this.m_selectedSceneID)) return; var active = this.m_canvas.getActiveGroup(); if (active) return; var blockID = i_block.getBlockData().blockID; var sceneDomPlayerData = this.rp.getScenePlayerdataDom(this.m_selectedSceneID); var domBlockData = $(sceneDomPlayerData).find('[id="' + blockID + '"]'); switch (i_pushDirection) { case this.PUSH_TOP: { $(sceneDomPlayerData).find('Players').append($(domBlockData)); break; } case this.PUSH_BOTTOM: { $(sceneDomPlayerData).find('Players').prepend($(domBlockData)); break; } } this.rp.setScenePlayerData(this.m_selectedSceneID, (new XMLSerializer()).serializeToString(sceneDomPlayerData)); this.rp.reduxCommit(); } /** Pre render creates all of the Fabric blocks that will later get added when we call _render This allows for smooth (non flickering) rendering since when we are ready to render, the blocks have already been instantiated and ready to be added to canvas @method _preRender @param {Object} i_domPlayerData @param {Object} [i_blockIDs] optionally render only a single block **/ _preRender(i_domPlayerData, i_blockIDs ?) { var zIndex = -1; if (i_blockIDs) { if (i_blockIDs.indexOf(this.m_selectedSceneID) > -1) return; } this._renderPause(); this.m_blocks.blocksPre = []; this.m_blocks.blocksPost = {}; con('pre-rendering new blocks'); // if rendering specific blocks instead of entire canvas if (i_blockIDs) { $(i_domPlayerData).find('Players').children('Player').each((i, player) => { zIndex++; var blockID = $(player).attr('id'); if (_.indexOf(i_blockIDs, blockID) > -1) { var block = { blockID: blockID, blockType: $(player).attr('player'), zIndex: zIndex, player_data: (new XMLSerializer()).serializeToString(player) }; this.m_blocks.blocksPre.push(block); } }); } else { $(i_domPlayerData).find('Players').children('Player').each((i, player) => { var block = { blockID: $(player).attr('id'), blockType: $(player).attr('player'), zIndex: -1, player_data: (new XMLSerializer()).serializeToString(player) }; this.m_blocks.blocksPre.push(block); }); } this._createBlock(i_blockIDs); } /** Render the pre created blocks (via _preRender) and add all blocks to fabric canvas @method _render **/ _render(i_blockIDs) { if (!this.m_canvas) return; var nZooms = Math.round(Math.log(1 / this.m_canvasScale) / Math.log(1.2)); var selectedBlockID = this.m_blocks.blockSelected; var createAll = i_blockIDs[0] == undefined ? true : false; // if to re-render entire canvas if (createAll) { this._disposeBlocks(); this._zoomReset(); } else { // if to re-render only changed blocks for (var i = 0; i < i_blockIDs.length; i++) this._disposeBlocks(i_blockIDs[i]); } _.forEach(this.m_blocks.blocksPost, (i_block) => { this.m_canvas.add(i_block); }); if (createAll) { this._resetAllObjectScale(); this._zoomTo(nZooms); this._updateBlocksCount(); } else { // if to re-render only changed blocks _.forEach(this.m_blocks.blocksPost, (i_block: any) => { var zIndex = i_block.getZindex(); if (zIndex > -1) i_block.moveTo(zIndex); this.m_canvas.setActiveObject(i_block); this._zoomToBlock(nZooms, i_block); this._resetObjectScale(i_block); }); } this._scrollTo(this.m_sceneScrollTop, this.m_sceneScrollLeft); this.m_canvas.renderAll(); this._sceneProcessing(false, () => { }); this._renderContinue(); // if (createAll) // this._updateBlockCount(); // select previous selection if (_.isUndefined(selectedBlockID)) return; if (createAll) { for (var i = 0; i < this.m_canvas.getObjects().length; i++) { if (selectedBlockID == this.m_canvas.item(i).getBlockData().blockID) { this._blockSelected(this.m_canvas.item(i)); break; } } } else { var block = this.m_blocks.blocksPost[Object.keys(this.m_blocks.blocksPost)[0]]; this._blockSelected(block); } } /** Prevent rendering of canvas to continue and remove canvas listeners @method _renderPause **/ _renderPause() { this.m_rendering = true; if (_.isUndefined(this.m_canvas)) return; this.m_canvas.removeListeners(); } /** Allow rendering of canvas to continue and add canvas listeners @method _renderContinue **/ _renderContinue() { this.m_rendering = false; if (_.isUndefined(this.m_canvas)) return; this.m_canvas._initEventListeners(); } /** Create all the blocks that have been pre injected to m_blocks.blocksPre and after each block is created created the next block; thus creating blocks sequentially due to fabric bug. When no more blocks are to be created (m_blocks.blocksPre queue is empty) we _render the canvas @method _createBlock @param {Array} [i_blockIDs] optional array of block ids to render, or non if we render the entire canvas **/ _createBlock(i_blockIDs) { var blockData = this.m_blocks.blocksPre.shift(); if (blockData == undefined) { this._render([i_blockIDs]); return; } var newBlock = this.blockFactory.createBlock(blockData.blockID, blockData.player_data, this.m_selectedSceneID); newBlock.setZindex(blockData.zIndex); var blockID = newBlock.getBlockData().blockID; newBlock.fabricateBlock(this.m_canvasScale, () => { this.m_blocks.blocksPost[blockID] = newBlock; this._createBlock(i_blockIDs); }); } /** Announce to all that scene was re-rendered but do it via debounce @method _delegateSceneBlockModified **/ _delegateSceneBlockModified() { var self = this; self._sceneBlockModified = _.debounce(function (e) { self.commBroker.fire({event: SCENE_BLOCKS_RENDERED, fromInstance: self.m_canvas, message: ''}); self._mementoAddState(); // self._drawGrid(); }, 200); } /** Listen to when the app is resized so we can re-render @method _listenAppResized **/ _listenAppResized() { this.cancelOnDestroy( // this.commBroker.onEvent(Consts.Events().WIN_SIZED).subscribe((msg: IMessage) => { if (_.isUndefined(this.m_canvas)) return; this.m_canvas.calcOffset(); }, (e) => console.error(e)) ) } /** Scene block scales via mouse UI @method _sceneBlockScaled @param {Event} e **/ _sceneBlockScaled(e) { var self = this; if (self.m_objectScaling) return; self.m_objectScaling = 1; var block = e.target; if (_.isUndefined(block)) return; self.zone.runOutsideAngular(() => { block.on('modified', function () { setTimeout(function () { block.off('modified'); if (!(block instanceof BlockFabric)) return; var blockID = block.getBlockData().blockID; self.commBroker.fire({event: SCENE_BLOCK_CHANGE, fromInstance: this, message: [blockID]}); self.m_objectScaling = 0; }, 15) }); }); } /** Scene block moving @method _sceneBlockMoving @param {Object} i_options **/ _sceneBlockMoving(i_options) { var grid = 0; if (i_options.target.lockMovementX) return; if (this.m_gridMagneticMode == 0) return; if (this.m_gridMagneticMode == 1) grid = 5; if (this.m_gridMagneticMode == 2) grid = 10; i_options.target.set({ left: Math.round(i_options.target.left / grid) * grid, top: Math.round(i_options.target.top / grid) * grid }); } /** Listen to changes in scale so we can reset back to non-zoom on any block object @method _listenBlockModified **/ _listenBlockModified() { var self = this; self.zone.runOutsideAngular(() => { self.m_canvas.on({ //'object:moving': self.m_objectScaleHandler, //'object:selected': self.m_objectScaleHandler, 'object:modified': () => { self._sceneBlockModified(); }, 'object:scaling': $.proxy(self._sceneBlockScaled, self) }); self.m_canvas.on('object:moving', $.proxy(self._sceneBlockMoving, self)); }); } _drawGrid() { this.m_canvas.setBackgroundColor('', this.m_canvas.renderAll.bind(this.m_canvas)); var c: any = $(this.m_canvas)[0]; var context = c.getContext("2d"); var h = 600; var w = 700; for (var x = 0.5; x < (w + 1); x += 10) { context.moveTo(x, 0); context.lineTo(x, (h + 1)); } for (var y = 0.5; y < (h + 1); y += 10) { context.moveTo(0, y); context.lineTo(w, y); } context.globalAlpha = 0.1; context.strokeStyle = "black"; context.stroke(); context.globalAlpha = 1; } /** Listen to canvas user selections @method _listenCanvasSelections **/ _listenCanvasSelections() { this.zone.runOutsideAngular(() => { //this.m_canvas.on('object:selected', (e) => { // var blockID = e.target.m_blockType; // BB.comBroker.fire(BB.EVENTS.BLOCK_SELECTED, this, null, blockID); //}); this.m_canvas.on('mouse:down', (options) => { this.m_canvasMouseState = 1; }); this.m_canvas.on('mouse:up', (options) => { this.m_canvasMouseState = 0; var active = this.m_canvas.getActiveObject(); var group = this.m_canvas.getActiveGroup(); //options.e.stopImmediatePropagation(); //options.e.preventDefault(); //// Group if (group) { con('group selected'); var selectedGroup = options.target || group; _.each(group.objects, (selectedObject:any) => { var objectPos = { x: (selectedGroup.left + (selectedObject.left)), y: (selectedGroup.top + (selectedObject.top)) }; if (objectPos.x < 0 && objectPos.y < 0) { // objectPos.x = objectPos.x * -1; // objectPos.y = objectPos.y * -1; return; } var blockID = selectedObject.getBlockData().blockID; con('object: ' + selectedObject.m_blockType + ' ' + blockID); this._updateBlockCords(selectedObject, true, objectPos.x, objectPos.y, selectedObject.currentWidth, selectedObject.currentHeight, selectedObject.angle); // this._updateZorder(); }); // this._mementoAddState(); selectedGroup.hasControls = false; // this.m_property = BB.comBroker.getService(BB.SERVICES['PROPERTIES_VIEW']).resetPropertiesView(); return; } //// Object if (options.target || active) { var block = options.target || active; this._blockSelected(block); return; } //// Scene this._sceneCanvasSelected(); con('scene: ' + this.m_selectedSceneID); // log('object ' + options.e.clientX + ' ' + options.e.clientY + ' ' + options.target.m_blockType); }); }); } /** Select a block object on the canvas @method _blockSelected @param {Object} i_block **/ _blockSelected(i_block) { this.m_canvas.setActiveObject(i_block); var blockID = i_block.getBlockData().blockID; // con('object: ' + i_block.m_blockType + ' ' + blockID); this._updateBlockCords(i_block, true, i_block.left, i_block.top, i_block.currentWidth, i_block.currentHeight, i_block.angle); this.commBroker.fire({event: BLOCK_SELECTED, fromInstance: this, message: blockID}); } /** Deselect current group and or block selections @method _canvasDiscardSelections **/ _discardSelections() { if (!this.m_canvas) return; this.m_canvas.discardActiveGroup(); this.m_canvas.discardActiveObject(); } /** Set the scene (i.e.: Canvas) as the selected block @method _sceneCanvasSelected **/ _sceneCanvasSelected() { var self = this; if (_.isUndefined(self.m_selectedSceneID)) return; self._discardSelections(); this.commBroker.fire({event: BLOCK_SELECTED, fromInstance: this, message: self.m_selectedSceneID}); } _onItemSelectedFromToolbar(blockID) { if (blockID == 'SCENE') return this._sceneCanvasSelected(); for (var i = 0; i < this.m_canvas.getObjects().length; i++) { if (this.m_canvas.item(i).getBlockData().blockID == blockID) { this._blockSelected(this.m_canvas.item(i)); break; } } } /** Update the coordinates of a block in pepper db, don't allow below w/h MIN_SIZE **/ _updateBlockCords(i_block, i_calcScale, x, y, w, h, a) { var blockID = i_block.getBlockData().blockID; var blockMinWidth = i_block.getBlockData().blockMinWidth; var blockMinHeight = i_block.getBlockData().blockMinHeight; if (i_calcScale) { var sy = 1 / this.m_canvasScale; var sx = 1 / this.m_canvasScale; h = h * sy; w = w * sx; x = x * sx; y = y * sy; } if (h < blockMinHeight) h = blockMinHeight; if (w < blockMinWidth) w = blockMinWidth; var domPlayerData = this.rp.getScenePlayerdataBlock(this.m_selectedSceneID, blockID); var layout = $(domPlayerData).find('Layout'); layout.attr('rotation', parseInt(a)); layout.attr('x', parseInt(x)); layout.attr('y', parseInt(y)); layout.attr('width', parseInt(w)); layout.attr('height', parseInt(h)); var player_data = (new XMLSerializer()).serializeToString(domPlayerData); this.rp.setScenePlayerdataBlock(this.m_selectedSceneID, blockID, player_data); this.rp.reduxCommit(); } /** Reset all canvas objects to their scale is set to 1 @method _resetAllObjectScale **/ _resetAllObjectScale() { if (_.isUndefined(this.m_selectedSceneID)) return; _.each(this.m_canvas.getObjects(), (obj) => { this._resetObjectScale(obj); }); // this.m_canvas.renderAll(); } /** Reset a canvas object so its scale is set to 1 @method _resetObjectScale **/ _resetObjectScale(i_target) { if (_.isNull(i_target)) return; if (i_target.width != i_target.currentWidth || i_target.height != i_target.currentHeight) { i_target.width = i_target.currentWidth; i_target.height = i_target.currentHeight; i_target.scaleX = 1; i_target.scaleY = 1; } } /** Remove all block instances @method _disposeBlocks @params {Number} [i_blockID] optional to remove only a single block **/ _disposeBlocks(i_blockID ?) { var i; if (_.isUndefined(this.m_canvas)) return; var totalObjects = this.m_canvas.getObjects().length; var c = -1; for (i = 0; i < totalObjects; i++) { c++; var block = this.m_canvas.item(c); // single block if (i_blockID) { if (block.getBlockData().blockID == i_blockID) { block.selectable = false; // fix fabric scale block bug this.m_canvas.remove(block); block.deleteBlock(); break; } } else { // all blocks block.selectable = false; // fix fabric scale block bug this.m_canvas.remove(block); if (block) { block.deleteBlock(); c--; } } } if (!i_blockID) this.m_canvas.clear(); } _canvasUnselectable() { var i; if (_.isUndefined(this.m_canvas)) return; this.m_canvas.removeListeners(); //this.m_canvas.interactive = false; // this.m_canvas.selection = false; var totalObjects = this.m_canvas.getObjects().length; var c = -1; for (i = 0; i < totalObjects; i++) { c++; var block = this.m_canvas.item(c); block.selectable = false; if (block) c--; } } /** Zoom to scale size **/ _zoomTo(nZooms) { var i; if (nZooms > 0) { for (i = 0; i < nZooms; i++) this._zoomOut(); } else { for (i = 0; i > nZooms; nZooms++) this._zoomIn(); } } /** Zoom to scale size **/ _zoomToBlock(nZooms, block) { var i; if (nZooms > 0) { for (i = 0; i < nZooms; i++) this._zoomOutBlock(block); } else { for (i = 0; i > nZooms; nZooms++) this._zoomInBlock(block); } } /** Scroll canvas to set position **/ _scrollTo(i_top, i_left) { var self = this; $('#scenesPanel', this.el.nativeElement).scrollTop(i_top); $('#scenesPanel', this.el.nativeElement).scrollLeft(i_left); } /** Zoom scene in @method _zoomIn **/ _zoomIn() { if (_.isUndefined(this.m_selectedSceneID)) return; this.m_canvasScale = this.m_canvasScale * this.SCALE_FACTOR; this.m_canvas.setHeight(this.m_canvas.getHeight() * this.SCALE_FACTOR); this.m_canvas.setWidth(this.m_canvas.getWidth() * this.SCALE_FACTOR); this._notifyScaleChange(); var objects = this.m_canvas.getObjects(); for (var i in objects) { if (_.isNull(objects[i])) return; this._zoomInBlock(objects[i]); } } /** Zoom scene in @method _zoomIn **/ _zoomInBlock(i_block) { var scaleX = i_block.scaleX; var scaleY = i_block.scaleY; var left = i_block.left; var top = i_block.top; var tempScaleX = scaleX * this.SCALE_FACTOR; var tempScaleY = scaleY * this.SCALE_FACTOR; var tempLeft = left * this.SCALE_FACTOR; var tempTop = top * this.SCALE_FACTOR; i_block['canvasScale'] = this.m_canvasScale; i_block.scaleX = tempScaleX; i_block.scaleY = tempScaleY; i_block.left = tempLeft; i_block.top = tempTop; i_block.setCoords(); if (i_block.forEachObject != undefined) { i_block.forEachObject((obj) => { var scaleX = obj.scaleX; var scaleY = obj.scaleY; var left = obj.left; var top = obj.top; var tempScaleX = scaleX * this.SCALE_FACTOR; var tempScaleY = scaleY * this.SCALE_FACTOR; var tempLeft = left * this.SCALE_FACTOR; var tempTop = top * this.SCALE_FACTOR; obj['canvasScale'] = this.m_canvasScale; obj.scaleX = tempScaleX; obj.scaleY = tempScaleY; obj.left = tempLeft; obj.top = tempTop; obj.setCoords(); }); } } /** Zoom scene out @method _zoomOut **/ _zoomOutBlock(i_block) { var scaleX = i_block.scaleX; var scaleY = i_block.scaleY; var left = i_block.left; var top = i_block.top; var tempScaleX = scaleX * (1 / this.SCALE_FACTOR); var tempScaleY = scaleY * (1 / this.SCALE_FACTOR); var tempLeft = left * (1 / this.SCALE_FACTOR); var tempTop = top * (1 / this.SCALE_FACTOR); i_block['canvasScale'] = this.m_canvasScale; i_block.scaleX = tempScaleX; i_block.scaleY = tempScaleY; i_block.left = tempLeft; i_block.top = tempTop; i_block.setCoords(); if (i_block.forEachObject != undefined) { i_block.forEachObject((obj) => { var scaleX = obj.scaleX; var scaleY = obj.scaleY; var left = obj.left; var top = obj.top; var tempScaleX = scaleX * (1 / this.SCALE_FACTOR); var tempScaleY = scaleY * (1 / this.SCALE_FACTOR); var tempLeft = left * (1 / this.SCALE_FACTOR); var tempTop = top * (1 / this.SCALE_FACTOR); obj['canvasScale'] = this.m_canvasScale; obj.scaleX = tempScaleX; obj.scaleY = tempScaleY; obj.left = tempLeft; obj.top = tempTop; obj.setCoords(); }); } } /** Zoom scene out @method _zoomOut **/ _zoomOut() { if (_.isUndefined(this.m_selectedSceneID)) return; this.m_canvasScale = this.m_canvasScale / this.SCALE_FACTOR; this.m_canvas.setHeight(this.m_canvas.getHeight() * (1 / this.SCALE_FACTOR)); this.m_canvas.setWidth(this.m_canvas.getWidth() * (1 / this.SCALE_FACTOR)); this._notifyScaleChange(); var objects = this.m_canvas.getObjects(); for (var i in objects) { if (_.isNull(objects[i])) return; this._zoomOutBlock(objects[i]); } } /** Zoom reset back to scale 1 @method _zoomReset **/ _zoomReset() { if (_.isUndefined(this.m_selectedSceneID) || _.isUndefined(this.m_canvas)) { this.m_canvasScale = 1; this._notifyScaleChange(); return; } this._discardSelections(); this.m_canvas.setHeight(this.m_canvas.getHeight() * (1 / this.m_canvasScale)); this.m_canvas.setWidth(this.m_canvas.getWidth() * (1 / this.m_canvasScale)); var objects = this.m_canvas.getObjects(); for (var i in objects) { if (_.isNull(objects[i])) return; var scaleX = objects[i].scaleX; var scaleY = objects[i].scaleY; var left = objects[i].left; var top = objects[i].top; var tempScaleX = scaleX * (1 / this.m_canvasScale); var tempScaleY = scaleY * (1 / this.m_canvasScale); var tempLeft = left * (1 / this.m_canvasScale); var tempTop = top * (1 / this.m_canvasScale); objects[i].scaleX = tempScaleX; objects[i].scaleY = tempScaleY; objects[i].left = tempLeft; objects[i].top = tempTop; objects[i].setCoords(); objects[i]['canvasScale'] = 1; if (objects[i].forEachObject != undefined) { objects[i].forEachObject((obj) => { var scaleX = obj.scaleX; var scaleY = obj.scaleY; var left = obj.left; var top = obj.top; var tempScaleX = scaleX * (1 / this.m_canvasScale); var tempScaleY = scaleY * (1 / this.m_canvasScale); var tempLeft = left * (1 / this.m_canvasScale); var tempTop = top * (1 / this.m_canvasScale); obj.scaleX = tempScaleX; obj.scaleY = tempScaleY; obj.left = tempLeft; obj.top = tempTop; obj.setCoords(); obj['canvasScale'] = 1; }); } } this.m_canvasScale = 1; this._notifyScaleChange(); } /** Remove a Scene and cleanup after @method disposeScene **/ disposeScene() { if (_.isUndefined(this.m_canvas)) return; this.m_canvas.off('mouse:up'); this.m_canvas.off('mouse:down'); this.m_canvas.off('object:modified'); this.m_canvas.off('object:moving'); this.m_canvas.off('object:scaling'); this.m_canvas.off(); this.m_blocks.blocksPost = {}; this._disposeBlocks(); this.m_sceneBlock.deleteBlock(); this.m_canvas.dispose(); this.m_canvas = undefined; // this.m_property.resetPropertiesView(); } /** Create a new scene based on player_data and strip injected IDs if arged @method createScene @param {String} i_scenePlayerData @optional {Boolean} i_stripIDs @optional {Boolean} i_loadScene @optional {String} i_mimeType @optional {String} i_name **/ createScene(i_scenePlayerData, i_stripIDs, i_loadScene, i_mimeType, i_name) { if (i_stripIDs) i_scenePlayerData = this.rp.stripPlayersID(i_scenePlayerData); this.m_selectedSceneID = this.rp.createScene(i_scenePlayerData, i_mimeType, i_name); // BB.comBroker.fire(BB.EVENTS.NEW_SCENE_ADDED, this, null, this.m_selectedSceneID); if (i_loadScene) this._loadScene(); this.commBroker.fire({event: SCENE_LIST_UPDATED, fromInstance: this}); this.rp.reduxCommit(); } /** Get currently selected scene id @method getSelectedSceneID @return {Number} scene id **/ getSelectedSceneID() { return this.m_selectedSceneID; } destroy() { // removed since we need to keep this data in store when adding new LocationBlock // let uiState: IUiState = { // scene: { // sceneSelected: -1, // blockSelected: -1 // } // } // this.yp.ngrxStore.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})) this.disposeScene(); this.m_canvasScale = -1; this._notifyScaleChange(); } } // } // /** // Listen to undo and redo // @method _listenMemento // **/ // _listenMemento() { // this.commBroker.onEvent(SCENE_UNDO).subscribe((msg: IMessage) => { // if (this.m_rendering) // return; // this.m_blocks.blockSelected = undefined; // this._discardSelections(); // this._mementoLoadState('undo'); // }); // this.commBroker.onEvent(SCENE_REDO).subscribe((msg: IMessage) => { // if (this.m_rendering) // return; // this.m_blocks.blockSelected = undefined; // this._discardSelections(); // this._mementoLoadState('redo'); // }); // } // _initDimensionProps() { // var self = this; // require(['DimensionProps'], (DimensionProps) => { // self.m_dimensionProps = new DimensionProps({ // appendTo: Elements.SCENE_BLOCK_PROPS, // showAngle: true, // showLock: true, // hideSpinners: true // }); // // self.m_dimensionProps.hideSpinners(); // BB.comBroker.setService(BB.SERVICES['DIMENSION_PROPS_LAYOUT'], self.m_dimensionProps); // $(self.m_dimensionProps).on('changed', (e) => { // var block = self.m_canvas.getActiveObject(); // if (_.isNull(block)) // return; // var props = e.target.getValues(); // var block_id = block.getBlockData().blockID; // self._updateBlockCords(block, false, props.x, props.y, props.w, props.h, props.a); // BB.comBroker.fire(BB.EVENTS['SCENE_BLOCK_CHANGE'], self, null, [block_id]); // }); // self._sceneActive(); // }) // } // this._sceneProcessing(true, jQueryAny.proxy(function () { // self._disposeBlocks(); // self.disposeScene(); // self._zoomReset(); // // self.m_property.resetPropertiesView(); // var domPlayerData = self.rp.getScenePlayerdataDom(self.m_selectedSceneID); // var l = $(domPlayerData).find('Layout').eq(0); // var w = $(l).attr('width'); // var h = $(l).attr('height'); // self._initializeCanvas(w, h); // // self._initializeScene(self.m_selectedSceneID); // self._initializeScene(); // self._preRender(domPlayerData); // }), this); // /** // Listen to re-order of screen division, putting selected on top // @method _listenPushToTop // **/ // _listenPushToTop() { // this.commBroker.onEvent(SCENE_PUSH_TOP).subscribe((msg: IMessage) => { // if (_.isUndefined(this.m_selectedSceneID)) // return; // var block = this.m_canvas.getActiveObject(); // if (_.isNull(block)) { // this._discardSelections(); // return; // } // this.m_canvas.bringToFront(block); // this._updateZorder(this.PUSH_TOP, block); // this._mementoAddState(); // }); // } // /** // Listen to re-order of screen division, putting selected at bottom // @method _listenPushToBottom // **/ // _listenPushToBottom() { // this.commBroker.onEvent(SCENE_PUSH_BOTTOM).subscribe((msg: IMessage) => { // if (_.isUndefined(this.m_selectedSceneID)) // return; // var block = this.m_canvas.getActiveObject(); // if (_.isNull(block)) { // this._discardSelections(); // return; // } // this.m_canvas.sendToBack(block); // this._updateZorder(this.PUSH_BOTTOM, block); // this._mementoAddState(); // }); // } // /** // Listen grid magnet when dragging objects // @method _listenGridMagnet // **/ // _listenGridMagnet() { // $('#sceneGridMagnet', this.el.nativeElement).on('click', () => { // if (this.m_rendering || _.isUndefined(this.m_canvas)) // return; // switch (this.m_gridMagneticMode) { // case 0: { // this.m_gridMagneticMode = 1; // break; // } // case 1: { // this.m_gridMagneticMode = 2; // break; // } // case 2: { // this.m_gridMagneticMode = 0; // break; // } // } // this.m_sceneBlock.setCanvas(this.m_canvas, this.m_gridMagneticMode); // }); // } // _listenStackViewSelected() { // var appContentFaderView = BB.comBroker.getService(BB.SERVICES['APP_CONTENT_FADER_VIEW']); // appContentFaderView.on(BB.EVENTS.SELECTED_STACK_VIEW, (e) => { // if (e == BB.comBroker.getService(BB.SERVICES['SCENES_LOADER_VIEW'])) { // setTimeout( () => { // if (_.isUndefined(this.m_canvas)) // return; // this.m_canvas.calcOffset(); // }, 500); // } // }); // } // /** // Listen to all zoom events via wiring the UI // @method _listenZoom // **/ // _listenZoom() { // this.commBroker.onEvent(SCENE_ZOOM_IN).subscribe((msg: IMessage) => { // // this.m_property = BB.comBroker.getService(BB.SERVICES['PROPERTIES_VIEW']).resetPropertiesView(); // this._zoomIn(); // this._discardSelections(); // this._resetAllObjectScale(); // this.m_canvas.renderAll(); // }); // // this.commBroker.onEvent(SCENE_ZOOM_OUT).subscribe((msg: IMessage) => { // // this.m_property = BB.comBroker.getService(BB.SERVICES['PROPERTIES_VIEW']).resetPropertiesView(); // this._zoomOut(); // this._discardSelections(); // this._resetAllObjectScale(); // this.m_canvas.renderAll(); // }); // // this.commBroker.onEvent(SCENE_ZOOM_RESET).subscribe((msg: IMessage) => { // // this.m_property = BB.comBroker.getService(BB.SERVICES['PROPERTIES_VIEW']).resetPropertiesView(); // this._zoomReset(); // this._resetAllObjectScale(); // this.m_canvas.renderAll(); // }); // } // /** // Announce that block count changed with block array of ids // @method self._updateBlockCount(); // **/ // _updateBlockCount() { // setTimeout(() => { // this.m_sceneBlockIds = List([{id: 'SCENE', name: '[Scene canvas]'}]); // var objects = this.m_canvas.getObjects().length; // for (var i = 0; i < objects; i++) { // this.m_sceneBlockIds = this.m_sceneBlockIds.push({ // id: this.m_canvas.item(i).m_block_id, // name: this.m_canvas.item(i).m_blockName // }); // } // // con('NEW LIST CREATED'); // // this.sceneToolbar.updateBlocks(this.m_sceneBlockIds); // }, 1000) // // } ================================================ FILE: src/app/scenes/scene-list.ts ================================================ import {Component, ChangeDetectionStrategy, Input, Output, EventEmitter} from "@angular/core"; import {Compbaser} from "ng-mslib"; import {IUiState} from "../../store/store.data"; import {SideProps} from "../../store/actions/appdb.actions"; import {BlockService, ISceneData} from "../blocks/block-service"; @Component({ selector: 'scene-list', changeDetection: ChangeDetectionStrategy.OnPush, template: ` {{me}} `, }) export class SceneList extends Compbaser { selectedIdx = -1; m_scenes: Array; m_selectedScene: ISceneData; constructor(private bs: BlockService) { super(); } @Input() set scenes(i_scenes: Array) { this.m_scenes = i_scenes; } @Output() slideToSceneEditor: EventEmitter = new EventEmitter(); @Output() slideToSceneName: EventEmitter = new EventEmitter(); @Output() onSceneSelected: EventEmitter = new EventEmitter(); _onSceneSelected(event: MouseEvent, scene: ISceneData, index) { this.selectedIdx = index; let uiState: IUiState; if (jQuery(event.target).hasClass('props')) { uiState = { uiSideProps: SideProps.sceneProps, scene: {sceneSelected: scene.scene_id} } this.onSceneSelected.emit(uiState) } else { uiState = { uiSideProps: SideProps.miniDashboard, scene: {sceneSelected: scene.scene_id} } this.slideToSceneEditor.emit(); this.onSceneSelected.emit(uiState) } this.m_selectedScene = scene; } resetSelection(){ this.selectedIdx = -1; } ngOnInit() { } destroy() { } } ================================================ FILE: src/app/scenes/scene-manager.ts ================================================ import {Component, ElementRef, EventEmitter, Output, ViewChild} from "@angular/core"; import {Observable} from "rxjs"; import {Compbaser} from "ng-mslib"; import {Router} from "@angular/router"; import {RedPepperService} from "../../services/redpepper.service"; import {ACTION_UISTATE_UPDATE, SideProps} from "../../store/actions/appdb.actions"; import {IUiState} from "../../store/store.data"; import {YellowPepperService} from "../../services/yellowpepper.service"; import {ISceneData} from "../blocks/block-service"; import {SceneList} from "./scene-list"; import {ToastsManager} from "ng2-toastr"; import {WizardService} from "../../services/wizard-service"; @Component({ // changeDetection: ChangeDetectionStrategy.OnPush, selector: 'scene-manager', template: ` {{me}}
      scene selection
      ` }) export class SceneManager extends Compbaser { sceneSelected$; public scenes$: Observable> constructor(private yp: YellowPepperService, private rp: RedPepperService, private toastr: ToastsManager, private wizardService:WizardService) { super(); this.preventRedirect(true); this.scenes$ = this.yp.listenScenes() this.sceneSelected$ = this.yp.ngrxStore.select(store => store.appDb.uiState.scene.sceneSelected) this._notifyResetSceneSelection(); // setTimeout(()=>{ // this.wizardService.inModule('scenes'); // },4000) } @ViewChild(SceneList) sceneList:SceneList; @Output() sceneCreate: EventEmitter = new EventEmitter(); @Output() slideToSceneEditor: EventEmitter = new EventEmitter(); @Output() slideToCampaignName: EventEmitter = new EventEmitter(); _notifyResetSceneSelection(){ var uiState: IUiState = {scene: {sceneSelected: -1}} this.yp.ngrxStore.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})) } _removeScene() { this.cancelOnDestroy( this.yp.ngrxStore.select(store => store.appDb.uiState.scene.sceneSelected) .take(1) .subscribe(scene_id => { bootbox.confirm('Are you sure you want to remove the selected scene', (result) => { if (result == true) { this._notifyResetSceneSelection(); this.rp.removeBlocksWithSceneID(scene_id); this.rp.removeSceneFromBlockCollectionInScenes(scene_id); this.rp.removeSceneFromBlockCollectionsInChannels(scene_id); this.rp.removeSceneFromBlockLocationInScenes(scene_id); this.rp.removeSceneFromBlockLocationInChannels(scene_id); this.rp.removeScene(scene_id); this.rp.reduxCommit(); this.sceneList.resetSelection(); this.toastr.info('scene removed from scene list'); } }); }) ) } _duplicateScene() { this.cancelOnDestroy( this.yp.ngrxStore.select(store => store.appDb.uiState.scene.sceneSelected) .take(1) .subscribe(scene_id => { var scenePlayerData = this.rp.getScenePlayerdata(scene_id); this.createScene(scenePlayerData, true, ''); this.toastr.info('scene duplicated and is available in scene list'); }) ) } /** Create a new scene based on player_data and strip injected IDs if arged @method createScene @param {String} i_scenePlayerData @optional {Boolean} i_stripIDs @optional {Boolean} i_loadScene @optional {String} i_mimeType @optional {String} i_name **/ private createScene(i_scenePlayerData, i_stripIDs, i_mimeType, i_name?) { if (i_stripIDs) i_scenePlayerData = this.rp.stripPlayersID(i_scenePlayerData); var sceneId = this.rp.createScene(i_scenePlayerData, i_mimeType, i_name); this.rp.reduxCommit(); } private save() { con('saving...'); this.rp.save((result) => { if (result.status == true) { bootbox.alert('saved'); } else { alert(JSON.stringify(result)); } }); } _onSceneSelected(i_uiState: IUiState) { this.yp.ngrxStore.dispatch(({type: ACTION_UISTATE_UPDATE, payload: i_uiState})) } _newScene() { this.sceneCreate.emit(); var uiState: IUiState = {uiSideProps: SideProps.miniDashboard} this.yp.ngrxStore.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})) this.slideToCampaignName.emit(); } destroy() { } } ================================================ FILE: src/app/scenes/scene-props-manager.ts ================================================ import {ChangeDetectionStrategy, Component} from "@angular/core"; import {Compbaser} from "ng-mslib"; import {Observable} from "rxjs"; import {ACTION_UISTATE_UPDATE, SideProps} from "../../store/actions/appdb.actions"; import {YellowPepperService} from "../../services/yellowpepper.service"; import {BlockService, IBlockData, ISceneData} from "../blocks/block-service"; import {IUiState} from "../../store/store.data"; import * as _ from 'lodash'; import {CommBroker} from "../../services/CommBroker"; @Component({ selector: 'scene-props-manager', // changeDetection: ChangeDetectionStrategy.OnPush, styles: [` ul { padding: 0 } `], template: ` {{me}}
        scene editor
      `, }) export class ScenePropsManager extends Compbaser { m_blockData: IBlockData; constructor(private yp: YellowPepperService, private bs: BlockService, private commBroker: CommBroker) { super(); this.m_sideProps$ = this.yp.ngrxStore.select(store => store.appDb.uiState.uiSideProps); this.cancelOnDestroy( // this.commBroker.onEvent('SCENE_ITEM_REMOVE') .subscribe(() => { var uiState: IUiState = {uiSideProps: SideProps.miniDashboard} this.yp.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})) }) ) this.cancelOnDestroy( // this.yp.listenSceneSelected(true) .mergeMap((i_sceneData: ISceneData) => { return this.bs.getBlockDataInScene(i_sceneData); }).subscribe((i_blockData) => { this.m_blockData = i_blockData; }, (e) => console.error(e)) ) } m_sidePropsEnum = SideProps; m_sideProps$: Observable; ngOnInit() { } destroy() { } } ================================================ FILE: src/app/scenes/scene-toolbar.ts ================================================ import {Component, ChangeDetectionStrategy, AfterViewInit, Output, EventEmitter, Input, ChangeDetectorRef} from "@angular/core"; import {Compbaser} from "ng-mslib"; import {YellowPepperService} from "../../services/yellowpepper.service"; import {FormBuilder, FormGroup} from "@angular/forms"; import * as _ from 'lodash'; import {PlayerDataModelExt} from "../../store/model/msdb-models-extended"; import {RedPepperService} from "../../services/redpepper.service"; import {timeout} from "../../decorators/timeout-decorator"; @Component({ selector: 'scene-toolbar', changeDetection: ChangeDetectionStrategy.OnPush, template: ` {{me}}
      `, }) export class SceneToolbar extends Compbaser { m_formInputs = {}; m_contGroup: FormGroup; m_blocks = []; constructor(private fb: FormBuilder, private cd: ChangeDetectorRef) { super(); this.m_contGroup = fb.group({ 'blocks': [] }); } @Input() set blocks(i_blocks) { this.m_blocks = i_blocks; } @Output() onToolbarAction: EventEmitter = new EventEmitter(); @Output() onItemSelected: EventEmitter = new EventEmitter(); @timeout(100) _goBack(){ this.onToolbarAction.emit('back') } _onBlockSelected(event) { this.onItemSelected.emit(event.target.value); } ngOnInit() { } destroy() { } } ================================================ FILE: src/app/scenes/scenes-navigation.ts ================================================ import {ChangeDetectionStrategy, Component} from "@angular/core"; import {Compbaser} from "ng-mslib"; import {BlockService} from "../blocks/block-service"; import {BlockFactoryService} from "../../services/block-factory-service"; import {PLACEMENT_SCENE} from "../../interfaces/Consts"; import {animate, state, style, transition, trigger} from "@angular/animations"; import {AppdbAction, AuthenticateFlags} from "../../store/actions/appdb.actions"; import {YellowPepperService} from "../../services/yellowpepper.service"; @Component({ changeDetection: ChangeDetectionStrategy.OnPush, host: { '[@routeAnimation]': 'true', '[style.display]': "'block'" }, providers: [BlockService, BlockFactoryService, { provide: "BLOCK_PLACEMENT", useValue: PLACEMENT_SCENE }], animations: [ trigger('routeAnimation', [ state('*', style({opacity: 1})), transition('void => *', [ style({opacity: 0}), animate(333) ]), transition('* => void', animate(333, style({opacity: 0}))) ]) ], template: ` scene-navigation
      ` }) export class ScenesNavigation extends Compbaser { userModel$; m_AuthenticateFlags = AuthenticateFlags; constructor(private yp: YellowPepperService) { super(); this.userModel$ = this.yp.listenUserModel(); } } ================================================ FILE: src/app/scenes/scenes.ts ================================================ import {ChangeDetectionStrategy, Component, ViewChild} from "@angular/core"; import {Compbaser} from "ng-mslib"; import {ACTION_UISTATE_UPDATE, SideProps} from "../../store/actions/appdb.actions"; import {IUiState} from "../../store/store.data"; import {YellowPepperService} from "../../services/yellowpepper.service"; import {RedPepperService} from "../../services/redpepper.service"; import {PLACEMENT_SCENE} from "../../interfaces/Consts"; import {ISliderItemData, Slideritem} from "../../comps/sliderpanel/Slideritem"; @Component({ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'scenes', template: ` {{me}} ` }) export class Scenes extends Compbaser { private m_placement = PLACEMENT_SCENE; constructor(private yp: YellowPepperService, private rp: RedPepperService) { super(); var uiState: IUiState = {uiSideProps: SideProps.miniDashboard} this.yp.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})) this.cancelOnDestroy( // this.yp.listenLocationMapLoad() .subscribe((v) => { if (v){ this.sliderSceneCreator.slideTo('locationMap','right') } }, (e) => console.error(e)) ) } @ViewChild('sliderSceneCreator') sliderSceneCreator:Slideritem; _onLocationMapClosed(){ this.sliderSceneCreator.slideTo('sceneEditor','left') } _onSlideChange(event: ISliderItemData) { if (event.direction == 'left' && event.to == 'sceneList') { var uiState:IUiState = { uiSideProps: SideProps.miniDashboard, scene: {sceneSelected: -1} } return this.yp.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})) } // if (event.direction == 'right' && event.to == 'campaignEditor') // return this._createCampaign(); } } ================================================ FILE: src/app/settings/index.ts ================================================ import {NgModule} from "@angular/core"; import {CommonModule} from "@angular/common"; import {RouterModule} from "@angular/router"; import {SettingsNavigation} from "./settings-navigation"; import {DropdownModule as DropdownModulePrime} from "primeng/primeng"; import {SharedModule} from "../../modules/shared.module"; import {Twofactor} from "./twofactor"; export const LAZY_ROUTES = [ {path: ':folder', component: SettingsNavigation}, {path: ':folder/:id', component: SettingsNavigation}, {path: '**', component: SettingsNavigation} ]; @NgModule({ imports: [DropdownModulePrime, SharedModule, CommonModule, RouterModule.forChild(LAZY_ROUTES)], declarations: [SettingsNavigation, Twofactor] }) export class SettingsLazyModule { } ================================================ FILE: src/app/settings/settings-navigation.ts ================================================ import {ChangeDetectionStrategy, Component} from "@angular/core"; import {Compbaser} from "ng-mslib"; import {animate, state, style, transition, trigger} from "@angular/animations"; @Component({ changeDetection: ChangeDetectionStrategy.OnPush, host: { '[@routeAnimation]': 'true', '[style.display]': "'block'" }, animations: [ trigger('routeAnimation', [ state('*', style({opacity: 1})), transition('void => *', [ style({opacity: 0}), animate(333) ]), transition('* => void', animate(333, style({opacity: 0}))) ]) ], template: ` `, }) export class SettingsNavigation extends Compbaser { constructor() { super(); } destroy() { } } ================================================ FILE: src/app/settings/twofactor.ts ================================================ import {Component, ChangeDetectionStrategy, ElementRef, ViewChild, ChangeDetectorRef} from "@angular/core"; import {FormControl, FormGroup, FormBuilder} from "@angular/forms"; import * as _ from "lodash"; import {LocalStorage} from "../../services/LocalStorage"; import {AppdbAction, AuthenticateFlags} from "../../store/actions/appdb.actions"; import {UserModel} from "../../models/UserModel"; import {Map} from "immutable"; import {EFFECT_TWO_FACTOR_UPDATING} from "../../store/effects/appdb.effects"; import {Compbaser} from "ng-mslib"; import {YellowPepperService} from "../../services/yellowpepper.service"; @Component({ selector: 'Twofactor', changeDetection: ChangeDetectionStrategy.OnPush, template: `
      general properties
      • Two factor login with Google Authenticator
      `, styles: [`.material-switch { position: relative; padding-top: 10px; }`] }) export class Twofactor extends Compbaser { constructor(private fb: FormBuilder, private localStorage: LocalStorage, private el: ElementRef, private cd: ChangeDetectorRef, private appdbAction: AppdbAction, private yp: YellowPepperService) { super(); this.contGroup = fb.group({ 'TwofactorCont': [''] }); _.forEach(this.contGroup.controls, (value, key: string) => { this.formInputs[key] = this.contGroup.controls[key] as FormControl; }) this.yp.ngrxStore.select(store => store.appDb.userModel).subscribe((userModel: UserModel) => { this.twoFactorStatus = userModel.getTwoFactorRequired(); this.changeTwoFactorStatus(); }, (e) => { console.error(e) }) this.renderFormInputs(); this.listenEvents(); } @ViewChild('activate') activateToken; twoFactorStatus: boolean; contGroup: FormGroup; formInputs = {}; private listenEvents() { this.cancelOnDestroy( this.yp.ngrxStore.select(store => store.appDb.appAuthStatus).skip(1).subscribe((i_authStatus: Map) => { let authStatus: AuthenticateFlags = i_authStatus.get('authStatus') switch (authStatus) { case AuthenticateFlags.TWO_FACTOR_UPDATE_PASS: { bootbox.alert('Congratulations, activated'); break; } case AuthenticateFlags.TWO_FACTOR_UPDATE_FAIL: { bootbox.alert('wrong token entered'); break; } } }, (e) => { console.error(e) })) } private changeTwoFactorStatus() { if (this.twoFactorStatus) { this.twoFactorStatus = true; this.removeQrCode(); this.cd.markForCheck(); this.localStorage.removeItem('remember_me_studioweb'); this.renderFormInputs(); } else { // this.removeQrCode(); this.cd.markForCheck(); } } private onActivate() { if (this.activateToken.nativeElement.value.length < 6) return bootbox.alert('token is too short'); this.yp.ngrxStore.dispatch({ type: EFFECT_TWO_FACTOR_UPDATING, payload: { token: this.activateToken.nativeElement.value, enable: true } }) } private addQrCode() { this.removeQrCode(); this.appdbAction.getQrCodeTwoFactor().take(1).subscribe((qrCode) => { this.removeQrCode(); jQuery(this.el.nativeElement).append(qrCode); var svg = jQuery(this.el.nativeElement).find('svg').get(0); // var w = svg.width.baseVal.value; // var h = svg.height.baseVal.value; svg.setAttribute('viewBox', '0 0 ' + 100 + ' ' + 100); svg.setAttribute('width', '40%'); // svg.setAttribute('height', '100%'); }, (e) => console.error(e)); } private removeQrCode() { jQuery(this.el.nativeElement).find('svg').remove(); } private onChangeStatus(i_twoFactorStatus: boolean) { this.twoFactorStatus = i_twoFactorStatus; if (this.twoFactorStatus) { this.removeQrCode(); } else { this.addQrCode(); bootbox.alert('Token removed, be sure to delete the entry now from Google Authenticator as it is no longer valid'); } } private renderFormInputs() { this.formInputs['TwofactorCont'].setValue(this.twoFactorStatus); if (this.twoFactorStatus) { this.removeQrCode(); } else { this.addQrCode(); } } } ================================================ FILE: src/app/stations/index.ts ================================================ import {NgModule} from "@angular/core"; import {CommonModule} from "@angular/common"; import {RouterModule} from "@angular/router"; import {StationsNavigation} from "./stations-navigation"; import {DropdownModule as DropdownModulePrime} from "primeng/primeng"; import {SharedModule} from "../../modules/shared.module"; import {Stations} from "./stations"; import {StationsList} from "./stations-list"; import {StationsPropsManager} from "./stations-props-manager"; export const LAZY_ROUTES = [ {path: ':folder', component: StationsNavigation}, {path: ':folder/:id', component: StationsNavigation}, {path: '**', component: StationsNavigation} ]; @NgModule({ imports: [DropdownModulePrime, SharedModule, CommonModule, RouterModule.forChild(LAZY_ROUTES)], declarations: [StationsNavigation, Stations, StationsList, StationsPropsManager] }) export class StationsLazyModule { } ================================================ FILE: src/app/stations/stations-list.ts ================================================ import {ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, EventEmitter, Input, Output} from "@angular/core"; import {Compbaser} from "ng-mslib"; import {List} from "immutable"; import {BlockService} from "../blocks/block-service"; import {StationModel} from "../../models/StationModel"; import {timeout} from "../../decorators/timeout-decorator"; @Component({ selector: 'stations-list', changeDetection: ChangeDetectionStrategy.OnPush, styles: [` input[type="checkbox"] { transform: scale(2, 2); position: relative; top: -40px; } .green { color: green; } .red { color: red; } .yellow { color: yellow; } span { background-color: transparent; -webkit-text-stroke: 1px black; text-shadow: 1px 1px 0 #000, -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000; } `], template: ` {{me}} `, }) export class StationsList extends Compbaser { selectedIdx = -1; m_stations: List; m_checked = {}; constructor(private bs: BlockService, private el: ElementRef, private cd:ChangeDetectorRef) { super(); } @Input() set stations(i_stations: List) { this.m_stations = i_stations; } @Input() filter; @Output() onSelected: EventEmitter = new EventEmitter(); @Output() onMultiSelected: EventEmitter = new EventEmitter(); @timeout() _onCheckedChange(station: StationModel) { if (!this.m_checked[station.id]){ this.m_checked[station.id] = true; } else { this.m_checked[station.id] = !this.m_checked[station.id]; } this.cd.markForCheck(); this.onMultiSelected.emit(this.m_checked) } _onSelected(event: MouseEvent, i_station: StationModel, index) { this.selectedIdx = index; this.onSelected.emit(i_station) // this.cd.markForCheck(); // this.m_selected = i_resource; } resetSelection() { this.selectedIdx = -1; } ngOnInit() { } destroy() { } } ================================================ FILE: src/app/stations/stations-navigation.ts ================================================ import {ChangeDetectionStrategy, Component} from "@angular/core"; import {Compbaser} from "ng-mslib"; import {animate, state, style, transition, trigger} from "@angular/animations"; import {BlockService} from "../blocks/block-service"; @Component({ changeDetection: ChangeDetectionStrategy.OnPush, host: { '[@routeAnimation]': 'true', '[style.display]': "'block'" }, providers: [BlockService, { provide: "BLOCK_PLACEMENT", useValue: '' } ], animations: [ trigger('routeAnimation', [ state('*', style({opacity: 1})), transition('void => *', [ style({opacity: 0}), animate(333) ]), transition('* => void', animate(333, style({opacity: 0}))) ]) ], template: ` station-navigation ` }) export class StationsNavigation extends Compbaser { constructor() { super(); } ngOnInit() { } destroy() { } } ================================================ FILE: src/app/stations/stations-props-manager.html ================================================ {{me}}




        • Name : {{m_selectedStation.name}}
        • Station id: : {{m_selectedStation.id}}
        • OS : {{m_selectedStation.os}}
        • AIR : {{m_selectedStation.airVersion}}
        • App version: : {{m_selectedStation.appVersion}}
        • Peak memory: : {{m_selectedStation.peakMemory}}
        • Total memory: : {{m_selectedStation.totalMemory}}
        • Running: : {{m_selectedStation.runningTime}}
        • Watchdog: : {{m_selectedStation.watchDogConnection == "1" ? 'ON' : 'OFF'}}
        • Last update: : {{m_selectedStation.lastUpdate}}



        • ip address
        • port
      ================================================ FILE: src/app/stations/stations-props-manager.ts ================================================ import {ChangeDetectorRef, Component, ViewChild} from "@angular/core"; import {Compbaser} from "ng-mslib"; import {Observable} from "rxjs"; import {ACTION_LIVELOG_UPDATE, ACTION_UISTATE_UPDATE, SideProps} from "../../store/actions/appdb.actions"; import {YellowPepperService} from "../../services/yellowpepper.service"; import {RedPepperService} from "../../services/redpepper.service"; import {StationModel} from "../../models/StationModel"; import {animate, state, style, transition, trigger} from "@angular/animations"; import {FormBuilder, FormGroup} from "@angular/forms"; import {timeout} from "../../decorators/timeout-decorator"; import {BranchStationsModelExt, CampaignsModelExt} from "../../store/model/msdb-models-extended"; import {List} from "immutable"; import {LazyImage} from "../../comps/lazy-image/lazy-image"; import {ToastsManager} from "ng2-toastr"; import {LiveLogModel} from "../../models/live-log-model"; import * as _ from 'lodash'; import {IUiState} from "../../store/store.data"; import {MainAppShowStateEnum} from "../app-component"; import {PreviewModeEnum} from "../live-preview/live-preview"; @Component({ selector: 'stations-props-manager', host: { '(input-blur)': 'saveToStore($event)' }, animations: [ trigger('toggleState', [ state('true', style({})), state('false', style({maxHeight: 0, padding: 0, display: 'none'})), // transition transition('* => *', animate('300ms')), ]) ], styles: [` ul { padding: 0px; } #stationcontrol { width: 100%; } #stationcontrol button { width: 25%; } /*.loading {*/ /*float: left;*/ /*position: relative;*/ /*top: -106px;*/ /*left: calc((100% / 2) - 30px);*/ /*}*/ /*img {*/ /*float: left;*/ /*position: relative;*/ /*width: 210px;*/ /*top: -140px;*/ /*left: calc((100% / 2) - 109px);*/ /*}*/ /*#propWrap {*/ /*position: fixed;*/ /*padding-left: 20px;*/ /*}*/ `], templateUrl: './stations-props-manager.html' }) export class StationsPropsManager extends Compbaser { contGroup: FormGroup; m_sideProps$: Observable; m_sidePropsEnum = SideProps; m_uiUserFocusItem$: Observable; m_selectedStation: StationModel; m_selectedBranchStation: BranchStationsModelExt; m_selectedCampaignId = -1; m_loading = false; m_totalStationsSelected = []; m_snapPath = ''; shouldToggle = true; m_disabled = true; m_port = ''; m_campaigns: List; m_ip = ''; m_inFocus = false; m_multiOptions = ['change campaigns, no save','change campaigns & save','change campaigns, save & restart station'] constructor(private toast: ToastsManager, private fb: FormBuilder, private yp: YellowPepperService, private rp: RedPepperService, private cd: ChangeDetectorRef) { super(); this.m_uiUserFocusItem$ = this.yp.ngrxStore.select(store => store.appDb.uiState.uiSideProps); this.m_sideProps$ = this.yp.ngrxStore.select(store => store.appDb.uiState.uiSideProps); this.contGroup = fb.group({ 'm_campaignsControl': [''], 'm_campaignsMultiControl': [''], 'm_stationName': [''], 'm_eventValue': [''], 'm_enableLan': [], 'm_ip': [], 'm_port': [] }); this.cancelOnDestroy( // this.yp.getMultiSelectedStations() .map(i_stations => { var jStationsData = i_stations.toJSON(); this.m_totalStationsSelected = []; _.forEach(jStationsData, (i_selected, i_stationId) => { if (i_selected) this.m_totalStationsSelected.push(i_stationId) }) return this.m_totalStationsSelected; }) .subscribe((i_stationsIds) => { console.log(i_stationsIds); }, (e) => console.error(e)) ) this.cancelOnDestroy( // this.yp.getCampaigns() .subscribe((i_campaigns: List) => { this.m_campaigns = i_campaigns; }, (e) => console.error(e)) ) this.cancelOnDestroy( // this.yp.listenStationBranchSelected() .map((i_station: StationModel) => { this.m_snapPath = ''; if (this.m_selectedStation && this.m_selectedStation.id != i_station.id) this._resetSnapshotSelection(); this.m_selectedStation = i_station; this.m_disabled = this.m_selectedStation.connection == "0"; return this.m_selectedStation.id; }) .mergeMap(i_station_id => { return this.yp.getStationCampaignID(i_station_id) .map((i_campaign_id) => { return {i_station_id, i_campaign_id}; }) }) .mergeMap(({i_station_id, i_campaign_id}) => { this.m_selectedCampaignId = i_campaign_id; return this.yp.listenStationRecord(i_station_id) }) .subscribe((i_branchStationsModel) => { this.m_selectedBranchStation = i_branchStationsModel; this._render() }, (e) => console.error(e)) ) } @ViewChild(LazyImage) lazyImage: LazyImage; loadImage(imagePath: string): Observable { return Observable .create(observer => { const img = new Image(); try { img.src = imagePath; img.onload = () => { observer.next(imagePath); observer.complete(); }; img.onerror = err => { observer.error(null); }; } catch (e) { console.log('some error ' + e); } }); } _resetSnapshotSelection() { if (this.lazyImage) this.lazyImage.resetToDefault(); } _onMultiChangeCampaign(){ this.m_totalStationsSelected.forEach((i_stationId)=>{ this.rp.setStationCampaignID(i_stationId, this.contGroup.value.m_campaignsControl); }); switch (this.contGroup.value.m_campaignsMultiControl) { case 'change campaigns, no save': { break; } case 'change campaigns & save': { let uiState: IUiState = {mainAppState: MainAppShowStateEnum.SAVE, previewMode: PreviewModeEnum.NONE} this.yp.ngrxStore.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})) break; } case 'change campaigns, save & restart station': { let uiState: IUiState = {mainAppState: MainAppShowStateEnum.SAVE, previewMode: PreviewModeEnum.NONE} this.yp.ngrxStore.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})) this.yp.listenMainAppState() .skip(1) .take(1) .subscribe(i_status => { if (i_status == MainAppShowStateEnum.SAVED) { this.m_totalStationsSelected.forEach((i_stationId)=>{ this.rp.sendCommand('rebootPlayer', i_stationId, () => { // this.rp.sendCommand('rebootPlayer', -1, () => { }); }); } }) break; } } this.rp.reduxCommit(); // bootbox.alert('next, restart selected stations to have them play your newly selected campaign'); } _render() { if (!this.m_selectedBranchStation) return; this.contGroup.controls.m_campaignsControl.setValue(this.m_selectedCampaignId); this.contGroup.controls.m_enableLan.setValue(this.m_selectedBranchStation.getLanEnabled); if (this.m_inFocus) return; this.contGroup.controls.m_stationName.setValue(this.m_selectedBranchStation.getStationName()); this.contGroup.controls.m_ip.setValue(this.m_selectedBranchStation.getLanServerIp()); this.contGroup.controls.m_port.setValue(this.m_selectedBranchStation.getLanServerPort()); } _onFocus(i_value) { this.m_inFocus = i_value; } _onStationRename() { this.toast.info('Station name will apply a few minutes after you save Studio changes, click [Save]'); } _onCommand(i_command) { this.yp.dispatch(({type: ACTION_LIVELOG_UPDATE, payload: new LiveLogModel({event: 'send station event ' + i_command})})); switch (i_command) { case 'play': { this.rp.sendCommand('start', this.m_selectedStation.id, () => { }); break; } case 'stop': { this.rp.sendCommand('stop', this.m_selectedStation.id, () => { }); break; } case 'snap': { this._takeSnapshot(); break; } case 'off': { this.rp.sendCommand('rebootPlayer', this.m_selectedStation.id, () => { }); break; } } } _onLoaded() { console.log('img loaded'); } _onError() { console.log('img error'); } _onCompleted() { console.log('img completed'); } _takeSnapshot() { var d = new Date().getTime(); this.m_snapPath = ''; this.m_loading = true; var path = this.rp.sendSnapshot(d, 0.2, this.m_selectedStation.id, () => { this.m_snapPath = path; this.lazyImage.urls = [path]; }); setTimeout(() => { this.loadImage(path) .subscribe(v => { // console.log(v); }) }, 100) setTimeout(() => { this.m_loading = false; this.m_snapPath = path; // console.log('loading image from ' + this.m_snapPath); }, 100); } _onSendEvent() { this.shouldToggle != this.shouldToggle; this.rp.sendEvent(this.contGroup.controls.m_eventValue.value, this.m_selectedStation.id, function () { }); this.yp.dispatch(({type: ACTION_LIVELOG_UPDATE, payload: new LiveLogModel({event: 'send station event ' + this.contGroup.controls.m_eventValue.value})})); } @timeout() private saveToStore() { // console.log(this.contGroup.status + ' ' + JSON.stringify(this.ngmslibService.cleanCharForXml(this.contGroup.value))); if (this.contGroup.status != 'VALID') return; if (this.contGroup.value.m_ip.match(/\b((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.|$)){4}\b/)) this.rp.setStationRecordValue(this.m_selectedStation.id, 'lan_server_ip', this.contGroup.value.m_ip); this.rp.setStationCampaignID(this.m_selectedStation.id, this.contGroup.value.m_campaignsControl); this.rp.setStationRecordValue(this.m_selectedStation.id, 'lan_server_enabled', this.contGroup.value.m_enableLan == true ? 'True' : 'False'); this.rp.setStationRecordValue(this.m_selectedStation.id, 'lan_server_port', this.contGroup.value.m_port); this.rp.setStationName(this.m_selectedStation.id, this.contGroup.value.m_stationName); this.rp.reduxCommit(); this.cd.markForCheck(); } destroy() { } } ================================================ FILE: src/app/stations/stations.ts ================================================ import {ChangeDetectionStrategy, Component} from "@angular/core"; import {YellowPepperService} from "../../services/yellowpepper.service"; import {RedPepperService} from "../../services/redpepper.service"; import {ResourcesModel} from "../../store/imsdb.interfaces_auto"; import {List, Map} from "immutable"; import {Observable} from "rxjs"; import {Compbaser} from "ng-mslib"; import {IUiState} from "../../store/store.data"; import {ACTION_UISTATE_UPDATE, SideProps} from "../../store/actions/appdb.actions"; import {BlockService} from "../blocks/block-service"; import {MainAppShowStateEnum} from "../app-component"; import {EFFECT_LOAD_STATIONS} from "../../store/effects/appdb.effects"; import {StationModel} from "../../models/StationModel"; import * as _ from 'lodash'; @Component({ // changeDetection: ChangeDetectionStrategy.OnPush, selector: 'stations', template: ` {{me}}
        `, styles: [` /*:host /deep/ vg-player {*/ /*background-color: transparent;*/ /*margin: 30px;*/ /*width: 30%;*/ /*height: calc(100% - 60px);*/ /*}*/ /*:host /deep/ vg-player {*/ /*background-color: transparent;*/ /*margin: 30px;*/ /*width: 30%;*/ /*height: calc(100% - 60px);*/ /*}*/ * { border-radius: 0 !important; } vg-player { background-color: transparent; margin: 30px; width: 30%; height: calc(100% - 60px); } vg-controls { padding: 30px; transition: all 1s; } #resourcesPanel { padding: 10px; } .myFile { position: relative; overflow: hidden; float: left; clear: left; } .myFile input[type="file"] { display: block; position: absolute; top: 0; right: 0; opacity: 0; font-size: 100px; filter: alpha(opacity=0); cursor: pointer; } `] }) export class Stations extends Compbaser { m_filter; m_stationModel: StationModel; m_stationModels$: Observable>; m_loadStationsHandle; constructor(private yp: YellowPepperService, private rp: RedPepperService, private bs: BlockService) { super(); this._loadData(); this.m_stationModels$ = this.yp.listenStations(); this.cancelOnDestroy( // this.yp.listenStationSelected() .subscribe((i_stationModel: StationModel) => { this.m_stationModel = i_stationModel; }, (e) => console.error(e)) ); this.cancelOnDestroy( // this.yp.listenMainAppState() .subscribe((i_value: MainAppShowStateEnum) => { switch (i_value) { case MainAppShowStateEnum.SAVED: { this._loadData(); break; } } }, (e) => console.error(e)) ); } _loadData() { this.yp.ngrxStore.dispatch({type: EFFECT_LOAD_STATIONS, payload: {userData: this.rp.getUserData()}}); if (_.isUndefined(this.m_loadStationsHandle)) { this.m_loadStationsHandle = setInterval(() => { this._loadData(); }, 4000); } } _removeStation() { if (_.isUndefined(this.m_stationModel)) { bootbox.dialog({ message: 'No station selected...', buttons: { danger: { label: 'close', className: "btn-danger", callback: function () { } } } }); return false; } bootbox.confirm('Are you sure you want to uninstall the selected station?', (result) => { if (!result) return; this.yp.dispatch(({type: ACTION_UISTATE_UPDATE, payload: {uiSideProps: SideProps.miniDashboard}})); this.rp.removeStation(this.m_stationModel.id); // this.rp.sendCommand('rebootStation', this.m_stationModel.id, () => {}); this.rp.sendCommand('rebootPlayer', this.m_stationModel.id, () => {}); this.rp.sendCommand('rebootPlayer', this.m_stationModel.id, () => {}); this.rp.sendCommand('rebootPlayer', this.m_stationModel.id, () => {}); var uiState: IUiState = {uiSideProps: SideProps.miniDashboard, stations: {stationSelected: -1}}; this.yp.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})); this.yp.ngrxStore.dispatch(({type: ACTION_UISTATE_UPDATE, payload: {mainAppState: MainAppShowStateEnum.SAVE}})); }); } _onSelected(i_station: StationModel) { const uiState: IUiState = { uiSideProps: SideProps.stationProps, stations: {stationSelected: i_station.id} }; this.yp.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})); } _onMultiSelected(i_stations: any) { const uiState: IUiState = { multiStationSelected: { stations: Map(i_stations) } }; this.yp.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})); } destroy() { clearInterval(this.m_loadStationsHandle); var uiState: IUiState = {stations: {stationSelected: -1}}; this.yp.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})); } } ================================================ FILE: src/app/studiopro/index.ts ================================================ import {NgModule} from "@angular/core"; import {CommonModule} from "@angular/common"; import {RouterModule} from "@angular/router"; import {StudioProNavigation} from "./studiopro-navigation"; import {DropdownModule as DropdownModulePrime} from "primeng/primeng"; import {SharedModule} from "../../modules/shared.module"; export const LAZY_ROUTES = [ {path: ':folder', component: StudioProNavigation}, {path: ':folder/:id', component: StudioProNavigation}, {path: '**', component: StudioProNavigation} ]; @NgModule({ imports: [DropdownModulePrime, SharedModule, CommonModule, RouterModule.forChild(LAZY_ROUTES)], declarations: [StudioProNavigation] }) export class StudioProLazyModule { } ================================================ FILE: src/app/studiopro/pro-upgrade.ts ================================================ import {Component} from "@angular/core"; import {FormBuilder, FormGroup, Validators} from "@angular/forms"; import {Compbaser} from "ng-mslib"; import {YellowPepperService} from "../../services/yellowpepper.service"; import {RedPepperService} from "../../services/redpepper.service"; import {NGValidators} from "ng-validators"; import {equalValueValidator} from "../../Lib"; @Component({ selector: 'pro-upgrade', templateUrl: './pro-upgrades.html', styles: [` input.ng-invalid { border-right: 10px solid red; } .material-switch { position: relative; padding-top: 10px; } .input-group { padding-top: 10px; } i { width: 20px; } `] }) export class ProUpgrade extends Compbaser { formInputs = {}; m_contGroup: FormGroup; constructor(private fb: FormBuilder, private yp: YellowPepperService, private rp: RedPepperService) { super(); this.m_contGroup = this.fb.group({ 'userName': ['', [Validators.required]], 'creditCard': ['', [NGValidators.isCreditCard()]], 'month': ['', [NGValidators.isNumeric()]], 'year': ['', [NGValidators.isNumeric()]], 'cv': ['', [NGValidators.isNumeric()]], 'password': ['', [Validators.required, Validators.minLength(3)]], 'confirmPassword': ['', [Validators.required, Validators.minLength(3)]] }, {validator: equalValueValidator('password', 'confirmPassword')} ); } private showMessage(i_title: string, i_msg: string, i_status: boolean): void { bootbox.hideAll(); bootbox.dialog({ closeButton: true, title: i_title, message: `


        ${i_status == true ? '' : ''}

        ${i_msg}




        ` }); } _detectCardType(number) { var re = { electron: /^(4026|417500|4405|4508|4844|4913|4917)\d+$/, maestro: /^(5018|5020|5038|5612|5893|6304|6759|6761|6762|6763|0604|6390)\d+$/, dankort: /^(5019)\d+$/, interpayment: /^(636)\d+$/, unionpay: /^(62|88)\d+$/, visa: /^4[0-9]{12}(?:[0-9]{3})?$/, mastercard: /^5[1-5][0-9]{14}$/, amex: /^3[47][0-9]{13}$/, diners: /^3(?:0[0-5]|[68][0-9])[0-9]{11}$/, discover: /^6(?:011|5[0-9]{2})[0-9]{12}$/, jcb: /^(?:2131|1800|35\d{3})\d{11}$/ }; if (re.electron.test(number)) { return 'ELECTRON'; } else if (re.maestro.test(number)) { return 'MAESTRO'; } else if (re.dankort.test(number)) { return 'DANKORT'; } else if (re.interpayment.test(number)) { return 'INTERPAYMENT'; } else if (re.unionpay.test(number)) { return 'UNIONPAY'; } else if (re.visa.test(number)) { return 'VISA'; } else if (re.mastercard.test(number)) { return 'MASTERCARD'; } else if (re.amex.test(number)) { return 'AMEX'; } else if (re.diners.test(number)) { return 'DINERS'; } else if (re.discover.test(number)) { return 'DISCOVER'; } else if (re.jcb.test(number)) { return 'JCB'; } else { return undefined; } } _onStartSubscription() { if (this.m_contGroup.status != 'VALID') return bootbox.alert('Please fix the fields marked in red'); const cardType = this._detectCardType(this.m_contGroup.controls.creditCard.value); const userName = this.rp.getUserData().userName; const userPass = this.rp.getUserData().userPass; const year = this.m_contGroup.controls.year.value.length == 2 ? '20' + this.m_contGroup.controls.year.value : this.m_contGroup.controls.year.value; const month = this.m_contGroup.controls.month.value.replace(/^0+/, ''); var card; switch (cardType) { case 'VISA': { card = '0'; break; } case 'MASTERCARD': { card = '1'; break; } case 'DISCOVER': { card = '2'; break; } case 'AMEX': { card = '3'; break; } default: { card = '-1'; } } var url = `https://galaxy.signage.me/WebService/UpgradeToResellerAccount.ashx?userName=${userName}&userPassword=${userPass}&resellerUserName=${this.m_contGroup.controls.userName.value}&resellerPassword=${this.m_contGroup.controls.password.value}&cardType=${card}&cardNumber=${this.m_contGroup.controls.creditCard.value}&expirationMonth=${month}&expirationYear=${year}&securityCode=${this.m_contGroup.controls.cv.value}&callback=?`; $.getJSON(url, (e) => { var msg = ''; console.log('Credit card status ' + e.result); switch (e.result) { case -4: { msg = 'This user is already under a subscription account'; this.showMessage(e.status, msg, false); return; } case -5: { msg = 'Enterprise use name is taken, please pick a different name'; this.showMessage(e.status, msg, false); return; } case -3: { } case -2: { } case -1: { msg = 'Problem with credit card, please use a different card'; this.showMessage(e.status, msg, false); return; } default: { if (e.result > 100) { bootbox.hideAll(); $('.modal').modal('hide'); //var snippet = `Congratulations, be sure to checkout the video tutorial on how to install your newly available components from the mediaSTORE`; var snippet = `Congratulations, be sure to go to the mediaSTORE and install your newly available components...`; this.showMessage('success', snippet, true); return; } else { msg = 'Problem with credit card, please use a different card'; this.showMessage(e.status, msg, false); } } } }).done((e) => { }).fail((e) => { this.showMessage(e.status, 'Could not complete, something went wrong on server side', false); }).always(() => { }); } destroy() { } } ================================================ FILE: src/app/studiopro/pro-upgrades.html ================================================
        target properties {{me}}
        Learn more about Enterprise

        ================================================ FILE: src/app/studiopro/studiopro-navigation.ts ================================================ import {ChangeDetectionStrategy, Component, ViewChild} from "@angular/core"; import {Compbaser} from "ng-mslib"; import {animate, state, style, transition, trigger} from "@angular/animations"; import {ModalComponent} from "ng2-bs3-modal/ng2-bs3-modal"; import {RedPepperService} from "../../services/redpepper.service"; import {YellowPepperService} from "../../services/yellowpepper.service"; import {Observable} from "rxjs/Observable"; import {UserModel} from "../../models/UserModel"; @Component({ changeDetection: ChangeDetectionStrategy.OnPush, styles: [` .dottedHR { height: 6px; width: 1500px; opacity: 0.6; position: relative; border-top: 12px dotted #c1c1c1; padding-bottom: 7px; top: 20px; } .activated { background-color: #428bca; color: white; } .headerPropIcon { position: fixed !important; top: 0px !important; color: #f5f5f5; right: 50px !important; z-index: 1500; background-color: #151515; float: right; border: black; } .whiteFont { color: white; } .pricingContainer { padding-top: 40px; } .price { font-size: 25px; float: left; } .faHeader { font-size: 1.9em !important; color: #bababa; } .faHeader:hover { color: white; } .pricing_header1 { background: none repeat scroll 0% 0% rgb(0, 181, 255); border-radius: 5px 5px 0px 0px; } .pricing_header2 { background: none repeat scroll 0% 0% rgb(0, 121, 171); border-radius: 5px 5px 0px 0px; } .pricing_header3 { background: none repeat scroll 0% 0% rgb(0, 81, 115); border-radius: 5px 5px 0px 0px; } .pricing_headerh2 { text-align: center; line-height: 25px; padding: 15px 0px; margin: 0px; font-size: 1.5em; font-weight: 400; color: white; } `], host: { '[@routeAnimation]': 'true', '[style.display]': "'block'" }, animations: [ trigger('routeAnimation', [ state('*', style({opacity: 1})), transition('void => *', [ style({opacity: 0}), animate(333) ]), transition('* => void', animate(333, style({opacity: 0}))) ]) ], template: `

        Choose the package that's right for you


        StudioLite

        • 100% FREE
        • simple to use interface
        • Campaign manager
        • Timeline management
        • Screen templates
        • Screen editor
        • Station Manager
        • Support 10 components
        • Scene editor (coming)
        • Files: videos/images/flash
        • Run on Tablets and phones
        • Open source (GitHub)

        $0.00

        StudioPro (FREE)

        • 100% FREE
        • Higher learning curve
        • Professional editor
        • Campaign manager
        • Timeline management
        • Channel management
        • Scheduler / sequencer
        • Rich scene editor
        • Support more components
        • Advance Station Manager
        • Runs on Desktop and Web
        • and much more...

        $0.00

        Convert

        StudioEnterprise

        • $99 a month (flat)
        • Everything from Lite & Pro
        • White label / branding
        • Support all components
        • Transitions / Effects
        • Multi user management
        • Advertising manager
        • Access control
        • Unlimited cloud storage
        • Phone support
        • Hardware discounts
        • and much more...

        $99/month

        ` }) export class StudioProNavigation extends Compbaser { @ViewChild(ModalComponent) modal: ModalComponent; subAccount = false; isBrandingDisabled: Observable constructor(private yp: YellowPepperService, private rp: RedPepperService) { super(); this.isBrandingDisabled = this.yp.isBrandingDisabled() } _initCustomer() { if (this.rp.getUserData().resellerID == 1 || this.rp.getUserData().whiteLabel == false) { this.subAccount = false; } else { this.subAccount = true; } } _onConvert(event) { window.open('http://galaxy.mediasignage.com/WebService/signagestudio.aspx?mode=login&v=4&eri=f7bee07a7e79c8efdb961c4d30d20e10c66442110de03d6141', '_blank'); } _onSubscribe(event) { this.modal.open(); } ngOnInit() { this.preventRedirect(true); this._initCustomer(); } destroy() { } } ================================================ FILE: src/app-routes.ts ================================================ import {PreloadAllModules, RouterModule, Routes} from "@angular/router"; import {LoginPanel} from "./comps/entry/LoginPanel"; import {Logout} from "./comps/logout/Logout"; import {AuthService} from "./services/AuthService"; import {AutoLogin} from "./comps/entry/AutoLogin"; import {Dashboard} from "./app/dashboard/dashboard-navigation"; import {Appwrap} from "./app/appwrap"; import {FasterqTerminal} from "./app/fasterq/fasterq-terminal"; const routes: Routes = [ {path: 'index.html', data: {title: 'Login'}, component: AutoLogin}, {path: 'AutoLogin', data: {title: 'Login'}, component: AutoLogin}, {path: 'FasterqTerminal/:id', data: {title: 'FasterqTerminal'}, component: FasterqTerminal}, {path: 'UserLogin', data: {title: 'Login'}, component: LoginPanel}, {path: 'UserLogin/:twoFactor', data: {title: 'Login'}, component: LoginPanel}, {path: 'UserLogin/:twoFactor/:user/:pass', data: {title: 'Login'}, component: LoginPanel}, {path: 'Logout', component: Logout}, {path: '', pathMatch: 'full', redirectTo: '/App1/Dashboard'}, {path: 'studioweb', pathMatch: 'full', redirectTo: '/App1/Dashboard'}, // IE/FF compatibility {path: 'studioweb/index.html', pathMatch: 'full', redirectTo: '/App1/Dashboard'}, // IE 11 { path: 'App1', component: Appwrap, children: [ {path: '', component: Appwrap, canActivate: [AuthService]}, {path: 'Dashboard', component: Dashboard, data: {title: 'Dashboard'}, canActivate: [AuthService]}, {path: 'Campaigns', loadChildren: '../app/campaigns/index#CampaignsLazyModule', data: {title: 'Campaigns'}, canActivate: [AuthService]}, {path: 'Fasterq', loadChildren: '../app/fasterq/index#FasterqLazyModule', data: {title: 'Fasterq'}, canActivate: [AuthService]}, {path: 'Scenes', loadChildren: '../app/scenes/index#ScenesLazyModule', data: {title: 'Scenes'}, canActivate: [AuthService]}, {path: 'Resources', loadChildren: '../app/resources/index#ResourcesLazyModule', data: {title: 'Resources'}, canActivate: [AuthService]}, {path: 'Help', loadChildren: '../app/help/index#HelpLazyModule', data: {title: 'Help'}, canActivate: [AuthService]}, {path: 'Install', loadChildren: '../app/install/index#InstallLazyModule', data: {title: 'Install'}, canActivate: [AuthService]}, {path: 'Settings', loadChildren: '../app/settings/index#SettingsLazyModule', data: {title: 'Settings'}, canActivate: [AuthService]}, {path: 'Stations', loadChildren: '../app/stations/index#StationsLazyModule', data: {title: 'Stations'}, canActivate: [AuthService]}, {path: 'Studiopro', loadChildren: '../app/studiopro/index#StudioProLazyModule', data: {title: 'Studiopro'}, canActivate: [AuthService]}, {path: 'Logout', component: Logout, data: {title: 'Logout'}, canActivate: [AuthService]}, {path: '**', redirectTo: 'Dashboard'} ] } ]; export const routing = RouterModule.forRoot(routes, {enableTracing: false, preloadingStrategy: PreloadAllModules}); ================================================ FILE: src/assets/geojson.json ================================================ { "type": "FeatureCollection", "crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:EPSG::31493" } }, "features": [ { "type": "Feature", "properties": { "cat": "2", "adm1_code": "DEU-1579", "OBJECTID_1": "942", "diss_me": "1579", "adm1_cod_1": "DEU-1579", "iso_3166_2": "DE-", "wikipedia": null, "iso_a2": "DE", "adm0_sr": "6", "name": "Schleswig-Holstein", "name_alt": null, "name_local": null, "type": "Land", "type_en": "State", "code_local": null, "code_hasc": "DE.SH", "note": null, "hasc_maybe": null, "region": null, "region_cod": null, "provnum_ne": "3", "gadm_level": "1", "check_me": "20", "scalerank": "3", "datarank": "3", "abbrev": null, "postal": "SH", "area_sqkm": "0", "sameascity": "-99", "labelrank": "3", "featurecla": "Admin-1 scale rank", "name_len": "18", "mapcolor9": "5", "mapcolor13": "1", "fips": "GM10", "fips_alt": null, "woe_id": "2345490", "woe_label": "Schleswig-Holstein, DE, Germany", "woe_name": "Schleswig-Holstein", "latitude": "54.1315", "longitude": "9.84605", "sov_a3": "DEU", "adm0_a3": "DEU", "adm0_label": "2", "admin": "Germany", "geonunit": "Germany", "gu_a3": "DEU", "gn_id": "2838632", "gn_name": "Land Schleswig-Holstein", "gns_id": "-1858348", "gns_name": "Schleswig-Holstein", "gn_level": "1", "gn_region": null, "gn_a1_code": "DE.10", "region_sub": null, "sub_code": null, "gns_level": "1", "gns_lang": "zho", "gns_adm1": "GM10", "gns_region": null }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 3457361.6953271437, 6064537.3175473278 ], [ 3473831.1032281509, 6066454.707440001 ], [ 3458254.2756455722, 6054378.8800201574 ], [ 3454292.4301971514, 6059770.7031057561 ], [ 3454356.6117983218, 6084731.7034898801 ], [ 3457361.6953271437, 6064537.3175473278 ] ] ], [ [ [ 3588249.9672536696, 5922981.0870051431 ], [ 3578686.4275546414, 5933125.8551333752 ], [ 3577703.2560399352, 5938504.9128987873 ], [ 3580874.49823714, 5945222.0506500695 ], [ 3576943.630466254, 5950819.015340128 ], [ 3578862.6937927571, 5955081.5497373492 ], [ 3572137.6137213707, 5954711.580675493 ], [ 3570371.6708244649, 5950301.8162747147 ], [ 3554853.9405998117, 5940794.9144034721 ], [ 3550830.3006076724, 5944304.1125011407 ], [ 3549069.0712087429, 5937985.4088263968 ], [ 3536429.5426071933, 5944772.404340459 ], [ 3526016.6806122339, 5966561.8734104037 ], [ 3519393.374612669, 5968304.8412422938 ], [ 3512239.2302019894, 5970199.5365770916 ], [ 3500514.2646667375, 5973331.8574014464 ], [ 3489082.291314546, 5989366.3785155555 ], [ 3499819.4056466105, 5988660.6677569058 ], [ 3498517.1762036402, 6001512.9417701392 ], [ 3490893.0391698242, 5999239.2651538216 ], [ 3489548.1277326951, 6013751.9056054596 ], [ 3497601.9580049324, 6020646.9336849805 ], [ 3477324.3183245538, 6015942.557420373 ], [ 3477174.6819570824, 6030585.0076404512 ], [ 3490956.4413303267, 6031361.2477700273 ], [ 3489931.1912721554, 6043152.005285589 ], [ 3499099.8590448922, 6044205.2552816924 ], [ 3479952.2035088921, 6067179.7789988136 ], [ 3478240.9941294421, 6085109.2448926223 ], [ 3488727.8879079837, 6086138.8696871242 ], [ 3529021.0772972321, 6075584.8843934499 ], [ 3537230.3970351499, 6081868.5508470545 ], [ 3553025.9213487152, 6069833.4444824299 ], [ 3558853.4378033783, 6073742.2482397221 ], [ 3566668.0124271861, 6060682.9319272507 ], [ 3566001.9292511554, 6046527.0230161548 ], [ 3557272.6619548667, 6037087.2706446396 ], [ 3572862.5690058409, 6041055.569442112 ], [ 3615633.3602376087, 6021826.8104329733 ], [ 3636653.86664266, 6031065.0210786732 ], [ 3630020.1589864371, 6039286.8314314149 ], [ 3637283.7395728445, 6047018.1667916644 ], [ 3650216.1204284448, 6033819.9766713399 ], [ 3635081.4884479907, 6025376.2926454805 ], [ 3634893.8065267834, 6007738.1217093375 ], [ 3614788.5216621999, 5991603.5560405627 ], [ 3625082.7900508232, 5982662.9355227295 ], [ 3625050.7337689409, 5979329.7269153669 ], [ 3628805.8970855772, 5977389.3176590335 ], [ 3626024.1404369744, 5976110.5464679133 ], [ 3623841.3356328616, 5977945.1403433578 ], [ 3615980.348949628, 5971721.3096266175 ], [ 3615255.5018173298, 5969260.8856087131 ], [ 3616689.3777262997, 5963752.7563475929 ], [ 3616154.1812646016, 5958546.736402004 ], [ 3619138.4221999128, 5958008.8936667377 ], [ 3623189.4327053195, 5953513.3434927408 ], [ 3628029.4118883419, 5951954.0380383944 ], [ 3629467.0720780403, 5938790.4934651023 ], [ 3621108.1006804346, 5938712.2612650106 ], [ 3620734.103808559, 5932842.580161592 ], [ 3608645.9904428972, 5924805.5896682888 ], [ 3606560.2346579311, 5916586.2707823943 ], [ 3601710.25338207, 5916543.2227652604 ], [ 3588249.9672536696, 5922981.0870051431 ] ] ] ] } }, { "type": "Feature", "properties": { "cat": "6", "adm1_code": "DEU-1599", "OBJECTID_1": "2334", "diss_me": "1599", "adm1_cod_1": "DEU-1599", "iso_3166_2": "DE-", "wikipedia": null, "iso_a2": "DE", "adm0_sr": "1", "name": "Berlin", "name_alt": null, "name_local": null, "type": "Land", "type_en": "State", "code_local": null, "code_hasc": "DE.BE", "note": null, "hasc_maybe": null, "region": null, "region_cod": null, "provnum_ne": "7", "gadm_level": "1", "check_me": "20", "scalerank": "3", "datarank": "3", "abbrev": null, "postal": "BE", "area_sqkm": "0", "sameascity": "9", "labelrank": "9", "featurecla": "Admin-1 scale rank", "name_len": "6", "mapcolor9": "5", "mapcolor13": "1", "fips": "GM16", "fips_alt": null, "woe_id": "2345496", "woe_label": "Berlin, DE, Germany", "woe_name": "Berlin", "latitude": "52.5131", "longitude": "13.4213", "sov_a3": "DEU", "adm0_a3": "DEU", "adm0_label": "2", "admin": "Germany", "geonunit": "Germany", "gu_a3": "DEU", "gn_id": "2950157", "gn_name": "Land Berlin", "gns_id": "-1746445", "gns_name": "Berlin", "gn_level": "1", "gn_region": null, "gn_a1_code": "DE.16", "region_sub": null, "sub_code": null, "gns_level": "1", "gns_lang": "zho", "gns_adm1": "GM16", "gns_region": null }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 3804566.4152815924, 5843865.692704617 ], [ 3804934.6433777702, 5840168.5899853837 ], [ 3809716.679845443, 5835249.9865113907 ], [ 3815207.714945185, 5832907.5054032393 ], [ 3814140.9994099671, 5827006.7575231045 ], [ 3816712.9457131149, 5827478.2731477637 ], [ 3822865.3678980898, 5823472.4171997104 ], [ 3820320.890532007, 5814819.0171839539 ], [ 3816409.7180140801, 5810966.0266671684 ], [ 3815681.7293974063, 5814709.1505647963 ], [ 3803208.3688719897, 5818473.9252682505 ], [ 3801245.3895061575, 5814373.9040043866 ], [ 3792079.8344209795, 5817614.5021004295 ], [ 3782830.3462029067, 5815037.8793154648 ], [ 3778418.5104975295, 5817224.1775639663 ], [ 3780958.9744146601, 5829146.268900374 ], [ 3781589.121700428, 5837662.7537919097 ], [ 3785070.6277251416, 5836354.5125904409 ], [ 3785707.312560183, 5840767.7494266415 ], [ 3791058.6585779274, 5844454.8433719436 ], [ 3796466.9763708967, 5841442.4283941165 ], [ 3802213.9156131558, 5845930.8582165847 ], [ 3804566.4152815924, 5843865.692704617 ] ] ] } }, { "type": "Feature", "properties": { "cat": "10", "adm1_code": "DEU-3488", "OBJECTID_1": "3312", "diss_me": "3488", "adm1_cod_1": "DEU-3488", "iso_3166_2": "DE-", "wikipedia": null, "iso_a2": "DE", "adm0_sr": "5", "name": "Mecklenburg-Vorpommern", "name_alt": "Mecklenburg-West Pomerania", "name_local": null, "type": "Land", "type_en": "State", "code_local": null, "code_hasc": "DE.MV", "note": null, "hasc_maybe": null, "region": null, "region_cod": null, "provnum_ne": "30001", "gadm_level": "1", "check_me": "20", "scalerank": "3", "datarank": "3", "abbrev": null, "postal": "MV", "area_sqkm": "0", "sameascity": "-99", "labelrank": "3", "featurecla": "Admin-1 scale rank", "name_len": "22", "mapcolor9": "5", "mapcolor13": "1", "fips": "GM12", "fips_alt": null, "woe_id": "2345492", "woe_label": "Mecklenburg-Vorpommern, DE, Germany", "woe_name": "Mecklenburg-Vorpommern", "latitude": "53.7528", "longitude": "12.5647", "sov_a3": "DEU", "adm0_a3": "DEU", "adm0_label": "2", "admin": "Germany", "geonunit": "Germany", "gu_a3": "DEU", "gn_id": "2872567", "gn_name": "Land Mecklenburg-Vorpommern", "gns_id": "-1824278", "gns_name": "Mecklenburg-Vorpommern", "gn_level": "1", "gn_region": null, "gn_a1_code": "DE.12", "region_sub": null, "sub_code": null, "gns_level": "1", "gns_lang": "zho", "gns_adm1": "GM12", "gns_region": null }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 3765978.7018767856, 6054368.3792266808 ], [ 3777435.6134433611, 6053854.901303824 ], [ 3782368.591728467, 6059205.16406429 ], [ 3786974.612363453, 6049178.2954727663 ], [ 3791952.7030979348, 6048995.307912183 ], [ 3790982.149199931, 6056628.7616059249 ], [ 3782166.0009250576, 6062203.9922618233 ], [ 3776325.3208203837, 6056130.6553788381 ], [ 3776858.0248962715, 6065718.7575505488 ], [ 3774350.7781503876, 6067627.8759364616 ], [ 3783033.4912099959, 6070747.65958957 ], [ 3783858.2782364748, 6061451.222502646 ], [ 3788686.5434686914, 6058680.8391875532 ], [ 3799870.6040519727, 6060388.0503517305 ], [ 3802580.9076147829, 6055519.6901947297 ], [ 3796496.4780506194, 6047458.1686205128 ], [ 3799128.0083435532, 6041146.6738467803 ], [ 3809821.4347207304, 6034166.5356042059 ], [ 3792493.4405632792, 6033025.5237082997 ], [ 3784186.8438639143, 6023359.0631650677 ], [ 3778032.6959456238, 6022255.4613743341 ], [ 3767578.4113822468, 6030737.7075944003 ], [ 3769494.9703409448, 6035222.8034836464 ], [ 3776065.5893489686, 6036691.2679821067 ], [ 3769692.2806164096, 6040765.0543835247 ], [ 3776060.2697977475, 6045791.7914314782 ], [ 3765978.7018767856, 6054368.3792266808 ] ] ], [ [ [ 3858958.4675184065, 5924746.6905517764 ], [ 3850096.8967003031, 5915716.2145906379 ], [ 3840283.6437742342, 5916498.3542627199 ], [ 3848470.2881636913, 5929329.4855594682 ], [ 3848307.6588879293, 5934358.7847815678 ], [ 3826929.8373607565, 5932832.0742536131 ], [ 3822905.9499626607, 5941589.9501121677 ], [ 3812528.0683271405, 5937242.9811624223 ], [ 3801803.8451427119, 5927759.7186010573 ], [ 3793795.8883584146, 5913641.643195576 ], [ 3777112.0423970157, 5910922.5465983907 ], [ 3767480.0978048071, 5900151.2545123501 ], [ 3763845.3670365461, 5903055.9980064519 ], [ 3728791.1339836149, 5909381.6571826441 ], [ 3712076.1365221734, 5919807.300273018 ], [ 3703941.6063928884, 5918019.7483437611 ], [ 3700755.786590965, 5911583.242706717 ], [ 3683284.378220486, 5901519.8736129059 ], [ 3680200.6158619821, 5905056.4653345328 ], [ 3669396.6476244526, 5899831.1085491451 ], [ 3670935.6606098032, 5893318.19994 ], [ 3660311.8800712302, 5888738.7558573624 ], [ 3652231.3320433423, 5889511.3574547973 ], [ 3643302.4959958429, 5895374.819350563 ], [ 3639785.0696396269, 5892100.1719632307 ], [ 3606560.2346579311, 5916586.2707823943 ], [ 3608645.9904428972, 5924805.5896682888 ], [ 3620734.103808559, 5932842.580161592 ], [ 3621108.1006804346, 5938712.2612650106 ], [ 3629467.0720780403, 5938790.4934651023 ], [ 3628029.4118883419, 5951954.0380383944 ], [ 3623189.4327053195, 5953513.3434927408 ], [ 3619138.4221999128, 5958008.8936667377 ], [ 3616154.1812646016, 5958546.736402004 ], [ 3616689.3777262997, 5963752.7563475929 ], [ 3615255.5018173298, 5969260.8856087131 ], [ 3615980.348949628, 5971721.3096266175 ], [ 3623841.3356328616, 5977945.1403433578 ], [ 3626024.1404369744, 5976110.5464679133 ], [ 3628805.8970855772, 5977389.3176590335 ], [ 3625050.7337689409, 5979329.7269153669 ], [ 3625082.7900508232, 5982662.9355227295 ], [ 3642541.5708868173, 5989491.9165221117 ], [ 3647245.3665982271, 5981531.2007794483 ], [ 3653174.0782078085, 5983522.0033757631 ], [ 3661492.4592031753, 5977652.8750231266 ], [ 3655820.8028928475, 5985889.7743025105 ], [ 3663350.6954817097, 5988433.0763060879 ], [ 3678217.7285471754, 6006380.5903663486 ], [ 3702468.7811940378, 6009068.5689904485 ], [ 3707700.6256526238, 6017070.9558728579 ], [ 3717497.3602633895, 6024278.7813600069 ], [ 3726955.9006420602, 6043652.8382368414 ], [ 3732879.1080247746, 6041514.9281786047 ], [ 3751573.5256050769, 6042181.2297516 ], [ 3751194.3818650926, 6038592.5262381751 ], [ 3739667.4854723434, 6039851.6737545151 ], [ 3729144.6259276099, 6032464.1750187948 ], [ 3724343.3351603751, 6034435.6926388405 ], [ 3718792.9118886008, 6025039.0342627419 ], [ 3722354.2783105765, 6018654.7492071465 ], [ 3726227.3404115206, 6027813.5854513822 ], [ 3738785.27984973, 6035167.4210286662 ], [ 3751847.0466833659, 6032895.4936694633 ], [ 3761685.9547273475, 6041061.5307539245 ], [ 3767918.6873595146, 6024278.3875305923 ], [ 3779544.7257449995, 6019867.6876431284 ], [ 3791982.7664662367, 6005478.7560929675 ], [ 3792719.4913930758, 6008509.8053144859 ], [ 3806813.9026390165, 6012547.7583060171 ], [ 3813624.1933684391, 6008114.9790483778 ], [ 3814273.5260422458, 6015341.28071272 ], [ 3826245.4298891709, 6004732.7182455854 ], [ 3841931.3593487581, 5991047.874779541 ], [ 3841857.4827590617, 5984285.0268656248 ], [ 3820735.1401745877, 5978971.9858000716 ], [ 3823658.5330290105, 6004032.5048946002 ], [ 3812743.7630230263, 5998386.4246438835 ], [ 3813388.7445945316, 6005505.544981841 ], [ 3810809.0447827573, 5999782.8875678834 ], [ 3821994.1922710077, 5990116.933645878 ], [ 3816749.3601884744, 5982489.2711326405 ], [ 3828446.727039618, 5971682.0152686974 ], [ 3836719.1779422434, 5968847.9592408147 ], [ 3844529.3000683878, 5971131.5817635488 ], [ 3851718.7367773792, 5943706.1401673108 ], [ 3858958.4675184065, 5924746.6905517764 ] ] ] ] } }, { "type": "Feature", "properties": { "cat": "14", "adm1_code": "DEU-1575", "OBJECTID_1": "1518", "diss_me": "1575", "adm1_cod_1": "DEU-1575", "iso_3166_2": "DE-", "wikipedia": null, "iso_a2": "DE", "adm0_sr": "1", "name": "Bremen", "name_alt": null, "name_local": null, "type": "Land", "type_en": "State", "code_local": null, "code_hasc": "DE.HB", "note": null, "hasc_maybe": null, "region": null, "region_cod": null, "provnum_ne": "5", "gadm_level": "1", "check_me": "20", "scalerank": "3", "datarank": "3", "abbrev": null, "postal": "HB", "area_sqkm": "0", "sameascity": "9", "labelrank": "9", "featurecla": "Admin-1 scale rank", "name_len": "6", "mapcolor9": "5", "mapcolor13": "1", "fips": "GM03", "fips_alt": null, "woe_id": "2345483", "woe_label": "Bremen, DE, Germany", "woe_name": "Bremen", "latitude": "53.1211", "longitude": "8.742990000000001", "sov_a3": "DEU", "adm0_a3": "DEU", "adm0_label": "2", "admin": "Germany", "geonunit": "Germany", "gu_a3": "DEU", "gn_id": "2944387", "gn_name": "Bremen", "gns_id": "-1752235", "gns_name": "Bremen", "gn_level": "1", "gn_region": null, "gn_a1_code": "DE.03", "region_sub": null, "sub_code": null, "gns_level": "1", "gns_lang": "zho", "gns_adm1": "GM03", "gns_region": null }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 3470648.2738475171, 5897758.2648433764 ], [ 3482994.3597778352, 5895366.5686806049 ], [ 3494135.47721026, 5889523.4182940461 ], [ 3498339.9930427396, 5889002.5856371522 ], [ 3498170.5207677865, 5879865.4094565529 ], [ 3494265.4796796911, 5876757.5917747803 ], [ 3491011.2500537927, 5879309.938417878 ], [ 3480045.6539797694, 5879745.4698925437 ], [ 3480079.7036048737, 5882942.5270079691 ], [ 3473997.6676363903, 5894121.7778715324 ], [ 3467459.3721810151, 5896352.760481637 ], [ 3470648.2738475171, 5897758.2648433764 ] ] ], [ [ [ 3470633.9099116446, 5931964.1095968718 ], [ 3471219.9106471892, 5934938.2581426231 ], [ 3467778.8065959434, 5942795.0136931287 ], [ 3475199.3643792034, 5941403.5468862616 ], [ 3476062.7607371015, 5931567.9643862806 ], [ 3473640.484591655, 5928668.0521080317 ], [ 3470633.9099116446, 5931964.1095968718 ] ] ] ] } }, { "type": "Feature", "properties": { "cat": "1", "adm1_code": "DEU-1578", "OBJECTID_1": "1520", "diss_me": "1578", "adm1_cod_1": "DEU-1578", "iso_3166_2": "DE-", "wikipedia": null, "iso_a2": "DE", "adm0_sr": "1", "name": "Hamburg", "name_alt": null, "name_local": null, "type": "Land", "type_en": "State", "code_local": null, "code_hasc": "DE.HH", "note": null, "hasc_maybe": null, "region": null, "region_cod": null, "provnum_ne": "21", "gadm_level": "1", "check_me": "20", "scalerank": "3", "datarank": "3", "abbrev": null, "postal": "HH", "area_sqkm": "0", "sameascity": "9", "labelrank": "9", "featurecla": "Admin-1 scale rank", "name_len": "7", "mapcolor9": "5", "mapcolor13": "1", "fips": "GM04", "fips_alt": null, "woe_id": "2345484", "woe_label": "Hamburg, DE, Germany", "woe_name": "Hamburg", "latitude": "53.559", "longitude": "10.0344", "sov_a3": "DEU", "adm0_a3": "DEU", "adm0_label": "2", "admin": "Germany", "geonunit": "Germany", "gu_a3": "DEU", "gn_id": "2911297", "gn_name": "Freie und Hansestadt Hamburg", "gns_id": "-1785435", "gns_name": "Hamburg", "gn_level": "1", "gn_region": null, "gn_a1_code": "DE.04", "region_sub": null, "sub_code": null, "gns_level": "1", "gns_lang": "zho", "gns_adm1": "GM04", "gns_region": null }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 3550632.9068574295, 5935231.2751757316 ], [ 3554307.569858544, 5936267.8784428686 ], [ 3549069.0712087429, 5937985.4088263968 ], [ 3550830.3006076724, 5944304.1125011407 ], [ 3554853.9405998117, 5940794.9144034721 ], [ 3570371.6708244649, 5950301.8162747147 ], [ 3572137.6137213707, 5954711.580675493 ], [ 3578862.6937927571, 5955081.5497373492 ], [ 3576943.630466254, 5950819.015340128 ], [ 3580874.49823714, 5945222.0506500695 ], [ 3577703.2560399352, 5938504.9128987873 ], [ 3578686.4275546414, 5933125.8551333752 ], [ 3588249.9672536696, 5922981.0870051431 ], [ 3578903.5037427521, 5919670.0220713178 ], [ 3571948.8982978156, 5924735.9639584571 ], [ 3565509.5510616428, 5920653.881467809 ], [ 3556965.7078730096, 5922524.892838276 ], [ 3550632.9068574295, 5935231.2751757316 ] ] ] } }, { "type": "Feature", "properties": { "cat": "3", "adm1_code": "DEU-1580", "OBJECTID_1": "2331", "diss_me": "1580", "adm1_cod_1": "DEU-1580", "iso_3166_2": "DE-", "wikipedia": null, "iso_a2": "DE", "adm0_sr": "1", "name": "Rheinland-Pfalz", "name_alt": "Rhineland-Palatinate", "name_local": null, "type": "Land", "type_en": "State", "code_local": null, "code_hasc": "DE.RP", "note": null, "hasc_maybe": null, "region": null, "region_cod": null, "provnum_ne": "16", "gadm_level": "1", "check_me": "20", "scalerank": "3", "datarank": "3", "abbrev": null, "postal": "RP", "area_sqkm": "0", "sameascity": "-99", "labelrank": "3", "featurecla": "Admin-1 scale rank", "name_len": "15", "mapcolor9": "5", "mapcolor13": "1", "fips": "GM08", "fips_alt": null, "woe_id": "2345488", "woe_label": "Rhineland-Palatinate, DE, Germany", "woe_name": "Rheinland-Pfalz", "latitude": "49.8685", "longitude": "7.36974", "sov_a3": "DEU", "adm0_a3": "DEU", "adm0_label": "2", "admin": "Germany", "geonunit": "Germany", "gu_a3": "DEU", "gn_id": "2847618", "gn_name": "Rheinland-Pfalz", "gns_id": "-1849325", "gns_name": "Rheinland-Pfalz", "gn_level": "1", "gn_region": null, "gn_a1_code": "DE.08", "region_sub": null, "sub_code": null, "gns_level": "1", "gns_lang": "zho", "gns_adm1": "GM08", "gns_region": null }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 3441413.1035388974, 5424738.8449200103 ], [ 3421909.7072348008, 5433496.2339400193 ], [ 3400206.9816703415, 5434171.874666106 ], [ 3381263.1691697384, 5448827.7981531238 ], [ 3375749.5360028311, 5453286.6340999817 ], [ 3382812.5808065045, 5467779.9515374238 ], [ 3372832.7086474616, 5480162.0484409798 ], [ 3377452.9388920865, 5484425.9351314809 ], [ 3374726.1658796789, 5494845.4324212102 ], [ 3371185.6562408176, 5493263.5940849595 ], [ 3365049.947543317, 5497693.0631523961 ], [ 3350931.1909566126, 5500859.6144528091 ], [ 3334486.4385668249, 5492783.5487769293 ], [ 3326657.9711445286, 5490789.7013586788 ], [ 3308089.9034189489, 5490864.744018699 ], [ 3313667.5787480427, 5506768.1633549016 ], [ 3319085.3295557285, 5510024.6220939485 ], [ 3320036.2148178266, 5520422.1984197628 ], [ 3313918.6219594479, 5521855.0304813217 ], [ 3300451.1426753607, 5531427.5120395822 ], [ 3292075.331599589, 5549727.9406546457 ], [ 3296390.0855788519, 5567982.1229122831 ], [ 3304168.46299545, 5572448.5800253376 ], [ 3307664.7511126897, 5578120.7324183807 ], [ 3312983.7700960236, 5579512.6769915028 ], [ 3316745.7783426233, 5580422.3833581302 ], [ 3314942.5281778327, 5586140.8450016081 ], [ 3319084.6380793764, 5581296.9993137084 ], [ 3328285.0183701818, 5585040.4950274136 ], [ 3330605.1985471123, 5582020.31771647 ], [ 3337596.2249325705, 5580760.0248874584 ], [ 3343107.5339642386, 5582155.4682631167 ], [ 3341011.5040215962, 5595025.0052518947 ], [ 3350093.4458034495, 5595426.0479528448 ], [ 3350049.3700410733, 5600448.5200936021 ], [ 3362065.5903108316, 5607751.5255306438 ], [ 3369368.6827267488, 5608763.7309827879 ], [ 3372459.6357410736, 5612603.93168246 ], [ 3378474.6462449804, 5610514.4526037313 ], [ 3384825.5188038861, 5613016.2159250593 ], [ 3386058.8756050379, 5619783.1269912859 ], [ 3401137.3966407343, 5622790.962612764 ], [ 3408249.7846913603, 5628022.5903055705 ], [ 3406666.4940470285, 5631204.3429187089 ], [ 3413201.5977249132, 5634040.7961232988 ], [ 3412628.2518007741, 5642375.6446921304 ], [ 3419827.9681884986, 5643501.2928428715 ], [ 3418700.2280780436, 5639011.3647475801 ], [ 3425511.3029791312, 5633355.6262931069 ], [ 3433322.8712637313, 5617549.7121808697 ], [ 3438838.0181758674, 5616717.762588284 ], [ 3440788.8477046415, 5605767.4036669126 ], [ 3436698.9808041384, 5599221.561436912 ], [ 3429406.6760302559, 5598670.4881470185 ], [ 3427486.3471034807, 5584796.3907665983 ], [ 3432385.6108222404, 5582006.6984252753 ], [ 3437471.8600296136, 5571557.030202195 ], [ 3423056.5161700593, 5563089.1127727879 ], [ 3422876.6023717704, 5552937.0920662321 ], [ 3418083.6548039587, 5553784.5111388061 ], [ 3412023.655965697, 5546950.898131174 ], [ 3422434.0216260618, 5537934.5923802424 ], [ 3440828.5246273172, 5545698.4037055941 ], [ 3444709.495428592, 5544811.9771885592 ], [ 3452319.9802185413, 5536002.3603432896 ], [ 3452586.3951217886, 5528367.2193213543 ], [ 3458495.5004734448, 5514559.1553209331 ], [ 3453841.9804025455, 5503159.6692345878 ], [ 3457830.8811206338, 5493673.0699443156 ], [ 3463300.0906483554, 5478415.3615263375 ], [ 3462173.0401205476, 5462228.3351949146 ], [ 3441413.1035388974, 5424738.8449200103 ] ] ] } }, { "type": "Feature", "properties": { "cat": "4", "adm1_code": "DEU-1581", "OBJECTID_1": "2332", "diss_me": "1581", "adm1_cod_1": "DEU-1581", "iso_3166_2": "DE-", "wikipedia": null, "iso_a2": "DE", "adm0_sr": "1", "name": "Saarland", "name_alt": null, "name_local": null, "type": "Land", "type_en": "State", "code_local": null, "code_hasc": "DE.SL", "note": null, "hasc_maybe": null, "region": null, "region_cod": null, "provnum_ne": "12", "gadm_level": "1", "check_me": "20", "scalerank": "3", "datarank": "3", "abbrev": null, "postal": "SL", "area_sqkm": "0", "sameascity": "-99", "labelrank": "3", "featurecla": "Admin-1 scale rank", "name_len": "8", "mapcolor9": "5", "mapcolor13": "1", "fips": "GM09", "fips_alt": null, "woe_id": "2345489", "woe_label": "Saarland, DE, Germany", "woe_name": "Saarland", "latitude": "49.4026", "longitude": "6.86625", "sov_a3": "DEU", "adm0_a3": "DEU", "adm0_label": "2", "admin": "Germany", "geonunit": "Germany", "gu_a3": "DEU", "gn_id": "2842635", "gn_name": "Saarland", "gns_id": "-1854333", "gns_name": "Saarland", "gn_level": "1", "gn_region": null, "gn_a1_code": "DE.09", "region_sub": null, "sub_code": null, "gns_level": "1", "gns_lang": "zho", "gns_adm1": "GM09", "gns_region": null }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 3381263.1691697384, 5448827.7981531238 ], [ 3373557.3606479331, 5442263.874895405 ], [ 3342161.1407189239, 5454623.4918628894 ], [ 3340179.5116371857, 5447601.5385523606 ], [ 3332385.342399545, 5450840.3922064183 ], [ 3318304.2294849427, 5480523.8351349905 ], [ 3307562.3294992363, 5483097.0605111523 ], [ 3308089.9034189489, 5490864.744018699 ], [ 3326657.9711445286, 5490789.7013586788 ], [ 3334486.4385668249, 5492783.5487769293 ], [ 3350931.1909566126, 5500859.6144528091 ], [ 3365049.947543317, 5497693.0631523961 ], [ 3371185.6562408176, 5493263.5940849595 ], [ 3374726.1658796789, 5494845.4324212102 ], [ 3377452.9388920865, 5484425.9351314809 ], [ 3372832.7086474616, 5480162.0484409798 ], [ 3382812.5808065045, 5467779.9515374238 ], [ 3375749.5360028311, 5453286.6340999817 ], [ 3381263.1691697384, 5448827.7981531238 ] ] ] } }, { "type": "Feature", "properties": { "cat": "5", "adm1_code": "DEU-1591", "OBJECTID_1": "2333", "diss_me": "1591", "adm1_cod_1": "DEU-1591", "iso_3166_2": "DE-", "wikipedia": null, "iso_a2": "DE", "adm0_sr": "1", "name": "Bayern", "name_alt": "Bavaria", "name_local": null, "type": "Land", "type_en": "State", "code_local": null, "code_hasc": "DE.BY", "note": null, "hasc_maybe": "DE.TH|DEU-BYR", "region": null, "region_cod": null, "provnum_ne": "14", "gadm_level": "1", "check_me": "20", "scalerank": "3", "datarank": "3", "abbrev": null, "postal": "BY", "area_sqkm": "0", "sameascity": "-99", "labelrank": "3", "featurecla": "Admin-1 scale rank", "name_len": "6", "mapcolor9": "5", "mapcolor13": "1", "fips": "GM02", "fips_alt": "GM15", "woe_id": "2345482", "woe_label": "Bavaria, DE, Germany", "woe_name": "Bayern", "latitude": "49.0056", "longitude": "11.3966", "sov_a3": "DEU", "adm0_a3": "DEU", "adm0_label": "2", "admin": "Germany", "geonunit": "Germany", "gu_a3": "DEU", "gn_id": "2951839", "gn_name": "Freistaat Bayern", "gns_id": "-1744755", "gns_name": "Bayern", "gn_level": "1", "gn_region": null, "gn_a1_code": "DE.02", "region_sub": null, "sub_code": null, "gns_level": "1", "gns_lang": "zho", "gns_adm1": "GM02", "gns_region": null }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 3720854.929931188, 5579802.1671278924 ], [ 3719631.2211950263, 5571814.3670201534 ], [ 3732520.2786687785, 5550332.2352868849 ], [ 3748749.2603212004, 5542736.809619463 ], [ 3753100.7648904314, 5535668.0080291722 ], [ 3743829.3417696748, 5517178.9862410398 ], [ 3762324.3291879585, 5492080.2687983979 ], [ 3764245.5673894449, 5483218.791022324 ], [ 3775995.855328233, 5472471.210996354 ], [ 3790870.3988914737, 5469570.8132270994 ], [ 3804947.7173350696, 5450646.8378275726 ], [ 3813933.9184407792, 5448396.0162059972 ], [ 3828298.2930317265, 5431829.4757764423 ], [ 3837496.0529524805, 5433331.9746355461 ], [ 3853922.3040218116, 5414298.7288533179 ], [ 3854027.9231568337, 5397041.137897308 ], [ 3848317.1758256047, 5386640.727975281 ], [ 3833446.5846785088, 5392755.7269397127 ], [ 3826289.9515585969, 5369140.3878430389 ], [ 3816994.2940813755, 5360866.1018757839 ], [ 3799346.7957504704, 5355056.062758225 ], [ 3778767.4968754789, 5338084.6226216014 ], [ 3780201.3775204262, 5331942.0280900477 ], [ 3798665.6127939913, 5308605.0331156831 ], [ 3791934.1650316818, 5294492.4678504989 ], [ 3801583.7247173521, 5293802.7550295144 ], [ 3806028.2541264431, 5283947.7214670992 ], [ 3804167.7842186769, 5269432.0791578349 ], [ 3797155.9113819273, 5266557.9084208813 ], [ 3784369.5568754105, 5275318.431105678 ], [ 3782489.4718682836, 5287708.8790438846 ], [ 3765869.8159498828, 5282967.6688195048 ], [ 3757000.8567382093, 5289276.9764530305 ], [ 3738359.0559389209, 5290070.1350489352 ], [ 3737999.8904091902, 5278714.7879365459 ], [ 3720699.45624005, 5278787.4608982084 ], [ 3697068.2414810816, 5275593.9712792682 ], [ 3692138.5890133549, 5266976.1832740316 ], [ 3681727.3248318103, 5265798.1394534484 ], [ 3670484.2581564616, 5253737.0374460649 ], [ 3649428.9658668973, 5252031.1020079171 ], [ 3634830.2636962491, 5265638.6508152951 ], [ 3606391.2058383985, 5271384.9862124128 ], [ 3609508.0145107368, 5256512.8726171581 ], [ 3598748.4894922166, 5241134.8247360373 ], [ 3587417.9932788452, 5237698.0592393083 ], [ 3589919.3672567643, 5249560.5825826228 ], [ 3571438.51553459, 5265428.8433407042 ], [ 3545727.4155536136, 5271763.9444706095 ], [ 3551119.3500004988, 5274925.3375281552 ], [ 3553760.0831742487, 5274202.1924148183 ], [ 3564921.0598903387, 5282095.2396906596 ], [ 3571596.2949043298, 5280467.0692903223 ], [ 3579419.406908934, 5282654.3130722083 ], [ 3580805.8182427208, 5279501.6412465433 ], [ 3582086.3286339194, 5282857.9677951159 ], [ 3584928.4473972623, 5283809.5219843267 ], [ 3583494.2047532503, 5292617.613166254 ], [ 3580930.5669586663, 5294773.3959587673 ], [ 3584580.1453866977, 5298103.7145693144 ], [ 3582134.3343479666, 5304897.8270773264 ], [ 3582885.1004398358, 5311565.5902857054 ], [ 3581170.5874427473, 5314040.8154020924 ], [ 3584271.0151908011, 5322385.9982684879 ], [ 3584603.1174242902, 5330438.8159838542 ], [ 3582536.9707668643, 5334936.9094386967 ], [ 3578187.6194506902, 5352316.3667016691 ], [ 3572624.432680374, 5360747.4124220517 ], [ 3578955.5050633829, 5370940.3940610792 ], [ 3583771.9889397854, 5372636.0281001786 ], [ 3585285.3429935728, 5370319.6556346128 ], [ 3592397.2803958142, 5374804.8767391955 ], [ 3597104.90080478, 5377768.9604256051 ], [ 3596820.2459671348, 5386336.2469680198 ], [ 3598254.0374123072, 5388042.2132638032 ], [ 3594990.0970565821, 5390440.2955783168 ], [ 3593537.9326087711, 5394395.9917849256 ], [ 3595531.1762953508, 5396915.3128908044 ], [ 3601043.7237907918, 5392675.2968838485 ], [ 3604817.5383069692, 5396914.4062103806 ], [ 3605494.6288470197, 5393679.6094944002 ], [ 3610040.9729735483, 5396732.7561689578 ], [ 3605288.9192014784, 5404018.894093167 ], [ 3607096.9641639437, 5411585.8563470664 ], [ 3606859.6900644084, 5423359.0073309727 ], [ 3599011.9484724626, 5433669.2124226196 ], [ 3593147.1400163225, 5435435.6389526604 ], [ 3592245.8891499159, 5443944.4963075556 ], [ 3583564.5297398535, 5452545.6756128054 ], [ 3582413.875438883, 5459974.4160899296 ], [ 3584382.777422816, 5460337.6364304721 ], [ 3581463.7540525994, 5470771.6749604018 ], [ 3585018.2661472317, 5472469.5360561004 ], [ 3582846.7077056891, 5477603.5611078702 ], [ 3580825.872080544, 5478633.7955291895 ], [ 3581644.3427704326, 5486557.8843275979 ], [ 3578523.3819369273, 5490167.6813984727 ], [ 3577176.3558150646, 5486245.6809542198 ], [ 3573881.9593119058, 5482987.1144731594 ], [ 3567840.6018891246, 5483063.8994000871 ], [ 3566013.3375535863, 5493864.1031941669 ], [ 3561886.1846542899, 5489700.4107388947 ], [ 3560708.0498845158, 5490710.2006405825 ], [ 3563761.6499401266, 5497211.0637213998 ], [ 3560208.8010403323, 5502015.7024308359 ], [ 3560005.6028370573, 5506214.9098258298 ], [ 3557139.2306370987, 5509661.1895087473 ], [ 3552489.5273762518, 5505757.5850912258 ], [ 3551176.5391815607, 5509357.2814978566 ], [ 3548880.8715447127, 5506083.0556571763 ], [ 3545480.2621061597, 5506998.8894933118 ], [ 3546539.9080542442, 5516416.3428387139 ], [ 3543354.8998105251, 5514967.3727186071 ], [ 3535345.8245763546, 5517624.6485599363 ], [ 3525335.9484673571, 5516510.933784782 ], [ 3522498.7099780627, 5514265.6125144539 ], [ 3522607.3664702717, 5510533.3209668808 ], [ 3530810.1855651978, 5507912.8665117286 ], [ 3529207.3879846265, 5500329.3255963055 ], [ 3527193.7326927111, 5501474.0234873584 ], [ 3520685.2364013265, 5498793.0887854239 ], [ 3518325.600151998, 5493330.7812480545 ], [ 3508004.7275028112, 5493032.8392732944 ], [ 3505250.4010145608, 5497006.3185954299 ], [ 3508038.9263903359, 5500227.9951685322 ], [ 3509617.7330651209, 5506477.500841843 ], [ 3510146.6536235306, 5516639.2130606221 ], [ 3505048.3221994299, 5521439.4779663766 ], [ 3501918.8327689632, 5529980.5791802146 ], [ 3501924.5455787638, 5545078.5930615086 ], [ 3498323.6014661267, 5546150.3840967752 ], [ 3500735.6345797917, 5551049.7819365254 ], [ 3507714.830290556, 5554233.6068744743 ], [ 3510910.1904314789, 5552779.3399014063 ], [ 3516350.8844319284, 5556299.1656100359 ], [ 3523525.7989939875, 5555823.0760990549 ], [ 3529102.3323403024, 5550628.9499717057 ], [ 3536241.2263133698, 5552425.6098567126 ], [ 3537768.1315709203, 5559166.6873750146 ], [ 3535646.7908835602, 5560063.0808881437 ], [ 3536454.1115450612, 5566675.5268396242 ], [ 3547168.3691143082, 5566552.8579348298 ], [ 3548486.4890143084, 5571588.4039183576 ], [ 3553322.554148009, 5574531.7938179336 ], [ 3554503.9289892185, 5585281.3163722064 ], [ 3561061.3854291611, 5585008.1051580943 ], [ 3571078.9444448808, 5590634.6929859295 ], [ 3574817.4056062475, 5598257.4420703379 ], [ 3579781.59930187, 5603487.1175612584 ], [ 3584213.7888737433, 5602032.7751879171 ], [ 3587805.4131642585, 5597748.057573312 ], [ 3593561.5613371767, 5595872.3240806991 ], [ 3599433.8822761257, 5588255.5182174174 ], [ 3599920.250670508, 5585260.6331641655 ], [ 3603043.3838240071, 5585677.0623686798 ], [ 3606720.0805508858, 5580673.1137679294 ], [ 3610451.0868479265, 5581336.1181971366 ], [ 3613301.3204261549, 5577745.2251591412 ], [ 3615378.8061682819, 5566488.7467001993 ], [ 3623231.2025344539, 5565109.5173362065 ], [ 3623502.8652153891, 5568663.640977269 ], [ 3631250.6056273249, 5568820.599108696 ], [ 3631003.5069937492, 5572109.5285800658 ], [ 3622115.8301066705, 5577492.5262480676 ], [ 3622821.4798547197, 5581827.2477174671 ], [ 3627205.3913595304, 5584768.6555711003 ], [ 3638188.6970979096, 5585165.1007029591 ], [ 3641467.2303942246, 5581085.1458215769 ], [ 3645868.0769925839, 5580963.0357561549 ], [ 3650284.4903703765, 5583030.593772362 ], [ 3653432.929886797, 5578485.1134469826 ], [ 3652087.5666547911, 5576864.6365224905 ], [ 3656266.8897332451, 5572781.6523313411 ], [ 3660745.4664473836, 5573283.5300583346 ], [ 3662238.810322443, 5582653.1846054439 ], [ 3659605.2232868215, 5595262.2291009063 ], [ 3664336.9864799059, 5597622.3606871739 ], [ 3666404.0989981135, 5600603.8442229992 ], [ 3672121.9713641307, 5600160.7438294999 ], [ 3671958.3111791341, 5592357.348135368 ], [ 3675994.001566703, 5590758.7787582632 ], [ 3676626.67844284, 5587664.6132867765 ], [ 3680049.0477793319, 5585432.8105548657 ], [ 3698749.3649529289, 5590010.1434312938 ], [ 3701124.6336581926, 5587599.6888292851 ], [ 3706456.6357335709, 5590490.103557012 ], [ 3712475.9410972646, 5583643.9742964255 ], [ 3720854.929931188, 5579802.1671278924 ] ] ] } }, { "type": "Feature", "properties": { "cat": "8", "adm1_code": "DEU-1601", "OBJECTID_1": "2336", "diss_me": "1601", "adm1_cod_1": "DEU-1601", "iso_3166_2": "DE-", "wikipedia": null, "iso_a2": "DE", "adm0_sr": "1", "name": "Sachsen", "name_alt": "Saxony", "name_local": null, "type": "Land", "type_en": "State", "code_local": null, "code_hasc": "DE.SN", "note": null, "hasc_maybe": null, "region": null, "region_cod": null, "provnum_ne": "13", "gadm_level": "1", "check_me": "20", "scalerank": "3", "datarank": "3", "abbrev": null, "postal": "SN", "area_sqkm": "0", "sameascity": "-99", "labelrank": "3", "featurecla": "Admin-1 scale rank", "name_len": "7", "mapcolor9": "5", "mapcolor13": "1", "fips": "GM13", "fips_alt": null, "woe_id": "2345493", "woe_label": "Saxony, DE, Germany", "woe_name": "Sachsen", "latitude": "51.0053", "longitude": "13.4596", "sov_a3": "DEU", "adm0_a3": "DEU", "adm0_label": "2", "admin": "Germany", "geonunit": "Germany", "gu_a3": "DEU", "gn_id": "2842566", "gn_name": "Freistaat Sachsen", "gns_id": "-1854402", "gns_name": "Sachsen", "gn_level": "1", "gn_region": null, "gn_a1_code": "DE.13", "region_sub": null, "sub_code": null, "gns_level": "1", "gns_lang": "zho", "gns_adm1": "GM13", "gns_region": null }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 3895729.9823607644, 5727122.6007731473 ], [ 3913070.5398604777, 5718259.5309541589 ], [ 3920360.2740866155, 5695097.0156875188 ], [ 3917262.6324534719, 5675524.3458810057 ], [ 3906855.3511170237, 5646619.4597514132 ], [ 3895163.1054948336, 5649360.6450482514 ], [ 3896125.4075728473, 5656567.4818767738 ], [ 3885322.9184645279, 5667742.8164319936 ], [ 3870722.8689866588, 5668920.658955303 ], [ 3867706.1925043422, 5662631.8681641854 ], [ 3876123.3069151207, 5651812.8112209244 ], [ 3844935.6204190049, 5638502.4966573529 ], [ 3841262.8713556938, 5631926.33240279 ], [ 3824931.3167273672, 5629596.8108225381 ], [ 3814830.689602342, 5616161.0174571536 ], [ 3799673.1328355772, 5613571.7049376061 ], [ 3795090.3541931743, 5603831.5304041319 ], [ 3784445.6694856659, 5602760.9429713953 ], [ 3778486.0135767977, 5592711.3187929457 ], [ 3771699.906899136, 5596372.6104919445 ], [ 3749573.2997890264, 5589410.5125307078 ], [ 3737872.9533890621, 5574367.7266012002 ], [ 3734558.6914271559, 5563483.4667501887 ], [ 3730459.2210461572, 5574098.141054092 ], [ 3720854.929931188, 5579802.1671278924 ], [ 3712475.9410972646, 5583643.9742964255 ], [ 3706456.6357335709, 5590490.103557012 ], [ 3707593.3788553113, 5598689.8272855673 ], [ 3711847.4358901861, 5605288.3101831255 ], [ 3716102.6109797498, 5603841.3164922409 ], [ 3722819.9620642923, 5607572.8059453368 ], [ 3725457.3308588881, 5614794.9990650406 ], [ 3730125.0831804471, 5614992.2367806258 ], [ 3734759.1665375163, 5620512.1446339721 ], [ 3728832.1004670118, 5625615.0790818483 ], [ 3729672.7793026781, 5633487.7130454583 ], [ 3741232.331247753, 5637557.6046812981 ], [ 3748218.9617048698, 5643708.7736610528 ], [ 3756509.8321495252, 5646715.3534608511 ], [ 3748992.2363486462, 5654296.6612281911 ], [ 3744176.9108976396, 5663770.8769144556 ], [ 3728697.3410449172, 5665894.003271156 ], [ 3725736.6071475316, 5666344.9610057147 ], [ 3720493.4261422371, 5675145.0456173113 ], [ 3718449.0036664321, 5688621.3779614437 ], [ 3721730.6767142406, 5692441.5749404654 ], [ 3719417.8191738939, 5706182.4262456577 ], [ 3724304.7276911819, 5719002.5429664785 ], [ 3737782.7113693953, 5720766.2362732422 ], [ 3738522.3460328085, 5724031.9576492552 ], [ 3753662.7909530257, 5724768.6707163947 ], [ 3761788.6117216963, 5728031.8281768858 ], [ 3767113.5430716942, 5732917.2825115556 ], [ 3781244.7607806674, 5727749.173669612 ], [ 3789159.7458350016, 5722743.0719234683 ], [ 3791429.4246440106, 5702687.0541994385 ], [ 3806709.1660082862, 5707620.2425152455 ], [ 3814794.8146041944, 5702036.2389156315 ], [ 3831961.9311943511, 5701008.7637389079 ], [ 3846067.6576036941, 5703199.0928839929 ], [ 3857862.6325678141, 5722744.2213756908 ], [ 3870467.4319547503, 5721757.1183289066 ], [ 3881910.1924875081, 5727851.2041914547 ], [ 3895729.9823607644, 5727122.6007731473 ] ] ] } }, { "type": "Feature", "properties": { "cat": "7", "adm1_code": "DEU-1600", "OBJECTID_1": "2335", "diss_me": "1600", "adm1_cod_1": "DEU-1600", "iso_3166_2": "DE-", "wikipedia": null, "iso_a2": "DE", "adm0_sr": "1", "name": "Sachsen-Anhalt", "name_alt": "Saxony-Anhalt", "name_local": null, "type": "Land", "type_en": "State", "code_local": null, "code_hasc": "DE.ST", "note": null, "hasc_maybe": null, "region": null, "region_cod": null, "provnum_ne": "18", "gadm_level": "1", "check_me": "20", "scalerank": "3", "datarank": "3", "abbrev": null, "postal": "ST", "area_sqkm": "0", "sameascity": "-99", "labelrank": "3", "featurecla": "Admin-1 scale rank", "name_len": "14", "mapcolor9": "5", "mapcolor13": "1", "fips": "GM14", "fips_alt": null, "woe_id": "2345494", "woe_label": "Saxony-Anhalt, DE, Germany", "woe_name": "Sachsen-Anhalt", "latitude": "51.9338", "longitude": "11.6796", "sov_a3": "DEU", "adm0_a3": "DEU", "adm0_label": "2", "admin": "Germany", "geonunit": "Germany", "gu_a3": "DEU", "gn_id": "2842565", "gn_name": "Land Sachsen-Anhalt", "gns_id": "-1854403", "gns_name": "Sachsen-Anhalt", "gn_level": "1", "gn_region": null, "gn_a1_code": "DE.14", "region_sub": null, "sub_code": null, "gns_level": "1", "gns_lang": "zho", "gns_adm1": "GM14", "gns_region": null }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 3781244.7607806674, 5727749.173669612 ], [ 3767113.5430716942, 5732917.2825115556 ], [ 3761788.6117216963, 5728031.8281768858 ], [ 3753662.7909530257, 5724768.6707163947 ], [ 3738522.3460328085, 5724031.9576492552 ], [ 3737782.7113693953, 5720766.2362732422 ], [ 3724304.7276911819, 5719002.5429664785 ], [ 3719417.8191738939, 5706182.4262456577 ], [ 3721730.6767142406, 5692441.5749404654 ], [ 3718449.0036664321, 5688621.3779614437 ], [ 3720493.4261422371, 5675145.0456173113 ], [ 3725736.6071475316, 5666344.9610057147 ], [ 3728697.3410449172, 5665894.003271156 ], [ 3729823.6229192242, 5657699.0347429402 ], [ 3724984.3058717526, 5648366.5871077562 ], [ 3721967.7634851355, 5650922.120966264 ], [ 3711102.7786706178, 5650136.7946595009 ], [ 3700671.5497429045, 5657917.8290444724 ], [ 3691711.4629774042, 5657002.0672275824 ], [ 3688360.598657859, 5665165.5228766529 ], [ 3670003.9710097802, 5665298.3177191904 ], [ 3669281.7923881463, 5672052.1862729536 ], [ 3662981.3890735451, 5678154.1523908963 ], [ 3671078.403472946, 5684562.7217747746 ], [ 3661128.3753905562, 5696690.098692731 ], [ 3639072.4658529945, 5698871.2434897516 ], [ 3632504.2172296499, 5716768.8556063659 ], [ 3634798.3906453014, 5719458.0870596841 ], [ 3617510.6850254778, 5723772.3194554569 ], [ 3615012.1854641521, 5731490.6919269329 ], [ 3609394.947188708, 5739233.3662485098 ], [ 3608880.3975983351, 5746675.8150769975 ], [ 3612899.5315223499, 5751066.7033877522 ], [ 3610948.6257142145, 5755584.0213080486 ], [ 3613030.801248, 5759432.3883808982 ], [ 3607797.4707193407, 5764671.3332481934 ], [ 3613361.9478737903, 5768780.5365629746 ], [ 3626913.4232676532, 5769958.5594496056 ], [ 3634968.6410203772, 5771763.776092371 ], [ 3633454.749544749, 5775329.8780293567 ], [ 3639545.4940197729, 5779363.0596436961 ], [ 3638079.8541451683, 5785463.3934184005 ], [ 3642306.9390903679, 5789843.2179832282 ], [ 3637457.8965149773, 5796638.6178118531 ], [ 3636231.0192434615, 5802737.3780365922 ], [ 3641160.67654408, 5806181.3832803816 ], [ 3631772.1436179769, 5815934.1879419796 ], [ 3635920.4080108143, 5820057.3019131161 ], [ 3632514.9164062119, 5825233.8930791495 ], [ 3633938.0988071673, 5832809.371821166 ], [ 3630000.1103232088, 5833404.7902165819 ], [ 3624130.2654388198, 5843359.7207674412 ], [ 3621666.099287462, 5844655.1274119448 ], [ 3618514.5615372388, 5851704.8690217081 ], [ 3618718.1755545707, 5857485.6109101465 ], [ 3632019.5397876874, 5860810.2693054294 ], [ 3635509.5308777369, 5866108.0874277316 ], [ 3648934.7473273841, 5864932.535072092 ], [ 3651333.2684875731, 5862791.5747473724 ], [ 3662276.0322155575, 5866329.1451455932 ], [ 3668159.9163241819, 5870732.0661629066 ], [ 3668261.3414150965, 5877081.257549637 ], [ 3671922.8545249584, 5878200.7057062704 ], [ 3673094.9477649452, 5881900.7282523569 ], [ 3685470.527182492, 5877043.1175059043 ], [ 3690394.3300075424, 5869785.5474416316 ], [ 3699574.5929267807, 5865363.214592156 ], [ 3708117.6930525554, 5866440.6340229502 ], [ 3715667.1928544352, 5863903.8374439515 ], [ 3717929.5001302152, 5858476.4293313287 ], [ 3715469.8620647853, 5849748.085141778 ], [ 3718214.6508923629, 5844431.1891566208 ], [ 3711850.4436102621, 5829091.543895375 ], [ 3717622.3749302062, 5827795.9666623687 ], [ 3723223.1986584924, 5821704.5994157083 ], [ 3720737.934966553, 5816927.2713739648 ], [ 3718959.61734682, 5798644.4757761015 ], [ 3721757.2971702325, 5795806.4581173742 ], [ 3718700.9173543877, 5789151.6860899935 ], [ 3723228.6656669825, 5782040.1035374934 ], [ 3734812.1546247685, 5769569.3694657935 ], [ 3737897.7987304418, 5770821.5788534619 ], [ 3747886.3812362328, 5765052.4747153148 ], [ 3751784.0961636314, 5768485.2524125446 ], [ 3766349.8402684638, 5760146.1772142258 ], [ 3771836.8357371399, 5759778.7603498688 ], [ 3780081.7644681148, 5754179.0442536157 ], [ 3791815.5008474807, 5755165.8817298934 ], [ 3787186.3117421567, 5747485.4105568705 ], [ 3788950.7222997635, 5738608.6159806 ], [ 3781244.7607806674, 5727749.173669612 ] ] ] } }, { "type": "Feature", "properties": { "cat": "9", "adm1_code": "DEU-3487", "OBJECTID_1": "3562", "diss_me": "3487", "adm1_cod_1": "DEU-3487", "iso_3166_2": "DE-", "wikipedia": null, "iso_a2": "DE", "adm0_sr": "1", "name": "Brandenburg", "name_alt": null, "name_local": null, "type": "Land", "type_en": "State", "code_local": null, "code_hasc": "DE.", "note": null, "hasc_maybe": "DE.BE|DEU-BRN", "region": null, "region_cod": null, "provnum_ne": "17", "gadm_level": "1", "check_me": "20", "scalerank": "3", "datarank": "3", "abbrev": null, "postal": "BE", "area_sqkm": "0", "sameascity": "-99", "labelrank": "3", "featurecla": "Admin-1 scale rank", "name_len": "11", "mapcolor9": "5", "mapcolor13": "1", "fips": "GM11", "fips_alt": "GM16", "woe_id": "2345491", "woe_label": "Brandenburg, DE, Germany", "woe_name": "Brandenburg", "latitude": "52.8156", "longitude": "12.9206", "sov_a3": "DEU", "adm0_a3": "DEU", "adm0_label": "2", "admin": "Germany", "geonunit": "Germany", "gu_a3": "DEU", "gn_id": "2945356", "gn_name": "Land Brandenburg", "gns_id": "-1751262", "gns_name": "Brandenburg", "gn_level": "1", "gn_region": null, "gn_a1_code": "DE.11", "region_sub": null, "sub_code": null, "gns_level": "1", "gns_lang": "zho", "gns_adm1": "GM11", "gns_region": null }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 3858958.4675184065, 5924746.6905517764 ], [ 3863006.6963384282, 5915868.9256445663 ], [ 3858145.8362342203, 5892787.8486107616 ], [ 3845536.6696635247, 5881951.7849794067 ], [ 3845024.9412794961, 5869707.4513760619 ], [ 3882462.763382134, 5841922.4558948586 ], [ 3876679.8774116514, 5824138.593258542 ], [ 3894793.9838867425, 5786961.4449581495 ], [ 3885105.1970240651, 5755706.9621667564 ], [ 3895439.295541347, 5742171.3496038476 ], [ 3895729.9823607644, 5727122.6007731473 ], [ 3881910.1924875081, 5727851.2041914547 ], [ 3870467.4319547503, 5721757.1183289066 ], [ 3857862.6325678141, 5722744.2213756908 ], [ 3846067.6576036941, 5703199.0928839929 ], [ 3831961.9311943511, 5701008.7637389079 ], [ 3814794.8146041944, 5702036.2389156315 ], [ 3806709.1660082862, 5707620.2425152455 ], [ 3791429.4246440106, 5702687.0541994385 ], [ 3789159.7458350016, 5722743.0719234683 ], [ 3781244.7607806674, 5727749.173669612 ], [ 3788950.7222997635, 5738608.6159806 ], [ 3787186.3117421567, 5747485.4105568705 ], [ 3791815.5008474807, 5755165.8817298934 ], [ 3780081.7644681148, 5754179.0442536157 ], [ 3771836.8357371399, 5759778.7603498688 ], [ 3766349.8402684638, 5760146.1772142258 ], [ 3751784.0961636314, 5768485.2524125446 ], [ 3747886.3812362328, 5765052.4747153148 ], [ 3737897.7987304418, 5770821.5788534619 ], [ 3734812.1546247685, 5769569.3694657935 ], [ 3723228.6656669825, 5782040.1035374934 ], [ 3718700.9173543877, 5789151.6860899935 ], [ 3721757.2971702325, 5795806.4581173742 ], [ 3718959.61734682, 5798644.4757761015 ], [ 3720737.934966553, 5816927.2713739648 ], [ 3723223.1986584924, 5821704.5994157083 ], [ 3717622.3749302062, 5827795.9666623687 ], [ 3711850.4436102621, 5829091.543895375 ], [ 3718214.6508923629, 5844431.1891566208 ], [ 3715469.8620647853, 5849748.085141778 ], [ 3717929.5001302152, 5858476.4293313287 ], [ 3715667.1928544352, 5863903.8374439515 ], [ 3708117.6930525554, 5866440.6340229502 ], [ 3699574.5929267807, 5865363.214592156 ], [ 3690394.3300075424, 5869785.5474416316 ], [ 3685470.527182492, 5877043.1175059043 ], [ 3673094.9477649452, 5881900.7282523569 ], [ 3661924.2617668891, 5885747.2731848815 ], [ 3652231.3320433423, 5889511.3574547973 ], [ 3660311.8800712302, 5888738.7558573624 ], [ 3670935.6606098032, 5893318.19994 ], [ 3669396.6476244526, 5899831.1085491451 ], [ 3680200.6158619821, 5905056.4653345328 ], [ 3683284.378220486, 5901519.8736129059 ], [ 3700755.786590965, 5911583.242706717 ], [ 3703941.6063928884, 5918019.7483437611 ], [ 3712076.1365221734, 5919807.300273018 ], [ 3728791.1339836149, 5909381.6571826441 ], [ 3763845.3670365461, 5903055.9980064519 ], [ 3767480.0978048071, 5900151.2545123501 ], [ 3777112.0423970157, 5910922.5465983907 ], [ 3793795.8883584146, 5913641.643195576 ], [ 3801803.8451427119, 5927759.7186010573 ], [ 3812528.0683271405, 5937242.9811624223 ], [ 3822905.9499626607, 5941589.9501121677 ], [ 3826929.8373607565, 5932832.0742536131 ], [ 3848307.6588879293, 5934358.7847815678 ], [ 3848470.2881636913, 5929329.4855594682 ], [ 3840283.6437742342, 5916498.3542627199 ], [ 3850096.8967003031, 5915716.2145906379 ], [ 3858958.4675184065, 5924746.6905517764 ] ], [ [ 3804566.4152815924, 5843865.692704617 ], [ 3802213.9156131558, 5845930.8582165847 ], [ 3796466.9763708967, 5841442.4283941165 ], [ 3791058.6585779274, 5844454.8433719436 ], [ 3785707.312560183, 5840767.7494266415 ], [ 3785070.6277251416, 5836354.5125904409 ], [ 3781589.121700428, 5837662.7537919097 ], [ 3780958.9744146601, 5829146.268900374 ], [ 3778418.5104975295, 5817224.1775639663 ], [ 3782830.3462029067, 5815037.8793154648 ], [ 3792079.8344209795, 5817614.5021004295 ], [ 3801245.3895061575, 5814373.9040043866 ], [ 3803208.3688719897, 5818473.9252682505 ], [ 3815681.7293974063, 5814709.1505647963 ], [ 3816409.7180140801, 5810966.0266671684 ], [ 3820320.890532007, 5814819.0171839539 ], [ 3822865.3678980898, 5823472.4171997104 ], [ 3816712.9457131149, 5827478.2731477637 ], [ 3814140.9994099671, 5827006.7575231045 ], [ 3815207.714945185, 5832907.5054032393 ], [ 3809716.679845443, 5835249.9865113907 ], [ 3804934.6433777702, 5840168.5899853837 ], [ 3804566.4152815924, 5843865.692704617 ] ] ] } }, { "type": "Feature", "properties": { "cat": "11", "adm1_code": "DEU-1572", "OBJECTID_1": "1515", "diss_me": "1572", "adm1_cod_1": "DEU-1572", "iso_3166_2": "DE-", "wikipedia": null, "iso_a2": "DE", "adm0_sr": "1", "name": "Nordrhein-Westfalen", "name_alt": "North Rhine-Westphalia", "name_local": null, "type": "Land", "type_en": "State", "code_local": null, "code_hasc": "DE.NW", "note": null, "hasc_maybe": "DE.NI|DEU-NWS", "region": null, "region_cod": null, "provnum_ne": "11", "gadm_level": "1", "check_me": "20", "scalerank": "3", "datarank": "3", "abbrev": null, "postal": "NW", "area_sqkm": "0", "sameascity": "-99", "labelrank": "3", "featurecla": "Admin-1 scale rank", "name_len": "19", "mapcolor9": "5", "mapcolor13": "1", "fips": "GM07", "fips_alt": "GM06", "woe_id": "2345487", "woe_label": "North Rhine-Westphalia, DE, Germany", "woe_name": "Nordrhein-Westfalen", "latitude": "51.6146", "longitude": "7.65708", "sov_a3": "DEU", "adm0_a3": "DEU", "adm0_label": "2", "admin": "Germany", "geonunit": "Germany", "gu_a3": "DEU", "gn_id": "2861876", "gn_name": "Land Nordrhein-Westfalen", "gns_id": "-1835009", "gns_name": "Nordrhein-Westfalen", "gn_level": "1", "gn_region": null, "gn_a1_code": "DE.07", "region_sub": null, "sub_code": null, "gns_level": "1", "gns_lang": "zho", "gns_adm1": "GM07", "gns_region": null }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 3438838.0181758674, 5616717.762588284 ], [ 3433322.8712637313, 5617549.7121808697 ], [ 3425511.3029791312, 5633355.6262931069 ], [ 3418700.2280780436, 5639011.3647475801 ], [ 3419827.9681884986, 5643501.2928428715 ], [ 3412628.2518007741, 5642375.6446921304 ], [ 3413201.5977249132, 5634040.7961232988 ], [ 3406666.4940470285, 5631204.3429187089 ], [ 3408249.7846913603, 5628022.5903055705 ], [ 3401137.3966407343, 5622790.962612764 ], [ 3386058.8756050379, 5619783.1269912859 ], [ 3384825.5188038861, 5613016.2159250593 ], [ 3378474.6462449804, 5610514.4526037313 ], [ 3372459.6357410736, 5612603.93168246 ], [ 3369368.6827267488, 5608763.7309827879 ], [ 3362065.5903108316, 5607751.5255306438 ], [ 3350049.3700410733, 5600448.5200936021 ], [ 3350093.4458034495, 5595426.0479528448 ], [ 3341011.5040215962, 5595025.0052518947 ], [ 3343107.5339642386, 5582155.4682631167 ], [ 3337596.2249325705, 5580760.0248874584 ], [ 3330605.1985471123, 5582020.31771647 ], [ 3328285.0183701818, 5585040.4950274136 ], [ 3319084.6380793764, 5581296.9993137084 ], [ 3314942.5281778327, 5586140.8450016081 ], [ 3316745.7783426233, 5580422.3833581302 ], [ 3312983.7700960236, 5579512.6769915028 ], [ 3311015.0402032044, 5597166.2959220903 ], [ 3301286.5663866308, 5598571.7062137835 ], [ 3300452.0495598861, 5612535.5799334673 ], [ 3286562.4368676911, 5631605.5381454192 ], [ 3293515.8540427848, 5645328.8087019995 ], [ 3289593.1201755358, 5652859.9777060952 ], [ 3280490.6418140358, 5652305.5355140213 ], [ 3283946.8383823703, 5659327.0333167221 ], [ 3300441.7297592955, 5672312.375694558 ], [ 3295054.930980491, 5681365.4637364801 ], [ 3305673.7855360452, 5698330.3926210487 ], [ 3305185.7435152312, 5711900.5580570772 ], [ 3298151.4557415806, 5721776.7622817336 ], [ 3299248.1908475813, 5727184.30292344 ], [ 3288593.3455453222, 5737357.14206855 ], [ 3288166.3396910564, 5745714.5100391265 ], [ 3302277.8919111043, 5755163.8046153989 ], [ 3319581.1853357637, 5746810.0729826046 ], [ 3349540.1338124895, 5760629.0532384655 ], [ 3340479.231191908, 5769175.6678363914 ], [ 3365215.7405231372, 5791134.4245591592 ], [ 3384012.9715766446, 5794350.67907946 ], [ 3392025.9757624478, 5798862.5502476348 ], [ 3404900.1974876495, 5809887.5124256918 ], [ 3405503.5945286378, 5816570.4759445861 ], [ 3420259.1575914957, 5803751.5274206437 ], [ 3425492.757720896, 5805288.4202747019 ], [ 3430006.2427960923, 5797970.8222356346 ], [ 3424868.898425628, 5786447.4626845969 ], [ 3432793.2771179159, 5782578.7181442473 ], [ 3430848.1481474047, 5776941.4217684427 ], [ 3424043.0116273453, 5773317.3629900236 ], [ 3429320.229406042, 5767538.2512281965 ], [ 3443866.3179077418, 5772309.6550858961 ], [ 3449806.276862137, 5777869.5358370543 ], [ 3458464.1366839507, 5775302.8801533738 ], [ 3465646.1563843205, 5783918.4884388642 ], [ 3463355.0737599577, 5785668.4196412982 ], [ 3461024.8513124967, 5803711.8401982216 ], [ 3454665.8384169149, 5807279.4963338831 ], [ 3452513.9193353336, 5813170.6235983437 ], [ 3462486.1358933239, 5813475.3430145392 ], [ 3465828.1105889371, 5819223.8873425638 ], [ 3475033.4589592386, 5821106.0705908509 ], [ 3479867.1735936282, 5816213.7362615485 ], [ 3480044.9075819287, 5808105.8626193544 ], [ 3488204.8196057775, 5806021.1578105967 ], [ 3497362.7964672055, 5808264.4660474733 ], [ 3505512.7507341616, 5818113.1914334437 ], [ 3508734.1906899149, 5808202.5161512913 ], [ 3501228.9466671981, 5800697.4286702024 ], [ 3498856.9730779752, 5792231.21404994 ], [ 3504814.6166028902, 5789111.4519898761 ], [ 3499183.6395015051, 5783721.9442453487 ], [ 3499759.3356492291, 5777670.6405627411 ], [ 3509276.9756461484, 5777434.9458997874 ], [ 3513012.1422050977, 5771107.5125731593 ], [ 3511749.4542092225, 5761204.0843012407 ], [ 3519653.3203679961, 5759759.9832918076 ], [ 3517560.304288995, 5754143.8004587572 ], [ 3523025.290853254, 5752320.4393789358 ], [ 3522181.9700715416, 5746808.8859115178 ], [ 3531347.6256220187, 5748185.8522464707 ], [ 3530138.6548991445, 5737303.7716195555 ], [ 3525818.084766577, 5723573.9568773992 ], [ 3530455.4859661362, 5723798.1047803881 ], [ 3528924.4766209414, 5721351.5218874197 ], [ 3523070.5236526416, 5719044.8562405137 ], [ 3524603.0147794806, 5717491.2277388889 ], [ 3520561.9854983916, 5709122.9644377604 ], [ 3515576.2447461095, 5705707.6914275726 ], [ 3514925.1214373852, 5702549.6671367111 ], [ 3510983.0960355494, 5700337.8754208274 ], [ 3507554.9044111446, 5700780.0367017193 ], [ 3505875.6567809652, 5705238.8299312703 ], [ 3501068.9273003335, 5708144.4145278726 ], [ 3493481.3893631604, 5705006.7893563118 ], [ 3495486.2723003584, 5695033.5096952291 ], [ 3487092.8830240588, 5693984.3327005329 ], [ 3478098.1711568064, 5691979.9560178882 ], [ 3470564.1349909166, 5682285.2168887602 ], [ 3472321.6289579044, 5678561.7930466589 ], [ 3480242.9781700782, 5681704.1365179941 ], [ 3482531.6234846874, 5670756.3682026779 ], [ 3478703.4031630475, 5666065.6576269772 ], [ 3479299.5390543016, 5663197.6244847337 ], [ 3476066.6090996503, 5661389.358788928 ], [ 3466708.3078764328, 5661993.1278259987 ], [ 3468070.104050932, 5653902.1312206704 ], [ 3460909.0517494865, 5642310.59995 ], [ 3455235.7066972801, 5636303.2577909315 ], [ 3450712.3612730294, 5638488.3772231052 ], [ 3443440.0761954072, 5632575.1928710276 ], [ 3439308.1902859993, 5627939.3903268781 ], [ 3441783.7754965182, 5623139.8062560717 ], [ 3438838.0181758674, 5616717.762588284 ] ] ] } }, { "type": "Feature", "properties": { "cat": "12", "adm1_code": "DEU-1573", "OBJECTID_1": "1516", "diss_me": "1573", "adm1_cod_1": "DEU-1573", "iso_3166_2": "DE-", "wikipedia": null, "iso_a2": "DE", "adm0_sr": "1", "name": "Baden-Württemberg", "name_alt": null, "name_local": null, "type": "Land", "type_en": "State", "code_local": null, "code_hasc": "DE.BW", "note": null, "hasc_maybe": null, "region": null, "region_cod": null, "provnum_ne": "15", "gadm_level": "1", "check_me": "20", "scalerank": "3", "datarank": "3", "abbrev": null, "postal": "BW", "area_sqkm": "0", "sameascity": "-99", "labelrank": "3", "featurecla": "Admin-1 scale rank", "name_len": "17", "mapcolor9": "5", "mapcolor13": "1", "fips": "GM01", "fips_alt": null, "woe_id": "2345481", "woe_label": "Baden-Wurttemberg, DE, Germany", "woe_name": "Baden-Württemberg", "latitude": "48.59", "longitude": "9.00328", "sov_a3": "DEU", "adm0_a3": "DEU", "adm0_label": "2", "admin": "Germany", "geonunit": "Germany", "gu_a3": "DEU", "gn_id": "2953481", "gn_name": "Baden-Wuerttemberg", "gns_id": "-1743106", "gns_name": "Baden-Wurttemberg", "gn_level": "1", "gn_region": null, "gn_a1_code": "DE.01", "region_sub": null, "sub_code": null, "gns_level": "1", "gns_lang": "zho", "gns_adm1": "GM01", "gns_region": null }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 3441413.1035388974, 5424738.8449200103 ], [ 3462173.0401205476, 5462228.3351949146 ], [ 3463300.0906483554, 5478415.3615263375 ], [ 3457830.8811206338, 5493673.0699443156 ], [ 3463711.7080498426, 5492955.3966393284 ], [ 3468596.5891445591, 5487801.5832253546 ], [ 3472314.7994839814, 5489979.6376655754 ], [ 3471193.28598797, 5497091.6041746819 ], [ 3476270.1188411755, 5498354.3952498306 ], [ 3479600.3046743623, 5489358.4316641716 ], [ 3492069.8971853391, 5485360.722180808 ], [ 3489052.2447243668, 5482837.3566248845 ], [ 3486398.2881462742, 5474042.1804142566 ], [ 3488841.5516411392, 5473151.6657701498 ], [ 3496055.1333653242, 5479681.4650302604 ], [ 3496946.0138544249, 5485085.7153924536 ], [ 3502541.2115189768, 5484821.1008404233 ], [ 3509069.49997, 5486471.6288755341 ], [ 3505792.5326571916, 5492886.3640927905 ], [ 3508004.7275028112, 5493032.8392732944 ], [ 3518325.600151998, 5493330.7812480545 ], [ 3520685.2364013265, 5498793.0887854239 ], [ 3527193.7326927111, 5501474.0234873584 ], [ 3529207.3879846265, 5500329.3255963055 ], [ 3530810.1855651978, 5507912.8665117286 ], [ 3522607.3664702717, 5510533.3209668808 ], [ 3522498.7099780627, 5514265.6125144539 ], [ 3525335.9484673571, 5516510.933784782 ], [ 3535345.8245763546, 5517624.6485599363 ], [ 3543354.8998105251, 5514967.3727186071 ], [ 3546539.9080542442, 5516416.3428387139 ], [ 3545480.2621061597, 5506998.8894933118 ], [ 3548880.8715447127, 5506083.0556571763 ], [ 3551176.5391815607, 5509357.2814978566 ], [ 3552489.5273762518, 5505757.5850912258 ], [ 3557139.2306370987, 5509661.1895087473 ], [ 3560005.6028370573, 5506214.9098258298 ], [ 3560208.8010403323, 5502015.7024308359 ], [ 3563761.6499401266, 5497211.0637213998 ], [ 3560708.0498845158, 5490710.2006405825 ], [ 3561886.1846542899, 5489700.4107388947 ], [ 3566013.3375535863, 5493864.1031941669 ], [ 3567840.6018891246, 5483063.8994000871 ], [ 3573881.9593119058, 5482987.1144731594 ], [ 3577176.3558150646, 5486245.6809542198 ], [ 3578523.3819369273, 5490167.6813984727 ], [ 3581644.3427704326, 5486557.8843275979 ], [ 3580825.872080544, 5478633.7955291895 ], [ 3582846.7077056891, 5477603.5611078702 ], [ 3585018.2661472317, 5472469.5360561004 ], [ 3581463.7540525994, 5470771.6749604018 ], [ 3584382.777422816, 5460337.6364304721 ], [ 3582413.875438883, 5459974.4160899296 ], [ 3583564.5297398535, 5452545.6756128054 ], [ 3592245.8891499159, 5443944.4963075556 ], [ 3593147.1400163225, 5435435.6389526604 ], [ 3599011.9484724626, 5433669.2124226196 ], [ 3606859.6900644084, 5423359.0073309727 ], [ 3607096.9641639437, 5411585.8563470664 ], [ 3605288.9192014784, 5404018.894093167 ], [ 3610040.9729735483, 5396732.7561689578 ], [ 3605494.6288470197, 5393679.6094944002 ], [ 3604817.5383069692, 5396914.4062103806 ], [ 3601043.7237907918, 5392675.2968838485 ], [ 3595531.1762953508, 5396915.3128908044 ], [ 3593537.9326087711, 5394395.9917849256 ], [ 3594990.0970565821, 5390440.2955783168 ], [ 3598254.0374123072, 5388042.2132638032 ], [ 3596820.2459671348, 5386336.2469680198 ], [ 3597104.90080478, 5377768.9604256051 ], [ 3592397.2803958142, 5374804.8767391955 ], [ 3585285.3429935728, 5370319.6556346128 ], [ 3583771.9889397854, 5372636.0281001786 ], [ 3578955.5050633829, 5370940.3940610792 ], [ 3572624.432680374, 5360747.4124220517 ], [ 3578187.6194506902, 5352316.3667016691 ], [ 3582536.9707668643, 5334936.9094386967 ], [ 3584603.1174242902, 5330438.8159838542 ], [ 3584271.0151908011, 5322385.9982684879 ], [ 3581170.5874427473, 5314040.8154020924 ], [ 3582885.1004398358, 5311565.5902857054 ], [ 3582134.3343479666, 5304897.8270773264 ], [ 3584580.1453866977, 5298103.7145693144 ], [ 3580930.5669586663, 5294773.3959587673 ], [ 3583494.2047532503, 5292617.613166254 ], [ 3584928.4473972623, 5283809.5219843267 ], [ 3582086.3286339194, 5282857.9677951159 ], [ 3580805.8182427208, 5279501.6412465433 ], [ 3579419.406908934, 5282654.3130722083 ], [ 3571596.2949043298, 5280467.0692903223 ], [ 3564921.0598903387, 5282095.2396906596 ], [ 3553760.0831742487, 5274202.1924148183 ], [ 3551119.3500004988, 5274925.3375281552 ], [ 3545727.4155536136, 5271763.9444706095 ], [ 3538108.586976225, 5278216.6571631031 ], [ 3524201.4871307504, 5282560.200732233 ], [ 3505381.2646053382, 5297500.5593641382 ], [ 3513744.3973849737, 5281261.0549703501 ], [ 3501245.2564575989, 5282187.1291832635 ], [ 3492954.4217438856, 5279178.0382866459 ], [ 3470156.9156570975, 5295126.922488031 ], [ 3459797.2292908044, 5291776.8305603499 ], [ 3454288.3872752469, 5280872.8088062005 ], [ 3467574.511019954, 5280502.1190058449 ], [ 3458547.4047226133, 5271815.2668946693 ], [ 3438287.8564351345, 5275499.0383446272 ], [ 3431325.8307742607, 5270193.2645433815 ], [ 3418142.7353061661, 5269604.711872451 ], [ 3411241.1816176744, 5273572.6100377999 ], [ 3400901.6777409539, 5268058.8002021173 ], [ 3389002.1904692957, 5282303.4787578518 ], [ 3389551.2422212488, 5294829.8907658765 ], [ 3396957.1488953112, 5319103.1428725459 ], [ 3393692.7435733448, 5329429.7595380237 ], [ 3407518.0857715937, 5358145.5869738301 ], [ 3406601.0067125871, 5363861.6629127022 ], [ 3412357.6879469999, 5376321.143464623 ], [ 3412286.4552563648, 5386948.5981052546 ], [ 3424287.4372463357, 5402595.5740310866 ], [ 3434119.715337595, 5409591.547999708 ], [ 3441413.1035388974, 5424738.8449200103 ] ] ] } }, { "type": "Feature", "properties": { "cat": "13", "adm1_code": "DEU-1574", "OBJECTID_1": "1517", "diss_me": "1574", "adm1_cod_1": "DEU-1574", "iso_3166_2": "DE-", "wikipedia": null, "iso_a2": "DE", "adm0_sr": "1", "name": "Hessen", "name_alt": "Hesse", "name_local": null, "type": "Land", "type_en": "State", "code_local": null, "code_hasc": "DE.HE", "note": null, "hasc_maybe": null, "region": null, "region_cod": null, "provnum_ne": "19", "gadm_level": "1", "check_me": "20", "scalerank": "3", "datarank": "3", "abbrev": null, "postal": "HE", "area_sqkm": "0", "sameascity": "-99", "labelrank": "3", "featurecla": "Admin-1 scale rank", "name_len": "6", "mapcolor9": "5", "mapcolor13": "1", "fips": "GM05", "fips_alt": null, "woe_id": "2345485", "woe_label": "Hesse, DE, Germany", "woe_name": "Hessen", "latitude": "50.6098", "longitude": "8.958729999999999", "sov_a3": "DEU", "adm0_a3": "DEU", "adm0_label": "2", "admin": "Germany", "geonunit": "Germany", "gu_a3": "DEU", "gn_id": "2905330", "gn_name": "Land Hessen", "gns_id": "-1791414", "gns_name": "Hessen", "gn_level": "1", "gn_region": null, "gn_a1_code": "DE.05", "region_sub": null, "sub_code": null, "gns_level": "1", "gns_lang": "zho", "gns_adm1": "GM05", "gns_region": null }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 3574817.4056062475, 5598257.4420703379 ], [ 3571078.9444448808, 5590634.6929859295 ], [ 3561061.3854291611, 5585008.1051580943 ], [ 3554503.9289892185, 5585281.3163722064 ], [ 3553322.554148009, 5574531.7938179336 ], [ 3548486.4890143084, 5571588.4039183576 ], [ 3547168.3691143082, 5566552.8579348298 ], [ 3536454.1115450612, 5566675.5268396242 ], [ 3535646.7908835602, 5560063.0808881437 ], [ 3537768.1315709203, 5559166.6873750146 ], [ 3536241.2263133698, 5552425.6098567126 ], [ 3529102.3323403024, 5550628.9499717057 ], [ 3523525.7989939875, 5555823.0760990549 ], [ 3516350.8844319284, 5556299.1656100359 ], [ 3510910.1904314789, 5552779.3399014063 ], [ 3507714.830290556, 5554233.6068744743 ], [ 3500735.6345797917, 5551049.7819365254 ], [ 3498323.6014661267, 5546150.3840967752 ], [ 3501924.5455787638, 5545078.5930615086 ], [ 3501918.8327689632, 5529980.5791802146 ], [ 3505048.3221994299, 5521439.4779663766 ], [ 3510146.6536235306, 5516639.2130606221 ], [ 3509617.7330651209, 5506477.500841843 ], [ 3508038.9263903359, 5500227.9951685322 ], [ 3505250.4010145608, 5497006.3185954299 ], [ 3508004.7275028112, 5493032.8392732944 ], [ 3505792.5326571916, 5492886.3640927905 ], [ 3509069.49997, 5486471.6288755341 ], [ 3502541.2115189768, 5484821.1008404233 ], [ 3496946.0138544249, 5485085.7153924536 ], [ 3496055.1333653242, 5479681.4650302604 ], [ 3488841.5516411392, 5473151.6657701498 ], [ 3486398.2881462742, 5474042.1804142566 ], [ 3489052.2447243668, 5482837.3566248845 ], [ 3492069.8971853391, 5485360.722180808 ], [ 3479600.3046743623, 5489358.4316641716 ], [ 3476270.1188411755, 5498354.3952498306 ], [ 3471193.28598797, 5497091.6041746819 ], [ 3472314.7994839814, 5489979.6376655754 ], [ 3468596.5891445591, 5487801.5832253546 ], [ 3463711.7080498426, 5492955.3966393284 ], [ 3457830.8811206338, 5493673.0699443156 ], [ 3453841.9804025455, 5503159.6692345878 ], [ 3458495.5004734448, 5514559.1553209331 ], [ 3452586.3951217886, 5528367.2193213543 ], [ 3452319.9802185413, 5536002.3603432896 ], [ 3444709.495428592, 5544811.9771885592 ], [ 3440828.5246273172, 5545698.4037055941 ], [ 3422434.0216260618, 5537934.5923802424 ], [ 3412023.655965697, 5546950.898131174 ], [ 3418083.6548039587, 5553784.5111388061 ], [ 3422876.6023717704, 5552937.0920662321 ], [ 3423056.5161700593, 5563089.1127727879 ], [ 3437471.8600296136, 5571557.030202195 ], [ 3432385.6108222404, 5582006.6984252753 ], [ 3427486.3471034807, 5584796.3907665983 ], [ 3429406.6760302559, 5598670.4881470185 ], [ 3436698.9808041384, 5599221.561436912 ], [ 3440788.8477046415, 5605767.4036669126 ], [ 3438838.0181758674, 5616717.762588284 ], [ 3441783.7754965182, 5623139.8062560717 ], [ 3439308.1902859993, 5627939.3903268781 ], [ 3443440.0761954072, 5632575.1928710276 ], [ 3450712.3612730294, 5638488.3772231052 ], [ 3455235.7066972801, 5636303.2577909315 ], [ 3460909.0517494865, 5642310.59995 ], [ 3468070.104050932, 5653902.1312206704 ], [ 3466708.3078764328, 5661993.1278259987 ], [ 3476066.6090996503, 5661389.358788928 ], [ 3479299.5390543016, 5663197.6244847337 ], [ 3478703.4031630475, 5666065.6576269772 ], [ 3482531.6234846874, 5670756.3682026779 ], [ 3480242.9781700782, 5681704.1365179941 ], [ 3472321.6289579044, 5678561.7930466589 ], [ 3470564.1349909166, 5682285.2168887602 ], [ 3478098.1711568064, 5691979.9560178882 ], [ 3487092.8830240588, 5693984.3327005329 ], [ 3495486.2723003584, 5695033.5096952291 ], [ 3493481.3893631604, 5705006.7893563118 ], [ 3501068.9273003335, 5708144.4145278726 ], [ 3505875.6567809652, 5705238.8299312703 ], [ 3507554.9044111446, 5700780.0367017193 ], [ 3510983.0960355494, 5700337.8754208274 ], [ 3514925.1214373852, 5702549.6671367111 ], [ 3515576.2447461095, 5705707.6914275726 ], [ 3520561.9854983916, 5709122.9644377604 ], [ 3524603.0147794806, 5717491.2277388889 ], [ 3523070.5236526416, 5719044.8562405137 ], [ 3528924.4766209414, 5721351.5218874197 ], [ 3530455.4859661362, 5723798.1047803881 ], [ 3534226.8905062792, 5721936.5230261441 ], [ 3538023.0338339861, 5722871.9953770936 ], [ 3543159.0281349747, 5721711.6281607887 ], [ 3547558.9102820596, 5715352.1442766832 ], [ 3544642.9489397793, 5715322.7546808505 ], [ 3541427.0530289891, 5708583.377967434 ], [ 3544378.4200985883, 5703761.8090163535 ], [ 3544011.9748935108, 5696296.3759464175 ], [ 3540504.4158721655, 5696031.611479966 ], [ 3539668.6032787417, 5689741.4874358121 ], [ 3547588.8435190055, 5686762.122029556 ], [ 3549624.2787566381, 5684174.2859604955 ], [ 3553132.3127446934, 5686190.0345269963 ], [ 3549697.4774175971, 5692215.0098583559 ], [ 3557474.3696721108, 5696424.5482411729 ], [ 3559629.6316318894, 5693246.9378643679 ], [ 3560024.2843798888, 5697322.0626973845 ], [ 3562896.1835305644, 5698276.577285951 ], [ 3566077.5702712322, 5694404.3259839863 ], [ 3564312.7318813005, 5691987.1708498131 ], [ 3566034.6785900695, 5685742.3244812824 ], [ 3573198.2449193862, 5682792.5178340524 ], [ 3575528.1127700228, 5676892.8194844155 ], [ 3586006.8405960761, 5672728.1137020076 ], [ 3583764.4426440145, 5665075.652521369 ], [ 3582117.5519434675, 5668271.7900177678 ], [ 3578798.7772993003, 5667248.2472233446 ], [ 3581851.2486287467, 5665001.7837908594 ], [ 3580088.3767739702, 5658270.1789641948 ], [ 3583664.5405275868, 5657171.22150579 ], [ 3583383.0604236061, 5651698.8799884953 ], [ 3573767.2356044655, 5652811.9528761469 ], [ 3571869.1755224951, 5650014.132858675 ], [ 3573185.653422052, 5645450.4846842652 ], [ 3567177.6235405561, 5645472.6461312221 ], [ 3568515.6242985181, 5641517.5843244502 ], [ 3570742.7824758831, 5643197.4355528848 ], [ 3574302.524281817, 5638981.32835175 ], [ 3571762.6630286835, 5633277.0861994652 ], [ 3566901.1734591089, 5631378.4425946446 ], [ 3566734.0008735228, 5627081.9127885103 ], [ 3564520.4169722269, 5617718.1240404369 ], [ 3562073.2358358465, 5612230.2116798423 ], [ 3566738.0713194893, 5610456.5903575188 ], [ 3567228.3343895781, 5614423.7075280305 ], [ 3570507.0136910323, 5615967.2634153562 ], [ 3574366.9579191515, 5615279.0739612924 ], [ 3576134.9967648848, 5610012.6024658671 ], [ 3574029.2307750289, 5608145.7005352741 ], [ 3574817.4056062475, 5598257.4420703379 ] ] ] } }, { "type": "Feature", "properties": { "cat": "15", "adm1_code": "DEU-1576", "OBJECTID_1": "6321", "diss_me": "1576", "adm1_cod_1": "DEU-1576", "iso_3166_2": "DE-", "wikipedia": null, "iso_a2": "DE", "adm0_sr": "4", "name": "Niedersachsen", "name_alt": "Lower Saxony", "name_local": null, "type": "Land", "type_en": "State", "code_local": null, "code_hasc": "DE.NI", "note": null, "hasc_maybe": null, "region": null, "region_cod": null, "provnum_ne": "4", "gadm_level": "1", "check_me": "20", "scalerank": "3", "datarank": "3", "abbrev": null, "postal": "NI", "area_sqkm": "0", "sameascity": "-99", "labelrank": "3", "featurecla": "Admin-1 scale rank", "name_len": "13", "mapcolor9": "5", "mapcolor13": "1", "fips": "GM06", "fips_alt": null, "woe_id": "2345486", "woe_label": "Lower Saxony, DE, Germany", "woe_name": "Niedersachsen", "latitude": "52.775", "longitude": "8.861840000000001", "sov_a3": "DEU", "adm0_a3": "DEU", "adm0_label": "2", "admin": "Germany", "geonunit": "Germany", "gu_a3": "DEU", "gn_id": "2862926", "gn_name": "Land Niedersachsen", "gns_id": "-1833951", "gns_name": "Niedersachsen", "gn_level": "1", "gn_region": null, "gn_a1_code": "DE.06", "region_sub": null, "sub_code": null, "gns_level": "1", "gns_lang": "zho", "gns_adm1": "GM06", "gns_region": null }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 3550632.9068574295, 5935231.2751757316 ], [ 3556965.7078730096, 5922524.892838276 ], [ 3565509.5510616428, 5920653.881467809 ], [ 3571948.8982978156, 5924735.9639584571 ], [ 3578903.5037427521, 5919670.0220713178 ], [ 3588249.9672536696, 5922981.0870051431 ], [ 3601710.25338207, 5916543.2227652604 ], [ 3606560.2346579311, 5916586.2707823943 ], [ 3639785.0696396269, 5892100.1719632307 ], [ 3643302.4959958429, 5895374.819350563 ], [ 3652231.3320433423, 5889511.3574547973 ], [ 3661924.2617668891, 5885747.2731848815 ], [ 3673094.9477649452, 5881900.7282523569 ], [ 3671922.8545249584, 5878200.7057062704 ], [ 3668261.3414150965, 5877081.257549637 ], [ 3668159.9163241819, 5870732.0661629066 ], [ 3662276.0322155575, 5866329.1451455932 ], [ 3651333.2684875731, 5862791.5747473724 ], [ 3648934.7473273841, 5864932.535072092 ], [ 3635509.5308777369, 5866108.0874277316 ], [ 3632019.5397876874, 5860810.2693054294 ], [ 3618718.1755545707, 5857485.6109101465 ], [ 3618514.5615372388, 5851704.8690217081 ], [ 3621666.099287462, 5844655.1274119448 ], [ 3624130.2654388198, 5843359.7207674412 ], [ 3630000.1103232088, 5833404.7902165819 ], [ 3633938.0988071673, 5832809.371821166 ], [ 3632514.9164062119, 5825233.8930791495 ], [ 3635920.4080108143, 5820057.3019131161 ], [ 3631772.1436179769, 5815934.1879419796 ], [ 3641160.67654408, 5806181.3832803816 ], [ 3636231.0192434615, 5802737.3780365922 ], [ 3637457.8965149773, 5796638.6178118531 ], [ 3642306.9390903679, 5789843.2179832282 ], [ 3638079.8541451683, 5785463.3934184005 ], [ 3639545.4940197729, 5779363.0596436961 ], [ 3633454.749544749, 5775329.8780293567 ], [ 3634968.6410203772, 5771763.776092371 ], [ 3626913.4232676532, 5769958.5594496056 ], [ 3613361.9478737903, 5768780.5365629746 ], [ 3607797.4707193407, 5764671.3332481934 ], [ 3613030.801248, 5759432.3883808982 ], [ 3610948.6257142145, 5755584.0213080486 ], [ 3612899.5315223499, 5751066.7033877522 ], [ 3608880.3975983351, 5746675.8150769975 ], [ 3609394.947188708, 5739233.3662485098 ], [ 3615012.1854641521, 5731490.6919269329 ], [ 3617510.6850254778, 5723772.3194554569 ], [ 3611631.7745252792, 5716427.2014618823 ], [ 3605003.1711668326, 5714474.0370600866 ], [ 3598512.0890342994, 5718303.0865497692 ], [ 3595277.3502693637, 5717559.7764455471 ], [ 3594658.1928674169, 5712729.2957265247 ], [ 3586116.3035516692, 5704751.2523524342 ], [ 3582986.9514478082, 5705509.9902624423 ], [ 3579943.6412702766, 5700165.6181104556 ], [ 3572713.6175525445, 5700091.8007927611 ], [ 3566077.5702712322, 5694404.3259839863 ], [ 3562896.1835305644, 5698276.577285951 ], [ 3560024.2843798888, 5697322.0626973845 ], [ 3559629.6316318894, 5693246.9378643679 ], [ 3557474.3696721108, 5696424.5482411729 ], [ 3549697.4774175971, 5692215.0098583559 ], [ 3553132.3127446934, 5686190.0345269963 ], [ 3549624.2787566381, 5684174.2859604955 ], [ 3547588.8435190055, 5686762.122029556 ], [ 3539668.6032787417, 5689741.4874358121 ], [ 3540504.4158721655, 5696031.611479966 ], [ 3544011.9748935108, 5696296.3759464175 ], [ 3544378.4200985883, 5703761.8090163535 ], [ 3541427.0530289891, 5708583.377967434 ], [ 3544642.9489397793, 5715322.7546808505 ], [ 3547558.9102820596, 5715352.1442766832 ], [ 3543159.0281349747, 5721711.6281607887 ], [ 3538023.0338339861, 5722871.9953770936 ], [ 3534226.8905062792, 5721936.5230261441 ], [ 3530455.4859661362, 5723798.1047803881 ], [ 3525818.084766577, 5723573.9568773992 ], [ 3530138.6548991445, 5737303.7716195555 ], [ 3531347.6256220187, 5748185.8522464707 ], [ 3522181.9700715416, 5746808.8859115178 ], [ 3523025.290853254, 5752320.4393789358 ], [ 3517560.304288995, 5754143.8004587572 ], [ 3519653.3203679961, 5759759.9832918076 ], [ 3511749.4542092225, 5761204.0843012407 ], [ 3513012.1422050977, 5771107.5125731593 ], [ 3509276.9756461484, 5777434.9458997874 ], [ 3499759.3356492291, 5777670.6405627411 ], [ 3499183.6395015051, 5783721.9442453487 ], [ 3504814.6166028902, 5789111.4519898761 ], [ 3498856.9730779752, 5792231.21404994 ], [ 3501228.9466671981, 5800697.4286702024 ], [ 3508734.1906899149, 5808202.5161512913 ], [ 3505512.7507341616, 5818113.1914334437 ], [ 3497362.7964672055, 5808264.4660474733 ], [ 3488204.8196057775, 5806021.1578105967 ], [ 3480044.9075819287, 5808105.8626193544 ], [ 3479867.1735936282, 5816213.7362615485 ], [ 3475033.4589592386, 5821106.0705908509 ], [ 3465828.1105889371, 5819223.8873425638 ], [ 3462486.1358933239, 5813475.3430145392 ], [ 3452513.9193353336, 5813170.6235983437 ], [ 3454665.8384169149, 5807279.4963338831 ], [ 3461024.8513124967, 5803711.8401982216 ], [ 3463355.0737599577, 5785668.4196412982 ], [ 3465646.1563843205, 5783918.4884388642 ], [ 3458464.1366839507, 5775302.8801533738 ], [ 3449806.276862137, 5777869.5358370543 ], [ 3443866.3179077418, 5772309.6550858961 ], [ 3429320.229406042, 5767538.2512281965 ], [ 3424043.0116273453, 5773317.3629900236 ], [ 3430848.1481474047, 5776941.4217684427 ], [ 3432793.2771179159, 5782578.7181442473 ], [ 3424868.898425628, 5786447.4626845969 ], [ 3430006.2427960923, 5797970.8222356346 ], [ 3425492.757720896, 5805288.4202747019 ], [ 3420259.1575914957, 5803751.5274206437 ], [ 3405503.5945286378, 5816570.4759445861 ], [ 3404900.1974876495, 5809887.5124256918 ], [ 3392025.9757624478, 5798862.5502476348 ], [ 3384012.9715766446, 5794350.67907946 ], [ 3365215.7405231372, 5791134.4245591592 ], [ 3362238.7512589549, 5814902.0341639277 ], [ 3343415.361510009, 5818165.0608863141 ], [ 3349108.9968962353, 5836405.5838817265 ], [ 3367138.1978925127, 5836588.6911443137 ], [ 3377978.5938670379, 5871789.2388360733 ], [ 3379497.5438460801, 5902801.0457823742 ], [ 3386958.4166595987, 5912384.9993838463 ], [ 3368491.1976311509, 5917721.1951329885 ], [ 3373321.4256524909, 5941022.1998136546 ], [ 3382766.2664820482, 5949643.7128865141 ], [ 3430791.1632321044, 5954859.5760023473 ], [ 3444864.3727772231, 5935897.0754981693 ], [ 3437552.1160068554, 5930291.0044142148 ], [ 3447162.8743102439, 5920011.8600732181 ], [ 3454923.7023514169, 5930895.0261556925 ], [ 3450845.0647243001, 5941532.4429137409 ], [ 3470633.9099116446, 5931964.1095968718 ], [ 3473640.484591655, 5928668.0521080317 ], [ 3476062.7607371015, 5931567.9643862806 ], [ 3475199.3643792034, 5941403.5468862616 ], [ 3467778.8065959434, 5942795.0136931287 ], [ 3471923.858467692, 5969539.9600837333 ], [ 3512239.2302019894, 5970199.5365770916 ], [ 3518067.5505578616, 5970323.7222689753 ], [ 3519393.374612669, 5968304.8412422938 ], [ 3537537.0645105471, 5940886.6664245054 ], [ 3550632.9068574295, 5935231.2751757316 ] ], [ [ 3470648.2738475171, 5897758.2648433764 ], [ 3467459.3721810151, 5896352.760481637 ], [ 3473997.6676363903, 5894121.7778715324 ], [ 3480079.7036048737, 5882942.5270079691 ], [ 3480045.6539797694, 5879745.4698925437 ], [ 3491011.2500537927, 5879309.938417878 ], [ 3494265.4796796911, 5876757.5917747803 ], [ 3498170.5207677865, 5879865.4094565529 ], [ 3498339.9930427396, 5889002.5856371522 ], [ 3494135.47721026, 5889523.4182940461 ], [ 3482994.3597778352, 5895366.5686806049 ], [ 3470648.2738475171, 5897758.2648433764 ] ] ] } }, { "type": "Feature", "properties": { "cat": "16", "adm1_code": "DEU-1577", "OBJECTID_1": "1519", "diss_me": "1577", "adm1_cod_1": "DEU-1577", "iso_3166_2": "DE-", "wikipedia": null, "iso_a2": "DE", "adm0_sr": "1", "name": "Thüringen", "name_alt": "Thuringia", "name_local": null, "type": "Land", "type_en": "State", "code_local": null, "code_hasc": "DE.TH", "note": null, "hasc_maybe": null, "region": null, "region_cod": null, "provnum_ne": "20", "gadm_level": "1", "check_me": "20", "scalerank": "3", "datarank": "3", "abbrev": null, "postal": "TH", "area_sqkm": "0", "sameascity": "-99", "labelrank": "3", "featurecla": "Admin-1 scale rank", "name_len": "9", "mapcolor9": "5", "mapcolor13": "1", "fips": "GM15", "fips_alt": null, "woe_id": "2345495", "woe_label": "Thuringia, DE, Germany", "woe_name": "Thüringen", "latitude": "50.9052", "longitude": "11.0976", "sov_a3": "DEU", "adm0_a3": "DEU", "adm0_label": "2", "admin": "Germany", "geonunit": "Germany", "gu_a3": "DEU", "gn_id": "2822542", "gn_name": "Freistaat Thuringen", "gns_id": "-1874498", "gns_name": "Thuringen", "gn_level": "1", "gn_region": null, "gn_a1_code": "DE.15", "region_sub": null, "sub_code": null, "gns_level": "1", "gns_lang": "zho", "gns_adm1": "GM15", "gns_region": null }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 3728697.3410449172, 5665894.003271156 ], [ 3744176.9108976396, 5663770.8769144556 ], [ 3748992.2363486462, 5654296.6612281911 ], [ 3756509.8321495252, 5646715.3534608511 ], [ 3748218.9617048698, 5643708.7736610528 ], [ 3741232.331247753, 5637557.6046812981 ], [ 3729672.7793026781, 5633487.7130454583 ], [ 3728832.1004670118, 5625615.0790818483 ], [ 3734759.1665375163, 5620512.1446339721 ], [ 3730125.0831804471, 5614992.2367806258 ], [ 3725457.3308588881, 5614794.9990650406 ], [ 3722819.9620642923, 5607572.8059453368 ], [ 3716102.6109797498, 5603841.3164922409 ], [ 3711847.4358901861, 5605288.3101831255 ], [ 3707593.3788553113, 5598689.8272855673 ], [ 3706456.6357335709, 5590490.103557012 ], [ 3701124.6336581926, 5587599.6888292851 ], [ 3698749.3649529289, 5590010.1434312938 ], [ 3680049.0477793319, 5585432.8105548657 ], [ 3676626.67844284, 5587664.6132867765 ], [ 3675994.001566703, 5590758.7787582632 ], [ 3671958.3111791341, 5592357.348135368 ], [ 3672121.9713641307, 5600160.7438294999 ], [ 3666404.0989981135, 5600603.8442229992 ], [ 3664336.9864799059, 5597622.3606871739 ], [ 3659605.2232868215, 5595262.2291009063 ], [ 3662238.810322443, 5582653.1846054439 ], [ 3660745.4664473836, 5573283.5300583346 ], [ 3656266.8897332451, 5572781.6523313411 ], [ 3652087.5666547911, 5576864.6365224905 ], [ 3653432.929886797, 5578485.1134469826 ], [ 3650284.4903703765, 5583030.593772362 ], [ 3645868.0769925839, 5580963.0357561549 ], [ 3641467.2303942246, 5581085.1458215769 ], [ 3638188.6970979096, 5585165.1007029591 ], [ 3627205.3913595304, 5584768.6555711003 ], [ 3622821.4798547197, 5581827.2477174671 ], [ 3622115.8301066705, 5577492.5262480676 ], [ 3631003.5069937492, 5572109.5285800658 ], [ 3631250.6056273249, 5568820.599108696 ], [ 3623502.8652153891, 5568663.640977269 ], [ 3623231.2025344539, 5565109.5173362065 ], [ 3615378.8061682819, 5566488.7467001993 ], [ 3613301.3204261549, 5577745.2251591412 ], [ 3610451.0868479265, 5581336.1181971366 ], [ 3606720.0805508858, 5580673.1137679294 ], [ 3603043.3838240071, 5585677.0623686798 ], [ 3599920.250670508, 5585260.6331641655 ], [ 3599433.8822761257, 5588255.5182174174 ], [ 3593561.5613371767, 5595872.3240806991 ], [ 3587805.4131642585, 5597748.057573312 ], [ 3584213.7888737433, 5602032.7751879171 ], [ 3579781.59930187, 5603487.1175612584 ], [ 3574817.4056062475, 5598257.4420703379 ], [ 3574029.2307750289, 5608145.7005352741 ], [ 3576134.9967648848, 5610012.6024658671 ], [ 3574366.9579191515, 5615279.0739612924 ], [ 3570507.0136910323, 5615967.2634153562 ], [ 3567228.3343895781, 5614423.7075280305 ], [ 3566738.0713194893, 5610456.5903575188 ], [ 3562073.2358358465, 5612230.2116798423 ], [ 3564520.4169722269, 5617718.1240404369 ], [ 3566734.0008735228, 5627081.9127885103 ], [ 3566901.1734591089, 5631378.4425946446 ], [ 3571762.6630286835, 5633277.0861994652 ], [ 3574302.524281817, 5638981.32835175 ], [ 3570742.7824758831, 5643197.4355528848 ], [ 3568515.6242985181, 5641517.5843244502 ], [ 3567177.6235405561, 5645472.6461312221 ], [ 3573185.653422052, 5645450.4846842652 ], [ 3571869.1755224951, 5650014.132858675 ], [ 3573767.2356044655, 5652811.9528761469 ], [ 3583383.0604236061, 5651698.8799884953 ], [ 3583664.5405275868, 5657171.22150579 ], [ 3580088.3767739702, 5658270.1789641948 ], [ 3581851.2486287467, 5665001.7837908594 ], [ 3578798.7772993003, 5667248.2472233446 ], [ 3582117.5519434675, 5668271.7900177678 ], [ 3583764.4426440145, 5665075.652521369 ], [ 3586006.8405960761, 5672728.1137020076 ], [ 3575528.1127700228, 5676892.8194844155 ], [ 3573198.2449193862, 5682792.5178340524 ], [ 3566034.6785900695, 5685742.3244812824 ], [ 3564312.7318813005, 5691987.1708498131 ], [ 3566077.5702712322, 5694404.3259839863 ], [ 3572713.6175525445, 5700091.8007927611 ], [ 3579943.6412702766, 5700165.6181104556 ], [ 3582986.9514478082, 5705509.9902624423 ], [ 3586116.3035516692, 5704751.2523524342 ], [ 3594658.1928674169, 5712729.2957265247 ], [ 3595277.3502693637, 5717559.7764455471 ], [ 3598512.0890342994, 5718303.0865497692 ], [ 3605003.1711668326, 5714474.0370600866 ], [ 3611631.7745252792, 5716427.2014618823 ], [ 3617510.6850254778, 5723772.3194554569 ], [ 3634798.3906453014, 5719458.0870596841 ], [ 3632504.2172296499, 5716768.8556063659 ], [ 3639072.4658529945, 5698871.2434897516 ], [ 3661128.3753905562, 5696690.098692731 ], [ 3671078.403472946, 5684562.7217747746 ], [ 3662981.3890735451, 5678154.1523908963 ], [ 3669281.7923881463, 5672052.1862729536 ], [ 3670003.9710097802, 5665298.3177191904 ], [ 3688360.598657859, 5665165.5228766529 ], [ 3691711.4629774042, 5657002.0672275824 ], [ 3700671.5497429045, 5657917.8290444724 ], [ 3711102.7786706178, 5650136.7946595009 ], [ 3721967.7634851355, 5650922.120966264 ], [ 3724984.3058717526, 5648366.5871077562 ], [ 3729823.6229192242, 5657699.0347429402 ], [ 3728697.3410449172, 5665894.003271156 ] ] ] } } ] } ================================================ FILE: src/comps/blurforwarder/BlurForwarder.ts ================================================ import {Directive, ElementRef, Renderer} from "@angular/core"; @Directive({ selector: 'input,select,textarea', host: { '(blur)': 'onBlur($event)' } }) export class BlurForwarder { constructor(private elRef: ElementRef) { } onBlur($event) { // this.renderer.invokeElementMethod(this.elRef.nativeElement, 'dispatchEvent', [new CustomEvent('input-blur', {bubbles: true})]); // or just // don't error out on older browsers just fail silently try { this.elRef.nativeElement.dispatchEvent(new CustomEvent('input-blur', { bubbles: true })); } catch (e){ } // you don't care about webworker compatibility } } ================================================ FILE: src/comps/connect-form/connect-form.ts ================================================ import {Directive, EventEmitter, Input, Output} from '@angular/core'; import {FormGroupDirective} from '@angular/forms'; import {Subscription} from 'rxjs/Subscription'; import {Actions} from '@ngrx/effects'; import {YellowPepperService} from "../../services/yellowpepper.service"; import * as _ from 'lodash'; import {ACTION_FORM_UPDATE} from "../../store/actions/appdb.actions"; const FORM_SUBMIT_SUCCESS = 'FORM_SUBMIT_SUCCESS'; const FORM_SUBMIT_ERROR = 'FORM_SUBMIT_ERROR'; export const formSuccessAction = path => ({ type: FORM_SUBMIT_SUCCESS, payload: { path } }); export const formErrorAction = (path, error) => ({ type: FORM_SUBMIT_ERROR, payload: { path, error } }); @Directive({ selector: '[connectForm]' }) export class ConnectFormDirective { @Input('connectForm') path: string; @Input() debounce: number = 300; @Output() error = new EventEmitter(); @Output() success = new EventEmitter(); formChange: Subscription; formSuccess: Subscription; formError: Subscription; constructor(private formGroupDirective: FormGroupDirective, private actions$: Actions, private yp: YellowPepperService) { } ngOnInit() { this.yp.ngrxStore.select(state => _.get(state, this.path)) .take(1) .subscribe((val: Map) => { this.formGroupDirective.form.patchValue(val.toJSON()); }); this.formChange = this.formGroupDirective.form.valueChanges .debounceTime(this.debounce) .subscribe(value => { // remove the first level of store path, i.e 'appDb.contact' becomes just 'connect' this.yp.ngrxStore.dispatch({ type: ACTION_FORM_UPDATE, payload: { value, path: this.path.split('.').slice(1, this.path.length - 1).join(), } }); }); this.formSuccess = this.actions$ .ofType(FORM_SUBMIT_SUCCESS) .filter(({payload}) => payload.path === this.path) .subscribe(() => { this.formGroupDirective.form.reset(); this.success.emit(); }); this.formError = this.actions$ .ofType(FORM_SUBMIT_ERROR) .filter(({payload}) => payload.path === this.path) .subscribe(({payload}) => { return this.error.emit(payload.error) }) } ngOnDestroy() { this.formChange.unsubscribe(); this.formError.unsubscribe(); this.formSuccess.unsubscribe(); } } ================================================ FILE: src/comps/contact-us/contact-us.ts ================================================ import {Component} from "@angular/core"; import {FormControl, FormGroup, Validators} from "@angular/forms"; import {YellowPepperService} from "../../services/yellowpepper.service"; import {EFFECT_CONTACT_US} from "../../store/effects/appdb.effects"; @Component({ selector: 'contact-us', template: `

        Success!
        {{error}}
        ` }) export class ContactUs { options = [{id: 1, label: 'Support'}, {id: 2, label: 'Sales'}] error; success; contactForm: FormGroup; constructor(private yp: YellowPepperService) { } onError(error) { this.error = error; } onSuccess(e) { this.success = true; } ngOnInit() { this.contactForm = new FormGroup({ email: new FormControl(null, Validators.email), message: new FormControl(null, Validators.required), category: new FormControl(this.options[1].id, Validators.required) // ,draft: new FormControl(false) }); } // blog ref: https://netbasal.com/understanding-the-comparefn-input-in-angular-v4-4a401ef4fc4c // the trackBy Input is for performance optimization, and the compareFn used to identify specific member compareFn(optionOne, optionTwo): boolean { return optionOne === optionTwo; } submit(e) { this.success = false; this.yp.ngrxStore.dispatch({ type: EFFECT_CONTACT_US }) } } ================================================ FILE: src/comps/disable-control/disable-control.ts ================================================ import {NgControl} from '@angular/forms'; import {Directive, Input} from "@angular/core"; @Directive({ selector: '[disableControl]' }) export class DisableControlDirective { @Input() set disableControl(condition: boolean) { const action = condition ? 'disable' : 'enable'; this.ngControl.control[action](); } constructor(private ngControl: NgControl) { } } ================================================ FILE: src/comps/draggable-list/draggable-list.ts ================================================ import {AfterViewInit, ChangeDetectionStrategy, Component, ElementRef, EventEmitter, Input, Output, TemplateRef} from "@angular/core"; import {Compbaser} from "ng-mslib"; import {List} from "immutable"; import {timeout} from "../../decorators/timeout-decorator"; @Component({ selector: 'draggable-list', changeDetection: ChangeDetectionStrategy.OnPush, styles: [` .dragch { float: right; padding-right: 10px; position: relative; top: 5px; } .lengthTimer { float: right; padding-right: 10px; } .listItems { cursor: pointer; } .listItems a i { display: inline; font-size: 40px; padding-right: 20px; } .listItems a span { display: inline; font-size: 1.5em; position: relative; top: -12px; } `], template: ` {{me}} `, }) export class DraggableList extends Compbaser implements AfterViewInit { private m_draggables; private target; private y; constructor(private el: ElementRef) { super(); } m_items: List m_selectedIdx = -1; @Input() customTemplate: TemplateRef; @Input() set items(i_items: List) { // this.m_selectedIdx = -1; this.m_items = i_items; this.createSortable(); } public deselect() { this.m_selectedIdx = -1; } @Output() onDragComplete: EventEmitter = new EventEmitter(); @Output() onItemSelected: EventEmitter = new EventEmitter(); private _onItemSelected(item, event, i) { this.m_selectedIdx = i; this.onItemSelected.emit({item, event, i}) } /** Create a draggable sortable list @method _createSortable @param {String} i_selector **/ @timeout(300) public createSortable() { var self = this; var selector = '.sortableList'; if (jQuery(selector).children().length == 0) return; var sortable = document.querySelector(selector); if (this.m_draggables) { this.m_draggables.forEach((drag) => { drag.kill() }); // var a = Draggable.get(".sortableList"); // var sortable = document.querySelector(selector); // var a = Draggable.get(sortable); // con(a); // Draggable.get(".sortableList").kill(); } self.m_draggables = Draggable.create(sortable.children, { type: "y", bounds: sortable, dragClickables: true, edgeResistance: 1, onPress: self._sortablePress, onDragStart: self._sortableDragStart, onDrag: self._sortableDrag, liveSnap: self._sortableSnap, onDragEnd: function () { self.m_selectedIdx = -1; var t = this.target, max = t.kids.length - 1, newIndex = Math.round(this.y / t.currentHeight); newIndex += (newIndex < 0 ? -1 : 0) + t.currentIndex; if (newIndex === max) { t.parentNode.appendChild(t); } else { t.parentNode.insertBefore(t, t.kids[newIndex + 1]); } TweenLite.set(t.kids, {yPercent: 0, overwrite: "all"}); TweenLite.set(t, {y: 0, color: ""}); var items = jQuery('.sortableList', self.el.nativeElement).children(); self.onDragComplete.emit(items) //_.each(self.m_draggables, function(i){ // this.enabled(false); //}); } }); } /** Sortable list on press @method _sortablePress **/ _sortablePress() { var t = this.target, i = 0, child = t; while (child = child.previousSibling) if (child.nodeType === 1) i++; t.currentIndex = i; t.currentHeight = t.offsetHeight; t.kids = [].slice.call(t.parentNode.children); // convert to array } /** Sortable drag list on press @method _sortableDragStart **/ _sortableDragStart() { TweenLite.set(this.target, {color: "#88CE02"}); } /** Sortable drag list @method _sortableDrag **/ _sortableDrag() { var t = this.target, elements = t.kids.slice(), // clone indexChange = Math.round(this.y / t.currentHeight), bound1 = t.currentIndex, bound2 = bound1 + indexChange; if (bound1 < bound2) { // moved down TweenLite.to(elements.splice(bound1 + 1, bound2 - bound1), 0.15, {yPercent: -100}); TweenLite.to(elements, 0.15, {yPercent: 0}); } else if (bound1 === bound2) { elements.splice(bound1, 1); TweenLite.to(elements, 0.15, {yPercent: 0}); } else { // moved up TweenLite.to(elements.splice(bound2, bound1 - bound2), 0.15, {yPercent: 100}); TweenLite.to(elements, 0.15, {yPercent: 0}); } } /** snap to set rounder values @method _sortableSnap **/ _sortableSnap(y) { return y; // enable code below to enable snapinnes on dragging // var h = this.target.currentHeight; // return Math.round(y / h) * h; } ngAfterViewInit() { } ngOnInit() { } destroy() { if (this.m_draggables) { this.m_draggables.forEach((drag) => { drag.kill() }); } this.m_draggables = null; } } ================================================ FILE: src/comps/duration-input/duration-input.component.css ================================================ div { display: inline-block; } input { width: 22px; border: none; outline: 0; } .btn.active.focus, .btn.active:focus, .btn.focus, .btn:active.focus, .btn:active:focus, .btn:focus { outline: 0; } a { height: 12px; width: 12px; cursor: pointer; } i { font-size: 8px; position: relative; top: -5px; left: 5px; color: black; } .wrap { background-color: white; height: 35px; width: 100px; margin: 5px; padding: 5px; border: solid #cccccc 1px; height: 30px; } ================================================ FILE: src/comps/duration-input/duration-input.component.html ================================================ ================================================ FILE: src/comps/duration-input/duration-input.component.spec.ts ================================================ /* tslint:disable:no-unused-variable */ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { DebugElement } from '@angular/core'; import { DurationInputComponent } from './duration-input.component'; describe('DurationInputComponent', () => { let component: DurationInputComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [ DurationInputComponent ] }) .compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(DurationInputComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: src/comps/duration-input/duration-input.component.ts ================================================ import {Component, EventEmitter, Input, OnInit, Output} from "@angular/core"; @Component({ selector: 'app-duration-input', templateUrl: './duration-input.component.html', styleUrls: ['./duration-input.component.css'] }) export class DurationInputComponent implements OnInit { hours = 0; minutes = 0; seconds = 1; hoursOutput = "00"; minutesOutput = "00"; secondsOutput = "01"; focus = "second"; focusedItem; timer; duration = 0; prevDuration = 0; @Input() set setDuration(i_duration: number) { i_duration = Math.round(i_duration); if (this.duration == i_duration || i_duration == -1) return; console.log(`>>>>>>>> setting new duration old ${this.duration} > ${i_duration}`); this.duration = i_duration; this.calcSeconds(); this.updateDisplay(); } @Output() durationChange = new EventEmitter(); constructor() { } ngOnInit() { document.body.onmouseup = () => { clearInterval(this.timer); } this.calcSeconds(); this.updateDisplay(); } calcSeconds() { var totalSeconds = this.duration; this.hours = Math.floor(totalSeconds / 3600); totalSeconds -= this.hours * 3600; this.minutes = Math.floor(totalSeconds / 60); totalSeconds -= this.minutes * 60; this.seconds = totalSeconds; } increment() { // console.log('increment'); if (this.focusedItem) { this.focusedItem.focus(); } switch (this.focus) { case "hour": ++this.hours; break; case "minute": ++this.minutes; break; case "second": ++this.seconds; break; default: break; } if (this.seconds == 60) { this.seconds = 0; this.minutes++; } if (this.minutes == 60) { this.minutes = 0; this.hours++; } if (this.hours > 24) { this.hours = 0; } this.updateDisplay(); } decrement() { // console.log('decrement'); switch (this.focus) { case "hour": if (--this.hours < 0) { this.hours = 24; } break; case "minute": if (this.minutes == 0 && this.hours == 0) break; if (--this.minutes == -1) { this.minutes = 59; this.hours--; } break; case "second": if (this.seconds == 0 && this.minutes == 0 && this.hours == 0) break; if (--this.seconds == -1) { this.seconds = 59; if (--this.minutes == -1) { this.minutes = 59; this.hours--; } } break; default: break; } this.updateDisplay(); } notifyChanges() { const newDuration = this.hours * 60 * 60 + this.minutes * 60 + this.seconds; if (newDuration != this.prevDuration) { this.prevDuration = newDuration; // console.log('change emitted ' + newDuration); this.durationChange.emit(newDuration); } } updateDisplay() { this.secondsOutput = this.padLeft(this.seconds); this.minutesOutput = this.padLeft(this.minutes); this.hoursOutput = this.padLeft(this.hours); } inputTime(e) { if (!/[0-9]/.test(e.key)) { e.preventDefault(); } else { } } keyUp(e) { this.seconds = +this.secondsOutput; this.minutes = +this.minutesOutput; this.hours = +this.hoursOutput; if (!this.hours) { this.hours = 0; } if (this.hours > 24) { this.hours = 24; } if (!this.minutes) { this.minutes = 0; } if (this.minutes > 59) { this.minutes = 59; } if (!this.seconds) { this.seconds = 0; } if (this.seconds > 59) { this.seconds = 59; } this.updateDisplay(); } setFocus(event, field) { this.focus = field; this.focusedItem = event.target; } mouseDownIncrement() { this.increment(); // this.timer = setInterval(() => this.increment(), 150); } mouseUpIncrement() { // clearInterval(this.timer); } mouseDownDecrement() { this.decrement(); // this.timer = setInterval(() => this.decrement(), 150); } mouseUpDecrement() { clearInterval(this.timer); } padLeft(n) { n = n.toString(); n = "00".substring(0, 2 - n.length) + "" + n.toString(); n = n.substring(n.length - 2); return n; } } ================================================ FILE: src/comps/entry/AutoLogin.ts ================================================ import {Component, ChangeDetectionStrategy} from "@angular/core"; import {Compbaser} from "ng-mslib"; @Component({ selector: 'AutoLogin', changeDetection: ChangeDetectionStrategy.OnPush, template: `
        verifying access...
        ` }) export class AutoLogin extends Compbaser { } ================================================ FILE: src/comps/entry/LoginPanel.ts ================================================ import {Component, ElementRef, Injectable, ViewChild} from "@angular/core"; import {animate, keyframes, state, style, transition, trigger} from "@angular/animations"; import {ActivatedRoute} from "@angular/router"; import {LocalStorage} from "../../services/LocalStorage"; import {AuthService} from "../../services/AuthService"; import {Map} from "immutable"; import {ToastsManager} from "ng2-toastr"; import {ApplicationState} from "../../store/application.state"; import {Store} from "@ngrx/store"; import {UserModel} from "../../models/UserModel"; import {AuthenticateFlags} from "../../store/actions/appdb.actions"; import {Compbaser, NgmslibService} from "ng-mslib"; import {RedPepperService} from "../../services/redpepper.service"; import {Lib} from "../../Lib"; enum ViewMod { LOGIN, FORGOT_PASSWORD, CHANGE_PASSWORD, CHANGE_BUSINESS_NAME } @Injectable() @Component({ selector: 'LoginPanel', providers: [LocalStorage], animations: [ trigger('loginState', [ state('inactive', style({ backgroundColor: 'red', transform: 'scale(1)', alpha: 0 })), state('default', style({ backgroundColor: '#313131', transform: 'scale(1)', alpha: 1 })), state('active', style({ backgroundColor: 'green', transform: 'scale(0.98)' })), transition('* => active', animate('600ms ease-out')), transition('* => inactive', animate('2000ms ease-out')) ]), trigger('showTwoFactor', [ state('true', style({ transform: 'scale(1)' })), transition(':enter', [ animate('1s 2s cubic-bezier(0.455,0.03,0.515,0.955)', keyframes([ style({ opacity: 0, transform: 'translateX(-400px)' }), style({ opacity: 1, transform: 'translateX(0)' }) ])) ]), transition(':leave', animate('500ms cubic-bezier(.17,.67,.83,.67)')) ]) ], styles: [` a { color: gray; } a:visited { color: #b9b9b9; } a:hover { color: #b9b9b9; } `], template: ` ` }) export class LoginPanel extends Compbaser { m_viewMod = ViewMod; m_currentViewMode = ViewMod.LOGIN; public m_user: string = ''; public m_pass: string = ''; public m_passNew: string = ''; public m_passRepeat: string = ''; public m_businessName: string = ''; public m_twoFactor: string; public m_showTwoFactor: boolean = false; public m_rememberMe: any; public loginState: string = ''; public userModel: UserModel; constructor(private ngmslibService: NgmslibService, private store: Store, private toast: ToastsManager, private rp: RedPepperService, private activatedRoute: ActivatedRoute, private authService: AuthService) { super(); this.listenEvents(); } @ViewChild('userPass') userPass: ElementRef; private listenEvents() { this.cancelOnDestroy( this.store.select(store => store.appDb.userModel) .subscribe((userModel: UserModel) => { this.userModel = userModel }, (e) => { console.error(e) }) ) this.cancelOnDestroy( this.store.select(store => store.appDb.appAuthStatus).subscribe((i_authStatus: Map) => { let authStatus = i_authStatus.get('authStatus') if (this.isAccessAllowed(authStatus) == false) return; switch (authStatus) { case AuthenticateFlags.TWO_FACTOR_ENABLED: { this.m_showTwoFactor = true; break; } case AuthenticateFlags.TWO_FACTOR_PASS: { this.loginState = 'active'; break; } case AuthenticateFlags.AUTH_PASS_NO_TWO_FACTOR: { this.loginState = 'active'; break; } } }, (e) => { console.error(e) }) ) this.cancelOnDestroy( this.activatedRoute.params.subscribe(params => { if (params['twoFactor']) { this.m_user = this.ngmslibService.base64().decode(params['user']); this.m_pass = this.ngmslibService.base64().decode(params['pass']); this.m_showTwoFactor = true; } }, (e) => console.error(e)) ) } passFocus() { // this.renderer.invokeElementMethod(this.userPass.nativeElement, 'focus', []) jQuery(this.userPass.nativeElement).focus(); } onChangeBusinessName(){ this.rp.changeBusinessName(this.m_user, this.m_pass, this.m_businessName, (value) => { if (!value.result) return bootbox.alert('Sorry there was a problem authenticating'); bootbox.alert('Your business name has been renamed successfully'); this.m_currentViewMode = ViewMod.LOGIN; }) } onResetPassword() { if (!Lib.ValidateEmail(this.m_user)) return bootbox.alert('user name must be in the form of an email address'); this.rp.resetPassword(this.m_user, (value) => { if (!value.result) return bootbox.alert('Sorry no account found'); bootbox.alert('Please check your email for a new password'); this.m_currentViewMode = ViewMod.LOGIN; }) } onChangePassword() { if (this.m_passNew != this.m_passRepeat) return bootbox.alert('passwords do not match') if (this.m_user.length < 2) return bootbox.alert('user name is too short') this.rp.changePassword(this.m_user, this.m_pass, this.m_passNew, (value) => { if (value.result == -1) return bootbox.alert('authentication for password change failed, try again'); bootbox.alert('Password changed successfully'); this.m_currentViewMode = ViewMod.LOGIN; }) } onClickedLogin() { if (this.m_showTwoFactor) { // this.toast.warning('Authenticating Two factor...'); this.authService.authServerTwoFactor(this.m_twoFactor); } else { // this.toast.info('Authenticating...'); this.authService.authUser(this.m_user, this.m_pass, this.m_rememberMe); } } private isAccessAllowed(i_reason: AuthenticateFlags): boolean { let msg1: string; let msg2: string; // this.loginState = 'default'; switch (i_reason) { case AuthenticateFlags.WRONG_PASS: { msg1 = 'User or password are incorrect...' msg2 = 'Please try again or click forgot password to reset your credentials' this.showMessage(msg1, msg2); this.loginState = 'inactive'; return false; } case AuthenticateFlags.WRONG_TWO_FACTOR: { msg1 = 'Invalid token' msg2 = 'Wrong token entered or the 60 seconds limit may have exceeded, try again...' this.showMessage(msg1, msg2); this.loginState = 'inactive'; return false; } case AuthenticateFlags.TWO_FACTOR_CHECK: { return false; } case AuthenticateFlags.TWO_FACTOR_FAIL: { msg1 = 'Two factor failed' msg2 = 'please try again...' this.showMessage(msg1, msg2); this.loginState = 'inactive'; return false; } case AuthenticateFlags.TWO_FACTOR_PASS: { this.loginState = 'active'; return true; } } } private showMessage(title, message) { setTimeout(() => { bootbox.dialog({ closeButton: true, title: title, message: message }); }, 200); } } ================================================ FILE: src/comps/font-selector/font-selector.ts ================================================ import {AfterViewInit, ChangeDetectionStrategy, Component, ElementRef, EventEmitter, Input, Output} from "@angular/core"; import {Compbaser} from "ng-mslib"; import {Subject} from "rxjs"; import {FontLoaderService} from "../../services/font-loader-service"; import {timeout} from "../../decorators/timeout-decorator"; import {Lib} from "../../Lib"; export interface IFontSelector { bold: boolean; italic: boolean; underline: boolean; alignment: 'left' | 'center' | 'right', font: string; color: string; size: number; } @Component({ selector: 'font-selector', changeDetection: ChangeDetectionStrategy.OnPush, template: ` {{me}}
        `, styles: [` /*:host /deep/ .color-picker {*/ /*position: relative;*/ /*} */ .fontSelection { width: 200px; } .offSet { position: relative; top: 5px; } input { height: 30px; } button { margin: 5px; height: 30px; } .colorPicker { width: 20px; float: left; display: inline-block; margin: 0 10px 0 0; padding: 15px 45px; border-radius: 0; border: 1px solid gray; background: #ffffff; padding: 10px 20px 10px 20px; text-decoration: none; } `] }) export class FontSelector extends Compbaser implements AfterViewInit { bold: boolean; italic: boolean; underline: boolean; alignment: 'left' | 'center' | 'right'; m_fonts: Array; m_borderColorChanged = new Subject(); m_moveColorPickerOnce = false; m_config: IFontSelector = { size: 12, alignment: 'right', bold: true, italic: true, font: 'Lora', underline: true, color: '#ff0000', } constructor(private fontService: FontLoaderService, private el: ElementRef) { super(); this.m_fonts = this.fontService.getFonts(); this._listenColorChanged(); } @Input() set setConfig(i_config: IFontSelector) { if (!i_config) return; if (Lib.ColorToDecimal(i_config.color)==0){ i_config.color = '#ffffff'; }; this.m_config = i_config } @Output() onChange: EventEmitter = new EventEmitter(); _listenColorChanged() { this.cancelOnDestroy( // this.m_borderColorChanged .debounceTime(500) .filter(v => v != '#123') .skip(1) .subscribe((i_color) => { this.m_config.color = String(i_color); this.onChange.emit(this.m_config); }, (e) => console.error(e)) ) } @timeout(1) _moveColorPicker() { if (this.m_moveColorPickerOnce) return; this.m_moveColorPickerOnce = true; jQuery(".color-picker", this.el.nativeElement).css("left", "+=100").css("top", "-=100"); ; } _onFontChanged(e) { this.m_config.font = e.target.value; this.onChange.emit(this.m_config); } _onFontSizeChanged(i_value) { this.m_config.size = i_value; this.onChange.emit(this.m_config); } _onFontStyleToggle(i_style) { this.m_config[i_style] = !this.m_config[i_style]; this.onChange.emit(this.m_config); } _onAlignmentChange(direction: 'left' | 'center' | 'right') { this.m_config.alignment = direction; this.onChange.emit(this.m_config); } ngAfterViewInit() { } ngOnInit() { } destroy() { } } ================================================ FILE: src/comps/hour-counter/hour-counter.ts ================================================ import {Component, ChangeDetectionStrategy, AfterViewInit, ElementRef, ViewChild, Input} from "@angular/core"; import {Compbaser} from "ng-mslib"; @Component({ selector: 'hour-counter', changeDetection: ChangeDetectionStrategy.OnPush, template: ` `, }) export class HourCounter { constructor(private el: ElementRef) { document.body.onmouseup = () => { clearInterval(this.intIncrement); clearInterval(this.intDecrement); } } @ViewChild('hour') inputHour; @ViewChild('minute') inputMinute:ElementRef; @ViewChild('second') inputSeconds:ElementRef; @ViewChild('up') up:ElementRef; @ViewChild('down') down:ElementRef; intIncrement; intDecrement; currentFocus = 'second'; setFocus(id) { this.currentFocus = id; this.inputHour.nativeElement.value; } intIncrementStart() { setInterval(this.increment,80); } intIncrementEnd() { clearInterval(this.intIncrement); } intDecrementStart() { setInterval(this.decrement,80) } intDecrementEnd() { clearInterval(this.intDecrement) } increment() { // document.getElementById(this.currentFocus).focus(); var hr = this.inputHour.nativeElement.value; var min = this.inputMinute.nativeElement.value; var sec = this.inputSeconds.nativeElement.value; // var hr = document.getElementById('hour').value; // var min = document.getElementById('minute').value; // var sec = document.getElementById('second').value; // if (this.currentFocus == "second") { // // sec++; // // if (sec == 60) { // sec = 0; // min++; // if (min == 60) { // min = 0; // hr++; // } // } // // } // else if (this.currentFocus == "minute") { // min++; // if (min == 60) { // min = 0; // hr++; // } // } // else if (this.currentFocus == "hour") { // hr++; // } // document.getElementById('hour').value = this.padLeft(hr); // document.getElementById('minute').value = this.padLeft(min); // document.getElementById('second').value = this.padLeft(sec); // this.validate(); } decrement() { // document.getElementById(this.currentFocus).focus(); // var hr = document.getElementById('hour').value; // var min = document.getElementById('minute').value; // var sec = document.getElementById('second').value; // if (this.currentFocus == "second") { // if (hr == 0 && min == 0 && sec == 1) { // return; // } // sec--; // if (sec == -1) { // sec = 59; // min--; // if (min == -1) { // min = 59; // hr--; // } // } // } // else if (this.currentFocus == "minute") { // min--; // if (min == -1) { // min = 59; // hr--; // } // } // else if (this.currentFocus == "hour") { // hr--; // } // document.getElementById('hour').value = padLeft(hr); // document.getElementById('minute').value = padLeft(min); // document.getElementById('second').value = padLeft(sec); // this.validate(); } padLeft(n) { n = n.toString(); n = "00".substring(0, 2 - n.length) + "" + n.toString(); n = n.substring(n.length - 2); return n; } validate() { // var val = document.getElementById(this.currentFocus).value; // if (isNaN(val)) { // document.getElementById(this.currentFocus).value = "00"; // } // var hr = document.getElementById('hour').value; // var min = document.getElementById('minute').value; // var sec = document.getElementById('second').value; // if (hr > 24) // hr = 24; // if (hr < 0) // hr = 0; // if (min < 0) // min = 0; // if (min > 59) // min = 59; // if (sec < 0) // sec = 0; // if (sec > 59) // sec = 59; // document.getElementById('hour').value = this.padLeft(hr); // document.getElementById('minute').value = this.padLeft(min); // document.getElementById('second').value = this.padLeft(sec); } } ================================================ FILE: src/comps/imgloader/ImgLoader.ts ================================================ import {Component, ChangeDetectionStrategy, ChangeDetectorRef, Input} from '@angular/core'; import 'rxjs/add/observable/fromEvent'; import 'rxjs/add/operator/do'; import 'rxjs/add/operator/merge'; import 'rxjs/add/operator/distinctUntilChanged'; import {Lib} from "../../Lib"; @Component({ selector: 'imgLoader', changeDetection: ChangeDetectionStrategy.Default, template: `
        ` }) export class ImgLoader { constructor(private cdr:ChangeDetectorRef) { } @Input('style') _style:any = {}; @Input() defaultImage:string = ''; @Input() circle:boolean = false; @Input() images:Array = []; private imageRetries:number = 0; private getImageUrl() { if (this.images.length == 0) return this.defaultImage; if (this.images[this.imageRetries] == undefined) return this.defaultImage; var url = this.images[this.imageRetries] + (Lib.DevMode() ? '?random=xyz' : `?random=' ${Math.random()}`); return url; } private onImageLoaded() { this.cdr.detach(); } private onImageError() { this.imageRetries++; } public reloadImage(){ this.imageRetries = 0; this.cdr.reattach(); } } ================================================ FILE: src/comps/infobox/Infobox.ts ================================================ import {Component, Input, Output, EventEmitter, ChangeDetectionStrategy, OnChanges, SimpleChange} from '@angular/core' @Component({ selector: 'Infobox', changeDetection: ChangeDetectionStrategy.OnPush, styles: [` .panel-footer { padding: 5px 3px; background-color: #fafafa; border: 1px solid #e2e2e2; border-bottom-right-radius: 2px; border-bottom-left-radius: 2px; } .br-a { border: 1px solid #eeeeee !important; } .br-grey { border-color: #d9d9d9 !important; } `], template: `



        {{value1}}

        {{value2}}
        ` }) export class Infobox { @Input() style:string = 'basic' @Input() value1:string = null; @Input() value2:string = ''; @Input() value3:string = ''; @Input() icon:string = 'fa-plus'; } ================================================ FILE: src/comps/lazy-image/lazy-image.ts ================================================ import {Directive, ElementRef, EventEmitter, Input, NgZone, Output} from "@angular/core"; import {Observable} from "rxjs/Observable"; import {Subject} from "rxjs/Subject"; /** * Usage * directive that allows you to load images which are deferred (not available right away) so image will be polled. While image is polled we can show default image as well as as loading image. usage: or to use via API ... @ViewChild(LazyImage) lazyImage: LazyImage; _lazyLoad() { this.lazyImage.setUrl(['https://secure.digitalsignage.com/studioweb/assets/some_lazy.png']); } _resetSnapshotSelection() { if (this.lazyImage) this.lazyImage.resetToDefault(); } _takeSnapshot() { this.lazyImage.url = 'http://example.com/foo.png; } ... */ @Directive({ selector: '[lazyImage]' }) export class LazyImage { private m_urls: Array = []; private m_index = 0; private cancel$ = new Subject(); constructor(private el: ElementRef, private ngZone: NgZone) { } // @Input('lazyImage') lazyImage; // to change support to directive of: = new EventEmitter(); @Output() completed: EventEmitter = new EventEmitter(); @Output() errored: EventEmitter = new EventEmitter(); @Input() defaultImage: string; @Input() loadingImage: string; @Input() errorImage: string; @Input() retry: number = 5; @Input() delay: number = 1000; @Input() set urls(i_urls: Array) { this.setUrls(i_urls) } setUrls(i_urls: Array) { this.m_index = 0; this.m_urls = i_urls; this.loadImage(i_urls); } public resetToDefault() { this.setImage(this.el.nativeElement, this.defaultImage); this.cancel$.next({}) } ngAfterViewInit() { this.setImage(this.el.nativeElement, this.defaultImage); } ngOnInit() { } setImage(element: HTMLElement, i_url) { // const isImgNode = element.nodeName.toLowerCase() === 'img'; // if (isImgNode) { // } else { // element.style.backgroundImage = `url('${imagePath}')`; // } (element).src = i_url; return element; } loadImage(i_urls) { const pollAPI$ = Observable.defer(() => { return new Promise((resolve, reject) => { const img = new Image(); var url; if (i_urls[this.m_index]){ url = i_urls[this.m_index]; this.m_index++; } else { this.m_index = 0; url = i_urls[this.m_index]; } img.src = url; img.onload = () => { resolve(url); }; img.onerror = err => { this.setImage(this.el.nativeElement, this.loadingImage); reject(err) }; }) }).retryWhen(err => { return err.scan((errorCount, err) => { if (errorCount >= this.retry) { throw err; } return errorCount + 1; }, 0) .delay(this.delay); }) .takeUntil(this.cancel$) pollAPI$.subscribe((v) => { this.setImage(this.el.nativeElement, v) this.loaded.emit(); }, (e) => { this.setImage(this.el.nativeElement, this.errorImage); this.errored.emit(); // console.error(e) }, () => { this.completed.emit(); }) } destroy() { } } ================================================ FILE: src/comps/limited-access/limited-access.ts ================================================ import {Component, ChangeDetectionStrategy, AfterViewInit} from "@angular/core"; import {Compbaser} from "ng-mslib"; @Component({ selector: 'limited-access', changeDetection: ChangeDetectionStrategy.OnPush, template: `

        Limited access:


        You are login in to StudioLite with StudioPro credentials

        This will result in limited functionality, please proceed to download StudioPro below...

        • Step 1: download Adobe AIR runtime download
        • Step 2: download StudioPro for Mac download
        • Step 3: Install the runtime and proceed with installing StudioPro for Mac
        ` }) export class LimitedAccess { } ================================================ FILE: src/comps/loading/loading.ts ================================================ import {Component, Input, ChangeDetectionStrategy} from '@angular/core' @Component({ selector: 'loading', styles: [` .spinner { display: inline-block; opacity: 1; border: 3px solid rgba(0,0,0,.3); border-radius: 50%; border-top-color: #fff; animation: spin 1s ease-in-out infinite; -webkit-animation: spin 1s ease-in-out infinite; height: 100px; width: 100px; } @keyframes spin { to { -webkit-transform: rotate(360deg); } } @-webkit-keyframes spin { to { -webkit-transform: rotate(360deg); } } .center { text-align: center } `], template: `
        `, changeDetection: ChangeDetectionStrategy.OnPush }) export class Loading { _style: Object; _size: Object; @Input() src: string = ''; @Input('style') set style(i_style: Object) { this._style = i_style; } @Input('size') set size(i_size) { this._size = { opacity: 1, height: i_size, width: i_size } } } ================================================ FILE: src/comps/logo/Logo.ts ================================================ import {Component, ElementRef, ChangeDetectionStrategy} from '@angular/core'; import {Observable} from "rxjs/Observable"; import 'rxjs/add/observable/of'; import 'rxjs/add/observable/fromEvent'; import 'rxjs/add/operator/do'; import 'rxjs/add/operator/merge'; import 'rxjs/add/operator/distinctUntilChanged'; /** * Logo component for Application header * activated via elementRef and listen to mouse events via angular * adapter interface **/ @Component({ selector: 'Logo', changeDetection: ChangeDetectionStrategy.OnPush, template: `
        ` }) export class Logo { constructor(private elementRef: ElementRef) { this.listenMouse(); } listenMouse(): void { var over: Observable = Observable.fromEvent(this.elementRef.nativeElement, 'mouseover').map(e => { return Observable.of(1) }); var out: Observable = Observable.fromEvent(this.elementRef.nativeElement, 'mouseout').map(e => { return Observable.of(0) }); over.merge(out).distinctUntilChanged().subscribe(events => { if (events.value) { jQuery(this.elementRef.nativeElement).find('.flipcard').addClass('flipped'); } else { jQuery(this.elementRef.nativeElement).find('.flipcard').removeClass('flipped'); } }, (e) => console.error(e)); } } ================================================ FILE: src/comps/logo/reseller-logo.ts ================================================ import {Component, ChangeDetectionStrategy, AfterViewInit, ViewChild} from "@angular/core"; import {Compbaser} from "ng-mslib"; import {YellowPepperService} from "../../services/yellowpepper.service"; import {RedPepperService} from "../../services/redpepper.service"; import {LazyImage} from "../lazy-image/lazy-image"; import {UserModel} from "../../models/UserModel"; import {Observable} from "rxjs/Observable"; @Component({ selector: 'reseller-logo', changeDetection: ChangeDetectionStrategy.OnPush, template: ` ` }) export class ResellerLogo extends Compbaser implements AfterViewInit { constructor(private yp: YellowPepperService, private rp: RedPepperService) { super(); } @ViewChild(LazyImage) lazyImage: LazyImage; _onLoaded() { // console.log('img loaded'); } _onError() { console.log('img error'); } _onCompleted() { // console.log('img completed'); } ngAfterViewInit() { this.cancelOnDestroy( this.yp.listenUserModel() .take(1) .subscribe((i_userModel: UserModel) => { if (i_userModel.resellerId == 1) return; var urls = [ `http://galaxy.signage.me/Resources/Resellers/${i_userModel.resellerId}/Logo.png`, `http://galaxy.signage.me/Resources/Resellers/${i_userModel.resellerId}/Logo.jpg` ]; this.lazyImage.setUrls(urls); }, (e) => console.error(e)) ) } ngOnInit() { } destroy() { } } ================================================ FILE: src/comps/logout/Logout.ts ================================================ import {Component} from "@angular/core"; import {LocalStorage} from "../../services/LocalStorage"; import {RedPepperService} from "../../services/redpepper.service"; import {MainAppShowStateEnum} from "../../app/app-component"; import {ACTION_UISTATE_UPDATE} from "../../store/actions/appdb.actions"; import {IUiState} from "../../store/store.data"; import {YellowPepperService} from "../../services/yellowpepper.service"; import {Compbaser} from "ng-mslib"; import {timeout} from "../../decorators/timeout-decorator"; @Component({ selector: 'Logout', providers: [LocalStorage], styles: [` .fa { display: inline-table; } .btn-xlarge { vertical-align: center; margin: 30px; /*width: 100%;*/ padding: 48px 48px; font-size: 22px; color: white; text-align: center; text-shadow: 0 1px 2px rgba(0, 0, 0, 0.25); background: #62b1d0; border: 0; border-bottom: 3px solid #9FE8EF; cursor: pointer; -webkit-box-shadow: inset 0 -3px #9FE8EF; box-shadow: inset 0 -3px #9FE8EF; width: 250px; } .btn-xlarge:active { top: 2px; outline: none; -webkit-box-shadow: none; box-shadow: none; } .btn-xlarge:hover { background: #a9a9a9; } `], template: `
        ` }) export class Logout extends Compbaser { constructor(private localStorage: LocalStorage, private rp: RedPepperService, private yp:YellowPepperService) { super(); this.cancelOnDestroy( this.yp.listenMainAppState() .subscribe((i_value: MainAppShowStateEnum) => { switch (i_value) { case MainAppShowStateEnum.SAVED: { this._onLogout(); break; } } }, (e) => console.error(e)) ) } _onSaveChangesLogout(){ let uiState: IUiState = {mainAppState: MainAppShowStateEnum.SAVE}; this.yp.ngrxStore.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})); } @timeout(500) _onLogout() { window.onbeforeunload = () => { }; this.localStorage.removeItem('remember_me_studioweb'); this.localStorage.removeItem('no_show_limited'); let uiState: IUiState = {mainAppState: MainAppShowStateEnum.GOODBYE} this.yp.ngrxStore.dispatch(({type: ACTION_UISTATE_UPDATE, payload: uiState})) if (this.rp.getUserData().resellerID == 1) jQuery('body').fadeOut(1000, function () { window.location.replace('http://www.digitalsignage.com'); }); } } ================================================ FILE: src/comps/match-body-height/match-body-height.ts ================================================ import {Directive, ElementRef, HostListener, Input, AfterContentInit} from "@angular/core"; @Directive({ selector: '[matchBodyHeight]' }) export class MatchBodyHeight implements AfterContentInit { constructor(private el: ElementRef) { } // offset total height @Input() matchBodyHeight:number = 50; ngAfterContentInit() { this.onResize(); } @HostListener('window:resize') onResize() { var bodyHeight = jQuery('body').height() - this.matchBodyHeight; jQuery(this.el.nativeElement).height(bodyHeight); } ngOnDestroy(){ // console.log('dest matchBodyHeight'); } } // import {timeout} from "../../decorators/timeout-decorator"; // constructor { this.enableScroller() } // @timeout(500) // private enableScroller() { // // jQuery('.ng-content-wrapper', this.el.nativeElement).css('overflow-y', 'scroll'); // } // @HostBinding('style.overflow') // overFlow; // jQuery('.ng-content-wrapper', this.el.nativeElement) // .delay(500) // .queue(function (next) { // $(this).css('overflow-y', 'scroll'); // next(); // }); ================================================ FILE: src/comps/media-player/media-player.ts ================================================ import {Component, Input} from "@angular/core"; import {VgAPI} from "videogular2/core"; @Component({ selector: 'media-player', template: ` ` }) export class MediaPlayer { sources: Array; api: VgAPI constructor() { this.sources = [ { src: "http://s3.signage.me/business1000/resources/OfflineUpdate.mp4", type: "video/mp4" } ]; } @Input() set playResource(i_resource: string) { this.onSwap(i_resource, 'video/mp4') } @Input() autoPlay:boolean = false; onSwap(source: string, type: string) { if (this.api) this.api.pause(); this.sources = new Array(); this.sources.push({ src: source, type: type }); setTimeout(() => { this.api.getDefaultMedia().currentTime = 0; if (this.api && this.autoPlay) this.api.play(); }, 300) } getApi(): VgAPI { return this.api; } onPlayerReady(i_api: VgAPI) { this.api = i_api; // this.api.fsAPI.toggleFullscreen() // this.api.getDefaultMedia().subscriptions.ended.subscribe( // () => { // // Set the video to the beginning // this.api.getDefaultMedia().currentTime = 0; // } // ); } } ================================================ FILE: src/comps/ng-menu/ng-menu-item.ts ================================================ import {Component, ChangeDetectionStrategy, Input} from "@angular/core"; import {NgMenu} from "./ng-menu"; import {Compbaser} from "ng-mslib"; @Component({ selector: 'ng-menu-item', changeDetection: ChangeDetectionStrategy.OnPush, template: ` `, }) export class NgMenuItem extends Compbaser { constructor(private ngMenu:NgMenu) { super(); this.ngMenu.addMenuItem(this); } @Input() fontawesome:string; @Input() title:string; @Input() name:string; get getTitle(): string { return this.title; } get getName(): string { return this.name; } get getFontAwesome(): string { return this.fontawesome; } ngOnInit() { } destroy() { } } ================================================ FILE: src/comps/ng-menu/ng-menu.ts ================================================ import {ChangeDetectionStrategy, ChangeDetectorRef, Component, Input} from "@angular/core"; import {NgMenuItem} from "./ng-menu-item"; import {CommBroker, IMessage} from "../../services/CommBroker"; import {Router} from "@angular/router"; import {Compbaser} from "ng-mslib"; import {Consts} from "../../interfaces/Consts"; @Component({ selector: 'ng-menu', changeDetection: ChangeDetectionStrategy.OnPush, template: `
        `, styles: [` .appMenu { background: #3e3f48; } .navicons { font-size: 0.9em; position: relative; top: 2px; left: 0px; text-align: left; padding-right: 5px; } .iconSize { font-size: 1.3em; padding-right: 20px; } `] }) export class NgMenu extends Compbaser { constructor(private router: Router, private commBroker: CommBroker, private cd: ChangeDetectorRef) { super(); } ngOnInit() { this.m_hidden = false; this.listenWinResize() } @Input() fileMenuMode: boolean = true; @Input() routePrefix: string = ''; public items: Array = []; public m_hidden: boolean = true; private listenWinResize() { this.commBroker.onEvent(Consts.Events().WIN_SIZED).subscribe((e: IMessage) => { if (e.message.width < Consts.Values().MENU_MIN_ICON_SHOW) { this.m_hidden = true; } else { this.m_hidden = false; } this.cd.markForCheck(); }, (e) => console.error(e)); } private listenMenuSelected(ngMenuItem:NgMenuItem, event: MouseEvent) { event.preventDefault(); this.router.navigate([`/${this.routePrefix}/${ngMenuItem.name}`]); } public addMenuItem(i_item: NgMenuItem): void { this.items.push(i_item); } destroy() { } } ================================================ FILE: src/comps/panel-split/panel-split-container.ts ================================================ import {Component, ChangeDetectionStrategy, ElementRef, ContentChild, AfterViewInit} from "@angular/core"; import {PanelSplitMain} from "./panel-split-main"; import {PanelSplitSide} from "./panel-split-side"; @Component({ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'panel-split-container', template: `
        ` }) export class PanelSplitContainer implements AfterViewInit { constructor(private el: ElementRef) { } @ContentChild(PanelSplitMain) panelSpiltMain: PanelSplitMain @ContentChild(PanelSplitSide) panelSplitSide: PanelSplitSide ngAfterViewInit() { if (!this.panelSpiltMain || !this.panelSplitSide) throw new Error('panel-split-container requires main and side children'); this.panelSplitSide.onToggle.subscribe((value: boolean) => { this.panelSpiltMain.setFullScreen(value) }, (e) => console.error(e)) } } ================================================ FILE: src/comps/panel-split/panel-split-main.ts ================================================ import {Component, ChangeDetectionStrategy, ElementRef} from "@angular/core"; @Component({ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'panel-split-main', styles: [` .mainPanelWrap { -webkit-transition: width 1s ease, margin 1s ease; -moz-transition: width 1s ease, margin 1s ease; -o-transition: width 1s ease, margin 1s ease; transition: width 1s ease, margin 1s ease; } button { width: 200px; margin: 5px; } .mainPanelWrap { padding: 0; margin: 0; } `], template: `
        ` }) export class PanelSplitMain { constructor(private el: ElementRef) { } setFullScreen(value) { if (!value) { // full screen jQuery(this.el.nativeElement).find('.mainPanelWrap').removeClass('col-xs-7 col-sm-8 col-md-9 col-lg-10') jQuery(this.el.nativeElement).find('.mainPanelWrap').addClass('col-xs-12') } else { jQuery(this.el.nativeElement).find('.mainPanelWrap').addClass('col-xs-7 col-sm-8 col-md-9 col-lg-10') jQuery(this.el.nativeElement).find('.mainPanelWrap').removeClass('col-xs-12') } } } ; ================================================ FILE: src/comps/panel-split/panel-split-side.ts ================================================ import {Component, ChangeDetectionStrategy, ElementRef, Output, EventEmitter} from "@angular/core"; @Component({ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'panel-split-side', styles: [` .propPanelWrap { position: fixed; /*min-height: 100%;*/ /*max-height: 100%;*/ right: 0; top: 0; height: 100%; overflow: hidden; -webkit-transition: width 1s ease, margin 1s ease; -moz-transition: width 1s ease, margin 1s ease; -o-transition: width 1s ease, margin 1s ease; transition: width 1s ease, margin 1s ease; background-color: #ffffff; margin: 0; z-index: 200; border-left: 2px #c9c9c9 solid; } .toggleArrow { font-size: 1.5em; color: #313335 } .restorePanel { position: absolute; right: 0; top: 3px; } .fa-arrow-circle-right { padding-top: 60px; } `], template: `
        ` }) export class PanelSplitSide { constructor(private el: ElementRef) { } @Output() onToggle: EventEmitter = new EventEmitter(); showSidePanel: boolean = true; _toggle(event: MouseEvent) { event.preventDefault(); this.showSidePanel = !this.showSidePanel; this.onToggle.emit(this.showSidePanel); if (this.showSidePanel) { jQuery(this.el.nativeElement).find('.propPanelWrap').addClass('col-xs-7 col-sm-8 col-md-9 col-lg-10') jQuery(this.el.nativeElement).find('.propPanelWrap').fadeIn('50') } else { jQuery(this.el.nativeElement).find('.propPanelWrap').fadeOut(0) jQuery(this.el.nativeElement).find('.propPanelWrap').removeClass('col-xs-7 col-sm-8 col-md-9 col-lg-10') } } } ================================================ FILE: src/comps/screen-template/screen-template.ts ================================================ /** The class generates the UI for a template (a.k.a Screen Division) that is a selectable widget including the drawing of each viewer (division) within the screen, as well as firing related click events on action. @param {object} i_screenTemplateData hold data as instructions for factory creation component @param {String} i_type the type of widget that we will create. This includes VIEWER_SELECTABLE as well as ENTIRE_SELECTABLE with respect to the ability to select the components viewers individually or the entire screen division @param {object} i_owner the owner of this class (parent) that we can query at the listening end, to examine if the event is of any interest to the listener. This is a key event in the framework as many different instances subscribe to ON_VIEWER_SELECTED to reconfigure themselves. The event is fired when a viewer (i.e.: a screen division) is selected inside a Template (i.e. Screen). The key to remember is that the Factory instance (this) is always created with respect to it's owner (i_owner), so when ON_VIEWER_SELECTED is fired, the owner is carried with the event so listeners can act accordingly, and only if the owner is of interest to a subscribed listener. **/ import {ChangeDetectionStrategy, Component, ElementRef, EventEmitter, Input, Output} from "@angular/core"; import {Compbaser} from "ng-mslib"; import * as _ from "lodash"; import {Lib} from "../../Lib"; import {OrientationEnum} from "../../app/campaigns/campaign-orientation"; import {IScreenTemplateData} from "../../interfaces/IScreenTemplate"; @Component({ selector: 'screen-template', changeDetection: ChangeDetectionStrategy.OnPush, template: ``, }) export class ScreenTemplate extends Compbaser { private created = false; private m_mouseHoverEffect = false; private m_campaign_timeline_id = -1; constructor(private el: ElementRef) { super(); } ngAfterViewInit(){ if (this.m_mouseHoverEffect) this._mouseOverEffect() this.m_mouseHoverEffect = false; } @Output() onDivisionDoubleClicked: EventEmitter = new EventEmitter(); @Input() set mouseHoverEffect(i_value) { this.m_mouseHoverEffect = i_value; } @Input() set setTemplate(i_screenTemplateData: IScreenTemplateData) { if (this.created) return this.created = true; // this.m_selfDestruct = i_screenTemplateData.i_selfDestruct; this.m_myElementID = 'svgScreenLayout' + '_' + _.uniqueId(); this.m_screenTemplateData = i_screenTemplateData; this.m_orientation = i_screenTemplateData['orientation']; this.m_resolution = i_screenTemplateData['resolution']; this.m_screenProps = i_screenTemplateData['screenProps']; this.m_scale = i_screenTemplateData['scale']; this.m_svgWidth = (this.m_resolution.split('x')[0]) / this.m_scale; this.m_svgHeight = (this.m_resolution.split('x')[1]) / this.m_scale; this.m_useLabels = false; this._create() // this.selectableFrame(); this._mouseDoubleClickDivision(); } m_selfDestruct; m_screenTemplateData; m_myElementID; m_orientation; m_resolution; m_screenProps; m_scale; m_svgWidth; m_svgHeight; m_useLabels; /** Method is called when an entire screen frame of the UI is clicked, in contrast to when a single viewer is selected. The difference in dispatch of the event depends on how the factory created this instance. @method _onViewSelected @param {Event} e @param {Object} i_caller @return {Boolean} false **/ private _onViewSelected(e, i_caller) { var self = i_caller; var element = e.target; var campaign_timeline_board_viewer_id = jQuery(element).data('campaign_timeline_board_viewer_id'); var campaign_timeline_id = jQuery(element).data('campaign_timeline_id'); var screenData = { sd: jQuery(element).data('sd'), elementID: i_caller.m_myElementID, // owner: i_caller.getOwner(), campaign_timeline_board_viewer_id: campaign_timeline_board_viewer_id, campaign_timeline_id: campaign_timeline_id, screenTemplateData: self.m_screenTemplateData }; self._deselectViewers(); // BB.comBroker.fire(BB.EVENTS.ON_VIEWER_SELECTED, this, screenData); } /** Deselect all viewers, thus change their colors back to default. @method _deselectViewers @return none **/ private _deselectViewers() { var self = this; jQuery('.screenDivisionClass', self.el.nativeElement).each(function () { if (jQuery(this).is('rect')) { jQuery(this).css({'fill': 'rgb(230,230,230)'}); } }); } /** When enabled, _mouseOverEffect will highlight viewers when mouse is hovered over them. @method _mouseOverEffect @return none **/ private _mouseOverEffect() { var self = this; // var a = jQuery('#' + self.m_myElementID); // var b = jQuery('#' + self.m_myElementID).find('rect'); jQuery('#' + self.m_myElementID, self.el.nativeElement).find('rect').each(function () { jQuery(this).on('mouseover', function () { jQuery(this).css({'fill': 'rgb(190,190,190)'}); }).mouseout(function () { jQuery(this).css({'fill': 'rgb(230,230,230)'}); }); }); } private _mouseDoubleClickDivision() { var self = this; // var a = jQuery('#' + self.m_myElementID); // var b = jQuery('#' + self.m_myElementID).find('rect'); jQuery('#' + self.m_myElementID, self.el.nativeElement).find('rect').each(function () { jQuery(this).on('dblclick', function () { var e = jQuery(this).data('campaign_timeline_board_viewer_id'); self.onDivisionDoubleClicked.emit(e) }); }); } // Get the owner (parent) of this instance, i.e., the one who created this. // We use the owner attribute as a way to distinguish what type of instance this was created as. // @method getOwner // @return {Object} m_owner // // getOwner() { // var self = this; // return self.m_owner; // } // /** Create all the screen divisions (aka viewers) as svg snippets and push them into an array @method getDivisions @return {array} f array of all svg divisions **/ getDivisions() { var self = this; var svg = self._create(); return $(svg).find('rect'); // var f = $(svg).find('rect').map(function (k, v) { // return ' ' + // '' + // v.outerHTML + // ' ' + // ''; // }); // return f; } /** Create will produce the actual SVG based Template (screen) with inner viewers and return HTML snippet to the caller. @method create @return {Object} html element produced by this factory **/ private _create() { var self = this; var screensDivisons = ''; var screenLabels = ''; // sort for proper z-order creating the viewers var orderedScreenValues = [], i = 0; for (var screenValues in self.m_screenProps) { var viewOrder = self.m_screenProps[screenValues]['view_order']; viewOrder = _.isUndefined(viewOrder) ? i : viewOrder; orderedScreenValues[viewOrder] = self.m_screenProps[screenValues]; i++; } // create the viewers i = 0; for (var ordered in orderedScreenValues) { i++; var screenValue = orderedScreenValues[ordered]; var x = screenValue['x'] == 0 ? 0 : screenValue['x'] / self.m_scale; var y = screenValue['y'] == 0 ? 0 : screenValue['y'] / self.m_scale; var w = screenValue['w'] == 0 ? 0 : screenValue['w'] / self.m_scale; var h = screenValue['h'] == 0 ? 0 : screenValue['h'] / self.m_scale; var campaign_timeline_board_viewer_id = screenValue['campaign_timeline_board_viewer_id']; var campaign_timeline_id = self.m_campaign_timeline_id = screenValue['campaign_timeline_id']; var sd = screenValues; var uniqueID = 'rectSD' + '_' + _.uniqueId(); if (self.m_useLabels == true) screenLabels += '' + i + ''; screensDivisons += ''; } var snippet = (jQuery(' ' + '' + screensDivisons + screenLabels + ' ' + '')); jQuery(self.el.nativeElement).append(snippet); return snippet; } /** When enabled, selectableFrame will allow for UI mouse / click of the outer frame of the template (screen) and not individual viewers. @method selectableFrame @return none **/ selectableFrame() { var self = this; var applyToSelected = function (e) { jQuery('#' + self.m_myElementID, self.el.nativeElement).parent().find('rect').css({ 'stroke-width': '2', 'stroke': 'rgb(72,72,72)' }); jQuery('#' + self.m_myElementID, self.el.nativeElement).find('rect').css({'stroke-width': '2', 'stroke': Lib.GetThemeColor()}); self._onViewSelected(e, self); } // listen one if (self.m_selfDestruct) { jQuery('.screenDivisionClass', '#' + self.m_myElementID).one('mouseup contextmenu', function (e) { applyToSelected(e); }); } else { // listen on jQuery('.screenDivisionClass', '#' + self.m_myElementID).on('mouseup contextmenu', function (e) { applyToSelected(e); }); } } get campaignTimelineId(){ return this.m_campaign_timeline_id; } /** When enabled, selectableFrame will allow for UI mouse / click of the outer frame of the template (screen) and not individual viewers. @method selectableFrame @return none **/ selectFrame() { jQuery('#' + this.m_myElementID, this.el.nativeElement).parent().find('rect').css({ 'stroke-width': '2', 'stroke': 'rgb(72,72,72)' }); jQuery('#' + this.m_myElementID, this.el.nativeElement).find('rect').css({'stroke-width': '2', 'stroke': Lib.GetThemeColor()}); // this._onViewSelected(e, this); } deSelectFrame() { jQuery('#' + this.m_myElementID, this.el.nativeElement).parent().find('rect').css({ 'stroke-width': '2', 'stroke': 'rgb(72,72,72)' }); } /** The public method version of _deselectViewers, which de-selects all viewers @method deselectDivisons **/ deselectDivisons() { var self = this; self._deselectViewers(); } /** Select a division (aka viewer) using it's viewer_id, only applicable when class represents an actual timelime > board > viewer_id @method selectDivison @param {Number} i_campaign_timeline_board_viewer_id **/ selectDivison(i_campaign_timeline_board_viewer_id) { var self = this; self._deselectViewers(); var selectedElement = jQuery('#' + self.m_myElementID, self.el.nativeElement).find('[data-campaign_timeline_board_viewer_id="' + i_campaign_timeline_board_viewer_id + '"]'); // jQuery(selectedElement).css({'fill': Lib.GetThemeColor()}); jQuery(selectedElement).css({'fill': '#aed0ed'}); } destroy() { var self = this; jQuery('.screenDivisionClass', '#' + self.m_myElementID).off('mouseup contextmenu'); jQuery('.screenDivisionClass', self.el.nativeElement).off('click contextmenu', function (e) { self._onViewSelected(e, self); }); jQuery(this).off('mouseover', function () { jQuery(this).css({'fill': 'rgb(190,190,190)'}); }).mouseout(function () { jQuery(this).css({'fill': 'rgb(230,230,230)'}); }); jQuery('#' + this.m_myElementID, self.el.nativeElement).find('rect').each(function () { jQuery(this).off(); }); // jQuery.each(self, function (k) { // self[k] = undefined; // }); } } ================================================ FILE: src/comps/simple-grid-module/SimpleGrid.css ================================================ .simpleTable { background-color: white; } * { font-size: 0.9em; } ================================================ FILE: src/comps/simple-grid-module/SimpleGrid.ts ================================================ import {SimpleGridData} from "./SimpleGridData"; import {SimpleGridTable} from "./SimpleGridTable"; import {SimpleGridSortableHeader} from "./SimpleGridSortableHeader"; import {SimpleGridRecord} from "./SimpleGridRecord"; import {SimpleGridDataImage} from "./SimpleGridDataImage"; import {SimpleGridDataCurrency} from "./SimpleGridDataCurrency"; import {StoreModel} from "../../models/StoreModel"; import {SimpleGridDataChecks} from "./SimpleGridDataChecks"; import {SimpleGridDataDropdown} from "./SimpleGridDataDropdown"; export const SIMPLEGRID_DIRECTIVES:Array = [SimpleGridTable, SimpleGridSortableHeader, SimpleGridRecord, SimpleGridData, SimpleGridDataCurrency, SimpleGridDataImage, SimpleGridDataChecks, SimpleGridDataDropdown]; export interface ISimpleGridEdit { value:string; item:StoreModel; } ================================================ FILE: src/comps/simple-grid-module/SimpleGridData.ts ================================================ import {Component, Input, ChangeDetectionStrategy, Output, EventEmitter} from '@angular/core' import {StoreModel} from "../../models/StoreModel"; import {ISimpleGridEdit} from "./SimpleGrid"; import {timeout} from "../../decorators/timeout-decorator"; @Component({ selector: 'td[simpleGridData]', changeDetection: ChangeDetectionStrategy.OnPush, styles: [` label { padding: 0; margin: 0; } .editableLabel { cursor: pointer; } input { padding: 0; margin: 0; } a { cursor: pointer; } `], template: ` ` }) export class SimpleGridData { value:string = ''; private storeModel:StoreModel; private _editable:boolean|string = false; _editing:boolean = false; @Input() set item(i_storeModel:StoreModel) { this.storeModel = i_storeModel } @Input() set field(i_field) { this.value = this.storeModel.getKey(i_field) } @Input() set processField(i_processField:(storeModel:StoreModel)=>string) { this.value = i_processField(this.storeModel) } @Input() set editable(i_editable) { this._editable = i_editable; } @Output() labelEdited:EventEmitter = new EventEmitter(); onEdit(isEditing:boolean) { if (this._editable == false || this._editable == 'false') return; this._editing = isEditing; if (this._editing) return; // done editing, so notify var payload:ISimpleGridEdit = { value: this.value, item: this.storeModel } this.labelEdited.emit(payload); } } ================================================ FILE: src/comps/simple-grid-module/SimpleGridDataChecks.ts ================================================ import {Component, Input, Output, EventEmitter, ViewChildren, QueryList, ChangeDetectorRef} from "@angular/core"; import {List} from "immutable"; import {StoreModel} from "../../models/StoreModel"; import * as _ from "lodash"; @Component({ selector: 'td[simpleGridDataChecks]', styles: [` i { cursor: pointer; } .slideMode { padding-top: 8px; padding-right: 20px; } `], template: `
        ` }) export class SimpleGridDataChecks { constructor(private cdr: ChangeDetectorRef) { } m_checkId = _.uniqueId('slideCheck'); m_checkboxes: List private m_storeModel: StoreModel; @ViewChildren('checkInputs') inputs: QueryList @Input() set checkboxes(i_checkboxes: List) { this.m_checkboxes = i_checkboxes } @Input() set item(i_storeModel: StoreModel) { this.m_storeModel = i_storeModel } @Input() slideMode: boolean = false; @Output() changed: EventEmitter = new EventEmitter(); //@HostListener('click', ['$event']) private onClick(e) { this.cdr.detach(); let values = [] this.inputs.map(v => { values.push(v.nativeElement.checked); }); this.changed.emit({item: this.m_storeModel, value: values}); return true; } } ================================================ FILE: src/comps/simple-grid-module/SimpleGridDataCurrency.ts ================================================ import {Component, Input, ChangeDetectionStrategy, Output, EventEmitter} from '@angular/core' import {StoreModel} from "../../models/StoreModel"; import {ISimpleGridEdit} from "./SimpleGrid"; @Component({ selector: 'td[simpleGridDataCurrency]', changeDetection: ChangeDetectionStrategy.OnPush, styles: [` label { padding: 0; margin: 0; } `], template: ` ` }) export class SimpleGridDataCurrency { value:string = ''; storeModel:StoreModel; @Input() set item(i_storeModel:StoreModel) { this.storeModel = i_storeModel } @Input() set field(i_field) { this.value = this.storeModel.getKey(i_field) } @Input() set processField(i_processField:(storeModel:StoreModel)=>string) { this.value = i_processField(this.storeModel) } } ================================================ FILE: src/comps/simple-grid-module/SimpleGridDataDropdown.ts ================================================ import { Component, Input, ChangeDetectionStrategy, Output, EventEmitter, ViewChildren, QueryList, HostListener } from '@angular/core' import {List} from "immutable"; import {StoreModel} from "../../models/StoreModel"; @Component({ selector: 'td[simpleGridDataDropdown]', changeDetection: ChangeDetectionStrategy.OnPush, styles: [` i { cursor: pointer; } .select button { width: 100%; text-align: left; } .select .caret { position: absolute; right: 10px; margin-top: 10px; } .select:last-child > .btn { border-top-left-radius: 5px; border-bottom-left-radius: 5px; } .selected { padding-right: 10px; } .option { width: 100%; } `], template: `
        ` }) export class SimpleGridDataDropdown { m_dropdown: List m_storeModel: StoreModel; m_field: string = ''; value: string = ''; m_testSelection: Function; @ViewChildren('checkInputs') inputs: QueryList @Input() set dropdown(i_dropdown: List) { this.m_dropdown = i_dropdown } @Input() set item(i_storeModel: StoreModel) { this.m_storeModel = i_storeModel } @Input() set field(i_field) { this.m_field = i_field; } @Input() set testSelection(i_testSelection: (dropItem: any, storeModel: StoreModel) => 'checked' | '') { this.m_testSelection = i_testSelection; } @Output() changed: EventEmitter = new EventEmitter(); onChanges(event) { this.changed.emit({item: this.m_storeModel, value: event.target.value}); } private getSelected(i_dropItem): string { if (this.m_testSelection) { return this.m_testSelection(i_dropItem, this.m_storeModel); } return ''; } } ================================================ FILE: src/comps/simple-grid-module/SimpleGridDataImage.ts ================================================ import {Component, Input, ChangeDetectionStrategy} from '@angular/core' import {StoreModel} from "../../models/StoreModel"; @Component({ selector: 'td[simpleGridDataImage]', changeDetection: ChangeDetectionStrategy.OnPush, styles: [` i { cursor: pointer; } `], template: ` ` }) export class SimpleGridDataImage { value; style; storeModel:StoreModel; @Input() set item(i_storeModel:StoreModel) { this.storeModel = i_storeModel } @Input() set field(i_field) { this.value = i_field; } @Input() set color(i_color:string) { this.style = { color: i_color } } onClick(event){ } } ================================================ FILE: src/comps/simple-grid-module/SimpleGridDraggable.ts ================================================ import { ContentChildren, Directive, ElementRef, EventEmitter, forwardRef, Inject, Output, QueryList } from "@angular/core"; import {SimpleGridTable} from "./SimpleGridTable"; import {timeout} from "../../decorators/timeout-decorator"; import {SimpleGridRecord} from "./SimpleGridRecord"; import {Subscription} from "rxjs"; import {StoreModel} from "../../models/StoreModel"; // import TweenLite = gsap.TweenLite; export interface ISimpleGridDraggedData { newIndex: number; currentIndex: number; items: Array; } @Directive({ selector: 'tbody[simpleGridDraggable]' }) export class SimpleGridDraggable { // we have to Inject -> forwardRef as SimpleGridTable is not yet due to load order of files constructor(@Inject(forwardRef(() => SimpleGridTable)) i_table: SimpleGridTable, private el: ElementRef) { this.m_table = i_table; } private m_table: SimpleGridTable private m_draggables; private target; private y; m_items; m_selectedIdx = -1; m_sub: Subscription; @ContentChildren(SimpleGridRecord) simpleGridRecords: QueryList; @Output() dragCompleted: EventEmitter = new EventEmitter(); ngAfterViewInit() { this.createSortable(); this.m_sub = this.simpleGridRecords .changes.subscribe(v => { this.createSortable(); }); } _cleanSortables() { if (this.m_draggables) this.m_draggables.forEach((drag) => drag.kill()); } /** Create a draggable sortable list **/ @timeout(500) public createSortable() { var self = this; jQueryAny(self.el.nativeElement).children().each((i, child) => jQuery.data(child, "idx", i)); this.simpleGridRecords.forEach((rec: SimpleGridRecord, i) => rec.index = i); if (jQuery(self.el.nativeElement).children().length == 0) return; this._cleanSortables(); self.m_draggables = Draggable.create(jQuery(self.el.nativeElement).children(), { type: "y", bounds: self.el.nativeElement, dragClickables: false, edgeResistance: 1, onPress: self._sortablePress, onDragStart: self._sortableDragStart, onDrag: self._sortableDrag, liveSnap: self._sortableSnap, onDragEnd: function () { self.m_selectedIdx = -1; var t = this.target var max = t.kids.length - 1; var newIndex = Math.round(this.y / t.currentHeight); newIndex += (newIndex < 0 ? -1 : 0) + t.currentIndex; if (newIndex === max) { t.parentNode.appendChild(t); } else { t.parentNode.insertBefore(t, t.kids[newIndex + 1]); } var result:ISimpleGridDraggedData = { items: [], newIndex: newIndex < t.currentIndex ? newIndex + 1 : newIndex, currentIndex: t.currentIndex }; TweenLite.set(t.kids, {yPercent: 0, overwrite: "all"}); TweenLite.set(t, {y: 0, color: ""}); jQuery(self.el.nativeElement).children().each((i, child) => { var oldIndex = jQuery.data(child, "idx"); var found: SimpleGridRecord = self.simpleGridRecords.find((rec: SimpleGridRecord) => { return rec.index == oldIndex; }) // con(i + ' ' + found.item.getKey('event')); result.items.push(found.item) }) self.dragCompleted.emit(result) } }); } /** Sortable list on press @method _sortablePress **/ _sortablePress() { var t = this.target, i = 0, child = t; while (child = child.previousSibling) if (child.nodeType === 1) i++; t.currentIndex = i; t.currentHeight = t.offsetHeight; t.kids = [].slice.call(t.parentNode.children); // convert to array } /** Sortable drag list on press @method _sortableDragStart **/ _sortableDragStart() { TweenLite.set(this.target, {color: "#88CE02"}); } /** Sortable drag list @method _sortableDrag **/ _sortableDrag() { var t = this.target, elements = t.kids.slice(), // clone indexChange = Math.round(this.y / t.currentHeight), bound1 = t.currentIndex, bound2 = bound1 + indexChange; if (bound1 < bound2) { // moved down TweenLite.to(elements.splice(bound1 + 1, bound2 - bound1), 0.15, {yPercent: -100}); TweenLite.to(elements, 0.15, {yPercent: 0}); } else if (bound1 === bound2) { elements.splice(bound1, 1); TweenLite.to(elements, 0.15, {yPercent: 0}); } else { // moved up TweenLite.to(elements.splice(bound2, bound1 - bound2), 0.15, {yPercent: 100}); TweenLite.to(elements, 0.15, {yPercent: 0}); } } /** snap to set rounder values @method _sortableSnap **/ _sortableSnap(y) { return y; // enable code below to enable snapinnes on dragging // var h = this.target.currentHeight; // return Math.round(y / h) * h; } ngOnDestroy() { this._cleanSortables(); this.m_sub.unsubscribe(); this.m_draggables = null; } } ================================================ FILE: src/comps/simple-grid-module/SimpleGridExample.txt ================================================ import { Component, ChangeDetectionStrategy, ChangeDetectorRef, } from "@angular/core"; import {AppModel} from "../../../reseller/AppModel"; import {List} from "immutable"; import {AppStore} from "angular2-redux-util"; import {ResellerAction} from "../../../reseller/ResellerAction"; // import {ComponentInstruction} from "@angular/router"; @Component({ selector: 'apps', host: { // '[@routeAnimation]': 'true', '[style.display]': "'block'" }, template: `
        icon app name available (off | on)
        `, changeDetection: ChangeDetectionStrategy.OnPush }) export class Apps { constructor(private appStore: AppStore, private resellerAction: ResellerAction, private ref: ChangeDetectorRef) { var i_reseller = this.appStore.getState().reseller; this.apps = i_reseller.getIn(['apps']); this.unsub = this.appStore.sub((apps) => { this.apps = apps; this.ref.markForCheck(); }, 'reseller.apps'); } private sort: {field: string, desc: boolean} = {field: null, desc: false}; private apps: List; private unsub; private getInstalledStatus(item: AppModel) { return [Number(item.getInstalled())]; } private onAppInstalledChange(event, index) { this.appStore.dispatch(this.resellerAction.appStatus(event.item, event.value["0"])); } private ngOnDestroy() { this.unsub(); } } ================================================ FILE: src/comps/simple-grid-module/SimpleGridModule.ts ================================================ import {SimpleGridData} from "./SimpleGridData"; import {SimpleGridTable} from "./SimpleGridTable"; import {SimpleGridSortableHeader} from "./SimpleGridSortableHeader"; import {SimpleGridRecord} from "./SimpleGridRecord"; import {SimpleGridDataImage} from "./SimpleGridDataImage"; import {SimpleGridDataCurrency} from "./SimpleGridDataCurrency"; import {SimpleGridDataChecks} from "./SimpleGridDataChecks"; import {SimpleGridDataDropdown} from "./SimpleGridDataDropdown"; import { NgModule, ModuleWithProviders } from "@angular/core"; import {CommonModule} from "@angular/common"; import { FormsModule, ReactiveFormsModule } from "@angular/forms"; import {SimpleGridDraggable} from "./SimpleGridDraggable"; // import {StoreModel} from "../../models/StoreModel"; export const SIMPLEGRID_DIRECTIVES: Array = [SimpleGridTable, SimpleGridSortableHeader, SimpleGridRecord, SimpleGridData, SimpleGridDataCurrency, SimpleGridDataImage, SimpleGridDataChecks, SimpleGridDataDropdown, SimpleGridDraggable]; export interface ISimpleGridEdit { value: string; item: any; } @NgModule({ imports: [CommonModule, FormsModule, ReactiveFormsModule], declarations: SIMPLEGRID_DIRECTIVES, exports: SIMPLEGRID_DIRECTIVES }) // here we are loading the providers ONLY when this shared module is loaded by the app and not // by a feature or lazy loaded module, this making sure we share a single instance of AuthService export class SimpleGridModule { static forRoot(): ModuleWithProviders { return { ngModule: SimpleGridModule, providers: [] }; } } ================================================ FILE: src/comps/simple-grid-module/SimpleGridRecord.ts ================================================ import {Component, Input, Output, ChangeDetectionStrategy, HostListener, forwardRef, Inject, HostBinding, EventEmitter} from '@angular/core' import {SimpleGridTable} from "./SimpleGridTable"; @Component({ selector: 'tr[simpleGridRecord]', changeDetection: ChangeDetectionStrategy.OnPush, template: ` ` }) export class SimpleGridRecord { // we have to Inject -> forwardRef as SimpleGridTable is not yet due to load order of files constructor(@Inject(forwardRef(() => SimpleGridTable)) i_table: SimpleGridTable){ this.m_table = i_table; } private m_table:SimpleGridTable private m_index; @Input() item; @Input() selectable:boolean = true; // @Input() // set table(i_table) { // this.m_table = i_table; // } @Output() onDoubleClicked:EventEmitter = new EventEmitter(); @Output() onClicked:EventEmitter = new EventEmitter(); @HostListener('dblclick', ['$event']) doubleClicked(event) { this.setSelected(); this.onDoubleClicked.emit({ target: event.target, item: this.item ? this.item : null }); return true; } @HostListener('click', ['$event']) onSelected() { if (!this.selectable) return; this.setSelected(); this.onClicked.emit({ target: event.target, item: this.item ? this.item : null }); return true; } @HostBinding('class.selectedTr') selectedClass:boolean = false; @Input() set index(i_index:number) { this.m_index = i_index; } get index() { return this.m_index; } ngOnInit() { var selected:SimpleGridRecord = this.m_table.getSelected(); // even though the index is same as this, the Immutable data propSelectedModel // is out of sync inside table, so we need to update to latest version of this if (selected && selected.m_index == this.index) this.setSelected(); } private setSelected() { this.m_table.setSelected(this); this.selectedClass = true; } } ================================================ FILE: src/comps/simple-grid-module/SimpleGridSortableHeader.ts ================================================ import {Component, Input, ChangeDetectionStrategy} from '@angular/core'; @Component({ selector: 'th[sortableHeader]', changeDetection: ChangeDetectionStrategy.OnPush, template: `
        `, styles: [` div { cursor: pointer; width: 80px; } `] }) export class SimpleGridSortableHeader { // map the sortableHeader input into a local var named fieldName @Input('sortableHeader') fieldName: string; // no mapping just create local reference sort which gets passed in // the sort points to same instance as the one from the host component @Input() sort: {field: string, desc: boolean}; headerClicked(): void { if (this.sort.field === this.fieldName) { if (this.sort.desc === true) { this.sort.desc = false; this.sort.field = null; } else { this.sort.desc = true; } } else { this.sort.field = this.fieldName; this.sort.desc = false; } } } ================================================ FILE: src/comps/simple-grid-module/SimpleGridTable.ts ================================================ import {ChangeDetectionStrategy, Component, ContentChild, ContentChildren, Input, QueryList} from "@angular/core"; import {SimpleGridRecord} from "./SimpleGridRecord"; import {SimpleGridDraggable} from "./SimpleGridDraggable"; @Component({ selector: 'simpleGridTable', changeDetection: ChangeDetectionStrategy.OnPush, styles: [` .simpleTable { background-color: white; } * { font-size: 0.9em; } `], template: `
        `, }) export class SimpleGridTable { @Input() sort; @Input() list; private selected; @ContentChildren(SimpleGridRecord) simpleGridRecords: QueryList; @ContentChild(SimpleGridDraggable) simpleGridDraggable; public setSelected(i_selected: SimpleGridRecord) { this.deselect(); this.selected = i_selected; // var rec = i_selected.item; // console.log(`user selected ${rec.getBusinessId()} ${rec.getName()} ${rec.getAccessMask()}`); } public deselect() { this.selected = null; if (!this.simpleGridRecords && !this.simpleGridDraggable) return; // content children parsed differently depending if we are using SimpleGridDraggable or not var records: QueryList = this.simpleGridRecords.length > 0 ? this.simpleGridRecords : this.simpleGridDraggable.simpleGridRecords; records.map((i_simpleGridRecord: SimpleGridRecord) => { i_simpleGridRecord.selectedClass = false; }) } public getSelected(): SimpleGridRecord { return this.selected; } // ngAfterViewInit() { // if (!this.simpleGridRecords && !this.simpleGridDraggable) return; // var records: QueryList = this.simpleGridRecords.length > 0 || this.simpleGridDraggable.simpleGridRecords; // records.changes.subscribe(val => { // var arr = records.toArray(); // }); // } // public getOrder() { // if (!this.simpleGridRecords && !this.simpleGridDraggable) return; // var records: QueryList = this.simpleGridRecords.length > 0 || this.simpleGridDraggable.simpleGridRecords; // records.notifyOnChanges() // // this.cd.detectChanges(); // records.forEach((s: SimpleGridRecord) => { // console.log(s.index + ' ' + s.item.getKey('event')); // }); // // } } ================================================ FILE: src/comps/sliderpanel/SliderItemContent.ts ================================================ import {ApplicationRef, ChangeDetectorRef, Component, DoCheck, Input, Output, TemplateRef, ViewContainerRef} from "@angular/core"; import {Sliderpanel} from "./Sliderpanel"; import {Observable, Subject} from "rxjs"; import {Compbaser} from "ng-mslib"; export interface ISliderItemData { to: string; direction: string; } @Component({ selector: 'SlideritemContent', template: ` `, }) export class SlideritemContent extends Compbaser { private m_onChanges$ = new Subject() constructor(private viewContainer: ViewContainerRef, protected sliderPanel: Sliderpanel, private cd: ChangeDetectorRef, ap:ApplicationRef) { super(); this.viewContainer.element.nativeElement.classList.add('page'); this.sliderPanel.addSlider(this); this.cancelOnDestroy( this.m_onChanges$.debounceTime(300) .subscribe((data: any) => { this.sliderPanel.slideToPage(data.to, data.direction) this.cd.markForCheck(); }) ) } @Input() toDirection: 'left' | 'right'; @Input() fromDirection: 'left' | 'right'; @Input() to: string; @Input() from: string; @Input() showToButton: boolean = true; @Input() showFromButton: boolean = true; @Output() onChange: Observable = new Subject().delay(300).debounceTime(1000); public addClass(i_className) { this.viewContainer.element.nativeElement.classList.add(i_className); } public hasClass(i_className) { this.viewContainer.element.nativeElement.classList.contains(i_className); } public getNative() { return this.viewContainer.element.nativeElement; } public removeClass(i_className) { this.viewContainer.element.nativeElement.classList.remove(i_className); } public slideTo(to: string, direction: string) { (this.onChange as Subject).next({ to: to, direction: direction }) this.m_onChanges$.next({to, direction}) // this.sliderPanel.slideToPage(to, direction) } public onNext() { this.slideTo(this.to, this.toDirection); } public onPrev() { this.slideTo(this.from, this.fromDirection); } destroy(){ // console.log('dest SliderItem'); } } ================================================ FILE: src/comps/sliderpanel/Slideritem.ts ================================================ import {ApplicationRef, ChangeDetectorRef, Component, DoCheck, Input, Output, TemplateRef, ViewContainerRef} from "@angular/core"; import {Sliderpanel} from "./Sliderpanel"; import {Observable, Subject} from "rxjs"; import {Compbaser} from "ng-mslib"; export interface ISliderItemData { to: string; direction: string; } @Component({ selector: 'Slideritem', // changeDetection: ChangeDetectionStrategy.OnPush, template: ` `, }) export class Slideritem extends Compbaser implements DoCheck { m_render: boolean = false; m_onChanges$ = new Subject() constructor(private viewContainer: ViewContainerRef, protected sliderPanel: Sliderpanel, private cd: ChangeDetectorRef, ap: ApplicationRef) { super(); this.viewContainer.element.nativeElement.classList.add('page'); this.sliderPanel.addSlider(this); this.cancelOnDestroy( this.m_onChanges$.debounceTime(300) .subscribe((data: any) => { this.sliderPanel.slideToPage(data.to, data.direction) this.cd.markForCheck(); }) ) } ngDoCheck() { if (this.viewContainer.element.nativeElement.classList.contains('selected')) { if (this.m_render == true) return; this.m_render = true; // console.log('added'); this.cd.detectChanges(); } else { if (this.m_render == false) return; setTimeout(() => { this.m_render = false; // console.log('removed'); this.cd.detectChanges(); }, 500) } } @Input() templateRef: TemplateRef; @Input() toDirection: 'left' | 'right'; @Input() fromDirection: 'left' | 'right'; @Input() to: string; @Input() from: string; @Input() showToButton: boolean = true; @Input() showFromButton: boolean = true; @Output() onChange: Observable = new Subject().delay(300).debounceTime(1000); public addClass(i_className) { this.viewContainer.element.nativeElement.classList.add(i_className); } public hasClass(i_className) { this.viewContainer.element.nativeElement.classList.contains(i_className); } public getNative() { return this.viewContainer.element.nativeElement; } public removeClass(i_className) { this.viewContainer.element.nativeElement.classList.remove(i_className); } public slideTo(to: string, direction: string) { (this.onChange as Subject).next({ to: to, direction: direction }) this.m_onChanges$.next({to, direction}) // this.sliderPanel.slideToPage(to, direction) } public onNext() { this.slideTo(this.to, this.toDirection); } public onPrev() { this.slideTo(this.from, this.fromDirection); } destroy() { // console.log('dest SliderItem'); } } ================================================ FILE: src/comps/sliderpanel/Sliderpanel.ts ================================================ import { Component, ViewContainerRef, Inject } from '@angular/core'; import {DOCUMENT} from '@angular/platform-browser'; import {Slideritem} from "./Slideritem"; /** @class Sliderpanel example: this.slideToPage('campaignNameSelectorView', 'left') **/ @Component({ selector: 'Sliderpanel', template: `` }) export class Sliderpanel { private el: any; private viewContainer: ViewContainerRef; private dom: HTMLBodyElement; private sliders: Array = []; constructor(viewContainer: ViewContainerRef, @Inject(DOCUMENT) private doc:any) { this.dom = doc.body; this.viewContainer = viewContainer; this.el = viewContainer.element.nativeElement; } private getElementByClass(element: string) { var jq: any = jQuery; return jq(this.dom).find('.' + element, this.el)[0]; } private removeAllClassesFrom(elementClass: any, selected?: boolean) { var element = this.getElementByClass(elementClass); if (selected) { jQuery(element).removeClass('selected'); return; } jQuery(element).removeClass('left'); jQuery(element).removeClass('right'); jQuery(element).removeClass('center'); jQuery(element).removeClass('transition'); } private addClassesTo(elementClass: any, classesToAdd: string[]) { var element = this.getElementByClass(elementClass); for (var i = 0; i < classesToAdd.length; i++) { jQuery(element).addClass(classesToAdd[i]) } } public slideToPage(toClassName: string, i_direction: string): void { if (toClassName == 'selected') return; // Position the new page at the starting position of the animation this.removeAllClassesFrom(toClassName); this.addClassesTo(toClassName, ["page", i_direction]); // Position the new page and the current page at the ending position of their animation with a transition class indicating the duration of the animation and force reflow of page so it renders var parent = jQuery(this.getElementByClass(toClassName)).parent(); var grandparent = jQuery(parent).parent(); var offsetWidth = jQuery(grandparent).prop('offsetWidth'); this.removeAllClassesFrom(toClassName); this.addClassesTo(toClassName, ['page', 'transition', 'center']); this.removeAllClassesFrom('selected'); this.addClassesTo('selected', ['page', 'transition', i_direction === 'left' ? 'right' : 'left']); this.removeAllClassesFrom('selected', true); this.addClassesTo(toClassName, ['selected']); } public addSlider(i_slider) { this.sliders.push(i_slider); } } ================================================ FILE: src/comps/svg-icon/svg-icon.ts ================================================ import {Component, Input, ChangeDetectionStrategy, ElementRef, Renderer} from '@angular/core'; import {Http, Response} from '@angular/http'; @Component({ selector: 'svg-icon', template: ` `, changeDetection: ChangeDetectionStrategy.OnPush }) export class SvgIcon { @Input() set path(val: string) { this.loadSvg(val); } @Input() height; @Input() width; @Input() alt: string; constructor(private http: Http, private elementRef: ElementRef) { } loadSvg(val: string) { // this.http.get(`svgs/${val}.svg`) // grab locally this.http.get(val) .subscribe( res => { // get our element and clean it out const element = this.elementRef.nativeElement; var svgFinal; element.innerHTML = ''; // get response and build svg element var response = res.text(); const parser = new DOMParser(); if (this.height && this.width) { var svgHeight, svgWidth, re; svgHeight = response.match(/(height=")([^\"]*)/)[2]; re = new RegExp('height="' + svgHeight + '"', "ig"); response = response.replace(re, `height="${this.height}"`); svgWidth = response.match(/(width=")([^\"]*)/)[2]; re = new RegExp('width="' + svgWidth + '"', "ig"); response = response.replace(re, `width="${this.width}"`); // var s = new String(response); } svgFinal = parser.parseFromString(response, 'image/svg+xml'); element.appendChild(svgFinal.documentElement); // insert the svg result }, err => { console.error(err); }); } } ================================================ FILE: src/comps/tabs/tab.ts ================================================ import {Component, Host, Output, EventEmitter} from '@angular/core'; import {Tabs} from '../tabs/tabs'; @Component({ selector: 'tab', inputs: [ 'title:tabtitle', 'active' ], styles: [` .pane { padding: 1em; background-color: white; border-left: 1px solid #dddddd; border-right: 1px solid #dddddd; border-bottom: 1px solid #dddddd; } `], template: `
        ` }) /** Add this Tab as part of it's parents Tabs component use @Host to make sure we only look for a parent Tabs dependency injector and don't go any further to prevent lookup of wrong Tabs under misconfiguration **/ export class Tab { constructor(@Host() private tabs: Tabs) { tabs.addTab(this); } @Output() activated: EventEmitter = new EventEmitter(); public title: string; _active = false; _show = true; public set active(value) { this._active = value || false; if (this._active) this.activated.emit(true); } public get active() { return this._active; } public set show(value) { this._show = value; if (value == false) { this._active = false this.tabs.checkActive(); } } public get show() { return this._show; } } ================================================ FILE: src/comps/tabs/tabs.ts ================================================ import {Component} from '@angular/core'; //import {Tab} from 'tab'; @Component({ selector: 'tabs', template: ` ` }) export class Tabs { tabs: any[]; removed; constructor() { this.tabs = []; } private selectTab(tab, event) { event.preventDefault; this.tabs.forEach((tab) => tab.active = false); tab.active = true; return false; } /** make sure at least one tab is active **/ public checkActive() { var actives = this.tabs.filter((tab) => tab.active == true); actives.length == 0 ? this.tabs[0].active = true : null; } addTab(tab: any) { if (this.tabs.length === 0) { tab.active = true; } this.tabs.push(tab); } } ================================================ FILE: src/create_reflection.html ================================================ ================================================ FILE: src/decorators/once-decorator.ts ================================================ import {Subscriber} from "rxjs"; export function Once(milliseconds: number = 0) { return function (target, key, descriptor) { var originalMethod = descriptor.value; descriptor.value = function (...args) { var sub = originalMethod.apply(this, args); setTimeout(() => { if (sub instanceof Subscriber) { sub.unsubscribe(); } else if (sub instanceof Function) { sub() } else if (sub === null) { } else { throw new Error('@Once did not receive something to unsubscribe from, did you forget to return an Observable maybe?'); } }, milliseconds); }; return descriptor; } } ================================================ FILE: src/decorators/timeout-decorator.ts ================================================ // ref: http://blog.wolksoftware.com/decorators-reflection-javascript-typescript // ref: https://medium.com/@NetanelBasal/javascript-make-your-code-cleaner-with-decorators-d34fc72af947#.fe9f2rfb8 export function timeout( milliseconds: number = 0 ) { return function( target, key, descriptor ) { var originalMethod = descriptor.value; descriptor.value = function (...args) { setTimeout(() => { originalMethod.apply(this, args); }, milliseconds); }; return descriptor; } } ================================================ FILE: src/environments/environment.hmr.ts ================================================ import {StoreDevtoolsModule} from "@ngrx/store-devtools"; export const environment = { production: false, hmr: true, imports: [], // imports: [ // StoreDevtoolsModule.instrumentStore({maxAge: 2}), // ] }; ================================================ FILE: src/environments/environment.prod.ts ================================================ export const environment = { production: true, hmr: false, imports: [] }; ================================================ FILE: src/environments/environment.ts ================================================ // The file contents for the current environment will overwrite these during build. // The build system defaults to the dev environment which uses `environment.ts`, but if you do // `ng build --env=prod` then `environment.prod.ts` will be used instead. // The list of which env maps to which file can be found in `angular-cli.json`. import {StoreDevtoolsModule} from "@ngrx/store-devtools"; export const environment = { production: false, hmr: false, imports: [ StoreDevtoolsModule.instrumentStore({maxAge: 2}), ] }; ================================================ FILE: src/filters/filter-model-pipe.ts ================================================ import {Pipe, PipeTransform} from '@angular/core'; import {StoreModel} from "../store/model/StoreModel"; import * as _ from 'lodash'; @Pipe({ name: 'FilterModelPipe' }) export class FilterModelPipe implements PipeTransform { transform(model:StoreModel, ...args:any[]):boolean { if (_.isUndefined(args['0']) || _.isEmpty(args['0'])) return false; try { var field = args[2]; var str1:string = args[0].toLowerCase(); var str2:string; if (typeof model[field] === "function"){ str2 = model[field]().toLowerCase(); } else { str2 = model[field].toLowerCase(); } if (str2.indexOf(str1) > -1) return false; return true; } catch (e) { return false; } } } ================================================ FILE: src/hmr.ts ================================================ import { NgModuleRef, ApplicationRef } from '@angular/core'; import { createNewHosts } from '@angularclass/hmr'; export const hmrBootstrap = (module: any, bootstrap: () => Promise>) => { let ngModule: NgModuleRef; module.hot.accept(); bootstrap().then(mod => ngModule = mod); module.hot.dispose(() => { let appRef: ApplicationRef = ngModule.injector.get(ApplicationRef); let elements = appRef.components.map(c => c.location.nativeElement); let makeVisible = createNewHosts(elements); ngModule.destroy(); makeVisible(); }); }; ================================================ FILE: src/index.html ================================================ Dashboard
        ================================================ FILE: src/interfaces/BlockTypeEnum.ts ================================================ export enum BlockTypeEnum { 'COMPONENT', 'RESOURCE', 'SCENE' } ================================================ FILE: src/interfaces/Consts.ts ================================================ export const BLOCKS_LOADED = 'BLOCKS_LOADED'; export const PLACEMENT_SCENE = 'PLACEMENT_SCENE'; export const PLACEMENT_CHANNEL = 'PLACEMENT_CHANNEL'; export const PLACEMENT_IS_SCENE = 'PLACEMENT_IS_SCENE'; export const PLACEMENT_LISTS = 'PLACEMENT_LISTS'; export const FASTERQ_QUEUE_CALL_CANCLED = 'FASTERQ_QUEUE_CALL_CANCLED'; export const BLOCK_SERVICE = 'BLOCK_SERVICE'; export const BlockLabels = { 'BLOCKCODE_SCENE': 3510, 'BLOCKCODE_COLLECTION': 4100, 'BLOCKCODE_TWITTER': 4500, 'BLOCKCODE_TWITTER_ITEM': 4505, 'BLOCKCODE_JSON': 4300, 'BLOCKCODE_JSON_ITEM': 4310, 'BLOCKCODE_WORLD_WEATHER': 6010, 'BLOCKCODE_GOOGLE_SHEETS': 6022, 'BLOCKCODE_CALENDAR': 6020, 'BLOCKCODE_TWITTERV3': 6230, 'BLOCKCODE_INSTAGRAM': 6050, 'BLOCKCODE_DIGG': 6000, 'BLOCKCODE_IMAGE': 3130, 'BLOCKCODE_SVG': 3140, 'BLOCKCODE_VIDEO': 3100, 'RSS': 3345, 'QR': 3430, 'YOUTUBE': 4600, 'LOCATION': 4105, 'FASTERQ': 6100, 'IMAGE': 3160, 'EXTERNAL_VIDEO': 3150, 'CLOCK': 3320, 'HTML': 3235, 'LABEL': 3241, 'MRSS': 3340 } export class Consts { public static Clas() { return { CLASS_APP_HEIGHT: '.appHeight' }; } public static Events() { return { WIN_SIZED: 'winSized', MENU_SELECTION: 'menuSelection', STATIONS_NETWORK_ERROR: 'stationsNetworkError', UPGRADE_ENTERPRISE: 'UPGRADE_ENTERPRISE' }; } public static Values() { return { MENU_MIN_ICON_SHOW: 1550, APP_SIZE: 'AppSize', SERVER_MODE: 'serverMode', // 0 = cloud, 1 = private 2 = hybrid USER_NAME: 'userName', USER_PASS: 'userPass' }; } public static Services() { return { App: 'Application', Properties: 'Properties', ActionService: 'ActionService' }; } } ================================================ FILE: src/interfaces/IAddContent.ts ================================================ import {BlockTypeEnum} from "./BlockTypeEnum"; import {ISceneData} from "../app/blocks/block-service"; export interface IAddContents { type:BlockTypeEnum; blockCode: number; name: string; allow: boolean; fa: string; description: string; resourceId?: number; blockId?:number; sceneData?: ISceneData; size?:string; specialJsonItemName?: string; specialJsonItemColor?: string; } ================================================ FILE: src/interfaces/IRegisterCaller.ts ================================================ export interface IRegisterCaller { registerCaller(caller:any):void; } ================================================ FILE: src/interfaces/IScreenTemplate.ts ================================================ import {OrientationEnum} from "../app/campaigns/campaign-orientation"; export interface IScreenTemplateData { resolution: string; screenType: string; orientation: OrientationEnum; screenProps: {}; name: string; scale: number; campaignTimelineId?: number, campaignTimelineBoardTemplateId?: number } ================================================ FILE: src/libs/bootstrap-timepicker/CHANGELOG.md ================================================ # Changelog All notable changes will be documented in this file. This project sort of conforms to Semantic Versioning. Since we're still pre-1.0, it's like the Wild West up in here! ## Unreleased ### Added (not started) - Still planning out how to include i18n data and functionality. ### Deprecated (not started) - Incorrect usage of the word "meridian" will be deprecated. It should be "meridiem". - `showWidgetOnAddonClick`'s current behavior is not intuitive. Clicking the input addon should _toggle_ the widget instead of showing it. ## 0.5.2 - 2016-01-02 ### Added - Tabbing out of the timepicker widget will now close it. - You can specify your own icon classes. See docs for the option. ### Changed - Cleaned up `package.json` and `bower.json` files. The npm/bower package should be cleaner now. - `timepicker.less` now lives in the `css/` directory of the package. - bootstrap-timepicker now uses the latest minor releases for jQuery 2 and Bootstrap 3 ### Fixed - Fixed bad interaction between `setTime("12:00 AM")` and `showMeridian` - Various documentation issues were fixed. ## 0.5.1 - 2015-08-06 ### Changed - Critical fix (#279) for bootstrap initialization. If you happened to list your timepicker's classes in an order other than "input-group bootstrap-timepicker", you'd be out of luck. Now we use jQuery's `hasClass` method correctly. Yay! ## 0.5 - 2015-07-31 ### Changed - Bootstrap 3 support. No more Bootstrap 2 support. - setTime sets time better - more tests, and they exercise Bootstrap 3 support! - snapToStep is a new option, off by default, which snaps times to the nearest step or overflows to 0 if it would otherwise snap to 60 or more. - explicitMode is a new option, off by default, which lets you leave out colons when typing times. - shift+tab now correctly moves the cursor to the previously highlighted unit, and blurs the timepicker when expected. - We have cut out significant amounts of old cruft from the repository. - Minified/Uglified code is no longer kept in the repo. Please download a release tarball or zip file to get the compiled and minified CSS and Javascript files. ================================================ FILE: src/libs/bootstrap-timepicker/README.md ================================================ Timepicker for Twitter Bootstrap ======= [![Build Status](https://travis-ci.org/jdewit/bootstrap-timepicker.svg?branch=gh-pages)](https://travis-ci.org/jdewit/bootstrap-timepicker) A simple timepicker component for Twitter Bootstrap. Status ====== Please take a look at the `CHANGELOG.md` and the issues tab for issues we're working on and their relative priorities. Installation ============ This project is registered as a Bower package, and can be installed with the following command: ```bash bower install bootstrap-timepicker ``` You can also download our latest release (and any previous release) here. Demos & Documentation ===================== View demos & documentation. Support ======= If you make money using this timepicker, please consider supporting its development. Click here to support bootstrap-timepicker! Contributing ============ 1. Install NodeJS and Node Package Manager. 2. Install packages ```bash npm install ``` 3. Use Bower to get the dev dependencies. ```bash bower install ``` 4. Use Grunt to run tests, compress assets, etc. ```bash grunt test // run jshint and jasmine tests grunt watch // run jsHint and Jasmine tests whenever a file is changed grunt compile // minify the js and css files ``` - Please make it easy on me by covering any new features or issues with Jasmine tests. - If your changes need documentation, please take the time to update the docs. Acknowledgements ================ Thanks to everyone who have given feedback and submitted pull requests. A list of all the contributors can be found here. Special thanks to @eternicode and his Twitter Datepicker for inspiration. ================================================ FILE: src/libs/bootstrap-timepicker/bower.json ================================================ { "name": "bootstrap-timepicker", "description": "A timepicker component for Twitter Bootstrap", "version": "0.5.2", "main": "js/bootstrap-timepicker.js", "license": "MIT", "ignore": [ "**/.*", "_layouts", "node_modules", "_config.yml", "assets", "spec", "index.html", "Gruntfile.js", "package.json", "composer.json" ], "repository": { "type": "git", "url": "https://github.com/jdewit/bootstrap-timepicker" }, "dependencies": { "bootstrap": "^3.0", "jquery": "^2.0" }, "devDependencies": { "autotype": "https://raw.github.com/mmonteleone/jquery.autotype/master/jquery.autotype.js" }, "keywords": [ "widget", "timepicker", "time" ] } ================================================ FILE: src/libs/bootstrap-timepicker/composer.json ================================================ { "name" : "jdewit/bootstrap-timepicker", "description" : "A simple timepicker component for Twitter Bootstrap.", "version" : "0.5.2", "license" : "MIT", "authors": [ { "name" : "Joris de Wit", "email" : "joris.w.dewit@gmail.com" } ] } ================================================ FILE: src/libs/bootstrap-timepicker/css/bootstrap-timepicker.css ================================================ /*! * Timepicker Component for Twitter Bootstrap * * Copyright 2013 Joris de Wit * * Contributors https://github.com/jdewit/bootstrap-timepicker/graphs/contributors * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ .bootstrap-timepicker { position: relative; } .bootstrap-timepicker.pull-right .bootstrap-timepicker-widget.dropdown-menu { left: auto; right: 0; } .bootstrap-timepicker.pull-right .bootstrap-timepicker-widget.dropdown-menu:before { left: auto; right: 12px; } .bootstrap-timepicker.pull-right .bootstrap-timepicker-widget.dropdown-menu:after { left: auto; right: 13px; } .bootstrap-timepicker .input-group-addon { cursor: pointer; } .bootstrap-timepicker .input-group-addon i { display: inline-block; width: 16px; height: 16px; } .bootstrap-timepicker-widget.dropdown-menu { padding: 4px; } .bootstrap-timepicker-widget.dropdown-menu.open { display: inline-block; } .bootstrap-timepicker-widget.dropdown-menu:before { border-bottom: 7px solid rgba(0, 0, 0, 0.2); border-left: 7px solid transparent; border-right: 7px solid transparent; content: ""; display: inline-block; position: absolute; } .bootstrap-timepicker-widget.dropdown-menu:after { border-bottom: 6px solid #FFFFFF; border-left: 6px solid transparent; border-right: 6px solid transparent; content: ""; display: inline-block; position: absolute; } .bootstrap-timepicker-widget.timepicker-orient-left:before { left: 6px; } .bootstrap-timepicker-widget.timepicker-orient-left:after { left: 7px; } .bootstrap-timepicker-widget.timepicker-orient-right:before { right: 6px; } .bootstrap-timepicker-widget.timepicker-orient-right:after { right: 7px; } .bootstrap-timepicker-widget.timepicker-orient-top:before { top: -7px; } .bootstrap-timepicker-widget.timepicker-orient-top:after { top: -6px; } .bootstrap-timepicker-widget.timepicker-orient-bottom:before { bottom: -7px; border-bottom: 0; border-top: 7px solid #999; } .bootstrap-timepicker-widget.timepicker-orient-bottom:after { bottom: -6px; border-bottom: 0; border-top: 6px solid #ffffff; } .bootstrap-timepicker-widget a.btn, .bootstrap-timepicker-widget input { border-radius: 4px; } .bootstrap-timepicker-widget table { width: 100%; margin: 0; } .bootstrap-timepicker-widget table td { text-align: center; height: 30px; margin: 0; padding: 2px; } .bootstrap-timepicker-widget table td:not(.separator) { min-width: 30px; } .bootstrap-timepicker-widget table td span { width: 100%; } .bootstrap-timepicker-widget table td a { border: 1px transparent solid; width: 100%; display: inline-block; margin: 0; padding: 8px 0; outline: 0; color: #333; } .bootstrap-timepicker-widget table td a:hover { text-decoration: none; background-color: #eee; -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px; border-color: #ddd; } .bootstrap-timepicker-widget table td a i { margin-top: 2px; font-size: 18px; } .bootstrap-timepicker-widget table td input { width: 25px; margin: 0; text-align: center; } .bootstrap-timepicker-widget .modal-content { padding: 4px; } @media (min-width: 767px) { .bootstrap-timepicker-widget.modal { width: 200px; margin-left: -100px; } } @media (max-width: 767px) { .bootstrap-timepicker { width: 100%; } .bootstrap-timepicker .dropdown-menu { width: 100%; } } ================================================ FILE: src/libs/bootstrap-timepicker/css/bootstrap-timepicker.min.css ================================================ /*! * Timepicker Component for Twitter Bootstrap * * Copyright 2013 Joris de Wit * * Contributors https://github.com/jdewit/bootstrap-timepicker/graphs/contributors * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */.bootstrap-timepicker{position:relative}.bootstrap-timepicker.pull-right .bootstrap-timepicker-widget.dropdown-menu{left:auto;right:0}.bootstrap-timepicker.pull-right .bootstrap-timepicker-widget.dropdown-menu:before{left:auto;right:12px}.bootstrap-timepicker.pull-right .bootstrap-timepicker-widget.dropdown-menu:after{left:auto;right:13px}.bootstrap-timepicker .input-group-addon{cursor:pointer}.bootstrap-timepicker .input-group-addon i{display:inline-block;width:16px;height:16px}.bootstrap-timepicker-widget.dropdown-menu{padding:4px}.bootstrap-timepicker-widget.dropdown-menu.open{display:inline-block}.bootstrap-timepicker-widget.dropdown-menu:before{border-bottom:7px solid rgba(0,0,0,0.2);border-left:7px solid transparent;border-right:7px solid transparent;content:"";display:inline-block;position:absolute}.bootstrap-timepicker-widget.dropdown-menu:after{border-bottom:6px solid #fff;border-left:6px solid transparent;border-right:6px solid transparent;content:"";display:inline-block;position:absolute}.bootstrap-timepicker-widget.timepicker-orient-left:before{left:6px}.bootstrap-timepicker-widget.timepicker-orient-left:after{left:7px}.bootstrap-timepicker-widget.timepicker-orient-right:before{right:6px}.bootstrap-timepicker-widget.timepicker-orient-right:after{right:7px}.bootstrap-timepicker-widget.timepicker-orient-top:before{top:-7px}.bootstrap-timepicker-widget.timepicker-orient-top:after{top:-6px}.bootstrap-timepicker-widget.timepicker-orient-bottom:before{bottom:-7px;border-bottom:0;border-top:7px solid #999}.bootstrap-timepicker-widget.timepicker-orient-bottom:after{bottom:-6px;border-bottom:0;border-top:6px solid #fff}.bootstrap-timepicker-widget a.btn,.bootstrap-timepicker-widget input{border-radius:4px}.bootstrap-timepicker-widget table{width:100%;margin:0}.bootstrap-timepicker-widget table td{text-align:center;height:30px;margin:0;padding:2px}.bootstrap-timepicker-widget table td:not(.separator){min-width:30px}.bootstrap-timepicker-widget table td span{width:100%}.bootstrap-timepicker-widget table td a{border:1px transparent solid;width:100%;display:inline-block;margin:0;padding:8px 0;outline:0;color:#333}.bootstrap-timepicker-widget table td a:hover{text-decoration:none;background-color:#eee;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;border-color:#ddd}.bootstrap-timepicker-widget table td a i{margin-top:2px;font-size:18px}.bootstrap-timepicker-widget table td input{width:25px;margin:0;text-align:center}.bootstrap-timepicker-widget .modal-content{padding:4px}@media(min-width:767px){.bootstrap-timepicker-widget.modal{width:200px;margin-left:-100px}}@media(max-width:767px){.bootstrap-timepicker{width:100%}.bootstrap-timepicker .dropdown-menu{width:100%}} ================================================ FILE: src/libs/bootstrap-timepicker/css/timepicker.less ================================================ /*! * Timepicker Component for Twitter Bootstrap * * Copyright 2013 Joris de Wit * * Contributors https://github.com/jdewit/bootstrap-timepicker/graphs/contributors * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ .bootstrap-timepicker { position: relative; &.pull-right { .bootstrap-timepicker-widget { &.dropdown-menu { left: auto; right: 0; &:before { left: auto; right: 12px; } &:after { left: auto; right: 13px; } } } } .input-group-addon { cursor: pointer; i { display: inline-block; width: 16px; height: 16px; } } } .bootstrap-timepicker-widget { &.dropdown-menu { padding: 4px; &.open { display: inline-block; } &:before { border-bottom: 7px solid rgba(0, 0, 0, 0.2); border-left: 7px solid transparent; border-right: 7px solid transparent; content: ""; display: inline-block; position: absolute; } &:after { border-bottom: 6px solid #FFFFFF; border-left: 6px solid transparent; border-right: 6px solid transparent; content: ""; display: inline-block; position: absolute; } } &.timepicker-orient-left { &:before { left: 6px; } &:after { left: 7px; } } &.timepicker-orient-right { &:before { right: 6px; } &:after { right: 7px; } } &.timepicker-orient-top { &:before { top: -7px; } &:after { top: -6px; } } &.timepicker-orient-bottom { &:before { bottom: -7px; border-bottom: 0; border-top: 7px solid #999; } &:after { bottom: -6px; border-bottom: 0; border-top: 6px solid #ffffff; } } a.btn, input { border-radius: 4px; } table { width: 100%; margin: 0; td { text-align: center; height: 30px; margin: 0; padding: 2px; &:not(.separator) { min-width: 30px; } span { width: 100%; } a { border: 1px transparent solid; width: 100%; display: inline-block; margin: 0; padding: 8px 0; outline: 0; color: #333; &:hover { text-decoration: none; background-color: #eee; -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px; border-color: #ddd; } i { margin-top: 2px; font-size: 18px; } } input { width: 25px; margin: 0; text-align: center; } } } } .bootstrap-timepicker-widget .modal-content { padding: 4px; } @media (min-width: 767px) { .bootstrap-timepicker-widget.modal { width: 200px; margin-left: -100px; } } @media (max-width: 767px) { .bootstrap-timepicker { width: 100%; .dropdown-menu { width: 100%; } } } ================================================ FILE: src/libs/bootstrap-timepicker/js/bootstrap-timepicker.js ================================================ /*! * Timepicker Component for Twitter Bootstrap * * Copyright 2013 Joris de Wit and bootstrap-timepicker contributors * * Contributors https://github.com/jdewit/bootstrap-timepicker/graphs/contributors * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ (function($, window, document) { 'use strict'; // TIMEPICKER PUBLIC CLASS DEFINITION var Timepicker = function(element, options) { this.widget = ''; this.$element = $(element); this.defaultTime = options.defaultTime; this.disableFocus = options.disableFocus; this.disableMousewheel = options.disableMousewheel; this.isOpen = options.isOpen; this.minuteStep = options.minuteStep; this.modalBackdrop = options.modalBackdrop; this.orientation = options.orientation; this.secondStep = options.secondStep; this.snapToStep = options.snapToStep; this.showInputs = options.showInputs; this.showMeridian = options.showMeridian; this.showSeconds = options.showSeconds; this.template = options.template; this.appendWidgetTo = options.appendWidgetTo; this.showWidgetOnAddonClick = options.showWidgetOnAddonClick; this.icons = options.icons; this.maxHours = options.maxHours; this.explicitMode = options.explicitMode; // If true 123 = 1:23, 12345 = 1:23:45, else invalid. this.handleDocumentClick = function (e) { var self = e.data.scope; // This condition was inspired by bootstrap-datepicker. // The element the timepicker is invoked on is the input but it has a sibling for addon/button. if (!(self.$element.parent().find(e.target).length || self.$widget.is(e.target) || self.$widget.find(e.target).length)) { self.hideWidget(); } }; this._init(); }; Timepicker.prototype = { constructor: Timepicker, _init: function() { var self = this; if (this.showWidgetOnAddonClick && (this.$element.parent().hasClass('input-group') && this.$element.parent().hasClass('bootstrap-timepicker'))) { this.$element.parent('.input-group.bootstrap-timepicker').find('.input-group-addon').on({ 'click.timepicker': $.proxy(this.showWidget, this) }); this.$element.on({ 'focus.timepicker': $.proxy(this.highlightUnit, this), 'click.timepicker': $.proxy(this.highlightUnit, this), 'keydown.timepicker': $.proxy(this.elementKeydown, this), 'blur.timepicker': $.proxy(this.blurElement, this), 'mousewheel.timepicker DOMMouseScroll.timepicker': $.proxy(this.mousewheel, this) }); } else { if (this.template) { this.$element.on({ 'focus.timepicker': $.proxy(this.showWidget, this), 'click.timepicker': $.proxy(this.showWidget, this), 'blur.timepicker': $.proxy(this.blurElement, this), 'mousewheel.timepicker DOMMouseScroll.timepicker': $.proxy(this.mousewheel, this) }); } else { this.$element.on({ 'focus.timepicker': $.proxy(this.highlightUnit, this), 'click.timepicker': $.proxy(this.highlightUnit, this), 'keydown.timepicker': $.proxy(this.elementKeydown, this), 'blur.timepicker': $.proxy(this.blurElement, this), 'mousewheel.timepicker DOMMouseScroll.timepicker': $.proxy(this.mousewheel, this) }); } } if (this.template !== false) { this.$widget = $(this.getTemplate()).on('click', $.proxy(this.widgetClick, this)); } else { this.$widget = false; } if (this.showInputs && this.$widget !== false) { this.$widget.find('input').each(function() { $(this).on({ 'click.timepicker': function() { $(this).select(); }, 'keydown.timepicker': $.proxy(self.widgetKeydown, self), 'keyup.timepicker': $.proxy(self.widgetKeyup, self) }); }); } this.setDefaultTime(this.defaultTime); }, blurElement: function() { this.highlightedUnit = null; this.updateFromElementVal(); }, clear: function() { this.hour = ''; this.minute = ''; this.second = ''; this.meridian = ''; this.$element.val(''); }, decrementHour: function() { if (this.showMeridian) { if (this.hour === 1) { this.hour = 12; } else if (this.hour === 12) { this.hour--; return this.toggleMeridian(); } else if (this.hour === 0) { this.hour = 11; return this.toggleMeridian(); } else { this.hour--; } } else { if (this.hour <= 0) { this.hour = this.maxHours - 1; } else { this.hour--; } } }, decrementMinute: function(step) { var newVal; if (step) { newVal = this.minute - step; } else { newVal = this.minute - this.minuteStep; } if (newVal < 0) { this.decrementHour(); this.minute = newVal + 60; } else { this.minute = newVal; } }, decrementSecond: function() { var newVal = this.second - this.secondStep; if (newVal < 0) { this.decrementMinute(true); this.second = newVal + 60; } else { this.second = newVal; } }, elementKeydown: function(e) { switch (e.which) { case 9: //tab if (e.shiftKey) { if (this.highlightedUnit === 'hour') { this.hideWidget(); break; } this.highlightPrevUnit(); } else if ((this.showMeridian && this.highlightedUnit === 'meridian') || (this.showSeconds && this.highlightedUnit === 'second') || (!this.showMeridian && !this.showSeconds && this.highlightedUnit ==='minute')) { this.hideWidget(); break; } else { this.highlightNextUnit(); } e.preventDefault(); this.updateFromElementVal(); break; case 27: // escape this.updateFromElementVal(); break; case 37: // left arrow e.preventDefault(); this.highlightPrevUnit(); this.updateFromElementVal(); break; case 38: // up arrow e.preventDefault(); switch (this.highlightedUnit) { case 'hour': this.incrementHour(); this.highlightHour(); break; case 'minute': this.incrementMinute(); this.highlightMinute(); break; case 'second': this.incrementSecond(); this.highlightSecond(); break; case 'meridian': this.toggleMeridian(); this.highlightMeridian(); break; } this.update(); break; case 39: // right arrow e.preventDefault(); this.highlightNextUnit(); this.updateFromElementVal(); break; case 40: // down arrow e.preventDefault(); switch (this.highlightedUnit) { case 'hour': this.decrementHour(); this.highlightHour(); break; case 'minute': this.decrementMinute(); this.highlightMinute(); break; case 'second': this.decrementSecond(); this.highlightSecond(); break; case 'meridian': this.toggleMeridian(); this.highlightMeridian(); break; } this.update(); break; } }, getCursorPosition: function() { var input = this.$element.get(0); if ('selectionStart' in input) {// Standard-compliant browsers return input.selectionStart; } else if (document.selection) {// IE fix input.focus(); var sel = document.selection.createRange(), selLen = document.selection.createRange().text.length; sel.moveStart('character', - input.value.length); return sel.text.length - selLen; } }, getTemplate: function() { var template, hourTemplate, minuteTemplate, secondTemplate, meridianTemplate, templateContent; if (this.showInputs) { hourTemplate = ''; minuteTemplate = ''; secondTemplate = ''; meridianTemplate = ''; } else { hourTemplate = ''; minuteTemplate = ''; secondTemplate = ''; meridianTemplate = ''; } templateContent = ''+ ''+ ''+ ''+ ''+ (this.showSeconds ? ''+ '' : '') + (this.showMeridian ? ''+ '' : '') + ''+ ''+ ' '+ ''+ ' '+ (this.showSeconds ? ''+ '' : '') + (this.showMeridian ? ''+ '' : '') + ''+ ''+ ''+ ''+ ''+ (this.showSeconds ? ''+ '' : '') + (this.showMeridian ? ''+ '' : '') + ''+ '
           
        '+ hourTemplate +':'+ minuteTemplate +':'+ secondTemplate +' '+ meridianTemplate +'
          
        '; switch(this.template) { case 'modal': template = ''; break; case 'dropdown': template = ''; break; } return template; }, getTime: function() { if (this.hour === '') { return ''; } return this.hour + ':' + (this.minute.toString().length === 1 ? '0' + this.minute : this.minute) + (this.showSeconds ? ':' + (this.second.toString().length === 1 ? '0' + this.second : this.second) : '') + (this.showMeridian ? ' ' + this.meridian : ''); }, hideWidget: function() { if (this.isOpen === false) { return; } this.$element.trigger({ 'type': 'hide.timepicker', 'time': { 'value': this.getTime(), 'hours': this.hour, 'minutes': this.minute, 'seconds': this.second, 'meridian': this.meridian } }); if (this.template === 'modal' && this.$widget.modal) { this.$widget.modal('hide'); } else { this.$widget.removeClass('open'); } $(document).off('mousedown.timepicker, touchend.timepicker', this.handleDocumentClick); this.isOpen = false; // m_show/hide approach taken by datepicker this.$widget.detach(); }, highlightUnit: function() { this.position = this.getCursorPosition(); if (this.position >= 0 && this.position <= 2) { this.highlightHour(); } else if (this.position >= 3 && this.position <= 5) { this.highlightMinute(); } else if (this.position >= 6 && this.position <= 8) { if (this.showSeconds) { this.highlightSecond(); } else { this.highlightMeridian(); } } else if (this.position >= 9 && this.position <= 11) { this.highlightMeridian(); } }, highlightNextUnit: function() { switch (this.highlightedUnit) { case 'hour': this.highlightMinute(); break; case 'minute': if (this.showSeconds) { this.highlightSecond(); } else if (this.showMeridian){ this.highlightMeridian(); } else { this.highlightHour(); } break; case 'second': if (this.showMeridian) { this.highlightMeridian(); } else { this.highlightHour(); } break; case 'meridian': this.highlightHour(); break; } }, highlightPrevUnit: function() { switch (this.highlightedUnit) { case 'hour': if(this.showMeridian){ this.highlightMeridian(); } else if (this.showSeconds) { this.highlightSecond(); } else { this.highlightMinute(); } break; case 'minute': this.highlightHour(); break; case 'second': this.highlightMinute(); break; case 'meridian': if (this.showSeconds) { this.highlightSecond(); } else { this.highlightMinute(); } break; } }, highlightHour: function() { var $element = this.$element.get(0), self = this; this.highlightedUnit = 'hour'; if ($element.setSelectionRange) { setTimeout(function() { if (self.hour < 10) { $element.setSelectionRange(0,1); } else { $element.setSelectionRange(0,2); } }, 0); } }, highlightMinute: function() { var $element = this.$element.get(0), self = this; this.highlightedUnit = 'minute'; if ($element.setSelectionRange) { setTimeout(function() { if (self.hour < 10) { $element.setSelectionRange(2,4); } else { $element.setSelectionRange(3,5); } }, 0); } }, highlightSecond: function() { var $element = this.$element.get(0), self = this; this.highlightedUnit = 'second'; if ($element.setSelectionRange) { setTimeout(function() { if (self.hour < 10) { $element.setSelectionRange(5,7); } else { $element.setSelectionRange(6,8); } }, 0); } }, highlightMeridian: function() { var $element = this.$element.get(0), self = this; this.highlightedUnit = 'meridian'; if ($element.setSelectionRange) { if (this.showSeconds) { setTimeout(function() { if (self.hour < 10) { $element.setSelectionRange(8,10); } else { $element.setSelectionRange(9,11); } }, 0); } else { setTimeout(function() { if (self.hour < 10) { $element.setSelectionRange(5,7); } else { $element.setSelectionRange(6,8); } }, 0); } } }, incrementHour: function() { if (this.showMeridian) { if (this.hour === 11) { this.hour++; return this.toggleMeridian(); } else if (this.hour === 12) { this.hour = 0; } } if (this.hour === this.maxHours - 1) { this.hour = 0; return; } this.hour++; }, incrementMinute: function(step) { var newVal; if (step) { newVal = this.minute + step; } else { newVal = this.minute + this.minuteStep - (this.minute % this.minuteStep); } if (newVal > 59) { this.incrementHour(); this.minute = newVal - 60; } else { this.minute = newVal; } }, incrementSecond: function() { var newVal = this.second + this.secondStep - (this.second % this.secondStep); if (newVal > 59) { this.incrementMinute(true); this.second = newVal - 60; } else { this.second = newVal; } }, mousewheel: function(e) { if (this.disableMousewheel) { return; } e.preventDefault(); e.stopPropagation(); var delta = e.originalEvent.wheelDelta || -e.originalEvent.detail, scrollTo = null; if (e.type === 'mousewheel') { scrollTo = (e.originalEvent.wheelDelta * -1); } else if (e.type === 'DOMMouseScroll') { scrollTo = 40 * e.originalEvent.detail; } if (scrollTo) { e.preventDefault(); $(this).scrollTop(scrollTo + $(this).scrollTop()); } switch (this.highlightedUnit) { case 'minute': if (delta > 0) { this.incrementMinute(); } else { this.decrementMinute(); } this.highlightMinute(); break; case 'second': if (delta > 0) { this.incrementSecond(); } else { this.decrementSecond(); } this.highlightSecond(); break; case 'meridian': this.toggleMeridian(); this.highlightMeridian(); break; default: if (delta > 0) { this.incrementHour(); } else { this.decrementHour(); } this.highlightHour(); break; } return false; }, /** * Given a segment value like 43, will round and snap the segment * to the nearest "step", like 45 if step is 15. Segment will * "overflow" to 0 if it's larger than 59 or would otherwise * round up to 60. */ changeToNearestStep: function (segment, step) { if (segment % step === 0) { return segment; } if (Math.round((segment % step) / step)) { return (segment + (step - segment % step)) % 60; } else { return segment - segment % step; } }, // This method was adapted from bootstrap-datepicker. place : function() { if (this.isInline) { return; } var widgetWidth = this.$widget.outerWidth(), widgetHeight = this.$widget.outerHeight(), visualPadding = 10, windowWidth = $(window).width(), windowHeight = $(window).height(), scrollTop = $(window).scrollTop(); var zIndex = parseInt(this.$element.parents().filter(function() { return $(this).css('z-index') !== 'auto'; }).first().css('z-index'), 10) + 10; var offset = this.component ? this.component.parent().offset() : this.$element.offset(); var height = this.component ? this.component.outerHeight(true) : this.$element.outerHeight(false); var width = this.component ? this.component.outerWidth(true) : this.$element.outerWidth(false); var left = offset.left, top = offset.top; this.$widget.removeClass('timepicker-orient-top timepicker-orient-bottom timepicker-orient-right timepicker-orient-left'); if (this.orientation.x !== 'auto') { this.$widget.addClass('timepicker-orient-' + this.orientation.x); if (this.orientation.x === 'right') { left -= widgetWidth - width; } } else{ // auto x orientation is best-placement: if it crosses a window edge, fudge it sideways // Default to left this.$widget.addClass('timepicker-orient-left'); if (offset.left < 0) { left -= offset.left - visualPadding; } else if (offset.left + widgetWidth > windowWidth) { left = windowWidth - widgetWidth - visualPadding; } } // auto y orientation is best-situation: top or bottom, no fudging, decision based on which shows more of the widget var yorient = this.orientation.y, topOverflow, bottomOverflow; if (yorient === 'auto') { topOverflow = -scrollTop + offset.top - widgetHeight; bottomOverflow = scrollTop + windowHeight - (offset.top + height + widgetHeight); if (Math.max(topOverflow, bottomOverflow) === bottomOverflow) { yorient = 'top'; } else { yorient = 'bottom'; } } this.$widget.addClass('timepicker-orient-' + yorient); if (yorient === 'top'){ top += height; } else{ top -= widgetHeight + parseInt(this.$widget.css('padding-top'), 10); } this.$widget.css({ top : top, left : left, zIndex : zIndex }); }, remove: function() { $('document').off('.timepicker'); if (this.$widget) { this.$widget.remove(); } delete this.$element.data().timepicker; }, setDefaultTime: function(defaultTime) { if (!this.$element.val()) { if (defaultTime === 'current') { var dTime = new Date(), hours = dTime.getHours(), minutes = dTime.getMinutes(), seconds = dTime.getSeconds(), meridian = 'AM'; if (seconds !== 0) { seconds = Math.ceil(dTime.getSeconds() / this.secondStep) * this.secondStep; if (seconds === 60) { minutes += 1; seconds = 0; } } if (minutes !== 0) { minutes = Math.ceil(dTime.getMinutes() / this.minuteStep) * this.minuteStep; if (minutes === 60) { hours += 1; minutes = 0; } } if (this.showMeridian) { if (hours === 0) { hours = 12; } else if (hours >= 12) { if (hours > 12) { hours = hours - 12; } meridian = 'PM'; } else { meridian = 'AM'; } } this.hour = hours; this.minute = minutes; this.second = seconds; this.meridian = meridian; this.update(); } else if (defaultTime === false) { this.hour = 0; this.minute = 0; this.second = 0; this.meridian = 'AM'; } else { this.setTime(defaultTime); } } else { this.updateFromElementVal(); } }, setTime: function(time, ignoreWidget) { if (!time) { this.clear(); return; } var timeMode, timeArray, hour, minute, second, meridian; if (typeof time === 'object' && time.getMonth){ // this is a date object hour = time.getHours(); minute = time.getMinutes(); second = time.getSeconds(); if (this.showMeridian){ meridian = 'AM'; if (hour > 12){ meridian = 'PM'; hour = hour % 12; } if (hour === 12){ meridian = 'PM'; } } } else { timeMode = ((/a/i).test(time) ? 1 : 0) + ((/p/i).test(time) ? 2 : 0); // 0 = none, 1 = AM, 2 = PM, 3 = BOTH. if (timeMode > 2) { // If both are present, fail. this.clear(); return; } timeArray = time.replace(/[^0-9\:]/g, '').split(':'); hour = timeArray[0] ? timeArray[0].toString() : timeArray.toString(); if(this.explicitMode && hour.length > 2 && (hour.length % 2) !== 0 ) { this.clear(); return; } minute = timeArray[1] ? timeArray[1].toString() : ''; second = timeArray[2] ? timeArray[2].toString() : ''; // adaptive time parsing if (hour.length > 4) { second = hour.slice(-2); hour = hour.slice(0, -2); } if (hour.length > 2) { minute = hour.slice(-2); hour = hour.slice(0, -2); } if (minute.length > 2) { second = minute.slice(-2); minute = minute.slice(0, -2); } hour = parseInt(hour, 10); minute = parseInt(minute, 10); second = parseInt(second, 10); if (isNaN(hour)) { hour = 0; } if (isNaN(minute)) { minute = 0; } if (isNaN(second)) { second = 0; } // Adjust the time based upon unit boundary. // NOTE: Negatives will never occur due to time.replace() above. if (second > 59) { second = 59; } if (minute > 59) { minute = 59; } if (hour >= this.maxHours) { // No day/date handling. hour = this.maxHours - 1; } if (this.showMeridian) { if (hour > 12) { // Force PM. timeMode = 2; hour -= 12; } if (!timeMode) { timeMode = 1; } if (hour === 0) { hour = 12; // AM or PM, reset to 12. 0 AM = 12 AM. 0 PM = 12 PM, etc. } meridian = timeMode === 1 ? 'AM' : 'PM'; } else if (hour < 12 && timeMode === 2) { hour += 12; } else { if (hour >= this.maxHours) { hour = this.maxHours - 1; } else if ((hour < 0) || (hour === 12 && timeMode === 1)){ hour = 0; } } } this.hour = hour; if (this.snapToStep) { this.minute = this.changeToNearestStep(minute, this.minuteStep); this.second = this.changeToNearestStep(second, this.secondStep); } else { this.minute = minute; this.second = second; } this.meridian = meridian; this.update(ignoreWidget); }, showWidget: function() { if (this.isOpen) { return; } if (this.$element.is(':disabled')) { return; } // m_show/hide approach taken by datepicker this.$widget.appendTo(this.appendWidgetTo); $(document).on('mousedown.timepicker, touchend.timepicker', {scope: this}, this.handleDocumentClick); this.$element.trigger({ 'type': 'show.timepicker', 'time': { 'value': this.getTime(), 'hours': this.hour, 'minutes': this.minute, 'seconds': this.second, 'meridian': this.meridian } }); this.place(); if (this.disableFocus) { this.$element.blur(); } // widget shouldn't be empty on open if (this.hour === '') { if (this.defaultTime) { this.setDefaultTime(this.defaultTime); } else { this.setTime('0:0:0'); } } if (this.template === 'modal' && this.$widget.modal) { this.$widget.modal('show').on('hidden', $.proxy(this.hideWidget, this)); } else { if (this.isOpen === false) { this.$widget.addClass('open'); } } this.isOpen = true; }, toggleMeridian: function() { this.meridian = this.meridian === 'AM' ? 'PM' : 'AM'; }, update: function(ignoreWidget) { this.updateElement(); if (!ignoreWidget) { this.updateWidget(); } this.$element.trigger({ 'type': 'changeTime.timepicker', 'time': { 'value': this.getTime(), 'hours': this.hour, 'minutes': this.minute, 'seconds': this.second, 'meridian': this.meridian } }); }, updateElement: function() { this.$element.val(this.getTime()).change(); }, updateFromElementVal: function() { this.setTime(this.$element.val()); }, updateWidget: function() { if (this.$widget === false) { return; } var hour = this.hour, minute = this.minute.toString().length === 1 ? '0' + this.minute : this.minute, second = this.second.toString().length === 1 ? '0' + this.second : this.second; if (this.showInputs) { this.$widget.find('input.bootstrap-timepicker-hour').val(hour); this.$widget.find('input.bootstrap-timepicker-minute').val(minute); if (this.showSeconds) { this.$widget.find('input.bootstrap-timepicker-second').val(second); } if (this.showMeridian) { this.$widget.find('input.bootstrap-timepicker-meridian').val(this.meridian); } } else { this.$widget.find('span.bootstrap-timepicker-hour').text(hour); this.$widget.find('span.bootstrap-timepicker-minute').text(minute); if (this.showSeconds) { this.$widget.find('span.bootstrap-timepicker-second').text(second); } if (this.showMeridian) { this.$widget.find('span.bootstrap-timepicker-meridian').text(this.meridian); } } }, updateFromWidgetInputs: function() { if (this.$widget === false) { return; } var t = this.$widget.find('input.bootstrap-timepicker-hour').val() + ':' + this.$widget.find('input.bootstrap-timepicker-minute').val() + (this.showSeconds ? ':' + this.$widget.find('input.bootstrap-timepicker-second').val() : '') + (this.showMeridian ? this.$widget.find('input.bootstrap-timepicker-meridian').val() : '') ; this.setTime(t, true); }, widgetClick: function(e) { e.stopPropagation(); e.preventDefault(); var $input = $(e.target), action = $input.closest('a').data('action'); if (action) { this[action](); } this.update(); if ($input.is('input')) { $input.get(0).setSelectionRange(0,2); } }, widgetKeydown: function(e) { var $input = $(e.target), name = $input.attr('class').replace('bootstrap-timepicker-', ''); switch (e.which) { case 9: //tab if (e.shiftKey) { if (name === 'hour') { return this.hideWidget(); } } else if ((this.showMeridian && name === 'meridian') || (this.showSeconds && name === 'second') || (!this.showMeridian && !this.showSeconds && name === 'minute')) { return this.hideWidget(); } break; case 27: // escape this.hideWidget(); break; case 38: // up arrow e.preventDefault(); switch (name) { case 'hour': this.incrementHour(); break; case 'minute': this.incrementMinute(); break; case 'second': this.incrementSecond(); break; case 'meridian': this.toggleMeridian(); break; } this.setTime(this.getTime()); $input.get(0).setSelectionRange(0,2); break; case 40: // down arrow e.preventDefault(); switch (name) { case 'hour': this.decrementHour(); break; case 'minute': this.decrementMinute(); break; case 'second': this.decrementSecond(); break; case 'meridian': this.toggleMeridian(); break; } this.setTime(this.getTime()); $input.get(0).setSelectionRange(0,2); break; } }, widgetKeyup: function(e) { if ((e.which === 65) || (e.which === 77) || (e.which === 80) || (e.which === 46) || (e.which === 8) || (e.which >= 48 && e.which <= 57) || (e.which >= 96 && e.which <= 105)) { this.updateFromWidgetInputs(); } } }; //TIMEPICKER PLUGIN DEFINITION $.fn.timepicker = function(option) { var args = Array.apply(null, arguments); args.shift(); return this.each(function() { var $this = $(this), data = $this.data('timepicker'), options = typeof option === 'object' && option; if (!data) { $this.data('timepicker', (data = new Timepicker(this, $.extend({}, $.fn.timepicker.defaults, options, $(this).data())))); } if (typeof option === 'string') { data[option].apply(data, args); } }); }; $.fn.timepicker.defaults = { defaultTime: 'current', disableFocus: false, disableMousewheel: false, isOpen: false, minuteStep: 15, modalBackdrop: false, orientation: { x: 'auto', y: 'auto'}, secondStep: 15, snapToStep: false, showSeconds: false, showInputs: true, showMeridian: true, template: 'dropdown', appendWidgetTo: 'body', showWidgetOnAddonClick: true, icons: { up: 'glyphicon glyphicon-chevron-up', down: 'glyphicon glyphicon-chevron-down' }, maxHours: 24, explicitMode: false }; $.fn.timepicker.Constructor = Timepicker; $(document).on( 'focus.timepicker.data-api click.timepicker.data-api', '[data-provide="timepicker"]', function(e){ var $this = $(this); if ($this.data('timepicker')) { return; } e.preventDefault(); // component click requires us to explicitly m_show it $this.timepicker(); } ); })(jQuery, window, document); ================================================ FILE: src/libs/bootstrap-timepicker/js/bootstrap-timepicker.min.js ================================================ /*! bootstrap-timepicker v0.5.2 * http://jdewit.github.com/bootstrap-timepicker * Copyright (c) 2016 Joris de Wit and bootstrap-timepicker contributors * MIT License */!function(a,b,c){"use strict";var d=function(b,c){this.widget="",this.$element=a(b),this.defaultTime=c.defaultTime,this.disableFocus=c.disableFocus,this.disableMousewheel=c.disableMousewheel,this.isOpen=c.isOpen,this.minuteStep=c.minuteStep,this.modalBackdrop=c.modalBackdrop,this.orientation=c.orientation,this.secondStep=c.secondStep,this.snapToStep=c.snapToStep,this.showInputs=c.showInputs,this.showMeridian=c.showMeridian,this.showSeconds=c.showSeconds,this.template=c.template,this.appendWidgetTo=c.appendWidgetTo,this.showWidgetOnAddonClick=c.showWidgetOnAddonClick,this.icons=c.icons,this.maxHours=c.maxHours,this.explicitMode=c.explicitMode,this.handleDocumentClick=function(a){var b=a.data.scope;b.$element.parent().find(a.target).length||b.$widget.is(a.target)||b.$widget.find(a.target).length||b.hideWidget()},this._init()};d.prototype={constructor:d,_init:function(){var b=this;this.showWidgetOnAddonClick&&this.$element.parent().hasClass("input-group")&&this.$element.parent().hasClass("bootstrap-timepicker")?(this.$element.parent(".input-group.bootstrap-timepicker").find(".input-group-addon").on({"click.timepicker":a.proxy(this.showWidget,this)}),this.$element.on({"focus.timepicker":a.proxy(this.highlightUnit,this),"click.timepicker":a.proxy(this.highlightUnit,this),"keydown.timepicker":a.proxy(this.elementKeydown,this),"blur.timepicker":a.proxy(this.blurElement,this),"mousewheel.timepicker DOMMouseScroll.timepicker":a.proxy(this.mousewheel,this)})):this.template?this.$element.on({"focus.timepicker":a.proxy(this.showWidget,this),"click.timepicker":a.proxy(this.showWidget,this),"blur.timepicker":a.proxy(this.blurElement,this),"mousewheel.timepicker DOMMouseScroll.timepicker":a.proxy(this.mousewheel,this)}):this.$element.on({"focus.timepicker":a.proxy(this.highlightUnit,this),"click.timepicker":a.proxy(this.highlightUnit,this),"keydown.timepicker":a.proxy(this.elementKeydown,this),"blur.timepicker":a.proxy(this.blurElement,this),"mousewheel.timepicker DOMMouseScroll.timepicker":a.proxy(this.mousewheel,this)}),this.template!==!1?this.$widget=a(this.getTemplate()).on("click",a.proxy(this.widgetClick,this)):this.$widget=!1,this.showInputs&&this.$widget!==!1&&this.$widget.find("input").each(function(){a(this).on({"click.timepicker":function(){a(this).select()},"keydown.timepicker":a.proxy(b.widgetKeydown,b),"keyup.timepicker":a.proxy(b.widgetKeyup,b)})}),this.setDefaultTime(this.defaultTime)},blurElement:function(){this.highlightedUnit=null,this.updateFromElementVal()},clear:function(){this.hour="",this.minute="",this.second="",this.meridian="",this.$element.val("")},decrementHour:function(){if(this.showMeridian)if(1===this.hour)this.hour=12;else{if(12===this.hour)return this.hour--,this.toggleMeridian();if(0===this.hour)return this.hour=11,this.toggleMeridian();this.hour--}else this.hour<=0?this.hour=this.maxHours-1:this.hour--},decrementMinute:function(a){var b;b=a?this.minute-a:this.minute-this.minuteStep,0>b?(this.decrementHour(),this.minute=b+60):this.minute=b},decrementSecond:function(){var a=this.second-this.secondStep;0>a?(this.decrementMinute(!0),this.second=a+60):this.second=a},elementKeydown:function(a){switch(a.which){case 9:if(a.shiftKey){if("hour"===this.highlightedUnit){this.hideWidget();break}this.highlightPrevUnit()}else{if(this.showMeridian&&"meridian"===this.highlightedUnit||this.showSeconds&&"second"===this.highlightedUnit||!this.showMeridian&&!this.showSeconds&&"minute"===this.highlightedUnit){this.hideWidget();break}this.highlightNextUnit()}a.preventDefault(),this.updateFromElementVal();break;case 27:this.updateFromElementVal();break;case 37:a.preventDefault(),this.highlightPrevUnit(),this.updateFromElementVal();break;case 38:switch(a.preventDefault(),this.highlightedUnit){case"hour":this.incrementHour(),this.highlightHour();break;case"minute":this.incrementMinute(),this.highlightMinute();break;case"second":this.incrementSecond(),this.highlightSecond();break;case"meridian":this.toggleMeridian(),this.highlightMeridian()}this.update();break;case 39:a.preventDefault(),this.highlightNextUnit(),this.updateFromElementVal();break;case 40:switch(a.preventDefault(),this.highlightedUnit){case"hour":this.decrementHour(),this.highlightHour();break;case"minute":this.decrementMinute(),this.highlightMinute();break;case"second":this.decrementSecond(),this.highlightSecond();break;case"meridian":this.toggleMeridian(),this.highlightMeridian()}this.update()}},getCursorPosition:function(){var a=this.$element.get(0);if("selectionStart"in a)return a.selectionStart;if(c.selection){a.focus();var b=c.selection.createRange(),d=c.selection.createRange().text.length;return b.moveStart("character",-a.value.length),b.text.length-d}},getTemplate:function(){var a,b,c,d,e,f;switch(this.showInputs?(b='',c='',d='',e=''):(b='',c='',d='',e=''),f=''+(this.showSeconds?'':"")+(this.showMeridian?'':"")+" "+(this.showSeconds?'":"")+(this.showMeridian?'":"")+''+(this.showSeconds?'':"")+(this.showMeridian?'':"")+"
           
        "+b+' :'+c+":'+d+" '+e+"
          
        ",this.template){case"modal":a='';break;case"dropdown":a='"}return a},getTime:function(){return""===this.hour?"":this.hour+":"+(1===this.minute.toString().length?"0"+this.minute:this.minute)+(this.showSeconds?":"+(1===this.second.toString().length?"0"+this.second:this.second):"")+(this.showMeridian?" "+this.meridian:"")},hideWidget:function(){this.isOpen!==!1&&(this.$element.trigger({type:"hide.timepicker",time:{value:this.getTime(),hours:this.hour,minutes:this.minute,seconds:this.second,meridian:this.meridian}}),"modal"===this.template&&this.$widget.modal?this.$widget.modal("hide"):this.$widget.removeClass("open"),a(c).off("mousedown.timepicker, touchend.timepicker",this.handleDocumentClick),this.isOpen=!1,this.$widget.detach())},highlightUnit:function(){this.position=this.getCursorPosition(),this.position>=0&&this.position<=2?this.highlightHour():this.position>=3&&this.position<=5?this.highlightMinute():this.position>=6&&this.position<=8?this.showSeconds?this.highlightSecond():this.highlightMeridian():this.position>=9&&this.position<=11&&this.highlightMeridian()},highlightNextUnit:function(){switch(this.highlightedUnit){case"hour":this.highlightMinute();break;case"minute":this.showSeconds?this.highlightSecond():this.showMeridian?this.highlightMeridian():this.highlightHour();break;case"second":this.showMeridian?this.highlightMeridian():this.highlightHour();break;case"meridian":this.highlightHour()}},highlightPrevUnit:function(){switch(this.highlightedUnit){case"hour":this.showMeridian?this.highlightMeridian():this.showSeconds?this.highlightSecond():this.highlightMinute();break;case"minute":this.highlightHour();break;case"second":this.highlightMinute();break;case"meridian":this.showSeconds?this.highlightSecond():this.highlightMinute()}},highlightHour:function(){var a=this.$element.get(0),b=this;this.highlightedUnit="hour",a.setSelectionRange&&setTimeout(function(){b.hour<10?a.setSelectionRange(0,1):a.setSelectionRange(0,2)},0)},highlightMinute:function(){var a=this.$element.get(0),b=this;this.highlightedUnit="minute",a.setSelectionRange&&setTimeout(function(){b.hour<10?a.setSelectionRange(2,4):a.setSelectionRange(3,5)},0)},highlightSecond:function(){var a=this.$element.get(0),b=this;this.highlightedUnit="second",a.setSelectionRange&&setTimeout(function(){b.hour<10?a.setSelectionRange(5,7):a.setSelectionRange(6,8)},0)},highlightMeridian:function(){var a=this.$element.get(0),b=this;this.highlightedUnit="meridian",a.setSelectionRange&&(this.showSeconds?setTimeout(function(){b.hour<10?a.setSelectionRange(8,10):a.setSelectionRange(9,11)},0):setTimeout(function(){b.hour<10?a.setSelectionRange(5,7):a.setSelectionRange(6,8)},0))},incrementHour:function(){if(this.showMeridian){if(11===this.hour)return this.hour++,this.toggleMeridian();12===this.hour&&(this.hour=0)}return this.hour===this.maxHours-1?void(this.hour=0):void this.hour++},incrementMinute:function(a){var b;b=a?this.minute+a:this.minute+this.minuteStep-this.minute%this.minuteStep,b>59?(this.incrementHour(),this.minute=b-60):this.minute=b},incrementSecond:function(){var a=this.second+this.secondStep-this.second%this.secondStep;a>59?(this.incrementMinute(!0),this.second=a-60):this.second=a},mousewheel:function(b){if(!this.disableMousewheel){b.preventDefault(),b.stopPropagation();var c=b.originalEvent.wheelDelta||-b.originalEvent.detail,d=null;switch("mousewheel"===b.type?d=-1*b.originalEvent.wheelDelta:"DOMMouseScroll"===b.type&&(d=40*b.originalEvent.detail),d&&(b.preventDefault(),a(this).scrollTop(d+a(this).scrollTop())),this.highlightedUnit){case"minute":c>0?this.incrementMinute():this.decrementMinute(),this.highlightMinute();break;case"second":c>0?this.incrementSecond():this.decrementSecond(),this.highlightSecond();break;case"meridian":this.toggleMeridian(),this.highlightMeridian();break;default:c>0?this.incrementHour():this.decrementHour(),this.highlightHour()}return!1}},changeToNearestStep:function(a,b){return a%b===0?a:Math.round(a%b/b)?(a+(b-a%b))%60:a-a%b},place:function(){if(!this.isInline){var c=this.$widget.outerWidth(),d=this.$widget.outerHeight(),e=10,f=a(b).width(),g=a(b).height(),h=a(b).scrollTop(),i=parseInt(this.$element.parents().filter(function(){return"auto"!==a(this).css("z-index")}).first().css("z-index"),10)+10,j=this.component?this.component.parent().offset():this.$element.offset(),k=this.component?this.component.outerHeight(!0):this.$element.outerHeight(!1),l=this.component?this.component.outerWidth(!0):this.$element.outerWidth(!1),m=j.left,n=j.top;this.$widget.removeClass("timepicker-orient-top timepicker-orient-bottom timepicker-orient-right timepicker-orient-left"),"auto"!==this.orientation.x?(this.$widget.addClass("timepicker-orient-"+this.orientation.x),"right"===this.orientation.x&&(m-=c-l)):(this.$widget.addClass("timepicker-orient-left"),j.left<0?m-=j.left-e:j.left+c>f&&(m=f-c-e));var o,p,q=this.orientation.y;"auto"===q&&(o=-h+j.top-d,p=h+g-(j.top+k+d),q=Math.max(o,p)===p?"top":"bottom"),this.$widget.addClass("timepicker-orient-"+q),"top"===q?n+=k:n-=d+parseInt(this.$widget.css("padding-top"),10),this.$widget.css({top:n,left:m,zIndex:i})}},remove:function(){a("document").off(".timepicker"),this.$widget&&this.$widget.remove(),delete this.$element.data().timepicker},setDefaultTime:function(a){if(this.$element.val())this.updateFromElementVal();else if("current"===a){var b=new Date,c=b.getHours(),d=b.getMinutes(),e=b.getSeconds(),f="AM";0!==e&&(e=Math.ceil(b.getSeconds()/this.secondStep)*this.secondStep,60===e&&(d+=1,e=0)),0!==d&&(d=Math.ceil(b.getMinutes()/this.minuteStep)*this.minuteStep,60===d&&(c+=1,d=0)),this.showMeridian&&(0===c?c=12:c>=12?(c>12&&(c-=12),f="PM"):f="AM"),this.hour=c,this.minute=d,this.second=e,this.meridian=f,this.update()}else a===!1?(this.hour=0,this.minute=0,this.second=0,this.meridian="AM"):this.setTime(a)},setTime:function(a,b){if(!a)return void this.clear();var c,d,e,f,g,h;if("object"==typeof a&&a.getMonth)e=a.getHours(),f=a.getMinutes(),g=a.getSeconds(),this.showMeridian&&(h="AM",e>12&&(h="PM",e%=12),12===e&&(h="PM"));else{if(c=(/a/i.test(a)?1:0)+(/p/i.test(a)?2:0),c>2)return void this.clear();if(d=a.replace(/[^0-9\:]/g,"").split(":"),e=d[0]?d[0].toString():d.toString(),this.explicitMode&&e.length>2&&e.length%2!==0)return void this.clear();f=d[1]?d[1].toString():"",g=d[2]?d[2].toString():"",e.length>4&&(g=e.slice(-2),e=e.slice(0,-2)),e.length>2&&(f=e.slice(-2),e=e.slice(0,-2)),f.length>2&&(g=f.slice(-2),f=f.slice(0,-2)),e=parseInt(e,10),f=parseInt(f,10),g=parseInt(g,10),isNaN(e)&&(e=0),isNaN(f)&&(f=0),isNaN(g)&&(g=0),g>59&&(g=59),f>59&&(f=59),e>=this.maxHours&&(e=this.maxHours-1),this.showMeridian?(e>12&&(c=2,e-=12),c||(c=1),0===e&&(e=12),h=1===c?"AM":"PM"):12>e&&2===c?e+=12:e>=this.maxHours?e=this.maxHours-1:(0>e||12===e&&1===c)&&(e=0)}this.hour=e,this.snapToStep?(this.minute=this.changeToNearestStep(f,this.minuteStep),this.second=this.changeToNearestStep(g,this.secondStep)):(this.minute=f,this.second=g),this.meridian=h,this.update(b)},showWidget:function(){this.isOpen||this.$element.is(":disabled")||(this.$widget.appendTo(this.appendWidgetTo),a(c).on("mousedown.timepicker, touchend.timepicker",{scope:this},this.handleDocumentClick),this.$element.trigger({type:"show.timepicker",time:{value:this.getTime(),hours:this.hour,minutes:this.minute,seconds:this.second,meridian:this.meridian}}),this.place(),this.disableFocus&&this.$element.blur(),""===this.hour&&(this.defaultTime?this.setDefaultTime(this.defaultTime):this.setTime("0:0:0")),"modal"===this.template&&this.$widget.modal?this.$widget.modal("show").on("hidden",a.proxy(this.hideWidget,this)):this.isOpen===!1&&this.$widget.addClass("open"),this.isOpen=!0)},toggleMeridian:function(){this.meridian="AM"===this.meridian?"PM":"AM"},update:function(a){this.updateElement(),a||this.updateWidget(),this.$element.trigger({type:"changeTime.timepicker",time:{value:this.getTime(),hours:this.hour,minutes:this.minute,seconds:this.second,meridian:this.meridian}})},updateElement:function(){this.$element.val(this.getTime()).change()},updateFromElementVal:function(){this.setTime(this.$element.val())},updateWidget:function(){if(this.$widget!==!1){var a=this.hour,b=1===this.minute.toString().length?"0"+this.minute:this.minute,c=1===this.second.toString().length?"0"+this.second:this.second;this.showInputs?(this.$widget.find("input.bootstrap-timepicker-hour").val(a),this.$widget.find("input.bootstrap-timepicker-minute").val(b),this.showSeconds&&this.$widget.find("input.bootstrap-timepicker-second").val(c),this.showMeridian&&this.$widget.find("input.bootstrap-timepicker-meridian").val(this.meridian)):(this.$widget.find("span.bootstrap-timepicker-hour").text(a),this.$widget.find("span.bootstrap-timepicker-minute").text(b),this.showSeconds&&this.$widget.find("span.bootstrap-timepicker-second").text(c),this.showMeridian&&this.$widget.find("span.bootstrap-timepicker-meridian").text(this.meridian))}},updateFromWidgetInputs:function(){if(this.$widget!==!1){var a=this.$widget.find("input.bootstrap-timepicker-hour").val()+":"+this.$widget.find("input.bootstrap-timepicker-minute").val()+(this.showSeconds?":"+this.$widget.find("input.bootstrap-timepicker-second").val():"")+(this.showMeridian?this.$widget.find("input.bootstrap-timepicker-meridian").val():"");this.setTime(a,!0)}},widgetClick:function(b){b.stopPropagation(),b.preventDefault();var c=a(b.target),d=c.closest("a").data("action");d&&this[d](),this.update(),c.is("input")&&c.get(0).setSelectionRange(0,2)},widgetKeydown:function(b){var c=a(b.target),d=c.attr("class").replace("bootstrap-timepicker-","");switch(b.which){case 9:if(b.shiftKey){if("hour"===d)return this.hideWidget()}else if(this.showMeridian&&"meridian"===d||this.showSeconds&&"second"===d||!this.showMeridian&&!this.showSeconds&&"minute"===d)return this.hideWidget();break;case 27:this.hideWidget();break;case 38:switch(b.preventDefault(),d){case"hour":this.incrementHour();break;case"minute":this.incrementMinute();break;case"second":this.incrementSecond();break;case"meridian":this.toggleMeridian()}this.setTime(this.getTime()),c.get(0).setSelectionRange(0,2);break;case 40:switch(b.preventDefault(),d){case"hour":this.decrementHour();break;case"minute":this.decrementMinute();break;case"second":this.decrementSecond();break;case"meridian":this.toggleMeridian()}this.setTime(this.getTime()),c.get(0).setSelectionRange(0,2)}},widgetKeyup:function(a){(65===a.which||77===a.which||80===a.which||46===a.which||8===a.which||a.which>=48&&a.which<=57||a.which>=96&&a.which<=105)&&this.updateFromWidgetInputs()}},a.fn.timepicker=function(b){var c=Array.apply(null,arguments);return c.shift(),this.each(function(){var e=a(this),f=e.data("timepicker"),g="object"==typeof b&&b;f||e.data("timepicker",f=new d(this,a.extend({},a.fn.timepicker.defaults,g,a(this).data()))),"string"==typeof b&&f[b].apply(f,c)})},a.fn.timepicker.defaults={defaultTime:"current",disableFocus:!1,disableMousewheel:!1,isOpen:!1,minuteStep:15,modalBackdrop:!1,orientation:{x:"auto",y:"auto"},secondStep:15,snapToStep:!1,showSeconds:!1,showInputs:!0,showMeridian:!0,template:"dropdown",appendWidgetTo:"body",showWidgetOnAddonClick:!0,icons:{up:"glyphicon glyphicon-chevron-up",down:"glyphicon glyphicon-chevron-down"},maxHours:24,explicitMode:!1},a.fn.timepicker.Constructor=d,a(c).on("focus.timepicker.data-api click.timepicker.data-api",'[data-provide="timepicker"]',function(b){var c=a(this);c.data("timepicker")||(b.preventDefault(),c.timepicker())})}(jQuery,window,document); ================================================ FILE: src/libs/bootstrap-timepicker/package.json ================================================ { "name": "bootstrap-timepicker", "description": "Timepicker widget for Twitter Bootstrap", "version": "0.5.2", "license": "MIT", "homepage": "http://jdewit.github.com/bootstrap-timepicker", "author": { "name": "Joris de Wit", "email": "joris.w.dewit@gmail.com", "url": "http://jorisdewit.ca" }, "main": "js/bootstrap-timepicker.js", "files": ["js", "css"], "repository": { "type": "git", "url": "git://github.com/jdewit/bootstrap-timepicker" }, "scripts": { "test": "bower install; grunt test;" }, "bugs": { "url": "https://github.com/jdewit/bootstrap-timepicker/issues" }, "devDependencies": { "grunt-cli": "~0.1", "grunt": "~0.4.1", "bower": "latest", "grunt-contrib-less": "~0.5.0", "grunt-contrib-jshint": "~0.4.3", "grunt-contrib-uglify": "~0.2.0", "grunt-contrib-jasmine": "~0.4.2", "grunt-contrib-watch": "~0.4.3", "grunt-shell": "~0.2.2", "grunt-bump": "0.0.11" } } ================================================ FILE: src/libs/contextmenu/bootstrap-contextmenu.js ================================================ /*! * Bootstrap Context Menu * Version: 0.2.0 * Author: @sydcanem * https://github.com/sydcanem/bootstrap-contextmenu * * Inspired by Twitter Bootstrap's dropdown plugin. * Twitter Bootstrap (http://twitter.github.com/bootstrap). * * Licensed under MIT * ========================================================= */ ; (function ($) { 'use strict'; /* CONTEXTMENU CLASS DEFINITION * ============================ */ var toggle = '[data-toggle="context"]'; var ContextMenu = function (element, options) { this.$element = $(element); this.before = options.before || this.before; this.onItem = options.onItem || this.onItem; this.scopes = options.scopes || null; if (options.target) { this.$element.data('target', options.target); } this.listen(); }; ContextMenu.prototype = { constructor: ContextMenu, show: function (e) { var $menu , evt , tp , items , relatedTarget = { relatedTarget: this }; if (this.isDisabled()) return; this.closemenu(); if (!this.before.call(this, e, $(e.currentTarget))) return; $menu = this.getMenu(); $menu.trigger(evt = $.Event('show.bs.context', relatedTarget)); tp = this.getPosition(e, $menu); items = 'li:not(.divider)'; $menu.attr('style', '') .css(tp) .addClass('open') .on('click.context.data-api', items, $.proxy(this.onItem, this, $(e.currentTarget))) .trigger('shown.bs.context', relatedTarget); // Delegating the `closemenu` only on the currently opened menu. // This prevents other opened menus from closing. $('html') .on('click.context.data-api', $menu.selector, $.proxy(this.closemenu, this)); return false; }, closemenu: function (e) { var $menu , evt , items , relatedTarget; $menu = this.getMenu(); if (!$menu.hasClass('open')) return; relatedTarget = { relatedTarget: this }; $menu.trigger(evt = $.Event('hide.bs.context', relatedTarget)); items = 'li:not(.divider)'; $menu.removeClass('open') .off('click.context.data-api', items) .trigger('hidden.bs.context', relatedTarget); $('html') .off('click.context.data-api', $menu.selector); // Don't propagate click event so other currently // opened menus won't close. return false; }, before: function (e) { return true; }, onItem: function (e) { return true; }, listen: function () { this.$element.on('contextmenu.context.data-api', this.scopes, $.proxy(this.show, this)); $('html').on('click.context.data-api', $.proxy(this.closemenu, this)); }, destroy: function () { this.$element.off('.context.data-api').removeData('context'); $('html').off('.context.data-api'); }, isDisabled: function () { return this.$element.hasClass('.disabled') || this.$element.attr('disabled'); }, getMenu: function () { var selector = this.$element.data('target') , $menu; if (!selector) { selector = this.$element.attr('href'); selector = selector && selector.replace(/.*(?=#[^\s]*$)/, ''); //strip for ie7 } $menu = $(selector); return $menu && $menu.length ? $menu : this.$element.find(selector); }, getPosition: function (e, $menu) { var mouseX = e.clientX , mouseY = e.clientY , boundsX = $(window).width() , boundsY = $(window).height() , menuWidth = $menu.find('.dropdown-menu').outerWidth() , menuHeight = $menu.find('.dropdown-menu').outerHeight() , tp = {"position": "absolute", "z-index": 9999} , Y, X, parentOffset; if (mouseY + menuHeight > boundsY) { Y = {"top": mouseY - menuHeight + $(window).scrollTop()}; } else { Y = {"top": mouseY + $(window).scrollTop()}; } if ((mouseX + menuWidth > boundsX) && ((mouseX - menuWidth) > 0)) { X = {"left": mouseX - menuWidth + $(window).scrollLeft()}; } else { X = {"left": mouseX + $(window).scrollLeft()}; } // If context-menu's parent is positioned using absolute or relative positioning, // the calculated mouse position will be incorrect. // Adjust the position of the menu by its offset parent position. parentOffset = $menu.offsetParent().offset(); X.left = X.left - parentOffset.left; Y.top = Y.top - parentOffset.top; return $.extend(tp, Y, X); } }; /* CONTEXT MENU PLUGIN DEFINITION * ========================== */ $.fn.contextmenu = function (option, e) { return this.each(function () { var $this = $(this) , data = $this.data('context') , options = (typeof option == 'object') && option; if (!data) $this.data('context', (data = new ContextMenu($this, options))); if (typeof option == 'string') data[option].call(data, e); }); }; $.fn.contextmenu.Constructor = ContextMenu; /* APPLY TO STANDARD CONTEXT MENU ELEMENTS * =================================== */ $(document) .on('contextmenu.context.data-api', toggle, function (e) { $(this).contextmenu('show', e); e.preventDefault(); }); }(jQuery)); ================================================ FILE: src/libs/enjoyhint/enjoyhint.js ================================================ var EnjoyHint = function (_options) { var that = this; // Some options var defaults = { onStart: function () { }, onEnd: function () { } }; var options = $.extend(defaults, _options); var data = []; var current_step = 0; $body = $('body'); /********************* PRIVAT METHODS ***************************************/ var init = function () { if ($('.enjoyhint')) $('.enjoyhint').remove(); $('body').css({'overflow':'hidden'}); $(document).on("touchmove",lockTouch); $body.enjoyhint({ onNextClick: function () { current_step++; stepAction(); }, onSkipClick: function () { var step_data = data[current_step]; var $element = $(step_data.selector); off(step_data.event); $element.off(makeEventName(step_data.event)); destroyEnjoy(); } }); }; var lockTouch = function(e) { e.preventDefault(); }; var destroyEnjoy = function () { $body = $('body'); $('.enjoyhint').remove(); $("body").css({'overflow':'auto'}); $(document).off("touchmove", lockTouch); }; that.clear = function(){ //(Remove userClass and set default text) $(".enjoyhint_next_btn").removeClass(that.nextUserClass); $(".enjoyhint_next_btn").text("Next"); $(".enjoyhint_skip_btn").removeClass(that.skipUserClass); $(".enjoyhint_skip_btn").text("Skip"); } var $body = $('body'); var stepAction = function () { if (data && data[current_step]) { $(".enjoyhint").removeClass("enjoyhint-step-"+current_step); $(".enjoyhint").addClass("enjoyhint-step-"+(current_step+1)); var step_data = data[current_step]; if (step_data.onBeforeStart && typeof step_data.onBeforeStart === 'function') { step_data.onBeforeStart(); } var timeout = step_data.timeout || 0; setTimeout(function () { if (!step_data.selector) { for (var prop in step_data) { if (step_data.hasOwnProperty(prop) && prop.split(" ")[1]) { step_data.selector = prop.split(" ")[1]; step_data.event = prop.split(" ")[0]; if (prop.split(" ")[0] == 'next' || prop.split(" ")[0] == 'auto' || prop.split(" ")[0] == 'custom') { step_data.event_type = prop.split(" ")[0]; } step_data.description = step_data[prop]; } } } setTimeout(function(){ that.clear(); }, 250); $(document.body).scrollTo(step_data.selector, step_data.scrollAnimationSpeed || 250, {offset: -100}); setTimeout(function () { var $element = $(step_data.selector); var event = makeEventName(step_data.event); $body.enjoyhint('show'); $body.enjoyhint('hide_next'); var $event_element = $element; if (step_data.event_selector) { $event_element = $(step_data.event_selector); } if (!step_data.event_type && step_data.event == "key"){ $element.keydown(function( event ) { if ( event.which == step_data.keyCode ) { current_step++; stepAction(); } }); } if (step_data.showNext == true){ $body.enjoyhint('show_next'); } if (step_data.showSkip == false){ $body.enjoyhint('hide_skip'); }else{ $body.enjoyhint('show_skip'); } if (step_data.showSkip == true){ } if (step_data.nextButton){ $(".enjoyhint_next_btn").addClass(step_data.nextButton.className || ""); $(".enjoyhint_next_btn").text(step_data.nextButton.text || "Next"); that.nextUserClass = step_data.nextButton.className } if (step_data.skipButton){ $(".enjoyhint_skip_btn").addClass(step_data.skipButton.className || ""); $(".enjoyhint_skip_btn").text(step_data.skipButton.text || "Skip"); that.skipUserClass = step_data.skipButton.className } if (step_data.event_type) { switch (step_data.event_type) { case 'auto': $element[step_data.event](); switch (step_data.event) { case 'click': break; } current_step++; stepAction(); return; break; case 'custom': on(step_data.event, function () { current_step++; off(step_data.event); stepAction(); }); break; case 'next': $body.enjoyhint('show_next'); break; } } else { $event_element.on(event, function (e) { if (step_data.keyCode && e.keyCode != step_data.keyCode) { return; } current_step++; $(this).off(event); stepAction(); }); } var max_habarites = Math.max($element.outerWidth(), $element.outerHeight()); var radius = step_data.radius || Math.round(max_habarites / 2) + 5; var offset = $element.offset(); var w = $element.outerWidth(); var h = $element.outerHeight(); var shape_margin = (step_data.margin !== undefined) ? step_data.margin : 10; var coords = { x: offset.left + Math.round(w / 2) , y: offset.top + Math.round(h / 2) - $(document).scrollTop() }; var shape_data = { center_x: coords.x, center_y: coords.y, text: step_data.description, top: step_data.top, bottom: step_data.bottom, left: step_data.left, right: step_data.right, margin: step_data.margin, scroll: step_data.scroll }; if (step_data.shape && step_data.shape == 'circle') { shape_data.shape = 'circle'; shape_data.radius = radius; } else { shape_data.radius = 0; shape_data.width = w + shape_margin; shape_data.height = h + shape_margin; } $body.enjoyhint('render_label_with_shape', shape_data); }, step_data.scrollAnimationSpeed + 20 || 270); }, timeout); } else { $body.enjoyhint('hide'); options.onEnd(); destroyEnjoy(); } }; var makeEventName = function (name, is_custom) { return name + (is_custom ? 'custom' : '') + '.enjoy_hint'; }; var on = function (event_name, callback) { $body.on(makeEventName(event_name, true), callback); }; var off = function (event_name) { $body.off(makeEventName(event_name, true)); }; /********************* PUBLIC METHODS ***************************************/ that.runScript = function () { current_step = 0; options.onStart(); stepAction(); }; this.end = function(){ destroyEnjoy(); } that.resumeScript = function () { stepAction(); }; that.getCurrentStep = function () { return current_step; }; that.trigger = function (event_name) { $body.trigger(makeEventName(event_name, true)); }; that.setScript = function (_data) { if (_data) { data = _data; } }; //support deprecated API methods that.set = function (_data) { that.setScript(_data); }; that.setSteps = function (_data) { that.setScript(_data); }; that.run = function () { that.runScript(); }; that.resume = function () { that.resumeScript(); }; init(); }; ;CanvasRenderingContext2D.prototype.roundRect = function (x, y, w, h, r) { if (w < 2 * r) r = w / 2; if (h < 2 * r) r = h / 2; this.beginPath(); this.moveTo(x + r, y); this.arcTo(x + w, y, x + w, y + h, r); this.arcTo(x + w, y + h, x, y + h, r); this.arcTo(x, y + h, x, y, r); this.arcTo(x, y, x + w, y, r); this.closePath(); return this; }; (function ($) { var methods = { init: function (options) { console.log(options,'-------------'); return this.each(function () { var defaults = { onNextClick: function () { }, onSkipClick: function () { }, animation_time: 800 }; this.enjoyhint_obj = {}; var that = this.enjoyhint_obj; var $that = $(this); var $body = $('body'); that.options = jQuery.extend(defaults, options); //general classes that.gcl = { chooser: 'enjoyhint' }; // classes that.cl = { enjoy_hint: 'enjoyhint', hide: 'enjoyhint_hide', disable_events_element: 'enjoyhint_disable_events', btn: 'enjoyhint_btn', skip_btn: 'enjoyhint_skip_btn', close_btn: 'enjoyhint_close_btn', next_btn: 'enjoyhint_next_btn', main_canvas: 'enjoyhint_canvas', main_svg: 'enjoyhint_svg', svg_wrapper: 'enjoyhint_svg_wrapper', svg_transparent: 'enjoyhint_svg_transparent', kinetic_container: 'kinetic_container' }; function makeSVG(tag, attrs) { var el = document.createElementNS('http://www.w3.org/2000/svg', tag); for (var k in attrs) el.setAttribute(k, attrs[k]); return el; } // ======================================================================= // ========================---- enjoyhint ----============================== // ======================================================================= that.canvas_size = { w: $(window).width()*1.4, h: $(window).height()*1.4 }; var canvas_id = "enj_canvas"; that.enjoyhint = $('
        ', {'class': that.cl.enjoy_hint + ' ' + that.cl.svg_transparent}).appendTo($that); that.enjoyhint_svg_wrapper = $('
        ', {'class': that.cl.svg_wrapper + ' ' + that.cl.svg_transparent}).appendTo(that.enjoyhint); that.$stage_container = $('
        ').appendTo(that.enjoyhint); that.$canvas = $('').appendTo(that.enjoyhint); that.$svg = $('').appendTo(that.enjoyhint_svg_wrapper); var defs = $(makeSVG('defs')); var marker = $(makeSVG('marker', {id: "arrowMarker", viewBox: "0 0 36 21", refX: "21", refY: "10", markerUnits: "strokeWidth", orient: "auto", markerWidth: "16", markerHeight: "12"})); var polilyne = $(makeSVG('path', {style: "fill:none; stroke:rgb(255,255,255); stroke-width:2", d: "M0,0 c30,11 30,9 0,20"})); defs.append(marker.append(polilyne)).appendTo(that.$svg); that.kinetic_stage = new Kinetic.Stage({ container: that.cl.kinetic_container, width: that.canvas_size.w, height: that.canvas_size.h }); console.log(that.enjoyhint); that.layer = new Kinetic.Layer(); that.rect = new Kinetic.Rect({ // x: 0, // y: 0, fill: 'rgba(0,0,0,0.6)', width: that.canvas_size.w, height: that.canvas_size.h }); var $top_dis_events = $('
        ', {'class': that.cl.disable_events_element}).appendTo(that.enjoyhint); var $bottom_dis_events = $top_dis_events.clone().appendTo(that.enjoyhint); var $left_dis_events = $top_dis_events.clone().appendTo(that.enjoyhint); var $right_dis_events = $top_dis_events.clone().appendTo(that.enjoyhint); that.$skip_btn = $('
        ', {'class': that.cl.skip_btn}).appendTo(that.enjoyhint).html('Skip').click(function (e) { that.hide(); that.options.onSkipClick(); }); that.$next_btn = $('
        ', {'class': that.cl.next_btn}).appendTo(that.enjoyhint).html('Next').click(function (e) { that.options.onNextClick(); }); that.$close_btn = $('
        ', {'class': that.cl.close_btn}).appendTo(that.enjoyhint).html('').click(function (e){ that.hide(); that.options.onSkipClick(); }); that.$canvas.mousedown(function (e) { console.log('cl') $('canvas').css({left: '4000px'}); var BottomElement = document.elementFromPoint(e.clientX, e.clientY); console.log(BottomElement.tagName) $('canvas').css({left: '0px'}); $(BottomElement).click(); // that.$canvas.show(); return false; }); var circle_r = 0; var shape_init_shift = 130; that.shape = new Kinetic.Shape({ radius: circle_r, center_x: -shape_init_shift, center_y: -shape_init_shift, width: 0, height: 0, sceneFunc: function (context) { var ctx = this.getContext("2d")._context; var pos = this.pos; var def_comp = ctx.globalCompositeOperation; ctx.globalCompositeOperation = 'destination-out'; ctx.beginPath(); var x = this.attrs.center_x - Math.round(this.attrs.width / 2); var y = this.attrs.center_y - Math.round(this.attrs.height / 2); ctx.roundRect(x, y, this.attrs.width, this.attrs.height, this.attrs.radius); ctx.fillStyle = "red"; ctx.fill(); ctx.globalCompositeOperation = def_comp; } }); that.shape.radius = circle_r; that.layer.add(that.rect); that.layer.add(that.shape); that.kinetic_stage.add(that.layer); var enjoyhint_elements = [ that.enjoyhint, $top_dis_events, $bottom_dis_events, $left_dis_events, $right_dis_events ]; that.show = function () { that.enjoyhint.removeClass(that.cl.hide); }; that.hide = function () { that.enjoyhint.addClass(that.cl.hide); var tween = new Kinetic.Tween({ node: that.shape, duration: 0.002, center_x: -shape_init_shift, center_y: -shape_init_shift }); tween.play(); }; that.hide(); that.hideNextBtn = function () { that.$next_btn.addClass(that.cl.hide); that.nextBtn = "hide"; }; that.showNextBtn = function () { that.$next_btn.removeClass(that.cl.hide); that.nextBtn = "show"; }; that.hideSkipBtn = function () { that.$skip_btn.addClass(that.cl.hide); }; that.showSkipBtn = function () { that.$skip_btn.removeClass(that.cl.hide); }; that.renderCircle = function (data) { var r = data.r || 0; var x = data.x || 0; var y = data.y || 0; var tween = new Kinetic.Tween({ node: that.shape, duration: 0.2, center_x: x, center_y: y, width: r * 2, height: r * 2, radius: r }); tween.play(); var left = x - r; var right = x + r; var top = y - r; var bottom = y + r; var margin = 20; return { x: x, y: y, left: left, right: right, top: top, bottom: bottom, conn: { left: { x: left - margin, y: y }, right: { x: right + margin, y: y }, top: { x: x, y: top - margin }, bottom: { x: x, y: bottom + margin } } }; }; that.renderRect = function (data) { var r = data.r || 0; var x = data.x || 0; var y = data.y || 0; var w = data.w || 0; var h = data.h || 0; var margin = 20; var tween = new Kinetic.Tween({ node: that.shape, duration: 0.2, center_x: x, center_y: y, width: w, height: h, radius: r }); tween.play(); var half_w = Math.round(w / 2); var half_h = Math.round(h / 2); var left = x - half_w; var right = x + half_w; var top = y - half_h; var bottom = y + half_h; return { x: x, y: y, left: left, right: right, top: top, bottom: bottom, conn: { left: { x: left - margin, y: y }, right: { x: right + margin, y: y }, top: { x: x, y: top - margin }, bottom: { x: x, y: bottom + margin } } }; }; that.renderLabel = function (data) { var x = data.x || 0; var y = data.y || 0; var text = data.text || 0; var label = that.getLabelElement({ x: x, y: y, text: data.text }); var label_w = label.width(); var label_h = label.height(); var label_left = label.offset().left; var label_right = label.offset().left + label_w; var label_top = label.offset().top - $(document).scrollTop();; var label_bottom = label.offset().top + label_h; var margin = 10; var conn_left = { x: label_left - margin, y: label_top + Math.round(label_h / 2) }; var conn_right = { x: label_right + margin, y: label_top + Math.round(label_h / 2) }; var conn_top = { x: label_left + Math.round(label_w / 2), y: label_top - margin }; var conn_bottom = { x: label_left + Math.round(label_w / 2), y: label_bottom + margin }; label.detach(); setTimeout(function () { $('#enjoyhint_label').remove(); label.appendTo(that.enjoyhint); }, that.options.animation_time / 2); return { label: label, left: label_left, right: label_right, top: label_top, bottom: label_bottom, conn: { left: conn_left, right: conn_right, top: conn_top, bottom: conn_bottom } }; }; that.renderArrow = function (data) { var x_from = data.x_from || 0; var y_from = data.y_from || 0; var x_to = data.x_to || 0; var y_to = data.y_to || 0; var by_top_side = data.by_top_side; var control_point_x = 0; var control_point_y = 0; if (by_top_side) { if (y_from >= y_to) { control_point_y = y_to; control_point_x = x_from; } else { control_point_y = y_from; control_point_x = x_to; } } else { if (y_from >= y_to) { control_point_y = y_from; control_point_x = x_to; } else { control_point_y = y_to; control_point_x = x_from; } } var text = data.text || ''; that.enjoyhint.addClass(that.cl.svg_transparent); setTimeout(function () { $('#enjoyhint_arrpw_line').remove(); var d = 'M' + x_from + ',' + y_from + ' Q' + control_point_x + ',' + control_point_y + ' ' + x_to + ',' + y_to; that.$svg.append(makeSVG('path', {style: "fill:none; stroke:rgb(255,255,255); stroke-width:3", 'marker-end': "url(#arrowMarker)", d: d, id: 'enjoyhint_arrpw_line'})); that.enjoyhint.removeClass(that.cl.svg_transparent); }, that.options.animation_time / 2); }; that.getLabelElement = function (data) { return $('
        ', {"class": 'enjoy_hint_label', id: 'enjoyhint_label'}) .css({ 'top': data.y + 'px', 'left': data.x + 'px' }) .html(data.text).appendTo(that.enjoyhint); }; that.disableEventsNearRect = function (rect) { $top_dis_events.css({ top: '0', left: '0' }).height(rect.top); $bottom_dis_events.css({ top: rect.bottom + 'px', left: '0' }); $left_dis_events.css({ top: '0', left: 0 + 'px' }).width(rect.left); $right_dis_events.css({ top: '0', left: rect.right + 'px' }); }; that.renderLabelWithShape = function (data) { var shape_type = data.shape || 'rect'; var shape_data = {}; var half_w = 0; var half_h = 0; var shape_offsets = { top: data.top || 0, bottom: data.bottom || 0, left: data.left || 0, right: data.right || 0 }; switch (shape_type) { case 'circle': half_w = half_h = data.radius; var sides_pos = { top: data.center_y - half_h + shape_offsets.top, bottom: data.center_y + half_h - shape_offsets.bottom, left: data.center_x - half_w + shape_offsets.left, right: data.center_x + half_w - shape_offsets.right }; var width = sides_pos.right - sides_pos.left; var height = sides_pos.bottom - sides_pos.top; data.radius = Math.round(Math.min(width, height) / 2); //new half habarites half_w = half_h = Math.round(data.radius / 2); var new_half_w = Math.round(width / 2); var new_half_h = Math.round(height / 2); //new center_x and center_y data.center_x = sides_pos.left + new_half_w; data.center_y = sides_pos.top + new_half_h; shape_data = that.renderCircle({ x: data.center_x, y: data.center_y, r: data.radius }); break; case 'rect': half_w = Math.round(data.width / 2); half_h = Math.round(data.height / 2); var sides_pos = { top: data.center_y - half_h + shape_offsets.top, bottom: data.center_y + half_h - shape_offsets.bottom, left: data.center_x - half_w + shape_offsets.left, right: data.center_x + half_w - shape_offsets.right }; data.width = sides_pos.right - sides_pos.left; data.height = sides_pos.bottom - sides_pos.top; half_w = Math.round(data.width / 2); half_h = Math.round(data.height / 2); //new center_x and center_y data.center_x = sides_pos.left + half_w; data.center_y = sides_pos.top + half_h; shape_data = that.renderRect({ x: data.center_x, y: data.center_y, w: data.width, h: data.height, r: data.radius, }); break; } var body_size = { w: that.enjoyhint.width(), h: that.enjoyhint.height() }; var label = that.getLabelElement({ x: 0, y: 0, text: data.text }); var label_width = label.outerWidth(); var label_height = label.outerHeight(); label.remove(); var top_offset = data.center_y - half_h; var bottom_offset = body_size.h - (data.center_y + half_h); var left_offset = data.center_x - half_w; var right_offset = body_size.w - (data.center_x + half_w); var label_hor_side = (body_size.w - data.center_x) < data.center_x ? 'left' : 'right'; var label_ver_side = (body_size.h - data.center_y) < data.center_y ? 'top' : 'bottom'; var label_shift = 150; var label_margin = 40; var label_shift_with_label_width = label_shift + label_width + label_margin; var label_shift_with_label_height = label_shift + label_height + label_margin; var label_hor_offset = half_w + label_shift; var label_ver_offset = half_h + label_shift; var label_x = (label_hor_side == 'left') ? data.center_x - label_hor_offset - label_width : data.center_x + label_hor_offset; var label_y = (label_ver_side == 'top') ? data.center_y - label_ver_offset - label_height : data.center_y + label_ver_offset; if (top_offset < label_shift_with_label_height && bottom_offset < label_shift_with_label_height) { label_y = data.center_y + label_margin; } if (left_offset < label_shift_with_label_width && right_offset < label_shift_with_label_width) { label_x = data.center_x; } var label_data = that.renderLabel({ x: label_x, y: label_y, text: data.text }); that.$next_btn.css({ left: label_x, top: label_y + label_height + 20 }); var left_skip = label_x + that.$next_btn.width() + 10; console.log(that.nextBtn); if (that.nextBtn == "hide"){ left_skip = label_x; } that.$skip_btn.css({ left: left_skip, top: label_y + label_height + 20 }); that.$close_btn.css({ right : 10, top: 10 }); that.disableEventsNearRect({ top: shape_data.top, bottom: shape_data.bottom, left: shape_data.left, right: shape_data.right }); var x_to = 0; var y_to = 0; var arrow_side = false; var conn_label_side = 'left'; var conn_circle_side = 'left'; var is_center = (label_data.left <= shape_data.x && label_data.right >= shape_data.x); var is_left = (label_data.right < shape_data.x); var is_right = (label_data.left > shape_data.x); var is_abs_left = (label_data.right < shape_data.left); var is_abs_right = (label_data.left > shape_data.right); var is_top = (label_data.bottom < shape_data.top); var is_bottom = (label_data.top > shape_data.bottom); var is_mid = (label_data.bottom >= shape_data.y && label_data.top <= shape_data.y); var is_mid_top = (label_data.bottom <= shape_data.y && !is_top); var is_mid_bottom = (label_data.top >= shape_data.y && !is_bottom); function setArrowData(l_s, c_s, a_s) { conn_label_side = l_s; conn_circle_side = c_s; arrow_side = a_s; } function sideStatements(top_s, mid_top_s, mid_s, mid_bottom_s, bottom_s) { var statement = []; if (is_top) { statement = top_s; } else if (is_mid_top) { statement = mid_top_s; } else if (is_mid) { statement = mid_s; } else if (is_mid_bottom) { statement = mid_bottom_s; } else {//bottom statement = bottom_s; } if (!statement) { return; } else { setArrowData(statement[0], statement[1], statement[2]); } } if (is_center) { if (is_top) { setArrowData('bottom', 'top', 'top'); } else if (is_bottom) { setArrowData('top', 'bottom', 'bottom'); } else { return; } } else if (is_left) { sideStatements( ['right', 'top', 'top'],//top ['bottom', 'left', 'bottom'],//mid_top ['right', 'left', 'top'],//mid ['top', 'left', 'top'],//mid_bot ['right', 'bottom', 'bottom']//bot ); } else {//right sideStatements( ['left', 'top', 'top'],//top ['bottom', 'right', 'bottom'],//mid_top ['left', 'right', 'top'],//mid ['top', 'right', 'top'],//mid_bot ['left', 'bottom', 'bottom']//bot ); } var label_conn_coordinates = label_data.conn[conn_label_side]; var circle_conn_coordinates = shape_data.conn[conn_circle_side]; var by_top_side = (arrow_side == 'top') ? true : false; that.renderArrow({ x_from: label_conn_coordinates.x, y_from: label_conn_coordinates.y, x_to: circle_conn_coordinates.x, y_to: circle_conn_coordinates.y, by_top_side: by_top_side }); }; that.clear = function () { that.ctx.clearRect(0, 0, 3000, 2000); }; return this; }); }, set: function (val) { this.each(function () { this.enjoyhint_obj.setValue(val); }); return this; }, show: function () { this.each(function () { this.enjoyhint_obj.show(); }); return this; }, hide: function () { this.each(function () { this.enjoyhint_obj.hide(); }); return this; }, hide_next: function () { this.each(function () { this.enjoyhint_obj.hideNextBtn(); }); return this; }, show_next: function () { this.each(function () { this.enjoyhint_obj.showNextBtn(); }); return this; }, hide_skip: function () { this.each(function () { this.enjoyhint_obj.hideSkipBtn(); }); return this; }, show_skip: function () { this.each(function () { this.enjoyhint_obj.showSkipBtn(); }); return this; }, render_circle: function (x, y, r) { this.each(function () { this.enjoyhint_obj.renderCircle(x, y, r); }); return this; }, render_label: function (x, y, r) { this.each(function () { this.enjoyhint_obj.renderLabel(x, y, r); }); return this; }, render_label_with_shape: function (data) { this.each(function () { this.enjoyhint_obj.renderLabelWithShape(data); }); return this; }, clear: function () { this.each(function () { this.enjoyhint_obj.clear(); }); return this; }, close: function (val) { this.each(function () { this.enjoyhint_obj.closePopdown(); }); return this; } }; $.fn.enjoyhint = function (method) { console.log(method); if (methods[method]) { return methods[method].apply(this, Array.prototype.slice.call(arguments, 1)); } else if (typeof method === 'object' || !method) { return methods.init.apply(this, arguments); } else { $.error('Method ' + method + ' does not exist on $.numinput'); } return this; }; })(window.jQuery); ;/*! KineticJS v5.2.0 2015-01-22 http://lavrton.github.io/KineticJS/ by Eric Rowell @ericdrowell, Anton Lavrenov @lavrton - MIT License https://github.com/lavrton/KineticJS/wiki/License*/ var Kinetic={};!function(a){var b=Math.PI/180;Kinetic={version:"5.2.0",stages:[],idCounter:0,ids:{},names:{},shapes:{},listenClickTap:!1,inDblClickWindow:!1,enableTrace:!1,traceArrMax:100,dblClickWindow:400,pixelRatio:void 0,dragDistance:0,angleDeg:!0,showWarnings:!0,Filters:{},Node:function(a){this._init(a)},Shape:function(a){this.__init(a)},Container:function(a){this.__init(a)},Stage:function(a){this.___init(a)},BaseLayer:function(a){this.___init(a)},Layer:function(a){this.____init(a)},FastLayer:function(a){this.____init(a)},Group:function(a){this.___init(a)},isDragging:function(){var a=Kinetic.DD;return a?a.isDragging:!1},isDragReady:function(){var a=Kinetic.DD;return a?!!a.node:!1},_addId:function(a,b){void 0!==b&&(this.ids[b]=a)},_removeId:function(a){void 0!==a&&delete this.ids[a]},_addName:function(a,b){if(void 0!==b)for(var c=b.split(/\s/g),d=0;d"),Kinetic.window=Kinetic.document.createWindow(),Kinetic.window.Image=d.Image,Kinetic._nodeCanvas=d}return Kinetic.root=a,void(module.exports=c)}"function"==typeof define&&define.amd&&define(b),Kinetic.document=document,Kinetic.window=window,Kinetic.root=a}(this,function(){return Kinetic}),function(){Kinetic.Collection=function(){var a=[].slice.call(arguments),b=a.length,c=0;for(this.length=b;b>c;c++)this[c]=a[c];return this},Kinetic.Collection.prototype=[],Kinetic.Collection.prototype.each=function(a){for(var b=0;ba;a++)b.push(this[a]);return b},Kinetic.Collection.toCollection=function(a){var b,c=new Kinetic.Collection,d=a.length;for(b=0;d>b;b++)c.push(a[b]);return c},Kinetic.Collection._mapMethod=function(a){Kinetic.Collection.prototype[a]=function(){var b,c=this.length,d=[].slice.call(arguments);for(b=0;c>b;b++)this[b][a].apply(this[b],d);return this}},Kinetic.Collection.mapMethods=function(a){var b=a.prototype;for(var c in b)Kinetic.Collection._mapMethod(c)},Kinetic.Transform=function(a){this.m=a&&a.slice()||[1,0,0,1,0,0]},Kinetic.Transform.prototype={copy:function(){return new Kinetic.Transform(this.m)},point:function(a){var b=this.m;return{x:b[0]*a.x+b[2]*a.y+b[4],y:b[1]*a.x+b[3]*a.y+b[5]}},translate:function(a,b){return this.m[4]+=this.m[0]*a+this.m[2]*b,this.m[5]+=this.m[1]*a+this.m[3]*b,this},scale:function(a,b){return this.m[0]*=a,this.m[1]*=a,this.m[2]*=b,this.m[3]*=b,this},rotate:function(a){var b=Math.cos(a),c=Math.sin(a),d=this.m[0]*b+this.m[2]*c,e=this.m[1]*b+this.m[3]*c,f=this.m[0]*-c+this.m[2]*b,g=this.m[1]*-c+this.m[3]*b;return this.m[0]=d,this.m[1]=e,this.m[2]=f,this.m[3]=g,this},getTranslation:function(){return{x:this.m[4],y:this.m[5]}},skew:function(a,b){var c=this.m[0]+this.m[2]*b,d=this.m[1]+this.m[3]*b,e=this.m[2]+this.m[0]*a,f=this.m[3]+this.m[1]*a;return this.m[0]=c,this.m[1]=d,this.m[2]=e,this.m[3]=f,this},multiply:function(a){var b=this.m[0]*a.m[0]+this.m[2]*a.m[1],c=this.m[1]*a.m[0]+this.m[3]*a.m[1],d=this.m[0]*a.m[2]+this.m[2]*a.m[3],e=this.m[1]*a.m[2]+this.m[3]*a.m[3],f=this.m[0]*a.m[4]+this.m[2]*a.m[5]+this.m[4],g=this.m[1]*a.m[4]+this.m[3]*a.m[5]+this.m[5];return this.m[0]=b,this.m[1]=c,this.m[2]=d,this.m[3]=e,this.m[4]=f,this.m[5]=g,this},invert:function(){var a=1/(this.m[0]*this.m[3]-this.m[1]*this.m[2]),b=this.m[3]*a,c=-this.m[1]*a,d=-this.m[2]*a,e=this.m[0]*a,f=a*(this.m[2]*this.m[5]-this.m[3]*this.m[4]),g=a*(this.m[1]*this.m[4]-this.m[0]*this.m[5]);return this.m[0]=b,this.m[1]=c,this.m[2]=d,this.m[3]=e,this.m[4]=f,this.m[5]=g,this},getMatrix:function(){return this.m},setAbsolutePosition:function(a,b){var c=this.m[0],d=this.m[1],e=this.m[2],f=this.m[3],g=this.m[4],h=this.m[5],i=(c*(b-h)-d*(a-g))/(c*f-d*e),j=(a-g-e*i)/c;return this.translate(j,i)}};var a="2d",b="[object Array]",c="[object Number]",d="[object String]",e=Math.PI/180,f=180/Math.PI,g="#",h="",i="0",j="Kinetic warning: ",k="Kinetic error: ",l="rgb(",m={aqua:[0,255,255],lime:[0,255,0],silver:[192,192,192],black:[0,0,0],maroon:[128,0,0],teal:[0,128,128],blue:[0,0,255],navy:[0,0,128],white:[255,255,255],fuchsia:[255,0,255],olive:[128,128,0],yellow:[255,255,0],orange:[255,165,0],gray:[128,128,128],purple:[128,0,128],green:[0,128,0],red:[255,0,0],pink:[255,192,203],cyan:[0,255,255],transparent:[255,255,255,0]},n=/rgb\((\d{1,3}),(\d{1,3}),(\d{1,3})\)/;Kinetic.Util={_isElement:function(a){return!(!a||1!=a.nodeType)},_isFunction:function(a){return!!(a&&a.constructor&&a.call&&a.apply)},_isObject:function(a){return!!a&&a.constructor==Object},_isArray:function(a){return Object.prototype.toString.call(a)==b},_isNumber:function(a){return Object.prototype.toString.call(a)==c},_isString:function(a){return Object.prototype.toString.call(a)==d},_throttle:function(a,b,c){var d,e,f,g=null,h=0,i=c||{},j=function(){h=i.leading===!1?0:(new Date).getTime(),g=null,f=a.apply(d,e),d=e=null};return function(){var c=(new Date).getTime();h||i.leading!==!1||(h=c);var k=b-(c-h);return d=this,e=arguments,0>=k?(clearTimeout(g),g=null,h=c,f=a.apply(d,e),d=e=null):g||i.trailing===!1||(g=setTimeout(j,k)),f}},_hasMethods:function(a){var b,c=[];for(b in a)this._isFunction(a[b])&&c.push(b);return c.length>0},createCanvasElement:function(){var a=Kinetic.document.createElement("canvas");try{a.style=a.style||{}}catch(b){}return a},isBrowser:function(){return"object"!=typeof exports},_isInDocument:function(a){for(;a=a.parentNode;)if(a==Kinetic.document)return!0;return!1},_simplifyArray:function(a){var b,c,d=[],e=a.length,f=Kinetic.Util;for(b=0;e>b;b++)c=a[b],f._isNumber(c)?c=Math.round(1e3*c)/1e3:f._isString(c)||(c=c.toString()),d.push(c);return d},_getImage:function(b,c){var d,e;if(b)if(this._isElement(b))c(b);else if(this._isString(b))d=new Kinetic.window.Image,d.onload=function(){c(d)},d.src=b;else if(b.data){e=Kinetic.Util.createCanvasElement(),e.width=b.width,e.height=b.height;var f=e.getContext(a);f.putImageData(b,0,0),this._getImage(e.toDataURL(),c)}else c(null);else c(null)},_getRGBAString:function(a){var b=a.red||0,c=a.green||0,d=a.blue||0,e=a.alpha||1;return["rgba(",b,",",c,",",d,",",e,")"].join(h)},_rgbToHex:function(a,b,c){return((1<<24)+(a<<16)+(b<<8)+c).toString(16).slice(1)},_hexToRgb:function(a){a=a.replace(g,h);var b=parseInt(a,16);return{r:b>>16&255,g:b>>8&255,b:255&b}},getRandomColor:function(){for(var a=(16777215*Math.random()<<0).toString(16);a.length<6;)a=i+a;return g+a},get:function(a,b){return void 0===a?b:a},getRGB:function(a){var b;return a in m?(b=m[a],{r:b[0],g:b[1],b:b[2]}):a[0]===g?this._hexToRgb(a.substring(1)):a.substr(0,4)===l?(b=n.exec(a.replace(/ /g,"")),{r:parseInt(b[1],10),g:parseInt(b[2],10),b:parseInt(b[3],10)}):{r:0,g:0,b:0}},_merge:function(a,b){var c=this._clone(b);for(var d in a)c[d]=this._isObject(a[d])?this._merge(a[d],c[d]):a[d];return c},cloneObject:function(a){var b={};for(var c in a)b[c]=this._isObject(a[c])?this.cloneObject(a[c]):this._isArray(a[c])?this.cloneArray(a[c]):a[c];return b},cloneArray:function(a){return a.slice(0)},_degToRad:function(a){return a*e},_radToDeg:function(a){return a*f},_capitalize:function(a){return a.charAt(0).toUpperCase()+a.slice(1)},error:function(a){throw new Error(k+a)},warn:function(a){Kinetic.root.console&&console.warn&&Kinetic.showWarnings&&console.warn(j+a)},extend:function(a,b){function c(){this.constructor=a}c.prototype=b.prototype;var d=a.prototype;a.prototype=new c;for(var e in d)d.hasOwnProperty(e)&&(a.prototype[e]=d[e]);a.__super__=b.prototype},addMethods:function(a,b){var c;for(c in b)a.prototype[c]=b[c]},_getControlPoints:function(a,b,c,d,e,f,g){var h=Math.sqrt(Math.pow(c-a,2)+Math.pow(d-b,2)),i=Math.sqrt(Math.pow(e-c,2)+Math.pow(f-d,2)),j=g*h/(h+i),k=g*i/(h+i),l=c-j*(e-a),m=d-j*(f-b),n=c+k*(e-a),o=d+k*(f-b);return[l,m,n,o]},_expandPoints:function(a,b){var c,d,e=a.length,f=[];for(c=2;e-2>c;c+=2)d=Kinetic.Util._getControlPoints(a[c-2],a[c-1],a[c],a[c+1],a[c+2],a[c+3],b),f.push(d[0]),f.push(d[1]),f.push(a[c]),f.push(a[c+1]),f.push(d[2]),f.push(d[3]);return f},_removeLastLetter:function(a){return a.substring(0,a.length-1)}}}(),function(){var a=Kinetic.Util.createCanvasElement(),b=a.getContext("2d"),c=Kinetic.UA.mobile?function(){var a=window.devicePixelRatio||1,c=b.webkitBackingStorePixelRatio||b.mozBackingStorePixelRatio||b.msBackingStorePixelRatio||b.oBackingStorePixelRatio||b.backingStorePixelRatio||1;return a/c}():1;Kinetic.Canvas=function(a){this.init(a)},Kinetic.Canvas.prototype={init:function(a){var b=a||{},d=b.pixelRatio||Kinetic.pixelRatio||c;this.pixelRatio=d,this._canvas=Kinetic.Util.createCanvasElement(),this._canvas.style.padding=0,this._canvas.style.margin=0,this._canvas.style.border=0,this._canvas.style.background="transparent",this._canvas.style.position="absolute",this._canvas.style.top=0,this._canvas.style.left=0},getContext:function(){return this.context},getPixelRatio:function(){return this.pixelRatio},setPixelRatio:function(a){this.pixelRatio=a,this.setSize(this.getWidth(),this.getHeight())},setWidth:function(a){this.width=this._canvas.width=a*this.pixelRatio,this._canvas.style.width=a+"px"},setHeight:function(a){this.height=this._canvas.height=a*this.pixelRatio,this._canvas.style.height=a+"px"},getWidth:function(){return this.width},getHeight:function(){return this.height},setSize:function(a,b){this.setWidth(a),this.setHeight(b)},toDataURL:function(a,b){try{return this._canvas.toDataURL(a,b)}catch(c){try{return this._canvas.toDataURL()}catch(d){return Kinetic.Util.warn("Unable to get data URL. "+d.message),""}}}},Kinetic.SceneCanvas=function(a){var b=a||{},c=b.width||0,d=b.height||0;Kinetic.Canvas.call(this,b),this.context=new Kinetic.SceneContext(this),this.setSize(c,d)},Kinetic.SceneCanvas.prototype={setWidth:function(a){var b=this.pixelRatio,c=this.getContext()._context;Kinetic.Canvas.prototype.setWidth.call(this,a),c.scale(b,b)},setHeight:function(a){var b=this.pixelRatio,c=this.getContext()._context;Kinetic.Canvas.prototype.setHeight.call(this,a),c.scale(b,b)}},Kinetic.Util.extend(Kinetic.SceneCanvas,Kinetic.Canvas),Kinetic.HitCanvas=function(a){var b=a||{},c=b.width||0,d=b.height||0;Kinetic.Canvas.call(this,b),this.context=new Kinetic.HitContext(this),this.setSize(c,d),this.hitCanvas=!0},Kinetic.Util.extend(Kinetic.HitCanvas,Kinetic.Canvas)}(),function(){var a=",",b="(",c=")",d="([",e="])",f=";",g="()",h="=",i=["arc","arcTo","beginPath","bezierCurveTo","clearRect","clip","closePath","createLinearGradient","createPattern","createRadialGradient","drawImage","fill","fillText","getImageData","createImageData","lineTo","moveTo","putImageData","quadraticCurveTo","rect","restore","rotate","save","scale","setLineDash","setTransform","stroke","strokeText","transform","translate"];Kinetic.Context=function(a){this.init(a)},Kinetic.Context.prototype={init:function(a){this.canvas=a,this._context=a._canvas.getContext("2d"),Kinetic.enableTrace&&(this.traceArr=[],this._enableTrace())},fillShape:function(a){a.getFillEnabled()&&this._fill(a)},strokeShape:function(a){a.getStrokeEnabled()&&this._stroke(a)},fillStrokeShape:function(a){var b=a.getFillEnabled();b&&this._fill(a),a.getStrokeEnabled()&&this._stroke(a)},getTrace:function(i){var j,k,l,m,n=this.traceArr,o=n.length,p="";for(j=0;o>j;j++)k=n[j],l=k.method,l?(m=k.args,p+=l,p+=i?g:Kinetic.Util._isArray(m[0])?d+m.join(a)+e:b+m.join(a)+c):(p+=k.property,i||(p+=h+k.val)),p+=f;return p},clearTrace:function(){this.traceArr=[]},_trace:function(a){var b,c=this.traceArr;c.push(a),b=c.length,b>=Kinetic.traceArrMax&&c.shift()},reset:function(){var a=this.getCanvas().getPixelRatio();this.setTransform(1*a,0,0,1*a,0,0)},getCanvas:function(){return this.canvas},clear:function(a){var b=this.getCanvas();a?this.clearRect(a.x||0,a.y||0,a.width||0,a.height||0):this.clearRect(0,0,b.getWidth(),b.getHeight())},_applyLineCap:function(a){var b=a.getLineCap();b&&this.setAttr("lineCap",b)},_applyOpacity:function(a){var b=a.getAbsoluteOpacity();1!==b&&this.setAttr("globalAlpha",b)},_applyLineJoin:function(a){var b=a.getLineJoin();b&&this.setAttr("lineJoin",b)},setAttr:function(a,b){this._context[a]=b},arc:function(){var a=arguments;this._context.arc(a[0],a[1],a[2],a[3],a[4],a[5])},beginPath:function(){this._context.beginPath()},bezierCurveTo:function(){var a=arguments;this._context.bezierCurveTo(a[0],a[1],a[2],a[3],a[4],a[5])},clearRect:function(){var a=arguments;this._context.clearRect(a[0],a[1],a[2],a[3])},clip:function(){this._context.clip()},closePath:function(){this._context.closePath()},createImageData:function(){var a=arguments;return 2===a.length?this._context.createImageData(a[0],a[1]):1===a.length?this._context.createImageData(a[0]):void 0},createLinearGradient:function(){var a=arguments;return this._context.createLinearGradient(a[0],a[1],a[2],a[3])},createPattern:function(){var a=arguments;return this._context.createPattern(a[0],a[1])},createRadialGradient:function(){var a=arguments;return this._context.createRadialGradient(a[0],a[1],a[2],a[3],a[4],a[5])},drawImage:function(){var a=arguments,b=this._context;3===a.length?b.drawImage(a[0],a[1],a[2]):5===a.length?b.drawImage(a[0],a[1],a[2],a[3],a[4]):9===a.length&&b.drawImage(a[0],a[1],a[2],a[3],a[4],a[5],a[6],a[7],a[8])},fill:function(){this._context.fill()},fillText:function(){var a=arguments;this._context.fillText(a[0],a[1],a[2])},getImageData:function(){var a=arguments;return this._context.getImageData(a[0],a[1],a[2],a[3])},lineTo:function(){var a=arguments;this._context.lineTo(a[0],a[1])},moveTo:function(){var a=arguments;this._context.moveTo(a[0],a[1])},rect:function(){var a=arguments;this._context.rect(a[0],a[1],a[2],a[3])},putImageData:function(){var a=arguments;this._context.putImageData(a[0],a[1],a[2])},quadraticCurveTo:function(){var a=arguments;this._context.quadraticCurveTo(a[0],a[1],a[2],a[3])},restore:function(){this._context.restore()},rotate:function(){var a=arguments;this._context.rotate(a[0])},save:function(){this._context.save()},scale:function(){var a=arguments;this._context.scale(a[0],a[1])},setLineDash:function(){var a=arguments,b=this._context;this._context.setLineDash?b.setLineDash(a[0]):"mozDash"in b?b.mozDash=a[0]:"webkitLineDash"in b&&(b.webkitLineDash=a[0])},setTransform:function(){var a=arguments;this._context.setTransform(a[0],a[1],a[2],a[3],a[4],a[5])},stroke:function(){this._context.stroke()},strokeText:function(){var a=arguments;this._context.strokeText(a[0],a[1],a[2])},transform:function(){var a=arguments;this._context.transform(a[0],a[1],a[2],a[3],a[4],a[5])},translate:function(){var a=arguments;this._context.translate(a[0],a[1])},_enableTrace:function(){var a,b,c=this,d=i.length,e=Kinetic.Util._simplifyArray,f=this.setAttr,g=function(a){var d,f=c[a];c[a]=function(){return b=e(Array.prototype.slice.call(arguments,0)),d=f.apply(c,arguments),c._trace({method:a,args:b}),d}};for(a=0;d>a;a++)g(i[a]);c.setAttr=function(){f.apply(c,arguments),c._trace({property:arguments[0],val:arguments[1]})}}},Kinetic.SceneContext=function(a){Kinetic.Context.call(this,a)},Kinetic.SceneContext.prototype={_fillColor:function(a){var b=a.fill()||Kinetic.Util._getRGBAString({red:a.fillRed(),green:a.fillGreen(),blue:a.fillBlue(),alpha:a.fillAlpha()});this.setAttr("fillStyle",b),a._fillFunc(this)},_fillPattern:function(a){var b=a.getFillPatternImage(),c=a.getFillPatternX(),d=a.getFillPatternY(),e=a.getFillPatternScale(),f=Kinetic.getAngle(a.getFillPatternRotation()),g=a.getFillPatternOffset(),h=a.getFillPatternRepeat();(c||d)&&this.translate(c||0,d||0),f&&this.rotate(f),e&&this.scale(e.x,e.y),g&&this.translate(-1*g.x,-1*g.y),this.setAttr("fillStyle",this.createPattern(b,h||"repeat")),this.fill()},_fillLinearGradient:function(a){var b=a.getFillLinearGradientStartPoint(),c=a.getFillLinearGradientEndPoint(),d=a.getFillLinearGradientColorStops(),e=this.createLinearGradient(b.x,b.y,c.x,c.y);if(d){for(var f=0;fh;h++)i=e[h],a[i]=this.getAttr(d+k(i));return a},c.prototype[m]=function(a){var b,c=this.attrs[d];f&&(a=f.call(this,a));for(b in a)this._setAttr(d+k(b),a[b]);return this._fireChangeEvent(d,c,a),g&&g.call(this),this},this.addOverloadedGetterSetter(c,d)},addOverloadedGetterSetter:function(c,d){var e=Kinetic.Util._capitalize(d),f=b+e,g=a+e;c.prototype[d]=function(){return arguments.length?(this[f](arguments[0]),this):this[g]()}},backCompat:function(a,b){var c;for(c in b)a.prototype[c]=a.prototype[b[c]]},afterSetFilter:function(){this._filterUpToDate=!1}},Kinetic.Validators={RGBComponent:function(a){return a>255?255:0>a?0:Math.round(a)},alphaComponent:function(a){return a>1?1:1e-4>a?1e-4:a}}}(),function(){var a="absoluteOpacity",b="absoluteTransform",c="Change",d="children",e=".",f="",g="get",h="id",i="kinetic",j="listening",k="mouseenter",l="mouseleave",m="name",n="set",o="Shape",p=" ",q="stage",r="transform",s="Stage",t="visible",u=["id"],v=["xChange.kinetic","yChange.kinetic","scaleXChange.kinetic","scaleYChange.kinetic","skewXChange.kinetic","skewYChange.kinetic","rotationChange.kinetic","offsetXChange.kinetic","offsetYChange.kinetic","transformsEnabledChange.kinetic"].join(p);Kinetic.Util.addMethods(Kinetic.Node,{_init:function(c){var d=this;this._id=Kinetic.idCounter++,this.eventListeners={},this.attrs={},this._cache={},this._filterUpToDate=!1,this.setAttrs(c),this.on(v,function(){this._clearCache(r),d._clearSelfAndDescendantCache(b)}),this.on("visibleChange.kinetic",function(){d._clearSelfAndDescendantCache(t)}),this.on("listeningChange.kinetic",function(){d._clearSelfAndDescendantCache(j)}),this.on("opacityChange.kinetic",function(){d._clearSelfAndDescendantCache(a)})},_clearCache:function(a){a?delete this._cache[a]:this._cache={}},_getCache:function(a,b){var c=this._cache[a];return void 0===c&&(this._cache[a]=b.call(this)),this._cache[a]},_clearSelfAndDescendantCache:function(a){this._clearCache(a),this.children&&this.getChildren().each(function(b){b._clearSelfAndDescendantCache(a)})},clearCache:function(){return delete this._cache.canvas,this._filterUpToDate=!1,this},cache:function(a){var b=a||{},c=b.x||0,d=b.y||0,e=b.width||this.width(),f=b.height||this.height(),g=b.drawBorder||!1;if(0===e||0===f)return void Kinetic.Util.warn("Width or height of caching configuration equals 0. Cache is ignored.");var h=new Kinetic.SceneCanvas({pixelRatio:1,width:e,height:f}),i=new Kinetic.SceneCanvas({pixelRatio:1,width:e,height:f}),j=new Kinetic.HitCanvas({width:e,height:f}),k=h.getContext(),l=j.getContext();return j.isCache=!0,this.clearCache(),k.save(),l.save(),g&&(k.save(),k.beginPath(),k.rect(0,0,e,f),k.closePath(),k.setAttr("strokeStyle","red"),k.setAttr("lineWidth",5),k.stroke(),k.restore()),k.translate(-1*c,-1*d),l.translate(-1*c,-1*d),"Shape"===this.nodeType&&(k.translate(-1*this.x(),-1*this.y()),l.translate(-1*this.x(),-1*this.y())),this.drawScene(h,this),this.drawHit(j,this),k.restore(),l.restore(),this._cache.canvas={scene:h,filter:i,hit:j},this},_drawCachedSceneCanvas:function(a){a.save(),this.getLayer()._applyTransform(this,a),a._applyOpacity(this),a.drawImage(this._getCachedSceneCanvas()._canvas,0,0),a.restore()},_getCachedSceneCanvas:function(){var a,b,c,d,e=this.filters(),f=this._cache.canvas,g=f.scene,h=f.filter,i=h.getContext();if(e){if(!this._filterUpToDate){try{for(a=e.length,i.clear(),i.drawImage(g._canvas,0,0),b=i.getImageData(0,0,h.getWidth(),h.getHeight()),c=0;a>c;c++)d=e[c],d.call(this,b),i.putImageData(b,0,0)}catch(j){Kinetic.Util.warn("Unable to apply filter. "+j.message)}this._filterUpToDate=!0}return h}return g},_drawCachedHitCanvas:function(a){var b=this._cache.canvas,c=b.hit;a.save(),this.getLayer()._applyTransform(this,a),a.drawImage(c._canvas,0,0),a.restore()},on:function(a,b){var c,d,g,h,i,j=a.split(p),k=j.length;for(c=0;k>c;c++)d=j[c],g=d.split(e),h=g[0],i=g[1]||f,this.eventListeners[h]||(this.eventListeners[h]=[]),this.eventListeners[h].push({name:i,handler:b});return this},off:function(a){var b,c,d,f,g,h,i=(a||"").split(p),j=i.length;if(!a)for(c in this.eventListeners)this._off(c);for(b=0;j>b;b++)if(d=i[b],f=d.split(e),g=f[0],h=f[1],g)this.eventListeners[g]&&this._off(g,h);else for(c in this.eventListeners)this._off(c,h);return this},dispatchEvent:function(a){var b={target:this,type:a.type,evt:a};this.fire(a.type,b)},addEventListener:function(a,b){this.on(a,function(a){b.call(this,a.evt)})},removeEventListener:function(a){this.off(a)},remove:function(){var c=this.getParent();return c&&c.children&&(c.children.splice(this.index,1),c._setChildrenIndices(),delete this.parent),this._clearSelfAndDescendantCache(q),this._clearSelfAndDescendantCache(b),this._clearSelfAndDescendantCache(t),this._clearSelfAndDescendantCache(j),this._clearSelfAndDescendantCache(a),this},destroy:function(){Kinetic._removeId(this.getId()),Kinetic._removeName(this.getName(),this._id),this.remove()},getAttr:function(a){var b=g+Kinetic.Util._capitalize(a);return Kinetic.Util._isFunction(this[b])?this[b]():this.attrs[a]},getAncestors:function(){for(var a=this.getParent(),b=new Kinetic.Collection;a;)b.push(a),a=a.getParent();return b},getAttrs:function(){return this.attrs||{}},setAttrs:function(a){var b,c;if(a)for(b in a)b===d||a[b]instanceof Kinetic.Node||(c=n+Kinetic.Util._capitalize(b),Kinetic.Util._isFunction(this[c])?this[c](a[b]):this._setAttr(b,a[b]));return this},isListening:function(){return this._getCache(j,this._isListening)},_isListening:function(){var a=this.getListening(),b=this.getParent();return"inherit"===a?b?b.isListening():!0:a},isVisible:function(){return this._getCache(t,this._isVisible)},_isVisible:function(){var a=this.getVisible(),b=this.getParent();return"inherit"===a?b?b.isVisible():!0:a},shouldDrawHit:function(a){var b=this.getLayer();return a&&a.isCache||b&&b.hitGraphEnabled()&&this.isListening()&&this.isVisible()},show:function(){return this.setVisible(!0),this},hide:function(){return this.setVisible(!1),this},getZIndex:function(){return this.index||0},getAbsoluteZIndex:function(){function a(i){for(b=[],c=i.length,d=0;c>d;d++)e=i[d],h++,e.nodeType!==o&&(b=b.concat(e.getChildren().toArray())),e._id===g._id&&(d=c);b.length>0&&b[0].getDepth()<=f&&a(b)}var b,c,d,e,f=this.getDepth(),g=this,h=0;return g.nodeType!==s&&a(g.getStage().getChildren()),h},getDepth:function(){for(var a=0,b=this.parent;b;)a++,b=b.parent;return a},setPosition:function(a){return this.setX(a.x),this.setY(a.y),this},getPosition:function(){return{x:this.getX(),y:this.getY()}},getAbsolutePosition:function(){var a=this.getAbsoluteTransform().getMatrix(),b=new Kinetic.Transform,c=this.offset();return b.m=a.slice(),b.translate(c.x,c.y),b.getTranslation()},setAbsolutePosition:function(a){var b,c=this._clearTransform();return this.attrs.x=c.x,this.attrs.y=c.y,delete c.x,delete c.y,b=this.getAbsoluteTransform(),b.invert(),b.translate(a.x,a.y),a={x:this.attrs.x+b.getTranslation().x,y:this.attrs.y+b.getTranslation().y},this.setPosition({x:a.x,y:a.y}),this._setTransform(c),this},_setTransform:function(a){var c;for(c in a)this.attrs[c]=a[c];this._clearCache(r),this._clearSelfAndDescendantCache(b)},_clearTransform:function(){var a={x:this.getX(),y:this.getY(),rotation:this.getRotation(),scaleX:this.getScaleX(),scaleY:this.getScaleY(),offsetX:this.getOffsetX(),offsetY:this.getOffsetY(),skewX:this.getSkewX(),skewY:this.getSkewY()};return this.attrs.x=0,this.attrs.y=0,this.attrs.rotation=0,this.attrs.scaleX=1,this.attrs.scaleY=1,this.attrs.offsetX=0,this.attrs.offsetY=0,this.attrs.skewX=0,this.attrs.skewY=0,this._clearCache(r),this._clearSelfAndDescendantCache(b),a},move:function(a){var b=a.x,c=a.y,d=this.getX(),e=this.getY();return void 0!==b&&(d+=b),void 0!==c&&(e+=c),this.setPosition({x:d,y:e}),this},_eachAncestorReverse:function(a,b){var c,d,e=[],f=this.getParent();if(b&&b._id===this._id)return a(this),!0;for(e.unshift(this);f&&(!b||f._id!==b._id);)e.unshift(f),f=f.parent;for(c=e.length,d=0;c>d;d++)a(e[d])},rotate:function(a){return this.setRotation(this.getRotation()+a),this},moveToTop:function(){if(!this.parent)return void Kinetic.Util.warn("Node has no parent. moveToTop function is ignored.");var a=this.index;return this.parent.children.splice(a,1),this.parent.children.push(this),this.parent._setChildrenIndices(),!0},moveUp:function(){if(!this.parent)return void Kinetic.Util.warn("Node has no parent. moveUp function is ignored.");var a=this.index,b=this.parent.getChildren().length;return b-1>a?(this.parent.children.splice(a,1),this.parent.children.splice(a+1,0,this),this.parent._setChildrenIndices(),!0):!1},moveDown:function(){if(!this.parent)return void Kinetic.Util.warn("Node has no parent. moveDown function is ignored.");var a=this.index;return a>0?(this.parent.children.splice(a,1),this.parent.children.splice(a-1,0,this),this.parent._setChildrenIndices(),!0):!1},moveToBottom:function(){if(!this.parent)return void Kinetic.Util.warn("Node has no parent. moveToBottom function is ignored.");var a=this.index;return a>0?(this.parent.children.splice(a,1),this.parent.children.unshift(this),this.parent._setChildrenIndices(),!0):!1},setZIndex:function(a){if(!this.parent)return void Kinetic.Util.warn("Node has no parent. zIndex parameter is ignored.");var b=this.index;return this.parent.children.splice(b,1),this.parent.children.splice(a,0,this),this.parent._setChildrenIndices(),this},getAbsoluteOpacity:function(){return this._getCache(a,this._getAbsoluteOpacity)},_getAbsoluteOpacity:function(){var a=this.getOpacity();return this.getParent()&&(a*=this.getParent().getAbsoluteOpacity()),a},moveTo:function(a){return this.getParent()!==a&&(this.remove(),a.add(this)),this},toObject:function(){var a,b,c,d,e=Kinetic.Util,f={},g=this.getAttrs();f.attrs={};for(a in g)b=g[a],e._isFunction(b)||e._isElement(b)||e._isObject(b)&&e._hasMethods(b)||(c=this[a],delete g[a],d=c?c.call(this):null,g[a]=b,d!==b&&(f.attrs[a]=b));return f.className=this.getClassName(),f},toJSON:function(){return JSON.stringify(this.toObject())},getParent:function(){return this.parent},getLayer:function(){var a=this.getParent();return a?a.getLayer():null},getStage:function(){return this._getCache(q,this._getStage)},_getStage:function(){var a=this.getParent();return a?a.getStage():void 0},fire:function(a,b,c){return c?this._fireAndBubble(a,b||{}):this._fire(a,b||{}),this},getAbsoluteTransform:function(a){return a?this._getAbsoluteTransform(a):this._getCache(b,this._getAbsoluteTransform)},_getAbsoluteTransform:function(a){var b,c,d=new Kinetic.Transform;return this._eachAncestorReverse(function(a){b=a.transformsEnabled(),c=a.getTransform(),"all"===b?d.multiply(c):"position"===b&&d.translate(a.x(),a.y())},a),d},getTransform:function(){return this._getCache(r,this._getTransform)},_getTransform:function(){var a=new Kinetic.Transform,b=this.getX(),c=this.getY(),d=Kinetic.getAngle(this.getRotation()),e=this.getScaleX(),f=this.getScaleY(),g=this.getSkewX(),h=this.getSkewY(),i=this.getOffsetX(),j=this.getOffsetY();return(0!==b||0!==c)&&a.translate(b,c),0!==d&&a.rotate(d),(0!==g||0!==h)&&a.skew(g,h),(1!==e||1!==f)&&a.scale(e,f),(0!==i||0!==j)&&a.translate(-1*i,-1*j),a},clone:function(a){var b,c,d,e,f,g=this.getClassName(),h=Kinetic.Util.cloneObject(this.attrs);for(var j in u){var k=u[j];delete h[k]}for(b in a)h[b]=a[b];var l=new Kinetic[g](h);for(b in this.eventListeners)for(c=this.eventListeners[b],d=c.length,e=0;d>e;e++)f=c[e],f.name.indexOf(i)<0&&(l.eventListeners[b]||(l.eventListeners[b]=[]),l.eventListeners[b].push(f));return l},toDataURL:function(a){a=a||{};var b=a.mimeType||null,c=a.quality||null,d=this.getStage(),e=a.x||0,f=a.y||0,g=new Kinetic.SceneCanvas({width:a.width||this.getWidth()||(d?d.getWidth():0),height:a.height||this.getHeight()||(d?d.getHeight():0),pixelRatio:1}),h=g.getContext();return h.save(),(e||f)&&h.translate(-1*e,-1*f),this.drawScene(g),h.restore(),g.toDataURL(b,c)},toImage:function(a){Kinetic.Util._getImage(this.toDataURL(a),function(b){a.callback(b)})},setSize:function(a){return this.setWidth(a.width),this.setHeight(a.height),this},getSize:function(){return{width:this.getWidth(),height:this.getHeight()}},getWidth:function(){return this.attrs.width||0},getHeight:function(){return this.attrs.height||0},getClassName:function(){return this.className||this.nodeType},getType:function(){return this.nodeType},getDragDistance:function(){return void 0!==this.attrs.dragDistance?this.attrs.dragDistance:this.parent?this.parent.getDragDistance():Kinetic.dragDistance },_get:function(a){return this.className===a||this.nodeType===a?[this]:[]},_off:function(a,b){var c,d,e=this.eventListeners[a];for(c=0;ce;e++)c.add(this._createNode(g[e]));return c},Kinetic.Factory.addOverloadedGetterSetter(Kinetic.Node,"position"),Kinetic.Factory.addGetterSetter(Kinetic.Node,"x",0),Kinetic.Factory.addGetterSetter(Kinetic.Node,"y",0),Kinetic.Factory.addGetterSetter(Kinetic.Node,"opacity",1),Kinetic.Factory.addGetter(Kinetic.Node,"name"),Kinetic.Factory.addOverloadedGetterSetter(Kinetic.Node,"name"),Kinetic.Factory.addGetter(Kinetic.Node,"id"),Kinetic.Factory.addOverloadedGetterSetter(Kinetic.Node,"id"),Kinetic.Factory.addGetterSetter(Kinetic.Node,"rotation",0),Kinetic.Factory.addComponentsGetterSetter(Kinetic.Node,"scale",["x","y"]),Kinetic.Factory.addGetterSetter(Kinetic.Node,"scaleX",1),Kinetic.Factory.addGetterSetter(Kinetic.Node,"scaleY",1),Kinetic.Factory.addComponentsGetterSetter(Kinetic.Node,"skew",["x","y"]),Kinetic.Factory.addGetterSetter(Kinetic.Node,"skewX",0),Kinetic.Factory.addGetterSetter(Kinetic.Node,"skewY",0),Kinetic.Factory.addComponentsGetterSetter(Kinetic.Node,"offset",["x","y"]),Kinetic.Factory.addGetterSetter(Kinetic.Node,"offsetX",0),Kinetic.Factory.addGetterSetter(Kinetic.Node,"offsetY",0),Kinetic.Factory.addSetter(Kinetic.Node,"dragDistance"),Kinetic.Factory.addOverloadedGetterSetter(Kinetic.Node,"dragDistance"),Kinetic.Factory.addSetter(Kinetic.Node,"width",0),Kinetic.Factory.addOverloadedGetterSetter(Kinetic.Node,"width"),Kinetic.Factory.addSetter(Kinetic.Node,"height",0),Kinetic.Factory.addOverloadedGetterSetter(Kinetic.Node,"height"),Kinetic.Factory.addGetterSetter(Kinetic.Node,"listening","inherit"),Kinetic.Factory.addGetterSetter(Kinetic.Node,"filters",void 0,function(a){return this._filterUpToDate=!1,a}),Kinetic.Factory.addGetterSetter(Kinetic.Node,"visible","inherit"),Kinetic.Factory.addGetterSetter(Kinetic.Node,"transformsEnabled","all"),Kinetic.Factory.addOverloadedGetterSetter(Kinetic.Node,"size"),Kinetic.Factory.backCompat(Kinetic.Node,{rotateDeg:"rotate",setRotationDeg:"setRotation",getRotationDeg:"getRotation"}),Kinetic.Collection.mapMethods(Kinetic.Node)}(),function(){Kinetic.Filters.Grayscale=function(a){var b,c,d=a.data,e=d.length;for(b=0;e>b;b+=4)c=.34*d[b]+.5*d[b+1]+.16*d[b+2],d[b]=c,d[b+1]=c,d[b+2]=c}}(),function(){Kinetic.Filters.Brighten=function(a){var b,c=255*this.brightness(),d=a.data,e=d.length;for(b=0;e>b;b+=4)d[b]+=c,d[b+1]+=c,d[b+2]+=c},Kinetic.Factory.addGetterSetter(Kinetic.Node,"brightness",0,null,Kinetic.Factory.afterSetFilter)}(),function(){Kinetic.Filters.Invert=function(a){var b,c=a.data,d=c.length;for(b=0;d>b;b+=4)c[b]=255-c[b],c[b+1]=255-c[b+1],c[b+2]=255-c[b+2]}}(),function(){function a(){this.r=0,this.g=0,this.b=0,this.a=0,this.next=null}function b(b,e){var f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,A,B,C,D=b.data,E=b.width,F=b.height,G=e+e+1,H=E-1,I=F-1,J=e+1,K=J*(J+1)/2,L=new a,M=null,N=L,O=null,P=null,Q=c[e],R=d[e];for(h=1;G>h;h++)N=N.next=new a,h==J&&(M=N);for(N.next=L,l=k=0,g=0;F>g;g++){for(u=v=w=x=m=n=o=p=0,q=J*(y=D[k]),r=J*(z=D[k+1]),s=J*(A=D[k+2]),t=J*(B=D[k+3]),m+=K*y,n+=K*z,o+=K*A,p+=K*B,N=L,h=0;J>h;h++)N.r=y,N.g=z,N.b=A,N.a=B,N=N.next;for(h=1;J>h;h++)i=k+((h>H?H:h)<<2),m+=(N.r=y=D[i])*(C=J-h),n+=(N.g=z=D[i+1])*C,o+=(N.b=A=D[i+2])*C,p+=(N.a=B=D[i+3])*C,u+=y,v+=z,w+=A,x+=B,N=N.next;for(O=L,P=M,f=0;E>f;f++)D[k+3]=B=p*Q>>R,0!==B?(B=255/B,D[k]=(m*Q>>R)*B,D[k+1]=(n*Q>>R)*B,D[k+2]=(o*Q>>R)*B):D[k]=D[k+1]=D[k+2]=0,m-=q,n-=r,o-=s,p-=t,q-=O.r,r-=O.g,s-=O.b,t-=O.a,i=l+((i=f+e+1)f;f++){for(v=w=x=u=n=o=p=m=0,k=f<<2,q=J*(y=D[k]),r=J*(z=D[k+1]),s=J*(A=D[k+2]),t=J*(B=D[k+3]),m+=K*y,n+=K*z,o+=K*A,p+=K*B,N=L,h=0;J>h;h++)N.r=y,N.g=z,N.b=A,N.a=B,N=N.next;for(j=E,h=1;e>=h;h++)k=j+f<<2,m+=(N.r=y=D[k])*(C=J-h),n+=(N.g=z=D[k+1])*C,o+=(N.b=A=D[k+2])*C,p+=(N.a=B=D[k+3])*C,u+=y,v+=z,w+=A,x+=B,N=N.next,I>h&&(j+=E);for(k=f,O=L,P=M,g=0;F>g;g++)i=k<<2,D[i+3]=B=p*Q>>R,B>0?(B=255/B,D[i]=(m*Q>>R)*B,D[i+1]=(n*Q>>R)*B,D[i+2]=(o*Q>>R)*B):D[i]=D[i+1]=D[i+2]=0,m-=q,n-=r,o-=s,p-=t,q-=O.r,r-=O.g,s-=O.b,t-=O.a,i=f+((i=g+J)0&&b(a,c)},Kinetic.Factory.addGetterSetter(Kinetic.Node,"blurRadius",0,null,Kinetic.Factory.afterSetFilter)}(),function(){function a(a,b,c){var d=4*(c*a.width+b),e=[];return e.push(a.data[d++],a.data[d++],a.data[d++],a.data[d++]),e}function b(a,b){return Math.sqrt(Math.pow(a[0]-b[0],2)+Math.pow(a[1]-b[1],2)+Math.pow(a[2]-b[2],2))}function c(a){for(var b=[0,0,0],c=0;cn?0:255}return l}}function e(a,b){for(var c=0;ch;h++)for(var i=0;b>i;i++){for(var j=h*b+i,k=0,l=0;e>l;l++)for(var m=0;e>m;m++){var n=h+l-f,o=i+m-f;if(n>=0&&c>n&&o>=0&&b>o){var p=n*b+o,q=d[l*e+m];k+=a[p]*q}}g[j]=2040===k?255:0}return g}function g(a,b,c){for(var d=[1,1,1,1,1,1,1,1,1],e=Math.round(Math.sqrt(d.length)),f=Math.floor(e/2),g=[],h=0;c>h;h++)for(var i=0;b>i;i++){for(var j=h*b+i,k=0,l=0;e>l;l++)for(var m=0;e>m;m++){var n=h+l-f,o=i+m-f;if(n>=0&&c>n&&o>=0&&b>o){var p=n*b+o,q=d[l*e+m];k+=a[p]*q}}g[j]=k>=1020?255:0}return g}function h(a,b,c){for(var d=[1/9,1/9,1/9,1/9,1/9,1/9,1/9,1/9,1/9],e=Math.round(Math.sqrt(d.length)),f=Math.floor(e/2),g=[],h=0;c>h;h++)for(var i=0;b>i;i++){for(var j=h*b+i,k=0,l=0;e>l;l++)for(var m=0;e>m;m++){var n=h+l-f,o=i+m-f;if(n>=0&&c>n&&o>=0&&b>o){var p=n*b+o,q=d[l*e+m];k+=a[p]*q}}g[j]=k}return g}Kinetic.Filters.Mask=function(a){var b=this.threshold(),c=d(a,b);return c&&(c=f(c,a.width,a.height),c=g(c,a.width,a.height),c=h(c,a.width,a.height),e(a,c)),a},Kinetic.Factory.addGetterSetter(Kinetic.Node,"threshold",0,null,Kinetic.Factory.afterSetFilter)}(),function(){Kinetic.Filters.RGB=function(a){var b,c,d=a.data,e=d.length,f=this.red(),g=this.green(),h=this.blue();for(b=0;e>b;b+=4)c=(.34*d[b]+.5*d[b+1]+.16*d[b+2])/255,d[b]=c*f,d[b+1]=c*g,d[b+2]=c*h,d[b+3]=d[b+3]},Kinetic.Factory.addGetterSetter(Kinetic.Node,"red",0,function(a){return this._filterUpToDate=!1,a>255?255:0>a?0:Math.round(a)}),Kinetic.Factory.addGetterSetter(Kinetic.Node,"green",0,function(a){return this._filterUpToDate=!1,a>255?255:0>a?0:Math.round(a)}),Kinetic.Factory.addGetterSetter(Kinetic.Node,"blue",0,Kinetic.Validators.RGBComponent,Kinetic.Factory.afterSetFilter)}(),function(){Kinetic.Filters.HSV=function(a){var b,c,d,e,f,g=a.data,h=g.length,i=Math.pow(2,this.value()),j=Math.pow(2,this.saturation()),k=Math.abs(this.hue()+360)%360,l=i*j*Math.cos(k*Math.PI/180),m=i*j*Math.sin(k*Math.PI/180),n=.299*i+.701*l+.167*m,o=.587*i-.587*l+.33*m,p=.114*i-.114*l-.497*m,q=.299*i-.299*l-.328*m,r=.587*i+.413*l+.035*m,s=.114*i-.114*l+.293*m,t=.299*i-.3*l+1.25*m,u=.587*i-.586*l-1.05*m,v=.114*i+.886*l-.2*m;for(b=0;h>b;b+=4)c=g[b+0],d=g[b+1],e=g[b+2],f=g[b+3],g[b+0]=n*c+o*d+p*e,g[b+1]=q*c+r*d+s*e,g[b+2]=t*c+u*d+v*e,g[b+3]=f},Kinetic.Factory.addGetterSetter(Kinetic.Node,"hue",0,null,Kinetic.Factory.afterSetFilter),Kinetic.Factory.addGetterSetter(Kinetic.Node,"saturation",0,null,Kinetic.Factory.afterSetFilter),Kinetic.Factory.addGetterSetter(Kinetic.Node,"value",0,null,Kinetic.Factory.afterSetFilter)}(),function(){Kinetic.Factory.addGetterSetter(Kinetic.Node,"hue",0,null,Kinetic.Factory.afterSetFilter),Kinetic.Factory.addGetterSetter(Kinetic.Node,"saturation",0,null,Kinetic.Factory.afterSetFilter),Kinetic.Factory.addGetterSetter(Kinetic.Node,"luminance",0,null,Kinetic.Factory.afterSetFilter),Kinetic.Filters.HSL=function(a){var b,c,d,e,f,g=a.data,h=g.length,i=1,j=Math.pow(2,this.saturation()),k=Math.abs(this.hue()+360)%360,l=127*this.luminance(),m=i*j*Math.cos(k*Math.PI/180),n=i*j*Math.sin(k*Math.PI/180),o=.299*i+.701*m+.167*n,p=.587*i-.587*m+.33*n,q=.114*i-.114*m-.497*n,r=.299*i-.299*m-.328*n,s=.587*i+.413*m+.035*n,t=.114*i-.114*m+.293*n,u=.299*i-.3*m+1.25*n,v=.587*i-.586*m-1.05*n,w=.114*i+.886*m-.2*n;for(b=0;h>b;b+=4)c=g[b+0],d=g[b+1],e=g[b+2],f=g[b+3],g[b+0]=o*c+p*d+q*e+l,g[b+1]=r*c+s*d+t*e+l,g[b+2]=u*c+v*d+w*e+l,g[b+3]=f}}(),function(){Kinetic.Filters.Emboss=function(a){var b=10*this.embossStrength(),c=255*this.embossWhiteLevel(),d=this.embossDirection(),e=this.embossBlend(),f=0,g=0,h=a.data,i=a.width,j=a.height,k=4*i,l=j;switch(d){case"top-left":f=-1,g=-1;break;case"top":f=-1,g=0;break;case"top-right":f=-1,g=1;break;case"right":f=0,g=1;break;case"bottom-right":f=1,g=1;break;case"bottom":f=1,g=0;break;case"bottom-left":f=1,g=-1;break;case"left":f=0,g=-1}do{var m=(l-1)*k,n=f;1>l+n&&(n=0),l+n>j&&(n=0);var o=(l-1+n)*i*4,p=i;do{var q=m+4*(p-1),r=g;1>p+r&&(r=0),p+r>i&&(r=0);var s=o+4*(p-1+r),t=h[q]-h[s],u=h[q+1]-h[s+1],v=h[q+2]-h[s+2],w=t,x=w>0?w:-w,y=u>0?u:-u,z=v>0?v:-v;if(y>x&&(w=u),z>x&&(w=v),w*=b,e){var A=h[q]+w,B=h[q+1]+w,C=h[q+2]+w;h[q]=A>255?255:0>A?0:A,h[q+1]=B>255?255:0>B?0:B,h[q+2]=C>255?255:0>C?0:C}else{var D=c-w;0>D?D=0:D>255&&(D=255),h[q]=h[q+1]=h[q+2]=D}}while(--p)}while(--l)},Kinetic.Factory.addGetterSetter(Kinetic.Node,"embossStrength",.5,null,Kinetic.Factory.afterSetFilter),Kinetic.Factory.addGetterSetter(Kinetic.Node,"embossWhiteLevel",.5,null,Kinetic.Factory.afterSetFilter),Kinetic.Factory.addGetterSetter(Kinetic.Node,"embossDirection","top-left",null,Kinetic.Factory.afterSetFilter),Kinetic.Factory.addGetterSetter(Kinetic.Node,"embossBlend",!1,null,Kinetic.Factory.afterSetFilter)}(),function(){function a(a,b,c,d,e){var f,g=c-b,h=e-d;return 0===g?d+h/2:0===h?d:(f=(a-b)/g,f=h*f+d)}Kinetic.Filters.Enhance=function(b){var c,d,e,f,g=b.data,h=g.length,i=g[0],j=i,k=g[1],l=k,m=g[2],n=m,o=this.enhance();if(0!==o){for(f=0;h>f;f+=4)c=g[f+0],i>c?i=c:c>j&&(j=c),d=g[f+1],k>d?k=d:d>l&&(l=d),e=g[f+2],m>e?m=e:e>n&&(n=e);j===i&&(j=255,i=0),l===k&&(l=255,k=0),n===m&&(n=255,m=0);var p,q,r,s,t,u,v,w,x;for(o>0?(q=j+o*(255-j),r=i-o*(i-0),t=l+o*(255-l),u=k-o*(k-0),w=n+o*(255-n),x=m-o*(m-0)):(p=.5*(j+i),q=j+o*(j-p),r=i+o*(i-p),s=.5*(l+k),t=l+o*(l-s),u=k+o*(k-s),v=.5*(n+m),w=n+o*(n-v),x=m+o*(m-v)),f=0;h>f;f+=4)g[f+0]=a(g[f+0],i,j,r,q),g[f+1]=a(g[f+1],k,l,u,t),g[f+2]=a(g[f+2],m,n,x,w)}},Kinetic.Factory.addGetterSetter(Kinetic.Node,"enhance",0,null,Kinetic.Factory.afterSetFilter)}(),function(){Kinetic.Filters.Posterize=function(a){var b,c=Math.round(254*this.levels())+1,d=a.data,e=d.length,f=255/c;for(b=0;e>b;b+=1)d[b]=Math.floor(d[b]/f)*f},Kinetic.Factory.addGetterSetter(Kinetic.Node,"levels",.5,null,Kinetic.Factory.afterSetFilter)}(),function(){Kinetic.Filters.Noise=function(a){var b,c=255*this.noise(),d=a.data,e=d.length,f=c/2;for(b=0;e>b;b+=4)d[b+0]+=f-2*f*Math.random(),d[b+1]+=f-2*f*Math.random(),d[b+2]+=f-2*f*Math.random()},Kinetic.Factory.addGetterSetter(Kinetic.Node,"noise",.2,null,Kinetic.Factory.afterSetFilter)}(),function(){Kinetic.Filters.Pixelate=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p=Math.ceil(this.pixelSize()),q=a.width,r=a.height,s=Math.ceil(q/p),t=Math.ceil(r/p);for(a=a.data,m=0;s>m;m+=1)for(n=0;t>n;n+=1){for(e=0,f=0,g=0,h=0,i=m*p,j=i+p,k=n*p,l=k+p,o=0,b=i;j>b;b+=1)if(!(b>=q))for(c=k;l>c;c+=1)c>=r||(d=4*(q*c+b),e+=a[d+0],f+=a[d+1],g+=a[d+2],h+=a[d+3],o+=1);for(e/=o,f/=o,g/=o,b=i;j>b;b+=1)if(!(b>=q))for(c=k;l>c;c+=1)c>=r||(d=4*(q*c+b),a[d+0]=e,a[d+1]=f,a[d+2]=g,a[d+3]=h)}},Kinetic.Factory.addGetterSetter(Kinetic.Node,"pixelSize",8,null,Kinetic.Factory.afterSetFilter)}(),function(){Kinetic.Filters.Threshold=function(a){var b,c=255*this.threshold(),d=a.data,e=d.length;for(b=0;e>b;b+=1)d[b]=d[b]255?255:h,k[d+1]=i>255?255:i,k[d+2]=j>255?255:j,k[d+3]=k[d+3];while(--c)}while(--m)}}(),function(){Kinetic.Filters.Solarize=function(a){var b=a.data,c=a.width,d=a.height,e=4*c,f=d;do{var g=(f-1)*e,h=c;do{var i=g+4*(h-1),j=b[i],k=b[i+1],l=b[i+2];j>127&&(j=255-j),k>127&&(k=255-k),l>127&&(l=255-l),b[i]=j,b[i+1]=k,b[i+2]=l}while(--h)}while(--f)}}(),function(){var a=function(a,b,c){var d,e,f,g,h=a.data,i=b.data,j=a.width,k=a.height,l=c.polarCenterX||j/2,m=c.polarCenterY||k/2,n=0,o=0,p=0,q=0,r=Math.sqrt(l*l+m*m);e=j-l,f=k-m,g=Math.sqrt(e*e+f*f),r=g>r?g:r;var s,t,u,v,w=k,x=j,y=360/x*Math.PI/180;for(t=0;x>t;t+=1)for(u=Math.sin(t*y),v=Math.cos(t*y),s=0;w>s;s+=1)e=Math.floor(l+r*s/w*v),f=Math.floor(m+r*s/w*u),d=4*(f*j+e),n=h[d+0],o=h[d+1],p=h[d+2],q=h[d+3],d=4*(t+s*j),i[d+0]=n,i[d+1]=o,i[d+2]=p,i[d+3]=q},b=function(a,b,c){var d,e,f,g,h,i,j=a.data,k=b.data,l=a.width,m=a.height,n=c.polarCenterX||l/2,o=c.polarCenterY||m/2,p=0,q=0,r=0,s=0,t=Math.sqrt(n*n+o*o);e=l-n,f=m-o,i=Math.sqrt(e*e+f*f),t=i>t?i:t;var u,v,w,x,y=m,z=l,A=c.polarRotation||0;for(e=0;l>e;e+=1)for(f=0;m>f;f+=1)g=e-n,h=f-o,u=Math.sqrt(g*g+h*h)*y/t,v=(180*Math.atan2(h,g)/Math.PI+360+A)%360,v=v*z/360,w=Math.floor(v),x=Math.floor(u),d=4*(x*l+w),p=j[d+0],q=j[d+1],r=j[d+2],s=j[d+3],d=4*(f*l+e),k[d+0]=p,k[d+1]=q,k[d+2]=r,k[d+3]=s},c=Kinetic.Util.createCanvasElement();Kinetic.Filters.Kaleidoscope=function(d){var e,f,g,h,i,j,k,l,m,n,o=d.width,p=d.height,q=Math.round(this.kaleidoscopePower()),r=Math.round(this.kaleidoscopeAngle()),s=Math.floor(o*(r%360)/360);if(!(1>q)){c.width=o,c.height=p;var t=c.getContext("2d").getImageData(0,0,o,p);a(d,t,{polarCenterX:o/2,polarCenterY:p/2});for(var u=o/Math.pow(2,q);8>=u;)u=2*u,q-=1;u=Math.ceil(u);var v=u,w=0,x=v,y=1;for(s+u>o&&(w=v,x=0,y=-1),f=0;p>f;f+=1)for(e=w;e!==x;e+=y)g=Math.round(e+s)%o,m=4*(o*f+g),i=t.data[m+0],j=t.data[m+1],k=t.data[m+2],l=t.data[m+3],n=4*(o*f+e),t.data[n+0]=i,t.data[n+1]=j,t.data[n+2]=k,t.data[n+3]=l;for(f=0;p>f;f+=1)for(v=Math.floor(u),h=0;q>h;h+=1){for(e=0;v+1>e;e+=1)m=4*(o*f+e),i=t.data[m+0],j=t.data[m+1],k=t.data[m+2],l=t.data[m+3],n=4*(o*f+2*v-e-1),t.data[n+0]=i,t.data[n+1]=j,t.data[n+2]=k,t.data[n+3]=l;v*=2}b(t,d,{polarRotation:0})}},Kinetic.Factory.addGetterSetter(Kinetic.Node,"kaleidoscopePower",2,null,Kinetic.Factory.afterSetFilter),Kinetic.Factory.addGetterSetter(Kinetic.Node,"kaleidoscopeAngle",0,null,Kinetic.Factory.afterSetFilter)}(),function(){function a(a){setTimeout(a,1e3/60)}function b(){return e.apply(Kinetic.root,arguments)}var c=500,d=function(){return Kinetic.root.performance&&Kinetic.root.performance.now?function(){return Kinetic.root.performance.now()}:function(){return(new Date).getTime()}}(),e=function(){return Kinetic.root.requestAnimationFrame||Kinetic.root.webkitRequestAnimationFrame||Kinetic.root.mozRequestAnimationFrame||Kinetic.root.oRequestAnimationFrame||Kinetic.root.msRequestAnimationFrame||a}();Kinetic.Animation=function(a,b){var c=Kinetic.Animation;this.func=a,this.setLayers(b),this.id=c.animIdCounter++,this.frame={time:0,timeDiff:0,lastTime:d()}},Kinetic.Animation.prototype={setLayers:function(a){var b=[];b=a?a.length>0?a:[a]:[],this.layers=b},getLayers:function(){return this.layers},addLayer:function(a){var b,c,d=this.layers;if(d){for(b=d.length,c=0;b>c;c++)if(d[c]._id===a._id)return!1}else this.layers=[];return this.layers.push(a),!0},isRunning:function(){var a,b=Kinetic.Animation,c=b.animations,d=c.length;for(a=0;d>a;a++)if(c[a].id===this.id)return!0;return!1},start:function(){var a=Kinetic.Animation;this.stop(),this.frame.timeDiff=0,this.frame.lastTime=d(),a._addAnimation(this)},stop:function(){Kinetic.Animation._removeAnimation(this)},_updateFrameObject:function(a){this.frame.timeDiff=a-this.frame.lastTime,this.frame.lastTime=a,this.frame.time+=this.frame.timeDiff,this.frame.frameRate=1e3/this.frame.timeDiff}},Kinetic.Animation.animations=[],Kinetic.Animation.animIdCounter=0,Kinetic.Animation.animRunning=!1,Kinetic.Animation._addAnimation=function(a){this.animations.push(a),this._handleAnimation()},Kinetic.Animation._removeAnimation=function(a){var b,c=a.id,d=this.animations,e=d.length;for(b=0;e>b;b++)if(d[b].id===c){this.animations.splice(b,1);break}},Kinetic.Animation._runFrames=function(){var a,b,c,e,f,g,h,i,j,k={},l=this.animations;for(e=0;ef;f++)h=b[f],void 0!==h._id&&(k[h._id]=h);for(i in k)k[i].draw()},Kinetic.Animation._animationLoop=function(){var a=Kinetic.Animation;a.animations.length?(b(a._animationLoop),a._runFrames()):a.animRunning=!1},Kinetic.Animation._handleAnimation=function(){var a=this;this.animRunning||(this.animRunning=!0,a._animationLoop())};var f=Kinetic.Node.prototype.moveTo;Kinetic.Node.prototype.moveTo=function(a){f.call(this,a)},Kinetic.BaseLayer.prototype.batchDraw=function(){var a=this,b=Kinetic.Animation;this.batchAnim||(this.batchAnim=new b(function(){a.lastBatchDrawTime&&d()-a.lastBatchDrawTime>c&&a.batchAnim.stop()},this)),this.lastBatchDrawTime=d(),this.batchAnim.isRunning()||(this.draw(),this.batchAnim.start())},Kinetic.Stage.prototype.batchDraw=function(){this.getChildren().each(function(a){a.batchDraw()})}}(this),function(){var a={node:1,duration:1,easing:1,onFinish:1,yoyo:1},b=1,c=2,d=3,e=0;Kinetic.Tween=function(b){var c,d,g=this,h=b.node,i=h._id,j=b.easing||Kinetic.Easings.Linear,k=!!b.yoyo;c="undefined"==typeof b.duration?1:0===b.duration?.001:b.duration,this.node=h,this._id=e++,this.anim=new Kinetic.Animation(function(){g.tween.onEnterFrame()},h.getLayer()||(h instanceof Kinetic.Stage?h.getLayers():null)),this.tween=new f(d,function(a){g._tweenFunc(a)},j,0,1,1e3*c,k),this._addListeners(),Kinetic.Tween.attrs[i]||(Kinetic.Tween.attrs[i]={}),Kinetic.Tween.attrs[i][this._id]||(Kinetic.Tween.attrs[i][this._id]={}),Kinetic.Tween.tweens[i]||(Kinetic.Tween.tweens[i]={});for(d in b)void 0===a[d]&&this._addAttr(d,b[d]);this.reset(),this.onFinish=b.onFinish,this.onReset=b.onReset},Kinetic.Tween.attrs={},Kinetic.Tween.tweens={},Kinetic.Tween.prototype={_addAttr:function(a,b){var c,d,e,f,g,h=this.node,i=h._id;if(e=Kinetic.Tween.tweens[i][a],e&&delete Kinetic.Tween.attrs[i][e][a],c=h.getAttr(a),Kinetic.Util._isArray(b))for(d=[],g=b.length,f=0;g>f;f++)d.push(b[f]-c[f]);else d=b-c;Kinetic.Tween.attrs[i][this._id][a]={start:c,diff:d},Kinetic.Tween.tweens[i][a]=this._id},_tweenFunc:function(a){var b,c,d,e,f,g,h,i=this.node,j=Kinetic.Tween.attrs[i._id][this._id];for(b in j){if(c=j[b],d=c.start,e=c.diff,Kinetic.Util._isArray(d))for(f=[],h=d.length,g=0;h>g;g++)f.push(d[g]+e[g]*a);else f=d+e*a;i.setAttr(b,f)}},_addListeners:function(){var a=this;this.tween.onPlay=function(){a.anim.start()},this.tween.onReverse=function(){a.anim.start()},this.tween.onPause=function(){a.anim.stop()},this.tween.onFinish=function(){a.onFinish&&a.onFinish()},this.tween.onReset=function(){a.onReset&&a.onReset()}},play:function(){return this.tween.play(),this},reverse:function(){return this.tween.reverse(),this},reset:function(){return this.tween.reset(),this},seek:function(a){return this.tween.seek(1e3*a),this},pause:function(){return this.tween.pause(),this},finish:function(){return this.tween.finish(),this},destroy:function(){var a,b=this.node._id,c=this._id,d=Kinetic.Tween.tweens[b];this.pause();for(a in d)delete Kinetic.Tween.tweens[b][a];delete Kinetic.Tween.attrs[b][c]}};var f=function(a,b,c,d,e,f,g){this.prop=a,this.propFunc=b,this.begin=d,this._pos=d,this.duration=f,this._change=0,this.prevPos=0,this.yoyo=g,this._time=0,this._position=0,this._startTime=0,this._finish=0,this.func=c,this._change=e-this.begin,this.pause()};f.prototype={fire:function(a){var b=this[a];b&&b()},setTime:function(a){a>this.duration?this.yoyo?(this._time=this.duration,this.reverse()):this.finish():0>a?this.yoyo?(this._time=0,this.play()):this.reset():(this._time=a,this.update())},getTime:function(){return this._time},setPosition:function(a){this.prevPos=this._pos,this.propFunc(a),this._pos=a},getPosition:function(a){return void 0===a&&(a=this._time),this.func(a,this.begin,this._change,this.duration)},play:function(){this.state=c,this._startTime=this.getTimer()-this._time,this.onEnterFrame(),this.fire("onPlay")},reverse:function(){this.state=d,this._time=this.duration-this._time,this._startTime=this.getTimer()-this._time,this.onEnterFrame(),this.fire("onReverse")},seek:function(a){this.pause(),this._time=a,this.update(),this.fire("onSeek")},reset:function(){this.pause(),this._time=0,this.update(),this.fire("onReset")},finish:function(){this.pause(),this._time=this.duration,this.update(),this.fire("onFinish")},update:function(){this.setPosition(this.getPosition(this._time))},onEnterFrame:function(){var a=this.getTimer()-this._startTime;this.state===c?this.setTime(a):this.state===d&&this.setTime(this.duration-a)},pause:function(){this.state=b,this.fire("onPause")},getTimer:function(){return(new Date).getTime()}},Kinetic.Easings={BackEaseIn:function(a,b,c,d){var e=1.70158;return c*(a/=d)*a*((e+1)*a-e)+b},BackEaseOut:function(a,b,c,d){var e=1.70158;return c*((a=a/d-1)*a*((e+1)*a+e)+1)+b},BackEaseInOut:function(a,b,c,d){var e=1.70158;return(a/=d/2)<1?c/2*a*a*(((e*=1.525)+1)*a-e)+b:c/2*((a-=2)*a*(((e*=1.525)+1)*a+e)+2)+b},ElasticEaseIn:function(a,b,c,d,e,f){var g=0;return 0===a?b:1==(a/=d)?b+c:(f||(f=.3*d),!e||ea?-.5*e*Math.pow(2,10*(a-=1))*Math.sin(2*(a*d-g)*Math.PI/f)+b:e*Math.pow(2,-10*(a-=1))*Math.sin(2*(a*d-g)*Math.PI/f)*.5+c+b)},BounceEaseOut:function(a,b,c,d){return(a/=d)<1/2.75?7.5625*c*a*a+b:2/2.75>a?c*(7.5625*(a-=1.5/2.75)*a+.75)+b:2.5/2.75>a?c*(7.5625*(a-=2.25/2.75)*a+.9375)+b:c*(7.5625*(a-=2.625/2.75)*a+.984375)+b},BounceEaseIn:function(a,b,c,d){return c-Kinetic.Easings.BounceEaseOut(d-a,0,c,d)+b},BounceEaseInOut:function(a,b,c,d){return d/2>a?.5*Kinetic.Easings.BounceEaseIn(2*a,0,c,d)+b:.5*Kinetic.Easings.BounceEaseOut(2*a-d,0,c,d)+.5*c+b},EaseIn:function(a,b,c,d){return c*(a/=d)*a+b},EaseOut:function(a,b,c,d){return-c*(a/=d)*(a-2)+b},EaseInOut:function(a,b,c,d){return(a/=d/2)<1?c/2*a*a+b:-c/2*(--a*(a-2)-1)+b},StrongEaseIn:function(a,b,c,d){return c*(a/=d)*a*a*a*a+b},StrongEaseOut:function(a,b,c,d){return c*((a=a/d-1)*a*a*a*a+1)+b},StrongEaseInOut:function(a,b,c,d){return(a/=d/2)<1?c/2*a*a*a*a*a+b:c/2*((a-=2)*a*a*a*a+2)+b},Linear:function(a,b,c,d){return c*a/d+b}}}(),function(){Kinetic.DD={anim:new Kinetic.Animation(function(){var a=this.dirty;return this.dirty=!1,a}),isDragging:!1,justDragged:!1,offset:{x:0,y:0},node:null,_drag:function(a){var b=Kinetic.DD,c=b.node;if(c){if(!b.isDragging){var d=c.getStage().getPointerPosition(),e=c.dragDistance(),f=Math.max(Math.abs(d.x-b.startPointerPos.x),Math.abs(d.y-b.startPointerPos.y));if(e>f)return}c._setDragPosition(a),b.isDragging||(b.isDragging=!0,c.fire("dragstart",{type:"dragstart",target:c,evt:a},!0)),c.fire("dragmove",{type:"dragmove",target:c,evt:a},!0)}},_endDragBefore:function(a){var b,c,d=Kinetic.DD,e=d.node;e&&(b=e.nodeType,c=e.getLayer(),d.anim.stop(),d.isDragging&&(d.isDragging=!1,d.justDragged=!0,Kinetic.listenClickTap=!1,a&&(a.dragEndNode=e)),delete d.node,(c||e).draw())},_endDragAfter:function(a){a=a||{};var b=a.dragEndNode;a&&b&&b.fire("dragend",{type:"dragend",target:b,evt:a},!0)}},Kinetic.Node.prototype.startDrag=function(){var a=Kinetic.DD,b=this.getStage(),c=this.getLayer(),d=b.getPointerPosition(),e=this.getAbsolutePosition();d&&(a.node&&a.node.stopDrag(),a.node=this,a.startPointerPos=d,a.offset.x=d.x-e.x,a.offset.y=d.y-e.y,a.anim.setLayers(c||this.getLayers()),a.anim.start(),this._setDragPosition())},Kinetic.Node.prototype._setDragPosition=function(a){var b=Kinetic.DD,c=this.getStage().getPointerPosition(),d=this.getDragBoundFunc();if(c){var e={x:c.x-b.offset.x,y:c.y-b.offset.y};void 0!==d&&(e=d.call(this,e,a)),this.setAbsolutePosition(e),this._lastPos&&this._lastPos.x===e.x&&this._lastPos.y===e.y||(b.anim.dirty=!0),this._lastPos=e}},Kinetic.Node.prototype.stopDrag=function(){var a=Kinetic.DD,b={};a._endDragBefore(b),a._endDragAfter(b)},Kinetic.Node.prototype.setDraggable=function(a){this._setAttr("draggable",a),this._dragChange()};var a=Kinetic.Node.prototype.destroy;Kinetic.Node.prototype.destroy=function(){var b=Kinetic.DD;b.node&&b.node._id===this._id&&this.stopDrag(),a.call(this)},Kinetic.Node.prototype.isDragging=function(){var a=Kinetic.DD;return!(!a.node||a.node._id!==this._id||!a.isDragging)},Kinetic.Node.prototype._listenDrag=function(){var a=this;this._dragCleanup(),"Stage"===this.getClassName()?this.on("contentMousedown.kinetic contentTouchstart.kinetic",function(b){Kinetic.DD.node||a.startDrag(b)}):this.on("mousedown.kinetic touchstart.kinetic",function(b){1!==b.evt.button&&2!==b.evt.button&&(Kinetic.DD.node||a.startDrag(b))})},Kinetic.Node.prototype._dragChange=function(){if(this.attrs.draggable)this._listenDrag();else{this._dragCleanup();var a=this.getStage(),b=Kinetic.DD;a&&b.node&&b.node._id===this._id&&b.node.stopDrag()}},Kinetic.Node.prototype._dragCleanup=function(){"Stage"===this.getClassName()?(this.off("contentMousedown.kinetic"),this.off("contentTouchstart.kinetic")):(this.off("mousedown.kinetic"),this.off("touchstart.kinetic"))},Kinetic.Factory.addGetterSetter(Kinetic.Node,"dragBoundFunc"),Kinetic.Factory.addGetter(Kinetic.Node,"draggable",!1),Kinetic.Factory.addOverloadedGetterSetter(Kinetic.Node,"draggable");var b=Kinetic.document.documentElement;b.addEventListener("mouseup",Kinetic.DD._endDragBefore,!0),b.addEventListener("touchend",Kinetic.DD._endDragBefore,!0),b.addEventListener("mouseup",Kinetic.DD._endDragAfter,!1),b.addEventListener("touchend",Kinetic.DD._endDragAfter,!1)}(),function(){Kinetic.Util.addMethods(Kinetic.Container,{__init:function(a){this.children=new Kinetic.Collection,Kinetic.Node.call(this,a)},getChildren:function(a){if(a){var b=new Kinetic.Collection;return this.children.each(function(c){a(c)&&b.push(c)}),b}return this.children},hasChildren:function(){return this.getChildren().length>0},removeChildren:function(){for(var a,b=Kinetic.Collection.toCollection(this.children),c=0;c1){for(var b=0;bb;b++)if(d=j[b],"#"===d.charAt(0))f=this._getNodeById(d.slice(1)),f&&i.push(f);else if("."===d.charAt(0))e=this._getNodesByName(d.slice(1)),i=i.concat(e);else for(g=this.getChildren(),h=g.length,c=0;h>c;c++)i=i.concat(g[c]._get(d));return Kinetic.Collection.toCollection(i)},_getNodeById:function(a){var b=Kinetic.ids[a];return void 0!==b&&this.isAncestorOf(b)?b:null},_getNodesByName:function(a){var b=Kinetic.names[a]||[];return this._getDescendants(b)},_get:function(a){for(var b=Kinetic.Node.prototype._get.call(this,a),c=this.getChildren(),d=c.length,e=0;d>e;e++)b=b.concat(c[e]._get(a));return b},toObject:function(){var a=Kinetic.Node.prototype.toObject.call(this); a.children=[];for(var b=this.getChildren(),c=b.length,d=0;c>d;d++){var e=b[d];a.children.push(e.toObject())}return a},_getDescendants:function(a){for(var b=[],c=a.length,d=0;c>d;d++){var e=a[d];this.isAncestorOf(e)&&b.push(e)}return b},isAncestorOf:function(a){for(var b=a.getParent();b;){if(b._id===this._id)return!0;b=b.getParent()}return!1},clone:function(a){var b=Kinetic.Node.prototype.clone.call(this,a);return this.getChildren().each(function(a){b.add(a.clone())}),b},getAllIntersections:function(a){var b=[];return this.find("Shape").each(function(c){c.isVisible()&&c.intersects(a)&&b.push(c)}),b},_setChildrenIndices:function(){this.children.each(function(a,b){a.index=b})},drawScene:function(a,b){var c=this.getLayer(),d=a||c&&c.getCanvas(),e=d&&d.getContext(),f=this._cache.canvas,g=f&&f.scene;return this.isVisible()&&(g?this._drawCachedSceneCanvas(e):this._drawChildren(d,"drawScene",b)),this},drawHit:function(a,b){var c=this.getLayer(),d=a||c&&c.hitCanvas,e=d&&d.getContext(),f=this._cache.canvas,g=f&&f.hit;return this.shouldDrawHit(d)&&(c&&c.clearHitCache(),g?this._drawCachedHitCanvas(e):this._drawChildren(d,"drawHit",b)),this},_drawChildren:function(a,b,c){var d,e,f=this.getLayer(),g=a&&a.getContext(),h=this.getClipWidth(),i=this.getClipHeight(),j=h&&i;j&&f&&(d=this.getClipX(),e=this.getClipY(),g.save(),f._applyTransform(this,g),g.beginPath(),g.rect(d,e,h,i),g.clip(),g.reset()),this.children.each(function(d){d[b](a,c)}),j&&g.restore()},shouldDrawHit:function(a){var b=this.getLayer(),c=Kinetic.DD,d=c&&Kinetic.isDragging()&&-1!==Kinetic.DD.anim.getLayers().indexOf(b);return a&&a.isCache||b&&b.hitGraphEnabled()&&this.isVisible()&&!d}}),Kinetic.Util.extend(Kinetic.Container,Kinetic.Node),Kinetic.Container.prototype.get=Kinetic.Container.prototype.find,Kinetic.Factory.addComponentsGetterSetter(Kinetic.Container,"clip",["x","y","width","height"]),Kinetic.Factory.addGetterSetter(Kinetic.Container,"clipX"),Kinetic.Factory.addGetterSetter(Kinetic.Container,"clipY"),Kinetic.Factory.addGetterSetter(Kinetic.Container,"clipWidth"),Kinetic.Factory.addGetterSetter(Kinetic.Container,"clipHeight"),Kinetic.Collection.mapMethods(Kinetic.Container)}(),function(){function a(a){a.fill()}function b(a){a.stroke()}function c(a){a.fill()}function d(a){a.stroke()}function e(){this._clearCache(f)}var f="hasShadow";Kinetic.Util.addMethods(Kinetic.Shape,{__init:function(f){this.nodeType="Shape",this._fillFunc=a,this._strokeFunc=b,this._fillFuncHit=c,this._strokeFuncHit=d;for(var g,h=Kinetic.shapes;;)if(g=Kinetic.Util.getRandomColor(),g&&!(g in h))break;this.colorKey=g,h[g]=this,Kinetic.Node.call(this,f),this.on("shadowColorChange.kinetic shadowBlurChange.kinetic shadowOffsetChange.kinetic shadowOpacityChange.kinetic shadowEnabledChange.kinetic",e)},hasChildren:function(){return!1},getChildren:function(){return[]},getContext:function(){return this.getLayer().getContext()},getCanvas:function(){return this.getLayer().getCanvas()},hasShadow:function(){return this._getCache(f,this._hasShadow)},_hasShadow:function(){return this.getShadowEnabled()&&0!==this.getShadowOpacity()&&!!(this.getShadowColor()||this.getShadowBlur()||this.getShadowOffsetX()||this.getShadowOffsetY())},hasFill:function(){return!!(this.getFill()||this.getFillPatternImage()||this.getFillLinearGradientColorStops()||this.getFillRadialGradientColorStops())},hasStroke:function(){return!!(this.stroke()||this.strokeRed()||this.strokeGreen()||this.strokeBlue())},intersects:function(a){var b,c=this.getStage(),d=c.bufferHitCanvas;return d.getContext().clear(),this.drawScene(d),b=d.context.getImageData(Math.round(a.x),Math.round(a.y),1,1).data,b[3]>0},destroy:function(){Kinetic.Node.prototype.destroy.call(this),delete Kinetic.shapes[this.colorKey]},_useBufferCanvas:function(){return(this.hasShadow()||1!==this.getAbsoluteOpacity())&&this.hasFill()&&this.hasStroke()&&this.getStage()},drawScene:function(a,b){var c,d,e,f=this.getLayer(),g=a||f.getCanvas(),h=g.getContext(),i=this._cache.canvas,j=this.sceneFunc(),k=this.hasShadow();if(this.isVisible())if(i)this._drawCachedSceneCanvas(h);else if(j){if(h.save(),this._useBufferCanvas()){if(c=this.getStage(),d=c.bufferCanvas,e=d.getContext(),e.clear(),e.save(),e._applyLineJoin(this),f)f._applyTransform(this,e,b);else{var l=this.getAbsoluteTransform(b).getMatrix();h.transform(l[0],l[1],l[2],l[3],l[4],l[5])}j.call(this,e),e.restore(),k&&!g.hitCanvas&&(h.save(),h._applyShadow(this),h.drawImage(d._canvas,0,0),h.restore()),h._applyOpacity(this),h.drawImage(d._canvas,0,0)}else{if(h._applyLineJoin(this),f)f._applyTransform(this,h,b);else{var m=this.getAbsoluteTransform(b).getMatrix();h.transform(m[0],m[1],m[2],m[3],m[4],m[5])}k&&!g.hitCanvas&&(h.save(),h._applyShadow(this),j.call(this,h),h.restore()),h._applyOpacity(this),j.call(this,h)}h.restore()}return this},drawHit:function(a,b){var c=this.getLayer(),d=a||c.hitCanvas,e=d.getContext(),f=this.hitFunc()||this.sceneFunc(),g=this._cache.canvas,h=g&&g.hit;if(this.shouldDrawHit(d))if(c&&c.clearHitCache(),h)this._drawCachedHitCanvas(e);else if(f){if(e.save(),e._applyLineJoin(this),c)c._applyTransform(this,e,b);else{var i=this.getAbsoluteTransform(b).getMatrix();e.transform(i[0],i[1],i[2],i[3],i[4],i[5])}f.call(this,e),e.restore()}return this},drawHitFromCache:function(a){var b,c,d,e,f,g,h,i,j=a||0,k=this._cache.canvas,l=this._getCachedSceneCanvas(),m=l.getContext(),n=k.hit,o=n.getContext(),p=l.getWidth(),q=l.getHeight();o.clear();try{for(b=m.getImageData(0,0,p,q),c=b.data,d=o.getImageData(0,0,p,q),e=d.data,f=c.length,g=Kinetic.Util._hexToRgb(this.colorKey),h=0;f>h;h+=4)i=c[h+3],i>j&&(e[h]=g.r,e[h+1]=g.g,e[h+2]=g.b,e[h+3]=255);o.putImageData(d,0,0)}catch(r){Kinetic.Util.warn("Unable to draw hit graph from cached scene canvas. "+r.message)}return this}}),Kinetic.Util.extend(Kinetic.Shape,Kinetic.Node),Kinetic.Factory.addGetterSetter(Kinetic.Shape,"stroke"),Kinetic.Factory.addGetterSetter(Kinetic.Shape,"strokeRed",0,Kinetic.Validators.RGBComponent),Kinetic.Factory.addGetterSetter(Kinetic.Shape,"strokeGreen",0,Kinetic.Validators.RGBComponent),Kinetic.Factory.addGetterSetter(Kinetic.Shape,"strokeBlue",0,Kinetic.Validators.RGBComponent),Kinetic.Factory.addGetterSetter(Kinetic.Shape,"strokeAlpha",1,Kinetic.Validators.alphaComponent),Kinetic.Factory.addGetterSetter(Kinetic.Shape,"strokeWidth",2),Kinetic.Factory.addGetterSetter(Kinetic.Shape,"lineJoin"),Kinetic.Factory.addGetterSetter(Kinetic.Shape,"lineCap"),Kinetic.Factory.addGetterSetter(Kinetic.Shape,"sceneFunc"),Kinetic.Factory.addGetterSetter(Kinetic.Shape,"hitFunc"),Kinetic.Factory.addGetterSetter(Kinetic.Shape,"dash"),Kinetic.Factory.addGetterSetter(Kinetic.Shape,"shadowColor"),Kinetic.Factory.addGetterSetter(Kinetic.Shape,"shadowRed",0,Kinetic.Validators.RGBComponent),Kinetic.Factory.addGetterSetter(Kinetic.Shape,"shadowGreen",0,Kinetic.Validators.RGBComponent),Kinetic.Factory.addGetterSetter(Kinetic.Shape,"shadowBlue",0,Kinetic.Validators.RGBComponent),Kinetic.Factory.addGetterSetter(Kinetic.Shape,"shadowAlpha",1,Kinetic.Validators.alphaComponent),Kinetic.Factory.addGetterSetter(Kinetic.Shape,"shadowBlur"),Kinetic.Factory.addGetterSetter(Kinetic.Shape,"shadowOpacity"),Kinetic.Factory.addComponentsGetterSetter(Kinetic.Shape,"shadowOffset",["x","y"]),Kinetic.Factory.addGetterSetter(Kinetic.Shape,"shadowOffsetX",0),Kinetic.Factory.addGetterSetter(Kinetic.Shape,"shadowOffsetY",0),Kinetic.Factory.addGetterSetter(Kinetic.Shape,"fillPatternImage"),Kinetic.Factory.addGetterSetter(Kinetic.Shape,"fill"),Kinetic.Factory.addGetterSetter(Kinetic.Shape,"fillRed",0,Kinetic.Validators.RGBComponent),Kinetic.Factory.addGetterSetter(Kinetic.Shape,"fillGreen",0,Kinetic.Validators.RGBComponent),Kinetic.Factory.addGetterSetter(Kinetic.Shape,"fillBlue",0,Kinetic.Validators.RGBComponent),Kinetic.Factory.addGetterSetter(Kinetic.Shape,"fillAlpha",1,Kinetic.Validators.alphaComponent),Kinetic.Factory.addGetterSetter(Kinetic.Shape,"fillPatternX",0),Kinetic.Factory.addGetterSetter(Kinetic.Shape,"fillPatternY",0),Kinetic.Factory.addGetterSetter(Kinetic.Shape,"fillLinearGradientColorStops"),Kinetic.Factory.addGetterSetter(Kinetic.Shape,"fillRadialGradientStartRadius",0),Kinetic.Factory.addGetterSetter(Kinetic.Shape,"fillRadialGradientEndRadius",0),Kinetic.Factory.addGetterSetter(Kinetic.Shape,"fillRadialGradientColorStops"),Kinetic.Factory.addGetterSetter(Kinetic.Shape,"fillPatternRepeat","repeat"),Kinetic.Factory.addGetterSetter(Kinetic.Shape,"fillEnabled",!0),Kinetic.Factory.addGetterSetter(Kinetic.Shape,"strokeEnabled",!0),Kinetic.Factory.addGetterSetter(Kinetic.Shape,"shadowEnabled",!0),Kinetic.Factory.addGetterSetter(Kinetic.Shape,"dashEnabled",!0),Kinetic.Factory.addGetterSetter(Kinetic.Shape,"strokeScaleEnabled",!0),Kinetic.Factory.addGetterSetter(Kinetic.Shape,"fillPriority","color"),Kinetic.Factory.addComponentsGetterSetter(Kinetic.Shape,"fillPatternOffset",["x","y"]),Kinetic.Factory.addGetterSetter(Kinetic.Shape,"fillPatternOffsetX",0),Kinetic.Factory.addGetterSetter(Kinetic.Shape,"fillPatternOffsetY",0),Kinetic.Factory.addComponentsGetterSetter(Kinetic.Shape,"fillPatternScale",["x","y"]),Kinetic.Factory.addGetterSetter(Kinetic.Shape,"fillPatternScaleX",1),Kinetic.Factory.addGetterSetter(Kinetic.Shape,"fillPatternScaleY",1),Kinetic.Factory.addComponentsGetterSetter(Kinetic.Shape,"fillLinearGradientStartPoint",["x","y"]),Kinetic.Factory.addGetterSetter(Kinetic.Shape,"fillLinearGradientStartPointX",0),Kinetic.Factory.addGetterSetter(Kinetic.Shape,"fillLinearGradientStartPointY",0),Kinetic.Factory.addComponentsGetterSetter(Kinetic.Shape,"fillLinearGradientEndPoint",["x","y"]),Kinetic.Factory.addGetterSetter(Kinetic.Shape,"fillLinearGradientEndPointX",0),Kinetic.Factory.addGetterSetter(Kinetic.Shape,"fillLinearGradientEndPointY",0),Kinetic.Factory.addComponentsGetterSetter(Kinetic.Shape,"fillRadialGradientStartPoint",["x","y"]),Kinetic.Factory.addGetterSetter(Kinetic.Shape,"fillRadialGradientStartPointX",0),Kinetic.Factory.addGetterSetter(Kinetic.Shape,"fillRadialGradientStartPointY",0),Kinetic.Factory.addComponentsGetterSetter(Kinetic.Shape,"fillRadialGradientEndPoint",["x","y"]),Kinetic.Factory.addGetterSetter(Kinetic.Shape,"fillRadialGradientEndPointX",0),Kinetic.Factory.addGetterSetter(Kinetic.Shape,"fillRadialGradientEndPointY",0),Kinetic.Factory.addGetterSetter(Kinetic.Shape,"fillPatternRotation",0),Kinetic.Factory.backCompat(Kinetic.Shape,{dashArray:"dash",getDashArray:"getDash",setDashArray:"getDash",drawFunc:"sceneFunc",getDrawFunc:"getSceneFunc",setDrawFunc:"setSceneFunc",drawHitFunc:"hitFunc",getDrawHitFunc:"getHitFunc",setDrawHitFunc:"setHitFunc"}),Kinetic.Collection.mapMethods(Kinetic.Shape)}(),function(){function a(a,b){a.content.addEventListener(b,function(c){a[L+b](c)},!1)}var b="Stage",c="string",d="px",e="mouseout",f="mouseleave",g="mouseover",h="mouseenter",i="mousemove",j="mousedown",k="mouseup",l="click",m="dblclick",n="touchstart",o="touchend",p="tap",q="dbltap",r="touchmove",s="DOMMouseScroll",t="mousewheel",u="wheel",v="contentMouseout",w="contentMouseover",x="contentMousemove",y="contentMousedown",z="contentMouseup",A="contentClick",B="contentDblclick",C="contentTouchstart",D="contentTouchend",E="contentDbltap",F="contentTouchmove",G="div",H="relative",I="inline-block",J="kineticjs-content",K=" ",L="_",M="container",N="",O=[j,i,k,e,n,r,o,g,s,t,u],P=O.length;Kinetic.Util.addMethods(Kinetic.Stage,{___init:function(a){this.nodeType=b,Kinetic.Container.call(this,a),this._id=Kinetic.idCounter++,this._buildDOM(),this._bindContentEvents(),this._enableNestedTransforms=!1,Kinetic.stages.push(this)},_validateAdd:function(a){"Layer"!==a.getType()&&Kinetic.Util.error("You may only add layers to the stage.")},setContainer:function(a){if(typeof a===c){var b=a;if(a=Kinetic.document.getElementById(a),!a)throw"Can not find container in document with id "+b}return this._setAttr(M,a),this},shouldDrawHit:function(){return!0},draw:function(){return Kinetic.Node.prototype.draw.call(this),this},setHeight:function(a){return Kinetic.Node.prototype.setHeight.call(this,a),this._resizeDOM(),this},setWidth:function(a){return Kinetic.Node.prototype.setWidth.call(this,a),this._resizeDOM(),this},clear:function(){var a,b=this.children,c=b.length;for(a=0;c>a;a++)b[a].clear();return this},clone:function(a){return a||(a={}),a.container=Kinetic.document.createElement(G),Kinetic.Container.prototype.clone.call(this,a)},destroy:function(){var a=this.content;Kinetic.Container.prototype.destroy.call(this),a&&Kinetic.Util._isInDocument(a)&&this.getContainer().removeChild(a);var b=Kinetic.stages.indexOf(this);b>-1&&Kinetic.stages.splice(b,1)},getPointerPosition:function(){return this.pointerPos},getStage:function(){return this},getContent:function(){return this.content},toDataURL:function(a){function b(e){var f=i[e],j=f.toDataURL(),k=new Kinetic.window.Image;k.onload=function(){h.drawImage(k,0,0),e=0;b--)if(c=d[b].getIntersection(a))return c;return null},_resizeDOM:function(){if(this.content){var a,b,c=this.getWidth(),e=this.getHeight(),f=this.getChildren(),g=f.length;for(this.content.style.width=c+d,this.content.style.height=e+d,this.bufferCanvas.setSize(c,e),this.bufferHitCanvas.setSize(c,e),a=0;g>a;a++)b=f[a],b.setSize(c,e),b.draw()}},add:function(a){if(!(arguments.length>1))return Kinetic.Container.prototype.add.call(this,a),a._setCanvasSize(this.width(),this.height()),a.draw(),this.content.appendChild(a.canvas._canvas),this;for(var b=0;bb;b++)a(this,O[b])},_mouseover:function(a){Kinetic.UA.mobile||(this._setPointerPosition(a),this._fire(w,{evt:a}))},_mouseout:function(a){if(!Kinetic.UA.mobile){this._setPointerPosition(a);var b=this.targetShape;b&&!Kinetic.isDragging()&&(b._fireAndBubble(e,{evt:a}),b._fireAndBubble(f,{evt:a}),this.targetShape=null),this.pointerPos=void 0,this._fire(v,{evt:a})}},_mousemove:function(a){if(Kinetic.UA.ieMobile)return this._touchmove(a);if(("undefined"==typeof a.webkitMovementX&&"undefined"==typeof a.webkitMovementY||0!==a.webkitMovementY||0!==a.webkitMovementX)&&!Kinetic.UA.mobile){this._setPointerPosition(a);var b,c=Kinetic.DD;Kinetic.isDragging()||(b=this.getIntersection(this.getPointerPosition()),b&&b.isListening()?Kinetic.isDragging()||this.targetShape&&this.targetShape._id===b._id?b._fireAndBubble(i,{evt:a}):(this.targetShape&&(this.targetShape._fireAndBubble(e,{evt:a},b),this.targetShape._fireAndBubble(f,{evt:a},b)),b._fireAndBubble(g,{evt:a},this.targetShape),b._fireAndBubble(h,{evt:a},this.targetShape),this.targetShape=b):this.targetShape&&!Kinetic.isDragging()&&(this.targetShape._fireAndBubble(e,{evt:a}),this.targetShape._fireAndBubble(f,{evt:a}),this.targetShape=null),this._fire(x,{evt:a})),c&&c._drag(a),a.preventDefault&&a.preventDefault()}},_mousedown:function(a){if(Kinetic.UA.ieMobile)return this._touchstart(a);if(!Kinetic.UA.mobile){this._setPointerPosition(a);var b=this.getIntersection(this.getPointerPosition());Kinetic.listenClickTap=!0,b&&b.isListening()&&(this.clickStartShape=b,b._fireAndBubble(j,{evt:a})),this._fire(y,{evt:a})}a.preventDefault&&a.preventDefault()},_mouseup:function(a){if(Kinetic.UA.ieMobile)return this._touchend(a);if(!Kinetic.UA.mobile){this._setPointerPosition(a);var b=this.getIntersection(this.getPointerPosition()),c=this.clickStartShape,d=!1,e=Kinetic.DD;Kinetic.inDblClickWindow?(d=!0,Kinetic.inDblClickWindow=!1):e&&e.justDragged?e&&(e.justDragged=!1):Kinetic.inDblClickWindow=!0,setTimeout(function(){Kinetic.inDblClickWindow=!1},Kinetic.dblClickWindow),b&&b.isListening()&&(b._fireAndBubble(k,{evt:a}),Kinetic.listenClickTap&&c&&c._id===b._id&&(b._fireAndBubble(l,{evt:a}),d&&b._fireAndBubble(m,{evt:a}))),this._fire(z,{evt:a}),Kinetic.listenClickTap&&(this._fire(A,{evt:a}),d&&this._fire(B,{evt:a})),Kinetic.listenClickTap=!1}a.preventDefault&&a.preventDefault()},_touchstart:function(a){this._setPointerPosition(a);var b=this.getIntersection(this.getPointerPosition());Kinetic.listenClickTap=!0,b&&b.isListening()&&(this.tapStartShape=b,b._fireAndBubble(n,{evt:a}),b.isListening()&&a.preventDefault&&a.preventDefault()),this._fire(C,{evt:a})},_touchend:function(a){this._setPointerPosition(a);var b=this.getIntersection(this.getPointerPosition()),c=!1;Kinetic.inDblClickWindow?(c=!0,Kinetic.inDblClickWindow=!1):Kinetic.inDblClickWindow=!0,setTimeout(function(){Kinetic.inDblClickWindow=!1},Kinetic.dblClickWindow),b&&b.isListening()&&(b._fireAndBubble(o,{evt:a}),Kinetic.listenClickTap&&b._id===this.tapStartShape._id&&(b._fireAndBubble(p,{evt:a}),c&&b._fireAndBubble(q,{evt:a})),b.isListening()&&a.preventDefault&&a.preventDefault()),Kinetic.listenClickTap&&(this._fire(D,{evt:a}),c&&this._fire(E,{evt:a})),Kinetic.listenClickTap=!1},_touchmove:function(a){this._setPointerPosition(a);var b,c=Kinetic.DD;Kinetic.isDragging()||(b=this.getIntersection(this.getPointerPosition()),b&&b.isListening()&&(b._fireAndBubble(r,{evt:a}),b.isListening()&&a.preventDefault&&a.preventDefault()),this._fire(F,{evt:a})),c&&(c._drag(a),Kinetic.isDragging()&&a.preventDefault())},_DOMMouseScroll:function(a){this._mousewheel(a)},_mousewheel:function(a){this._setPointerPosition(a);var b=this.getIntersection(this.getPointerPosition());b&&b.isListening()&&b._fireAndBubble(t,{evt:a})},_wheel:function(a){this._mousewheel(a)},_setPointerPosition:function(a){var b,c=this._getContentPosition(),d=a.offsetX,e=a.clientX,f=null,g=null;a=a?a:window.event,void 0!==a.touches?a.touches.length>0&&(b=a.touches[0],f=b.clientX-c.left,g=b.clientY-c.top):void 0!==d?(f=d,g=a.offsetY):"mozilla"===Kinetic.UA.browser?(f=a.layerX,g=a.layerY):void 0!==e&&c&&(f=e-c.left,g=a.clientY-c.top),null!==f&&null!==g&&(this.pointerPos={x:f,y:g})},_getContentPosition:function(){var a=this.content.getBoundingClientRect?this.content.getBoundingClientRect():{top:0,left:0};return{top:a.top,left:a.left}},_buildDOM:function(){var a=this.getContainer();if(!a){if(Kinetic.Util.isBrowser())throw"Stage has no container. A container is required.";a=Kinetic.document.createElement(G)}a.innerHTML=N,this.content=Kinetic.document.createElement(G),this.content.style.position=H,this.content.style.display=I,this.content.className=J,this.content.setAttribute("role","presentation"),a.appendChild(this.content),this.bufferCanvas=new Kinetic.SceneCanvas({pixelRatio:1}),this.bufferHitCanvas=new Kinetic.HitCanvas,this._resizeDOM()},_onContent:function(a,b){var c,d,e=a.split(K),f=e.length;for(c=0;f>c;c++)d=e[c],this.content.addEventListener(d,b,!1)},cache:function(){Kinetic.Util.warn("Cache function is not allowed for stage. You may use cache only for layers, groups and shapes.")},clearCache:function(){}}),Kinetic.Util.extend(Kinetic.Stage,Kinetic.Container),Kinetic.Factory.addGetter(Kinetic.Stage,"container"),Kinetic.Factory.addOverloadedGetterSetter(Kinetic.Stage,"container")}(),function(){Kinetic.Util.addMethods(Kinetic.BaseLayer,{___init:function(a){this.nodeType="Layer",Kinetic.Container.call(this,a)},createPNGStream:function(){return this.canvas._canvas.createPNGStream()},getCanvas:function(){return this.canvas},getHitCanvas:function(){return this.hitCanvas},getContext:function(){return this.getCanvas().getContext()},clear:function(a){return this.getContext().clear(a),this.getHitCanvas().getContext().clear(a),this},clearHitCache:function(){this._hitImageData=void 0},setZIndex:function(a){Kinetic.Node.prototype.setZIndex.call(this,a);var b=this.getStage();return b&&(b.content.removeChild(this.getCanvas()._canvas),ac;c++){if(f=d[c],b=this._getIntersection({x:a.x+f.x*h,y:a.y+f.y*h}),g=b.shape)return g;b.antialiased&&(i=!0)}if(!i)return;h+=1}},_getImageData:function(a,b){var c=this.hitCanvas.width||1,d=this.hitCanvas.height||1,e=Math.round(b)*c+Math.round(a);return this._hitImageData||(this._hitImageData=this.hitCanvas.context.getImageData(0,0,c,d)),[this._hitImageData.data[4*e+0],this._hitImageData.data[4*e+1],this._hitImageData.data[4*e+2],this._hitImageData.data[4*e+3]]},_getIntersection:function(b){var c,d,e=this.hitCanvas.context.getImageData(b.x,b.y,1,1).data,f=e[3];return 255===f?(c=Kinetic.Util._rgbToHex(e[0],e[1],e[2]),d=Kinetic.shapes[a+c],{shape:d}):f>0?{antialiased:!0}:{}},drawScene:function(a,d){var e=this.getLayer(),f=a||e&&e.getCanvas();return this._fire(b,{node:this}),this.getClearBeforeDraw()&&f.getContext().clear(),Kinetic.Container.prototype.drawScene.call(this,f,d),this._fire(c,{node:this}),this},_applyTransform:function(a,b,c){var d=a.getAbsoluteTransform(c).getMatrix();b.transform(d[0],d[1],d[2],d[3],d[4],d[5])},drawHit:function(a,b){var c=this.getLayer(),d=a||c&&c.hitCanvas;return c&&c.getClearBeforeDraw()&&c.getHitCanvas().getContext().clear(),Kinetic.Container.prototype.drawHit.call(this,d,b),this.imageData=null,this},clear:function(a){return this.getContext().clear(a),this.getHitCanvas().getContext().clear(a),this.imageData=null,this},setVisible:function(a){return Kinetic.Node.prototype.setVisible.call(this,a),a?(this.getCanvas()._canvas.style.display="block",this.hitCanvas._canvas.style.display="block"):(this.getCanvas()._canvas.style.display="none",this.hitCanvas._canvas.style.display="none"),this},enableHitGraph:function(){return this.setHitGraphEnabled(!0),this},disableHitGraph:function(){return this.setHitGraphEnabled(!1),this},setSize:function(a,b){Kinetic.BaseLayer.prototype.setSize.call(this,a,b),this.hitCanvas.setSize(a,b)}}),Kinetic.Util.extend(Kinetic.Layer,Kinetic.BaseLayer),Kinetic.Factory.addGetterSetter(Kinetic.Layer,"hitGraphEnabled",!0),Kinetic.Collection.mapMethods(Kinetic.Layer)}(),function(){Kinetic.Util.addMethods(Kinetic.FastLayer,{____init:function(a){this.nodeType="Layer",this.canvas=new Kinetic.SceneCanvas,Kinetic.BaseLayer.call(this,a)},_validateAdd:function(a){var b=a.getType();"Shape"!==b&&Kinetic.Util.error("You may only add shapes to a fast layer.")},_setCanvasSize:function(a,b){this.canvas.setSize(a,b)},hitGraphEnabled:function(){return!1},getIntersection:function(){return null},drawScene:function(a){var b=this.getLayer(),c=a||b&&b.getCanvas();return this.getClearBeforeDraw()&&c.getContext().clear(),Kinetic.Container.prototype.drawScene.call(this,c),this},_applyTransform:function(a,b,c){if(!c||c._id!==this._id){var d=a.getTransform().getMatrix();b.transform(d[0],d[1],d[2],d[3],d[4],d[5])}},draw:function(){return this.drawScene(),this},clear:function(a){return this.getContext().clear(a),this},setVisible:function(a){return Kinetic.Node.prototype.setVisible.call(this,a),this.getCanvas()._canvas.style.display=a?"block":"none",this}}),Kinetic.Util.extend(Kinetic.FastLayer,Kinetic.BaseLayer),Kinetic.Collection.mapMethods(Kinetic.FastLayer)}(),function(){Kinetic.Util.addMethods(Kinetic.Group,{___init:function(a){this.nodeType="Group",Kinetic.Container.call(this,a)},_validateAdd:function(a){var b=a.getType();"Group"!==b&&"Shape"!==b&&Kinetic.Util.error("You may only add groups and shapes to groups.")}}),Kinetic.Util.extend(Kinetic.Group,Kinetic.Container),Kinetic.Collection.mapMethods(Kinetic.Group)}(),function(){Kinetic.Rect=function(a){this.___init(a)},Kinetic.Rect.prototype={___init:function(a){Kinetic.Shape.call(this,a),this.className="Rect",this.sceneFunc(this._sceneFunc)},_sceneFunc:function(a){var b=this.getCornerRadius(),c=this.getWidth(),d=this.getHeight();a.beginPath(),b?(a.moveTo(b,0),a.lineTo(c-b,0),a.arc(c-b,b,b,3*Math.PI/2,0,!1),a.lineTo(c,d-b),a.arc(c-b,d-b,b,0,Math.PI/2,!1),a.lineTo(b,d),a.arc(b,d-b,b,Math.PI/2,Math.PI,!1),a.lineTo(0,b),a.arc(b,b,b,Math.PI,3*Math.PI/2,!1)):a.rect(0,0,c,d),a.closePath(),a.fillStrokeShape(this)}},Kinetic.Util.extend(Kinetic.Rect,Kinetic.Shape),Kinetic.Factory.addGetterSetter(Kinetic.Rect,"cornerRadius",0),Kinetic.Collection.mapMethods(Kinetic.Rect)}(),function(){var a=2*Math.PI-1e-4,b="Circle";Kinetic.Circle=function(a){this.___init(a)},Kinetic.Circle.prototype={___init:function(a){Kinetic.Shape.call(this,a),this.className=b,this.sceneFunc(this._sceneFunc)},_sceneFunc:function(b){b.beginPath(),b.arc(0,0,this.getRadius(),0,a,!1),b.closePath(),b.fillStrokeShape(this)},getWidth:function(){return 2*this.getRadius()},getHeight:function(){return 2*this.getRadius()},setWidth:function(a){Kinetic.Node.prototype.setWidth.call(this,a),this.radius()!==a/2&&this.setRadius(a/2)},setHeight:function(a){Kinetic.Node.prototype.setHeight.call(this,a),this.radius()!==a/2&&this.setRadius(a/2)},setRadius:function(a){this._setAttr("radius",a),this.setWidth(2*a),this.setHeight(2*a)}},Kinetic.Util.extend(Kinetic.Circle,Kinetic.Shape),Kinetic.Factory.addGetter(Kinetic.Circle,"radius",0),Kinetic.Factory.addOverloadedGetterSetter(Kinetic.Circle,"radius"),Kinetic.Collection.mapMethods(Kinetic.Circle)}(),function(){var a=2*Math.PI-1e-4,b="Ellipse";Kinetic.Ellipse=function(a){this.___init(a)},Kinetic.Ellipse.prototype={___init:function(a){Kinetic.Shape.call(this,a),this.className=b,this.sceneFunc(this._sceneFunc)},_sceneFunc:function(b){var c=this.getRadiusX(),d=this.getRadiusY();b.beginPath(),b.save(),c!==d&&b.scale(1,d/c),b.arc(0,0,c,0,a,!1),b.restore(),b.closePath(),b.fillStrokeShape(this)},getWidth:function(){return 2*this.getRadiusX()},getHeight:function(){return 2*this.getRadiusY()},setWidth:function(a){Kinetic.Node.prototype.setWidth.call(this,a),this.setRadius({x:a/2})},setHeight:function(a){Kinetic.Node.prototype.setHeight.call(this,a),this.setRadius({y:a/2})}},Kinetic.Util.extend(Kinetic.Ellipse,Kinetic.Shape),Kinetic.Factory.addComponentsGetterSetter(Kinetic.Ellipse,"radius",["x","y"]),Kinetic.Factory.addGetterSetter(Kinetic.Ellipse,"radiusX",0),Kinetic.Factory.addGetterSetter(Kinetic.Ellipse,"radiusY",0),Kinetic.Collection.mapMethods(Kinetic.Ellipse)}(),function(){var a=2*Math.PI-1e-4;Kinetic.Ring=function(a){this.___init(a)},Kinetic.Ring.prototype={___init:function(a){Kinetic.Shape.call(this,a),this.className="Ring",this.sceneFunc(this._sceneFunc)},_sceneFunc:function(b){b.beginPath(),b.arc(0,0,this.getInnerRadius(),0,a,!1),b.moveTo(this.getOuterRadius(),0),b.arc(0,0,this.getOuterRadius(),a,0,!0),b.closePath(),b.fillStrokeShape(this)},getWidth:function(){return 2*this.getOuterRadius()},getHeight:function(){return 2*this.getOuterRadius()},setWidth:function(a){Kinetic.Node.prototype.setWidth.call(this,a),this.outerRadius()!==a/2&&this.setOuterRadius(a/2)},setHeight:function(a){Kinetic.Node.prototype.setHeight.call(this,a),this.outerRadius()!==a/2&&this.setOuterRadius(a/2)},setOuterRadius:function(a){this._setAttr("outerRadius",a),this.setWidth(2*a),this.setHeight(2*a)}},Kinetic.Util.extend(Kinetic.Ring,Kinetic.Shape),Kinetic.Factory.addGetterSetter(Kinetic.Ring,"innerRadius",0),Kinetic.Factory.addGetter(Kinetic.Ring,"outerRadius",0),Kinetic.Factory.addOverloadedGetterSetter(Kinetic.Ring,"outerRadius"),Kinetic.Collection.mapMethods(Kinetic.Ring)}(),function(){Kinetic.Wedge=function(a){this.___init(a)},Kinetic.Wedge.prototype={___init:function(a){Kinetic.Shape.call(this,a),this.className="Wedge",this.sceneFunc(this._sceneFunc)},_sceneFunc:function(a){a.beginPath(),a.arc(0,0,this.getRadius(),0,Kinetic.getAngle(this.getAngle()),this.getClockwise()),a.lineTo(0,0),a.closePath(),a.fillStrokeShape(this)}},Kinetic.Util.extend(Kinetic.Wedge,Kinetic.Shape),Kinetic.Factory.addGetterSetter(Kinetic.Wedge,"radius",0),Kinetic.Factory.addGetterSetter(Kinetic.Wedge,"angle",0),Kinetic.Factory.addGetterSetter(Kinetic.Wedge,"clockwise",!1),Kinetic.Factory.backCompat(Kinetic.Wedge,{angleDeg:"angle",getAngleDeg:"getAngle",setAngleDeg:"setAngle"}),Kinetic.Collection.mapMethods(Kinetic.Wedge)}(),function(){Kinetic.Arc=function(a){this.___init(a)},Kinetic.Arc.prototype={___init:function(a){Kinetic.Shape.call(this,a),this.className="Arc",this.sceneFunc(this._sceneFunc)},_sceneFunc:function(a){var b=Kinetic.getAngle(this.angle()),c=this.clockwise();a.beginPath(),a.arc(0,0,this.getOuterRadius(),0,b,c),a.arc(0,0,this.getInnerRadius(),b,0,!c),a.closePath(),a.fillStrokeShape(this)}},Kinetic.Util.extend(Kinetic.Arc,Kinetic.Shape),Kinetic.Factory.addGetterSetter(Kinetic.Arc,"innerRadius",0),Kinetic.Factory.addGetterSetter(Kinetic.Arc,"outerRadius",0),Kinetic.Factory.addGetterSetter(Kinetic.Arc,"angle",0),Kinetic.Factory.addGetterSetter(Kinetic.Arc,"clockwise",!1),Kinetic.Collection.mapMethods(Kinetic.Arc)}(),function(){var a="Image";Kinetic.Image=function(a){this.___init(a)},Kinetic.Image.prototype={___init:function(b){Kinetic.Shape.call(this,b),this.className=a,this.sceneFunc(this._sceneFunc),this.hitFunc(this._hitFunc)},_useBufferCanvas:function(){return(this.hasShadow()||1!==this.getAbsoluteOpacity())&&this.hasStroke()&&this.getStage()},_sceneFunc:function(a){var b,c,d,e=this.getWidth(),f=this.getHeight(),g=this.getImage();g&&(b=this.getCropWidth(),c=this.getCropHeight(),d=b&&c?[g,this.getCropX(),this.getCropY(),b,c,0,0,e,f]:[g,0,0,e,f]),(this.hasFill()||this.hasStroke()||this.hasShadow())&&(a.beginPath(),a.rect(0,0,e,f),a.closePath(),a.fillStrokeShape(this)),g&&a.drawImage.apply(a,d) },_hitFunc:function(a){var b=this.getWidth(),c=this.getHeight();a.beginPath(),a.rect(0,0,b,c),a.closePath(),a.fillStrokeShape(this)},getWidth:function(){var a=this.getImage();return this.attrs.width||(a?a.width:0)},getHeight:function(){var a=this.getImage();return this.attrs.height||(a?a.height:0)}},Kinetic.Util.extend(Kinetic.Image,Kinetic.Shape),Kinetic.Factory.addGetterSetter(Kinetic.Image,"image"),Kinetic.Factory.addComponentsGetterSetter(Kinetic.Image,"crop",["x","y","width","height"]),Kinetic.Factory.addGetterSetter(Kinetic.Image,"cropX",0),Kinetic.Factory.addGetterSetter(Kinetic.Image,"cropY",0),Kinetic.Factory.addGetterSetter(Kinetic.Image,"cropWidth",0),Kinetic.Factory.addGetterSetter(Kinetic.Image,"cropHeight",0),Kinetic.Collection.mapMethods(Kinetic.Image)}(),function(){function a(a){a.fillText(this.partialText,0,0)}function b(a){a.strokeText(this.partialText,0,0)}var c="auto",d="center",e="Change.kinetic",f="2d",g="-",h="",i="left",j="text",k="Text",l="middle",m="normal",n="px ",o=" ",p="right",q="word",r="char",s="none",t=["fontFamily","fontSize","fontStyle","fontVariant","padding","align","lineHeight","text","width","height","wrap"],u=t.length,v=Kinetic.Util.createCanvasElement().getContext(f);Kinetic.Text=function(a){this.___init(a)},Kinetic.Text.prototype={___init:function(d){d=d||{},d.fill=d.fill||"black",void 0===d.width&&(d.width=c),void 0===d.height&&(d.height=c),Kinetic.Shape.call(this,d),this._fillFunc=a,this._strokeFunc=b,this.className=k;for(var f=0;u>f;f++)this.on(t[f]+e,this._setTextData);this._setTextData(),this.sceneFunc(this._sceneFunc),this.hitFunc(this._hitFunc)},_sceneFunc:function(a){var b,c=this.getPadding(),e=this.getTextHeight(),f=this.getLineHeight()*e,g=this.textArr,h=g.length,j=this.getWidth();for(a.setAttr("font",this._getContextFont()),a.setAttr("textBaseline",l),a.setAttr("textAlign",i),a.save(),a.translate(c,0),a.translate(0,c+e/2),b=0;h>b;b++){var k=g[b],m=k.text,n=k.width;a.save(),this.getAlign()===p?a.translate(j-n-2*c,0):this.getAlign()===d&&a.translate((j-n-2*c)/2,0),this.partialText=m,a.fillStrokeShape(this),a.restore(),a.translate(0,f)}a.restore()},_hitFunc:function(a){var b=this.getWidth(),c=this.getHeight();a.beginPath(),a.rect(0,0,b,c),a.closePath(),a.fillStrokeShape(this)},setText:function(a){var b=Kinetic.Util._isString(a)?a:a.toString();return this._setAttr(j,b),this},getWidth:function(){return this.attrs.width===c?this.getTextWidth()+2*this.getPadding():this.attrs.width},getHeight:function(){return this.attrs.height===c?this.getTextHeight()*this.textArr.length*this.getLineHeight()+2*this.getPadding():this.attrs.height},getTextWidth:function(){return this.textWidth},getTextHeight:function(){return this.textHeight},_getTextSize:function(a){var b,c=v,d=this.getFontSize();return c.save(),c.font=this._getContextFont(),b=c.measureText(a),c.restore(),{width:b.width,height:parseInt(d,10)}},_getContextFont:function(){return this.getFontStyle()+o+this.getFontVariant()+o+this.getFontSize()+n+this.getFontFamily()},_addTextLine:function(a,b){return this.textArr.push({text:a,width:b})},_getTextWidth:function(a){return v.measureText(a).width},_setTextData:function(){var a=this.getText().split("\n"),b=+this.getFontSize(),d=0,e=this.getLineHeight()*b,f=this.attrs.width,h=this.attrs.height,i=f!==c,j=h!==c,k=this.getPadding(),l=f-2*k,m=h-2*k,n=0,p=this.getWrap(),q=p!==s,t=p!==r&&q;this.textArr=[],v.save(),v.font=this._getContextFont();for(var u=0,w=a.length;w>u;++u){var x=a[u],y=this._getTextWidth(x);if(i&&y>l)for(;x.length>0;){for(var z=0,A=x.length,B="",C=0;A>z;){var D=z+A>>>1,E=x.slice(0,D+1),F=this._getTextWidth(E);l>=F?(z=D+1,B=E,C=F):A=D}if(!B)break;if(t){var G=Math.max(B.lastIndexOf(o),B.lastIndexOf(g))+1;G>0&&(z=G,B=B.slice(0,z),C=this._getTextWidth(B))}if(this._addTextLine(B,C),d=Math.max(d,C),n+=e,!q||j&&n+e>m)break;if(x=x.slice(z),x.length>0&&(y=this._getTextWidth(x),l>=y)){this._addTextLine(x,y),n+=e,d=Math.max(d,y);break}}else this._addTextLine(x,y),n+=e,d=Math.max(d,y);if(j&&n+e>m)break}v.restore(),this.textHeight=b,this.textWidth=d}},Kinetic.Util.extend(Kinetic.Text,Kinetic.Shape),Kinetic.Factory.addGetterSetter(Kinetic.Text,"fontFamily","Arial"),Kinetic.Factory.addGetterSetter(Kinetic.Text,"fontSize",12),Kinetic.Factory.addGetterSetter(Kinetic.Text,"fontStyle",m),Kinetic.Factory.addGetterSetter(Kinetic.Text,"fontVariant",m),Kinetic.Factory.addGetterSetter(Kinetic.Text,"padding",0),Kinetic.Factory.addGetterSetter(Kinetic.Text,"align",i),Kinetic.Factory.addGetterSetter(Kinetic.Text,"lineHeight",1),Kinetic.Factory.addGetterSetter(Kinetic.Text,"wrap",q),Kinetic.Factory.addGetter(Kinetic.Text,"text",h),Kinetic.Factory.addOverloadedGetterSetter(Kinetic.Text,"text"),Kinetic.Collection.mapMethods(Kinetic.Text)}(),function(){Kinetic.Line=function(a){this.___init(a)},Kinetic.Line.prototype={___init:function(a){Kinetic.Shape.call(this,a),this.className="Line",this.on("pointsChange.kinetic tensionChange.kinetic closedChange.kinetic",function(){this._clearCache("tensionPoints")}),this.sceneFunc(this._sceneFunc)},_sceneFunc:function(a){var b,c,d,e=this.getPoints(),f=e.length,g=this.getTension(),h=this.getClosed();if(f){if(a.beginPath(),a.moveTo(e[0],e[1]),0!==g&&f>4){for(b=this.getTensionPoints(),c=b.length,d=h?0:4,h||a.quadraticCurveTo(b[0],b[1],b[2],b[3]);c-2>d;)a.bezierCurveTo(b[d++],b[d++],b[d++],b[d++],b[d++],b[d++]);h||a.quadraticCurveTo(b[c-2],b[c-1],e[f-2],e[f-1])}else for(d=2;f>d;d+=2)a.lineTo(e[d],e[d+1]);h?(a.closePath(),a.fillStrokeShape(this)):a.strokeShape(this)}},getTensionPoints:function(){return this._getCache("tensionPoints",this._getTensionPoints)},_getTensionPoints:function(){return this.getClosed()?this._getTensionPointsClosed():Kinetic.Util._expandPoints(this.getPoints(),this.getTension())},_getTensionPointsClosed:function(){var a=this.getPoints(),b=a.length,c=this.getTension(),d=Kinetic.Util,e=d._getControlPoints(a[b-2],a[b-1],a[0],a[1],a[2],a[3],c),f=d._getControlPoints(a[b-4],a[b-3],a[b-2],a[b-1],a[0],a[1],c),g=Kinetic.Util._expandPoints(a,c),h=[e[2],e[3]].concat(g).concat([f[0],f[1],a[b-2],a[b-1],f[2],f[3],e[0],e[1],a[0],a[1]]);return h}},Kinetic.Util.extend(Kinetic.Line,Kinetic.Shape),Kinetic.Factory.addGetterSetter(Kinetic.Line,"closed",!1),Kinetic.Factory.addGetterSetter(Kinetic.Line,"tension",0),Kinetic.Factory.addGetterSetter(Kinetic.Line,"points",[]),Kinetic.Collection.mapMethods(Kinetic.Line)}(),function(){Kinetic.Sprite=function(a){this.___init(a)},Kinetic.Sprite.prototype={___init:function(a){Kinetic.Shape.call(this,a),this.className="Sprite",this._updated=!0;var b=this;this.anim=new Kinetic.Animation(function(){var a=b._updated;return b._updated=!1,a}),this.on("animationChange.kinetic",function(){this.frameIndex(0)}),this.on("frameIndexChange.kinetic",function(){this._updated=!0}),this.on("frameRateChange.kinetic",function(){this.anim.isRunning()&&(clearInterval(this.interval),this._setInterval())}),this.sceneFunc(this._sceneFunc),this.hitFunc(this._hitFunc)},_sceneFunc:function(a){var b=this.getAnimation(),c=this.frameIndex(),d=4*c,e=this.getAnimations()[b],f=this.frameOffsets(),g=e[d+0],h=e[d+1],i=e[d+2],j=e[d+3],k=this.getImage();if(k)if(f){var l=f[b],m=2*c;a.drawImage(k,g,h,i,j,l[m+0],l[m+1],i,j)}else a.drawImage(k,g,h,i,j,0,0,i,j)},_hitFunc:function(a){var b=this.getAnimation(),c=this.frameIndex(),d=4*c,e=this.getAnimations()[b],f=this.frameOffsets(),g=e[d+2],h=e[d+3];if(a.beginPath(),f){var i=f[b],j=2*c;a.rect(i[j+0],i[j+1],g,h)}else a.rect(0,0,g,h);a.closePath(),a.fillShape(this)},_useBufferCanvas:function(){return(this.hasShadow()||1!==this.getAbsoluteOpacity())&&this.hasStroke()},_setInterval:function(){var a=this;this.interval=setInterval(function(){a._updateIndex()},1e3/this.getFrameRate())},start:function(){var a=this.getLayer();this.anim.setLayers(a),this._setInterval(),this.anim.start()},stop:function(){this.anim.stop(),clearInterval(this.interval)},isRunning:function(){return this.anim.isRunning()},_updateIndex:function(){var a=this.frameIndex(),b=this.getAnimation(),c=this.getAnimations(),d=c[b],e=d.length/4;this.frameIndex(e-1>a?a+1:0)}},Kinetic.Util.extend(Kinetic.Sprite,Kinetic.Shape),Kinetic.Factory.addGetterSetter(Kinetic.Sprite,"animation"),Kinetic.Factory.addGetterSetter(Kinetic.Sprite,"animations"),Kinetic.Factory.addGetterSetter(Kinetic.Sprite,"frameOffsets"),Kinetic.Factory.addGetterSetter(Kinetic.Sprite,"image"),Kinetic.Factory.addGetterSetter(Kinetic.Sprite,"frameIndex",0),Kinetic.Factory.addGetterSetter(Kinetic.Sprite,"frameRate",17),Kinetic.Factory.backCompat(Kinetic.Sprite,{index:"frameIndex",getIndex:"getFrameIndex",setIndex:"setFrameIndex"}),Kinetic.Collection.mapMethods(Kinetic.Sprite)}(),function(){Kinetic.Path=function(a){this.___init(a)},Kinetic.Path.prototype={___init:function(a){this.dataArray=[];var b=this;Kinetic.Shape.call(this,a),this.className="Path",this.dataArray=Kinetic.Path.parsePathData(this.getData()),this.on("dataChange.kinetic",function(){b.dataArray=Kinetic.Path.parsePathData(this.getData())}),this.sceneFunc(this._sceneFunc)},_sceneFunc:function(a){var b=this.dataArray,c=!1;a.beginPath();for(var d=0;dj?i:j,p=i>j?1:i/j,q=i>j?j/i:1;a.translate(g,h),a.rotate(m),a.scale(p,q),a.arc(0,0,o,k,k+l,1-n),a.scale(1/p,1/q),a.rotate(-m),a.translate(-g,-h);break;case"z":a.closePath(),c=!0}}c?a.fillStrokeShape(this):a.strokeShape(this)}},Kinetic.Util.extend(Kinetic.Path,Kinetic.Shape),Kinetic.Path.getLineLength=function(a,b,c,d){return Math.sqrt((c-a)*(c-a)+(d-b)*(d-b))},Kinetic.Path.getPointOnLine=function(a,b,c,d,e,f,g){void 0===f&&(f=b),void 0===g&&(g=c);var h=(e-c)/(d-b+1e-8),i=Math.sqrt(a*a/(1+h*h));b>d&&(i*=-1);var j,k=h*i;if(d===b)j={x:f,y:g+k};else if((g-c)/(f-b+1e-8)===h)j={x:f+i,y:g+k};else{var l,m,n=this.getLineLength(b,c,d,e);if(1e-8>n)return void 0;var o=(f-b)*(d-b)+(g-c)*(e-c);o/=n*n,l=b+o*(d-b),m=c+o*(e-c);var p=this.getLineLength(f,g,l,m),q=Math.sqrt(a*a-p*p);i=Math.sqrt(q*q/(1+h*h)),b>d&&(i*=-1),k=h*i,j={x:l+i,y:m+k}}return j},Kinetic.Path.getPointOnCubicBezier=function(a,b,c,d,e,f,g,h,i){function j(a){return a*a*a}function k(a){return 3*a*a*(1-a)}function l(a){return 3*a*(1-a)*(1-a)}function m(a){return(1-a)*(1-a)*(1-a)}var n=h*j(a)+f*k(a)+d*l(a)+b*m(a),o=i*j(a)+g*k(a)+e*l(a)+c*m(a);return{x:n,y:o}},Kinetic.Path.getPointOnQuadraticBezier=function(a,b,c,d,e,f,g){function h(a){return a*a}function i(a){return 2*a*(1-a)}function j(a){return(1-a)*(1-a)}var k=f*h(a)+d*i(a)+b*j(a),l=g*h(a)+e*i(a)+c*j(a);return{x:k,y:l}},Kinetic.Path.getPointOnEllipticalArc=function(a,b,c,d,e,f){var g=Math.cos(f),h=Math.sin(f),i={x:c*Math.cos(e),y:d*Math.sin(e)};return{x:a+(i.x*g-i.y*h),y:b+(i.x*h+i.y*g)}},Kinetic.Path.parsePathData=function(a){if(!a)return[];var b=a,c=["m","M","l","L","v","V","h","H","z","Z","c","C","q","Q","t","T","s","S","a","A"];b=b.replace(new RegExp(" ","g"),",");for(var d=0;d0&&""===k[0]&&k.shift();for(var l=0;l0&&!isNaN(k[0]);){var m,n,o,p,q,r,s,t,u,v,w=null,x=[],y=g,z=h;switch(j){case"l":g+=k.shift(),h+=k.shift(),w="L",x.push(g,h);break;case"L":g=k.shift(),h=k.shift(),x.push(g,h);break;case"m":var A=k.shift(),B=k.shift();if(g+=A,h+=B,w="M",f.length>2&&"z"===f[f.length-1].command)for(var C=f.length-2;C>=0;C--)if("M"===f[C].command){g=f[C].points[0]+A,h=f[C].points[1]+B;break}x.push(g,h),j="l";break;case"M":g=k.shift(),h=k.shift(),w="M",x.push(g,h),j="L";break;case"h":g+=k.shift(),w="L",x.push(g,h);break;case"H":g=k.shift(),w="L",x.push(g,h);break;case"v":h+=k.shift(),w="L",x.push(g,h);break;case"V":h=k.shift(),w="L",x.push(g,h);break;case"C":x.push(k.shift(),k.shift(),k.shift(),k.shift()),g=k.shift(),h=k.shift(),x.push(g,h);break;case"c":x.push(g+k.shift(),h+k.shift(),g+k.shift(),h+k.shift()),g+=k.shift(),h+=k.shift(),w="C",x.push(g,h);break;case"S":n=g,o=h,m=f[f.length-1],"C"===m.command&&(n=g+(g-m.points[2]),o=h+(h-m.points[3])),x.push(n,o,k.shift(),k.shift()),g=k.shift(),h=k.shift(),w="C",x.push(g,h);break;case"s":n=g,o=h,m=f[f.length-1],"C"===m.command&&(n=g+(g-m.points[2]),o=h+(h-m.points[3])),x.push(n,o,g+k.shift(),h+k.shift()),g+=k.shift(),h+=k.shift(),w="C",x.push(g,h);break;case"Q":x.push(k.shift(),k.shift()),g=k.shift(),h=k.shift(),x.push(g,h);break;case"q":x.push(g+k.shift(),h+k.shift()),g+=k.shift(),h+=k.shift(),w="Q",x.push(g,h);break;case"T":n=g,o=h,m=f[f.length-1],"Q"===m.command&&(n=g+(g-m.points[0]),o=h+(h-m.points[1])),g=k.shift(),h=k.shift(),w="Q",x.push(n,o,g,h);break;case"t":n=g,o=h,m=f[f.length-1],"Q"===m.command&&(n=g+(g-m.points[0]),o=h+(h-m.points[1])),g+=k.shift(),h+=k.shift(),w="Q",x.push(n,o,g,h);break;case"A":p=k.shift(),q=k.shift(),r=k.shift(),s=k.shift(),t=k.shift(),u=g,v=h,g=k.shift(),h=k.shift(),w="A",x=this.convertEndpointToCenterParameterization(u,v,g,h,s,t,p,q,r);break;case"a":p=k.shift(),q=k.shift(),r=k.shift(),s=k.shift(),t=k.shift(),u=g,v=h,g+=k.shift(),h+=k.shift(),w="A",x=this.convertEndpointToCenterParameterization(u,v,g,h,s,t,p,q,r)}f.push({command:w||j,points:x,start:{x:y,y:z},pathLength:this.calcLength(y,z,w||j,x)})}("z"===j||"Z"===j)&&f.push({command:"z",points:[],start:void 0,pathLength:0})}return f},Kinetic.Path.calcLength=function(a,b,c,d){var e,f,g,h,i=Kinetic.Path;switch(c){case"L":return i.getLineLength(a,b,d[0],d[1]);case"C":for(e=0,f=i.getPointOnCubicBezier(0,a,b,d[0],d[1],d[2],d[3],d[4],d[5]),h=.01;1>=h;h+=.01)g=i.getPointOnCubicBezier(h,a,b,d[0],d[1],d[2],d[3],d[4],d[5]),e+=i.getLineLength(f.x,f.y,g.x,g.y),f=g;return e;case"Q":for(e=0,f=i.getPointOnQuadraticBezier(0,a,b,d[0],d[1],d[2],d[3]),h=.01;1>=h;h+=.01)g=i.getPointOnQuadraticBezier(h,a,b,d[0],d[1],d[2],d[3]),e+=i.getLineLength(f.x,f.y,g.x,g.y),f=g;return e;case"A":e=0;var j=d[4],k=d[5],l=d[4]+k,m=Math.PI/180;if(Math.abs(j-l)k)for(h=j-m;h>l;h-=m)g=i.getPointOnEllipticalArc(d[0],d[1],d[2],d[3],h,0),e+=i.getLineLength(f.x,f.y,g.x,g.y),f=g;else for(h=j+m;l>h;h+=m)g=i.getPointOnEllipticalArc(d[0],d[1],d[2],d[3],h,0),e+=i.getLineLength(f.x,f.y,g.x,g.y),f=g;return g=i.getPointOnEllipticalArc(d[0],d[1],d[2],d[3],l,0),e+=i.getLineLength(f.x,f.y,g.x,g.y)}return 0},Kinetic.Path.convertEndpointToCenterParameterization=function(a,b,c,d,e,f,g,h,i){var j=i*(Math.PI/180),k=Math.cos(j)*(a-c)/2+Math.sin(j)*(b-d)/2,l=-1*Math.sin(j)*(a-c)/2+Math.cos(j)*(b-d)/2,m=k*k/(g*g)+l*l/(h*h);m>1&&(g*=Math.sqrt(m),h*=Math.sqrt(m));var n=Math.sqrt((g*g*h*h-g*g*l*l-h*h*k*k)/(g*g*l*l+h*h*k*k));e===f&&(n*=-1),isNaN(n)&&(n=0);var o=n*g*l/h,p=n*-h*k/g,q=(a+c)/2+Math.cos(j)*o-Math.sin(j)*p,r=(b+d)/2+Math.sin(j)*o+Math.cos(j)*p,s=function(a){return Math.sqrt(a[0]*a[0]+a[1]*a[1])},t=function(a,b){return(a[0]*b[0]+a[1]*b[1])/(s(a)*s(b))},u=function(a,b){return(a[0]*b[1]=1&&(y=0),0===f&&y>0&&(y-=2*Math.PI),1===f&&0>y&&(y+=2*Math.PI),[q,r,g,h,v,y,j,f]},Kinetic.Factory.addGetterSetter(Kinetic.Path,"data"),Kinetic.Collection.mapMethods(Kinetic.Path)}(),function(){function a(a){a.fillText(this.partialText,0,0)}function b(a){a.strokeText(this.partialText,0,0)}var c="",d="normal";Kinetic.TextPath=function(a){this.___init(a)},Kinetic.TextPath.prototype={___init:function(c){var d=this;this.dummyCanvas=Kinetic.Util.createCanvasElement(),this.dataArray=[],Kinetic.Shape.call(this,c),this._fillFunc=a,this._strokeFunc=b,this._fillFuncHit=a,this._strokeFuncHit=b,this.className="TextPath",this.dataArray=Kinetic.Path.parsePathData(this.attrs.data),this.on("dataChange.kinetic",function(){d.dataArray=Kinetic.Path.parsePathData(this.attrs.data)}),this.on("textChange.kinetic textStroke.kinetic textStrokeWidth.kinetic",d._setTextData),d._setTextData(),this.sceneFunc(this._sceneFunc)},_sceneFunc:function(a){a.setAttr("font",this._getContextFont()),a.setAttr("textBaseline","middle"),a.setAttr("textAlign","left"),a.save();for(var b=this.glyphInfo,c=0;c0)return g=d,b[d];"M"==b[d].command&&(c={x:b[d].points[0],y:b[d].points[1]})}return{}},j=function(b){var f=a._getTextSize(b).width,g=0,j=0;for(d=void 0;Math.abs(f-g)/f>.01&&25>j;){j++;for(var k=g;void 0===e;)e=i(),e&&k+e.pathLengthf?d=Kinetic.Path.getPointOnLine(f,c.x,c.y,e.points[0],e.points[1],c.x,c.y):e=void 0;break;case"A":var m=e.points[4],n=e.points[5],o=e.points[4]+n;0===h?h=m+1e-8:f>g?h+=Math.PI/180*n/Math.abs(n):h-=Math.PI/360*n/Math.abs(n),(0>n&&o>h||n>=0&&h>o)&&(h=o,l=!0),d=Kinetic.Path.getPointOnEllipticalArc(e.points[0],e.points[1],e.points[2],e.points[3],h,e.points[6]);break;case"C":0===h?h=f>e.pathLength?1e-8:f/e.pathLength:f>g?h+=(f-g)/e.pathLength:h-=(g-f)/e.pathLength,h>1&&(h=1,l=!0),d=Kinetic.Path.getPointOnCubicBezier(h,e.start.x,e.start.y,e.points[0],e.points[1],e.points[2],e.points[3],e.points[4],e.points[5]);break;case"Q":0===h?h=f/e.pathLength:f>g?h+=(f-g)/e.pathLength:h-=(g-f)/e.pathLength,h>1&&(h=1,l=!0),d=Kinetic.Path.getPointOnQuadraticBezier(h,e.start.x,e.start.y,e.points[0],e.points[1],e.points[2],e.points[3])}void 0!==d&&(g=Kinetic.Path.getLineLength(c.x,c.y,d.x,d.y)),l&&(l=!1,e=void 0)}},k=0;kb;b++)c=f*Math.sin(2*b*Math.PI/e),d=-1*f*Math.cos(2*b*Math.PI/e),a.lineTo(c,d);a.closePath(),a.fillStrokeShape(this)}},Kinetic.Util.extend(Kinetic.RegularPolygon,Kinetic.Shape),Kinetic.Factory.addGetterSetter(Kinetic.RegularPolygon,"radius",0),Kinetic.Factory.addGetterSetter(Kinetic.RegularPolygon,"sides",0),Kinetic.Collection.mapMethods(Kinetic.RegularPolygon)}(),function(){Kinetic.Star=function(a){this.___init(a)},Kinetic.Star.prototype={___init:function(a){Kinetic.Shape.call(this,a),this.className="Star",this.sceneFunc(this._sceneFunc)},_sceneFunc:function(a){var b=this.innerRadius(),c=this.outerRadius(),d=this.numPoints();a.beginPath(),a.moveTo(0,0-c);for(var e=1;2*d>e;e++){var f=e%2===0?c:b,g=f*Math.sin(e*Math.PI/d),h=-1*f*Math.cos(e*Math.PI/d);a.lineTo(g,h)}a.closePath(),a.fillStrokeShape(this)}},Kinetic.Util.extend(Kinetic.Star,Kinetic.Shape),Kinetic.Factory.addGetterSetter(Kinetic.Star,"numPoints",5),Kinetic.Factory.addGetterSetter(Kinetic.Star,"innerRadius",0),Kinetic.Factory.addGetterSetter(Kinetic.Star,"outerRadius",0),Kinetic.Collection.mapMethods(Kinetic.Star)}(),function(){var a=["fontFamily","fontSize","fontStyle","padding","lineHeight","text"],b="Change.kinetic",c="none",d="up",e="right",f="down",g="left",h="Label",i=a.length;Kinetic.Label=function(a){this.____init(a)},Kinetic.Label.prototype={____init:function(a){var b=this;Kinetic.Group.call(this,a),this.className=h,this.on("add.kinetic",function(a){b._addListeners(a.child),b._sync()})},getText:function(){return this.find("Text")[0]},getTag:function(){return this.find("Tag")[0]},_addListeners:function(c){var d,e=this,f=function(){e._sync()};for(d=0;i>d;d++)c.on(a[d]+b,f)},getWidth:function(){return this.getText().getWidth()},getHeight:function(){return this.getText().getHeight()},_sync:function(){var a,b,c,h,i,j,k,l=this.getText(),m=this.getTag();if(l&&m){switch(a=l.getWidth(),b=l.getHeight(),c=m.getPointerDirection(),h=m.getPointerWidth(),k=m.getPointerHeight(),i=0,j=0,c){case d:i=a/2,j=-1*k;break;case e:i=a+h,j=b/2;break;case f:i=a/2,j=b+k;break;case g:i=-1*h,j=b/2}m.setAttrs({x:-1*i,y:-1*j,width:a,height:b}),l.setAttrs({x:-1*i,y:-1*j})}}},Kinetic.Util.extend(Kinetic.Label,Kinetic.Group),Kinetic.Collection.mapMethods(Kinetic.Label),Kinetic.Tag=function(a){this.___init(a)},Kinetic.Tag.prototype={___init:function(a){Kinetic.Shape.call(this,a),this.className="Tag",this.sceneFunc(this._sceneFunc)},_sceneFunc:function(a){var b=this.getWidth(),c=this.getHeight(),h=this.getPointerDirection(),i=this.getPointerWidth(),j=this.getPointerHeight(),k=this.getCornerRadius();a.beginPath(),a.moveTo(0,0),h===d&&(a.lineTo((b-i)/2,0),a.lineTo(b/2,-1*j),a.lineTo((b+i)/2,0)),k?(a.lineTo(b-k,0),a.arc(b-k,k,k,3*Math.PI/2,0,!1)):a.lineTo(b,0),h===e&&(a.lineTo(b,(c-j)/2),a.lineTo(b+i,c/2),a.lineTo(b,(c+j)/2)),k?(a.lineTo(b,c-k),a.arc(b-k,c-k,k,0,Math.PI/2,!1)):a.lineTo(b,c),h===f&&(a.lineTo((b+i)/2,c),a.lineTo(b/2,c+j),a.lineTo((b-i)/2,c)),k?(a.lineTo(k,c),a.arc(k,c-k,k,Math.PI/2,Math.PI,!1)):a.lineTo(0,c),h===g&&(a.lineTo(0,(c+j)/2),a.lineTo(-1*i,c/2),a.lineTo(0,(c-j)/2)),k&&(a.lineTo(0,k),a.arc(k,k,k,Math.PI,3*Math.PI/2,!1)),a.closePath(),a.fillStrokeShape(this)}},Kinetic.Util.extend(Kinetic.Tag,Kinetic.Shape),Kinetic.Factory.addGetterSetter(Kinetic.Tag,"pointerDirection",c),Kinetic.Factory.addGetterSetter(Kinetic.Tag,"pointerWidth",0),Kinetic.Factory.addGetterSetter(Kinetic.Tag,"pointerHeight",0),Kinetic.Factory.addGetterSetter(Kinetic.Tag,"cornerRadius",0),Kinetic.Collection.mapMethods(Kinetic.Tag)}(),function(){Kinetic.Arrow=function(a){this.____init(a)},Kinetic.Arrow.prototype={____init:function(a){Kinetic.Line.call(this,a),this.className="Arrow"},_sceneFunc:function(a){var b=2*Math.PI,c=this.points(),d=c.length,e=c[d-2]-c[d-4],f=c[d-1]-c[d-3],g=(Math.atan2(f,e)+b)%b,h=this.pointerLength(),i=this.pointerWidth();a.save(),a.beginPath(),a.translate(c[d-2],c[d-1]),a.rotate(g),a.moveTo(0,0),a.lineTo(-h,i/2),a.lineTo(-h,-i/2),a.closePath(),a.restore(),this.pointerAtBeginning()&&(a.save(),a.translate(c[0],c[1]),e=c[2]-c[0],f=c[3]-c[1],a.rotate((Math.atan2(-f,-e)+b)%b),a.moveTo(0,0),a.lineTo(-10,6),a.lineTo(-10,-6),a.closePath(),a.restore()),a.fillStrokeShape(this),Kinetic.Line.prototype._sceneFunc.apply(this,arguments)}},Kinetic.Util.extend(Kinetic.Arrow,Kinetic.Line),Kinetic.Factory.addGetterSetter(Kinetic.Arrow,"pointerLength",10),Kinetic.Factory.addGetterSetter(Kinetic.Arrow,"pointerWidth",10),Kinetic.Factory.addGetterSetter(Kinetic.Arrow,"pointerAtBeginning",!1),Kinetic.Collection.mapMethods(Kinetic.Arrow)}();;/** * Copyright (c) 2007-2014 Ariel Flesler - afleslergmailcom | http://flesler.blogspot.com * Licensed under MIT * @author Ariel Flesler * @version 1.4.14 */ ;(function(k){'use strict';k(['jquery'],function($){var j=$.scrollTo=function(a,b,c){return $(window).scrollTo(a,b,c)};j.defaults={axis:'xy',duration:0,limit:!0};j.window=function(a){return $(window)._scrollable()};$.fn._scrollable=function(){return this.map(function(){var a=this,isWin=!a.nodeName||$.inArray(a.nodeName.toLowerCase(),['iframe','#document','html','body'])!=-1;if(!isWin)return a;var b=(a.contentWindow||a).document||a.ownerDocument||a;return/webkit/i.test(navigator.userAgent)||b.compatMode=='BackCompat'?b.body:b.documentElement})};$.fn.scrollTo=function(f,g,h){if(typeof g=='object'){h=g;g=0}if(typeof h=='function')h={onAfter:h};if(f=='max')f=9e9;h=$.extend({},j.defaults,h);g=g||h.duration;h.queue=h.queue&&h.axis.length>1;if(h.queue)g/=2;h.offset=both(h.offset);h.over=both(h.over);return this._scrollable().each(function(){if(f==null)return;var d=this,$elem=$(d),targ=f,toff,attr={},win=$elem.is('html,body');switch(typeof targ){case'number':case'string':if(/^([+-]=?)?\d+(\.\d+)?(px|%)?$/.test(targ)){targ=both(targ);break}targ=win?$(targ):$(targ,this);if(!targ.length)return;case'object':if(targ.is||targ.style)toff=(targ=$(targ)).offset()}var e=$.isFunction(h.offset)&&h.offset(d,targ)||h.offset;$.each(h.axis.split(''),function(i,a){var b=a=='x'?'Left':'Top',pos=b.toLowerCase(),key='scroll'+b,old=d[key],max=j.max(d,a);if(toff){attr[key]=toff[pos]+(win?0:old-$elem.offset()[pos]);if(h.margin){attr[key]-=parseInt(targ.css('margin'+b))||0;attr[key]-=parseInt(targ.css('border'+b+'Width'))||0}attr[key]+=e[pos]||0;if(h.over[pos])attr[key]+=targ[a=='x'?'width':'height']()*h.over[pos]}else{var c=targ[pos];attr[key]=c.slice&&c.slice(-1)=='%'?parseFloat(c)/100*max:c}if(h.limit&&/^\d+$/.test(attr[key]))attr[key]=attr[key]<=0?0:Math.min(attr[key],max);if(!i&&h.queue){if(old!=attr[key])animate(h.onAfterFirst);delete attr[key]}});animate(h.onAfter);function animate(a){$elem.animate(attr,g,h.easing,a&&function(){a.call(this,targ,h)})}}).end()};j.max=function(a,b){var c=b=='x'?'Width':'Height',scroll='scroll'+c;if(!$(a).is('html,body'))return a[scroll]-$(a)[c.toLowerCase()]();var d='client'+c,html=a.ownerDocument.documentElement,body=a.ownerDocument.body;return Math.max(html[scroll],body[scroll])-Math.min(html[d],body[d])};function both(a){return $.isFunction(a)||$.isPlainObject(a)?a:{top:a,left:a}}return j})}(typeof define==='function'&&define.amd?define:function(a,b){if(typeof module!=='undefined'&&module.exports){module.exports=b(require('jquery'))}else{b(jQuery)}})); ================================================ FILE: src/libs/fabric.require1-4-12.js ================================================ /* build: `node build.js modules=ALL exclude=gestures,cufon,json minifier=uglifyjs` */ /*! Fabric.js Copyright 2008-2014, Printio (Juriy Zaytsev, Maxim Chernyak) */ var fabric = fabric || { version: "1.4.12" }; if (typeof exports !== 'undefined') { exports.fabric = fabric; } if (typeof document !== 'undefined' && typeof window !== 'undefined') { fabric.document = document; fabric.window = window; } /** * True when in environment that supports touch events * @type boolean */ fabric.isTouchSupported = "ontouchstart" in fabric.document.documentElement; /** * True when in environment that's probably Node.js * @type boolean */ fabric.isLikelyNode = typeof Buffer !== 'undefined' && typeof window === 'undefined'; /** * Attributes parsed from all SVG elements * @type array */ fabric.SHARED_ATTRIBUTES = [ "display", "transform", "fill", "fill-opacity", "fill-rule", "opacity", "stroke", "stroke-dasharray", "stroke-linecap", "stroke-linejoin", "stroke-miterlimit", "stroke-opacity", "stroke-width" ]; /** * Pixel per Inch as a default value set to 96. Can be changed for more realistic conversion. */ fabric.DPI = 96; (function() { /** * @private * @param {String} eventName * @param {Function} handler */ function _removeEventListener(eventName, handler) { if (!this.__eventListeners[eventName]) { return; } if (handler) { fabric.util.removeFromArray(this.__eventListeners[eventName], handler); } else { this.__eventListeners[eventName].length = 0; } } /** * Observes specified event * @deprecated `observe` deprecated since 0.8.34 (use `on` instead) * @memberOf fabric.Observable * @alias on * @param {String|Object} eventName Event name (eg. 'after:render') or object with key/value pairs (eg. {'after:render': handler, 'selection:cleared': handler}) * @param {Function} handler Function that receives a notification when an event of the specified type occurs * @return {Self} thisArg * @chainable */ function observe(eventName, handler) { if (!this.__eventListeners) { this.__eventListeners = { }; } // one object with key/value pairs was passed if (arguments.length === 1) { for (var prop in eventName) { this.on(prop, eventName[prop]); } } else { if (!this.__eventListeners[eventName]) { this.__eventListeners[eventName] = [ ]; } this.__eventListeners[eventName].push(handler); } return this; } /** * Stops event observing for a particular event handler. Calling this method * without arguments removes all handlers for all events * @deprecated `stopObserving` deprecated since 0.8.34 (use `off` instead) * @memberOf fabric.Observable * @alias off * @param {String|Object} eventName Event name (eg. 'after:render') or object with key/value pairs (eg. {'after:render': handler, 'selection:cleared': handler}) * @param {Function} handler Function to be deleted from EventListeners * @return {Self} thisArg * @chainable */ function stopObserving(eventName, handler) { if (!this.__eventListeners) { return; } // remove all key/value pairs (event name -> event handler) if (arguments.length === 0) { this.__eventListeners = { }; } // one object with key/value pairs was passed else if (arguments.length === 1 && typeof arguments[0] === 'object') { for (var prop in eventName) { _removeEventListener.call(this, prop, eventName[prop]); } } else { _removeEventListener.call(this, eventName, handler); } return this; } /** * Fires event with an optional options object * @deprecated `fire` deprecated since 1.0.7 (use `trigger` instead) * @memberOf fabric.Observable * @alias trigger * @param {String} eventName Event name to fire * @param {Object} [options] Options object * @return {Self} thisArg * @chainable */ function fire(eventName, options) { if (!this.__eventListeners) { return; } var listenersForEvent = this.__eventListeners[eventName]; if (!listenersForEvent) { return; } for (var i = 0, len = listenersForEvent.length; i < len; i++) { // avoiding try/catch for perf. reasons listenersForEvent[i].call(this, options || { }); } return this; } /** * @namespace fabric.Observable * @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#events} * @see {@link http://fabricjs.com/events/|Events demo} */ fabric.Observable = { observe: observe, stopObserving: stopObserving, fire: fire, on: observe, off: stopObserving, trigger: fire }; })(); /** * @namespace fabric.Collection */ fabric.Collection = { /** * Adds objects to collection, then renders canvas (if `renderOnAddRemove` is not `false`) * Objects should be instances of (or inherit from) fabric.Object * @param {...fabric.Object} object Zero or more fabric instances * @return {Self} thisArg */ add: function () { this._objects.push.apply(this._objects, arguments); for (var i = 0, length = arguments.length; i < length; i++) { this._onObjectAdded(arguments[i]); } this.renderOnAddRemove && this.renderAll(); return this; }, /** * Inserts an object into collection at specified index, then renders canvas (if `renderOnAddRemove` is not `false`) * An object should be an instance of (or inherit from) fabric.Object * @param {Object} object Object to insert * @param {Number} index Index to insert object at * @param {Boolean} nonSplicing When `true`, no splicing (shifting) of objects occurs * @return {Self} thisArg * @chainable */ insertAt: function (object, index, nonSplicing) { var objects = this.getObjects(); if (nonSplicing) { objects[index] = object; } else { objects.splice(index, 0, object); } this._onObjectAdded(object); this.renderOnAddRemove && this.renderAll(); return this; }, /** * Removes objects from a collection, then renders canvas (if `renderOnAddRemove` is not `false`) * @param {...fabric.Object} object Zero or more fabric instances * @return {Self} thisArg * @chainable */ remove: function() { var objects = this.getObjects(), index; for (var i = 0, length = arguments.length; i < length; i++) { index = objects.indexOf(arguments[i]); // only call onObjectRemoved if an object was actually removed if (index !== -1) { objects.splice(index, 1); this._onObjectRemoved(arguments[i]); } } this.renderOnAddRemove && this.renderAll(); return this; }, /** * Executes given function for each object in this group * @param {Function} callback * Callback invoked with current object as first argument, * index - as second and an array of all objects - as third. * Iteration happens in reverse order (for performance reasons). * Callback is invoked in a context of Global Object (e.g. `window`) * when no `context` argument is given * * @param {Object} context Context (aka thisObject) * @return {Self} thisArg */ forEachObject: function(callback, context) { var objects = this.getObjects(), i = objects.length; while (i--) { callback.call(context, objects[i], i, objects); } return this; }, /** * Returns an array of children objects of this instance * Type parameter introduced in 1.3.10 * @param {String} [type] When specified, only objects of this type are returned * @return {Array} */ getObjects: function(type) { if (typeof type === 'undefined') { return this._objects; } return this._objects.filter(function(o) { return o.type === type; }); }, /** * Returns object at specified index * @param {Number} index * @return {Self} thisArg */ item: function (index) { return this.getObjects()[index]; }, /** * Returns true if collection contains no objects * @return {Boolean} true if collection is empty */ isEmpty: function () { return this.getObjects().length === 0; }, /** * Returns a size of a collection (i.e: length of an array containing its objects) * @return {Number} Collection size */ size: function() { return this.getObjects().length; }, /** * Returns true if collection contains an object * @param {Object} object Object to check against * @return {Boolean} `true` if collection contains an object */ contains: function(object) { return this.getObjects().indexOf(object) > -1; }, /** * Returns number representation of a collection complexity * @return {Number} complexity */ complexity: function () { return this.getObjects().reduce(function (memo, current) { memo += current.complexity ? current.complexity() : 0; return memo; }, 0); } }; (function(global) { var sqrt = Math.sqrt, atan2 = Math.atan2, PiBy180 = Math.PI / 180; /** * @namespace fabric.util */ fabric.util = { /** * Removes value from an array. * Presence of value (and its position in an array) is determined via `Array.prototype.indexOf` * @static * @memberOf fabric.util * @param {Array} array * @param {Any} value * @return {Array} original array */ removeFromArray: function(array, value) { var idx = array.indexOf(value); if (idx !== -1) { array.splice(idx, 1); } return array; }, /** * Returns random number between 2 specified ones. * @static * @memberOf fabric.util * @param {Number} min lower limit * @param {Number} max upper limit * @return {Number} random value (between min and max) */ getRandomInt: function(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; }, /** * Transforms degrees to radians. * @static * @memberOf fabric.util * @param {Number} degrees value in degrees * @return {Number} value in radians */ degreesToRadians: function(degrees) { return degrees * PiBy180; }, /** * Transforms radians to degrees. * @static * @memberOf fabric.util * @param {Number} radians value in radians * @return {Number} value in degrees */ radiansToDegrees: function(radians) { return radians / PiBy180; }, /** * Rotates `point` around `origin` with `radians` * @static * @memberOf fabric.util * @param {fabric.Point} point The point to rotate * @param {fabric.Point} origin The origin of the rotation * @param {Number} radians The radians of the angle for the rotation * @return {fabric.Point} The new rotated point */ rotatePoint: function(point, origin, radians) { var sin = Math.sin(radians), cos = Math.cos(radians); point.subtractEquals(origin); var rx = point.x * cos - point.y * sin, ry = point.x * sin + point.y * cos; return new fabric.Point(rx, ry).addEquals(origin); }, /** * Apply transform t to point p * @static * @memberOf fabric.util * @param {fabric.Point} p The point to transform * @param {Array} t The transform * @param {Boolean} [ignoreOffset] Indicates that the offset should not be applied * @return {fabric.Point} The transformed point */ transformPoint: function(p, t, ignoreOffset) { if (ignoreOffset) { return new fabric.Point( t[0] * p.x + t[1] * p.y, t[2] * p.x + t[3] * p.y ); } return new fabric.Point( t[0] * p.x + t[1] * p.y + t[4], t[2] * p.x + t[3] * p.y + t[5] ); }, /** * Invert transformation t * @static * @memberOf fabric.util * @param {Array} t The transform * @return {Array} The inverted transform */ invertTransform: function(t) { var r = t.slice(), a = 1 / (t[0] * t[3] - t[1] * t[2]); r = [a * t[3], -a * t[1], -a * t[2], a * t[0], 0, 0]; var o = fabric.util.transformPoint({ x: t[4], y: t[5] }, r); r[4] = -o.x; r[5] = -o.y; return r; }, /** * A wrapper around Number#toFixed, which contrary to native method returns number, not string. * @static * @memberOf fabric.util * @param {Number|String} number number to operate on * @param {Number} fractionDigits number of fraction digits to "leave" * @return {Number} */ toFixed: function(number, fractionDigits) { return parseFloat(Number(number).toFixed(fractionDigits)); }, /** * Converts from attribute value to pixel value if applicable. * Returns converted pixels or original value not converted. * @param {Number|String} value number to operate on * @return {Number|String} */ parseUnit: function(value) { var unit = /\D{0,2}$/.exec(value), number = parseFloat(value); switch (unit[0]) { case 'mm': return number * fabric.DPI / 25.4; case 'cm': return number * fabric.DPI / 2.54; case 'in': return number * fabric.DPI; case 'pt': return number * fabric.DPI / 72; // or * 4 / 3 case 'pc': return number * fabric.DPI / 72 * 12; // or * 16 default: return number; } }, /** * Function which always returns `false`. * @static * @memberOf fabric.util * @return {Boolean} */ falseFunction: function() { return false; }, /** * Returns klass "Class" object of given namespace * @memberOf fabric.util * @param {String} type Type of object (eg. 'circle') * @param {String} namespace Namespace to get klass "Class" object from * @return {Object} klass "Class" */ getKlass: function(type, namespace) { // capitalize first letter only type = fabric.util.string.camelize(type.charAt(0).toUpperCase() + type.slice(1)); return fabric.util.resolveNamespace(namespace)[type]; }, /** * Returns object of given namespace * @memberOf fabric.util * @param {String} namespace Namespace string e.g. 'fabric.Image.filter' or 'fabric' * @return {Object} Object for given namespace (default fabric) */ resolveNamespace: function(namespace) { if (!namespace) { return fabric; } var parts = namespace.split('.'), len = parts.length, obj = global || fabric.window; for (var i = 0; i < len; ++i) { obj = obj[parts[i]]; } return obj; }, /** * Loads image element from given url and passes it to a callback * @memberOf fabric.util * @param {String} url URL representing an image * @param {Function} callback Callback; invoked with loaded image * @param {Any} [context] Context to invoke callback in * @param {Object} [crossOrigin] crossOrigin value to set image element to */ loadImage: function(url, callback, context, crossOrigin) { if (!url) { callback && callback.call(context, url); return; } var img = fabric.util.createImage(); /** @ignore */ img.onload = function () { callback && callback.call(context, img); img = img.onload = img.onerror = null; }; /** @ignore */ img.onerror = function() { fabric.log('Error loading ' + img.src); callback && callback.call(context, null, true); img = img.onload = img.onerror = null; }; // data-urls appear to be buggy with crossOrigin // https://github.com/kangax/fabric.js/commit/d0abb90f1cd5c5ef9d2a94d3fb21a22330da3e0a#commitcomment-4513767 // see https://code.google.com/p/chromium/issues/detail?id=315152 // https://bugzilla.mozilla.org/show_bug.cgi?id=935069 if (url.indexOf('data') !== 0 && typeof crossOrigin !== 'undefined') { img.crossOrigin = crossOrigin; } img.src = url; }, /** * Creates corresponding fabric instances from their object representations * @static * @memberOf fabric.util * @param {Array} objects Objects to enliven * @param {Function} callback Callback to invoke when all objects are created * @param {String} namespace Namespace to get klass "Class" object from * @param {Function} reviver Method for further parsing of object elements, * called after each fabric object created. */ enlivenObjects: function(objects, callback, namespace, reviver) { objects = objects || [ ]; function onLoaded() { if (++numLoadedObjects === numTotalObjects) { callback && callback(enlivenedObjects); } } var enlivenedObjects = [ ], numLoadedObjects = 0, numTotalObjects = objects.length; if (!numTotalObjects) { callback && callback(enlivenedObjects); return; } objects.forEach(function (o, index) { // if sparse array if (!o || !o.type) { onLoaded(); return; } var klass = fabric.util.getKlass(o.type, namespace); if (klass.async) { klass.fromObject(o, function (obj, error) { if (!error) { enlivenedObjects[index] = obj; reviver && reviver(o, enlivenedObjects[index]); } onLoaded(); }); } else { enlivenedObjects[index] = klass.fromObject(o); reviver && reviver(o, enlivenedObjects[index]); onLoaded(); } }); }, /** * Groups SVG elements (usually those retrieved from SVG document) * @static * @memberOf fabric.util * @param {Array} elements SVG elements to group * @param {Object} [options] Options object * @return {fabric.Object|fabric.PathGroup} */ groupSVGElements: function(elements, options, path) { var object; object = new fabric.PathGroup(elements, options); if (typeof path !== 'undefined') { object.setSourcePath(path); } return object; }, /** * Populates an object with properties of another object * @static * @memberOf fabric.util * @param {Object} source Source object * @param {Object} destination Destination object * @return {Array} properties Propertie names to include */ populateWithProperties: function(source, destination, properties) { if (properties && Object.prototype.toString.call(properties) === '[object Array]') { for (var i = 0, len = properties.length; i < len; i++) { if (properties[i] in source) { destination[properties[i]] = source[properties[i]]; } } } }, /** * Draws a dashed line between two points * * This method is used to draw dashed line around selection area. * See dotted stroke in canvas * * @param {CanvasRenderingContext2D} ctx context * @param {Number} x start x coordinate * @param {Number} y start y coordinate * @param {Number} x2 end x coordinate * @param {Number} y2 end y coordinate * @param {Array} da dash array pattern */ drawDashedLine: function(ctx, x, y, x2, y2, da) { var dx = x2 - x, dy = y2 - y, len = sqrt(dx * dx + dy * dy), rot = atan2(dy, dx), dc = da.length, di = 0, draw = true; ctx.save(); ctx.translate(x, y); ctx.moveTo(0, 0); ctx.rotate(rot); x = 0; while (len > x) { x += da[di++ % dc]; if (x > len) { x = len; } ctx[draw ? 'lineTo' : 'moveTo'](x, 0); draw = !draw; } ctx.restore(); }, /** * Creates canvas element and initializes it via excanvas if necessary * @static * @memberOf fabric.util * @param {CanvasElement} [canvasEl] optional canvas element to initialize; * when not given, element is created implicitly * @return {CanvasElement} initialized canvas element */ createCanvasElement: function(canvasEl) { canvasEl || (canvasEl = fabric.document.createElement('canvas')); //jscs:disable requireCamelCaseOrUpperCaseIdentifiers if (!canvasEl.getContext && typeof G_vmlCanvasManager !== 'undefined') { G_vmlCanvasManager.initElement(canvasEl); } //jscs:enable requireCamelCaseOrUpperCaseIdentifiers return canvasEl; }, /** * Creates image element (works on client and node) * @static * @memberOf fabric.util * @return {HTMLImageElement} HTML image element */ createImage: function() { return fabric.isLikelyNode ? new (require('canvas').Image)() : fabric.document.createElement('img'); }, /** * Creates accessors (getXXX, setXXX) for a "class", based on "stateProperties" array * @static * @memberOf fabric.util * @param {Object} klass "Class" to create accessors for */ createAccessors: function(klass) { var proto = klass.prototype; for (var i = proto.stateProperties.length; i--; ) { var propName = proto.stateProperties[i], capitalizedPropName = propName.charAt(0).toUpperCase() + propName.slice(1), setterName = 'set' + capitalizedPropName, getterName = 'get' + capitalizedPropName; // using `new Function` for better introspection if (!proto[getterName]) { proto[getterName] = (function(property) { return new Function('return this.get("' + property + '")'); })(propName); } if (!proto[setterName]) { proto[setterName] = (function(property) { return new Function('value', 'return this.set("' + property + '", value)'); })(propName); } } }, /** * @static * @memberOf fabric.util * @param {fabric.Object} receiver Object implementing `clipTo` method * @param {CanvasRenderingContext2D} ctx Context to clip */ clipContext: function(receiver, ctx) { ctx.save(); ctx.beginPath(); receiver.clipTo(ctx); ctx.clip(); }, /** * Multiply matrix A by matrix B to nest transformations * @static * @memberOf fabric.util * @param {Array} matrixA First transformMatrix * @param {Array} matrixB Second transformMatrix * @return {Array} The product of the two transform matrices */ multiplyTransformMatrices: function(matrixA, matrixB) { // Matrix multiply matrixA * matrixB var a = [ [matrixA[0], matrixA[2], matrixA[4]], [matrixA[1], matrixA[3], matrixA[5]], [0, 0, 1 ] ], b = [ [matrixB[0], matrixB[2], matrixB[4]], [matrixB[1], matrixB[3], matrixB[5]], [0, 0, 1 ] ], result = []; for (var r = 0; r < 3; r++) { result[r] = []; for (var c = 0; c < 3; c++) { var sum = 0; for (var k = 0; k < 3; k++) { sum += a[r][k] * b[k][c]; } result[r][c] = sum; } } return [ result[0][0], result[1][0], result[0][1], result[1][1], result[0][2], result[1][2] ]; }, /** * Returns string representation of function body * @param {Function} fn Function to get body of * @return {String} Function body */ getFunctionBody: function(fn) { return (String(fn).match(/function[^{]*\{([\s\S]*)\}/) || {})[1]; }, /** * Returns true if context has transparent pixel * at specified location (taking tolerance into account) * @param {CanvasRenderingContext2D} ctx context * @param {Number} x x coordinate * @param {Number} y y coordinate * @param {Number} tolerance Tolerance */ isTransparent: function(ctx, x, y, tolerance) { // If tolerance is > 0 adjust start coords to take into account. // If moves off Canvas fix to 0 if (tolerance > 0) { if (x > tolerance) { x -= tolerance; } else { x = 0; } if (y > tolerance) { y -= tolerance; } else { y = 0; } } var _isTransparent = true, imageData = ctx.getImageData(x, y, (tolerance * 2) || 1, (tolerance * 2) || 1); // Split image data - for tolerance > 1, pixelDataSize = 4; for (var i = 3, l = imageData.data.length; i < l; i += 4) { var temp = imageData.data[i]; _isTransparent = temp <= 0; if (_isTransparent === false) { break; // Stop if colour found } } imageData = null; return _isTransparent; } }; })(typeof exports !== 'undefined' ? exports : this); (function() { var arcToSegmentsCache = { }, segmentToBezierCache = { }, boundsOfCurveCache = { }, _join = Array.prototype.join; /* Adapted from http://dxr.mozilla.org/mozilla-central/source/content/svg/content/src/nsSVGPathDataParser.cpp * by Andrea Bogazzi code is under MPL. if you don't have a copy of the license you can take it here * http://mozilla.org/MPL/2.0/ */ function arcToSegments(toX, toY, rx, ry, large, sweep, rotateX) { var argsString = _join.call(arguments); if (arcToSegmentsCache[argsString]) { return arcToSegmentsCache[argsString]; } var PI = Math.PI, th = rotateX * PI / 180, sinTh = Math.sin(th), cosTh = Math.cos(th), fromX = 0, fromY = 0; rx = Math.abs(rx); ry = Math.abs(ry); var px = -cosTh * toX * 0.5 - sinTh * toY * 0.5, py = -cosTh * toY * 0.5 + sinTh * toX * 0.5, rx2 = rx * rx, ry2 = ry * ry, py2 = py * py, px2 = px * px, pl = rx2 * ry2 - rx2 * py2 - ry2 * px2, root = 0; if (pl < 0) { var s = Math.sqrt(1 - pl/(rx2 * ry2)); rx *= s; ry *= s; } else { root = (large === sweep ? -1.0 : 1.0) * Math.sqrt( pl /(rx2 * py2 + ry2 * px2)); } var cx = root * rx * py / ry, cy = -root * ry * px / rx, cx1 = cosTh * cx - sinTh * cy + toX * 0.5, cy1 = sinTh * cx + cosTh * cy + toY * 0.5, mTheta = calcVectorAngle(1, 0, (px - cx) / rx, (py - cy) / ry), dtheta = calcVectorAngle((px - cx) / rx, (py - cy) / ry, (-px - cx) / rx, (-py - cy) / ry); if (sweep === 0 && dtheta > 0) { dtheta -= 2 * PI; } else if (sweep === 1 && dtheta < 0) { dtheta += 2 * PI; } // Convert into cubic bezier segments <= 90deg var segments = Math.ceil(Math.abs(dtheta / PI * 2)), result = [], mDelta = dtheta / segments, mT = 8 / 3 * Math.sin(mDelta / 4) * Math.sin(mDelta / 4) / Math.sin(mDelta / 2), th3 = mTheta + mDelta; for (var i = 0; i < segments; i++) { result[i] = segmentToBezier(mTheta, th3, cosTh, sinTh, rx, ry, cx1, cy1, mT, fromX, fromY); fromX = result[i][4]; fromY = result[i][5]; mTheta = th3; th3 += mDelta; } arcToSegmentsCache[argsString] = result; return result; } function segmentToBezier(th2, th3, cosTh, sinTh, rx, ry, cx1, cy1, mT, fromX, fromY) { var argsString2 = _join.call(arguments); if (segmentToBezierCache[argsString2]) { return segmentToBezierCache[argsString2]; } var costh2 = Math.cos(th2), sinth2 = Math.sin(th2), costh3 = Math.cos(th3), sinth3 = Math.sin(th3), toX = cosTh * rx * costh3 - sinTh * ry * sinth3 + cx1, toY = sinTh * rx * costh3 + cosTh * ry * sinth3 + cy1, cp1X = fromX + mT * ( - cosTh * rx * sinth2 - sinTh * ry * costh2), cp1Y = fromY + mT * ( - sinTh * rx * sinth2 + cosTh * ry * costh2), cp2X = toX + mT * ( cosTh * rx * sinth3 + sinTh * ry * costh3), cp2Y = toY + mT * ( sinTh * rx * sinth3 - cosTh * ry * costh3); segmentToBezierCache[argsString2] = [ cp1X, cp1Y, cp2X, cp2Y, toX, toY ]; return segmentToBezierCache[argsString2]; } /* * Private */ function calcVectorAngle(ux, uy, vx, vy) { var ta = Math.atan2(uy, ux), tb = Math.atan2(vy, vx); if (tb >= ta) { return tb - ta; } else { return 2 * Math.PI - (ta - tb); } } /** * Draws arc * @param {CanvasRenderingContext2D} ctx * @param {Number} fx * @param {Number} fy * @param {Array} coords */ fabric.util.drawArc = function(ctx, fx, fy, coords) { var rx = coords[0], ry = coords[1], rot = coords[2], large = coords[3], sweep = coords[4], tx = coords[5], ty = coords[6], segs = [[ ], [ ], [ ], [ ]], segsNorm = arcToSegments(tx - fx, ty - fy, rx, ry, large, sweep, rot); for (var i = 0, len = segsNorm.length; i < len; i++) { segs[i][0] = segsNorm[i][0] + fx; segs[i][1] = segsNorm[i][1] + fy; segs[i][2] = segsNorm[i][2] + fx; segs[i][3] = segsNorm[i][3] + fy; segs[i][4] = segsNorm[i][4] + fx; segs[i][5] = segsNorm[i][5] + fy; ctx.bezierCurveTo.apply(ctx, segs[i]); } }; /** * Calculate bounding box of a elliptic-arc * @param {Number} fx start point of arc * @param {Number} fy * @param {Number} rx horizontal radius * @param {Number} ry vertical radius * @param {Number} rot angle of horizontal axe * @param {Number} large 1 or 0, whatever the arc is the big or the small on the 2 points * @param {Number} sweep 1 or 0, 1 clockwise or counterclockwise direction * @param {Number} tx end point of arc * @param {Number} ty */ fabric.util.getBoundsOfArc = function(fx, fy, rx, ry, rot, large, sweep, tx, ty) { var fromX = 0, fromY = 0, bound = [ ], bounds = [ ], segs = arcToSegments(tx - fx, ty - fy, rx, ry, large, sweep, rot); for (var i = 0, len = segs.length; i < len; i++) { bound = getBoundsOfCurve(fromX, fromY, segs[i][0], segs[i][1], segs[i][2], segs[i][3], segs[i][4], segs[i][5]); bound[0].x += fx; bound[0].y += fy; bound[1].x += fx; bound[1].y += fy; bounds.push(bound[0]); bounds.push(bound[1]); fromX = segs[i][4]; fromY = segs[i][5]; } return bounds; }; /** * Calculate bounding box of a beziercurve * @param {Number} x0 starting point * @param {Number} y0 * @param {Number} x1 first control point * @param {Number} y1 * @param {Number} x2 secondo control point * @param {Number} y2 * @param {Number} x3 end of beizer * @param {Number} y3 */ // taken from http://jsbin.com/ivomiq/56/edit no credits available for that. function getBoundsOfCurve(x0, y0, x1, y1, x2, y2, x3, y3) { var argsString = _join.call(arguments); if (boundsOfCurveCache[argsString]) { return boundsOfCurveCache[argsString]; } var sqrt = Math.sqrt, min = Math.min, max = Math.max, abs = Math.abs, tvalues = [ ], bounds = [[ ], [ ]], a, b, c, t, t1, t2, b2ac, sqrtb2ac; b = 6 * x0 - 12 * x1 + 6 * x2; a = -3 * x0 + 9 * x1 - 9 * x2 + 3 * x3; c = 3 * x1 - 3 * x0; for (var i = 0; i < 2; ++i) { if (i > 0) { b = 6 * y0 - 12 * y1 + 6 * y2; a = -3 * y0 + 9 * y1 - 9 * y2 + 3 * y3; c = 3 * y1 - 3 * y0; } if (abs(a) < 1e-12) { if (abs(b) < 1e-12) { continue; } t = -c / b; if (0 < t && t < 1) { tvalues.push(t); } continue; } b2ac = b * b - 4 * c * a; if (b2ac < 0) { continue; } sqrtb2ac = sqrt(b2ac); t1 = (-b + sqrtb2ac) / (2 * a); if (0 < t1 && t1 < 1) { tvalues.push(t1); } t2 = (-b - sqrtb2ac) / (2 * a); if (0 < t2 && t2 < 1) { tvalues.push(t2); } } var x, y, j = tvalues.length, jlen = j, mt; while (j--) { t = tvalues[j]; mt = 1 - t; x = (mt * mt * mt * x0) + (3 * mt * mt * t * x1) + (3 * mt * t * t * x2) + (t * t * t * x3); bounds[0][j] = x; y = (mt * mt * mt * y0) + (3 * mt * mt * t * y1) + (3 * mt * t * t * y2) + (t * t * t * y3); bounds[1][j] = y; } bounds[0][jlen] = x0; bounds[1][jlen] = y0; bounds[0][jlen + 1] = x3; bounds[1][jlen + 1] = y3; var result = [ { x: min.apply(null, bounds[0]), y: min.apply(null, bounds[1]) }, { x: max.apply(null, bounds[0]), y: max.apply(null, bounds[1]) } ]; boundsOfCurveCache[argsString] = result; return result; } fabric.util.getBoundsOfCurve = getBoundsOfCurve; })(); (function() { var slice = Array.prototype.slice; /* _ES5_COMPAT_START_ */ if (!Array.prototype.indexOf) { /** * Finds index of an element in an array * @param {Any} searchElement * @param {Number} [fromIndex] * @return {Number} */ Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) { if (this === void 0 || this === null) { throw new TypeError(); } var t = Object(this), len = t.length >>> 0; if (len === 0) { return -1; } var n = 0; if (arguments.length > 0) { n = Number(arguments[1]); if (n !== n) { // shortcut for verifying if it's NaN n = 0; } else if (n !== 0 && n !== Number.POSITIVE_INFINITY && n !== Number.NEGATIVE_INFINITY) { n = (n > 0 || -1) * Math.floor(Math.abs(n)); } } if (n >= len) { return -1; } var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0); for (; k < len; k++) { if (k in t && t[k] === searchElement) { return k; } } return -1; }; } if (!Array.prototype.forEach) { /** * Iterates an array, invoking callback for each element * @param {Function} fn Callback to invoke for each element * @param {Object} [context] Context to invoke callback in * @return {Array} */ Array.prototype.forEach = function(fn, context) { for (var i = 0, len = this.length >>> 0; i < len; i++) { if (i in this) { fn.call(context, this[i], i, this); } } }; } if (!Array.prototype.map) { /** * Returns a result of iterating over an array, invoking callback for each element * @param {Function} fn Callback to invoke for each element * @param {Object} [context] Context to invoke callback in * @return {Array} */ Array.prototype.map = function(fn, context) { var result = [ ]; for (var i = 0, len = this.length >>> 0; i < len; i++) { if (i in this) { result[i] = fn.call(context, this[i], i, this); } } return result; }; } if (!Array.prototype.every) { /** * Returns true if a callback returns truthy value for all elements in an array * @param {Function} fn Callback to invoke for each element * @param {Object} [context] Context to invoke callback in * @return {Boolean} */ Array.prototype.every = function(fn, context) { for (var i = 0, len = this.length >>> 0; i < len; i++) { if (i in this && !fn.call(context, this[i], i, this)) { return false; } } return true; }; } if (!Array.prototype.some) { /** * Returns true if a callback returns truthy value for at least one element in an array * @param {Function} fn Callback to invoke for each element * @param {Object} [context] Context to invoke callback in * @return {Boolean} */ Array.prototype.some = function(fn, context) { for (var i = 0, len = this.length >>> 0; i < len; i++) { if (i in this && fn.call(context, this[i], i, this)) { return true; } } return false; }; } if (!Array.prototype.filter) { /** * Returns the result of iterating over elements in an array * @param {Function} fn Callback to invoke for each element * @param {Object} [context] Context to invoke callback in * @return {Array} */ Array.prototype.filter = function(fn, context) { var result = [ ], val; for (var i = 0, len = this.length >>> 0; i < len; i++) { if (i in this) { val = this[i]; // in case fn mutates this if (fn.call(context, val, i, this)) { result.push(val); } } } return result; }; } if (!Array.prototype.reduce) { /** * Returns "folded" (reduced) result of iterating over elements in an array * @param {Function} fn Callback to invoke for each element * @param {Object} [initial] Object to use as the first argument to the first call of the callback * @return {Any} */ Array.prototype.reduce = function(fn /*, initial*/) { var len = this.length >>> 0, i = 0, rv; if (arguments.length > 1) { rv = arguments[1]; } else { do { if (i in this) { rv = this[i++]; break; } // if array contains no values, no initial value to return if (++i >= len) { throw new TypeError(); } } while (true); } for (; i < len; i++) { if (i in this) { rv = fn.call(null, rv, this[i], i, this); } } return rv; }; } /* _ES5_COMPAT_END_ */ /** * Invokes method on all items in a given array * @memberOf fabric.util.array * @param {Array} array Array to iterate over * @param {String} method Name of a method to invoke * @return {Array} */ function invoke(array, method) { var args = slice.call(arguments, 2), result = [ ]; for (var i = 0, len = array.length; i < len; i++) { result[i] = args.length ? array[i][method].apply(array[i], args) : array[i][method].call(array[i]); } return result; } /** * Finds maximum value in array (not necessarily "first" one) * @memberOf fabric.util.array * @param {Array} array Array to iterate over * @param {String} byProperty * @return {Any} */ function max(array, byProperty) { return find(array, byProperty, function(value1, value2) { return value1 >= value2; }); } /** * Finds minimum value in array (not necessarily "first" one) * @memberOf fabric.util.array * @param {Array} array Array to iterate over * @param {String} byProperty * @return {Any} */ function min(array, byProperty) { return find(array, byProperty, function(value1, value2) { return value1 < value2; }); } /** * @private */ function find(array, byProperty, condition) { if (!array || array.length === 0) { return; } var i = array.length - 1, result = byProperty ? array[i][byProperty] : array[i]; if (byProperty) { while (i--) { if (condition(array[i][byProperty], result)) { result = array[i][byProperty]; } } } else { while (i--) { if (condition(array[i], result)) { result = array[i]; } } } return result; } /** * @namespace fabric.util.array */ fabric.util.array = { invoke: invoke, min: min, max: max }; })(); (function() { /** * Copies all enumerable properties of one object to another * @memberOf fabric.util.object * @param {Object} destination Where to copy to * @param {Object} source Where to copy from * @return {Object} */ function extend(destination, source) { // JScript DontEnum bug is not taken care of for (var property in source) { destination[property] = source[property]; } return destination; } /** * Creates an empty object and copies all enumerable properties of another object to it * @memberOf fabric.util.object * @param {Object} object Object to clone * @return {Object} */ function clone(object) { return extend({ }, object); } /** @namespace fabric.util.object */ fabric.util.object = { extend: extend, clone: clone }; })(); (function() { /* _ES5_COMPAT_START_ */ if (!String.prototype.trim) { /** * Trims a string (removing whitespace from the beginning and the end) * @function external:String#trim * @see String#trim on MDN */ String.prototype.trim = function () { // this trim is not fully ES3 or ES5 compliant, but it should cover most cases for now return this.replace(/^[\s\xA0]+/, '').replace(/[\s\xA0]+$/, ''); }; } /* _ES5_COMPAT_END_ */ /** * Camelizes a string * @memberOf fabric.util.string * @param {String} string String to camelize * @return {String} Camelized version of a string */ function camelize(string) { return string.replace(/-+(.)?/g, function(match, character) { return character ? character.toUpperCase() : ''; }); } /** * Capitalizes a string * @memberOf fabric.util.string * @param {String} string String to capitalize * @param {Boolean} [firstLetterOnly] If true only first letter is capitalized * and other letters stay untouched, if false first letter is capitalized * and other letters are converted to lowercase. * @return {String} Capitalized version of a string */ function capitalize(string, firstLetterOnly) { return string.charAt(0).toUpperCase() + (firstLetterOnly ? string.slice(1) : string.slice(1).toLowerCase()); } /** * Escapes XML in a string * @memberOf fabric.util.string * @param {String} string String to escape * @return {String} Escaped version of a string */ function escapeXml(string) { return string.replace(/&/g, '&') .replace(/"/g, '"') .replace(/'/g, ''') .replace(//g, '>'); } /** * String utilities * @namespace fabric.util.string */ fabric.util.string = { camelize: camelize, capitalize: capitalize, escapeXml: escapeXml }; }()); /* _ES5_COMPAT_START_ */ (function() { var slice = Array.prototype.slice, apply = Function.prototype.apply, Dummy = function() { }; if (!Function.prototype.bind) { /** * Cross-browser approximation of ES5 Function.prototype.bind (not fully spec conforming) * @see Function#bind on MDN * @param {Object} thisArg Object to bind function to * @param {Any[]} [...] Values to pass to a bound function * @return {Function} */ Function.prototype.bind = function(thisArg) { var _this = this, args = slice.call(arguments, 1), bound; if (args.length) { bound = function() { return apply.call(_this, this instanceof Dummy ? this : thisArg, args.concat(slice.call(arguments))); }; } else { /** @ignore */ bound = function() { return apply.call(_this, this instanceof Dummy ? this : thisArg, arguments); }; } Dummy.prototype = this.prototype; bound.prototype = new Dummy(); return bound; }; } })(); /* _ES5_COMPAT_END_ */ (function() { var slice = Array.prototype.slice, emptyFunction = function() { }, IS_DONTENUM_BUGGY = (function() { for (var p in { toString: 1 }) { if (p === 'toString') { return false; } } return true; })(), /** @ignore */ addMethods = function(klass, source, parent) { for (var property in source) { if (property in klass.prototype && typeof klass.prototype[property] === 'function' && (source[property] + '').indexOf('callSuper') > -1) { klass.prototype[property] = (function(property) { return function() { var superclass = this.constructor.superclass; this.constructor.superclass = parent; var returnValue = source[property].apply(this, arguments); this.constructor.superclass = superclass; if (property !== 'initialize') { return returnValue; } }; })(property); } else { klass.prototype[property] = source[property]; } if (IS_DONTENUM_BUGGY) { if (source.toString !== Object.prototype.toString) { klass.prototype.toString = source.toString; } if (source.valueOf !== Object.prototype.valueOf) { klass.prototype.valueOf = source.valueOf; } } } }; function Subclass() { } function callSuper(methodName) { var fn = this.constructor.superclass.prototype[methodName]; return (arguments.length > 1) ? fn.apply(this, slice.call(arguments, 1)) : fn.call(this); } /** * Helper for creation of "classes". * @memberOf fabric.util * @param {Function} [parent] optional "Class" to inherit from * @param {Object} [properties] Properties shared by all instances of this class * (be careful modifying objects defined here as this would affect all instances) */ function createClass() { var parent = null, properties = slice.call(arguments, 0); if (typeof properties[0] === 'function') { parent = properties.shift(); } function klass() { this.initialize.apply(this, arguments); } klass.superclass = parent; klass.subclasses = [ ]; if (parent) { Subclass.prototype = parent.prototype; klass.prototype = new Subclass(); parent.subclasses.push(klass); } for (var i = 0, length = properties.length; i < length; i++) { addMethods(klass, properties[i], parent); } if (!klass.prototype.initialize) { klass.prototype.initialize = emptyFunction; } klass.prototype.constructor = klass; klass.prototype.callSuper = callSuper; return klass; } fabric.util.createClass = createClass; })(); (function () { var unknown = 'unknown'; /* EVENT HANDLING */ function areHostMethods(object) { var methodNames = Array.prototype.slice.call(arguments, 1), t, i, len = methodNames.length; for (i = 0; i < len; i++) { t = typeof object[methodNames[i]]; if (!(/^(?:function|object|unknown)$/).test(t)) { return false; } } return true; } /** @ignore */ var getElement, setElement, getUniqueId = (function () { var uid = 0; return function (element) { return element.__uniqueID || (element.__uniqueID = 'uniqueID__' + uid++); }; })(); (function () { var elements = { }; /** @ignore */ getElement = function (uid) { return elements[uid]; }; /** @ignore */ setElement = function (uid, element) { elements[uid] = element; }; })(); function createListener(uid, handler) { return { handler: handler, wrappedHandler: createWrappedHandler(uid, handler) }; } function createWrappedHandler(uid, handler) { return function (e) { handler.call(getElement(uid), e || fabric.window.event); }; } function createDispatcher(uid, eventName) { return function (e) { if (handlers[uid] && handlers[uid][eventName]) { var handlersForEvent = handlers[uid][eventName]; for (var i = 0, len = handlersForEvent.length; i < len; i++) { handlersForEvent[i].call(this, e || fabric.window.event); } } }; } var shouldUseAddListenerRemoveListener = ( areHostMethods(fabric.document.documentElement, 'addEventListener', 'removeEventListener') && areHostMethods(fabric.window, 'addEventListener', 'removeEventListener')), shouldUseAttachEventDetachEvent = ( areHostMethods(fabric.document.documentElement, 'attachEvent', 'detachEvent') && areHostMethods(fabric.window, 'attachEvent', 'detachEvent')), // IE branch listeners = { }, // DOM L0 branch handlers = { }, addListener, removeListener; if (shouldUseAddListenerRemoveListener) { /** @ignore */ addListener = function (element, eventName, handler) { element.addEventListener(eventName, handler, false); }; /** @ignore */ removeListener = function (element, eventName, handler) { element.removeEventListener(eventName, handler, false); }; } else if (shouldUseAttachEventDetachEvent) { /** @ignore */ addListener = function (element, eventName, handler) { var uid = getUniqueId(element); setElement(uid, element); if (!listeners[uid]) { listeners[uid] = { }; } if (!listeners[uid][eventName]) { listeners[uid][eventName] = [ ]; } var listener = createListener(uid, handler); listeners[uid][eventName].push(listener); element.attachEvent('on' + eventName, listener.wrappedHandler); }; /** @ignore */ removeListener = function (element, eventName, handler) { var uid = getUniqueId(element), listener; if (listeners[uid] && listeners[uid][eventName]) { for (var i = 0, len = listeners[uid][eventName].length; i < len; i++) { listener = listeners[uid][eventName][i]; if (listener && listener.handler === handler) { element.detachEvent('on' + eventName, listener.wrappedHandler); listeners[uid][eventName][i] = null; } } } }; } else { /** @ignore */ addListener = function (element, eventName, handler) { var uid = getUniqueId(element); if (!handlers[uid]) { handlers[uid] = { }; } if (!handlers[uid][eventName]) { handlers[uid][eventName] = [ ]; var existingHandler = element['on' + eventName]; if (existingHandler) { handlers[uid][eventName].push(existingHandler); } element['on' + eventName] = createDispatcher(uid, eventName); } handlers[uid][eventName].push(handler); }; /** @ignore */ removeListener = function (element, eventName, handler) { var uid = getUniqueId(element); if (handlers[uid] && handlers[uid][eventName]) { var handlersForEvent = handlers[uid][eventName]; for (var i = 0, len = handlersForEvent.length; i < len; i++) { if (handlersForEvent[i] === handler) { handlersForEvent.splice(i, 1); } } } }; } /** * Adds an event listener to an element * @function * @memberOf fabric.util * @param {HTMLElement} element * @param {String} eventName * @param {Function} handler */ fabric.util.addListener = addListener; /** * Removes an event listener from an element * @function * @memberOf fabric.util * @param {HTMLElement} element * @param {String} eventName * @param {Function} handler */ fabric.util.removeListener = removeListener; /** * Cross-browser wrapper for getting event's coordinates * @memberOf fabric.util * @param {Event} event Event object * @param {HTMLCanvasElement} upperCanvasEl <canvas> element on which object selection is drawn */ function getPointer(event, upperCanvasEl) { event || (event = fabric.window.event); var element = event.target || (typeof event.srcElement !== unknown ? event.srcElement : null), scroll = fabric.util.getScrollLeftTop(element, upperCanvasEl); return { x: pointerX(event) + scroll.left, y: pointerY(event) + scroll.top }; } var pointerX = function(event) { // looks like in IE (<9) clientX at certain point (apparently when mouseup fires on VML element) // is represented as COM object, with all the consequences, like "unknown" type and error on [[Get]] // need to investigate later return (typeof event.clientX !== unknown ? event.clientX : 0); }, pointerY = function(event) { return (typeof event.clientY !== unknown ? event.clientY : 0); }; function _getPointer(event, pageProp, clientProp) { var touchProp = event.type === 'touchend' ? 'changedTouches' : 'touches'; return (event[touchProp] && event[touchProp][0] ? (event[touchProp][0][pageProp] - (event[touchProp][0][pageProp] - event[touchProp][0][clientProp])) || event[clientProp] : event[clientProp]); } if (fabric.isTouchSupported) { pointerX = function(event) { return _getPointer(event, 'pageX', 'clientX'); }; pointerY = function(event) { return _getPointer(event, 'pageY', 'clientY'); }; } fabric.util.getPointer = getPointer; fabric.util.object.extend(fabric.util, fabric.Observable); })(); (function () { /** * Cross-browser wrapper for setting element's style * @memberOf fabric.util * @param {HTMLElement} element * @param {Object} styles * @return {HTMLElement} Element that was passed as a first argument */ function setStyle(element, styles) { var elementStyle = element.style; if (!elementStyle) { return element; } if (typeof styles === 'string') { element.style.cssText += ';' + styles; return styles.indexOf('opacity') > -1 ? setOpacity(element, styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) : element; } for (var property in styles) { if (property === 'opacity') { setOpacity(element, styles[property]); } else { var normalizedProperty = (property === 'float' || property === 'cssFloat') ? (typeof elementStyle.styleFloat === 'undefined' ? 'cssFloat' : 'styleFloat') : property; elementStyle[normalizedProperty] = styles[property]; } } return element; } var parseEl = fabric.document.createElement('div'), supportsOpacity = typeof parseEl.style.opacity === 'string', supportsFilters = typeof parseEl.style.filter === 'string', reOpacity = /alpha\s*\(\s*opacity\s*=\s*([^\)]+)\)/, /** @ignore */ setOpacity = function (element) { return element; }; if (supportsOpacity) { /** @ignore */ setOpacity = function(element, value) { element.style.opacity = value; return element; }; } else if (supportsFilters) { /** @ignore */ setOpacity = function(element, value) { var es = element.style; if (element.currentStyle && !element.currentStyle.hasLayout) { es.zoom = 1; } if (reOpacity.test(es.filter)) { value = value >= 0.9999 ? '' : ('alpha(opacity=' + (value * 100) + ')'); es.filter = es.filter.replace(reOpacity, value); } else { es.filter += ' alpha(opacity=' + (value * 100) + ')'; } return element; }; } fabric.util.setStyle = setStyle; })(); (function() { var _slice = Array.prototype.slice; /** * Takes id and returns an element with that id (if one exists in a document) * @memberOf fabric.util * @param {String|HTMLElement} id * @return {HTMLElement|null} */ function getById(id) { return typeof id === 'string' ? fabric.document.getElementById(id) : id; } var sliceCanConvertNodelists, /** * Converts an array-like object (e.g. arguments or NodeList) to an array * @memberOf fabric.util * @param {Object} arrayLike * @return {Array} */ toArray = function(arrayLike) { return _slice.call(arrayLike, 0); }; try { sliceCanConvertNodelists = toArray(fabric.document.childNodes) instanceof Array; } catch (err) { } if (!sliceCanConvertNodelists) { toArray = function(arrayLike) { var arr = new Array(arrayLike.length), i = arrayLike.length; while (i--) { arr[i] = arrayLike[i]; } return arr; }; } /** * Creates specified element with specified attributes * @memberOf fabric.util * @param {String} tagName Type of an element to create * @param {Object} [attributes] Attributes to set on an element * @return {HTMLElement} Newly created element */ function makeElement(tagName, attributes) { var el = fabric.document.createElement(tagName); for (var prop in attributes) { if (prop === 'class') { el.className = attributes[prop]; } else if (prop === 'for') { el.htmlFor = attributes[prop]; } else { el.setAttribute(prop, attributes[prop]); } } return el; } /** * Adds class to an element * @memberOf fabric.util * @param {HTMLElement} element Element to add class to * @param {String} className Class to add to an element */ function addClass(element, className) { if (element && (' ' + element.className + ' ').indexOf(' ' + className + ' ') === -1) { element.className += (element.className ? ' ' : '') + className; } } /** * Wraps element with another element * @memberOf fabric.util * @param {HTMLElement} element Element to wrap * @param {HTMLElement|String} wrapper Element to wrap with * @param {Object} [attributes] Attributes to set on a wrapper * @return {HTMLElement} wrapper */ function wrapElement(element, wrapper, attributes) { if (typeof wrapper === 'string') { wrapper = makeElement(wrapper, attributes); } if (element.parentNode) { element.parentNode.replaceChild(wrapper, element); } wrapper.appendChild(element); return wrapper; } /** * Returns element scroll offsets * @memberOf fabric.util * @param {HTMLElement} element Element to operate on * @param {HTMLElement} upperCanvasEl Upper canvas element * @return {Object} Object with left/top values */ function getScrollLeftTop(element, upperCanvasEl) { var firstFixedAncestor, origElement, left = 0, top = 0, docElement = fabric.document.documentElement, body = fabric.document.body || { scrollLeft: 0, scrollTop: 0 }; origElement = element; while (element && element.parentNode && !firstFixedAncestor) { element = element.parentNode; if (element.nodeType === 1 && fabric.util.getElementStyle(element, 'position') === 'fixed') { firstFixedAncestor = element; } if (element.nodeType === 1 && origElement !== upperCanvasEl && fabric.util.getElementStyle(element, 'position') === 'absolute') { left = 0; top = 0; } else if (element === fabric.document) { left = body.scrollLeft || docElement.scrollLeft || 0; top = body.scrollTop || docElement.scrollTop || 0; } else { left += element.scrollLeft || 0; top += element.scrollTop || 0; } } return { left: left, top: top }; } /** * Returns offset for a given element * @function * @memberOf fabric.util * @param {HTMLElement} element Element to get offset for * @return {Object} Object with "left" and "top" properties */ function getElementOffset(element) { var docElem, doc = element && element.ownerDocument, box = { left: 0, top: 0 }, offset = { left: 0, top: 0 }, scrollLeftTop, offsetAttributes = { borderLeftWidth: 'left', borderTopWidth: 'top', paddingLeft: 'left', paddingTop: 'top' }; if (!doc) { return { left: 0, top: 0 }; } for (var attr in offsetAttributes) { offset[offsetAttributes[attr]] += parseInt(getElementStyle(element, attr), 10) || 0; } docElem = doc.documentElement; if ( typeof element.getBoundingClientRect !== 'undefined' ) { box = element.getBoundingClientRect(); } scrollLeftTop = fabric.util.getScrollLeftTop(element, null); return { left: box.left + scrollLeftTop.left - (docElem.clientLeft || 0) + offset.left, top: box.top + scrollLeftTop.top - (docElem.clientTop || 0) + offset.top }; } /** * Returns style attribute value of a given element * @memberOf fabric.util * @param {HTMLElement} element Element to get style attribute for * @param {String} attr Style attribute to get for element * @return {String} Style attribute value of the given element. */ var getElementStyle; if (fabric.document.defaultView && fabric.document.defaultView.getComputedStyle) { getElementStyle = function(element, attr) { var style = fabric.document.defaultView.getComputedStyle(element, null); return style ? style[attr] : undefined; }; } else { getElementStyle = function(element, attr) { var value = element.style[attr]; if (!value && element.currentStyle) { value = element.currentStyle[attr]; } return value; }; } (function () { var style = fabric.document.documentElement.style, selectProp = 'userSelect' in style ? 'userSelect' : 'MozUserSelect' in style ? 'MozUserSelect' : 'WebkitUserSelect' in style ? 'WebkitUserSelect' : 'KhtmlUserSelect' in style ? 'KhtmlUserSelect' : ''; /** * Makes element unselectable * @memberOf fabric.util * @param {HTMLElement} element Element to make unselectable * @return {HTMLElement} Element that was passed in */ function makeElementUnselectable(element) { if (typeof element.onselectstart !== 'undefined') { element.onselectstart = fabric.util.falseFunction; } if (selectProp) { element.style[selectProp] = 'none'; } else if (typeof element.unselectable === 'string') { element.unselectable = 'on'; } return element; } /** * Makes element selectable * @memberOf fabric.util * @param {HTMLElement} element Element to make selectable * @return {HTMLElement} Element that was passed in */ function makeElementSelectable(element) { if (typeof element.onselectstart !== 'undefined') { element.onselectstart = null; } if (selectProp) { element.style[selectProp] = ''; } else if (typeof element.unselectable === 'string') { element.unselectable = ''; } return element; } fabric.util.makeElementUnselectable = makeElementUnselectable; fabric.util.makeElementSelectable = makeElementSelectable; })(); (function() { /** * Inserts a script element with a given url into a document; invokes callback, when that script is finished loading * @memberOf fabric.util * @param {String} url URL of a script to load * @param {Function} callback Callback to execute when script is finished loading */ function getScript(url, callback) { var headEl = fabric.document.getElementsByTagName('head')[0], scriptEl = fabric.document.createElement('script'), loading = true; /** @ignore */ scriptEl.onload = /** @ignore */ scriptEl.onreadystatechange = function(e) { if (loading) { if (typeof this.readyState === 'string' && this.readyState !== 'loaded' && this.readyState !== 'complete') { return; } loading = false; callback(e || fabric.window.event); scriptEl = scriptEl.onload = scriptEl.onreadystatechange = null; } }; scriptEl.src = url; headEl.appendChild(scriptEl); // causes issue in Opera // headEl.removeChild(scriptEl); } fabric.util.getScript = getScript; })(); fabric.util.getById = getById; fabric.util.toArray = toArray; fabric.util.makeElement = makeElement; fabric.util.addClass = addClass; fabric.util.wrapElement = wrapElement; fabric.util.getScrollLeftTop = getScrollLeftTop; fabric.util.getElementOffset = getElementOffset; fabric.util.getElementStyle = getElementStyle; })(); (function() { function addParamToUrl(url, param) { return url + (/\?/.test(url) ? '&' : '?') + param; } var makeXHR = (function() { var factories = [ function() { return new ActiveXObject('Microsoft.XMLHTTP'); }, function() { return new ActiveXObject('Msxml2.XMLHTTP'); }, function() { return new ActiveXObject('Msxml2.XMLHTTP.3.0'); }, function() { return new XMLHttpRequest(); } ]; for (var i = factories.length; i--; ) { try { var req = factories[i](); if (req) { return factories[i]; } } catch (err) { } } })(); function emptyFn() { } /** * Cross-browser abstraction for sending XMLHttpRequest * @memberOf fabric.util * @param {String} url URL to send XMLHttpRequest to * @param {Object} [options] Options object * @param {String} [options.method="GET"] * @param {Function} options.onComplete Callback to invoke when request is completed * @return {XMLHttpRequest} request */ function request(url, options) { options || (options = { }); var method = options.method ? options.method.toUpperCase() : 'GET', onComplete = options.onComplete || function() { }, xhr = makeXHR(), body; /** @ignore */ xhr.onreadystatechange = function() { if (xhr.readyState === 4) { onComplete(xhr); xhr.onreadystatechange = emptyFn; } }; if (method === 'GET') { body = null; if (typeof options.parameters === 'string') { url = addParamToUrl(url, options.parameters); } } xhr.open(method, url, true); if (method === 'POST' || method === 'PUT') { xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); } xhr.send(body); return xhr; } fabric.util.request = request; })(); /** * Wrapper around `console.log` (when available) * @param {Any} [values] Values to log */ fabric.log = function() { }; /** * Wrapper around `console.warn` (when available) * @param {Any} [values] Values to log as a warning */ fabric.warn = function() { }; if (typeof console !== 'undefined') { ['log', 'warn'].forEach(function(methodName) { if (typeof console[methodName] !== 'undefined' && console[methodName].apply) { fabric[methodName] = function() { return console[methodName].apply(console, arguments); }; } }); } (function() { /** * Changes value from one to another within certain period of time, invoking callbacks as value is being changed. * @memberOf fabric.util * @param {Object} [options] Animation options * @param {Function} [options.onChange] Callback; invoked on every value change * @param {Function} [options.onComplete] Callback; invoked when value change is completed * @param {Number} [options.startValue=0] Starting value * @param {Number} [options.endValue=100] Ending value * @param {Number} [options.byValue=100] Value to modify the property by * @param {Function} [options.easing] Easing function * @param {Number} [options.duration=500] Duration of change (in ms) */ function animate(options) { requestAnimFrame(function(timestamp) { options || (options = { }); var start = timestamp || +new Date(), duration = options.duration || 500, finish = start + duration, time, onChange = options.onChange || function() { }, abort = options.abort || function() { return false; }, easing = options.easing || function(t, b, c, d) {return -c * Math.cos(t / d * (Math.PI / 2)) + c + b;}, startValue = 'startValue' in options ? options.startValue : 0, endValue = 'endValue' in options ? options.endValue : 100, byValue = options.byValue || endValue - startValue; options.onStart && options.onStart(); (function tick(ticktime) { time = ticktime || +new Date(); var currentTime = time > finish ? duration : (time - start); if (abort()) { options.onComplete && options.onComplete(); return; } onChange(easing(currentTime, startValue, byValue, duration)); if (time > finish) { options.onComplete && options.onComplete(); return; } requestAnimFrame(tick); })(start); }); } var _requestAnimFrame = fabric.window.requestAnimationFrame || fabric.window.webkitRequestAnimationFrame || fabric.window.mozRequestAnimationFrame || fabric.window.oRequestAnimationFrame || fabric.window.msRequestAnimationFrame || function(callback) { fabric.window.setTimeout(callback, 1000 / 60); }; /** * requestAnimationFrame polyfill based on http://paulirish.com/2011/requestanimationframe-for-smart-animating/ * In order to get a precise start time, `requestAnimFrame` should be called as an entry into the method * @memberOf fabric.util * @param {Function} callback Callback to invoke * @param {DOMElement} element optional Element to associate with animation */ function requestAnimFrame() { return _requestAnimFrame.apply(fabric.window, arguments); } fabric.util.animate = animate; fabric.util.requestAnimFrame = requestAnimFrame; })(); (function() { function normalize(a, c, p, s) { if (a < Math.abs(c)) { a = c; s = p / 4; } else { s = p / (2 * Math.PI) * Math.asin(c / a); } return { a: a, c: c, p: p, s: s }; } function elastic(opts, t, d) { return opts.a * Math.pow(2, 10 * (t -= 1)) * Math.sin( (t * d - opts.s) * (2 * Math.PI) / opts.p ); } /** * Cubic easing out * @memberOf fabric.util.ease */ function easeOutCubic(t, b, c, d) { return c * ((t = t / d - 1) * t * t + 1) + b; } /** * Cubic easing in and out * @memberOf fabric.util.ease */ function easeInOutCubic(t, b, c, d) { t /= d/2; if (t < 1) { return c / 2 * t * t * t + b; } return c / 2 * ((t -= 2) * t * t + 2) + b; } /** * Quartic easing in * @memberOf fabric.util.ease */ function easeInQuart(t, b, c, d) { return c * (t /= d) * t * t * t + b; } /** * Quartic easing out * @memberOf fabric.util.ease */ function easeOutQuart(t, b, c, d) { return -c * ((t = t / d - 1) * t * t * t - 1) + b; } /** * Quartic easing in and out * @memberOf fabric.util.ease */ function easeInOutQuart(t, b, c, d) { t /= d / 2; if (t < 1) { return c / 2 * t * t * t * t + b; } return -c / 2 * ((t -= 2) * t * t * t - 2) + b; } /** * Quintic easing in * @memberOf fabric.util.ease */ function easeInQuint(t, b, c, d) { return c * (t /= d) * t * t * t * t + b; } /** * Quintic easing out * @memberOf fabric.util.ease */ function easeOutQuint(t, b, c, d) { return c * ((t = t / d - 1) * t * t * t * t + 1) + b; } /** * Quintic easing in and out * @memberOf fabric.util.ease */ function easeInOutQuint(t, b, c, d) { t /= d / 2; if (t < 1) { return c / 2 * t * t * t * t * t + b; } return c / 2 * ((t -= 2) * t * t * t * t + 2) + b; } /** * Sinusoidal easing in * @memberOf fabric.util.ease */ function easeInSine(t, b, c, d) { return -c * Math.cos(t / d * (Math.PI / 2)) + c + b; } /** * Sinusoidal easing out * @memberOf fabric.util.ease */ function easeOutSine(t, b, c, d) { return c * Math.sin(t / d * (Math.PI / 2)) + b; } /** * Sinusoidal easing in and out * @memberOf fabric.util.ease */ function easeInOutSine(t, b, c, d) { return -c / 2 * (Math.cos(Math.PI * t / d) - 1) + b; } /** * Exponential easing in * @memberOf fabric.util.ease */ function easeInExpo(t, b, c, d) { return (t === 0) ? b : c * Math.pow(2, 10 * (t / d - 1)) + b; } /** * Exponential easing out * @memberOf fabric.util.ease */ function easeOutExpo(t, b, c, d) { return (t === d) ? b + c : c * (-Math.pow(2, -10 * t / d) + 1) + b; } /** * Exponential easing in and out * @memberOf fabric.util.ease */ function easeInOutExpo(t, b, c, d) { if (t === 0) { return b; } if (t === d) { return b + c; } t /= d / 2; if (t < 1) { return c / 2 * Math.pow(2, 10 * (t - 1)) + b; } return c / 2 * (-Math.pow(2, -10 * --t) + 2) + b; } /** * Circular easing in * @memberOf fabric.util.ease */ function easeInCirc(t, b, c, d) { return -c * (Math.sqrt(1 - (t /= d) * t) - 1) + b; } /** * Circular easing out * @memberOf fabric.util.ease */ function easeOutCirc(t, b, c, d) { return c * Math.sqrt(1 - (t = t / d - 1) * t) + b; } /** * Circular easing in and out * @memberOf fabric.util.ease */ function easeInOutCirc(t, b, c, d) { t /= d / 2; if (t < 1) { return -c / 2 * (Math.sqrt(1 - t * t) - 1) + b; } return c / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1) + b; } /** * Elastic easing in * @memberOf fabric.util.ease */ function easeInElastic(t, b, c, d) { var s = 1.70158, p = 0, a = c; if (t === 0) { return b; } t /= d; if (t === 1) { return b + c; } if (!p) { p = d * 0.3; } var opts = normalize(a, c, p, s); return -elastic(opts, t, d) + b; } /** * Elastic easing out * @memberOf fabric.util.ease */ function easeOutElastic(t, b, c, d) { var s = 1.70158, p = 0, a = c; if (t === 0) { return b; } t /= d; if (t === 1) { return b + c; } if (!p) { p = d * 0.3; } var opts = normalize(a, c, p, s); return opts.a * Math.pow(2, -10 * t) * Math.sin((t * d - opts.s) * (2 * Math.PI) / opts.p ) + opts.c + b; } /** * Elastic easing in and out * @memberOf fabric.util.ease */ function easeInOutElastic(t, b, c, d) { var s = 1.70158, p = 0, a = c; if (t === 0) { return b; } t /= d / 2; if (t === 2) { return b + c; } if (!p) { p = d * (0.3 * 1.5); } var opts = normalize(a, c, p, s); if (t < 1) { return -0.5 * elastic(opts, t, d) + b; } return opts.a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t * d - opts.s) * (2 * Math.PI) / opts.p ) * 0.5 + opts.c + b; } /** * Backwards easing in * @memberOf fabric.util.ease */ function easeInBack(t, b, c, d, s) { if (s === undefined) { s = 1.70158; } return c * (t /= d) * t * ((s + 1) * t - s) + b; } /** * Backwards easing out * @memberOf fabric.util.ease */ function easeOutBack(t, b, c, d, s) { if (s === undefined) { s = 1.70158; } return c * ((t = t / d - 1) * t * ((s + 1) * t + s) + 1) + b; } /** * Backwards easing in and out * @memberOf fabric.util.ease */ function easeInOutBack(t, b, c, d, s) { if (s === undefined) { s = 1.70158; } t /= d / 2; if (t < 1) { return c / 2 * (t * t * (((s *= (1.525)) + 1) * t - s)) + b; } return c / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2) + b; } /** * Bouncing easing in * @memberOf fabric.util.ease */ function easeInBounce(t, b, c, d) { return c - easeOutBounce (d - t, 0, c, d) + b; } /** * Bouncing easing out * @memberOf fabric.util.ease */ function easeOutBounce(t, b, c, d) { if ((t /= d) < (1 / 2.75)) { return c * (7.5625 * t * t) + b; } else if (t < (2/2.75)) { return c * (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75) + b; } else if (t < (2.5/2.75)) { return c * (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375) + b; } else { return c * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375) + b; } } /** * Bouncing easing in and out * @memberOf fabric.util.ease */ function easeInOutBounce(t, b, c, d) { if (t < d / 2) { return easeInBounce (t * 2, 0, c, d) * 0.5 + b; } return easeOutBounce(t * 2 - d, 0, c, d) * 0.5 + c * 0.5 + b; } /** * Easing functions * See Easing Equations by Robert Penner * @namespace fabric.util.ease */ fabric.util.ease = { /** * Quadratic easing in * @memberOf fabric.util.ease */ easeInQuad: function(t, b, c, d) { return c * (t /= d) * t + b; }, /** * Quadratic easing out * @memberOf fabric.util.ease */ easeOutQuad: function(t, b, c, d) { return -c * (t /= d) * (t - 2) + b; }, /** * Quadratic easing in and out * @memberOf fabric.util.ease */ easeInOutQuad: function(t, b, c, d) { t /= (d / 2); if (t < 1) { return c / 2 * t * t + b; } return -c / 2 * ((--t) * (t - 2) - 1) + b; }, /** * Cubic easing in * @memberOf fabric.util.ease */ easeInCubic: function(t, b, c, d) { return c * (t /= d) * t * t + b; }, easeOutCubic: easeOutCubic, easeInOutCubic: easeInOutCubic, easeInQuart: easeInQuart, easeOutQuart: easeOutQuart, easeInOutQuart: easeInOutQuart, easeInQuint: easeInQuint, easeOutQuint: easeOutQuint, easeInOutQuint: easeInOutQuint, easeInSine: easeInSine, easeOutSine: easeOutSine, easeInOutSine: easeInOutSine, easeInExpo: easeInExpo, easeOutExpo: easeOutExpo, easeInOutExpo: easeInOutExpo, easeInCirc: easeInCirc, easeOutCirc: easeOutCirc, easeInOutCirc: easeInOutCirc, easeInElastic: easeInElastic, easeOutElastic: easeOutElastic, easeInOutElastic: easeInOutElastic, easeInBack: easeInBack, easeOutBack: easeOutBack, easeInOutBack: easeInOutBack, easeInBounce: easeInBounce, easeOutBounce: easeOutBounce, easeInOutBounce: easeInOutBounce }; }()); (function(global) { 'use strict'; /** * @name fabric * @namespace */ var fabric = global.fabric || (global.fabric = { }), extend = fabric.util.object.extend, capitalize = fabric.util.string.capitalize, clone = fabric.util.object.clone, toFixed = fabric.util.toFixed, parseUnit = fabric.util.parseUnit, multiplyTransformMatrices = fabric.util.multiplyTransformMatrices, attributesMap = { cx: 'left', x: 'left', r: 'radius', cy: 'top', y: 'top', display: 'visible', visibility: 'visible', transform: 'transformMatrix', 'fill-opacity': 'fillOpacity', 'fill-rule': 'fillRule', 'font-family': 'fontFamily', 'font-size': 'fontSize', 'font-style': 'fontStyle', 'font-weight': 'fontWeight', 'stroke-dasharray': 'strokeDashArray', 'stroke-linecap': 'strokeLineCap', 'stroke-linejoin': 'strokeLineJoin', 'stroke-miterlimit': 'strokeMiterLimit', 'stroke-opacity': 'strokeOpacity', 'stroke-width': 'strokeWidth', 'text-decoration': 'textDecoration', 'text-anchor': 'originX' }, colorAttributes = { stroke: 'strokeOpacity', fill: 'fillOpacity' }; fabric.cssRules = { }; fabric.gradientDefs = { }; function normalizeAttr(attr) { // transform attribute names if (attr in attributesMap) { return attributesMap[attr]; } return attr; } function normalizeValue(attr, value, parentAttributes) { var isArray = Object.prototype.toString.call(value) === '[object Array]', parsed; if ((attr === 'fill' || attr === 'stroke') && value === 'none') { value = ''; } else if (attr === 'strokeDashArray') { value = value.replace(/,/g, ' ').split(/\s+/).map(function(n) { return parseFloat(n); }); } else if (attr === 'transformMatrix') { if (parentAttributes && parentAttributes.transformMatrix) { value = multiplyTransformMatrices( parentAttributes.transformMatrix, fabric.parseTransformAttribute(value)); } else { value = fabric.parseTransformAttribute(value); } } else if (attr === 'visible') { value = (value === 'none' || value === 'hidden') ? false : true; // display=none on parent element always takes precedence over child element if (parentAttributes && parentAttributes.visible === false) { value = false; } } else if (attr === 'originX' /* text-anchor */) { value = value === 'start' ? 'left' : value === 'end' ? 'right' : 'center'; } else { parsed = isArray ? value.map(parseUnit) : parseUnit(value); } return (!isArray && isNaN(parsed) ? value : parsed); } /** * @private * @param {Object} attributes Array of attributes to parse */ function _setStrokeFillOpacity(attributes) { for (var attr in colorAttributes) { if (!attributes[attr] || typeof attributes[colorAttributes[attr]] === 'undefined') { continue; } if (attributes[attr].indexOf('url(') === 0) { continue; } var color = new fabric.Color(attributes[attr]); attributes[attr] = color.setAlpha(toFixed(color.getAlpha() * attributes[colorAttributes[attr]], 2)).toRgba(); } return attributes; } /** * Parses "transform" attribute, returning an array of values * @static * @function * @memberOf fabric * @param {String} attributeValue String containing attribute value * @return {Array} Array of 6 elements representing transformation matrix */ fabric.parseTransformAttribute = (function() { function rotateMatrix(matrix, args) { var angle = args[0]; matrix[0] = Math.cos(angle); matrix[1] = Math.sin(angle); matrix[2] = -Math.sin(angle); matrix[3] = Math.cos(angle); } function scaleMatrix(matrix, args) { var multiplierX = args[0], multiplierY = (args.length === 2) ? args[1] : args[0]; matrix[0] = multiplierX; matrix[3] = multiplierY; } function skewXMatrix(matrix, args) { matrix[2] = Math.tan(fabric.util.degreesToRadians(args[0])); } function skewYMatrix(matrix, args) { matrix[1] = Math.tan(fabric.util.degreesToRadians(args[0])); } function translateMatrix(matrix, args) { matrix[4] = args[0]; if (args.length === 2) { matrix[5] = args[1]; } } // identity matrix var iMatrix = [ 1, // a 0, // b 0, // c 1, // d 0, // e 0 // f ], // == begin transform regexp number = '(?:[-+]?(?:\\d+|\\d*\\.\\d+)(?:e[-+]?\\d+)?)', commaWsp = '(?:\\s+,?\\s*|,\\s*)', skewX = '(?:(skewX)\\s*\\(\\s*(' + number + ')\\s*\\))', skewY = '(?:(skewY)\\s*\\(\\s*(' + number + ')\\s*\\))', rotate = '(?:(rotate)\\s*\\(\\s*(' + number + ')(?:' + commaWsp + '(' + number + ')' + commaWsp + '(' + number + '))?\\s*\\))', scale = '(?:(scale)\\s*\\(\\s*(' + number + ')(?:' + commaWsp + '(' + number + '))?\\s*\\))', translate = '(?:(translate)\\s*\\(\\s*(' + number + ')(?:' + commaWsp + '(' + number + '))?\\s*\\))', matrix = '(?:(matrix)\\s*\\(\\s*' + '(' + number + ')' + commaWsp + '(' + number + ')' + commaWsp + '(' + number + ')' + commaWsp + '(' + number + ')' + commaWsp + '(' + number + ')' + commaWsp + '(' + number + ')' + '\\s*\\))', transform = '(?:' + matrix + '|' + translate + '|' + scale + '|' + rotate + '|' + skewX + '|' + skewY + ')', transforms = '(?:' + transform + '(?:' + commaWsp + transform + ')*' + ')', transformList = '^\\s*(?:' + transforms + '?)\\s*$', // http://www.w3.org/TR/SVG/coords.html#TransformAttribute reTransformList = new RegExp(transformList), // == end transform regexp reTransform = new RegExp(transform, 'g'); return function(attributeValue) { // start with identity matrix var matrix = iMatrix.concat(), matrices = [ ]; // return if no argument was given or // an argument does not match transform attribute regexp if (!attributeValue || (attributeValue && !reTransformList.test(attributeValue))) { return matrix; } attributeValue.replace(reTransform, function(match) { var m = new RegExp(transform).exec(match).filter(function (match) { return (match !== '' && match != null); }), operation = m[1], args = m.slice(2).map(parseFloat); switch (operation) { case 'translate': translateMatrix(matrix, args); break; case 'rotate': args[0] = fabric.util.degreesToRadians(args[0]); rotateMatrix(matrix, args); break; case 'scale': scaleMatrix(matrix, args); break; case 'skewX': skewXMatrix(matrix, args); break; case 'skewY': skewYMatrix(matrix, args); break; case 'matrix': matrix = args; break; } // snapshot current matrix into matrices array matrices.push(matrix.concat()); // reset matrix = iMatrix.concat(); }); var combinedMatrix = matrices[0]; while (matrices.length > 1) { matrices.shift(); combinedMatrix = fabric.util.multiplyTransformMatrices(combinedMatrix, matrices[0]); } return combinedMatrix; }; })(); function parseFontDeclaration(value, oStyle) { // TODO: support non-px font size var match = value.match(/(normal|italic)?\s*(normal|small-caps)?\s*(normal|bold|bolder|lighter|100|200|300|400|500|600|700|800|900)?\s*(\d+)px(?:\/(normal|[\d\.]+))?\s+(.*)/); if (!match) { return; } var fontStyle = match[1], // font variant is not used // fontVariant = match[2], fontWeight = match[3], fontSize = match[4], lineHeight = match[5], fontFamily = match[6]; if (fontStyle) { oStyle.fontStyle = fontStyle; } if (fontWeight) { oStyle.fontWeight = isNaN(parseFloat(fontWeight)) ? fontWeight : parseFloat(fontWeight); } if (fontSize) { oStyle.fontSize = parseFloat(fontSize); } if (fontFamily) { oStyle.fontFamily = fontFamily; } if (lineHeight) { oStyle.lineHeight = lineHeight === 'normal' ? 1 : lineHeight; } } /** * @private */ function parseStyleString(style, oStyle) { var attr, value; style.replace(/;$/, '').split(';').forEach(function (chunk) { var pair = chunk.split(':'); attr = normalizeAttr(pair[0].trim().toLowerCase()); value = normalizeValue(attr, pair[1].trim()); if (attr === 'font') { parseFontDeclaration(value, oStyle); } else { oStyle[attr] = value; } }); } /** * @private */ function parseStyleObject(style, oStyle) { var attr, value; for (var prop in style) { if (typeof style[prop] === 'undefined') { continue; } attr = normalizeAttr(prop.toLowerCase()); value = normalizeValue(attr, style[prop]); if (attr === 'font') { parseFontDeclaration(value, oStyle); } else { oStyle[attr] = value; } } } /** * @private */ function getGlobalStylesForElement(element, svgUid) { var styles = { }; for (var rule in fabric.cssRules[svgUid]) { if (elementMatchesRule(element, rule.split(' '))) { for (var property in fabric.cssRules[svgUid][rule]) { styles[property] = fabric.cssRules[svgUid][rule][property]; } } } return styles; } /** * @private */ function elementMatchesRule(element, selectors) { var firstMatching, parentMatching = true; //start from rightmost selector. firstMatching = selectorMatches(element, selectors.pop()); if (firstMatching && selectors.length) { parentMatching = doesSomeParentMatch(element, selectors); } return firstMatching && parentMatching && (selectors.length === 0); } function doesSomeParentMatch(element, selectors) { var selector, parentMatching = true; while (element.parentNode && element.parentNode.nodeType === 1 && selectors.length) { if (parentMatching) { selector = selectors.pop(); } element = element.parentNode; parentMatching = selectorMatches(element, selector); } return selectors.length === 0; } /** * @private */ function selectorMatches(element, selector) { var nodeName = element.nodeName, classNames = element.getAttribute('class'), id = element.getAttribute('id'), matcher; // i check if a selector matches slicing away part from it. // if i get empty string i should match matcher = new RegExp('^' + nodeName, 'i'); selector = selector.replace(matcher, ''); if (id && selector.length) { matcher = new RegExp('#' + id + '(?![a-zA-Z\\-]+)', 'i'); selector = selector.replace(matcher, ''); } if (classNames && selector.length) { classNames = classNames.split(' '); for (var i = classNames.length; i--;) { matcher = new RegExp('\\.' + classNames[i] + '(?![a-zA-Z\\-]+)', 'i'); selector = selector.replace(matcher, ''); } } return selector.length === 0; } /** * @private */ function parseUseDirectives(doc) { var nodelist = doc.getElementsByTagName('use'); while (nodelist.length) { var el = nodelist[0], xlink = el.getAttribute('xlink:href').substr(1), x = el.getAttribute('x') || 0, y = el.getAttribute('y') || 0, el2 = doc.getElementById(xlink).cloneNode(true), currentTrans = (el.getAttribute('transform') || '') + ' translate(' + x + ', ' + y + ')', parentNode; for (var j = 0, attrs = el.attributes, l = attrs.length; j < l; j++) { var attr = attrs.item(j); if (attr.nodeName === 'x' || attr.nodeName === 'y' || attr.nodeName === 'xlink:href') { continue; } if (attr.nodeName === 'transform') { currentTrans = currentTrans + ' ' + attr.nodeValue; } else { el2.setAttribute(attr.nodeName, attr.nodeValue); } } el2.setAttribute('transform', currentTrans); el2.removeAttribute('id'); parentNode = el.parentNode; parentNode.replaceChild(el2, el); } } /** * Add a element that envelop all SCG elements and makes the viewbox transformMatrix descend on all elements */ function addSvgTransform(doc, matrix) { matrix[3] = matrix[0] = (matrix[0] > matrix[3] ? matrix[3] : matrix[0]); if (!(matrix[0] !== 1 || matrix[3] !== 1 || matrix[4] !== 0 || matrix[5] !== 0)) { return; } // default is to preserve aspect ratio // preserveAspectRatio attribute to be implemented var el = doc.ownerDocument.createElement('g'); while (doc.firstChild != null) { el.appendChild(doc.firstChild); } el.setAttribute('transform', 'matrix(' + matrix[0] + ' ' + matrix[1] + ' ' + matrix[2] + ' ' + matrix[3] + ' ' + matrix[4] + ' ' + matrix[5] + ')'); doc.appendChild(el); } /** * Parses an SVG document, converts it to an array of corresponding fabric.* instances and passes them to a callback * @static * @function * @memberOf fabric * @param {SVGDocument} doc SVG document to parse * @param {Function} callback Callback to call when parsing is finished; It's being passed an array of elements (parsed from a document). * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created. */ fabric.parseSVGDocument = (function() { var reAllowedSVGTagNames = /^(path|circle|polygon|polyline|ellipse|rect|line|image|text)$/, // http://www.w3.org/TR/SVG/coords.html#ViewBoxAttribute // \d doesn't quite cut it (as we need to match an actual float number) // matches, e.g.: +14.56e-12, etc. reNum = '(?:[-+]?(?:\\d+|\\d*\\.\\d+)(?:e[-+]?\\d+)?)', reViewBoxAttrValue = new RegExp( '^' + '\\s*(' + reNum + '+)\\s*,?' + '\\s*(' + reNum + '+)\\s*,?' + '\\s*(' + reNum + '+)\\s*,?' + '\\s*(' + reNum + '+)\\s*' + '$' ); function hasAncestorWithNodeName(element, nodeName) { while (element && (element = element.parentNode)) { if (nodeName.test(element.nodeName)) { return true; } } return false; } return function(doc, callback, reviver) { if (!doc) { return; } var startTime = new Date(), svgUid = fabric.Object.__uid++; parseUseDirectives(doc); /* http://www.w3.org/TR/SVG/struct.html#SVGElementWidthAttribute * as per spec, width and height attributes are to be considered * 100% if no value is specified. */ var viewBoxAttr = doc.getAttribute('viewBox'), widthAttr = parseUnit(doc.getAttribute('width') || '100%'), heightAttr = parseUnit(doc.getAttribute('height') || '100%'), viewBoxWidth, viewBoxHeight; if (viewBoxAttr && (viewBoxAttr = viewBoxAttr.match(reViewBoxAttrValue))) { var minX = parseFloat(viewBoxAttr[1]), minY = parseFloat(viewBoxAttr[2]), scaleX = 1, scaleY = 1; viewBoxWidth = parseFloat(viewBoxAttr[3]); viewBoxHeight = parseFloat(viewBoxAttr[4]); if (widthAttr && widthAttr !== viewBoxWidth ) { scaleX = widthAttr / viewBoxWidth; } if (heightAttr && heightAttr !== viewBoxHeight) { scaleY = heightAttr / viewBoxHeight; } addSvgTransform(doc, [scaleX, 0, 0, scaleY, scaleX * -minX, scaleY * -minY]); } var descendants = fabric.util.toArray(doc.getElementsByTagName('*')); if (descendants.length === 0 && fabric.isLikelyNode) { // we're likely in node, where "o3-xml" library fails to gEBTN("*") // https://github.com/ajaxorg/node-o3-xml/issues/21 descendants = doc.selectNodes('//*[name(.)!="svg"]'); var arr = [ ]; for (var i = 0, len = descendants.length; i < len; i++) { arr[i] = descendants[i]; } descendants = arr; } var elements = descendants.filter(function(el) { return reAllowedSVGTagNames.test(el.tagName) && !hasAncestorWithNodeName(el, /^(?:pattern|defs)$/); // http://www.w3.org/TR/SVG/struct.html#DefsElement }); if (!elements || (elements && !elements.length)) { callback && callback([], {}); return; } var options = { width: widthAttr ? widthAttr : viewBoxWidth, height: heightAttr ? heightAttr : viewBoxHeight, widthAttr: widthAttr, heightAttr: heightAttr, svgUid: svgUid }; fabric.gradientDefs[svgUid] = fabric.getGradientDefs(doc); fabric.cssRules[svgUid] = fabric.getCSSRules(doc); // Precedence of rules: style > class > attribute fabric.parseElements(elements, function(instances) { fabric.documentParsingTime = new Date() - startTime; if (callback) { callback(instances, options); } }, clone(options), reviver); }; })(); /** * Used for caching SVG documents (loaded via `fabric.Canvas#loadSVGFromURL`) * @namespace */ var svgCache = { /** * @param {String} name * @param {Function} callback */ has: function (name, callback) { callback(false); }, get: function () { /* NOOP */ }, set: function () { /* NOOP */ } }; /** * @private */ function _enlivenCachedObject(cachedObject) { var objects = cachedObject.objects, options = cachedObject.options; objects = objects.map(function (o) { return fabric[capitalize(o.type)].fromObject(o); }); return ({ objects: objects, options: options }); } /** * @private */ function _createSVGPattern(markup, canvas, property) { if (canvas[property] && canvas[property].toSVG) { markup.push( '', '' ); } } extend(fabric, { /** * Parses an SVG document, returning all of the gradient declarations found in it * @static * @function * @memberOf fabric * @param {SVGDocument} doc SVG document to parse * @return {Object} Gradient definitions; key corresponds to element id, value -- to gradient definition element */ getGradientDefs: function(doc) { var linearGradientEls = doc.getElementsByTagName('linearGradient'), radialGradientEls = doc.getElementsByTagName('radialGradient'), el, i, j = 0, id, xlink, elList = [ ], gradientDefs = { }, idsToXlinkMap = { }; elList.length = linearGradientEls.length + radialGradientEls.length; i = linearGradientEls.length; while (i--) { elList[j++] = linearGradientEls[i]; } i = radialGradientEls.length; while (i--) { elList[j++] = radialGradientEls[i]; } while (j--) { el = elList[j]; xlink = el.getAttribute('xlink:href'); id = el.getAttribute('id'); if (xlink) { idsToXlinkMap[id] = xlink.substr(1); } gradientDefs[id] = el; } for (id in idsToXlinkMap) { var el2 = gradientDefs[idsToXlinkMap[id]].cloneNode(true); el = gradientDefs[id]; while (el2.firstChild) { el.appendChild(el2.firstChild); } } return gradientDefs; }, /** * Returns an object of attributes' name/value, given element and an array of attribute names; * Parses parent "g" nodes recursively upwards. * @static * @memberOf fabric * @param {DOMElement} element Element to parse * @param {Array} attributes Array of attributes to parse * @return {Object} object containing parsed attributes' names/values */ parseAttributes: function(element, attributes, svgUid) { if (!element) { return; } var value, parentAttributes = { }; if (typeof svgUid === 'undefined') { svgUid = element.getAttribute('svgUid'); } // if there's a parent container (`g` or `a` or `symbol` node), parse its attributes recursively upwards if (element.parentNode && /^symbol|[g|a]$/i.test(element.parentNode.nodeName)) { parentAttributes = fabric.parseAttributes(element.parentNode, attributes, svgUid); } var ownAttributes = attributes.reduce(function(memo, attr) { value = element.getAttribute(attr); if (value) { attr = normalizeAttr(attr); value = normalizeValue(attr, value, parentAttributes); memo[attr] = value; } return memo; }, { }); // add values parsed from style, which take precedence over attributes // (see: http://www.w3.org/TR/SVG/styling.html#UsingPresentationAttributes) ownAttributes = extend(ownAttributes, extend(getGlobalStylesForElement(element, svgUid), fabric.parseStyleAttribute(element))); return _setStrokeFillOpacity(extend(parentAttributes, ownAttributes)); }, /** * Transforms an array of svg elements to corresponding fabric.* instances * @static * @memberOf fabric * @param {Array} elements Array of elements to parse * @param {Function} callback Being passed an array of fabric instances (transformed from SVG elements) * @param {Object} [options] Options object * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created. */ parseElements: function(elements, callback, options, reviver) { new fabric.ElementsParser(elements, callback, options, reviver).parse(); }, /** * Parses "style" attribute, retuning an object with values * @static * @memberOf fabric * @param {SVGElement} element Element to parse * @return {Object} Objects with values parsed from style attribute of an element */ parseStyleAttribute: function(element) { var oStyle = { }, style = element.getAttribute('style'); if (!style) { return oStyle; } if (typeof style === 'string') { parseStyleString(style, oStyle); } else { parseStyleObject(style, oStyle); } return oStyle; }, /** * Parses "points" attribute, returning an array of values * @static * @memberOf fabric * @param {String} points points attribute string * @return {Array} array of points */ parsePointsAttribute: function(points) { // points attribute is required and must not be empty if (!points) { return null; } // replace commas with whitespace and remove bookending whitespace points = points.replace(/,/g, ' ').trim(); points = points.split(/\s+/); var parsedPoints = [ ], i, len; i = 0; len = points.length; for (; i < len; i+=2) { parsedPoints.push({ x: parseFloat(points[i]), y: parseFloat(points[i + 1]) }); } // odd number of points is an error // if (parsedPoints.length % 2 !== 0) { // return null; // } return parsedPoints; }, /** * Returns CSS rules for a given SVG document * @static * @function * @memberOf fabric * @param {SVGDocument} doc SVG document to parse * @return {Object} CSS rules of this document */ getCSSRules: function(doc) { var styles = doc.getElementsByTagName('style'), allRules = { }, rules; // very crude parsing of style contents for (var i = 0, len = styles.length; i < len; i++) { var styleContents = styles[i].textContent; // remove comments styleContents = styleContents.replace(/\/\*[\s\S]*?\*\//g, ''); if (styleContents.trim() === '') { continue; } rules = styleContents.match(/[^{]*\{[\s\S]*?\}/g); rules = rules.map(function(rule) { return rule.trim(); }); rules.forEach(function(rule) { var match = rule.match(/([\s\S]*?)\s*\{([^}]*)\}/), ruleObj = { }, declaration = match[2].trim(), propertyValuePairs = declaration.replace(/;$/, '').split(/\s*;\s*/); for (var i = 0, len = propertyValuePairs.length; i < len; i++) { var pair = propertyValuePairs[i].split(/\s*:\s*/), property = normalizeAttr(pair[0]), value = normalizeValue(property, pair[1], pair[0]); ruleObj[property] = value; } rule = match[1]; rule.split(',').forEach(function(_rule) { allRules[_rule.trim()] = fabric.util.object.clone(ruleObj); }); }); } return allRules; }, /** * Takes url corresponding to an SVG document, and parses it into a set of fabric objects. Note that SVG is fetched via XMLHttpRequest, so it needs to conform to SOP (Same Origin Policy) * @memberof fabric * @param {String} url * @param {Function} callback * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created. */ loadSVGFromURL: function(url, callback, reviver) { url = url.replace(/^\n\s*/, '').trim(); svgCache.has(url, function (hasUrl) { if (hasUrl) { svgCache.get(url, function (value) { var enlivedRecord = _enlivenCachedObject(value); callback(enlivedRecord.objects, enlivedRecord.options); }); } else { new fabric.util.request(url, { method: 'get', onComplete: onComplete }); } }); function onComplete(r) { var xml = r.responseXML; if (xml && !xml.documentElement && fabric.window.ActiveXObject && r.responseText) { xml = new ActiveXObject('Microsoft.XMLDOM'); xml.async = 'false'; //IE chokes on DOCTYPE xml.loadXML(r.responseText.replace(//i, '')); } if (!xml || !xml.documentElement) { return; } fabric.parseSVGDocument(xml.documentElement, function (results, options) { svgCache.set(url, { objects: fabric.util.array.invoke(results, 'toObject'), options: options }); callback(results, options); }, reviver); } }, /** * Takes string corresponding to an SVG document, and parses it into a set of fabric objects * @memberof fabric * @param {String} string * @param {Function} callback * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created. */ loadSVGFromString: function(string, callback, reviver) { string = string.trim(); var doc; if (typeof DOMParser !== 'undefined') { var parser = new DOMParser(); if (parser && parser.parseFromString) { doc = parser.parseFromString(string, 'text/xml'); } } else if (fabric.window.ActiveXObject) { doc = new ActiveXObject('Microsoft.XMLDOM'); doc.async = 'false'; // IE chokes on DOCTYPE doc.loadXML(string.replace(//i, '')); } fabric.parseSVGDocument(doc.documentElement, function (results, options) { callback(results, options); }, reviver); }, /** * Creates markup containing SVG font faces * @param {Array} objects Array of fabric objects * @return {String} */ createSVGFontFacesMarkup: function(objects) { var markup = ''; for (var i = 0, len = objects.length; i < len; i++) { if (objects[i].type !== 'text' || !objects[i].path) { continue; } markup += [ //jscs:disable validateIndentation '@font-face {', 'font-family: ', objects[i].fontFamily, '; ', 'src: url(\'', objects[i].path, '\')', '}' //jscs:enable validateIndentation ].join(''); } if (markup) { markup = [ //jscs:disable validateIndentation '' //jscs:enable validateIndentation ].join(''); } return markup; }, /** * Creates markup containing SVG referenced elements like patterns, gradients etc. * @param {fabric.Canvas} canvas instance of fabric.Canvas * @return {String} */ createSVGRefElementsMarkup: function(canvas) { var markup = [ ]; _createSVGPattern(markup, canvas, 'backgroundColor'); _createSVGPattern(markup, canvas, 'overlayColor'); return markup.join(''); } }); })(typeof exports !== 'undefined' ? exports : this); fabric.ElementsParser = function(elements, callback, options, reviver) { this.elements = elements; this.callback = callback; this.options = options; this.reviver = reviver; this.svgUid = (options && options.svgUid) || 0; }; fabric.ElementsParser.prototype.parse = function() { this.instances = new Array(this.elements.length); this.numElements = this.elements.length; this.createObjects(); }; fabric.ElementsParser.prototype.createObjects = function() { for (var i = 0, len = this.elements.length; i < len; i++) { this.elements[i].setAttribute('svgUid', this.svgUid); (function(_this, i) { setTimeout(function() { _this.createObject(_this.elements[i], i); }, 0); })(this, i); } }; fabric.ElementsParser.prototype.createObject = function(el, index) { var klass = fabric[fabric.util.string.capitalize(el.tagName)]; if (klass && klass.fromElement) { try { this._createObject(klass, el, index); } catch (err) { fabric.log(err); } } else { this.checkIfDone(); } }; fabric.ElementsParser.prototype._createObject = function(klass, el, index) { if (klass.async) { klass.fromElement(el, this.createCallback(index, el), this.options); } else { var obj = klass.fromElement(el, this.options); this.resolveGradient(obj, 'fill'); this.resolveGradient(obj, 'stroke'); this.reviver && this.reviver(el, obj); this.instances[index] = obj; this.checkIfDone(); } }; fabric.ElementsParser.prototype.createCallback = function(index, el) { var _this = this; return function(obj) { _this.resolveGradient(obj, 'fill'); _this.resolveGradient(obj, 'stroke'); _this.reviver && _this.reviver(el, obj); _this.instances[index] = obj; _this.checkIfDone(); }; }; fabric.ElementsParser.prototype.resolveGradient = function(obj, property) { var instanceFillValue = obj.get(property); if (!(/^url\(/).test(instanceFillValue)) { return; } var gradientId = instanceFillValue.slice(5, instanceFillValue.length - 1); if (fabric.gradientDefs[this.svgUid][gradientId]) { obj.set(property, fabric.Gradient.fromElement(fabric.gradientDefs[this.svgUid][gradientId], obj)); } }; fabric.ElementsParser.prototype.checkIfDone = function() { if (--this.numElements === 0) { this.instances = this.instances.filter(function(el) { return el != null; }); this.callback(this.instances); } }; (function(global) { 'use strict'; /* Adaptation of work of Kevin Lindsey (kevin@kevlindev.com) */ var fabric = global.fabric || (global.fabric = { }); if (fabric.Point) { fabric.warn('fabric.Point is already defined'); return; } fabric.Point = Point; /** * Point class * @class fabric.Point * @memberOf fabric * @constructor * @param {Number} x * @param {Number} y * @return {fabric.Point} thisArg */ function Point(x, y) { this.x = x; this.y = y; } Point.prototype = /** @lends fabric.Point.prototype */ { constructor: Point, /** * Adds another point to this one and returns another one * @param {fabric.Point} that * @return {fabric.Point} new Point instance with added values */ add: function (that) { return new Point(this.x + that.x, this.y + that.y); }, /** * Adds another point to this one * @param {fabric.Point} that * @return {fabric.Point} thisArg */ addEquals: function (that) { this.x += that.x; this.y += that.y; return this; }, /** * Adds value to this point and returns a new one * @param {Number} scalar * @return {fabric.Point} new Point with added value */ scalarAdd: function (scalar) { return new Point(this.x + scalar, this.y + scalar); }, /** * Adds value to this point * @param {Number} scalar * @return {fabric.Point} thisArg */ scalarAddEquals: function (scalar) { this.x += scalar; this.y += scalar; return this; }, /** * Subtracts another point from this point and returns a new one * @param {fabric.Point} that * @return {fabric.Point} new Point object with subtracted values */ subtract: function (that) { return new Point(this.x - that.x, this.y - that.y); }, /** * Subtracts another point from this point * @param {fabric.Point} that * @return {fabric.Point} thisArg */ subtractEquals: function (that) { this.x -= that.x; this.y -= that.y; return this; }, /** * Subtracts value from this point and returns a new one * @param {Number} scalar * @return {fabric.Point} */ scalarSubtract: function (scalar) { return new Point(this.x - scalar, this.y - scalar); }, /** * Subtracts value from this point * @param {Number} scalar * @return {fabric.Point} thisArg */ scalarSubtractEquals: function (scalar) { this.x -= scalar; this.y -= scalar; return this; }, /** * Miltiplies this point by a value and returns a new one * @param {Number} scalar * @return {fabric.Point} */ multiply: function (scalar) { return new Point(this.x * scalar, this.y * scalar); }, /** * Miltiplies this point by a value * @param {Number} scalar * @return {fabric.Point} thisArg */ multiplyEquals: function (scalar) { this.x *= scalar; this.y *= scalar; return this; }, /** * Divides this point by a value and returns a new one * @param {Number} scalar * @return {fabric.Point} */ divide: function (scalar) { return new Point(this.x / scalar, this.y / scalar); }, /** * Divides this point by a value * @param {Number} scalar * @return {fabric.Point} thisArg */ divideEquals: function (scalar) { this.x /= scalar; this.y /= scalar; return this; }, /** * Returns true if this point is equal to another one * @param {fabric.Point} that * @return {Boolean} */ eq: function (that) { return (this.x === that.x && this.y === that.y); }, /** * Returns true if this point is less than another one * @param {fabric.Point} that * @return {Boolean} */ lt: function (that) { return (this.x < that.x && this.y < that.y); }, /** * Returns true if this point is less than or equal to another one * @param {fabric.Point} that * @return {Boolean} */ lte: function (that) { return (this.x <= that.x && this.y <= that.y); }, /** * Returns true if this point is greater another one * @param {fabric.Point} that * @return {Boolean} */ gt: function (that) { return (this.x > that.x && this.y > that.y); }, /** * Returns true if this point is greater than or equal to another one * @param {fabric.Point} that * @return {Boolean} */ gte: function (that) { return (this.x >= that.x && this.y >= that.y); }, /** * Returns new point which is the result of linear interpolation with this one and another one * @param {fabric.Point} that * @param {Number} t * @return {fabric.Point} */ lerp: function (that, t) { return new Point(this.x + (that.x - this.x) * t, this.y + (that.y - this.y) * t); }, /** * Returns distance from this point and another one * @param {fabric.Point} that * @return {Number} */ distanceFrom: function (that) { var dx = this.x - that.x, dy = this.y - that.y; return Math.sqrt(dx * dx + dy * dy); }, /** * Returns the point between this point and another one * @param {fabric.Point} that * @return {fabric.Point} */ midPointFrom: function (that) { return new Point(this.x + (that.x - this.x)/2, this.y + (that.y - this.y)/2); }, /** * Returns a new point which is the min of this and another one * @param {fabric.Point} that * @return {fabric.Point} */ min: function (that) { return new Point(Math.min(this.x, that.x), Math.min(this.y, that.y)); }, /** * Returns a new point which is the max of this and another one * @param {fabric.Point} that * @return {fabric.Point} */ max: function (that) { return new Point(Math.max(this.x, that.x), Math.max(this.y, that.y)); }, /** * Returns string representation of this point * @return {String} */ toString: function () { return this.x + ',' + this.y; }, /** * Sets x/y of this point * @param {Number} x * @return {Number} y */ setXY: function (x, y) { this.x = x; this.y = y; }, /** * Sets x/y of this point from another point * @param {fabric.Point} that */ setFromPoint: function (that) { this.x = that.x; this.y = that.y; }, /** * Swaps x/y of this point and another point * @param {fabric.Point} that */ swap: function (that) { var x = this.x, y = this.y; this.x = that.x; this.y = that.y; that.x = x; that.y = y; } }; })(typeof exports !== 'undefined' ? exports : this); (function(global) { 'use strict'; /* Adaptation of work of Kevin Lindsey (kevin@kevlindev.com) */ var fabric = global.fabric || (global.fabric = { }); if (fabric.Intersection) { fabric.warn('fabric.Intersection is already defined'); return; } /** * Intersection class * @class fabric.Intersection * @memberOf fabric * @constructor */ function Intersection(status) { this.status = status; this.points = []; } fabric.Intersection = Intersection; fabric.Intersection.prototype = /** @lends fabric.Intersection.prototype */ { /** * Appends a point to intersection * @param {fabric.Point} point */ appendPoint: function (point) { this.points.push(point); }, /** * Appends points to intersection * @param {Array} points */ appendPoints: function (points) { this.points = this.points.concat(points); } }; /** * Checks if one line intersects another * @static * @param {fabric.Point} a1 * @param {fabric.Point} a2 * @param {fabric.Point} b1 * @param {fabric.Point} b2 * @return {fabric.Intersection} */ fabric.Intersection.intersectLineLine = function (a1, a2, b1, b2) { var result, uaT = (b2.x - b1.x) * (a1.y - b1.y) - (b2.y - b1.y) * (a1.x - b1.x), ubT = (a2.x - a1.x) * (a1.y - b1.y) - (a2.y - a1.y) * (a1.x - b1.x), uB = (b2.y - b1.y) * (a2.x - a1.x) - (b2.x - b1.x) * (a2.y - a1.y); if (uB !== 0) { var ua = uaT / uB, ub = ubT / uB; if (0 <= ua && ua <= 1 && 0 <= ub && ub <= 1) { result = new Intersection('Intersection'); result.points.push(new fabric.Point(a1.x + ua * (a2.x - a1.x), a1.y + ua * (a2.y - a1.y))); } else { result = new Intersection(); } } else { if (uaT === 0 || ubT === 0) { result = new Intersection('Coincident'); } else { result = new Intersection('Parallel'); } } return result; }; /** * Checks if line intersects polygon * @static * @param {fabric.Point} a1 * @param {fabric.Point} a2 * @param {Array} points * @return {fabric.Intersection} */ fabric.Intersection.intersectLinePolygon = function(a1, a2, points) { var result = new Intersection(), length = points.length; for (var i = 0; i < length; i++) { var b1 = points[i], b2 = points[(i + 1) % length], inter = Intersection.intersectLineLine(a1, a2, b1, b2); result.appendPoints(inter.points); } if (result.points.length > 0) { result.status = 'Intersection'; } return result; }; /** * Checks if polygon intersects another polygon * @static * @param {Array} points1 * @param {Array} points2 * @return {fabric.Intersection} */ fabric.Intersection.intersectPolygonPolygon = function (points1, points2) { var result = new Intersection(), length = points1.length; for (var i = 0; i < length; i++) { var a1 = points1[i], a2 = points1[(i + 1) % length], inter = Intersection.intersectLinePolygon(a1, a2, points2); result.appendPoints(inter.points); } if (result.points.length > 0) { result.status = 'Intersection'; } return result; }; /** * Checks if polygon intersects rectangle * @static * @param {Array} points * @param {Number} r1 * @param {Number} r2 * @return {fabric.Intersection} */ fabric.Intersection.intersectPolygonRectangle = function (points, r1, r2) { var min = r1.min(r2), max = r1.max(r2), topRight = new fabric.Point(max.x, min.y), bottomLeft = new fabric.Point(min.x, max.y), inter1 = Intersection.intersectLinePolygon(min, topRight, points), inter2 = Intersection.intersectLinePolygon(topRight, max, points), inter3 = Intersection.intersectLinePolygon(max, bottomLeft, points), inter4 = Intersection.intersectLinePolygon(bottomLeft, min, points), result = new Intersection(); result.appendPoints(inter1.points); result.appendPoints(inter2.points); result.appendPoints(inter3.points); result.appendPoints(inter4.points); if (result.points.length > 0) { result.status = 'Intersection'; } return result; }; })(typeof exports !== 'undefined' ? exports : this); (function(global) { 'use strict'; var fabric = global.fabric || (global.fabric = { }); if (fabric.Color) { fabric.warn('fabric.Color is already defined.'); return; } /** * Color class * The purpose of {@link fabric.Color} is to abstract and encapsulate common color operations; * {@link fabric.Color} is a constructor and creates instances of {@link fabric.Color} objects. * * @class fabric.Color * @param {String} color optional in hex or rgb(a) format * @return {fabric.Color} thisArg * @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#colors} */ function Color(color) { if (!color) { this.setSource([0, 0, 0, 1]); } else { this._tryParsingColor(color); } } fabric.Color = Color; fabric.Color.prototype = /** @lends fabric.Color.prototype */ { /** * @private * @param {String|Array} color Color value to parse */ _tryParsingColor: function(color) { var source; if (color in Color.colorNameMap) { color = Color.colorNameMap[color]; } if (color === 'transparent') { this.setSource([255, 255, 255, 0]); return; } source = Color.sourceFromHex(color); if (!source) { source = Color.sourceFromRgb(color); } if (!source) { source = Color.sourceFromHsl(color); } if (source) { this.setSource(source); } }, /** * Adapted from https://github.com/mjijackson * @private * @param {Number} r Red color value * @param {Number} g Green color value * @param {Number} b Blue color value * @return {Array} Hsl color */ _rgbToHsl: function(r, g, b) { r /= 255, g /= 255, b /= 255; var h, s, l, max = fabric.util.array.max([r, g, b]), min = fabric.util.array.min([r, g, b]); l = (max + min) / 2; if (max === min) { h = s = 0; // achromatic } else { var d = max - min; s = l > 0.5 ? d / (2 - max - min) : d / (max + min); switch (max) { case r: h = (g - b) / d + (g < b ? 6 : 0); break; case g: h = (b - r) / d + 2; break; case b: h = (r - g) / d + 4; break; } h /= 6; } return [ Math.round(h * 360), Math.round(s * 100), Math.round(l * 100) ]; }, /** * Returns source of this color (where source is an array representation; ex: [200, 200, 100, 1]) * @return {Array} */ getSource: function() { return this._source; }, /** * Sets source of this color (where source is an array representation; ex: [200, 200, 100, 1]) * @param {Array} source */ setSource: function(source) { this._source = source; }, /** * Returns color represenation in RGB format * @return {String} ex: rgb(0-255,0-255,0-255) */ toRgb: function() { var source = this.getSource(); return 'rgb(' + source[0] + ',' + source[1] + ',' + source[2] + ')'; }, /** * Returns color represenation in RGBA format * @return {String} ex: rgba(0-255,0-255,0-255,0-1) */ toRgba: function() { var source = this.getSource(); return 'rgba(' + source[0] + ',' + source[1] + ',' + source[2] + ',' + source[3] + ')'; }, /** * Returns color represenation in HSL format * @return {String} ex: hsl(0-360,0%-100%,0%-100%) */ toHsl: function() { var source = this.getSource(), hsl = this._rgbToHsl(source[0], source[1], source[2]); return 'hsl(' + hsl[0] + ',' + hsl[1] + '%,' + hsl[2] + '%)'; }, /** * Returns color represenation in HSLA format * @return {String} ex: hsla(0-360,0%-100%,0%-100%,0-1) */ toHsla: function() { var source = this.getSource(), hsl = this._rgbToHsl(source[0], source[1], source[2]); return 'hsla(' + hsl[0] + ',' + hsl[1] + '%,' + hsl[2] + '%,' + source[3] + ')'; }, /** * Returns color represenation in HEX format * @return {String} ex: FF5555 */ toHex: function() { var source = this.getSource(), r, g, b; r = source[0].toString(16); r = (r.length === 1) ? ('0' + r) : r; g = source[1].toString(16); g = (g.length === 1) ? ('0' + g) : g; b = source[2].toString(16); b = (b.length === 1) ? ('0' + b) : b; return r.toUpperCase() + g.toUpperCase() + b.toUpperCase(); }, /** * Gets value of alpha channel for this color * @return {Number} 0-1 */ getAlpha: function() { return this.getSource()[3]; }, /** * Sets value of alpha channel for this color * @param {Number} alpha Alpha value 0-1 * @return {fabric.Color} thisArg */ setAlpha: function(alpha) { var source = this.getSource(); source[3] = alpha; this.setSource(source); return this; }, /** * Transforms color to its grayscale representation * @return {fabric.Color} thisArg */ toGrayscale: function() { var source = this.getSource(), average = parseInt((source[0] * 0.3 + source[1] * 0.59 + source[2] * 0.11).toFixed(0), 10), currentAlpha = source[3]; this.setSource([average, average, average, currentAlpha]); return this; }, /** * Transforms color to its black and white representation * @param {Number} threshold * @return {fabric.Color} thisArg */ toBlackWhite: function(threshold) { var source = this.getSource(), average = (source[0] * 0.3 + source[1] * 0.59 + source[2] * 0.11).toFixed(0), currentAlpha = source[3]; threshold = threshold || 127; average = (Number(average) < Number(threshold)) ? 0 : 255; this.setSource([average, average, average, currentAlpha]); return this; }, /** * Overlays color with another color * @param {String|fabric.Color} otherColor * @return {fabric.Color} thisArg */ overlayWith: function(otherColor) { if (!(otherColor instanceof Color)) { otherColor = new Color(otherColor); } var result = [], alpha = this.getAlpha(), otherAlpha = 0.5, source = this.getSource(), otherSource = otherColor.getSource(); for (var i = 0; i < 3; i++) { result.push(Math.round((source[i] * (1 - otherAlpha)) + (otherSource[i] * otherAlpha))); } result[3] = alpha; this.setSource(result); return this; } }; /** * Regex matching color in RGB or RGBA formats (ex: rgb(0, 0, 0), rgba(255, 100, 10, 0.5), rgba( 255 , 100 , 10 , 0.5 ), rgb(1,1,1), rgba(100%, 60%, 10%, 0.5)) * @static * @field * @memberOf fabric.Color */ fabric.Color.reRGBa = /^rgba?\(\s*(\d{1,3}(?:\.\d+)?\%?)\s*,\s*(\d{1,3}(?:\.\d+)?\%?)\s*,\s*(\d{1,3}(?:\.\d+)?\%?)\s*(?:\s*,\s*(\d+(?:\.\d+)?)\s*)?\)$/; /** * Regex matching color in HSL or HSLA formats (ex: hsl(200, 80%, 10%), hsla(300, 50%, 80%, 0.5), hsla( 300 , 50% , 80% , 0.5 )) * @static * @field * @memberOf fabric.Color */ fabric.Color.reHSLa = /^hsla?\(\s*(\d{1,3})\s*,\s*(\d{1,3}\%)\s*,\s*(\d{1,3}\%)\s*(?:\s*,\s*(\d+(?:\.\d+)?)\s*)?\)$/; /** * Regex matching color in HEX format (ex: #FF5555, 010155, aff) * @static * @field * @memberOf fabric.Color */ fabric.Color.reHex = /^#?([0-9a-f]{6}|[0-9a-f]{3})$/i; /** * Map of the 17 basic color names with HEX code * @static * @field * @memberOf fabric.Color * @see: http://www.w3.org/TR/CSS2/syndata.html#color-units */ fabric.Color.colorNameMap = { aqua: '#00FFFF', black: '#000000', blue: '#0000FF', fuchsia: '#FF00FF', gray: '#808080', green: '#008000', lime: '#00FF00', maroon: '#800000', navy: '#000080', olive: '#808000', orange: '#FFA500', purple: '#800080', red: '#FF0000', silver: '#C0C0C0', teal: '#008080', white: '#FFFFFF', yellow: '#FFFF00' }; /** * @private * @param {Number} p * @param {Number} q * @param {Number} t * @return {Number} */ function hue2rgb(p, q, t) { if (t < 0) { t += 1; } if (t > 1) { t -= 1; } if (t < 1/6) { return p + (q - p) * 6 * t; } if (t < 1/2) { return q; } if (t < 2/3) { return p + (q - p) * (2/3 - t) * 6; } return p; } /** * Returns new color object, when given a color in RGB format * @memberOf fabric.Color * @param {String} color Color value ex: rgb(0-255,0-255,0-255) * @return {fabric.Color} */ fabric.Color.fromRgb = function(color) { return Color.fromSource(Color.sourceFromRgb(color)); }; /** * Returns array represenatation (ex: [100, 100, 200, 1]) of a color that's in RGB or RGBA format * @memberOf fabric.Color * @param {String} color Color value ex: rgb(0-255,0-255,0-255), rgb(0%-100%,0%-100%,0%-100%) * @return {Array} source */ fabric.Color.sourceFromRgb = function(color) { var match = color.match(Color.reRGBa); if (match) { var r = parseInt(match[1], 10) / (/%$/.test(match[1]) ? 100 : 1) * (/%$/.test(match[1]) ? 255 : 1), g = parseInt(match[2], 10) / (/%$/.test(match[2]) ? 100 : 1) * (/%$/.test(match[2]) ? 255 : 1), b = parseInt(match[3], 10) / (/%$/.test(match[3]) ? 100 : 1) * (/%$/.test(match[3]) ? 255 : 1); return [ parseInt(r, 10), parseInt(g, 10), parseInt(b, 10), match[4] ? parseFloat(match[4]) : 1 ]; } }; /** * Returns new color object, when given a color in RGBA format * @static * @function * @memberOf fabric.Color * @param {String} color * @return {fabric.Color} */ fabric.Color.fromRgba = Color.fromRgb; /** * Returns new color object, when given a color in HSL format * @param {String} color Color value ex: hsl(0-260,0%-100%,0%-100%) * @memberOf fabric.Color * @return {fabric.Color} */ fabric.Color.fromHsl = function(color) { return Color.fromSource(Color.sourceFromHsl(color)); }; /** * Returns array represenatation (ex: [100, 100, 200, 1]) of a color that's in HSL or HSLA format. * Adapted from https://github.com/mjijackson * @memberOf fabric.Color * @param {String} color Color value ex: hsl(0-360,0%-100%,0%-100%) or hsla(0-360,0%-100%,0%-100%, 0-1) * @return {Array} source * @see http://http://www.w3.org/TR/css3-color/#hsl-color */ fabric.Color.sourceFromHsl = function(color) { var match = color.match(Color.reHSLa); if (!match) { return; } var h = (((parseFloat(match[1]) % 360) + 360) % 360) / 360, s = parseFloat(match[2]) / (/%$/.test(match[2]) ? 100 : 1), l = parseFloat(match[3]) / (/%$/.test(match[3]) ? 100 : 1), r, g, b; if (s === 0) { r = g = b = l; } else { var q = l <= 0.5 ? l * (s + 1) : l + s - l * s, p = l * 2 - q; r = hue2rgb(p, q, h + 1/3); g = hue2rgb(p, q, h); b = hue2rgb(p, q, h - 1/3); } return [ Math.round(r * 255), Math.round(g * 255), Math.round(b * 255), match[4] ? parseFloat(match[4]) : 1 ]; }; /** * Returns new color object, when given a color in HSLA format * @static * @function * @memberOf fabric.Color * @param {String} color * @return {fabric.Color} */ fabric.Color.fromHsla = Color.fromHsl; /** * Returns new color object, when given a color in HEX format * @static * @memberOf fabric.Color * @param {String} color Color value ex: FF5555 * @return {fabric.Color} */ fabric.Color.fromHex = function(color) { return Color.fromSource(Color.sourceFromHex(color)); }; /** * Returns array represenatation (ex: [100, 100, 200, 1]) of a color that's in HEX format * @static * @memberOf fabric.Color * @param {String} color ex: FF5555 * @return {Array} source */ fabric.Color.sourceFromHex = function(color) { if (color.match(Color.reHex)) { var value = color.slice(color.indexOf('#') + 1), isShortNotation = (value.length === 3), r = isShortNotation ? (value.charAt(0) + value.charAt(0)) : value.substring(0, 2), g = isShortNotation ? (value.charAt(1) + value.charAt(1)) : value.substring(2, 4), b = isShortNotation ? (value.charAt(2) + value.charAt(2)) : value.substring(4, 6); return [ parseInt(r, 16), parseInt(g, 16), parseInt(b, 16), 1 ]; } }; /** * Returns new color object, when given color in array representation (ex: [200, 100, 100, 0.5]) * @static * @memberOf fabric.Color * @param {Array} source * @return {fabric.Color} */ fabric.Color.fromSource = function(source) { var oColor = new Color(); oColor.setSource(source); return oColor; }; })(typeof exports !== 'undefined' ? exports : this); (function() { /* _FROM_SVG_START_ */ function getColorStop(el) { var style = el.getAttribute('style'), offset = el.getAttribute('offset'), color, colorAlpha, opacity; // convert percents to absolute values offset = parseFloat(offset) / (/%$/.test(offset) ? 100 : 1); offset = offset < 0 ? 0 : offset > 1 ? 1 : offset; if (style) { var keyValuePairs = style.split(/\s*;\s*/); if (keyValuePairs[keyValuePairs.length - 1] === '') { keyValuePairs.pop(); } for (var i = keyValuePairs.length; i--; ) { var split = keyValuePairs[i].split(/\s*:\s*/), key = split[0].trim(), value = split[1].trim(); if (key === 'stop-color') { color = value; } else if (key === 'stop-opacity') { opacity = value; } } } if (!color) { color = el.getAttribute('stop-color') || 'rgb(0,0,0)'; } if (!opacity) { opacity = el.getAttribute('stop-opacity'); } color = new fabric.Color(color); colorAlpha = color.getAlpha(); opacity = isNaN(parseFloat(opacity)) ? 1 : parseFloat(opacity); opacity *= colorAlpha; return { offset: offset, color: color.toRgb(), opacity: opacity }; } function getLinearCoords(el) { return { x1: el.getAttribute('x1') || 0, y1: el.getAttribute('y1') || 0, x2: el.getAttribute('x2') || '100%', y2: el.getAttribute('y2') || 0 }; } function getRadialCoords(el) { return { x1: el.getAttribute('fx') || el.getAttribute('cx') || '50%', y1: el.getAttribute('fy') || el.getAttribute('cy') || '50%', r1: 0, x2: el.getAttribute('cx') || '50%', y2: el.getAttribute('cy') || '50%', r2: el.getAttribute('r') || '50%' }; } /* _FROM_SVG_END_ */ /** * Gradient class * @class fabric.Gradient * @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#gradients} * @see {@link fabric.Gradient#initialize} for constructor definition */ fabric.Gradient = fabric.util.createClass(/** @lends fabric.Gradient.prototype */ { /** * Horizontal offset for aligning gradients coming from SVG when outside pathgroups * @type Number * @default 0 */ offsetX: 0, /** * Vertical offset for aligning gradients coming from SVG when outside pathgroups * @type Number * @default 0 */ offsetY: 0, /** * Constructor * @param {Object} [options] Options object with type, coords, gradientUnits and colorStops * @return {fabric.Gradient} thisArg */ initialize: function(options) { options || (options = { }); var coords = { }; this.id = fabric.Object.__uid++; this.type = options.type || 'linear'; coords = { x1: options.coords.x1 || 0, y1: options.coords.y1 || 0, x2: options.coords.x2 || 0, y2: options.coords.y2 || 0 }; if (this.type === 'radial') { coords.r1 = options.coords.r1 || 0; coords.r2 = options.coords.r2 || 0; } this.coords = coords; this.colorStops = options.colorStops.slice(); if (options.gradientTransform) { this.gradientTransform = options.gradientTransform; } this.offsetX = options.offsetX || this.offsetX; this.offsetY = options.offsetY || this.offsetY; }, /** * Adds another colorStop * @param {Object} colorStop Object with offset and color * @return {fabric.Gradient} thisArg */ addColorStop: function(colorStop) { for (var position in colorStop) { var color = new fabric.Color(colorStop[position]); this.colorStops.push({ offset: position, color: color.toRgb(), opacity: color.getAlpha() }); } return this; }, /** * Returns object representation of a gradient * @return {Object} */ toObject: function() { return { type: this.type, coords: this.coords, colorStops: this.colorStops, offsetX: this.offsetX, offsetY: this.offsetY }; }, /* _TO_SVG_START_ */ /** * Returns SVG representation of an gradient * @param {Object} object Object to create a gradient for * @param {Boolean} normalize Whether coords should be normalized * @return {String} SVG representation of an gradient (linear/radial) */ toSVG: function(object) { var coords = fabric.util.object.clone(this.coords), markup, commonAttributes; // colorStops must be sorted ascending this.colorStops.sort(function(a, b) { return a.offset - b.offset; }); if (!(object.group && object.group.type === 'path-group')) { for (var prop in coords) { if (prop === 'x1' || prop === 'x2' || prop === 'r2') { coords[prop] += this.offsetX - object.width / 2; } else if (prop === 'y1' || prop === 'y2') { coords[prop] += this.offsetY - object.height / 2; } } } commonAttributes = 'id="SVGID_' + this.id + '" gradientUnits="userSpaceOnUse"'; if (this.gradientTransform) { commonAttributes += ' gradientTransform="matrix(' + this.gradientTransform.join(' ') + ')" '; } if (this.type === 'linear') { markup = [ //jscs:disable validateIndentation '\n' //jscs:enable validateIndentation ]; } else if (this.type === 'radial') { markup = [ //jscs:disable validateIndentation '\n' //jscs:enable validateIndentation ]; } for (var i = 0; i < this.colorStops.length; i++) { markup.push( //jscs:disable validateIndentation '\n' //jscs:enable validateIndentation ); } markup.push((this.type === 'linear' ? '\n' : '\n')); return markup.join(''); }, /* _TO_SVG_END_ */ /** * Returns an instance of CanvasGradient * @param {CanvasRenderingContext2D} ctx Context to render on * @return {CanvasGradient} */ toLive: function(ctx, object) { var gradient, coords = fabric.util.object.clone(this.coords); if (!this.type) { return; } if (object.group && object.group.type === 'path-group') { for (var prop in coords) { if (prop === 'x1' || prop === 'x2') { coords[prop] += -this.offsetX + object.width / 2; } else if (prop === 'y1' || prop === 'y2') { coords[prop] += -this.offsetY + object.height / 2; } } } if (this.type === 'linear') { gradient = ctx.createLinearGradient( coords.x1, coords.y1, coords.x2, coords.y2); } else if (this.type === 'radial') { gradient = ctx.createRadialGradient( coords.x1, coords.y1, coords.r1, coords.x2, coords.y2, coords.r2); } for (var i = 0, len = this.colorStops.length; i < len; i++) { var color = this.colorStops[i].color, opacity = this.colorStops[i].opacity, offset = this.colorStops[i].offset; if (typeof opacity !== 'undefined') { color = new fabric.Color(color).setAlpha(opacity).toRgba(); } gradient.addColorStop(parseFloat(offset), color); } return gradient; } }); fabric.util.object.extend(fabric.Gradient, { /* _FROM_SVG_START_ */ /** * Returns {@link fabric.Gradient} instance from an SVG element * @static * @memberof fabric.Gradient * @param {SVGGradientElement} el SVG gradient element * @param {fabric.Object} instance * @return {fabric.Gradient} Gradient instance * @see http://www.w3.org/TR/SVG/pservers.html#LinearGradientElement * @see http://www.w3.org/TR/SVG/pservers.html#RadialGradientElement */ fromElement: function(el, instance) { /** * @example: * * * * * * * OR * * * * * * * OR * * * * * * * * OR * * * * * * * */ var colorStopEls = el.getElementsByTagName('stop'), type = (el.nodeName === 'linearGradient' ? 'linear' : 'radial'), gradientUnits = el.getAttribute('gradientUnits') || 'objectBoundingBox', gradientTransform = el.getAttribute('gradientTransform'), colorStops = [], coords = { }, ellipseMatrix; if (type === 'linear') { coords = getLinearCoords(el); } else if (type === 'radial') { coords = getRadialCoords(el); } for (var i = colorStopEls.length; i--; ) { colorStops.push(getColorStop(colorStopEls[i])); } ellipseMatrix = _convertPercentUnitsToValues(instance, coords, gradientUnits); var gradient = new fabric.Gradient({ type: type, coords: coords, colorStops: colorStops, offsetX: -instance.left, offsetY: -instance.top }); if (gradientTransform || ellipseMatrix !== '') { gradient.gradientTransform = fabric.parseTransformAttribute((gradientTransform || '') + ellipseMatrix); } return gradient; }, /* _FROM_SVG_END_ */ /** * Returns {@link fabric.Gradient} instance from its object representation * @static * @memberof fabric.Gradient * @param {Object} obj * @param {Object} [options] Options object */ forObject: function(obj, options) { options || (options = { }); _convertPercentUnitsToValues(obj, options.coords, 'userSpaceOnUse'); return new fabric.Gradient(options); } }); /** * @private */ function _convertPercentUnitsToValues(object, options, gradientUnits) { var propValue, addFactor = 0, multFactor = 1, ellipseMatrix = ''; for (var prop in options) { propValue = parseFloat(options[prop], 10); if (typeof options[prop] === 'string' && /^\d+%$/.test(options[prop])) { multFactor = 0.01; } else { multFactor = 1; } if (prop === 'x1' || prop === 'x2' || prop === 'r2') { multFactor *= gradientUnits === 'objectBoundingBox' ? object.width : 1; addFactor = gradientUnits === 'objectBoundingBox' ? object.left || 0 : 0; } else if (prop === 'y1' || prop === 'y2') { multFactor *= gradientUnits === 'objectBoundingBox' ? object.height : 1; addFactor = gradientUnits === 'objectBoundingBox' ? object.top || 0 : 0; } options[prop] = propValue * multFactor + addFactor; } if (object.type === 'ellipse' && options.r2 !== null && gradientUnits === 'objectBoundingBox' && object.rx !== object.ry) { var scaleFactor = object.ry/object.rx; ellipseMatrix = ' scale(1, ' + scaleFactor + ')'; if (options.y1) { options.y1 /= scaleFactor; } if (options.y2) { options.y2 /= scaleFactor; } } return ellipseMatrix; } })(); /** * Pattern class * @class fabric.Pattern * @see {@link http://fabricjs.com/patterns/|Pattern demo} * @see {@link http://fabricjs.com/dynamic-patterns/|DynamicPattern demo} * @see {@link fabric.Pattern#initialize} for constructor definition */ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ { /** * Repeat property of a pattern (one of repeat, repeat-x, repeat-y or no-repeat) * @type String * @default */ repeat: 'repeat', /** * Pattern horizontal offset from object's left/top corner * @type Number * @default */ offsetX: 0, /** * Pattern vertical offset from object's left/top corner * @type Number * @default */ offsetY: 0, /** * Constructor * @param {Object} [options] Options object * @return {fabric.Pattern} thisArg */ initialize: function(options) { options || (options = { }); this.id = fabric.Object.__uid++; if (options.source) { if (typeof options.source === 'string') { // function string if (typeof fabric.util.getFunctionBody(options.source) !== 'undefined') { this.source = new Function(fabric.util.getFunctionBody(options.source)); } else { // img src string var _this = this; this.source = fabric.util.createImage(); fabric.util.loadImage(options.source, function(img) { _this.source = img; }); } } else { // img element this.source = options.source; } } if (options.repeat) { this.repeat = options.repeat; } if (options.offsetX) { this.offsetX = options.offsetX; } if (options.offsetY) { this.offsetY = options.offsetY; } }, /** * Returns object representation of a pattern * @return {Object} Object representation of a pattern instance */ toObject: function() { var source; // callback if (typeof this.source === 'function') { source = String(this.source); } // element else if (typeof this.source.src === 'string') { source = this.source.src; } return { source: source, repeat: this.repeat, offsetX: this.offsetX, offsetY: this.offsetY }; }, /* _TO_SVG_START_ */ /** * Returns SVG representation of a pattern * @param {fabric.Object} object * @return {String} SVG representation of a pattern */ toSVG: function(object) { var patternSource = typeof this.source === 'function' ? this.source() : this.source, patternWidth = patternSource.width / object.getWidth(), patternHeight = patternSource.height / object.getHeight(), patternImgSrc = ''; if (patternSource.src) { patternImgSrc = patternSource.src; } else if (patternSource.toDataURL) { patternImgSrc = patternSource.toDataURL(); } return '' + '' + ''; }, /* _TO_SVG_END_ */ /** * Returns an instance of CanvasPattern * @param {CanvasRenderingContext2D} ctx Context to create pattern * @return {CanvasPattern} */ toLive: function(ctx) { var source = typeof this.source === 'function' ? this.source() : this.source; // if the image failed to load, return, and allow rest to continue loading if (!source) { return ''; } // if an image if (typeof source.src !== 'undefined') { if (!source.complete) { return ''; } if (source.naturalWidth === 0 || source.naturalHeight === 0) { return ''; } } return ctx.createPattern(source, this.repeat); } }); (function(global) { 'use strict'; var fabric = global.fabric || (global.fabric = { }); if (fabric.Shadow) { fabric.warn('fabric.Shadow is already defined.'); return; } /** * Shadow class * @class fabric.Shadow * @see {@link http://fabricjs.com/shadows/|Shadow demo} * @see {@link fabric.Shadow#initialize} for constructor definition */ fabric.Shadow = fabric.util.createClass(/** @lends fabric.Shadow.prototype */ { /** * Shadow color * @type String * @default */ color: 'rgb(0,0,0)', /** * Shadow blur * @type Number */ blur: 0, /** * Shadow horizontal offset * @type Number * @default */ offsetX: 0, /** * Shadow vertical offset * @type Number * @default */ offsetY: 0, /** * Whether the shadow should affect stroke operations * @type Boolean * @default */ affectStroke: false, /** * Indicates whether toObject should include default values * @type Boolean * @default */ includeDefaultValues: true, /** * Constructor * @param {Object|String} [options] Options object with any of color, blur, offsetX, offsetX properties or string (e.g. "rgba(0,0,0,0.2) 2px 2px 10px, "2px 2px 10px rgba(0,0,0,0.2)") * @return {fabric.Shadow} thisArg */ initialize: function(options) { if (typeof options === 'string') { options = this._parseShadow(options); } for (var prop in options) { this[prop] = options[prop]; } this.id = fabric.Object.__uid++; }, /** * @private * @param {String} shadow Shadow value to parse * @return {Object} Shadow object with color, offsetX, offsetY and blur */ _parseShadow: function(shadow) { var shadowStr = shadow.trim(), offsetsAndBlur = fabric.Shadow.reOffsetsAndBlur.exec(shadowStr) || [ ], color = shadowStr.replace(fabric.Shadow.reOffsetsAndBlur, '') || 'rgb(0,0,0)'; return { color: color.trim(), offsetX: parseInt(offsetsAndBlur[1], 10) || 0, offsetY: parseInt(offsetsAndBlur[2], 10) || 0, blur: parseInt(offsetsAndBlur[3], 10) || 0 }; }, /** * Returns a string representation of an instance * @see http://www.w3.org/TR/css-text-decor-3/#text-shadow * @return {String} Returns CSS3 text-shadow declaration */ toString: function() { return [this.offsetX, this.offsetY, this.blur, this.color].join('px '); }, /* _TO_SVG_START_ */ /** * Returns SVG representation of a shadow * @param {fabric.Object} object * @return {String} SVG representation of a shadow */ toSVG: function(object) { var mode = 'SourceAlpha'; if (object && (object.fill === this.color || object.stroke === this.color)) { mode = 'SourceGraphic'; } return ( '' + '' + '' + '' + '' + '' + '' + ''); }, /* _TO_SVG_END_ */ /** * Returns object representation of a shadow * @return {Object} Object representation of a shadow instance */ toObject: function() { if (this.includeDefaultValues) { return { color: this.color, blur: this.blur, offsetX: this.offsetX, offsetY: this.offsetY }; } var obj = { }, proto = fabric.Shadow.prototype; if (this.color !== proto.color) { obj.color = this.color; } if (this.blur !== proto.blur) { obj.blur = this.blur; } if (this.offsetX !== proto.offsetX) { obj.offsetX = this.offsetX; } if (this.offsetY !== proto.offsetY) { obj.offsetY = this.offsetY; } return obj; } }); /** * Regex matching shadow offsetX, offsetY and blur (ex: "2px 2px 10px rgba(0,0,0,0.2)", "rgb(0,255,0) 2px 2px") * @static * @field * @memberOf fabric.Shadow */ fabric.Shadow.reOffsetsAndBlur = /(?:\s|^)(-?\d+(?:px)?(?:\s?|$))?(-?\d+(?:px)?(?:\s?|$))?(\d+(?:px)?)?(?:\s?|$)(?:$|\s)/; })(typeof exports !== 'undefined' ? exports : this); (function () { 'use strict'; if (fabric.StaticCanvas) { fabric.warn('fabric.StaticCanvas is already defined.'); return; } // aliases for faster resolution var extend = fabric.util.object.extend, getElementOffset = fabric.util.getElementOffset, removeFromArray = fabric.util.removeFromArray, CANVAS_INIT_ERROR = new Error('Could not initialize `canvas` element'); /** * Static canvas class * @class fabric.StaticCanvas * @mixes fabric.Collection * @mixes fabric.Observable * @see {@link http://fabricjs.com/static_canvas/|StaticCanvas demo} * @see {@link fabric.StaticCanvas#initialize} for constructor definition * @fires before:render * @fires after:render * @fires canvas:cleared * @fires object:added * @fires object:removed */ fabric.StaticCanvas = fabric.util.createClass(/** @lends fabric.StaticCanvas.prototype */ { /** * Constructor * @param {HTMLElement | String} el <canvas> element to initialize instance on * @param {Object} [options] Options object * @return {Object} thisArg */ initialize: function(el, options) { options || (options = { }); this._initStatic(el, options); fabric.StaticCanvas.activeInstance = this; }, /** * Background color of canvas instance. * Should be set via {@link fabric.StaticCanvas#setBackgroundColor}. * @type {(String|fabric.Pattern)} * @default */ backgroundColor: '', /** * Background image of canvas instance. * Should be set via {@link fabric.StaticCanvas#setBackgroundImage}. * Backwards incompatibility note: The "backgroundImageOpacity" * and "backgroundImageStretch" properties are deprecated since 1.3.9. * Use {@link fabric.Image#opacity}, {@link fabric.Image#width} and {@link fabric.Image#height}. * @type fabric.Image * @default */ backgroundImage: null, /** * Overlay color of canvas instance. * Should be set via {@link fabric.StaticCanvas#setOverlayColor} * @since 1.3.9 * @type {(String|fabric.Pattern)} * @default */ overlayColor: '', /** * Overlay image of canvas instance. * Should be set via {@link fabric.StaticCanvas#setOverlayImage}. * Backwards incompatibility note: The "overlayImageLeft" * and "overlayImageTop" properties are deprecated since 1.3.9. * Use {@link fabric.Image#left} and {@link fabric.Image#top}. * @type fabric.Image * @default */ overlayImage: null, /** * Indicates whether toObject/toDatalessObject should include default values * @type Boolean * @default */ includeDefaultValues: true, /** * Indicates whether objects' state should be saved * @type Boolean * @default */ stateful: true, /** * Indicates whether {@link fabric.Collection.add}, {@link fabric.Collection.insertAt} and {@link fabric.Collection.remove} should also re-render canvas. * Disabling this option could give a great performance boost when adding/removing a lot of objects to/from canvas at once * (followed by a manual rendering after addition/deletion) * @type Boolean * @default */ renderOnAddRemove: true, /** * Function that determines clipping of entire canvas area * Being passed context as first argument. See clipping canvas area in {@link https://github.com/kangax/fabric.js/wiki/FAQ} * @type Function * @default */ clipTo: null, /** * Indicates whether object controls (borders/controls) are rendered above overlay image * @type Boolean * @default */ controlsAboveOverlay: false, /** * Indicates whether the browser can be scrolled when using a touchscreen and dragging on the canvas * @type Boolean * @default */ allowTouchScrolling: false, /** * Indicates whether this canvas will use image smoothing, this is on by default in browsers * @type Boolean * @default */ imageSmoothingEnabled: true, /** * Indicates whether objects should remain in current stack position when selected. When false objects are brought to top and rendered as part of the selection group * @type Boolean * @default */ preserveObjectStacking: false, /** * The transformation (in the format of Canvas transform) which focuses the viewport * @type Array * @default */ viewportTransform: [1, 0, 0, 1, 0, 0], /** * Callback; invoked right before object is about to be scaled/rotated */ onBeforeScaleRotate: function () { /* NOOP */ }, /** * @private * @param {HTMLElement | String} el <canvas> element to initialize instance on * @param {Object} [options] Options object */ _initStatic: function(el, options) { this._objects = []; this._createLowerCanvas(el); this._initOptions(options); this._setImageSmoothing(); if (options.overlayImage) { this.setOverlayImage(options.overlayImage, this.renderAll.bind(this)); } if (options.backgroundImage) { this.setBackgroundImage(options.backgroundImage, this.renderAll.bind(this)); } if (options.backgroundColor) { this.setBackgroundColor(options.backgroundColor, this.renderAll.bind(this)); } if (options.overlayColor) { this.setOverlayColor(options.overlayColor, this.renderAll.bind(this)); } this.calcOffset(); }, /** * Calculates canvas element offset relative to the document * This method is also attached as "resize" event handler of window * @return {fabric.Canvas} instance * @chainable */ calcOffset: function () { this._offset = getElementOffset(this.lowerCanvasEl); return this; }, /** * Sets {@link fabric.StaticCanvas#overlayImage|overlay image} for this canvas * @param {(fabric.Image|String)} image fabric.Image instance or URL of an image to set overlay to * @param {Function} callback callback to invoke when image is loaded and set as an overlay * @param {Object} [options] Optional options to set for the {@link fabric.Image|overlay image}. * @return {fabric.Canvas} thisArg * @chainable * @see {@link http://jsfiddle.net/fabricjs/MnzHT/|jsFiddle demo} * @example Normal overlayImage with left/top = 0 * canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), { * // Needed to position overlayImage at 0/0 * originX: 'left', * originY: 'top' * }); * @example overlayImage with different properties * canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), { * opacity: 0.5, * angle: 45, * left: 400, * top: 400, * originX: 'left', * originY: 'top' * }); * @example Stretched overlayImage #1 - width/height correspond to canvas width/height * fabric.Image.fromURL('http://fabricjs.com/assets/jail_cell_bars.png', function(img) { * img.set({width: canvas.width, height: canvas.height, originX: 'left', originY: 'top'}); * canvas.setOverlayImage(img, canvas.renderAll.bind(canvas)); * }); * @example Stretched overlayImage #2 - width/height correspond to canvas width/height * canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), { * width: canvas.width, * height: canvas.height, * // Needed to position overlayImage at 0/0 * originX: 'left', * originY: 'top' * }); */ setOverlayImage: function (image, callback, options) { return this.__setBgOverlayImage('overlayImage', image, callback, options); }, /** * Sets {@link fabric.StaticCanvas#backgroundImage|background image} for this canvas * @param {(fabric.Image|String)} image fabric.Image instance or URL of an image to set background to * @param {Function} callback Callback to invoke when image is loaded and set as background * @param {Object} [options] Optional options to set for the {@link fabric.Image|background image}. * @return {fabric.Canvas} thisArg * @chainable * @see {@link http://jsfiddle.net/fabricjs/YH9yD/|jsFiddle demo} * @example Normal backgroundImage with left/top = 0 * canvas.setBackgroundImage('http://fabricjs.com/assets/honey_im_subtle.png', canvas.renderAll.bind(canvas), { * // Needed to position backgroundImage at 0/0 * originX: 'left', * originY: 'top' * }); * @example backgroundImage with different properties * canvas.setBackgroundImage('http://fabricjs.com/assets/honey_im_subtle.png', canvas.renderAll.bind(canvas), { * opacity: 0.5, * angle: 45, * left: 400, * top: 400, * originX: 'left', * originY: 'top' * }); * @example Stretched backgroundImage #1 - width/height correspond to canvas width/height * fabric.Image.fromURL('http://fabricjs.com/assets/honey_im_subtle.png', function(img) { * img.set({width: canvas.width, height: canvas.height, originX: 'left', originY: 'top'}); * canvas.setBackgroundImage(img, canvas.renderAll.bind(canvas)); * }); * @example Stretched backgroundImage #2 - width/height correspond to canvas width/height * canvas.setBackgroundImage('http://fabricjs.com/assets/honey_im_subtle.png', canvas.renderAll.bind(canvas), { * width: canvas.width, * height: canvas.height, * // Needed to position backgroundImage at 0/0 * originX: 'left', * originY: 'top' * }); */ setBackgroundImage: function (image, callback, options) { return this.__setBgOverlayImage('backgroundImage', image, callback, options); }, /** * Sets {@link fabric.StaticCanvas#overlayColor|background color} for this canvas * @param {(String|fabric.Pattern)} overlayColor Color or pattern to set background color to * @param {Function} callback Callback to invoke when background color is set * @return {fabric.Canvas} thisArg * @chainable * @see {@link http://jsfiddle.net/fabricjs/pB55h/|jsFiddle demo} * @example Normal overlayColor - color value * canvas.setOverlayColor('rgba(255, 73, 64, 0.6)', canvas.renderAll.bind(canvas)); * @example fabric.Pattern used as overlayColor * canvas.setOverlayColor({ * source: 'http://fabricjs.com/assets/escheresque_ste.png' * }, canvas.renderAll.bind(canvas)); * @example fabric.Pattern used as overlayColor with repeat and offset * canvas.setOverlayColor({ * source: 'http://fabricjs.com/assets/escheresque_ste.png', * repeat: 'repeat', * offsetX: 200, * offsetY: 100 * }, canvas.renderAll.bind(canvas)); */ setOverlayColor: function(overlayColor, callback) { return this.__setBgOverlayColor('overlayColor', overlayColor, callback); }, /** * Sets {@link fabric.StaticCanvas#backgroundColor|background color} for this canvas * @param {(String|fabric.Pattern)} backgroundColor Color or pattern to set background color to * @param {Function} callback Callback to invoke when background color is set * @return {fabric.Canvas} thisArg * @chainable * @see {@link http://jsfiddle.net/fabricjs/hXzvk/|jsFiddle demo} * @example Normal backgroundColor - color value * canvas.setBackgroundColor('rgba(255, 73, 64, 0.6)', canvas.renderAll.bind(canvas)); * @example fabric.Pattern used as backgroundColor * canvas.setBackgroundColor({ * source: 'http://fabricjs.com/assets/escheresque_ste.png' * }, canvas.renderAll.bind(canvas)); * @example fabric.Pattern used as backgroundColor with repeat and offset * canvas.setBackgroundColor({ * source: 'http://fabricjs.com/assets/escheresque_ste.png', * repeat: 'repeat', * offsetX: 200, * offsetY: 100 * }, canvas.renderAll.bind(canvas)); */ setBackgroundColor: function(backgroundColor, callback) { return this.__setBgOverlayColor('backgroundColor', backgroundColor, callback); }, /** * @private * @see {@link http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-imagesmoothingenabled|WhatWG Canvas Standard} */ _setImageSmoothing: function() { var ctx = this.getContext(); ctx.imageSmoothingEnabled = this.imageSmoothingEnabled; ctx.imageSmoothingEnabled = this.imageSmoothingEnabled; ctx.mozImageSmoothingEnabled = this.imageSmoothingEnabled; ctx.msImageSmoothingEnabled = this.imageSmoothingEnabled; ctx.oImageSmoothingEnabled = this.imageSmoothingEnabled; }, /** * @private * @param {String} property Property to set ({@link fabric.StaticCanvas#backgroundImage|backgroundImage} * or {@link fabric.StaticCanvas#overlayImage|overlayImage}) * @param {(fabric.Image|String|null)} image fabric.Image instance, URL of an image or null to set background or overlay to * @param {Function} callback Callback to invoke when image is loaded and set as background or overlay * @param {Object} [options] Optional options to set for the {@link fabric.Image|image}. */ __setBgOverlayImage: function(property, image, callback, options) { if (typeof image === 'string') { fabric.util.loadImage(image, function(img) { this[property] = new fabric.Image(img, options); callback && callback(); }, this); } else { this[property] = image; callback && callback(); } return this; }, /** * @private * @param {String} property Property to set ({@link fabric.StaticCanvas#backgroundColor|backgroundColor} * or {@link fabric.StaticCanvas#overlayColor|overlayColor}) * @param {(Object|String|null)} color Object with pattern information, color value or null * @param {Function} [callback] Callback is invoked when color is set */ __setBgOverlayColor: function(property, color, callback) { if (color && color.source) { var _this = this; fabric.util.loadImage(color.source, function(img) { _this[property] = new fabric.Pattern({ source: img, repeat: color.repeat, offsetX: color.offsetX, offsetY: color.offsetY }); callback && callback(); }); } else { this[property] = color; callback && callback(); } return this; }, /** * @private */ _createCanvasElement: function() { var element = fabric.document.createElement('canvas'); if (!element.style) { element.style = { }; } if (!element) { throw CANVAS_INIT_ERROR; } this._initCanvasElement(element); return element; }, /** * @private * @param {HTMLElement} element */ _initCanvasElement: function(element) { fabric.util.createCanvasElement(element); if (typeof element.getContext === 'undefined') { throw CANVAS_INIT_ERROR; } }, /** * @private * @param {Object} [options] Options object */ _initOptions: function (options) { for (var prop in options) { this[prop] = options[prop]; } this.width = this.width || parseInt(this.lowerCanvasEl.width, 10) || 0; this.height = this.height || parseInt(this.lowerCanvasEl.height, 10) || 0; if (!this.lowerCanvasEl.style) { return; } this.lowerCanvasEl.width = this.width; this.lowerCanvasEl.height = this.height; this.lowerCanvasEl.style.width = this.width + 'px'; this.lowerCanvasEl.style.height = this.height + 'px'; this.viewportTransform = this.viewportTransform.slice(); }, /** * Creates a bottom canvas * @private * @param {HTMLElement} [canvasEl] */ _createLowerCanvas: function (canvasEl) { this.lowerCanvasEl = fabric.util.getById(canvasEl) || this._createCanvasElement(); this._initCanvasElement(this.lowerCanvasEl); fabric.util.addClass(this.lowerCanvasEl, 'lower-canvas'); if (this.interactive) { this._applyCanvasStyle(this.lowerCanvasEl); } this.contextContainer = this.lowerCanvasEl.getContext('2d'); }, /** * Returns canvas width (in px) * @return {Number} */ getWidth: function () { return this.width; }, /** * Returns canvas height (in px) * @return {Number} */ getHeight: function () { return this.height; }, /** * Sets width of this canvas instance * @param {Number|String} value Value to set width to * @param {Object} [options] Options object * @param {Boolean} [options.backstoreOnly=false] Set the given dimensions only as canvas backstore dimensions * @param {Boolean} [options.cssOnly=false] Set the given dimensions only as css dimensions * @return {fabric.Canvas} instance * @chainable true */ setWidth: function (value, options) { return this.setDimensions({ width: value }, options); }, /** * Sets height of this canvas instance * @param {Number|String} value Value to set height to * @param {Object} [options] Options object * @param {Boolean} [options.backstoreOnly=false] Set the given dimensions only as canvas backstore dimensions * @param {Boolean} [options.cssOnly=false] Set the given dimensions only as css dimensions * @return {fabric.Canvas} instance * @chainable true */ setHeight: function (value, options) { return this.setDimensions({ height: value }, options); }, /** * Sets dimensions (width, height) of this canvas instance. when options.cssOnly flag active you should also supply the unit of measure (px/%/em) * @param {Object} dimensions Object with width/height properties * @param {Number|String} [dimensions.width] Width of canvas element * @param {Number|String} [dimensions.height] Height of canvas element * @param {Object} [options] Options object * @param {Boolean} [options.backstoreOnly=false] Set the given dimensions only as canvas backstore dimensions * @param {Boolean} [options.cssOnly=false] Set the given dimensions only as css dimensions * @return {fabric.Canvas} thisArg * @chainable */ setDimensions: function (dimensions, options) { var cssValue; options = options || {}; for (var prop in dimensions) { cssValue = dimensions[prop]; if (!options.cssOnly) { this._setBackstoreDimension(prop, dimensions[prop]); cssValue += 'px'; } if (!options.backstoreOnly) { this._setCssDimension(prop, cssValue); } } if (!options.cssOnly) { this.renderAll(); } this.calcOffset(); return this; }, /** * Helper for setting width/height * @private * @param {String} prop property (width|height) * @param {Number} value value to set property to * @return {fabric.Canvas} instance * @chainable true */ _setBackstoreDimension: function (prop, value) { this.lowerCanvasEl[prop] = value; if (this.upperCanvasEl) { this.upperCanvasEl[prop] = value; } if (this.cacheCanvasEl) { this.cacheCanvasEl[prop] = value; } this[prop] = value; return this; }, /** * Helper for setting css width/height * @private * @param {String} prop property (width|height) * @param {String} value value to set property to * @return {fabric.Canvas} instance * @chainable true */ _setCssDimension: function (prop, value) { this.lowerCanvasEl.style[prop] = value; if (this.upperCanvasEl) { this.upperCanvasEl.style[prop] = value; } if (this.wrapperEl) { this.wrapperEl.style[prop] = value; } return this; }, /** * Returns canvas zoom level * @return {Number} */ getZoom: function () { return Math.sqrt(this.viewportTransform[0] * this.viewportTransform[3]); }, /** * Sets viewport transform of this canvas instance * @param {Array} vpt the transform in the form of context.transform * @return {fabric.Canvas} instance * @chainable true */ setViewportTransform: function (vpt) { this.viewportTransform = vpt; this.renderAll(); for (var i = 0, len = this._objects.length; i < len; i++) { this._objects[i].setCoords(); } return this; }, /** * Sets zoom level of this canvas instance, zoom centered around point * @param {fabric.Point} point to zoom with respect to * @param {Number} value to set zoom to, less than 1 zooms out * @return {fabric.Canvas} instance * @chainable true */ zoomToPoint: function (point, value) { // TODO: just change the scale, preserve other transformations var before = point; point = fabric.util.transformPoint(point, fabric.util.invertTransform(this.viewportTransform)); this.viewportTransform[0] = value; this.viewportTransform[3] = value; var after = fabric.util.transformPoint(point, this.viewportTransform); this.viewportTransform[4] += before.x - after.x; this.viewportTransform[5] += before.y - after.y; this.renderAll(); for (var i = 0, len = this._objects.length; i < len; i++) { this._objects[i].setCoords(); } return this; }, /** * Sets zoom level of this canvas instance * @param {Number} value to set zoom to, less than 1 zooms out * @return {fabric.Canvas} instance * @chainable true */ setZoom: function (value) { this.zoomToPoint(new fabric.Point(0, 0), value); return this; }, /** * Pan viewport so as to place point at top left corner of canvas * @param {fabric.Point} point to move to * @return {fabric.Canvas} instance * @chainable true */ absolutePan: function (point) { this.viewportTransform[4] = -point.x; this.viewportTransform[5] = -point.y; this.renderAll(); for (var i = 0, len = this._objects.length; i < len; i++) { this._objects[i].setCoords(); } return this; }, /** * Pans viewpoint relatively * @param {fabric.Point} point (position vector) to move by * @return {fabric.Canvas} instance * @chainable true */ relativePan: function (point) { return this.absolutePan(new fabric.Point( -point.x - this.viewportTransform[4], -point.y - this.viewportTransform[5] )); }, /** * Returns <canvas> element corresponding to this instance * @return {HTMLCanvasElement} */ getElement: function () { return this.lowerCanvasEl; }, /** * Returns currently selected object, if any * @return {fabric.Object} */ getActiveObject: function() { return null; }, /** * Returns currently selected group of object, if any * @return {fabric.Group} */ getActiveGroup: function() { return null; }, /** * Given a context, renders an object on that context * @param {CanvasRenderingContext2D} ctx Context to render object on * @param {fabric.Object} object Object to render * @private */ _draw: function (ctx, object) { if (!object) { return; } ctx.save(); var v = this.viewportTransform; ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); if (this._shouldRenderObject(object)) { object.render(ctx); } ctx.restore(); if (!this.controlsAboveOverlay) { object._renderControls(ctx); } }, _shouldRenderObject: function(object) { if (!object) { return false; } return (object !== this.getActiveGroup() || !this.preserveObjectStacking); }, /** * @private * @param {fabric.Object} obj Object that was added */ _onObjectAdded: function(obj) { this.stateful && obj.setupState(); obj.canvas = this; obj.setCoords(); this.fire('object:added', { target: obj }); obj.fire('added'); }, /** * @private * @param {fabric.Object} obj Object that was removed */ _onObjectRemoved: function(obj) { // removing active object should fire "selection:cleared" events if (this.getActiveObject() === obj) { this.fire('before:selection:cleared', { target: obj }); this._discardActiveObject(); this.fire('selection:cleared'); } this.fire('object:removed', { target: obj }); obj.fire('removed'); }, /** * Clears specified context of canvas element * @param {CanvasRenderingContext2D} ctx Context to clear * @return {fabric.Canvas} thisArg * @chainable */ clearContext: function(ctx) { ctx.clearRect(0, 0, this.width, this.height); return this; }, /** * Returns context of canvas where objects are drawn * @return {CanvasRenderingContext2D} */ getContext: function () { return this.contextContainer; }, /** * Clears all contexts (background, main, top) of an instance * @return {fabric.Canvas} thisArg * @chainable */ clear: function () { this._objects.length = 0; if (this.discardActiveGroup) { this.discardActiveGroup(); } if (this.discardActiveObject) { this.discardActiveObject(); } this.clearContext(this.contextContainer); if (this.contextTop) { this.clearContext(this.contextTop); } this.fire('canvas:cleared'); this.renderAll(); return this; }, /** * Renders both the top canvas and the secondary container canvas. * @param {Boolean} [allOnTop] Whether we want to force all images to be rendered on the top canvas * @return {fabric.Canvas} instance * @chainable */ renderAll: function (allOnTop) { var canvasToDrawOn = this[(allOnTop === true && this.interactive) ? 'contextTop' : 'contextContainer'], activeGroup = this.getActiveGroup(); if (this.contextTop && this.selection && !this._groupSelector) { this.clearContext(this.contextTop); } if (!allOnTop) { this.clearContext(canvasToDrawOn); } this.fire('before:render'); if (this.clipTo) { fabric.util.clipContext(this, canvasToDrawOn); } this._renderBackground(canvasToDrawOn); this._renderObjects(canvasToDrawOn, activeGroup); this._renderActiveGroup(canvasToDrawOn, activeGroup); if (this.clipTo) { canvasToDrawOn.restore(); } this._renderOverlay(canvasToDrawOn); if (this.controlsAboveOverlay && this.interactive) { this.drawControls(canvasToDrawOn); } this.fire('after:render'); return this; }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on * @param {fabric.Group} activeGroup */ _renderObjects: function(ctx, activeGroup) { var i, length; // fast path if (!activeGroup || this.preserveObjectStacking) { for (i = 0, length = this._objects.length; i < length; ++i) { this._draw(ctx, this._objects[i]); } } else { for (i = 0, length = this._objects.length; i < length; ++i) { if (this._objects[i] && !activeGroup.contains(this._objects[i])) { this._draw(ctx, this._objects[i]); } } } }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on * @param {fabric.Group} activeGroup */ _renderActiveGroup: function(ctx, activeGroup) { // delegate rendering to group selection (if one exists) if (activeGroup) { //Store objects in group preserving order, then replace var sortedObjects = []; this.forEachObject(function (object) { if (activeGroup.contains(object)) { sortedObjects.push(object); } }); activeGroup._set('objects', sortedObjects); this._draw(ctx, activeGroup); } }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on */ _renderBackground: function(ctx) { if (this.backgroundColor) { ctx.fillStyle = this.backgroundColor.toLive ? this.backgroundColor.toLive(ctx) : this.backgroundColor; ctx.fillRect( this.backgroundColor.offsetX || 0, this.backgroundColor.offsetY || 0, this.width, this.height); } if (this.backgroundImage) { this._draw(ctx, this.backgroundImage); } }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on */ _renderOverlay: function(ctx) { if (this.overlayColor) { ctx.fillStyle = this.overlayColor.toLive ? this.overlayColor.toLive(ctx) : this.overlayColor; ctx.fillRect( this.overlayColor.offsetX || 0, this.overlayColor.offsetY || 0, this.width, this.height); } if (this.overlayImage) { this._draw(ctx, this.overlayImage); } }, /** * Method to render only the top canvas. * Also used to render the group selection box. * @return {fabric.Canvas} thisArg * @chainable */ renderTop: function () { var ctx = this.contextTop || this.contextContainer; this.clearContext(ctx); // we render the top context - last object if (this.selection && this._groupSelector) { this._drawSelection(); } // delegate rendering to group selection if one exists // used for drawing selection borders/controls var activeGroup = this.getActiveGroup(); if (activeGroup) { activeGroup.render(ctx); } this._renderOverlay(ctx); this.fire('after:render'); return this; }, /** * Returns coordinates of a center of canvas. * Returned value is an object with top and left properties * @return {Object} object with "top" and "left" number values */ getCenter: function () { return { top: this.getHeight() / 2, left: this.getWidth() / 2 }; }, /** * Centers object horizontally. * You might need to call `setCoords` on an object after centering, to update controls area. * @param {fabric.Object} object Object to center horizontally * @return {fabric.Canvas} thisArg */ centerObjectH: function (object) { this._centerObject(object, new fabric.Point(this.getCenter().left, object.getCenterPoint().y)); this.renderAll(); return this; }, /** * Centers object vertically. * You might need to call `setCoords` on an object after centering, to update controls area. * @param {fabric.Object} object Object to center vertically * @return {fabric.Canvas} thisArg * @chainable */ centerObjectV: function (object) { this._centerObject(object, new fabric.Point(object.getCenterPoint().x, this.getCenter().top)); this.renderAll(); return this; }, /** * Centers object vertically and horizontally. * You might need to call `setCoords` on an object after centering, to update controls area. * @param {fabric.Object} object Object to center vertically and horizontally * @return {fabric.Canvas} thisArg * @chainable */ centerObject: function(object) { var center = this.getCenter(); this._centerObject(object, new fabric.Point(center.left, center.top)); this.renderAll(); return this; }, /** * @private * @param {fabric.Object} object Object to center * @param {fabric.Point} center Center point * @return {fabric.Canvas} thisArg * @chainable */ _centerObject: function(object, center) { object.setPositionByOrigin(center, 'center', 'center'); return this; }, /** * Returs dataless JSON representation of canvas * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output * @return {String} json string */ toDatalessJSON: function (propertiesToInclude) { return this.toDatalessObject(propertiesToInclude); }, /** * Returns object representation of canvas * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output * @return {Object} object representation of an instance */ toObject: function (propertiesToInclude) { return this._toObjectMethod('toObject', propertiesToInclude); }, /** * Returns dataless object representation of canvas * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output * @return {Object} object representation of an instance */ toDatalessObject: function (propertiesToInclude) { return this._toObjectMethod('toDatalessObject', propertiesToInclude); }, /** * @private */ _toObjectMethod: function (methodName, propertiesToInclude) { var activeGroup = this.getActiveGroup(); if (activeGroup) { this.discardActiveGroup(); } var data = { objects: this._toObjects(methodName, propertiesToInclude) }; extend(data, this.__serializeBgOverlay()); fabric.util.populateWithProperties(this, data, propertiesToInclude); if (activeGroup) { this.setActiveGroup(new fabric.Group(activeGroup.getObjects(), { originX: 'center', originY: 'center' })); activeGroup.forEachObject(function(o) { o.set('active', true); }); if (this._currentTransform) { this._currentTransform.target = this.getActiveGroup(); } } return data; }, /** * @private */ _toObjects: function(methodName, propertiesToInclude) { return this.getObjects().map(function(instance) { return this._toObject(instance, methodName, propertiesToInclude); }, this); }, /** * @private */ _toObject: function(instance, methodName, propertiesToInclude) { var originalValue; if (!this.includeDefaultValues) { originalValue = instance.includeDefaultValues; instance.includeDefaultValues = false; } var object = instance[methodName](propertiesToInclude); if (!this.includeDefaultValues) { instance.includeDefaultValues = originalValue; } return object; }, /** * @private */ __serializeBgOverlay: function() { var data = { background: (this.backgroundColor && this.backgroundColor.toObject) ? this.backgroundColor.toObject() : this.backgroundColor }; if (this.overlayColor) { data.overlay = this.overlayColor.toObject ? this.overlayColor.toObject() : this.overlayColor; } if (this.backgroundImage) { data.backgroundImage = this.backgroundImage.toObject(); } if (this.overlayImage) { data.overlayImage = this.overlayImage.toObject(); } return data; }, /* _TO_SVG_START_ */ /** * When true, getSvgTransform() will apply the StaticCanvas.viewportTransform to the SVG transformation. When true, * a zoomed canvas will then produce zoomed SVG output. * @type Boolean * @default */ svgViewportTransformation: true, /** * Returns SVG representation of canvas * @function * @param {Object} [options] Options object for SVG output * @param {Boolean} [options.suppressPreamble=false] If true xml tag is not included * @param {Object} [options.viewBox] SVG viewbox object * @param {Number} [options.viewBox.x] x-cooridnate of viewbox * @param {Number} [options.viewBox.y] y-coordinate of viewbox * @param {Number} [options.viewBox.width] Width of viewbox * @param {Number} [options.viewBox.height] Height of viewbox * @param {String} [options.encoding=UTF-8] Encoding of SVG output * @param {Function} [reviver] Method for further parsing of svg elements, called after each fabric object converted into svg representation. * @return {String} SVG string * @tutorial {@link http://fabricjs.com/fabric-intro-part-3/#serialization} * @see {@link http://jsfiddle.net/fabricjs/jQ3ZZ/|jsFiddle demo} * @example Normal SVG output * var svg = canvas.toSVG(); * @example SVG output without preamble (without <?xml ../>) * var svg = canvas.toSVG({suppressPreamble: true}); * @example SVG output with viewBox attribute * var svg = canvas.toSVG({ * viewBox: { * x: 100, * y: 100, * width: 200, * height: 300 * } * }); * @example SVG output with different encoding (default: UTF-8) * var svg = canvas.toSVG({encoding: 'ISO-8859-1'}); * @example Modify SVG output with reviver function * var svg = canvas.toSVG(null, function(svg) { * return svg.replace('stroke-dasharray: ; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; ', ''); * }); */ toSVG: function(options, reviver) { options || (options = { }); var markup = []; this._setSVGPreamble(markup, options); this._setSVGHeader(markup, options); this._setSVGBgOverlayColor(markup, 'backgroundColor'); this._setSVGBgOverlayImage(markup, 'backgroundImage'); this._setSVGObjects(markup, reviver); this._setSVGBgOverlayColor(markup, 'overlayColor'); this._setSVGBgOverlayImage(markup, 'overlayImage'); markup.push(''); return markup.join(''); }, /** * @private */ _setSVGPreamble: function(markup, options) { if (!options.suppressPreamble) { markup.push( '', '\n' ); } }, /** * @private */ _setSVGHeader: function(markup, options) { var width, height, vpt; if (options.viewBox) { width = options.viewBox.width; height = options.viewBox.height; } else { width = this.width; height = this.height; if (!this.svgViewportTransformation) { vpt = this.viewportTransform; width /= vpt[0]; height /= vpt[3]; } } markup.push( '', 'Created with Fabric.js ', fabric.version, '', '', fabric.createSVGFontFacesMarkup(this.getObjects()), fabric.createSVGRefElementsMarkup(this), '' ); }, /** * @private */ _setSVGObjects: function(markup, reviver) { var activeGroup = this.getActiveGroup(); if (activeGroup) { this.discardActiveGroup(); } for (var i = 0, objects = this.getObjects(), len = objects.length; i < len; i++) { markup.push(objects[i].toSVG(reviver)); } if (activeGroup) { this.setActiveGroup(new fabric.Group(activeGroup.getObjects())); activeGroup.forEachObject(function(o) { o.set('active', true); }); } }, /** * @private */ _setSVGBgOverlayImage: function(markup, property) { if (this[property] && this[property].toSVG) { markup.push(this[property].toSVG()); } }, /** * @private */ _setSVGBgOverlayColor: function(markup, property) { if (this[property] && this[property].source) { markup.push( '' ); } else if (this[property] && property === 'overlayColor') { markup.push( '' ); } }, /* _TO_SVG_END_ */ /** * Moves an object to the bottom of the stack of drawn objects * @param {fabric.Object} object Object to send to back * @return {fabric.Canvas} thisArg * @chainable */ sendToBack: function (object) { removeFromArray(this._objects, object); this._objects.unshift(object); return this.renderAll && this.renderAll(); }, /** * Moves an object to the top of the stack of drawn objects * @param {fabric.Object} object Object to send * @return {fabric.Canvas} thisArg * @chainable */ bringToFront: function (object) { removeFromArray(this._objects, object); this._objects.push(object); return this.renderAll && this.renderAll(); }, /** * Moves an object down in stack of drawn objects * @param {fabric.Object} object Object to send * @param {Boolean} [intersecting] If `true`, send object behind next lower intersecting object * @return {fabric.Canvas} thisArg * @chainable */ sendBackwards: function (object, intersecting) { var idx = this._objects.indexOf(object); // if object is not on the bottom of stack if (idx !== 0) { var newIdx = this._findNewLowerIndex(object, idx, intersecting); removeFromArray(this._objects, object); this._objects.splice(newIdx, 0, object); this.renderAll && this.renderAll(); } return this; }, /** * @private */ _findNewLowerIndex: function(object, idx, intersecting) { var newIdx; if (intersecting) { newIdx = idx; // traverse down the stack looking for the nearest intersecting object for (var i = idx - 1; i >= 0; --i) { var isIntersecting = object.intersectsWithObject(this._objects[i]) || object.isContainedWithinObject(this._objects[i]) || this._objects[i].isContainedWithinObject(object); if (isIntersecting) { newIdx = i; break; } } } else { newIdx = idx - 1; } return newIdx; }, /** * Moves an object up in stack of drawn objects * @param {fabric.Object} object Object to send * @param {Boolean} [intersecting] If `true`, send object in front of next upper intersecting object * @return {fabric.Canvas} thisArg * @chainable */ bringForward: function (object, intersecting) { var idx = this._objects.indexOf(object); // if object is not on top of stack (last item in an array) if (idx !== this._objects.length - 1) { var newIdx = this._findNewUpperIndex(object, idx, intersecting); removeFromArray(this._objects, object); this._objects.splice(newIdx, 0, object); this.renderAll && this.renderAll(); } return this; }, /** * @private */ _findNewUpperIndex: function(object, idx, intersecting) { var newIdx; if (intersecting) { newIdx = idx; // traverse up the stack looking for the nearest intersecting object for (var i = idx + 1; i < this._objects.length; ++i) { var isIntersecting = object.intersectsWithObject(this._objects[i]) || object.isContainedWithinObject(this._objects[i]) || this._objects[i].isContainedWithinObject(object); if (isIntersecting) { newIdx = i; break; } } } else { newIdx = idx + 1; } return newIdx; }, /** * Moves an object to specified level in stack of drawn objects * @param {fabric.Object} object Object to send * @param {Number} index Position to move to * @return {fabric.Canvas} thisArg * @chainable */ moveTo: function (object, index) { removeFromArray(this._objects, object); this._objects.splice(index, 0, object); return this.renderAll && this.renderAll(); }, /** * Clears a canvas element and removes all event listeners * @return {fabric.Canvas} thisArg * @chainable */ dispose: function () { this.clear(); this.interactive && this.removeListeners(); return this; }, /** * Returns a string representation of an instance * @return {String} string representation of an instance */ toString: function () { return '#'; } }); extend(fabric.StaticCanvas.prototype, fabric.Observable); extend(fabric.StaticCanvas.prototype, fabric.Collection); extend(fabric.StaticCanvas.prototype, fabric.DataURLExporter); extend(fabric.StaticCanvas, /** @lends fabric.StaticCanvas */ { /** * @static * @type String * @default */ EMPTY_JSON: '{"objects": [], "background": "white"}', /** * Provides a way to check support of some of the canvas methods * (either those of HTMLCanvasElement itself, or rendering context) * * @param {String} methodName Method to check support for; * Could be one of "getImageData", "toDataURL", "toDataURLWithQuality" or "setLineDash" * @return {Boolean | null} `true` if method is supported (or at least exists), * `null` if canvas element or context can not be initialized */ supports: function (methodName) { var el = fabric.util.createCanvasElement(); if (!el || !el.getContext) { return null; } var ctx = el.getContext('2d'); if (!ctx) { return null; } switch (methodName) { case 'getImageData': return typeof ctx.getImageData !== 'undefined'; case 'setLineDash': return typeof ctx.setLineDash !== 'undefined'; case 'toDataURL': return typeof el.toDataURL !== 'undefined'; case 'toDataURLWithQuality': try { el.toDataURL('image/jpeg', 0); return true; } catch (e) { } return false; default: return null; } } }); /** * Returns JSON representation of canvas * @function * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output * @return {String} JSON string * @tutorial {@link http://fabricjs.com/fabric-intro-part-3/#serialization} * @see {@link http://jsfiddle.net/fabricjs/pec86/|jsFiddle demo} * @example JSON without additional properties * var json = canvas.toJSON(); * @example JSON with additional properties included * var json = canvas.toJSON(['lockMovementX', 'lockMovementY', 'lockRotation', 'lockScalingX', 'lockScalingY', 'lockUniScaling']); * @example JSON without default values * canvas.includeDefaultValues = false; * var json = canvas.toJSON(); */ fabric.StaticCanvas.prototype.toJSON = fabric.StaticCanvas.prototype.toObject; })(); /** * BaseBrush class * @class fabric.BaseBrush * @see {@link http://fabricjs.com/freedrawing/|Freedrawing demo} */ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype */ { /** * Color of a brush * @type String * @default */ color: 'rgb(0, 0, 0)', /** * Width of a brush * @type Number * @default */ width: 1, /** * Shadow object representing shadow of this shape. * Backwards incompatibility note: This property replaces "shadowColor" (String), "shadowOffsetX" (Number), * "shadowOffsetY" (Number) and "shadowBlur" (Number) since v1.2.12 * @type fabric.Shadow * @default */ shadow: null, /** * Line endings style of a brush (one of "butt", "round", "square") * @type String * @default */ strokeLineCap: 'round', /** * Corner style of a brush (one of "bevil", "round", "miter") * @type String * @default */ strokeLineJoin: 'round', /** * Sets shadow of an object * @param {Object|String} [options] Options object or string (e.g. "2px 2px 10px rgba(0,0,0,0.2)") * @return {fabric.Object} thisArg * @chainable */ setShadow: function(options) { this.shadow = new fabric.Shadow(options); return this; }, /** * Sets brush styles * @private */ _setBrushStyles: function() { var ctx = this.canvas.contextTop; ctx.strokeStyle = this.color; ctx.lineWidth = this.width; ctx.lineCap = this.strokeLineCap; ctx.lineJoin = this.strokeLineJoin; }, /** * Sets brush shadow styles * @private */ _setShadow: function() { if (!this.shadow) { return; } var ctx = this.canvas.contextTop; ctx.shadowColor = this.shadow.color; ctx.shadowBlur = this.shadow.blur; ctx.shadowOffsetX = this.shadow.offsetX; ctx.shadowOffsetY = this.shadow.offsetY; }, /** * Removes brush shadow styles * @private */ _resetShadow: function() { var ctx = this.canvas.contextTop; ctx.shadowColor = ''; ctx.shadowBlur = ctx.shadowOffsetX = ctx.shadowOffsetY = 0; } }); (function() { /** * PencilBrush class * @class fabric.PencilBrush * @extends fabric.BaseBrush */ fabric.PencilBrush = fabric.util.createClass(fabric.BaseBrush, /** @lends fabric.PencilBrush.prototype */ { /** * Constructor * @param {fabric.Canvas} canvas * @return {fabric.PencilBrush} Instance of a pencil brush */ initialize: function(canvas) { this.canvas = canvas; this._points = [ ]; }, /** * Inovoked on mouse down * @param {Object} pointer */ onMouseDown: function(pointer) { this._prepareForDrawing(pointer); // capture coordinates immediately // this allows to draw dots (when movement never occurs) this._captureDrawingPath(pointer); this._render(); }, /** * Inovoked on mouse move * @param {Object} pointer */ onMouseMove: function(pointer) { this._captureDrawingPath(pointer); // redraw curve // clear top canvas this.canvas.clearContext(this.canvas.contextTop); this._render(); }, /** * Invoked on mouse up */ onMouseUp: function() { this._finalizeAndAddPath(); }, /** * @private * @param {Object} pointer Actual mouse position related to the canvas. */ _prepareForDrawing: function(pointer) { var p = new fabric.Point(pointer.x, pointer.y); this._reset(); this._addPoint(p); this.canvas.contextTop.moveTo(p.x, p.y); }, /** * @private * @param {fabric.Point} point Point to be added to points array */ _addPoint: function(point) { this._points.push(point); }, /** * Clear points array and set contextTop canvas style. * @private */ _reset: function() { this._points.length = 0; this._setBrushStyles(); this._setShadow(); }, /** * @private * @param {Object} pointer Actual mouse position related to the canvas. */ _captureDrawingPath: function(pointer) { var pointerPoint = new fabric.Point(pointer.x, pointer.y); this._addPoint(pointerPoint); }, /** * Draw a smooth path on the topCanvas using quadraticCurveTo * @private */ _render: function() { var ctx = this.canvas.contextTop, v = this.canvas.viewportTransform, p1 = this._points[0], p2 = this._points[1]; ctx.save(); ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); ctx.beginPath(); //if we only have 2 points in the path and they are the same //it means that the user only clicked the canvas without moving the mouse //then we should be drawing a dot. A path isn't drawn between two identical dots //that's why we set them apart a bit if (this._points.length === 2 && p1.x === p2.x && p1.y === p2.y) { p1.x -= 0.5; p2.x += 0.5; } ctx.moveTo(p1.x, p1.y); for (var i = 1, len = this._points.length; i < len; i++) { // we pick the point between pi + 1 & pi + 2 as the // end point and p1 as our control point. var midPoint = p1.midPointFrom(p2); ctx.quadraticCurveTo(p1.x, p1.y, midPoint.x, midPoint.y); p1 = this._points[i]; p2 = this._points[i + 1]; } // Draw last line as a straight line while // we wait for the next point to be able to calculate // the bezier control point ctx.lineTo(p1.x, p1.y); ctx.stroke(); ctx.restore(); }, /** * Converts points to SVG path * @param {Array} points Array of points * @param {Number} minX * @param {Number} minY * @return {String} SVG path */ convertPointsToSVGPath: function(points) { var path = [], p1 = new fabric.Point(points[0].x, points[0].y), p2 = new fabric.Point(points[1].x, points[1].y); path.push('M ', points[0].x, ' ', points[0].y, ' '); for (var i = 1, len = points.length; i < len; i++) { var midPoint = p1.midPointFrom(p2); // p1 is our bezier control point // midpoint is our endpoint // start point is p(i-1) value. path.push('Q ', p1.x, ' ', p1.y, ' ', midPoint.x, ' ', midPoint.y, ' '); p1 = new fabric.Point(points[i].x, points[i].y); if ((i + 1) < points.length) { p2 = new fabric.Point(points[i + 1].x, points[i + 1].y); } } path.push('L ', p1.x, ' ', p1.y, ' '); return path; }, /** * Creates fabric.Path object to add on canvas * @param {String} pathData Path data * @return {fabric.Path} Path to add on canvas */ createPath: function(pathData) { var path = new fabric.Path(pathData); path.fill = null; path.stroke = this.color; path.strokeWidth = this.width; path.strokeLineCap = this.strokeLineCap; path.strokeLineJoin = this.strokeLineJoin; if (this.shadow) { this.shadow.affectStroke = true; path.setShadow(this.shadow); } return path; }, /** * On mouseup after drawing the path on contextTop canvas * we use the points captured to create an new fabric path object * and add it to the fabric canvas. */ _finalizeAndAddPath: function() { var ctx = this.canvas.contextTop; ctx.closePath(); var pathData = this.convertPointsToSVGPath(this._points).join(''); if (pathData === 'M 0 0 Q 0 0 0 0 L 0 0') { // do not create 0 width/height paths, as they are // rendered inconsistently across browsers // Firefox 4, for example, renders a dot, // whereas Chrome 10 renders nothing this.canvas.renderAll(); return; } var path = this.createPath(pathData); this.canvas.add(path); path.setCoords(); this.canvas.clearContext(this.canvas.contextTop); this._resetShadow(); this.canvas.renderAll(); // fire event 'path' created this.canvas.fire('path:created', { path: path }); } }); })(); /** * CircleBrush class * @class fabric.CircleBrush */ fabric.CircleBrush = fabric.util.createClass(fabric.BaseBrush, /** @lends fabric.CircleBrush.prototype */ { /** * Width of a brush * @type Number * @default */ width: 10, /** * Constructor * @param {fabric.Canvas} canvas * @return {fabric.CircleBrush} Instance of a circle brush */ initialize: function(canvas) { this.canvas = canvas; this.points = [ ]; }, /** * Invoked inside on mouse down and mouse move * @param {Object} pointer */ drawDot: function(pointer) { var point = this.addPoint(pointer), ctx = this.canvas.contextTop, v = this.canvas.viewportTransform; ctx.save(); ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); ctx.fillStyle = point.fill; ctx.beginPath(); ctx.arc(point.x, point.y, point.radius, 0, Math.PI * 2, false); ctx.closePath(); ctx.fill(); ctx.restore(); }, /** * Invoked on mouse down */ onMouseDown: function(pointer) { this.points.length = 0; this.canvas.clearContext(this.canvas.contextTop); this._setShadow(); this.drawDot(pointer); }, /** * Invoked on mouse move * @param {Object} pointer */ onMouseMove: function(pointer) { this.drawDot(pointer); }, /** * Invoked on mouse up */ onMouseUp: function() { var originalRenderOnAddRemove = this.canvas.renderOnAddRemove; this.canvas.renderOnAddRemove = false; var circles = [ ]; for (var i = 0, len = this.points.length; i < len; i++) { var point = this.points[i], circle = new fabric.Circle({ radius: point.radius, left: point.x, top: point.y, originX: 'center', originY: 'center', fill: point.fill }); this.shadow && circle.setShadow(this.shadow); circles.push(circle); } var group = new fabric.Group(circles, { originX: 'center', originY: 'center' }); group.canvas = this.canvas; this.canvas.add(group); this.canvas.fire('path:created', { path: group }); this.canvas.clearContext(this.canvas.contextTop); this._resetShadow(); this.canvas.renderOnAddRemove = originalRenderOnAddRemove; this.canvas.renderAll(); }, /** * @param {Object} pointer * @return {fabric.Point} Just added pointer point */ addPoint: function(pointer) { var pointerPoint = new fabric.Point(pointer.x, pointer.y), circleRadius = fabric.util.getRandomInt( Math.max(0, this.width - 20), this.width + 20) / 2, circleColor = new fabric.Color(this.color) .setAlpha(fabric.util.getRandomInt(0, 100) / 100) .toRgba(); pointerPoint.radius = circleRadius; pointerPoint.fill = circleColor; this.points.push(pointerPoint); return pointerPoint; } }); /** * SprayBrush class * @class fabric.SprayBrush */ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric.SprayBrush.prototype */ { /** * Width of a spray * @type Number * @default */ width: 10, /** * Density of a spray (number of dots per chunk) * @type Number * @default */ density: 20, /** * Width of spray dots * @type Number * @default */ dotWidth: 1, /** * Width variance of spray dots * @type Number * @default */ dotWidthVariance: 1, /** * Whether opacity of a dot should be random * @type Boolean * @default */ randomOpacity: false, /** * Whether overlapping dots (rectangles) should be removed (for performance reasons) * @type Boolean * @default */ optimizeOverlapping: true, /** * Constructor * @param {fabric.Canvas} canvas * @return {fabric.SprayBrush} Instance of a spray brush */ initialize: function(canvas) { this.canvas = canvas; this.sprayChunks = [ ]; }, /** * Invoked on mouse down * @param {Object} pointer */ onMouseDown: function(pointer) { this.sprayChunks.length = 0; this.canvas.clearContext(this.canvas.contextTop); this._setShadow(); this.addSprayChunk(pointer); this.render(); }, /** * Invoked on mouse move * @param {Object} pointer */ onMouseMove: function(pointer) { this.addSprayChunk(pointer); this.render(); }, /** * Invoked on mouse up */ onMouseUp: function() { var originalRenderOnAddRemove = this.canvas.renderOnAddRemove; this.canvas.renderOnAddRemove = false; var rects = [ ]; for (var i = 0, ilen = this.sprayChunks.length; i < ilen; i++) { var sprayChunk = this.sprayChunks[i]; for (var j = 0, jlen = sprayChunk.length; j < jlen; j++) { var rect = new fabric.Rect({ width: sprayChunk[j].width, height: sprayChunk[j].width, left: sprayChunk[j].x + 1, top: sprayChunk[j].y + 1, originX: 'center', originY: 'center', fill: this.color }); this.shadow && rect.setShadow(this.shadow); rects.push(rect); } } if (this.optimizeOverlapping) { rects = this._getOptimizedRects(rects); } var group = new fabric.Group(rects, { originX: 'center', originY: 'center' }); group.canvas = this.canvas; this.canvas.add(group); this.canvas.fire('path:created', { path: group }); this.canvas.clearContext(this.canvas.contextTop); this._resetShadow(); this.canvas.renderOnAddRemove = originalRenderOnAddRemove; this.canvas.renderAll(); }, /** * @private * @param {Array} rects */ _getOptimizedRects: function(rects) { // avoid creating duplicate rects at the same coordinates var uniqueRects = { }, key; for (var i = 0, len = rects.length; i < len; i++) { key = rects[i].left + '' + rects[i].top; if (!uniqueRects[key]) { uniqueRects[key] = rects[i]; } } var uniqueRectsArray = [ ]; for (key in uniqueRects) { uniqueRectsArray.push(uniqueRects[key]); } return uniqueRectsArray; }, /** * Renders brush */ render: function() { var ctx = this.canvas.contextTop; ctx.fillStyle = this.color; var v = this.canvas.viewportTransform; ctx.save(); ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); for (var i = 0, len = this.sprayChunkPoints.length; i < len; i++) { var point = this.sprayChunkPoints[i]; if (typeof point.opacity !== 'undefined') { ctx.globalAlpha = point.opacity; } ctx.fillRect(point.x, point.y, point.width, point.width); } ctx.restore(); }, /** * @param {Object} pointer */ addSprayChunk: function(pointer) { this.sprayChunkPoints = [ ]; var x, y, width, radius = this.width / 2; for (var i = 0; i < this.density; i++) { x = fabric.util.getRandomInt(pointer.x - radius, pointer.x + radius); y = fabric.util.getRandomInt(pointer.y - radius, pointer.y + radius); if (this.dotWidthVariance) { width = fabric.util.getRandomInt( // bottom clamp width to 1 Math.max(1, this.dotWidth - this.dotWidthVariance), this.dotWidth + this.dotWidthVariance); } else { width = this.dotWidth; } var point = new fabric.Point(x, y); point.width = width; if (this.randomOpacity) { point.opacity = fabric.util.getRandomInt(0, 100) / 100; } this.sprayChunkPoints.push(point); } this.sprayChunks.push(this.sprayChunkPoints); } }); /** * PatternBrush class * @class fabric.PatternBrush * @extends fabric.BaseBrush */ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fabric.PatternBrush.prototype */ { getPatternSrc: function() { var dotWidth = 20, dotDistance = 5, patternCanvas = fabric.document.createElement('canvas'), patternCtx = patternCanvas.getContext('2d'); patternCanvas.width = patternCanvas.height = dotWidth + dotDistance; patternCtx.fillStyle = this.color; patternCtx.beginPath(); patternCtx.arc(dotWidth / 2, dotWidth / 2, dotWidth / 2, 0, Math.PI * 2, false); patternCtx.closePath(); patternCtx.fill(); return patternCanvas; }, getPatternSrcFunction: function() { return String(this.getPatternSrc).replace('this.color', '"' + this.color + '"'); }, /** * Creates "pattern" instance property */ getPattern: function() { return this.canvas.contextTop.createPattern(this.source || this.getPatternSrc(), 'repeat'); }, /** * Sets brush styles */ _setBrushStyles: function() { this.callSuper('_setBrushStyles'); this.canvas.contextTop.strokeStyle = this.getPattern(); }, /** * Creates path */ createPath: function(pathData) { var path = this.callSuper('createPath', pathData); path.stroke = new fabric.Pattern({ source: this.source || this.getPatternSrcFunction() }); return path; } }); (function() { var getPointer = fabric.util.getPointer, degreesToRadians = fabric.util.degreesToRadians, radiansToDegrees = fabric.util.radiansToDegrees, atan2 = Math.atan2, abs = Math.abs, STROKE_OFFSET = 0.5; /** * Canvas class * @class fabric.Canvas * @extends fabric.StaticCanvas * @tutorial {@link http://fabricjs.com/fabric-intro-part-1/#canvas} * @see {@link fabric.Canvas#initialize} for constructor definition * * @fires object:modified * @fires object:rotating * @fires object:scaling * @fires object:moving * @fires object:selected * * @fires before:selection:cleared * @fires selection:cleared * @fires selection:created * * @fires path:created * @fires mouse:down * @fires mouse:move * @fires mouse:up * @fires mouse:over * @fires mouse:out * */ fabric.Canvas = fabric.util.createClass(fabric.StaticCanvas, /** @lends fabric.Canvas.prototype */ { /** * Constructor * @param {HTMLElement | String} el <canvas> element to initialize instance on * @param {Object} [options] Options object * @return {Object} thisArg */ initialize: function(el, options) { options || (options = { }); this._initStatic(el, options); this._initInteractive(); this._createCacheCanvas(); fabric.Canvas.activeInstance = this; }, /** * When true, objects can be transformed by one side (unproportionally) * @type Boolean * @default */ uniScaleTransform: false, /** * When true, objects use center point as the origin of scale transformation. * Backwards incompatibility note: This property replaces "centerTransform" (Boolean). * @since 1.3.4 * @type Boolean * @default */ centeredScaling: false, /** * When true, objects use center point as the origin of rotate transformation. * Backwards incompatibility note: This property replaces "centerTransform" (Boolean). * @since 1.3.4 * @type Boolean * @default */ centeredRotation: false, /** * Indicates that canvas is interactive. This property should not be changed. * @type Boolean * @default */ interactive: true, /** * Indicates whether group selection should be enabled * @type Boolean * @default */ selection: true, /** * Color of selection * @type String * @default */ selectionColor: 'rgba(100, 100, 255, 0.3)', // blue /** * Default dash array pattern * If not empty the selection border is dashed * @type Array */ selectionDashArray: [ ], /** * Color of the border of selection (usually slightly darker than color of selection itself) * @type String * @default */ selectionBorderColor: 'rgba(255, 255, 255, 0.3)', /** * Width of a line used in object/group selection * @type Number * @default */ selectionLineWidth: 1, /** * Default cursor value used when hovering over an object on canvas * @type String * @default */ hoverCursor: 'move', /** * Default cursor value used when moving an object on canvas * @type String * @default */ moveCursor: 'move', /** * Default cursor value used for the entire canvas * @type String * @default */ defaultCursor: 'default', /** * Cursor value used during free drawing * @type String * @default */ freeDrawingCursor: 'crosshair', /** * Cursor value used for rotation point * @type String * @default */ rotationCursor: 'crosshair', /** * Default element class that's given to wrapper (div) element of canvas * @type String * @default */ containerClass: 'canvas-container', /** * When true, object detection happens on per-pixel basis rather than on per-bounding-box * @type Boolean * @default */ perPixelTargetFind: false, /** * Number of pixels around target pixel to tolerate (consider active) during object detection * @type Number * @default */ targetFindTolerance: 0, /** * When true, target detection is skipped when hovering over canvas. This can be used to improve performance. * @type Boolean * @default */ skipTargetFind: false, /** * @private */ _initInteractive: function() { this._currentTransform = null; this._groupSelector = null; this._initWrapperElement(); this._createUpperCanvas(); this._initEventListeners(); this.freeDrawingBrush = fabric.PencilBrush && new fabric.PencilBrush(this); this.calcOffset(); }, /** * Resets the current transform to its original values and chooses the type of resizing based on the event * @private * @param {Event} e Event object fired on mousemove */ _resetCurrentTransform: function(e) { var t = this._currentTransform; t.target.set({ scaleX: t.original.scaleX, scaleY: t.original.scaleY, left: t.original.left, top: t.original.top }); if (this._shouldCenterTransform(e, t.target)) { if (t.action === 'rotate') { this._setOriginToCenter(t.target); } else { if (t.originX !== 'center') { if (t.originX === 'right') { t.mouseXSign = -1; } else { t.mouseXSign = 1; } } if (t.originY !== 'center') { if (t.originY === 'bottom') { t.mouseYSign = -1; } else { t.mouseYSign = 1; } } t.originX = 'center'; t.originY = 'center'; } } else { t.originX = t.original.originX; t.originY = t.original.originY; } }, /** * Checks if point is contained within an area of given object * @param {Event} e Event object * @param {fabric.Object} target Object to test against * @return {Boolean} true if point is contained within an area of given object */ containsPoint: function (e, target) { var pointer = this.getPointer(e, true), xy = this._normalizePointer(target, pointer); // http://www.geog.ubc.ca/courses/klink/gis.notes/ncgia/u32.html // http://idav.ucdavis.edu/~okreylos/TAship/Spring2000/PointInPolygon.html return (target.containsPoint(xy) || target._findTargetCorner(pointer)); }, /** * @private */ _normalizePointer: function (object, pointer) { var activeGroup = this.getActiveGroup(), x = pointer.x, y = pointer.y, isObjectInGroup = ( activeGroup && object.type !== 'group' && activeGroup.contains(object)), lt; if (isObjectInGroup) { lt = new fabric.Point(activeGroup.left, activeGroup.top); lt = fabric.util.transformPoint(lt, this.viewportTransform, true); x -= lt.x; y -= lt.y; } return { x: x, y: y }; }, /** * Returns true if object is transparent at a certain location * @param {fabric.Object} target Object to check * @param {Number} x Left coordinate * @param {Number} y Top coordinate * @return {Boolean} */ isTargetTransparent: function (target, x, y) { var hasBorders = target.hasBorders, transparentCorners = target.transparentCorners; target.hasBorders = target.transparentCorners = false; this._draw(this.contextCache, target); target.hasBorders = hasBorders; target.transparentCorners = transparentCorners; var isTransparent = fabric.util.isTransparent( this.contextCache, x, y, this.targetFindTolerance); this.clearContext(this.contextCache); return isTransparent; }, /** * @private * @param {Event} e Event object * @param {fabric.Object} target */ _shouldClearSelection: function (e, target) { var activeGroup = this.getActiveGroup(), activeObject = this.getActiveObject(); return ( !target || (target && activeGroup && !activeGroup.contains(target) && activeGroup !== target && !e.shiftKey) || (target && !target.evented) || (target && !target.selectable && activeObject && activeObject !== target) ); }, /** * @private * @param {Event} e Event object * @param {fabric.Object} target */ _shouldCenterTransform: function (e, target) { if (!target) { return; } var t = this._currentTransform, centerTransform; if (t.action === 'scale' || t.action === 'scaleX' || t.action === 'scaleY') { centerTransform = this.centeredScaling || target.centeredScaling; } else if (t.action === 'rotate') { centerTransform = this.centeredRotation || target.centeredRotation; } return centerTransform ? !e.altKey : e.altKey; }, /** * @private */ _getOriginFromCorner: function(target, corner) { var origin = { x: target.originX, y: target.originY }; if (corner === 'ml' || corner === 'tl' || corner === 'bl') { origin.x = 'right'; } else if (corner === 'mr' || corner === 'tr' || corner === 'br') { origin.x = 'left'; } if (corner === 'tl' || corner === 'mt' || corner === 'tr') { origin.y = 'bottom'; } else if (corner === 'bl' || corner === 'mb' || corner === 'br') { origin.y = 'top'; } return origin; }, /** * @private */ _getActionFromCorner: function(target, corner) { var action = 'drag'; if (corner) { action = (corner === 'ml' || corner === 'mr') ? 'scaleX' : (corner === 'mt' || corner === 'mb') ? 'scaleY' : corner === 'mtr' ? 'rotate' : 'scale'; } return action; }, /** * @private * @param {Event} e Event object * @param {fabric.Object} target */ _setupCurrentTransform: function (e, target) { if (!target) { return; } var pointer = this.getPointer(e), corner = target._findTargetCorner(this.getPointer(e, true)), action = this._getActionFromCorner(target, corner), origin = this._getOriginFromCorner(target, corner); this._currentTransform = { target: target, action: action, scaleX: target.scaleX, scaleY: target.scaleY, offsetX: pointer.x - target.left, offsetY: pointer.y - target.top, originX: origin.x, originY: origin.y, ex: pointer.x, ey: pointer.y, left: target.left, top: target.top, theta: degreesToRadians(target.angle), width: target.width * target.scaleX, mouseXSign: 1, mouseYSign: 1 }; this._currentTransform.original = { left: target.left, top: target.top, scaleX: target.scaleX, scaleY: target.scaleY, originX: origin.x, originY: origin.y }; this._resetCurrentTransform(e); }, /** * Translates object by "setting" its left/top * @private * @param {Number} x pointer's x coordinate * @param {Number} y pointer's y coordinate */ _translateObject: function (x, y) { var target = this._currentTransform.target; if (!target.get('lockMovementX')) { target.set('left', x - this._currentTransform.offsetX); } if (!target.get('lockMovementY')) { target.set('top', y - this._currentTransform.offsetY); } }, /** * Scales object by invoking its scaleX/scaleY methods * @private * @param {Number} x pointer's x coordinate * @param {Number} y pointer's y coordinate * @param {String} by Either 'x' or 'y' - specifies dimension constraint by which to scale an object. * When not provided, an object is scaled by both dimensions equally */ _scaleObject: function (x, y, by) { var t = this._currentTransform, target = t.target, lockScalingX = target.get('lockScalingX'), lockScalingY = target.get('lockScalingY'), lockScalingFlip = target.get('lockScalingFlip'); if (lockScalingX && lockScalingY) { return; } // Get the constraint point var constraintPosition = target.translateToOriginPoint(target.getCenterPoint(), t.originX, t.originY), localMouse = target.toLocalPoint(new fabric.Point(x, y), t.originX, t.originY); this._setLocalMouse(localMouse, t); // Actually scale the object this._setObjectScale(localMouse, t, lockScalingX, lockScalingY, by, lockScalingFlip); // Make sure the constraints apply target.setPositionByOrigin(constraintPosition, t.originX, t.originY); }, /** * @private */ _setObjectScale: function(localMouse, transform, lockScalingX, lockScalingY, by, lockScalingFlip) { var target = transform.target, forbidScalingX = false, forbidScalingY = false; transform.newScaleX = localMouse.x / (target.width + target.strokeWidth); transform.newScaleY = localMouse.y / (target.height + target.strokeWidth); if (lockScalingFlip && transform.newScaleX <= 0 && transform.newScaleX < target.scaleX) { forbidScalingX = true; } if (lockScalingFlip && transform.newScaleY <= 0 && transform.newScaleY < target.scaleY) { forbidScalingY = true; } if (by === 'equally' && !lockScalingX && !lockScalingY) { forbidScalingX || forbidScalingY || this._scaleObjectEqually(localMouse, target, transform); } else if (!by) { forbidScalingX || lockScalingX || target.set('scaleX', transform.newScaleX); forbidScalingY || lockScalingY || target.set('scaleY', transform.newScaleY); } else if (by === 'x' && !target.get('lockUniScaling')) { forbidScalingX || lockScalingX || target.set('scaleX', transform.newScaleX); } else if (by === 'y' && !target.get('lockUniScaling')) { forbidScalingY || lockScalingY || target.set('scaleY', transform.newScaleY); } forbidScalingX || forbidScalingY || this._flipObject(transform, by); }, /** * @private */ _scaleObjectEqually: function(localMouse, target, transform) { var dist = localMouse.y + localMouse.x, lastDist = (target.height + (target.strokeWidth)) * transform.original.scaleY + (target.width + (target.strokeWidth)) * transform.original.scaleX; // We use transform.scaleX/Y instead of target.scaleX/Y // because the object may have a min scale and we'll loose the proportions transform.newScaleX = transform.original.scaleX * dist / lastDist; transform.newScaleY = transform.original.scaleY * dist / lastDist; target.set('scaleX', transform.newScaleX); target.set('scaleY', transform.newScaleY); }, /** * @private */ _flipObject: function(transform, by) { if (transform.newScaleX < 0 && by !== 'y') { if (transform.originX === 'left') { transform.originX = 'right'; } else if (transform.originX === 'right') { transform.originX = 'left'; } } if (transform.newScaleY < 0 && by !== 'x') { if (transform.originY === 'top') { transform.originY = 'bottom'; } else if (transform.originY === 'bottom') { transform.originY = 'top'; } } }, /** * @private */ _setLocalMouse: function(localMouse, t) { var target = t.target; if (t.originX === 'right') { localMouse.x *= -1; } else if (t.originX === 'center') { localMouse.x *= t.mouseXSign * 2; if (localMouse.x < 0) { t.mouseXSign = -t.mouseXSign; } } if (t.originY === 'bottom') { localMouse.y *= -1; } else if (t.originY === 'center') { localMouse.y *= t.mouseYSign * 2; if (localMouse.y < 0) { t.mouseYSign = -t.mouseYSign; } } // adjust the mouse coordinates when dealing with padding if (abs(localMouse.x) > target.padding) { if (localMouse.x < 0) { localMouse.x += target.padding; } else { localMouse.x -= target.padding; } } else { // mouse is within the padding, set to 0 localMouse.x = 0; } if (abs(localMouse.y) > target.padding) { if (localMouse.y < 0) { localMouse.y += target.padding; } else { localMouse.y -= target.padding; } } else { localMouse.y = 0; } }, /** * Rotates object by invoking its rotate method * @private * @param {Number} x pointer's x coordinate * @param {Number} y pointer's y coordinate */ _rotateObject: function (x, y) { var t = this._currentTransform; if (t.target.get('lockRotation')) { return; } var lastAngle = atan2(t.ey - t.top, t.ex - t.left), curAngle = atan2(y - t.top, x - t.left), angle = radiansToDegrees(curAngle - lastAngle + t.theta); // normalize angle to positive value if (angle < 0) { angle = 360 + angle; } t.target.angle = angle; }, /** * Set the cursor type of the canvas element * @param {String} value Cursor type of the canvas element. * @see http://www.w3.org/TR/css3-ui/#cursor */ setCursor: function (value) { this.upperCanvasEl.style.cursor = value; }, /** * @private */ _resetObjectTransform: function (target) { target.scaleX = 1; target.scaleY = 1; target.setAngle(0); }, /** * @private */ _drawSelection: function () { var ctx = this.contextTop, groupSelector = this._groupSelector, left = groupSelector.left, top = groupSelector.top, aleft = abs(left), atop = abs(top); ctx.fillStyle = this.selectionColor; ctx.fillRect( groupSelector.ex - ((left > 0) ? 0 : -left), groupSelector.ey - ((top > 0) ? 0 : -top), aleft, atop ); ctx.lineWidth = this.selectionLineWidth; ctx.strokeStyle = this.selectionBorderColor; // selection border if (this.selectionDashArray.length > 1) { var px = groupSelector.ex + STROKE_OFFSET - ((left > 0) ? 0: aleft), py = groupSelector.ey + STROKE_OFFSET - ((top > 0) ? 0: atop); ctx.beginPath(); fabric.util.drawDashedLine(ctx, px, py, px + aleft, py, this.selectionDashArray); fabric.util.drawDashedLine(ctx, px, py + atop - 1, px + aleft, py + atop - 1, this.selectionDashArray); fabric.util.drawDashedLine(ctx, px, py, px, py + atop, this.selectionDashArray); fabric.util.drawDashedLine(ctx, px + aleft - 1, py, px + aleft - 1, py + atop, this.selectionDashArray); ctx.closePath(); ctx.stroke(); } else { ctx.strokeRect( groupSelector.ex + STROKE_OFFSET - ((left > 0) ? 0 : aleft), groupSelector.ey + STROKE_OFFSET - ((top > 0) ? 0 : atop), aleft, atop ); } }, /** * @private */ _isLastRenderedObject: function(e) { return ( this.controlsAboveOverlay && this.lastRenderedObjectWithControlsAboveOverlay && this.lastRenderedObjectWithControlsAboveOverlay.visible && this.containsPoint(e, this.lastRenderedObjectWithControlsAboveOverlay) && this.lastRenderedObjectWithControlsAboveOverlay._findTargetCorner(this.getPointer(e, true))); }, /** * Method that determines what object we are clicking on * @param {Event} e mouse event * @param {Boolean} skipGroup when true, group is skipped and only objects are traversed through */ findTarget: function (e, skipGroup) { if (this.skipTargetFind) { return; } if (this._isLastRenderedObject(e)) { return this.lastRenderedObjectWithControlsAboveOverlay; } // first check current group (if one exists) var activeGroup = this.getActiveGroup(); if (activeGroup && !skipGroup && this.containsPoint(e, activeGroup)) { return activeGroup; } var target = this._searchPossibleTargets(e); this._fireOverOutEvents(target); return target; }, /** * @private */ _fireOverOutEvents: function(target) { if (target) { if (this._hoveredTarget !== target) { this.fire('mouse:over', { target: target }); target.fire('mouseover'); if (this._hoveredTarget) { this.fire('mouse:out', { target: this._hoveredTarget }); //TODO: Sean fixed bug 5-19-2014 if (!this._hoveredTarget.fire) return; this._hoveredTarget.fire('mouseout'); } this._hoveredTarget = target; } } else if (this._hoveredTarget) { this.fire('mouse:out', { target: this._hoveredTarget }); //TODO: Sean fixed bug 6-4-2014 if (!this._hoveredTarget.fire) return; this._hoveredTarget.fire('mouseout'); this._hoveredTarget = null; } }, /** * @private */ _checkTarget: function(e, obj, pointer) { if (obj && obj.visible && obj.evented && this.containsPoint(e, obj)){ if ((this.perPixelTargetFind || obj.perPixelTargetFind) && !obj.isEditing) { var isTransparent = this.isTargetTransparent(obj, pointer.x, pointer.y); if (!isTransparent) { return true; } } else { return true; } } }, /** * @private */ _searchPossibleTargets: function(e) { // Cache all targets where their bounding box contains point. var target, pointer = this.getPointer(e, true), i = this._objects.length; while (i--) { if (this._checkTarget(e, this._objects[i], pointer)){ this.relatedTarget = this._objects[i]; target = this._objects[i]; break; } } return target; }, /** * Returns pointer coordinates relative to canvas. * @param {Event} e * @return {Object} object with "x" and "y" number values */ getPointer: function (e, ignoreZoom, upperCanvasEl) { if (!upperCanvasEl) { upperCanvasEl = this.upperCanvasEl; } var pointer = getPointer(e, upperCanvasEl), bounds = upperCanvasEl.getBoundingClientRect(), boundsWidth = bounds.width || 0, boundsHeight = bounds.height || 0, cssScale; if (!boundsWidth || !boundsHeight ) { if ('top' in bounds && 'bottom' in bounds) { boundsHeight = Math.abs( bounds.top - bounds.bottom ); } if ('right' in bounds && 'left' in bounds) { boundsWidth = Math.abs( bounds.right - bounds.left ); } } this.calcOffset(); pointer.x = pointer.x - this._offset.left; pointer.y = pointer.y - this._offset.top; if (!ignoreZoom) { pointer = fabric.util.transformPoint( pointer, fabric.util.invertTransform(this.viewportTransform) ); } if (boundsWidth === 0 || boundsHeight === 0) { // If bounds are not available (i.e. not visible), do not apply scale. cssScale = { width: 1, height: 1 }; } else { cssScale = { width: upperCanvasEl.width / boundsWidth, height: upperCanvasEl.height / boundsHeight }; } return { x: pointer.x * cssScale.width, y: pointer.y * cssScale.height }; }, /** * @private * @throws {CANVAS_INIT_ERROR} If canvas can not be initialized */ _createUpperCanvas: function () { var lowerCanvasClass = this.lowerCanvasEl.className.replace(/\s*lower-canvas\s*/, ''); this.upperCanvasEl = this._createCanvasElement(); fabric.util.addClass(this.upperCanvasEl, 'upper-canvas ' + lowerCanvasClass); this.wrapperEl.appendChild(this.upperCanvasEl); this._copyCanvasStyle(this.lowerCanvasEl, this.upperCanvasEl); this._applyCanvasStyle(this.upperCanvasEl); this.contextTop = this.upperCanvasEl.getContext('2d'); }, /** * @private */ _createCacheCanvas: function () { this.cacheCanvasEl = this._createCanvasElement(); this.cacheCanvasEl.setAttribute('width', this.width); this.cacheCanvasEl.setAttribute('height', this.height); this.contextCache = this.cacheCanvasEl.getContext('2d'); }, /** * @private */ _initWrapperElement: function () { this.wrapperEl = fabric.util.wrapElement(this.lowerCanvasEl, 'div', { 'class': this.containerClass }); fabric.util.setStyle(this.wrapperEl, { width: this.getWidth() + 'px', height: this.getHeight() + 'px', position: 'relative' }); fabric.util.makeElementUnselectable(this.wrapperEl); }, /** * @private * @param {HTMLElement} element canvas element to apply styles on */ _applyCanvasStyle: function (element) { var width = this.getWidth() || element.width, height = this.getHeight() || element.height; fabric.util.setStyle(element, { position: 'absolute', width: width + 'px', height: height + 'px', left: 0, top: 0 }); element.width = width; element.height = height; fabric.util.makeElementUnselectable(element); }, /** * Copys the the entire inline style from one element (fromEl) to another (toEl) * @private * @param {Element} fromEl Element style is copied from * @param {Element} toEl Element copied style is applied to */ _copyCanvasStyle: function (fromEl, toEl) { toEl.style.cssText = fromEl.style.cssText; }, /** * Returns context of canvas where object selection is drawn * @return {CanvasRenderingContext2D} */ getSelectionContext: function() { return this.contextTop; }, /** * Returns <canvas> element on which object selection is drawn * @return {HTMLCanvasElement} */ getSelectionElement: function () { return this.upperCanvasEl; }, /** * @private * @param {Object} object */ _setActiveObject: function(object) { if (this._activeObject) { this._activeObject.set('active', false); } this._activeObject = object; object.set('active', true); }, /** * Sets given object as the only active object on canvas * @param {fabric.Object} object Object to set as an active one * @param {Event} [e] Event (passed along when firing "object:selected") * @return {fabric.Canvas} thisArg * @chainable */ setActiveObject: function (object, e) { this._setActiveObject(object); this.renderAll(); this.fire('object:selected', { target: object, e: e }); object.fire('selected', { e: e }); return this; }, /** * Returns currently active object * @return {fabric.Object} active object */ getActiveObject: function () { return this._activeObject; }, /** * @private */ _discardActiveObject: function() { if (this._activeObject) { this._activeObject.set('active', false); } this._activeObject = null; }, /** * Discards currently active object * @return {fabric.Canvas} thisArg * @chainable */ discardActiveObject: function (e) { this._discardActiveObject(); this.renderAll(); this.fire('selection:cleared', { e: e }); return this; }, /** * @private * @param {fabric.Group} group */ _setActiveGroup: function(group) { this._activeGroup = group; if (group) { group.set('active', true); } }, /** * Sets active group to a speicified one * @param {fabric.Group} group Group to set as a current one * @return {fabric.Canvas} thisArg * @chainable */ setActiveGroup: function (group, e) { this._setActiveGroup(group); if (group) { this.fire('object:selected', { target: group, e: e }); group.fire('selected', { e: e }); } return this; }, /** * Returns currently active group * @return {fabric.Group} Current group */ getActiveGroup: function () { return this._activeGroup; }, /** * @private */ _discardActiveGroup: function() { var g = this.getActiveGroup(); if (g) { g.destroy(); } this.setActiveGroup(null); }, /** * Discards currently active group * @return {fabric.Canvas} thisArg */ discardActiveGroup: function (e) { this._discardActiveGroup(); this.fire('selection:cleared', { e: e }); return this; }, /** * Deactivates all objects on canvas, removing any active group or object * @return {fabric.Canvas} thisArg */ deactivateAll: function () { var allObjects = this.getObjects(), i = 0, len = allObjects.length; for ( ; i < len; i++) { allObjects[i].set('active', false); } this._discardActiveGroup(); this._discardActiveObject(); return this; }, /** * Deactivates all objects and dispatches appropriate events * @return {fabric.Canvas} thisArg */ deactivateAllWithDispatch: function (e) { var activeObject = this.getActiveGroup() || this.getActiveObject(); if (activeObject) { this.fire('before:selection:cleared', { target: activeObject, e: e }); } this.deactivateAll(); if (activeObject) { this.fire('selection:cleared', { e: e }); } return this; }, /** * Draws objects' controls (borders/controls) * @param {CanvasRenderingContext2D} ctx Context to render controls on */ drawControls: function(ctx) { var activeGroup = this.getActiveGroup(); if (activeGroup) { this._drawGroupControls(ctx, activeGroup); } else { this._drawObjectsControls(ctx); } }, /** * @private */ _drawGroupControls: function(ctx, activeGroup) { activeGroup._renderControls(ctx); }, /** * @private */ _drawObjectsControls: function(ctx) { for (var i = 0, len = this._objects.length; i < len; ++i) { if (!this._objects[i] || !this._objects[i].active) { continue; } this._objects[i]._renderControls(ctx); this.lastRenderedObjectWithControlsAboveOverlay = this._objects[i]; } } }); // copying static properties manually to work around Opera's bug, // where "prototype" property is enumerable and overrides existing prototype for (var prop in fabric.StaticCanvas) { if (prop !== 'prototype') { fabric.Canvas[prop] = fabric.StaticCanvas[prop]; } } if (fabric.isTouchSupported) { /** @ignore */ fabric.Canvas.prototype._setCursorFromEvent = function() { }; } /** * @class fabric.Element * @alias fabric.Canvas * @deprecated Use {@link fabric.Canvas} instead. * @constructor */ fabric.Element = fabric.Canvas; })() ; (function() { var cursorOffset = { mt: 0, // n tr: 1, // ne mr: 2, // e br: 3, // se mb: 4, // s bl: 5, // sw ml: 6, // w tl: 7 // nw }, addListener = fabric.util.addListener, removeListener = fabric.util.removeListener; fabric.util.object.extend(fabric.Canvas.prototype, /** @lends fabric.Canvas.prototype */ { /** * Map of cursor style values for each of the object controls * @private */ cursorMap: [ 'n-resize', 'ne-resize', 'e-resize', 'se-resize', 's-resize', 'sw-resize', 'w-resize', 'nw-resize' ], /** * Adds mouse listeners to canvas * @private */ _initEventListeners: function () { this._bindEvents(); addListener(fabric.window, 'resize', this._onResize); // mouse events addListener(this.upperCanvasEl, 'mousedown', this._onMouseDown); addListener(this.upperCanvasEl, 'mousemove', this._onMouseMove); addListener(this.upperCanvasEl, 'mousewheel', this._onMouseWheel); // touch events addListener(this.upperCanvasEl, 'touchstart', this._onMouseDown); addListener(this.upperCanvasEl, 'touchmove', this._onMouseMove); if (typeof Event !== 'undefined' && 'add' in Event) { Event.add(this.upperCanvasEl, 'gesture', this._onGesture); Event.add(this.upperCanvasEl, 'drag', this._onDrag); Event.add(this.upperCanvasEl, 'orientation', this._onOrientationChange); Event.add(this.upperCanvasEl, 'shake', this._onShake); } }, /** * @private */ _bindEvents: function() { this._onMouseDown = this._onMouseDown.bind(this); this._onMouseMove = this._onMouseMove.bind(this); this._onMouseUp = this._onMouseUp.bind(this); this._onResize = this._onResize.bind(this); this._onGesture = this._onGesture.bind(this); this._onDrag = this._onDrag.bind(this); this._onShake = this._onShake.bind(this); this._onOrientationChange = this._onOrientationChange.bind(this); this._onMouseWheel = this._onMouseWheel.bind(this); }, /** * Removes all event listeners */ removeListeners: function() { removeListener(fabric.window, 'resize', this._onResize); removeListener(this.upperCanvasEl, 'mousedown', this._onMouseDown); removeListener(this.upperCanvasEl, 'mousemove', this._onMouseMove); removeListener(this.upperCanvasEl, 'mousewheel', this._onMouseWheel); removeListener(this.upperCanvasEl, 'touchstart', this._onMouseDown); removeListener(this.upperCanvasEl, 'touchmove', this._onMouseMove); if (typeof Event !== 'undefined' && 'remove' in Event) { Event.remove(this.upperCanvasEl, 'gesture', this._onGesture); Event.remove(this.upperCanvasEl, 'drag', this._onDrag); Event.remove(this.upperCanvasEl, 'orientation', this._onOrientationChange); Event.remove(this.upperCanvasEl, 'shake', this._onShake); } }, /** * @private * @param {Event} [e] Event object fired on Event.js gesture * @param {Event} [self] Inner Event object */ _onGesture: function(e, self) { this.__onTransformGesture && this.__onTransformGesture(e, self); }, /** * @private * @param {Event} [e] Event object fired on Event.js drag * @param {Event} [self] Inner Event object */ _onDrag: function(e, self) { this.__onDrag && this.__onDrag(e, self); }, /** * @private * @param {Event} [e] Event object fired on Event.js wheel event * @param {Event} [self] Inner Event object */ _onMouseWheel: function(e, self) { this.__onMouseWheel && this.__onMouseWheel(e, self); }, /** * @private * @param {Event} [e] Event object fired on Event.js orientation change * @param {Event} [self] Inner Event object */ _onOrientationChange: function(e, self) { this.__onOrientationChange && this.__onOrientationChange(e, self); }, /** * @private * @param {Event} [e] Event object fired on Event.js shake * @param {Event} [self] Inner Event object */ _onShake: function(e, self) { this.__onShake && this.__onShake(e, self); }, /** * @private * @param {Event} e Event object fired on mousedown */ _onMouseDown: function (e) { this.__onMouseDown(e); addListener(fabric.document, 'touchend', this._onMouseUp); addListener(fabric.document, 'touchmove', this._onMouseMove); removeListener(this.upperCanvasEl, 'mousemove', this._onMouseMove); removeListener(this.upperCanvasEl, 'touchmove', this._onMouseMove); if (e.type === 'touchstart') { // Unbind mousedown to prevent double triggers from touch devices removeListener(this.upperCanvasEl, 'mousedown', this._onMouseDown); } else { addListener(fabric.document, 'mouseup', this._onMouseUp); addListener(fabric.document, 'mousemove', this._onMouseMove); } }, /** * @private * @param {Event} e Event object fired on mouseup */ _onMouseUp: function (e) { this.__onMouseUp(e); removeListener(fabric.document, 'mouseup', this._onMouseUp); removeListener(fabric.document, 'touchend', this._onMouseUp); removeListener(fabric.document, 'mousemove', this._onMouseMove); removeListener(fabric.document, 'touchmove', this._onMouseMove); addListener(this.upperCanvasEl, 'mousemove', this._onMouseMove); addListener(this.upperCanvasEl, 'touchmove', this._onMouseMove); if (e.type === 'touchend') { // Wait 400ms before rebinding mousedown to prevent double triggers // from touch devices var _this = this; setTimeout(function() { addListener(_this.upperCanvasEl, 'mousedown', _this._onMouseDown); }, 400); } }, /** * @private * @param {Event} e Event object fired on mousemove */ _onMouseMove: function (e) { !this.allowTouchScrolling && e.preventDefault && e.preventDefault(); this.__onMouseMove(e); }, /** * @private */ _onResize: function () { this.calcOffset(); }, /** * Decides whether the canvas should be redrawn in mouseup and mousedown events. * @private * @param {Object} target * @param {Object} pointer */ _shouldRender: function(target, pointer) { var activeObject = this.getActiveGroup() || this.getActiveObject(); return !!( (target && ( target.isMoving || target !== activeObject)) || (!target && !!activeObject) || (!target && !activeObject && !this._groupSelector) || (pointer && this._previousPointer && this.selection && ( pointer.x !== this._previousPointer.x || pointer.y !== this._previousPointer.y)) ); }, /** * Method that defines the actions when mouse is released on canvas. * The method resets the currentTransform parameters, store the image corner * position in the image object and render the canvas on top. * @private * @param {Event} e Event object fired on mouseup */ __onMouseUp: function (e) { var target; if (this.isDrawingMode && this._isCurrentlyDrawing) { this._onMouseUpInDrawingMode(e); return; } if (this._currentTransform) { this._finalizeCurrentTransform(); target = this._currentTransform.target; } else { target = this.findTarget(e, true); } var shouldRender = this._shouldRender(target, this.getPointer(e)); this._maybeGroupObjects(e); if (target) { target.isMoving = false; } shouldRender && this.renderAll(); this._handleCursorAndEvent(e, target); }, _handleCursorAndEvent: function(e, target) { this._setCursorFromEvent(e, target); // TODO: why are we doing this? var _this = this; setTimeout(function () { _this._setCursorFromEvent(e, target); }, 50); this.fire('mouse:up', { target: target, e: e }); target && target.fire('mouseup', { e: e }); }, /** * @private */ _finalizeCurrentTransform: function() { var transform = this._currentTransform, target = transform.target; if (target._scaling) { target._scaling = false; } target.setCoords(); // only fire :modified event if target coordinates were changed during mousedown-mouseup if (this.stateful && target.hasStateChanged()) { this.fire('object:modified', { target: target }); target.fire('modified'); } this._restoreOriginXY(target); }, /** * @private * @param {Object} target Object to restore */ _restoreOriginXY: function(target) { if (this._previousOriginX && this._previousOriginY) { var originPoint = target.translateToOriginPoint( target.getCenterPoint(), this._previousOriginX, this._previousOriginY); target.originX = this._previousOriginX; target.originY = this._previousOriginY; target.left = originPoint.x; target.top = originPoint.y; this._previousOriginX = null; this._previousOriginY = null; } }, /** * @private * @param {Event} e Event object fired on mousedown */ _onMouseDownInDrawingMode: function(e) { this._isCurrentlyDrawing = true; this.discardActiveObject(e).renderAll(); if (this.clipTo) { fabric.util.clipContext(this, this.contextTop); } var ivt = fabric.util.invertTransform(this.viewportTransform), pointer = fabric.util.transformPoint(this.getPointer(e, true), ivt); this.freeDrawingBrush.onMouseDown(pointer); this.fire('mouse:down', { e: e }); }, /** * @private * @param {Event} e Event object fired on mousemove */ _onMouseMoveInDrawingMode: function(e) { if (this._isCurrentlyDrawing) { var ivt = fabric.util.invertTransform(this.viewportTransform), pointer = fabric.util.transformPoint(this.getPointer(e, true), ivt); this.freeDrawingBrush.onMouseMove(pointer); } this.setCursor(this.freeDrawingCursor); this.fire('mouse:move', { e: e }); }, /** * @private * @param {Event} e Event object fired on mouseup */ _onMouseUpInDrawingMode: function(e) { this._isCurrentlyDrawing = false; if (this.clipTo) { this.contextTop.restore(); } this.freeDrawingBrush.onMouseUp(); this.fire('mouse:up', { e: e }); }, /** * Method that defines the actions when mouse is clic ked on canvas. * The method inits the currentTransform parameters and renders all the * canvas so the current image can be placed on the top canvas and the rest * in on the container one. * @private * @param {Event} e Event object fired on mousedown */ __onMouseDown: function (e) { // accept only left clicks var isLeftClick = 'which' in e ? e.which === 1 : e.button === 1; if (!isLeftClick && !fabric.isTouchSupported) { return; } if (this.isDrawingMode) { this._onMouseDownInDrawingMode(e); return; } // ignore if some object is being transformed at this moment if (this._currentTransform) { return; } var target = this.findTarget(e), pointer = this.getPointer(e, true); // save pointer for check in __onMouseUp event this._previousPointer = pointer; var shouldRender = this._shouldRender(target, pointer), shouldGroup = this._shouldGroup(e, target); if (this._shouldClearSelection(e, target)) { this._clearSelection(e, target, pointer); } else if (shouldGroup) { this._handleGrouping(e, target); target = this.getActiveGroup(); } if (target && target.selectable && !shouldGroup) { this._beforeTransform(e, target); this._setupCurrentTransform(e, target); } // we must renderAll so that active image is placed on the top canvas shouldRender && this.renderAll(); this.fire('mouse:down', { target: target, e: e }); target && target.fire('mousedown', { e: e }); }, /** * @private */ _beforeTransform: function(e, target) { var corner; this.stateful && target.saveState(); // determine if it's a drag or rotate case if ((corner = target._findTargetCorner(this.getPointer(e)))) { this.onBeforeScaleRotate(target); } if (target !== this.getActiveGroup() && target !== this.getActiveObject()) { this.deactivateAll(); this.setActiveObject(target, e); } }, /** * @private */ _clearSelection: function(e, target, pointer) { this.deactivateAllWithDispatch(e); if (target && target.selectable) { this.setActiveObject(target, e); } else if (this.selection) { this._groupSelector = { ex: pointer.x, ey: pointer.y, top: 0, left: 0 }; } }, /** * @private * @param {Object} target Object for that origin is set to center */ _setOriginToCenter: function(target) { this._previousOriginX = this._currentTransform.target.originX; this._previousOriginY = this._currentTransform.target.originY; var center = target.getCenterPoint(); target.originX = 'center'; target.originY = 'center'; target.left = center.x; target.top = center.y; this._currentTransform.left = target.left; this._currentTransform.top = target.top; }, /** * @private * @param {Object} target Object for that center is set to origin */ _setCenterToOrigin: function(target) { var originPoint = target.translateToOriginPoint( target.getCenterPoint(), this._previousOriginX, this._previousOriginY); target.originX = this._previousOriginX; target.originY = this._previousOriginY; target.left = originPoint.x; target.top = originPoint.y; this._previousOriginX = null; this._previousOriginY = null; }, /** * Method that defines the actions when mouse is hovering the canvas. * The currentTransform parameter will definde whether the user is rotating/scaling/translating * an image or neither of them (only hovering). A group selection is also possible and would cancel * all any other type of action. * In case of an image transformation only the top canvas will be rendered. * @private * @param {Event} e Event object fired on mousemove */ __onMouseMove: function (e) { var target, pointer; if (this.isDrawingMode) { this._onMouseMoveInDrawingMode(e); return; } var groupSelector = this._groupSelector; // We initially clicked in an empty area, so we draw a box for multiple selection if (groupSelector) { pointer = this.getPointer(e, true); groupSelector.left = pointer.x - groupSelector.ex; groupSelector.top = pointer.y - groupSelector.ey; this.renderTop(); } else if (!this._currentTransform) { target = this.findTarget(e); if (!target || target && !target.selectable) { this.setCursor(this.defaultCursor); } else { this._setCursorFromEvent(e, target); } } else { this._transformObject(e); } this.fire('mouse:move', { target: target, e: e }); target && target.fire('mousemove', { e: e }); }, /** * @private * @param {Event} e Event fired on mousemove */ _transformObject: function(e) { var pointer = this.getPointer(e), transform = this._currentTransform; transform.reset = false, transform.target.isMoving = true; this._beforeScaleTransform(e, transform); this._performTransformAction(e, transform, pointer); this.renderAll(); }, /** * @private */ _performTransformAction: function(e, transform, pointer) { var x = pointer.x, y = pointer.y, target = transform.target, action = transform.action; if (action === 'rotate') { this._rotateObject(x, y); this._fire('rotating', target, e); } else if (action === 'scale') { this._onScale(e, transform, x, y); this._fire('scaling', target, e); } else if (action === 'scaleX') { this._scaleObject(x, y, 'x'); this._fire('scaling', target, e); } else if (action === 'scaleY') { this._scaleObject(x, y, 'y'); this._fire('scaling', target, e); } else { this._translateObject(x, y); this._fire('moving', target, e); this.setCursor(this.moveCursor); } }, /** * @private */ _fire: function(eventName, target, e) { this.fire('object:' + eventName, { target: target, e: e }); target.fire(eventName, { e: e }); }, /** * @private */ _beforeScaleTransform: function(e, transform) { if (transform.action === 'scale' || transform.action === 'scaleX' || transform.action === 'scaleY') { var centerTransform = this._shouldCenterTransform(e, transform.target); // Switch from a normal resize to center-based if ((centerTransform && (transform.originX !== 'center' || transform.originY !== 'center')) || // Switch from center-based resize to normal one (!centerTransform && transform.originX === 'center' && transform.originY === 'center') ) { this._resetCurrentTransform(e); transform.reset = true; } } }, /** * @private */ _onScale: function(e, transform, x, y) { // rotate object only if shift key is not pressed // and if it is not a group we are transforming if ((e.shiftKey || this.uniScaleTransform) && !transform.target.get('lockUniScaling')) { transform.currentAction = 'scale'; this._scaleObject(x, y); } else { // Switch from a normal resize to proportional if (!transform.reset && transform.currentAction === 'scale') { this._resetCurrentTransform(e, transform.target); } transform.currentAction = 'scaleEqually'; this._scaleObject(x, y, 'equally'); } }, /** * Sets the cursor depending on where the canvas is being hovered. * Note: very buggy in Opera * @param {Event} e Event object * @param {Object} target Object that the mouse is hovering, if so. */ _setCursorFromEvent: function (e, target) { if (!target || !target.selectable) { this.setCursor(this.defaultCursor); return false; } else { var activeGroup = this.getActiveGroup(), // only show proper corner when group selection is not active corner = target._findTargetCorner && (!activeGroup || !activeGroup.contains(target)) && target._findTargetCorner(this.getPointer(e, true)); if (!corner) { this.setCursor(target.hoverCursor || this.hoverCursor); } else { this._setCornerCursor(corner, target); } } return true; }, /** * @private */ _setCornerCursor: function(corner, target) { if (corner in cursorOffset) { this.setCursor(this._getRotatedCornerCursor(corner, target)); } else if (corner === 'mtr' && target.hasRotatingPoint) { this.setCursor(this.rotationCursor); } else { this.setCursor(this.defaultCursor); return false; } }, /** * @private */ _getRotatedCornerCursor: function(corner, target) { var n = Math.round((target.getAngle() % 360) / 45); if (n < 0) { n += 8; // full circle ahead } n += cursorOffset[corner]; // normalize n to be from 0 to 7 n %= 8; return this.cursorMap[n]; } }); })(); (function() { var min = Math.min, max = Math.max; fabric.util.object.extend(fabric.Canvas.prototype, /** @lends fabric.Canvas.prototype */ { /** * @private * @param {Event} e Event object * @param {fabric.Object} target * @return {Boolean} */ _shouldGroup: function(e, target) { var activeObject = this.getActiveObject(); return e.shiftKey && (this.getActiveGroup() || (activeObject && activeObject !== target)) && this.selection; }, /** * @private * @param {Event} e Event object * @param {fabric.Object} target */ _handleGrouping: function (e, target) { if (target === this.getActiveGroup()) { // if it's a group, find target again, this time skipping group target = this.findTarget(e, true); // if even object is not found, bail out if (!target || target.isType('group')) { return; } } if (this.getActiveGroup()) { this._updateActiveGroup(target, e); } else { this._createActiveGroup(target, e); } if (this._activeGroup) { this._activeGroup.saveCoords(); } }, /** * @private */ _updateActiveGroup: function(target, e) { var activeGroup = this.getActiveGroup(); if (activeGroup.contains(target)) { activeGroup.removeWithUpdate(target); this._resetObjectTransform(activeGroup); target.set('active', false); if (activeGroup.size() === 1) { // remove group alltogether if after removal it only contains 1 object this.discardActiveGroup(e); // activate last remaining object this.setActiveObject(activeGroup.item(0)); return; } } else { activeGroup.addWithUpdate(target); this._resetObjectTransform(activeGroup); } this.fire('selection:created', { target: activeGroup, e: e }); activeGroup.set('active', true); }, /** * @private */ _createActiveGroup: function(target, e) { if (this._activeObject && target !== this._activeObject) { var group = this._createGroup(target); group.addWithUpdate(); this.setActiveGroup(group); this._activeObject = null; this.fire('selection:created', { target: group, e: e }); } target.set('active', true); }, /** * @private * @param {Object} target */ _createGroup: function(target) { var objects = this.getObjects(), isActiveLower = objects.indexOf(this._activeObject) < objects.indexOf(target), groupObjects = isActiveLower ? [ this._activeObject, target ] : [ target, this._activeObject ]; return new fabric.Group(groupObjects, { originX: 'center', originY: 'center', canvas: this }); }, /** * @private * @param {Event} e mouse event */ _groupSelectedObjects: function (e) { var group = this._collectObjects(); // do not create group for 1 element only if (group.length === 1) { this.setActiveObject(group[0], e); } else if (group.length > 1) { group = new fabric.Group(group.reverse(), { originX: 'center', originY: 'center', canvas: this }); group.addWithUpdate(); this.setActiveGroup(group, e); group.saveCoords(); this.fire('selection:created', { target: group }); this.renderAll(); } }, /** * @private */ _collectObjects: function() { var group = [ ], currentObject, x1 = this._groupSelector.ex, y1 = this._groupSelector.ey, x2 = x1 + this._groupSelector.left, y2 = y1 + this._groupSelector.top, selectionX1Y1 = new fabric.Point(min(x1, x2), min(y1, y2)), selectionX2Y2 = new fabric.Point(max(x1, x2), max(y1, y2)), isClick = x1 === x2 && y1 === y2; for (var i = this._objects.length; i--; ) { currentObject = this._objects[i]; if (!currentObject || !currentObject.selectable || !currentObject.visible) { continue; } if (currentObject.intersectsWithRect(selectionX1Y1, selectionX2Y2) || currentObject.isContainedWithinRect(selectionX1Y1, selectionX2Y2) || currentObject.containsPoint(selectionX1Y1) || currentObject.containsPoint(selectionX2Y2) ) { currentObject.set('active', true); group.push(currentObject); // only add one object if it's a click if (isClick) { break; } } } return group; }, /** * @private */ _maybeGroupObjects: function(e) { if (this.selection && this._groupSelector) { this._groupSelectedObjects(e); } var activeGroup = this.getActiveGroup(); if (activeGroup) { activeGroup.setObjectsCoords().setCoords(); activeGroup.isMoving = false; this.setCursor(this.defaultCursor); } // clear selection and current transformation this._groupSelector = null; this._currentTransform = null; } }); })(); fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.StaticCanvas.prototype */ { /** * Exports canvas element to a dataurl image. Note that when multiplier is used, cropping is scaled appropriately * @param {Object} [options] Options object * @param {String} [options.format=png] The format of the output image. Either "jpeg" or "png" * @param {Number} [options.quality=1] Quality level (0..1). Only used for jpeg. * @param {Number} [options.multiplier=1] Multiplier to scale by * @param {Number} [options.left] Cropping left offset. Introduced in v1.2.14 * @param {Number} [options.top] Cropping top offset. Introduced in v1.2.14 * @param {Number} [options.width] Cropping width. Introduced in v1.2.14 * @param {Number} [options.height] Cropping height. Introduced in v1.2.14 * @return {String} Returns a data: URL containing a representation of the object in the format specified by options.format * @see {@link http://jsfiddle.net/fabricjs/NfZVb/|jsFiddle demo} * @example Generate jpeg dataURL with lower quality * var dataURL = canvas.toDataURL({ * format: 'jpeg', * quality: 0.8 * }); * @example Generate cropped png dataURL (clipping of canvas) * var dataURL = canvas.toDataURL({ * format: 'png', * left: 100, * top: 100, * width: 200, * height: 200 * }); * @example Generate double scaled png dataURL * var dataURL = canvas.toDataURL({ * format: 'png', * multiplier: 2 * }); */ toDataURL: function (options) { options || (options = { }); var format = options.format || 'png', quality = options.quality || 1, multiplier = options.multiplier || 1, cropping = { left: options.left, top: options.top, width: options.width, height: options.height }; if (multiplier !== 1) { return this.__toDataURLWithMultiplier(format, quality, cropping, multiplier); } else { return this.__toDataURL(format, quality, cropping); } }, /** * @private */ __toDataURL: function(format, quality, cropping) { this.renderAll(true); var canvasEl = this.upperCanvasEl || this.lowerCanvasEl, croppedCanvasEl = this.__getCroppedCanvas(canvasEl, cropping); // to avoid common confusion https://github.com/kangax/fabric.js/issues/806 if (format === 'jpg') { format = 'jpeg'; } var data = (fabric.StaticCanvas.supports('toDataURLWithQuality')) ? (croppedCanvasEl || canvasEl).toDataURL('image/' + format, quality) : (croppedCanvasEl || canvasEl).toDataURL('image/' + format); this.contextTop && this.clearContext(this.contextTop); this.renderAll(); if (croppedCanvasEl) { croppedCanvasEl = null; } return data; }, /** * @private */ __getCroppedCanvas: function(canvasEl, cropping) { var croppedCanvasEl, croppedCtx, shouldCrop = 'left' in cropping || 'top' in cropping || 'width' in cropping || 'height' in cropping; if (shouldCrop) { croppedCanvasEl = fabric.util.createCanvasElement(); croppedCtx = croppedCanvasEl.getContext('2d'); croppedCanvasEl.width = cropping.width || this.width; croppedCanvasEl.height = cropping.height || this.height; croppedCtx.drawImage(canvasEl, -cropping.left || 0, -cropping.top || 0); } return croppedCanvasEl; }, /** * @private */ __toDataURLWithMultiplier: function(format, quality, cropping, multiplier) { var origWidth = this.getWidth(), origHeight = this.getHeight(), scaledWidth = origWidth * multiplier, scaledHeight = origHeight * multiplier, activeObject = this.getActiveObject(), activeGroup = this.getActiveGroup(), ctx = this.contextTop || this.contextContainer; if (multiplier > 1) { this.setWidth(scaledWidth).setHeight(scaledHeight); } ctx.scale(multiplier, multiplier); if (cropping.left) { cropping.left *= multiplier; } if (cropping.top) { cropping.top *= multiplier; } if (cropping.width) { cropping.width *= multiplier; } else if (multiplier < 1) { cropping.width = scaledWidth; } if (cropping.height) { cropping.height *= multiplier; } else if (multiplier < 1) { cropping.height = scaledHeight; } if (activeGroup) { // not removing group due to complications with restoring it with correct state afterwords this._tempRemoveBordersControlsFromGroup(activeGroup); } else if (activeObject && this.deactivateAll) { this.deactivateAll(); } this.renderAll(true); var data = this.__toDataURL(format, quality, cropping); // restoring width, height for `renderAll` to draw // background properly (while context is scaled) this.width = origWidth; this.height = origHeight; ctx.scale(1 / multiplier, 1 / multiplier); this.setWidth(origWidth).setHeight(origHeight); if (activeGroup) { this._restoreBordersControlsOnGroup(activeGroup); } else if (activeObject && this.setActiveObject) { this.setActiveObject(activeObject); } this.contextTop && this.clearContext(this.contextTop); this.renderAll(); return data; }, /** * Exports canvas element to a dataurl image (allowing to change image size via multiplier). * @deprecated since 1.0.13 * @param {String} format (png|jpeg) * @param {Number} multiplier * @param {Number} quality (0..1) * @return {String} */ toDataURLWithMultiplier: function (format, multiplier, quality) { return this.toDataURL({ format: format, multiplier: multiplier, quality: quality }); }, /** * @private */ _tempRemoveBordersControlsFromGroup: function(group) { group.origHasControls = group.hasControls; group.origBorderColor = group.borderColor; group.hasControls = true; group.borderColor = 'rgba(0,0,0,0)'; group.forEachObject(function(o) { o.origBorderColor = o.borderColor; o.borderColor = 'rgba(0,0,0,0)'; }); }, /** * @private */ _restoreBordersControlsOnGroup: function(group) { group.hideControls = group.origHideControls; group.borderColor = group.origBorderColor; group.forEachObject(function(o) { o.borderColor = o.origBorderColor; delete o.origBorderColor; }); } }); fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.StaticCanvas.prototype */ { /** * Populates canvas with data from the specified dataless JSON. * JSON format must conform to the one of {@link fabric.Canvas#toDatalessJSON} * @deprecated since 1.2.2 * @param {String|Object} json JSON string or object * @param {Function} callback Callback, invoked when json is parsed * and corresponding objects (e.g: {@link fabric.Image}) * are initialized * @param {Function} [reviver] Method for further parsing of JSON elements, called after each fabric object created. * @return {fabric.Canvas} instance * @chainable * @tutorial {@link http://fabricjs.com/fabric-intro-part-3/#deserialization} */ loadFromDatalessJSON: function (json, callback, reviver) { return this.loadFromJSON(json, callback, reviver); }, /** * Populates canvas with data from the specified JSON. * JSON format must conform to the one of {@link fabric.Canvas#toJSON} * @param {String|Object} json JSON string or object * @param {Function} callback Callback, invoked when json is parsed * and corresponding objects (e.g: {@link fabric.Image}) * are initialized * @param {Function} [reviver] Method for further parsing of JSON elements, called after each fabric object created. * @return {fabric.Canvas} instance * @chainable * @tutorial {@link http://fabricjs.com/fabric-intro-part-3/#deserialization} * @see {@link http://jsfiddle.net/fabricjs/fmgXt/|jsFiddle demo} * @example loadFromJSON * canvas.loadFromJSON(json, canvas.renderAll.bind(canvas)); * @example loadFromJSON with reviver * canvas.loadFromJSON(json, canvas.renderAll.bind(canvas), function(o, object) { * // `o` = json object * // `object` = fabric.Object instance * // ... do some stuff ... * }); */ loadFromJSON: function (json, callback, reviver) { if (!json) { return; } // serialize if it wasn't already var serialized = (typeof json === 'string') ? JSON.parse(json) : json; this.clear(); var _this = this; this._enlivenObjects(serialized.objects, function () { _this._setBgOverlay(serialized, callback); }, reviver); return this; }, /** * @private * @param {Object} serialized Object with background and overlay information * @param {Function} callback Invoked after all background and overlay images/patterns loaded */ _setBgOverlay: function(serialized, callback) { var _this = this, loaded = { backgroundColor: false, overlayColor: false, backgroundImage: false, overlayImage: false }; if (!serialized.backgroundImage && !serialized.overlayImage && !serialized.background && !serialized.overlay) { callback && callback(); return; } var cbIfLoaded = function () { if (loaded.backgroundImage && loaded.overlayImage && loaded.backgroundColor && loaded.overlayColor) { _this.renderAll(); callback && callback(); } }; this.__setBgOverlay('backgroundImage', serialized.backgroundImage, loaded, cbIfLoaded); this.__setBgOverlay('overlayImage', serialized.overlayImage, loaded, cbIfLoaded); this.__setBgOverlay('backgroundColor', serialized.background, loaded, cbIfLoaded); this.__setBgOverlay('overlayColor', serialized.overlay, loaded, cbIfLoaded); cbIfLoaded(); }, /** * @private * @param {String} property Property to set (backgroundImage, overlayImage, backgroundColor, overlayColor) * @param {(Object|String)} value Value to set * @param {Object} loaded Set loaded property to true if property is set * @param {Object} callback Callback function to invoke after property is set */ __setBgOverlay: function(property, value, loaded, callback) { var _this = this; if (!value) { loaded[property] = true; return; } if (property === 'backgroundImage' || property === 'overlayImage') { fabric.Image.fromObject(value, function(img) { _this[property] = img; loaded[property] = true; callback && callback(); }); } else { this['set' + fabric.util.string.capitalize(property, true)](value, function() { loaded[property] = true; callback && callback(); }); } }, /** * @private * @param {Array} objects * @param {Function} callback * @param {Function} [reviver] */ _enlivenObjects: function (objects, callback, reviver) { var _this = this; if (!objects || objects.length === 0) { callback && callback(); return; } var renderOnAddRemove = this.renderOnAddRemove; this.renderOnAddRemove = false; fabric.util.enlivenObjects(objects, function(enlivenedObjects) { enlivenedObjects.forEach(function(obj, index) { _this.insertAt(obj, index, true); }); _this.renderOnAddRemove = renderOnAddRemove; callback && callback(); }, null, reviver); }, /** * @private * @param {String} format * @param {Function} callback */ _toDataURL: function (format, callback) { this.clone(function (clone) { callback(clone.toDataURL(format)); }); }, /** * @private * @param {String} format * @param {Number} multiplier * @param {Function} callback */ _toDataURLWithMultiplier: function (format, multiplier, callback) { this.clone(function (clone) { callback(clone.toDataURLWithMultiplier(format, multiplier)); }); }, /** * Clones canvas instance * @param {Object} [callback] Receives cloned instance as a first argument * @param {Array} [properties] Array of properties to include in the cloned canvas and children */ clone: function (callback, properties) { var data = JSON.stringify(this.toJSON(properties)); this.cloneWithoutData(function(clone) { clone.loadFromJSON(data, function() { callback && callback(clone); }); }); }, /** * Clones canvas instance without cloning existing data. * This essentially copies canvas dimensions, clipping properties, etc. * but leaves data empty (so that you can populate it with your own) * @param {Object} [callback] Receives cloned instance as a first argument */ cloneWithoutData: function(callback) { var el = fabric.document.createElement('canvas'); el.width = this.getWidth(); el.height = this.getHeight(); var clone = new fabric.Canvas(el); clone.clipTo = this.clipTo; if (this.backgroundImage) { clone.setBackgroundImage(this.backgroundImage.src, function() { clone.renderAll(); callback && callback(clone); }); clone.backgroundImageOpacity = this.backgroundImageOpacity; clone.backgroundImageStretch = this.backgroundImageStretch; } else { callback && callback(clone); } } }); (function(global) { 'use strict'; var fabric = global.fabric || (global.fabric = { }), extend = fabric.util.object.extend, toFixed = fabric.util.toFixed, capitalize = fabric.util.string.capitalize, degreesToRadians = fabric.util.degreesToRadians, supportsLineDash = fabric.StaticCanvas.supports('setLineDash'); if (fabric.Object) { return; } /** * Root object class from which all 2d shape classes inherit from * @class fabric.Object * @tutorial {@link http://fabricjs.com/fabric-intro-part-1/#objects} * @see {@link fabric.Object#initialize} for constructor definition * * @fires added * @fires removed * * @fires selected * @fires modified * @fires rotating * @fires scaling * @fires moving * * @fires mousedown * @fires mouseup */ fabric.Object = fabric.util.createClass(/** @lends fabric.Object.prototype */ { /** * Retrieves object's {@link fabric.Object#clipTo|clipping function} * @method getClipTo * @memberOf fabric.Object.prototype * @return {Function} */ /** * Sets object's {@link fabric.Object#clipTo|clipping function} * @method setClipTo * @memberOf fabric.Object.prototype * @param {Function} clipTo Clipping function * @return {fabric.Object} thisArg * @chainable */ /** * Retrieves object's {@link fabric.Object#transformMatrix|transformMatrix} * @method getTransformMatrix * @memberOf fabric.Object.prototype * @return {Array} transformMatrix */ /** * Sets object's {@link fabric.Object#transformMatrix|transformMatrix} * @method setTransformMatrix * @memberOf fabric.Object.prototype * @param {Array} transformMatrix * @return {fabric.Object} thisArg * @chainable */ /** * Retrieves object's {@link fabric.Object#visible|visible} state * @method getVisible * @memberOf fabric.Object.prototype * @return {Boolean} True if visible */ /** * Sets object's {@link fabric.Object#visible|visible} state * @method setVisible * @memberOf fabric.Object.prototype * @param {Boolean} value visible value * @return {fabric.Object} thisArg * @chainable */ /** * Retrieves object's {@link fabric.Object#shadow|shadow} * @method getShadow * @memberOf fabric.Object.prototype * @return {Object} Shadow instance */ /** * Retrieves object's {@link fabric.Object#stroke|stroke} * @method getStroke * @memberOf fabric.Object.prototype * @return {String} stroke value */ /** * Sets object's {@link fabric.Object#stroke|stroke} * @method setStroke * @memberOf fabric.Object.prototype * @param {String} value stroke value * @return {fabric.Object} thisArg * @chainable */ /** * Retrieves object's {@link fabric.Object#strokeWidth|strokeWidth} * @method getStrokeWidth * @memberOf fabric.Object.prototype * @return {Number} strokeWidth value */ /** * Sets object's {@link fabric.Object#strokeWidth|strokeWidth} * @method setStrokeWidth * @memberOf fabric.Object.prototype * @param {Number} value strokeWidth value * @return {fabric.Object} thisArg * @chainable */ /** * Retrieves object's {@link fabric.Object#originX|originX} * @method getOriginX * @memberOf fabric.Object.prototype * @return {String} originX value */ /** * Sets object's {@link fabric.Object#originX|originX} * @method setOriginX * @memberOf fabric.Object.prototype * @param {String} value originX value * @return {fabric.Object} thisArg * @chainable */ /** * Retrieves object's {@link fabric.Object#originY|originY} * @method getOriginY * @memberOf fabric.Object.prototype * @return {String} originY value */ /** * Sets object's {@link fabric.Object#originY|originY} * @method setOriginY * @memberOf fabric.Object.prototype * @param {String} value originY value * @return {fabric.Object} thisArg * @chainable */ /** * Retrieves object's {@link fabric.Object#fill|fill} * @method getFill * @memberOf fabric.Object.prototype * @return {String} Fill value */ /** * Sets object's {@link fabric.Object#fill|fill} * @method setFill * @memberOf fabric.Object.prototype * @param {String} value Fill value * @return {fabric.Object} thisArg * @chainable */ /** * Retrieves object's {@link fabric.Object#opacity|opacity} * @method getOpacity * @memberOf fabric.Object.prototype * @return {Number} Opacity value (0-1) */ /** * Sets object's {@link fabric.Object#opacity|opacity} * @method setOpacity * @memberOf fabric.Object.prototype * @param {Number} value Opacity value (0-1) * @return {fabric.Object} thisArg * @chainable */ /** * Retrieves object's {@link fabric.Object#angle|angle} (in degrees) * @method getAngle * @memberOf fabric.Object.prototype * @return {Number} */ /** * Sets object's {@link fabric.Object#angle|angle} * @method setAngle * @memberOf fabric.Object.prototype * @param {Number} value Angle value (in degrees) * @return {fabric.Object} thisArg * @chainable */ /** * Retrieves object's {@link fabric.Object#top|top position} * @method getTop * @memberOf fabric.Object.prototype * @return {Number} Top value (in pixels) */ /** * Sets object's {@link fabric.Object#top|top position} * @method setTop * @memberOf fabric.Object.prototype * @param {Number} value Top value (in pixels) * @return {fabric.Object} thisArg * @chainable */ /** * Retrieves object's {@link fabric.Object#left|left position} * @method getLeft * @memberOf fabric.Object.prototype * @return {Number} Left value (in pixels) */ /** * Sets object's {@link fabric.Object#left|left position} * @method setLeft * @memberOf fabric.Object.prototype * @param {Number} value Left value (in pixels) * @return {fabric.Object} thisArg * @chainable */ /** * Retrieves object's {@link fabric.Object#scaleX|scaleX} value * @method getScaleX * @memberOf fabric.Object.prototype * @return {Number} scaleX value */ /** * Sets object's {@link fabric.Object#scaleX|scaleX} value * @method setScaleX * @memberOf fabric.Object.prototype * @param {Number} value scaleX value * @return {fabric.Object} thisArg * @chainable */ /** * Retrieves object's {@link fabric.Object#scaleY|scaleY} value * @method getScaleY * @memberOf fabric.Object.prototype * @return {Number} scaleY value */ /** * Sets object's {@link fabric.Object#scaleY|scaleY} value * @method setScaleY * @memberOf fabric.Object.prototype * @param {Number} value scaleY value * @return {fabric.Object} thisArg * @chainable */ /** * Retrieves object's {@link fabric.Object#flipX|flipX} value * @method getFlipX * @memberOf fabric.Object.prototype * @return {Boolean} flipX value */ /** * Sets object's {@link fabric.Object#flipX|flipX} value * @method setFlipX * @memberOf fabric.Object.prototype * @param {Boolean} value flipX value * @return {fabric.Object} thisArg * @chainable */ /** * Retrieves object's {@link fabric.Object#flipY|flipY} value * @method getFlipY * @memberOf fabric.Object.prototype * @return {Boolean} flipY value */ /** * Sets object's {@link fabric.Object#flipY|flipY} value * @method setFlipY * @memberOf fabric.Object.prototype * @param {Boolean} value flipY value * @return {fabric.Object} thisArg * @chainable */ /** * Type of an object (rect, circle, path, etc.) * @type String * @default */ type: 'object', /** * Horizontal origin of transformation of an object (one of "left", "right", "center") * @type String * @default */ originX: 'left', /** * Vertical origin of transformation of an object (one of "top", "bottom", "center") * @type String * @default */ originY: 'top', /** * Top position of an object. Note that by default it's relative to object center. You can change this by setting originY={top/center/bottom} * @type Number * @default */ top: 0, /** * Left position of an object. Note that by default it's relative to object center. You can change this by setting originX={left/center/right} * @type Number * @default */ left: 0, /** * Object width * @type Number * @default */ width: 0, /** * Object height * @type Number * @default */ height: 0, /** * Object scale factor (horizontal) * @type Number * @default */ scaleX: 1, /** * Object scale factor (vertical) * @type Number * @default */ scaleY: 1, /** * When true, an object is rendered as flipped horizontally * @type Boolean * @default */ flipX: false, /** * When true, an object is rendered as flipped vertically * @type Boolean * @default */ flipY: false, /** * Opacity of an object * @type Number * @default */ opacity: 1, /** * Angle of rotation of an object (in degrees) * @type Number * @default */ angle: 0, /** * Size of object's controlling corners (in pixels) * @type Number * @default */ cornerSize: 12, /** * When true, object's controlling corners are rendered as transparent inside (i.e. stroke instead of fill) * @type Boolean * @default */ transparentCorners: true, /** * Default cursor value used when hovering over this object on canvas * @type String * @default */ hoverCursor: null, /** * Padding between object and its controlling borders (in pixels) * @type Number * @default */ padding: 0, /** * Color of controlling borders of an object (when it's active) * @type String * @default */ borderColor: 'rgba(102,153,255,0.75)', /** * Color of controlling corners of an object (when it's active) * @type String * @default */ cornerColor: 'rgba(102,153,255,0.5)', /** * When true, this object will use center point as the origin of transformation * when being scaled via the controls. * Backwards incompatibility note: This property replaces "centerTransform" (Boolean). * @since 1.3.4 * @type Boolean * @default */ centeredScaling: false, /** * When true, this object will use center point as the origin of transformation * when being rotated via the controls. * Backwards incompatibility note: This property replaces "centerTransform" (Boolean). * @since 1.3.4 * @type Boolean * @default */ centeredRotation: true, /** * Color of object's fill * @type String * @default */ fill: 'rgb(0,0,0)', /** * Fill rule used to fill an object * accepted values are nonzero, evenodd * Backwards incompatibility note: This property was used for setting globalCompositeOperation until v1.4.12 (use `fabric.Object#globalCompositeOperation` instead) * @type String * @default */ fillRule: 'nonzero', /** * Composite rule used for canvas globalCompositeOperation * @type String * @default */ globalCompositeOperation: 'source-over', /** * Background color of an object. Only works with text objects at the moment. * @type String * @default */ backgroundColor: '', /** * When defined, an object is rendered via stroke and this property specifies its color * @type String * @default */ stroke: null, /** * Width of a stroke used to render this object * @type Number * @default */ strokeWidth: 1, /** * Array specifying dash pattern of an object's stroke (stroke must be defined) * @type Array */ strokeDashArray: null, /** * Line endings style of an object's stroke (one of "butt", "round", "square") * @type String * @default */ strokeLineCap: 'butt', /** * Corner style of an object's stroke (one of "bevil", "round", "miter") * @type String * @default */ strokeLineJoin: 'miter', /** * Maximum miter length (used for strokeLineJoin = "miter") of an object's stroke * @type Number * @default */ strokeMiterLimit: 10, /** * Shadow object representing shadow of this shape * @type fabric.Shadow * @default */ shadow: null, /** * Opacity of object's controlling borders when object is active and moving * @type Number * @default */ borderOpacityWhenMoving: 0.4, /** * Scale factor of object's controlling borders * @type Number * @default */ borderScaleFactor: 1, /** * Transform matrix (similar to SVG's transform matrix) * @type Array */ transformMatrix: null, /** * Minimum allowed scale value of an object * @type Number * @default */ minScaleLimit: 0.01, /** * When set to `false`, an object can not be selected for modification (using either point-click-based or group-based selection). * But events still fire on it. * @type Boolean * @default */ selectable: true, /** * When set to `false`, an object can not be a target of events. All events propagate through it. Introduced in v1.3.4 * @type Boolean * @default */ evented: true, /** * When set to `false`, an object is not rendered on canvas * @type Boolean * @default */ visible: true, /** * When set to `false`, object's controls are not displayed and can not be used to manipulate object * @type Boolean * @default */ hasControls: true, /** * When set to `false`, object's controlling borders are not rendered * @type Boolean * @default */ hasBorders: true, /** * When set to `false`, object's controlling rotating point will not be visible or selectable * @type Boolean * @default */ hasRotatingPoint: true, /** * Offset for object's controlling rotating point (when enabled via `hasRotatingPoint`) * @type Number * @default */ rotatingPointOffset: 40, /** * When set to `true`, objects are "found" on canvas on per-pixel basis rather than according to bounding box * @type Boolean * @default */ perPixelTargetFind: false, /** * When `false`, default object's values are not included in its serialization * @type Boolean * @default */ includeDefaultValues: true, /** * Function that determines clipping of an object (context is passed as a first argument) * Note that context origin is at the object's center point (not left/top corner) * @type Function */ clipTo: null, /** * When `true`, object horizontal movement is locked * @type Boolean * @default */ lockMovementX: false, /** * When `true`, object vertical movement is locked * @type Boolean * @default */ lockMovementY: false, /** * When `true`, object rotation is locked * @type Boolean * @default */ lockRotation: false, /** * When `true`, object horizontal scaling is locked * @type Boolean * @default */ lockScalingX: false, /** * When `true`, object vertical scaling is locked * @type Boolean * @default */ lockScalingY: false, /** * When `true`, object non-uniform scaling is locked * @type Boolean * @default */ lockUniScaling: false, /** * When `true`, object cannot be flipped by scaling into negative values * @type Boolean * @default */ lockScalingFlip: false, /** * List of properties to consider when checking if state * of an object is changed (fabric.Object#hasStateChanged) * as well as for history (undo/redo) purposes * @type Array */ stateProperties: ( 'top left width height scaleX scaleY flipX flipY originX originY transformMatrix ' + 'stroke strokeWidth strokeDashArray strokeLineCap strokeLineJoin strokeMiterLimit ' + 'angle opacity fill fillRule globalCompositeOperation shadow clipTo visible backgroundColor' ).split(' '), /** * Constructor * @param {Object} [options] Options object */ initialize: function(options) { if (options) { this.setOptions(options); } }, /** * @private * @param {Object} [options] Options object */ _initGradient: function(options) { if (options.fill && options.fill.colorStops && !(options.fill instanceof fabric.Gradient)) { this.set('fill', new fabric.Gradient(options.fill)); } }, /** * @private * @param {Object} [options] Options object */ _initPattern: function(options) { if (options.fill && options.fill.source && !(options.fill instanceof fabric.Pattern)) { this.set('fill', new fabric.Pattern(options.fill)); } if (options.stroke && options.stroke.source && !(options.stroke instanceof fabric.Pattern)) { this.set('stroke', new fabric.Pattern(options.stroke)); } }, /** * @private * @param {Object} [options] Options object */ _initClipping: function(options) { if (!options.clipTo || typeof options.clipTo !== 'string') { return; } var functionBody = fabric.util.getFunctionBody(options.clipTo); if (typeof functionBody !== 'undefined') { this.clipTo = new Function('ctx', functionBody); } }, /** * Sets object's properties from options * @param {Object} [options] Options object */ setOptions: function(options) { for (var prop in options) { this.set(prop, options[prop]); } this._initGradient(options); this._initPattern(options); this._initClipping(options); }, /** * Transforms context when rendering an object * @param {CanvasRenderingContext2D} ctx Context * @param {Boolean} fromLeft When true, context is transformed to object's top/left corner. This is used when rendering text on Node */ transform: function(ctx, fromLeft) { if (this.group) { this.group.transform(ctx, fromLeft); } var center = fromLeft ? this._getLeftTopCoords() : this.getCenterPoint(); ctx.translate(center.x, center.y); ctx.rotate(degreesToRadians(this.angle)); ctx.scale( this.scaleX * (this.flipX ? -1 : 1), this.scaleY * (this.flipY ? -1 : 1) ); }, /** * Returns an object representation of an instance * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output * @return {Object} Object representation of an instance */ toObject: function(propertiesToInclude) { var NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS, object = { type: this.type, originX: this.originX, originY: this.originY, left: toFixed(this.left, NUM_FRACTION_DIGITS), top: toFixed(this.top, NUM_FRACTION_DIGITS), width: toFixed(this.width, NUM_FRACTION_DIGITS), height: toFixed(this.height, NUM_FRACTION_DIGITS), fill: (this.fill && this.fill.toObject) ? this.fill.toObject() : this.fill, stroke: (this.stroke && this.stroke.toObject) ? this.stroke.toObject() : this.stroke, strokeWidth: toFixed(this.strokeWidth, NUM_FRACTION_DIGITS), strokeDashArray: this.strokeDashArray, strokeLineCap: this.strokeLineCap, strokeLineJoin: this.strokeLineJoin, strokeMiterLimit: toFixed(this.strokeMiterLimit, NUM_FRACTION_DIGITS), scaleX: toFixed(this.scaleX, NUM_FRACTION_DIGITS), scaleY: toFixed(this.scaleY, NUM_FRACTION_DIGITS), angle: toFixed(this.getAngle(), NUM_FRACTION_DIGITS), flipX: this.flipX, flipY: this.flipY, opacity: toFixed(this.opacity, NUM_FRACTION_DIGITS), shadow: (this.shadow && this.shadow.toObject) ? this.shadow.toObject() : this.shadow, visible: this.visible, clipTo: this.clipTo && String(this.clipTo), backgroundColor: this.backgroundColor, fillRule: this.fillRule, globalCompositeOperation: this.globalCompositeOperation }; if (!this.includeDefaultValues) { object = this._removeDefaultValues(object); } fabric.util.populateWithProperties(this, object, propertiesToInclude); return object; }, /** * Returns (dataless) object representation of an instance * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output * @return {Object} Object representation of an instance */ toDatalessObject: function(propertiesToInclude) { // will be overwritten by subclasses return this.toObject(propertiesToInclude); }, /** * @private * @param {Object} object */ _removeDefaultValues: function(object) { var prototype = fabric.util.getKlass(object.type).prototype, stateProperties = prototype.stateProperties; stateProperties.forEach(function(prop) { if (object[prop] === prototype[prop]) { delete object[prop]; } }); return object; }, /** * Returns a string representation of an instance * @return {String} */ toString: function() { return '#'; }, /** * Basic getter * @param {String} property Property name * @return {Any} value of a property */ get: function(property) { return this[property]; }, /** * @private */ _setObject: function(obj) { for (var prop in obj) { this._set(prop, obj[prop]); } }, /** * Sets property to a given value. When changing position/dimension -related properties (left, top, scale, angle, etc.) `set` does not update position of object's borders/controls. If you need to update those, call `setCoords()`. * @param {String|Object} key Property name or object (if object, iterate over the object properties) * @param {Object|Function} value Property value (if function, the value is passed into it and its return value is used as a new one) * @return {fabric.Object} thisArg * @chainable */ set: function(key, value) { if (typeof key === 'object') { this._setObject(key); } else { if (typeof value === 'function' && key !== 'clipTo') { this._set(key, value(this.get(key))); } else { this._set(key, value); } } return this; }, /** * @private * @param {String} key * @param {Any} value * @return {fabric.Object} thisArg */ _set: function(key, value) { var shouldConstrainValue = (key === 'scaleX' || key === 'scaleY'); if (shouldConstrainValue) { value = this._constrainScale(value); } if (key === 'scaleX' && value < 0) { this.flipX = !this.flipX; value *= -1; } else if (key === 'scaleY' && value < 0) { this.flipY = !this.flipY; value *= -1; } else if (key === 'width' || key === 'height') { this.minScaleLimit = toFixed(Math.min(0.1, 1/Math.max(this.width, this.height)), 2); } else if (key === 'shadow' && value && !(value instanceof fabric.Shadow)) { value = new fabric.Shadow(value); } this[key] = value; return this; }, /** * Toggles specified property from `true` to `false` or from `false` to `true` * @param {String} property Property to toggle * @return {fabric.Object} thisArg * @chainable */ toggle: function(property) { var value = this.get(property); if (typeof value === 'boolean') { this.set(property, !value); } return this; }, /** * Sets sourcePath of an object * @param {String} value Value to set sourcePath to * @return {fabric.Object} thisArg * @chainable */ setSourcePath: function(value) { this.sourcePath = value; return this; }, /** * Retrieves viewportTransform from Object's canvas if possible * @method getViewportTransform * @memberOf fabric.Object.prototype * @return {Boolean} flipY value // TODO */ getViewportTransform: function() { if (this.canvas && this.canvas.viewportTransform) { return this.canvas.viewportTransform; } return [1, 0, 0, 1, 0, 0]; }, /** * Renders an object on a specified context * @param {CanvasRenderingContext2D} ctx Context to render on * @param {Boolean} [noTransform] When true, context is not transformed */ render: function(ctx, noTransform) { // do not render if width/height are zeros or object is not visible if (this.width === 0 || this.height === 0 || !this.visible) { return; } ctx.save(); //setup fill rule for current object this._setupCompositeOperation(ctx); if (!noTransform) { this.transform(ctx); } this._setStrokeStyles(ctx); this._setFillStyles(ctx); if (this.group && this.group.type === 'path-group') { ctx.translate(-this.group.width/2, -this.group.height/2); } if (this.transformMatrix) { ctx.transform.apply(ctx, this.transformMatrix); } this._setOpacity(ctx); this._setShadow(ctx); this.clipTo && fabric.util.clipContext(this, ctx); this._render(ctx, noTransform); this.clipTo && ctx.restore(); this._removeShadow(ctx); this._restoreCompositeOperation(ctx); ctx.restore(); }, /* @private * @param {CanvasRenderingContext2D} ctx Context to render on */ _setOpacity: function(ctx) { if (this.group) { this.group._setOpacity(ctx); } ctx.globalAlpha *= this.opacity; }, _setStrokeStyles: function(ctx) { if (this.stroke) { ctx.lineWidth = this.strokeWidth; ctx.lineCap = this.strokeLineCap; ctx.lineJoin = this.strokeLineJoin; ctx.miterLimit = this.strokeMiterLimit; ctx.strokeStyle = this.stroke.toLive ? this.stroke.toLive(ctx, this) : this.stroke; } }, _setFillStyles: function(ctx) { if (this.fill) { ctx.fillStyle = this.fill.toLive ? this.fill.toLive(ctx, this) : this.fill; } }, /** * Renders controls and borders for the object * @param {CanvasRenderingContext2D} ctx Context to render on * @param {Boolean} [noTransform] When true, context is not transformed */ _renderControls: function(ctx, noTransform) { var vpt = this.getViewportTransform(); ctx.save(); if (this.active && !noTransform) { var center; if (this.group) { center = fabric.util.transformPoint(this.group.getCenterPoint(), vpt); ctx.translate(center.x, center.y); ctx.rotate(degreesToRadians(this.group.angle)); } center = fabric.util.transformPoint(this.getCenterPoint(), vpt, null != this.group); if (this.group) { center.x *= this.group.scaleX; center.y *= this.group.scaleY; } ctx.translate(center.x, center.y); ctx.rotate(degreesToRadians(this.angle)); this.drawBorders(ctx); this.drawControls(ctx); } ctx.restore(); }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on */ _setShadow: function(ctx) { if (!this.shadow) { return; } ctx.shadowColor = this.shadow.color; ctx.shadowBlur = this.shadow.blur; ctx.shadowOffsetX = this.shadow.offsetX; ctx.shadowOffsetY = this.shadow.offsetY; }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on */ _removeShadow: function(ctx) { if (!this.shadow) { return; } ctx.shadowColor = ''; ctx.shadowBlur = ctx.shadowOffsetX = ctx.shadowOffsetY = 0; }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on */ _renderFill: function(ctx) { if (!this.fill) { return; } ctx.save(); if (this.fill.gradientTransform) { var g = this.fill.gradientTransform; ctx.transform.apply(ctx, g); } if (this.fill.toLive) { ctx.translate( -this.width / 2 + this.fill.offsetX || 0, -this.height / 2 + this.fill.offsetY || 0); } if (this.fillRule === 'evenodd') { ctx.fill('evenodd'); } else { ctx.fill(); } ctx.restore(); if (this.shadow && !this.shadow.affectStroke) { this._removeShadow(ctx); } }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on */ _renderStroke: function(ctx) { if (!this.stroke || this.strokeWidth === 0) { return; } ctx.save(); if (this.strokeDashArray) { // Spec requires the concatenation of two copies the dash list when the number of elements is odd if (1 & this.strokeDashArray.length) { this.strokeDashArray.push.apply(this.strokeDashArray, this.strokeDashArray); } if (supportsLineDash) { ctx.setLineDash(this.strokeDashArray); this._stroke && this._stroke(ctx); } else { this._renderDashedStroke && this._renderDashedStroke(ctx); } ctx.stroke(); } else { if (this.stroke.gradientTransform) { var g = this.stroke.gradientTransform; ctx.transform.apply(ctx, g); } this._stroke ? this._stroke(ctx) : ctx.stroke(); } this._removeShadow(ctx); ctx.restore(); }, /** * Clones an instance * @param {Function} callback Callback is invoked with a clone as a first argument * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output * @return {fabric.Object} clone of an instance */ clone: function(callback, propertiesToInclude) { if (this.constructor.fromObject) { return this.constructor.fromObject(this.toObject(propertiesToInclude), callback); } return new fabric.Object(this.toObject(propertiesToInclude)); }, /** * Creates an instance of fabric.Image out of an object * @param {Function} callback callback, invoked with an instance as a first argument * @return {fabric.Object} thisArg */ cloneAsImage: function(callback) { var dataUrl = this.toDataURL(); fabric.util.loadImage(dataUrl, function(img) { if (callback) { callback(new fabric.Image(img)); } }); return this; }, /** * Converts an object into a data-url-like string * @param {Object} options Options object * @param {String} [options.format=png] The format of the output image. Either "jpeg" or "png" * @param {Number} [options.quality=1] Quality level (0..1). Only used for jpeg. * @param {Number} [options.multiplier=1] Multiplier to scale by * @param {Number} [options.left] Cropping left offset. Introduced in v1.2.14 * @param {Number} [options.top] Cropping top offset. Introduced in v1.2.14 * @param {Number} [options.width] Cropping width. Introduced in v1.2.14 * @param {Number} [options.height] Cropping height. Introduced in v1.2.14 * @return {String} Returns a data: URL containing a representation of the object in the format specified by options.format */ toDataURL: function(options) { options || (options = { }); var el = fabric.util.createCanvasElement(), boundingRect = this.getBoundingRect(); el.width = boundingRect.width; el.height = boundingRect.height; fabric.util.wrapElement(el, 'div'); var canvas = new fabric.Canvas(el); // to avoid common confusion https://github.com/kangax/fabric.js/issues/806 if (options.format === 'jpg') { options.format = 'jpeg'; } if (options.format === 'jpeg') { canvas.backgroundColor = '#fff'; } var origParams = { active: this.get('active'), left: this.getLeft(), top: this.getTop() }; this.set('active', false); this.setPositionByOrigin(new fabric.Point(el.width / 2, el.height / 2), 'center', 'center'); var originalCanvas = this.canvas; canvas.add(this); var data = canvas.toDataURL(options); this.set(origParams).setCoords(); this.canvas = originalCanvas; canvas.dispose(); canvas = null; return data; }, /** * Returns true if specified type is identical to the type of an instance * @param {String} type Type to check against * @return {Boolean} */ isType: function(type) { return this.type === type; }, /** * Returns complexity of an instance * @return {Number} complexity of this instance */ complexity: function() { return 0; }, /** * Returns a JSON representation of an instance * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output * @return {Object} JSON */ toJSON: function(propertiesToInclude) { // delegate, not alias return this.toObject(propertiesToInclude); }, /** * Sets gradient (fill or stroke) of an object * Backwards incompatibility note: This method was named "setGradientFill" until v1.1.0 * @param {String} property Property name 'stroke' or 'fill' * @param {Object} [options] Options object * @param {String} [options.type] Type of gradient 'radial' or 'linear' * @param {Number} [options.x1=0] x-coordinate of start point * @param {Number} [options.y1=0] y-coordinate of start point * @param {Number} [options.x2=0] x-coordinate of end point * @param {Number} [options.y2=0] y-coordinate of end point * @param {Number} [options.r1=0] Radius of start point (only for radial gradients) * @param {Number} [options.r2=0] Radius of end point (only for radial gradients) * @param {Object} [options.colorStops] Color stops object eg. {0: 'ff0000', 1: '000000'} * @return {fabric.Object} thisArg * @chainable * @see {@link http://jsfiddle.net/fabricjs/58y8b/|jsFiddle demo} * @example Set linear gradient * object.setGradient('fill', { * type: 'linear', * x1: -object.width / 2, * y1: 0, * x2: object.width / 2, * y2: 0, * colorStops: { * 0: 'red', * 0.5: '#005555', * 1: 'rgba(0,0,255,0.5)' * } * }); * canvas.renderAll(); * @example Set radial gradient * object.setGradient('fill', { * type: 'radial', * x1: 0, * y1: 0, * x2: 0, * y2: 0, * r1: object.width / 2, * r2: 10, * colorStops: { * 0: 'red', * 0.5: '#005555', * 1: 'rgba(0,0,255,0.5)' * } * }); * canvas.renderAll(); */ setGradient: function(property, options) { options || (options = { }); var gradient = { colorStops: [] }; gradient.type = options.type || (options.r1 || options.r2 ? 'radial' : 'linear'); gradient.coords = { x1: options.x1, y1: options.y1, x2: options.x2, y2: options.y2 }; if (options.r1 || options.r2) { gradient.coords.r1 = options.r1; gradient.coords.r2 = options.r2; } for (var position in options.colorStops) { var color = new fabric.Color(options.colorStops[position]); gradient.colorStops.push({ offset: position, color: color.toRgb(), opacity: color.getAlpha() }); } return this.set(property, fabric.Gradient.forObject(this, gradient)); }, /** * Sets pattern fill of an object * @param {Object} options Options object * @param {(String|HTMLImageElement)} options.source Pattern source * @param {String} [options.repeat=repeat] Repeat property of a pattern (one of repeat, repeat-x, repeat-y or no-repeat) * @param {Number} [options.offsetX=0] Pattern horizontal offset from object's left/top corner * @param {Number} [options.offsetY=0] Pattern vertical offset from object's left/top corner * @return {fabric.Object} thisArg * @chainable * @see {@link http://jsfiddle.net/fabricjs/QT3pa/|jsFiddle demo} * @example Set pattern * fabric.util.loadImage('http://fabricjs.com/assets/escheresque_ste.png', function(img) { * object.setPatternFill({ * source: img, * repeat: 'repeat' * }); * canvas.renderAll(); * }); */ setPatternFill: function(options) { return this.set('fill', new fabric.Pattern(options)); }, /** * Sets {@link fabric.Object#shadow|shadow} of an object * @param {Object|String} [options] Options object or string (e.g. "2px 2px 10px rgba(0,0,0,0.2)") * @param {String} [options.color=rgb(0,0,0)] Shadow color * @param {Number} [options.blur=0] Shadow blur * @param {Number} [options.offsetX=0] Shadow horizontal offset * @param {Number} [options.offsetY=0] Shadow vertical offset * @return {fabric.Object} thisArg * @chainable * @see {@link http://jsfiddle.net/fabricjs/7gvJG/|jsFiddle demo} * @example Set shadow with string notation * object.setShadow('2px 2px 10px rgba(0,0,0,0.2)'); * canvas.renderAll(); * @example Set shadow with object notation * object.setShadow({ * color: 'red', * blur: 10, * offsetX: 20, * offsetY: 20 * }); * canvas.renderAll(); */ setShadow: function(options) { return this.set('shadow', options ? new fabric.Shadow(options) : null); }, /** * Sets "color" of an instance (alias of `set('fill', …)`) * @param {String} color Color value * @return {fabric.Object} thisArg * @chainable */ setColor: function(color) { this.set('fill', color); return this; }, /** * Sets "angle" of an instance * @param {Number} angle Angle value * @return {fabric.Object} thisArg * @chainable */ setAngle: function(angle) { var shouldCenterOrigin = (this.originX !== 'center' || this.originY !== 'center') && this.centeredRotation; if (shouldCenterOrigin) { this._setOriginToCenter(); } this.set('angle', angle); if (shouldCenterOrigin) { this._resetOrigin(); } return this; }, /** * Centers object horizontally on canvas to which it was added last. * You might need to call `setCoords` on an object after centering, to update controls area. * @return {fabric.Object} thisArg * @chainable */ centerH: function () { this.canvas.centerObjectH(this); return this; }, /** * Centers object vertically on canvas to which it was added last. * You might need to call `setCoords` on an object after centering, to update controls area. * @return {fabric.Object} thisArg * @chainable */ centerV: function () { this.canvas.centerObjectV(this); return this; }, /** * Centers object vertically and horizontally on canvas to which is was added last * You might need to call `setCoords` on an object after centering, to update controls area. * @return {fabric.Object} thisArg * @chainable */ center: function () { this.canvas.centerObject(this); return this; }, /** * Removes object from canvas to which it was added last * @return {fabric.Object} thisArg * @chainable */ remove: function() { this.canvas.remove(this); return this; }, /** * Returns coordinates of a pointer relative to an object * @param {Event} e Event to operate upon * @param {Object} [pointer] Pointer to operate upon (instead of event) * @return {Object} Coordinates of a pointer (x, y) */ getLocalPointer: function(e, pointer) { pointer = pointer || this.canvas.getPointer(e); var objectLeftTop = this.translateToOriginPoint(this.getCenterPoint(), 'left', 'top'); return { x: pointer.x - objectLeftTop.x, y: pointer.y - objectLeftTop.y }; }, /** * Sets canvas globalCompositeOperation for specific object * custom composition operation for the particular object can be specifed using globalCompositeOperation property * @param {CanvasRenderingContext2D} ctx Rendering canvas context */ _setupCompositeOperation: function (ctx) { if (this.globalCompositeOperation) { this._prevGlobalCompositeOperation = ctx.globalCompositeOperation; ctx.globalCompositeOperation = this.globalCompositeOperation; } }, /** * Restores previously saved canvas globalCompositeOperation after obeject rendering * @param {CanvasRenderingContext2D} ctx Rendering canvas context */ _restoreCompositeOperation: function (ctx) { if (this.globalCompositeOperation && this._prevGlobalCompositeOperation) { ctx.globalCompositeOperation = this._prevGlobalCompositeOperation; } } }); fabric.util.createAccessors(fabric.Object); /** * Alias for {@link fabric.Object.prototype.setAngle} * @alias rotate -> setAngle * @memberof fabric.Object */ fabric.Object.prototype.rotate = fabric.Object.prototype.setAngle; extend(fabric.Object.prototype, fabric.Observable); /** * Defines the number of fraction digits to use when serializing object values. * You can use it to increase/decrease precision of such values like left, top, scaleX, scaleY, etc. * @static * @memberof fabric.Object * @constant * @type Number */ fabric.Object.NUM_FRACTION_DIGITS = 2; /** * Unique id used internally when creating SVG elements * @static * @memberof fabric.Object * @type Number */ fabric.Object.__uid = 0; })(typeof exports !== 'undefined' ? exports : this); (function() { var degreesToRadians = fabric.util.degreesToRadians; fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { /** * Translates the coordinates from origin to center coordinates (based on the object's dimensions) * @param {fabric.Point} point The point which corresponds to the originX and originY params * @param {String} originX Horizontal origin: 'left', 'center' or 'right' * @param {String} originY Vertical origin: 'top', 'center' or 'bottom' * @return {fabric.Point} */ translateToCenterPoint: function(point, originX, originY) { var cx = point.x, cy = point.y, strokeWidth = this.stroke ? this.strokeWidth : 0; if (originX === 'left') { cx = point.x + (this.getWidth() + strokeWidth * this.scaleX) / 2; } else if (originX === 'right') { cx = point.x - (this.getWidth() + strokeWidth * this.scaleX) / 2; } if (originY === 'top') { cy = point.y + (this.getHeight() + strokeWidth * this.scaleY) / 2; } else if (originY === 'bottom') { cy = point.y - (this.getHeight() + strokeWidth * this.scaleY) / 2; } // Apply the reverse rotation to the point (it's already scaled properly) return fabric.util.rotatePoint(new fabric.Point(cx, cy), point, degreesToRadians(this.angle)); }, /** * Translates the coordinates from center to origin coordinates (based on the object's dimensions) * @param {fabric.Point} center The point which corresponds to center of the object * @param {String} originX Horizontal origin: 'left', 'center' or 'right' * @param {String} originY Vertical origin: 'top', 'center' or 'bottom' * @return {fabric.Point} */ translateToOriginPoint: function(center, originX, originY) { var x = center.x, y = center.y, strokeWidth = this.stroke ? this.strokeWidth : 0; // Get the point coordinates if (originX === 'left') { x = center.x - (this.getWidth() + strokeWidth * this.scaleX) / 2; } else if (originX === 'right') { x = center.x + (this.getWidth() + strokeWidth * this.scaleX) / 2; } if (originY === 'top') { y = center.y - (this.getHeight() + strokeWidth * this.scaleY) / 2; } else if (originY === 'bottom') { y = center.y + (this.getHeight() + strokeWidth * this.scaleY) / 2; } // Apply the rotation to the point (it's already scaled properly) return fabric.util.rotatePoint(new fabric.Point(x, y), center, degreesToRadians(this.angle)); }, /** * Returns the real center coordinates of the object * @return {fabric.Point} */ getCenterPoint: function() { var leftTop = new fabric.Point(this.left, this.top); return this.translateToCenterPoint(leftTop, this.originX, this.originY); }, /** * Returns the coordinates of the object based on center coordinates * @param {fabric.Point} point The point which corresponds to the originX and originY params * @return {fabric.Point} */ // getOriginPoint: function(center) { // return this.translateToOriginPoint(center, this.originX, this.originY); // }, /** * Returns the coordinates of the object as if it has a different origin * @param {String} originX Horizontal origin: 'left', 'center' or 'right' * @param {String} originY Vertical origin: 'top', 'center' or 'bottom' * @return {fabric.Point} */ getPointByOrigin: function(originX, originY) { var center = this.getCenterPoint(); return this.translateToOriginPoint(center, originX, originY); }, /** * Returns the point in local coordinates * @param {fabric.Point} point The point relative to the global coordinate system * @param {String} originX Horizontal origin: 'left', 'center' or 'right' * @param {String} originY Vertical origin: 'top', 'center' or 'bottom' * @return {fabric.Point} */ toLocalPoint: function(point, originX, originY) { var center = this.getCenterPoint(), strokeWidth = this.stroke ? this.strokeWidth : 0, x, y; if (originX && originY) { if (originX === 'left') { x = center.x - (this.getWidth() + strokeWidth * this.scaleX) / 2; } else if (originX === 'right') { x = center.x + (this.getWidth() + strokeWidth * this.scaleX) / 2; } else { x = center.x; } if (originY === 'top') { y = center.y - (this.getHeight() + strokeWidth * this.scaleY) / 2; } else if (originY === 'bottom') { y = center.y + (this.getHeight() + strokeWidth * this.scaleY) / 2; } else { y = center.y; } } else { x = this.left; y = this.top; } return fabric.util.rotatePoint(new fabric.Point(point.x, point.y), center, -degreesToRadians(this.angle)) .subtractEquals(new fabric.Point(x, y)); }, /** * Returns the point in global coordinates * @param {fabric.Point} The point relative to the local coordinate system * @return {fabric.Point} */ // toGlobalPoint: function(point) { // return fabric.util.rotatePoint(point, this.getCenterPoint(), degreesToRadians(this.angle)).addEquals(new fabric.Point(this.left, this.top)); // }, /** * Sets the position of the object taking into consideration the object's origin * @param {fabric.Point} pos The new position of the object * @param {String} originX Horizontal origin: 'left', 'center' or 'right' * @param {String} originY Vertical origin: 'top', 'center' or 'bottom' * @return {void} */ setPositionByOrigin: function(pos, originX, originY) { var center = this.translateToCenterPoint(pos, originX, originY), position = this.translateToOriginPoint(center, this.originX, this.originY); this.set('left', position.x); this.set('top', position.y); }, /** * @param {String} to One of 'left', 'center', 'right' */ adjustPosition: function(to) { var angle = degreesToRadians(this.angle), hypotHalf = this.getWidth() / 2, xHalf = Math.cos(angle) * hypotHalf, yHalf = Math.sin(angle) * hypotHalf, hypotFull = this.getWidth(), xFull = Math.cos(angle) * hypotFull, yFull = Math.sin(angle) * hypotFull; if (this.originX === 'center' && to === 'left' || this.originX === 'right' && to === 'center') { // move half left this.left -= xHalf; this.top -= yHalf; } else if (this.originX === 'left' && to === 'center' || this.originX === 'center' && to === 'right') { // move half right this.left += xHalf; this.top += yHalf; } else if (this.originX === 'left' && to === 'right') { // move full right this.left += xFull; this.top += yFull; } else if (this.originX === 'right' && to === 'left') { // move full left this.left -= xFull; this.top -= yFull; } this.setCoords(); this.originX = to; }, /** * Sets the origin/position of the object to it's center point * @private * @return {void} */ _setOriginToCenter: function() { this._originalOriginX = this.originX; this._originalOriginY = this.originY; var center = this.getCenterPoint(); this.originX = 'center'; this.originY = 'center'; this.left = center.x; this.top = center.y; }, /** * Resets the origin/position of the object to it's original origin * @private * @return {void} */ _resetOrigin: function() { var originPoint = this.translateToOriginPoint( this.getCenterPoint(), this._originalOriginX, this._originalOriginY); this.originX = this._originalOriginX; this.originY = this._originalOriginY; this.left = originPoint.x; this.top = originPoint.y; this._originalOriginX = null; this._originalOriginY = null; }, /** * @private */ _getLeftTopCoords: function() { return this.translateToOriginPoint(this.getCenterPoint(), 'left', 'center'); } }); })(); (function() { var degreesToRadians = fabric.util.degreesToRadians; fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { /** * Object containing coordinates of object's controls * @type Object * @default */ oCoords: null, /** * Checks if object intersects with an area formed by 2 points * @param {Object} pointTL top-left point of area * @param {Object} pointBR bottom-right point of area * @return {Boolean} true if object intersects with an area formed by 2 points */ intersectsWithRect: function(pointTL, pointBR) { var oCoords = this.oCoords, tl = new fabric.Point(oCoords.tl.x, oCoords.tl.y), tr = new fabric.Point(oCoords.tr.x, oCoords.tr.y), bl = new fabric.Point(oCoords.bl.x, oCoords.bl.y), br = new fabric.Point(oCoords.br.x, oCoords.br.y), intersection = fabric.Intersection.intersectPolygonRectangle( [tl, tr, br, bl], pointTL, pointBR ); return intersection.status === 'Intersection'; }, /** * Checks if object intersects with another object * @param {Object} other Object to test * @return {Boolean} true if object intersects with another object */ intersectsWithObject: function(other) { // extracts coords function getCoords(oCoords) { return { tl: new fabric.Point(oCoords.tl.x, oCoords.tl.y), tr: new fabric.Point(oCoords.tr.x, oCoords.tr.y), bl: new fabric.Point(oCoords.bl.x, oCoords.bl.y), br: new fabric.Point(oCoords.br.x, oCoords.br.y) }; } var thisCoords = getCoords(this.oCoords), otherCoords = getCoords(other.oCoords), intersection = fabric.Intersection.intersectPolygonPolygon( [thisCoords.tl, thisCoords.tr, thisCoords.br, thisCoords.bl], [otherCoords.tl, otherCoords.tr, otherCoords.br, otherCoords.bl] ); return intersection.status === 'Intersection'; }, /** * Checks if object is fully contained within area of another object * @param {Object} other Object to test * @return {Boolean} true if object is fully contained within area of another object */ isContainedWithinObject: function(other) { var boundingRect = other.getBoundingRect(), point1 = new fabric.Point(boundingRect.left, boundingRect.top), point2 = new fabric.Point(boundingRect.left + boundingRect.width, boundingRect.top + boundingRect.height); return this.isContainedWithinRect(point1, point2); }, /** * Checks if object is fully contained within area formed by 2 points * @param {Object} pointTL top-left point of area * @param {Object} pointBR bottom-right point of area * @return {Boolean} true if object is fully contained within area formed by 2 points */ isContainedWithinRect: function(pointTL, pointBR) { var boundingRect = this.getBoundingRect(); return ( boundingRect.left >= pointTL.x && boundingRect.left + boundingRect.width <= pointBR.x && boundingRect.top >= pointTL.y && boundingRect.top + boundingRect.height <= pointBR.y ); }, /** * Checks if point is inside the object * @param {fabric.Point} point Point to check against * @return {Boolean} true if point is inside the object */ containsPoint: function(point) { var lines = this._getImageLines(this.oCoords), xPoints = this._findCrossPoints(point, lines); // if xPoints is odd then point is inside the object return (xPoints !== 0 && xPoints % 2 === 1); }, /** * Method that returns an object with the object edges in it, given the coordinates of the corners * @private * @param {Object} oCoords Coordinates of the object corners */ _getImageLines: function(oCoords) { return { topline: { o: oCoords.tl, d: oCoords.tr }, rightline: { o: oCoords.tr, d: oCoords.br }, bottomline: { o: oCoords.br, d: oCoords.bl }, leftline: { o: oCoords.bl, d: oCoords.tl } }; }, /** * Helper method to determine how many cross points are between the 4 object edges * and the horizontal line determined by a point on canvas * @private * @param {fabric.Point} point Point to check * @param {Object} oCoords Coordinates of the object being evaluated */ _findCrossPoints: function(point, oCoords) { var b1, b2, a1, a2, xi, yi, xcount = 0, iLine; for (var lineKey in oCoords) { iLine = oCoords[lineKey]; // optimisation 1: line below point. no cross if ((iLine.o.y < point.y) && (iLine.d.y < point.y)) { continue; } // optimisation 2: line above point. no cross if ((iLine.o.y >= point.y) && (iLine.d.y >= point.y)) { continue; } // optimisation 3: vertical line case if ((iLine.o.x === iLine.d.x) && (iLine.o.x >= point.x)) { xi = iLine.o.x; yi = point.y; } // calculate the intersection point else { b1 = 0; b2 = (iLine.d.y - iLine.o.y) / (iLine.d.x - iLine.o.x); a1 = point.y - b1 * point.x; a2 = iLine.o.y - b2 * iLine.o.x; xi = - (a1 - a2) / (b1 - b2); yi = a1 + b1 * xi; } // dont count xi < point.x cases if (xi >= point.x) { xcount += 1; } // optimisation 4: specific for square images if (xcount === 2) { break; } } return xcount; }, /** * Returns width of an object's bounding rectangle * @deprecated since 1.0.4 * @return {Number} width value */ getBoundingRectWidth: function() { return this.getBoundingRect().width; }, /** * Returns height of an object's bounding rectangle * @deprecated since 1.0.4 * @return {Number} height value */ getBoundingRectHeight: function() { return this.getBoundingRect().height; }, /** * Returns coordinates of object's bounding rectangle (left, top, width, height) * @return {Object} Object with left, top, width, height properties */ getBoundingRect: function() { this.oCoords || this.setCoords(); var xCoords = [this.oCoords.tl.x, this.oCoords.tr.x, this.oCoords.br.x, this.oCoords.bl.x], minX = fabric.util.array.min(xCoords), maxX = fabric.util.array.max(xCoords), width = Math.abs(minX - maxX), yCoords = [this.oCoords.tl.y, this.oCoords.tr.y, this.oCoords.br.y, this.oCoords.bl.y], minY = fabric.util.array.min(yCoords), maxY = fabric.util.array.max(yCoords), height = Math.abs(minY - maxY); return { left: minX, top: minY, width: width, height: height }; }, /** * Returns width of an object * @return {Number} width value */ getWidth: function() { return this.width * this.scaleX; }, /** * Returns height of an object * @return {Number} height value */ getHeight: function() { return this.height * this.scaleY; }, /** * Makes sure the scale is valid and modifies it if necessary * @private * @param {Number} value * @return {Number} */ _constrainScale: function(value) { if (Math.abs(value) < this.minScaleLimit) { if (value < 0) { return -this.minScaleLimit; } else { return this.minScaleLimit; } } return value; }, /** * Scales an object (equally by x and y) * @param {Number} value Scale factor * @return {fabric.Object} thisArg * @chainable */ scale: function(value) { value = this._constrainScale(value); if (value < 0) { this.flipX = !this.flipX; this.flipY = !this.flipY; value *= -1; } this.scaleX = value; this.scaleY = value; this.setCoords(); return this; }, /** * Scales an object to a given width, with respect to bounding box (scaling by x/y equally) * @param {Number} value New width value * @return {fabric.Object} thisArg * @chainable */ scaleToWidth: function(value) { // adjust to bounding rect factor so that rotated shapes would fit as well var boundingRectFactor = this.getBoundingRectWidth() / this.getWidth(); return this.scale(value / this.width / boundingRectFactor); }, /** * Scales an object to a given height, with respect to bounding box (scaling by x/y equally) * @param {Number} value New height value * @return {fabric.Object} thisArg * @chainable */ scaleToHeight: function(value) { // adjust to bounding rect factor so that rotated shapes would fit as well var boundingRectFactor = this.getBoundingRectHeight() / this.getHeight(); return this.scale(value / this.height / boundingRectFactor); }, /** * Sets corner position coordinates based on current angle, width and height * @return {fabric.Object} thisArg * @chainable */ setCoords: function() { var strokeWidth = this.strokeWidth > 1 ? this.strokeWidth : 0, theta = degreesToRadians(this.angle), vpt = this.getViewportTransform(), f = function (p) { return fabric.util.transformPoint(p, vpt); }, w = this.width, h = this.height, capped = this.strokeLineCap === 'round' || this.strokeLineCap === 'square', vLine = this.type === 'line' && this.width === 1, hLine = this.type === 'line' && this.height === 1, strokeW = (capped && hLine) || this.type !== 'line', strokeH = (capped && vLine) || this.type !== 'line'; if (vLine) { w = strokeWidth; } else if (hLine) { h = strokeWidth; } if (strokeW) { w += strokeWidth; } if (strokeH) { h += strokeWidth; } this.currentWidth = w * this.scaleX; this.currentHeight = h * this.scaleY; // If width is negative, make postive. Fixes path selection issue if (this.currentWidth < 0) { this.currentWidth = Math.abs(this.currentWidth); } var _hypotenuse = Math.sqrt( Math.pow(this.currentWidth / 2, 2) + Math.pow(this.currentHeight / 2, 2)), _angle = Math.atan( isFinite(this.currentHeight / this.currentWidth) ? this.currentHeight / this.currentWidth : 0), // offset added for rotate and scale actions offsetX = Math.cos(_angle + theta) * _hypotenuse, offsetY = Math.sin(_angle + theta) * _hypotenuse, sinTh = Math.sin(theta), cosTh = Math.cos(theta), coords = this.getCenterPoint(), wh = new fabric.Point(this.currentWidth, this.currentHeight), _tl = new fabric.Point(coords.x - offsetX, coords.y - offsetY), _tr = new fabric.Point(_tl.x + (wh.x * cosTh), _tl.y + (wh.x * sinTh)), _bl = new fabric.Point(_tl.x - (wh.y * sinTh), _tl.y + (wh.y * cosTh)), _mt = new fabric.Point(_tl.x + (wh.x/2 * cosTh), _tl.y + (wh.x/2 * sinTh)), tl = f(_tl), tr = f(_tr), br = f(new fabric.Point(_tr.x - (wh.y * sinTh), _tr.y + (wh.y * cosTh))), bl = f(_bl), ml = f(new fabric.Point(_tl.x - (wh.y/2 * sinTh), _tl.y + (wh.y/2 * cosTh))), mt = f(_mt), mr = f(new fabric.Point(_tr.x - (wh.y/2 * sinTh), _tr.y + (wh.y/2 * cosTh))), mb = f(new fabric.Point(_bl.x + (wh.x/2 * cosTh), _bl.y + (wh.x/2 * sinTh))), mtr = f(new fabric.Point(_mt.x, _mt.y)), // padding padX = Math.cos(_angle + theta) * this.padding * Math.sqrt(2), padY = Math.sin(_angle + theta) * this.padding * Math.sqrt(2); tl = tl.add(new fabric.Point(-padX, -padY)); tr = tr.add(new fabric.Point(padY, -padX)); br = br.add(new fabric.Point(padX, padY)); bl = bl.add(new fabric.Point(-padY, padX)); ml = ml.add(new fabric.Point((-padX - padY) / 2, (-padY + padX) / 2)); mt = mt.add(new fabric.Point((padY - padX) / 2, -(padY + padX) / 2)); mr = mr.add(new fabric.Point((padY + padX) / 2, (padY - padX) / 2)); mb = mb.add(new fabric.Point((padX - padY) / 2, (padX + padY) / 2)); mtr = mtr.add(new fabric.Point((padY - padX) / 2, -(padY + padX) / 2)); // debugging // setTimeout(function() { // canvas.contextTop.fillStyle = 'green'; // canvas.contextTop.fillRect(mb.x, mb.y, 3, 3); // canvas.contextTop.fillRect(bl.x, bl.y, 3, 3); // canvas.contextTop.fillRect(br.x, br.y, 3, 3); // canvas.contextTop.fillRect(tl.x, tl.y, 3, 3); // canvas.contextTop.fillRect(tr.x, tr.y, 3, 3); // canvas.contextTop.fillRect(ml.x, ml.y, 3, 3); // canvas.contextTop.fillRect(mr.x, mr.y, 3, 3); // canvas.contextTop.fillRect(mt.x, mt.y, 3, 3); // }, 50); this.oCoords = { // corners tl: tl, tr: tr, br: br, bl: bl, // middle ml: ml, mt: mt, mr: mr, mb: mb, // rotating point mtr: mtr }; // set coordinates of the draggable boxes in the corners used to scale/rotate the image this._setCornerCoords && this._setCornerCoords(); return this; } }); })(); fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { /** * Moves an object to the bottom of the stack of drawn objects * @return {fabric.Object} thisArg * @chainable */ sendToBack: function() { if (this.group) { fabric.StaticCanvas.prototype.sendToBack.call(this.group, this); } else { this.canvas.sendToBack(this); } return this; }, /** * Moves an object to the top of the stack of drawn objects * @return {fabric.Object} thisArg * @chainable */ bringToFront: function() { if (this.group) { fabric.StaticCanvas.prototype.bringToFront.call(this.group, this); } else { this.canvas.bringToFront(this); } return this; }, /** * Moves an object down in stack of drawn objects * @param {Boolean} [intersecting] If `true`, send object behind next lower intersecting object * @return {fabric.Object} thisArg * @chainable */ sendBackwards: function(intersecting) { if (this.group) { fabric.StaticCanvas.prototype.sendBackwards.call(this.group, this, intersecting); } else { this.canvas.sendBackwards(this, intersecting); } return this; }, /** * Moves an object up in stack of drawn objects * @param {Boolean} [intersecting] If `true`, send object in front of next upper intersecting object * @return {fabric.Object} thisArg * @chainable */ bringForward: function(intersecting) { if (this.group) { fabric.StaticCanvas.prototype.bringForward.call(this.group, this, intersecting); } else { this.canvas.bringForward(this, intersecting); } return this; }, /** * Moves an object to specified level in stack of drawn objects * @param {Number} index New position of object * @return {fabric.Object} thisArg * @chainable */ moveTo: function(index) { if (this.group) { fabric.StaticCanvas.prototype.moveTo.call(this.group, this, index); } else { this.canvas.moveTo(this, index); } return this; } }); /* _TO_SVG_START_ */ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { /** * Returns styles-string for svg-export * @return {String} */ getSvgStyles: function() { var fill = this.fill ? (this.fill.toLive ? 'url(#SVGID_' + this.fill.id + ')' : this.fill) : 'none', fillRule = this.fillRule, stroke = this.stroke ? (this.stroke.toLive ? 'url(#SVGID_' + this.stroke.id + ')' : this.stroke) : 'none', strokeWidth = this.strokeWidth ? this.strokeWidth : '0', strokeDashArray = this.strokeDashArray ? this.strokeDashArray.join(' ') : '', strokeLineCap = this.strokeLineCap ? this.strokeLineCap : 'butt', strokeLineJoin = this.strokeLineJoin ? this.strokeLineJoin : 'miter', strokeMiterLimit = this.strokeMiterLimit ? this.strokeMiterLimit : '4', opacity = typeof this.opacity !== 'undefined' ? this.opacity : '1', visibility = this.visible ? '' : ' visibility: hidden;', filter = this.shadow && this.type !== 'text' ? 'filter: url(#SVGID_' + this.shadow.id + ');' : ''; return [ 'stroke: ', stroke, '; ', 'stroke-width: ', strokeWidth, '; ', 'stroke-dasharray: ', strokeDashArray, '; ', 'stroke-linecap: ', strokeLineCap, '; ', 'stroke-linejoin: ', strokeLineJoin, '; ', 'stroke-miterlimit: ', strokeMiterLimit, '; ', 'fill: ', fill, '; ', 'fill-rule: ', fillRule, '; ', 'opacity: ', opacity, ';', filter, visibility ].join(''); }, /** * Returns transform-string for svg-export * @return {String} */ getSvgTransform: function() { if (this.group && this.group.type === 'path-group') { return ''; } var toFixed = fabric.util.toFixed, angle = this.getAngle(), vpt = !this.canvas || this.canvas.svgViewportTransformation ? this.getViewportTransform() : [1, 0, 0, 1, 0, 0], center = fabric.util.transformPoint(this.getCenterPoint(), vpt), NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS, translatePart = this.type === 'path-group' ? '' : 'translate(' + toFixed(center.x, NUM_FRACTION_DIGITS) + ' ' + toFixed(center.y, NUM_FRACTION_DIGITS) + ')', anglePart = angle !== 0 ? (' rotate(' + toFixed(angle, NUM_FRACTION_DIGITS) + ')') : '', scalePart = (this.scaleX === 1 && this.scaleY === 1 && vpt[0] === 1 && vpt[3] === 1) ? '' : (' scale(' + toFixed(this.scaleX * vpt[0], NUM_FRACTION_DIGITS) + ' ' + toFixed(this.scaleY * vpt[3], NUM_FRACTION_DIGITS) + ')'), addTranslateX = this.type === 'path-group' ? this.width * vpt[0] : 0, flipXPart = this.flipX ? ' matrix(-1 0 0 1 ' + addTranslateX + ' 0) ' : '', addTranslateY = this.type === 'path-group' ? this.height * vpt[3] : 0, flipYPart = this.flipY ? ' matrix(1 0 0 -1 0 ' + addTranslateY + ')' : ''; return [ translatePart, anglePart, scalePart, flipXPart, flipYPart ].join(''); }, /** * Returns transform-string for svg-export from the transform matrix of single elements * @return {String} */ getSvgTransformMatrix: function() { return this.transformMatrix ? ' matrix(' + this.transformMatrix.join(' ') + ')' : ''; }, /** * @private */ _createBaseSVGMarkup: function() { var markup = [ ]; if (this.fill && this.fill.toLive) { markup.push(this.fill.toSVG(this, false)); } if (this.stroke && this.stroke.toLive) { markup.push(this.stroke.toSVG(this, false)); } if (this.shadow) { markup.push(this.shadow.toSVG(this)); } return markup; } }); /* _TO_SVG_END_ */ /* Depends on `stateProperties` */ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { /** * Returns true if object state (one of its state properties) was changed * @return {Boolean} true if instance' state has changed since `{@link fabric.Object#saveState}` was called */ hasStateChanged: function() { return this.stateProperties.some(function(prop) { return this.get(prop) !== this.originalState[prop]; }, this); }, /** * Saves state of an object * @param {Object} [options] Object with additional `stateProperties` array to include when saving state * @return {fabric.Object} thisArg */ saveState: function(options) { this.stateProperties.forEach(function(prop) { this.originalState[prop] = this.get(prop); }, this); if (options && options.stateProperties) { options.stateProperties.forEach(function(prop) { this.originalState[prop] = this.get(prop); }, this); } return this; }, /** * Setups state of an object * @return {fabric.Object} thisArg */ setupState: function() { this.originalState = { }; this.saveState(); return this; } }); (function() { var degreesToRadians = fabric.util.degreesToRadians, //jscs:disable requireCamelCaseOrUpperCaseIdentifiers isVML = function() { return typeof G_vmlCanvasManager !== 'undefined'; }; //jscs:enable requireCamelCaseOrUpperCaseIdentifiers fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { /** * The object interactivity controls. * @private */ _controlsVisibility: null, /** * Determines which corner has been clicked * @private * @param {Object} pointer The pointer indicating the mouse position * @return {String|Boolean} corner code (tl, tr, bl, br, etc.), or false if nothing is found */ _findTargetCorner: function(pointer) { if (!this.hasControls || !this.active) { return false; } var ex = pointer.x, ey = pointer.y, xPoints, lines; for (var i in this.oCoords) { if (!this.isControlVisible(i)) { continue; } if (i === 'mtr' && !this.hasRotatingPoint) { continue; } if (this.get('lockUniScaling') && (i === 'mt' || i === 'mr' || i === 'mb' || i === 'ml')) { continue; } lines = this._getImageLines(this.oCoords[i].corner); // debugging // canvas.contextTop.fillRect(lines.bottomline.d.x, lines.bottomline.d.y, 2, 2); // canvas.contextTop.fillRect(lines.bottomline.o.x, lines.bottomline.o.y, 2, 2); // canvas.contextTop.fillRect(lines.leftline.d.x, lines.leftline.d.y, 2, 2); // canvas.contextTop.fillRect(lines.leftline.o.x, lines.leftline.o.y, 2, 2); // canvas.contextTop.fillRect(lines.topline.d.x, lines.topline.d.y, 2, 2); // canvas.contextTop.fillRect(lines.topline.o.x, lines.topline.o.y, 2, 2); // canvas.contextTop.fillRect(lines.rightline.d.x, lines.rightline.d.y, 2, 2); // canvas.contextTop.fillRect(lines.rightline.o.x, lines.rightline.o.y, 2, 2); xPoints = this._findCrossPoints({ x: ex, y: ey }, lines); if (xPoints !== 0 && xPoints % 2 === 1) { this.__corner = i; return i; } } return false; }, /** * Sets the coordinates of the draggable boxes in the corners of * the image used to scale/rotate it. * @private */ _setCornerCoords: function() { var coords = this.oCoords, theta = degreesToRadians(this.angle), newTheta = degreesToRadians(45 - this.angle), cornerHypotenuse = Math.sqrt(2 * Math.pow(this.cornerSize, 2)) / 2, cosHalfOffset = cornerHypotenuse * Math.cos(newTheta), sinHalfOffset = cornerHypotenuse * Math.sin(newTheta), sinTh = Math.sin(theta), cosTh = Math.cos(theta); coords.tl.corner = { tl: { x: coords.tl.x - sinHalfOffset, y: coords.tl.y - cosHalfOffset }, tr: { x: coords.tl.x + cosHalfOffset, y: coords.tl.y - sinHalfOffset }, bl: { x: coords.tl.x - cosHalfOffset, y: coords.tl.y + sinHalfOffset }, br: { x: coords.tl.x + sinHalfOffset, y: coords.tl.y + cosHalfOffset } }; coords.tr.corner = { tl: { x: coords.tr.x - sinHalfOffset, y: coords.tr.y - cosHalfOffset }, tr: { x: coords.tr.x + cosHalfOffset, y: coords.tr.y - sinHalfOffset }, br: { x: coords.tr.x + sinHalfOffset, y: coords.tr.y + cosHalfOffset }, bl: { x: coords.tr.x - cosHalfOffset, y: coords.tr.y + sinHalfOffset } }; coords.bl.corner = { tl: { x: coords.bl.x - sinHalfOffset, y: coords.bl.y - cosHalfOffset }, bl: { x: coords.bl.x - cosHalfOffset, y: coords.bl.y + sinHalfOffset }, br: { x: coords.bl.x + sinHalfOffset, y: coords.bl.y + cosHalfOffset }, tr: { x: coords.bl.x + cosHalfOffset, y: coords.bl.y - sinHalfOffset } }; coords.br.corner = { tr: { x: coords.br.x + cosHalfOffset, y: coords.br.y - sinHalfOffset }, bl: { x: coords.br.x - cosHalfOffset, y: coords.br.y + sinHalfOffset }, br: { x: coords.br.x + sinHalfOffset, y: coords.br.y + cosHalfOffset }, tl: { x: coords.br.x - sinHalfOffset, y: coords.br.y - cosHalfOffset } }; coords.ml.corner = { tl: { x: coords.ml.x - sinHalfOffset, y: coords.ml.y - cosHalfOffset }, tr: { x: coords.ml.x + cosHalfOffset, y: coords.ml.y - sinHalfOffset }, bl: { x: coords.ml.x - cosHalfOffset, y: coords.ml.y + sinHalfOffset }, br: { x: coords.ml.x + sinHalfOffset, y: coords.ml.y + cosHalfOffset } }; coords.mt.corner = { tl: { x: coords.mt.x - sinHalfOffset, y: coords.mt.y - cosHalfOffset }, tr: { x: coords.mt.x + cosHalfOffset, y: coords.mt.y - sinHalfOffset }, bl: { x: coords.mt.x - cosHalfOffset, y: coords.mt.y + sinHalfOffset }, br: { x: coords.mt.x + sinHalfOffset, y: coords.mt.y + cosHalfOffset } }; coords.mr.corner = { tl: { x: coords.mr.x - sinHalfOffset, y: coords.mr.y - cosHalfOffset }, tr: { x: coords.mr.x + cosHalfOffset, y: coords.mr.y - sinHalfOffset }, bl: { x: coords.mr.x - cosHalfOffset, y: coords.mr.y + sinHalfOffset }, br: { x: coords.mr.x + sinHalfOffset, y: coords.mr.y + cosHalfOffset } }; coords.mb.corner = { tl: { x: coords.mb.x - sinHalfOffset, y: coords.mb.y - cosHalfOffset }, tr: { x: coords.mb.x + cosHalfOffset, y: coords.mb.y - sinHalfOffset }, bl: { x: coords.mb.x - cosHalfOffset, y: coords.mb.y + sinHalfOffset }, br: { x: coords.mb.x + sinHalfOffset, y: coords.mb.y + cosHalfOffset } }; coords.mtr.corner = { tl: { x: coords.mtr.x - sinHalfOffset + (sinTh * this.rotatingPointOffset), y: coords.mtr.y - cosHalfOffset - (cosTh * this.rotatingPointOffset) }, tr: { x: coords.mtr.x + cosHalfOffset + (sinTh * this.rotatingPointOffset), y: coords.mtr.y - sinHalfOffset - (cosTh * this.rotatingPointOffset) }, bl: { x: coords.mtr.x - cosHalfOffset + (sinTh * this.rotatingPointOffset), y: coords.mtr.y + sinHalfOffset - (cosTh * this.rotatingPointOffset) }, br: { x: coords.mtr.x + sinHalfOffset + (sinTh * this.rotatingPointOffset), y: coords.mtr.y + cosHalfOffset - (cosTh * this.rotatingPointOffset) } }; }, /** * Draws borders of an object's bounding box. * Requires public properties: width, height * Requires public options: padding, borderColor * @param {CanvasRenderingContext2D} ctx Context to draw on * @return {fabric.Object} thisArg * @chainable */ drawBorders: function(ctx) { if (!this.hasBorders) { return this; } var padding = this.padding, padding2 = padding * 2, vpt = this.getViewportTransform(); ctx.save(); ctx.globalAlpha = this.isMoving ? this.borderOpacityWhenMoving : 1; ctx.strokeStyle = this.borderColor; var scaleX = 1 / this._constrainScale(this.scaleX), scaleY = 1 / this._constrainScale(this.scaleY); ctx.lineWidth = 1 / this.borderScaleFactor; var w = this.getWidth(), h = this.getHeight(), strokeWidth = this.strokeWidth > 1 ? this.strokeWidth : 0, capped = this.strokeLineCap === 'round' || this.strokeLineCap === 'square', vLine = this.type === 'line' && this.width === 1, hLine = this.type === 'line' && this.height === 1, strokeW = (capped && hLine) || this.type !== 'line', strokeH = (capped && vLine) || this.type !== 'line'; if (vLine) { w = strokeWidth / scaleX; } else if (hLine) { h = strokeWidth / scaleY; } if (strokeW) { w += strokeWidth / scaleX; } if (strokeH) { h += strokeWidth / scaleY; } var wh = fabric.util.transformPoint(new fabric.Point(w, h), vpt, true), width = wh.x, height = wh.y; if (this.group) { width = width * this.group.scaleX; height = height * this.group.scaleY; } ctx.strokeRect( ~~(-(width / 2) - padding) - 0.5, // offset needed to make lines look sharper ~~(-(height / 2) - padding) - 0.5, ~~(width + padding2) + 1, // double offset needed to make lines look sharper ~~(height + padding2) + 1 ); if (this.hasRotatingPoint && this.isControlVisible('mtr') && !this.get('lockRotation') && this.hasControls) { var rotateHeight = ( -height - (padding * 2)) / 2; ctx.beginPath(); ctx.moveTo(0, rotateHeight); ctx.lineTo(0, rotateHeight - this.rotatingPointOffset); ctx.closePath(); ctx.stroke(); } ctx.restore(); return this; }, /** * Draws corners of an object's bounding box. * Requires public properties: width, height * Requires public options: cornerSize, padding * @param {CanvasRenderingContext2D} ctx Context to draw on * @return {fabric.Object} thisArg * @chainable */ drawControls: function(ctx) { if (!this.hasControls) { return this; } var size = this.cornerSize, size2 = size / 2, vpt = this.getViewportTransform(), strokeWidth = this.strokeWidth > 1 ? this.strokeWidth : 0, w = this.width, h = this.height, capped = this.strokeLineCap === 'round' || this.strokeLineCap === 'square', vLine = this.type === 'line' && this.width === 1, hLine = this.type === 'line' && this.height === 1, strokeW = (capped && hLine) || this.type !== 'line', strokeH = (capped && vLine) || this.type !== 'line'; if (vLine) { w = strokeWidth; } else if (hLine) { h = strokeWidth; } if (strokeW) { w += strokeWidth; } if (strokeH) { h += strokeWidth; } w *= this.scaleX; h *= this.scaleY; var wh = fabric.util.transformPoint(new fabric.Point(w, h), vpt, true), width = wh.x, height = wh.y, left = -(width / 2), top = -(height / 2), padding = this.padding, scaleOffset = size2, scaleOffsetSize = size2 - size, methodName = this.transparentCorners ? 'strokeRect' : 'fillRect'; ctx.save(); ctx.lineWidth = 1; ctx.globalAlpha = this.isMoving ? this.borderOpacityWhenMoving : 1; ctx.strokeStyle = ctx.fillStyle = this.cornerColor; // top-left this._drawControl('tl', ctx, methodName, left - scaleOffset - padding, top - scaleOffset - padding); // top-right this._drawControl('tr', ctx, methodName, left + width - scaleOffset + padding, top - scaleOffset - padding); // bottom-left this._drawControl('bl', ctx, methodName, left - scaleOffset - padding, top + height + scaleOffsetSize + padding); // bottom-right this._drawControl('br', ctx, methodName, left + width + scaleOffsetSize + padding, top + height + scaleOffsetSize + padding); if (!this.get('lockUniScaling')) { // middle-top this._drawControl('mt', ctx, methodName, left + width/2 - scaleOffset, top - scaleOffset - padding); // middle-bottom this._drawControl('mb', ctx, methodName, left + width/2 - scaleOffset, top + height + scaleOffsetSize + padding); // middle-right this._drawControl('mr', ctx, methodName, left + width + scaleOffsetSize + padding, top + height/2 - scaleOffset); // middle-left this._drawControl('ml', ctx, methodName, left - scaleOffset - padding, top + height/2 - scaleOffset); } // middle-top-rotate if (this.hasRotatingPoint) { this._drawControl('mtr', ctx, methodName, left + width/2 - scaleOffset, top - this.rotatingPointOffset - this.cornerSize/2 - padding); } ctx.restore(); return this; }, /** * @private */ _drawControl: function(control, ctx, methodName, left, top) { var size = this.cornerSize; if (this.isControlVisible(control)) { isVML() || this.transparentCorners || ctx.clearRect(left, top, size, size); ctx[methodName](left, top, size, size); } }, /** * Returns true if the specified control is visible, false otherwise. * @param {String} controlName The name of the control. Possible values are 'tl', 'tr', 'br', 'bl', 'ml', 'mt', 'mr', 'mb', 'mtr'. * @returns {Boolean} true if the specified control is visible, false otherwise */ isControlVisible: function(controlName) { return this._getControlsVisibility()[controlName]; }, /** * Sets the visibility of the specified control. * @param {String} controlName The name of the control. Possible values are 'tl', 'tr', 'br', 'bl', 'ml', 'mt', 'mr', 'mb', 'mtr'. * @param {Boolean} visible true to set the specified control visible, false otherwise * @return {fabric.Object} thisArg * @chainable */ setControlVisible: function(controlName, visible) { this._getControlsVisibility()[controlName] = visible; return this; }, /** * Sets the visibility state of object controls. * @param {Object} [options] Options object * @param {Boolean} [options.bl] true to enable the bottom-left control, false to disable it * @param {Boolean} [options.br] true to enable the bottom-right control, false to disable it * @param {Boolean} [options.mb] true to enable the middle-bottom control, false to disable it * @param {Boolean} [options.ml] true to enable the middle-left control, false to disable it * @param {Boolean} [options.mr] true to enable the middle-right control, false to disable it * @param {Boolean} [options.mt] true to enable the middle-top control, false to disable it * @param {Boolean} [options.tl] true to enable the top-left control, false to disable it * @param {Boolean} [options.tr] true to enable the top-right control, false to disable it * @param {Boolean} [options.mtr] true to enable the middle-top-rotate control, false to disable it * @return {fabric.Object} thisArg * @chainable */ setControlsVisibility: function(options) { options || (options = { }); for (var p in options) { this.setControlVisible(p, options[p]); } return this; }, /** * Returns the instance of the control visibility set for this object. * @private * @returns {Object} */ _getControlsVisibility: function() { if (!this._controlsVisibility) { this._controlsVisibility = { tl: true, tr: true, br: true, bl: true, ml: true, mt: true, mr: true, mb: true, mtr: true }; } return this._controlsVisibility; } }); })(); fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.StaticCanvas.prototype */ { /** * Animation duration (in ms) for fx* methods * @type Number * @default */ FX_DURATION: 500, /** * Centers object horizontally with animation. * @param {fabric.Object} object Object to center * @param {Object} [callbacks] Callbacks object with optional "onComplete" and/or "onChange" properties * @param {Function} [callbacks.onComplete] Invoked on completion * @param {Function} [callbacks.onChange] Invoked on every step of animation * @return {fabric.Canvas} thisArg * @chainable */ fxCenterObjectH: function (object, callbacks) { callbacks = callbacks || { }; var empty = function() { }, onComplete = callbacks.onComplete || empty, onChange = callbacks.onChange || empty, _this = this; fabric.util.animate({ startValue: object.get('left'), endValue: this.getCenter().left, duration: this.FX_DURATION, onChange: function(value) { object.set('left', value); _this.renderAll(); onChange(); }, onComplete: function() { object.setCoords(); onComplete(); } }); return this; }, /** * Centers object vertically with animation. * @param {fabric.Object} object Object to center * @param {Object} [callbacks] Callbacks object with optional "onComplete" and/or "onChange" properties * @param {Function} [callbacks.onComplete] Invoked on completion * @param {Function} [callbacks.onChange] Invoked on every step of animation * @return {fabric.Canvas} thisArg * @chainable */ fxCenterObjectV: function (object, callbacks) { callbacks = callbacks || { }; var empty = function() { }, onComplete = callbacks.onComplete || empty, onChange = callbacks.onChange || empty, _this = this; fabric.util.animate({ startValue: object.get('top'), endValue: this.getCenter().top, duration: this.FX_DURATION, onChange: function(value) { object.set('top', value); _this.renderAll(); onChange(); }, onComplete: function() { object.setCoords(); onComplete(); } }); return this; }, /** * Same as `fabric.Canvas#remove` but animated * @param {fabric.Object} object Object to remove * @param {Object} [callbacks] Callbacks object with optional "onComplete" and/or "onChange" properties * @param {Function} [callbacks.onComplete] Invoked on completion * @param {Function} [callbacks.onChange] Invoked on every step of animation * @return {fabric.Canvas} thisArg * @chainable */ fxRemove: function (object, callbacks) { callbacks = callbacks || { }; var empty = function() { }, onComplete = callbacks.onComplete || empty, onChange = callbacks.onChange || empty, _this = this; fabric.util.animate({ startValue: object.get('opacity'), endValue: 0, duration: this.FX_DURATION, onStart: function() { object.set('active', false); }, onChange: function(value) { object.set('opacity', value); _this.renderAll(); onChange(); }, onComplete: function () { _this.remove(object); onComplete(); } }); return this; } }); fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { /** * Animates object's properties * @param {String|Object} property Property to animate (if string) or properties to animate (if object) * @param {Number|Object} value Value to animate property to (if string was given first) or options object * @return {fabric.Object} thisArg * @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#animation} * @chainable * * As object — multiple properties * * object.animate({ left: ..., top: ... }); * object.animate({ left: ..., top: ... }, { duration: ... }); * * As string — one property * * object.animate('left', ...); * object.animate('left', { duration: ... }); * */ animate: function() { if (arguments[0] && typeof arguments[0] === 'object') { var propsToAnimate = [ ], prop, skipCallbacks; for (prop in arguments[0]) { propsToAnimate.push(prop); } for (var i = 0, len = propsToAnimate.length; i < len; i++) { prop = propsToAnimate[i]; skipCallbacks = i !== len - 1; this._animate(prop, arguments[0][prop], arguments[1], skipCallbacks); } } else { this._animate.apply(this, arguments); } return this; }, /** * @private * @param {String} property Property to animate * @param {String} to Value to animate to * @param {Object} [options] Options object * @param {Boolean} [skipCallbacks] When true, callbacks like onchange and oncomplete are not invoked */ _animate: function(property, to, options, skipCallbacks) { var _this = this, propPair; to = to.toString(); if (!options) { options = { }; } else { options = fabric.util.object.clone(options); } if (~property.indexOf('.')) { propPair = property.split('.'); } var currentValue = propPair ? this.get(propPair[0])[propPair[1]] : this.get(property); if (!('from' in options)) { options.from = currentValue; } if (~to.indexOf('=')) { to = currentValue + parseFloat(to.replace('=', '')); } else { to = parseFloat(to); } fabric.util.animate({ startValue: options.from, endValue: to, byValue: options.by, easing: options.easing, duration: options.duration, abort: options.abort && function() { return options.abort.call(_this); }, onChange: function(value) { if (propPair) { _this[propPair[0]][propPair[1]] = value; } else { _this.set(property, value); } if (skipCallbacks) { return; } options.onChange && options.onChange(); }, onComplete: function() { if (skipCallbacks) { return; } _this.setCoords(); options.onComplete && options.onComplete(); } }); } }); (function(global) { 'use strict'; var fabric = global.fabric || (global.fabric = { }), extend = fabric.util.object.extend, coordProps = { x1: 1, x2: 1, y1: 1, y2: 1 }, supportsLineDash = fabric.StaticCanvas.supports('setLineDash'); if (fabric.Line) { fabric.warn('fabric.Line is already defined'); return; } /** * Line class * @class fabric.Line * @extends fabric.Object * @see {@link fabric.Line#initialize} for constructor definition */ fabric.Line = fabric.util.createClass(fabric.Object, /** @lends fabric.Line.prototype */ { /** * Type of an object * @type String * @default */ type: 'line', /** * x value or first line edge * @type Number * @default */ x1: 0, /** * y value or first line edge * @type Number * @default */ y1: 0, /** * x value or second line edge * @type Number * @default */ x2: 0, /** * y value or second line edge * @type Number * @default */ y2: 0, /** * Constructor * @param {Array} [points] Array of points * @param {Object} [options] Options object * @return {fabric.Line} thisArg */ initialize: function(points, options) { options = options || { }; if (!points) { points = [0, 0, 0, 0]; } this.callSuper('initialize', options); this.set('x1', points[0]); this.set('y1', points[1]); this.set('x2', points[2]); this.set('y2', points[3]); this._setWidthHeight(options); }, /** * @private * @param {Object} [options] Options */ _setWidthHeight: function(options) { options || (options = { }); this.width = Math.abs(this.x2 - this.x1) || 1; this.height = Math.abs(this.y2 - this.y1) || 1; this.left = 'left' in options ? options.left : this._getLeftToOriginX(); this.top = 'top' in options ? options.top : this._getTopToOriginY(); }, /** * @private * @param {String} key * @param {Any} value */ _set: function(key, value) { this.callSuper('_set', key, value); if (typeof coordProps[key] !== 'undefined') { this._setWidthHeight(); } return this; }, /** * @private * @return {Number} leftToOriginX Distance from left edge of canvas to originX of Line. */ _getLeftToOriginX: makeEdgeToOriginGetter( { // property names origin: 'originX', axis1: 'x1', axis2: 'x2', dimension: 'width' }, { // possible values of origin nearest: 'left', center: 'center', farthest: 'right' } ), /** * @private * @return {Number} topToOriginY Distance from top edge of canvas to originY of Line. */ _getTopToOriginY: makeEdgeToOriginGetter( { // property names origin: 'originY', axis1: 'y1', axis2: 'y2', dimension: 'height' }, { // possible values of origin nearest: 'top', center: 'center', farthest: 'bottom' } ), /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on */ _render: function(ctx, noTransform) { ctx.beginPath(); if (noTransform) { // Line coords are distances from left-top of canvas to origin of line. // // To render line in a path-group, we need to translate them to // distances from center of path-group to center of line. var cp = this.getCenterPoint(); ctx.translate( cp.x, cp.y ); } if (!this.strokeDashArray || this.strokeDashArray && supportsLineDash) { // move from center (of virtual box) to its left/top corner // we can't assume x1, y1 is top left and x2, y2 is bottom right var xMult = this.x1 <= this.x2 ? -1 : 1, yMult = this.y1 <= this.y2 ? -1 : 1; ctx.moveTo( this.width === 1 ? 0 : (xMult * this.width / 2), this.height === 1 ? 0 : (yMult * this.height / 2)); ctx.lineTo( this.width === 1 ? 0 : (xMult * -1 * this.width / 2), this.height === 1 ? 0 : (yMult * -1 * this.height / 2)); } ctx.lineWidth = this.strokeWidth; // TODO: test this // make sure setting "fill" changes color of a line // (by copying fillStyle to strokeStyle, since line is stroked, not filled) var origStrokeStyle = ctx.strokeStyle; ctx.strokeStyle = this.stroke || ctx.fillStyle; this.stroke && this._renderStroke(ctx); ctx.strokeStyle = origStrokeStyle; }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on */ _renderDashedStroke: function(ctx) { var xMult = this.x1 <= this.x2 ? -1 : 1, yMult = this.y1 <= this.y2 ? -1 : 1, x = this.width === 1 ? 0 : xMult * this.width / 2, y = this.height === 1 ? 0 : yMult * this.height / 2; ctx.beginPath(); fabric.util.drawDashedLine(ctx, x, y, -x, -y, this.strokeDashArray); ctx.closePath(); }, /** * Returns object representation of an instance * @methd toObject * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output * @return {Object} object representation of an instance */ toObject: function(propertiesToInclude) { return extend(this.callSuper('toObject', propertiesToInclude), this.calcLinePoints()); }, /** * @private * Recalculate line points from width and height. */ calcLinePoints: function() { var xMult = this.x1 <= this.x2 ? -1 : 1, yMult = this.y1 <= this.y2 ? -1 : 1, x1 = (xMult * this.width / 2), y1 = (yMult * this.height / 2), x2 = (xMult * -1 * this.width / 2), y2 = (yMult * -1 * this.height / 2); return { x1: x1, x2: x2, y1: y1, y2: y2 }; }, /* _TO_SVG_START_ */ /** * Returns SVG representation of an instance * @param {Function} [reviver] Method for further parsing of svg representation. * @return {String} svg representation of an instance */ toSVG: function(reviver) { var markup = this._createBaseSVGMarkup(), p = { x1: this.x1, x2: this.x2, y1: this.y1, y2: this.y2 }; if (!(this.group && this.group.type === 'path-group')) { p = this.calcLinePoints(); } markup.push( '\n' ); return reviver ? reviver(markup.join('')) : markup.join(''); }, /* _TO_SVG_END_ */ /** * Returns complexity of an instance * @return {Number} complexity */ complexity: function() { return 1; } }); /* _FROM_SVG_START_ */ /** * List of attribute names to account for when parsing SVG element (used by {@link fabric.Line.fromElement}) * @static * @memberOf fabric.Line * @see http://www.w3.org/TR/SVG/shapes.html#LineElement */ fabric.Line.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat('x1 y1 x2 y2'.split(' ')); /** * Returns fabric.Line instance from an SVG element * @static * @memberOf fabric.Line * @param {SVGElement} element Element to parse * @param {Object} [options] Options object * @return {fabric.Line} instance of fabric.Line */ fabric.Line.fromElement = function(element, options) { var parsedAttributes = fabric.parseAttributes(element, fabric.Line.ATTRIBUTE_NAMES), points = [ parsedAttributes.x1 || 0, parsedAttributes.y1 || 0, parsedAttributes.x2 || 0, parsedAttributes.y2 || 0 ]; return new fabric.Line(points, extend(parsedAttributes, options)); }; /* _FROM_SVG_END_ */ /** * Returns fabric.Line instance from an object representation * @static * @memberOf fabric.Line * @param {Object} object Object to create an instance from * @return {fabric.Line} instance of fabric.Line */ fabric.Line.fromObject = function(object) { var points = [object.x1, object.y1, object.x2, object.y2]; return new fabric.Line(points, object); }; /** * Produces a function that calculates distance from canvas edge to Line origin. */ function makeEdgeToOriginGetter(propertyNames, originValues) { var origin = propertyNames.origin, axis1 = propertyNames.axis1, axis2 = propertyNames.axis2, dimension = propertyNames.dimension, nearest = originValues.nearest, center = originValues.center, farthest = originValues.farthest; return function() { switch (this.get(origin)) { case nearest: return Math.min(this.get(axis1), this.get(axis2)); case center: return Math.min(this.get(axis1), this.get(axis2)) + (0.5 * this.get(dimension)); case farthest: return Math.max(this.get(axis1), this.get(axis2)); } }; } })(typeof exports !== 'undefined' ? exports : this); (function(global) { 'use strict'; var fabric = global.fabric || (global.fabric = { }), pi = Math.PI, extend = fabric.util.object.extend; if (fabric.Circle) { fabric.warn('fabric.Circle is already defined.'); return; } /** * Circle class * @class fabric.Circle * @extends fabric.Object * @see {@link fabric.Circle#initialize} for constructor definition */ fabric.Circle = fabric.util.createClass(fabric.Object, /** @lends fabric.Circle.prototype */ { /** * Type of an object * @type String * @default */ type: 'circle', /** * Radius of this circle * @type Number * @default */ radius: 0, /** * Start angle of the circle, moving clockwise * @type Number * @default 0 */ startAngle: 0, /** * End angle of the circle * @type Number * @default 2Pi */ endAngle: pi * 2, /** * Constructor * @param {Object} [options] Options object * @return {fabric.Circle} thisArg */ initialize: function(options) { options = options || { }; this.callSuper('initialize', options); this.set('radius', options.radius || 0); this.startAngle = options.startAngle || this.startAngle; this.endAngle = options.endAngle || this.endAngle; }, /** * @private * @param {String} key * @param {Any} value * @return {fabric.Circle} thisArg */ _set: function(key, value) { this.callSuper('_set', key, value); if (key === 'radius') { this.setRadius(value); } return this; }, /** * Returns object representation of an instance * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output * @return {Object} object representation of an instance */ toObject: function(propertiesToInclude) { return extend(this.callSuper('toObject', propertiesToInclude), { radius: this.get('radius'), startAngle: this.startAngle, endAngle: this.endAngle }); }, /* _TO_SVG_START_ */ /** * Returns svg representation of an instance * @param {Function} [reviver] Method for further parsing of svg representation. * @return {String} svg representation of an instance */ toSVG: function(reviver) { var markup = this._createBaseSVGMarkup(), x = 0, y = 0, angle = (this.endAngle - this.startAngle) % ( 2 * pi); if (angle === 0) { if (this.group && this.group.type === 'path-group') { x = this.left + this.radius; y = this.top + this.radius; } markup.push( '\n' ); } else { var startX = Math.cos(this.startAngle) * this.radius, startY = Math.sin(this.startAngle) * this.radius, endX = Math.cos(this.endAngle) * this.radius, endY = Math.sin(this.endAngle) * this.radius, largeFlag = angle > pi ? '1' : '0'; markup.push( '\n' ); } return reviver ? reviver(markup.join('')) : markup.join(''); }, /* _TO_SVG_END_ */ /** * @private * @param {CanvasRenderingContext2D} ctx context to render on * @param {Boolean} [noTransform] When true, context is not transformed */ _render: function(ctx, noTransform) { ctx.beginPath(); ctx.arc(noTransform ? this.left + this.radius : 0, noTransform ? this.top + this.radius : 0, this.radius, this.startAngle, this.endAngle, false); this._renderFill(ctx); this._renderStroke(ctx); }, /** * Returns horizontal radius of an object (according to how an object is scaled) * @return {Number} */ getRadiusX: function() { return this.get('radius') * this.get('scaleX'); }, /** * Returns vertical radius of an object (according to how an object is scaled) * @return {Number} */ getRadiusY: function() { return this.get('radius') * this.get('scaleY'); }, /** * Sets radius of an object (and updates width accordingly) * @return {Number} */ setRadius: function(value) { this.radius = value; this.set('width', value * 2).set('height', value * 2); }, /** * Returns complexity of an instance * @return {Number} complexity of this instance */ complexity: function() { return 1; } }); /* _FROM_SVG_START_ */ /** * List of attribute names to account for when parsing SVG element (used by {@link fabric.Circle.fromElement}) * @static * @memberOf fabric.Circle * @see: http://www.w3.org/TR/SVG/shapes.html#CircleElement */ fabric.Circle.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat('cx cy r'.split(' ')); /** * Returns {@link fabric.Circle} instance from an SVG element * @static * @memberOf fabric.Circle * @param {SVGElement} element Element to parse * @param {Object} [options] Options object * @throws {Error} If value of `r` attribute is missing or invalid * @return {fabric.Circle} Instance of fabric.Circle */ fabric.Circle.fromElement = function(element, options) { options || (options = { }); var parsedAttributes = fabric.parseAttributes(element, fabric.Circle.ATTRIBUTE_NAMES); if (!isValidRadius(parsedAttributes)) { throw new Error('value of `r` attribute is required and can not be negative'); } parsedAttributes.left = parsedAttributes.left || 0; parsedAttributes.top = parsedAttributes.top || 0; var obj = new fabric.Circle(extend(parsedAttributes, options)); obj.left -= obj.radius; obj.top -= obj.radius; return obj; }; /** * @private */ function isValidRadius(attributes) { return (('radius' in attributes) && (attributes.radius > 0)); } /* _FROM_SVG_END_ */ /** * Returns {@link fabric.Circle} instance from an object representation * @static * @memberOf fabric.Circle * @param {Object} object Object to create an instance from * @return {Object} Instance of fabric.Circle */ fabric.Circle.fromObject = function(object) { return new fabric.Circle(object); }; })(typeof exports !== 'undefined' ? exports : this); (function(global) { 'use strict'; var fabric = global.fabric || (global.fabric = { }); if (fabric.Triangle) { fabric.warn('fabric.Triangle is already defined'); return; } /** * Triangle class * @class fabric.Triangle * @extends fabric.Object * @return {fabric.Triangle} thisArg * @see {@link fabric.Triangle#initialize} for constructor definition */ fabric.Triangle = fabric.util.createClass(fabric.Object, /** @lends fabric.Triangle.prototype */ { /** * Type of an object * @type String * @default */ type: 'triangle', /** * Constructor * @param {Object} [options] Options object * @return {Object} thisArg */ initialize: function(options) { options = options || { }; this.callSuper('initialize', options); this.set('width', options.width || 100) .set('height', options.height || 100); }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on */ _render: function(ctx) { var widthBy2 = this.width / 2, heightBy2 = this.height / 2; ctx.beginPath(); ctx.moveTo(-widthBy2, heightBy2); ctx.lineTo(0, -heightBy2); ctx.lineTo(widthBy2, heightBy2); ctx.closePath(); this._renderFill(ctx); this._renderStroke(ctx); }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on */ _renderDashedStroke: function(ctx) { var widthBy2 = this.width / 2, heightBy2 = this.height / 2; ctx.beginPath(); fabric.util.drawDashedLine(ctx, -widthBy2, heightBy2, 0, -heightBy2, this.strokeDashArray); fabric.util.drawDashedLine(ctx, 0, -heightBy2, widthBy2, heightBy2, this.strokeDashArray); fabric.util.drawDashedLine(ctx, widthBy2, heightBy2, -widthBy2, heightBy2, this.strokeDashArray); ctx.closePath(); }, /* _TO_SVG_START_ */ /** * Returns SVG representation of an instance * @param {Function} [reviver] Method for further parsing of svg representation. * @return {String} svg representation of an instance */ toSVG: function(reviver) { var markup = this._createBaseSVGMarkup(), widthBy2 = this.width / 2, heightBy2 = this.height / 2, points = [ -widthBy2 + ' ' + heightBy2, '0 ' + -heightBy2, widthBy2 + ' ' + heightBy2 ] .join(','); markup.push( '' ); return reviver ? reviver(markup.join('')) : markup.join(''); }, /* _TO_SVG_END_ */ /** * Returns complexity of an instance * @return {Number} complexity of this instance */ complexity: function() { return 1; } }); /** * Returns fabric.Triangle instance from an object representation * @static * @memberOf fabric.Triangle * @param {Object} object Object to create an instance from * @return {Object} instance of Canvas.Triangle */ fabric.Triangle.fromObject = function(object) { return new fabric.Triangle(object); }; })(typeof exports !== 'undefined' ? exports : this); (function(global) { 'use strict'; var fabric = global.fabric || (global.fabric = { }), piBy2 = Math.PI * 2, extend = fabric.util.object.extend; if (fabric.Ellipse) { fabric.warn('fabric.Ellipse is already defined.'); return; } /** * Ellipse class * @class fabric.Ellipse * @extends fabric.Object * @return {fabric.Ellipse} thisArg * @see {@link fabric.Ellipse#initialize} for constructor definition */ fabric.Ellipse = fabric.util.createClass(fabric.Object, /** @lends fabric.Ellipse.prototype */ { /** * Type of an object * @type String * @default */ type: 'ellipse', /** * Horizontal radius * @type Number * @default */ rx: 0, /** * Vertical radius * @type Number * @default */ ry: 0, /** * Constructor * @param {Object} [options] Options object * @return {fabric.Ellipse} thisArg */ initialize: function(options) { options = options || { }; this.callSuper('initialize', options); this.set('rx', options.rx || 0); this.set('ry', options.ry || 0); }, /** * @private * @param {String} key * @param {Any} value * @return {fabric.Ellipse} thisArg */ _set: function(key, value) { this.callSuper('_set', key, value); switch (key) { case 'rx': this.rx = value; this.set('width', value * 2); break; case 'ry': this.ry = value; this.set('height', value * 2); break; } return this; }, /** * Returns horizontal radius of an object (according to how an object is scaled) * @return {Number} */ getRx: function() { return this.get('rx') * this.get('scaleX'); }, /** * Returns Vertical radius of an object (according to how an object is scaled) * @return {Number} */ getRy: function() { return this.get('ry') * this.get('scaleY'); }, /** * Returns object representation of an instance * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output * @return {Object} object representation of an instance */ toObject: function(propertiesToInclude) { return extend(this.callSuper('toObject', propertiesToInclude), { rx: this.get('rx'), ry: this.get('ry') }); }, /* _TO_SVG_START_ */ /** * Returns svg representation of an instance * @param {Function} [reviver] Method for further parsing of svg representation. * @return {String} svg representation of an instance */ toSVG: function(reviver) { var markup = this._createBaseSVGMarkup(), x = 0, y = 0; if (this.group && this.group.type === 'path-group') { x = this.left + this.rx; y = this.top + this.ry; } markup.push( '\n' ); return reviver ? reviver(markup.join('')) : markup.join(''); }, /* _TO_SVG_END_ */ /** * @private * @param {CanvasRenderingContext2D} ctx context to render on * @param {Boolean} [noTransform] When true, context is not transformed */ _render: function(ctx, noTransform) { ctx.beginPath(); ctx.save(); ctx.transform(1, 0, 0, this.ry/this.rx, 0, 0); ctx.arc( noTransform ? this.left + this.rx : 0, noTransform ? (this.top + this.ry) * this.rx/this.ry : 0, this.rx, 0, piBy2, false); ctx.restore(); this._renderFill(ctx); this._renderStroke(ctx); }, /** * Returns complexity of an instance * @return {Number} complexity */ complexity: function() { return 1; } }); /* _FROM_SVG_START_ */ /** * List of attribute names to account for when parsing SVG element (used by {@link fabric.Ellipse.fromElement}) * @static * @memberOf fabric.Ellipse * @see http://www.w3.org/TR/SVG/shapes.html#EllipseElement */ fabric.Ellipse.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat('cx cy rx ry'.split(' ')); /** * Returns {@link fabric.Ellipse} instance from an SVG element * @static * @memberOf fabric.Ellipse * @param {SVGElement} element Element to parse * @param {Object} [options] Options object * @return {fabric.Ellipse} */ fabric.Ellipse.fromElement = function(element, options) { options || (options = { }); var parsedAttributes = fabric.parseAttributes(element, fabric.Ellipse.ATTRIBUTE_NAMES); parsedAttributes.left = parsedAttributes.left || 0; parsedAttributes.top = parsedAttributes.top || 0; var ellipse = new fabric.Ellipse(extend(parsedAttributes, options)); ellipse.top -= ellipse.ry; ellipse.left -= ellipse.rx; return ellipse; }; /* _FROM_SVG_END_ */ /** * Returns {@link fabric.Ellipse} instance from an object representation * @static * @memberOf fabric.Ellipse * @param {Object} object Object to create an instance from * @return {fabric.Ellipse} */ fabric.Ellipse.fromObject = function(object) { return new fabric.Ellipse(object); }; })(typeof exports !== 'undefined' ? exports : this); (function(global) { 'use strict'; var fabric = global.fabric || (global.fabric = { }), extend = fabric.util.object.extend; if (fabric.Rect) { console.warn('fabric.Rect is already defined'); return; } var stateProperties = fabric.Object.prototype.stateProperties.concat(); stateProperties.push('rx', 'ry', 'x', 'y'); /** * Rectangle class * @class fabric.Rect * @extends fabric.Object * @return {fabric.Rect} thisArg * @see {@link fabric.Rect#initialize} for constructor definition */ fabric.Rect = fabric.util.createClass(fabric.Object, /** @lends fabric.Rect.prototype */ { /** * List of properties to consider when checking if state of an object is changed ({@link fabric.Object#hasStateChanged}) * as well as for history (undo/redo) purposes * @type Array */ stateProperties: stateProperties, /** * Type of an object * @type String * @default */ type: 'rect', /** * Horizontal border radius * @type Number * @default */ rx: 0, /** * Vertical border radius * @type Number * @default */ ry: 0, /** * Used to specify dash pattern for stroke on this object * @type Array */ strokeDashArray: null, /** * Constructor * @param {Object} [options] Options object * @return {Object} thisArg */ initialize: function(options) { options = options || { }; this.callSuper('initialize', options); this._initRxRy(); }, /** * Initializes rx/ry attributes * @private */ _initRxRy: function() { if (this.rx && !this.ry) { this.ry = this.rx; } else if (this.ry && !this.rx) { this.rx = this.ry; } }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on */ _render: function(ctx, noTransform) { // optimize 1x1 case (used in spray brush) if (this.width === 1 && this.height === 1) { ctx.fillRect(0, 0, 1, 1); return; } var rx = this.rx ? Math.min(this.rx, this.width / 2) : 0, ry = this.ry ? Math.min(this.ry, this.height / 2) : 0, w = this.width, h = this.height, x = noTransform ? this.left : -this.width / 2, y = noTransform ? this.top : -this.height / 2, isRounded = rx !== 0 || ry !== 0, k = 1 - 0.5522847498 /* "magic number" for bezier approximations of arcs (http://itc.ktu.lt/itc354/Riskus354.pdf) */; ctx.beginPath(); ctx.moveTo(x + rx, y); ctx.lineTo(x + w - rx, y); isRounded && ctx.bezierCurveTo(x + w - k * rx, y, x + w, y + k * ry, x + w, y + ry); ctx.lineTo(x + w, y + h - ry); isRounded && ctx.bezierCurveTo(x + w, y + h - k * ry, x + w - k * rx, y + h, x + w - rx, y + h); ctx.lineTo(x + rx, y + h); isRounded && ctx.bezierCurveTo(x + k * rx, y + h, x, y + h - k * ry, x, y + h - ry); ctx.lineTo(x, y + ry); isRounded && ctx.bezierCurveTo(x, y + k * ry, x + k * rx, y, x + rx, y); ctx.closePath(); this._renderFill(ctx); this._renderStroke(ctx); }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on */ _renderDashedStroke: function(ctx) { var x = -this.width / 2, y = -this.height / 2, w = this.width, h = this.height; ctx.beginPath(); fabric.util.drawDashedLine(ctx, x, y, x + w, y, this.strokeDashArray); fabric.util.drawDashedLine(ctx, x + w, y, x + w, y + h, this.strokeDashArray); fabric.util.drawDashedLine(ctx, x + w, y + h, x, y + h, this.strokeDashArray); fabric.util.drawDashedLine(ctx, x, y + h, x, y, this.strokeDashArray); ctx.closePath(); }, /** * Returns object representation of an instance * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output * @return {Object} object representation of an instance */ toObject: function(propertiesToInclude) { var object = extend(this.callSuper('toObject', propertiesToInclude), { rx: this.get('rx') || 0, ry: this.get('ry') || 0 }); if (!this.includeDefaultValues) { this._removeDefaultValues(object); } return object; }, /* _TO_SVG_START_ */ /** * Returns svg representation of an instance * @param {Function} [reviver] Method for further parsing of svg representation. * @return {String} svg representation of an instance */ toSVG: function(reviver) { var markup = this._createBaseSVGMarkup(), x = this.left, y = this.top; if (!(this.group && this.group.type === 'path-group')) { x = -this.width / 2; y = -this.height / 2; } markup.push( '\n'); return reviver ? reviver(markup.join('')) : markup.join(''); }, /* _TO_SVG_END_ */ /** * Returns complexity of an instance * @return {Number} complexity */ complexity: function() { return 1; } }); /* _FROM_SVG_START_ */ /** * List of attribute names to account for when parsing SVG element (used by `fabric.Rect.fromElement`) * @static * @memberOf fabric.Rect * @see: http://www.w3.org/TR/SVG/shapes.html#RectElement */ fabric.Rect.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat('x y rx ry width height'.split(' ')); /** * Returns {@link fabric.Rect} instance from an SVG element * @static * @memberOf fabric.Rect * @param {SVGElement} element Element to parse * @param {Object} [options] Options object * @return {fabric.Rect} Instance of fabric.Rect */ fabric.Rect.fromElement = function(element, options) { if (!element) { return null; } options = options || { }; var parsedAttributes = fabric.parseAttributes(element, fabric.Rect.ATTRIBUTE_NAMES); parsedAttributes.left = parsedAttributes.left || 0; parsedAttributes.top = parsedAttributes.top || 0; return new fabric.Rect(extend((options ? fabric.util.object.clone(options) : { }), parsedAttributes)); }; /* _FROM_SVG_END_ */ /** * Returns {@link fabric.Rect} instance from an object representation * @static * @memberOf fabric.Rect * @param {Object} object Object to create an instance from * @return {Object} instance of fabric.Rect */ fabric.Rect.fromObject = function(object) { return new fabric.Rect(object); }; })(typeof exports !== 'undefined' ? exports : this); (function(global) { 'use strict'; var fabric = global.fabric || (global.fabric = { }); if (fabric.Polyline) { fabric.warn('fabric.Polyline is already defined'); return; } /** * Polyline class * @class fabric.Polyline * @extends fabric.Object * @see {@link fabric.Polyline#initialize} for constructor definition */ fabric.Polyline = fabric.util.createClass(fabric.Object, /** @lends fabric.Polyline.prototype */ { /** * Type of an object * @type String * @default */ type: 'polyline', /** * Points array * @type Array * @default */ points: null, /** * Minimum X from points values, necessary to offset points * @type Number * @default */ minX: 0, /** * Minimum Y from points values, necessary to offset points * @type Number * @default */ minY: 0, /** * Constructor * @param {Array} points Array of points (where each point is an object with x and y) * @param {Object} [options] Options object * @param {Boolean} [skipOffset] Whether points offsetting should be skipped * @return {fabric.Polyline} thisArg * @example * var poly = new fabric.Polyline([ * { x: 10, y: 10 }, * { x: 50, y: 30 }, * { x: 40, y: 70 }, * { x: 60, y: 50 }, * { x: 100, y: 150 }, * { x: 40, y: 100 } * ], { * stroke: 'red', * left: 100, * top: 100 * }); */ initialize: function(points, options) { return fabric.Polygon.prototype.initialize.call(this, points, options); }, /** * @private */ _calcDimensions: function() { return fabric.Polygon.prototype._calcDimensions.call(this); }, /** * @private */ _applyPointOffset: function() { return fabric.Polygon.prototype._applyPointOffset.call(this); }, /** * Returns object representation of an instance * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output * @return {Object} Object representation of an instance */ toObject: function(propertiesToInclude) { return fabric.Polygon.prototype.toObject.call(this, propertiesToInclude); }, /* _TO_SVG_START_ */ /** * Returns SVG representation of an instance * @param {Function} [reviver] Method for further parsing of svg representation. * @return {String} svg representation of an instance */ toSVG: function(reviver) { return fabric.Polygon.prototype.toSVG.call(this, reviver); }, /* _TO_SVG_END_ */ /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on */ _render: function(ctx) { fabric.Polygon.prototype.commonRender.call(this, ctx); this._renderFill(ctx); this._renderStroke(ctx); }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on */ _renderDashedStroke: function(ctx) { var p1, p2; ctx.beginPath(); for (var i = 0, len = this.points.length; i < len; i++) { p1 = this.points[i]; p2 = this.points[i + 1] || p1; fabric.util.drawDashedLine(ctx, p1.x, p1.y, p2.x, p2.y, this.strokeDashArray); } }, /** * Returns complexity of an instance * @return {Number} complexity of this instance */ complexity: function() { return this.get('points').length; } }); /* _FROM_SVG_START_ */ /** * List of attribute names to account for when parsing SVG element (used by {@link fabric.Polyline.fromElement}) * @static * @memberOf fabric.Polyline * @see: http://www.w3.org/TR/SVG/shapes.html#PolylineElement */ fabric.Polyline.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat(); /** * Returns fabric.Polyline instance from an SVG element * @static * @memberOf fabric.Polyline * @param {SVGElement} element Element to parse * @param {Object} [options] Options object * @return {fabric.Polyline} Instance of fabric.Polyline */ fabric.Polyline.fromElement = function(element, options) { if (!element) { return null; } options || (options = { }); var points = fabric.parsePointsAttribute(element.getAttribute('points')), parsedAttributes = fabric.parseAttributes(element, fabric.Polyline.ATTRIBUTE_NAMES); if (points === null) { return null; } return new fabric.Polyline(points, fabric.util.object.extend(parsedAttributes, options)); }; /* _FROM_SVG_END_ */ /** * Returns fabric.Polyline instance from an object representation * @static * @memberOf fabric.Polyline * @param {Object} object Object to create an instance from * @return {fabric.Polyline} Instance of fabric.Polyline */ fabric.Polyline.fromObject = function(object) { var points = object.points; return new fabric.Polyline(points, object, true); }; })(typeof exports !== 'undefined' ? exports : this); (function(global) { 'use strict'; var fabric = global.fabric || (global.fabric = { }), extend = fabric.util.object.extend, min = fabric.util.array.min, max = fabric.util.array.max, toFixed = fabric.util.toFixed; if (fabric.Polygon) { fabric.warn('fabric.Polygon is already defined'); return; } /** * Polygon class * @class fabric.Polygon * @extends fabric.Object * @see {@link fabric.Polygon#initialize} for constructor definition */ fabric.Polygon = fabric.util.createClass(fabric.Object, /** @lends fabric.Polygon.prototype */ { /** * Type of an object * @type String * @default */ type: 'polygon', /** * Points array * @type Array * @default */ points: null, /** * Minimum X from points values, necessary to offset points * @type Number * @default */ minX: 0, /** * Minimum Y from points values, necessary to offset points * @type Number * @default */ minY: 0, /** * Constructor * @param {Array} points Array of points * @param {Object} [options] Options object * @return {fabric.Polygon} thisArg */ initialize: function(points, options) { options = options || { }; this.points = points; this.callSuper('initialize', options); this._calcDimensions(); if (!('top' in options)) { this.top = this.minY; } if (!('left' in options)) { this.left = this.minX; } }, /** * @private */ _calcDimensions: function() { var points = this.points, minX = min(points, 'x'), minY = min(points, 'y'), maxX = max(points, 'x'), maxY = max(points, 'y'); this.width = (maxX - minX) || 1; this.height = (maxY - minY) || 1; this.minX = minX, this.minY = minY; }, /** * @private */ _applyPointOffset: function() { // change points to offset polygon into a bounding box // executed one time this.points.forEach(function(p) { p.x -= (this.minX + this.width / 2); p.y -= (this.minY + this.height / 2); }, this); }, /** * Returns object representation of an instance * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output * @return {Object} Object representation of an instance */ toObject: function(propertiesToInclude) { return extend(this.callSuper('toObject', propertiesToInclude), { points: this.points.concat() }); }, /* _TO_SVG_START_ */ /** * Returns svg representation of an instance * @param {Function} [reviver] Method for further parsing of svg representation. * @return {String} svg representation of an instance */ toSVG: function(reviver) { var points = [], markup = this._createBaseSVGMarkup(); for (var i = 0, len = this.points.length; i < len; i++) { points.push(toFixed(this.points[i].x, 2), ',', toFixed(this.points[i].y, 2), ' '); } markup.push( '<', this.type, ' ', 'points="', points.join(''), '" style="', this.getSvgStyles(), '" transform="', this.getSvgTransform(), ' ', this.getSvgTransformMatrix(), '"/>\n' ); return reviver ? reviver(markup.join('')) : markup.join(''); }, /* _TO_SVG_END_ */ /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on */ _render: function(ctx) { this.commonRender(ctx); this._renderFill(ctx); if (this.stroke || this.strokeDashArray) { ctx.closePath(); this._renderStroke(ctx); } }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on */ commonRender: function(ctx) { var point; ctx.beginPath(); if (this._applyPointOffset) { if (!(this.group && this.group.type === 'path-group')) { this._applyPointOffset(); } this._applyPointOffset = null; } ctx.moveTo(this.points[0].x, this.points[0].y); for (var i = 0, len = this.points.length; i < len; i++) { point = this.points[i]; ctx.lineTo(point.x, point.y); } }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on */ _renderDashedStroke: function(ctx) { fabric.Polyline.prototype._renderDashedStroke.call(this, ctx); ctx.closePath(); }, /** * Returns complexity of an instance * @return {Number} complexity of this instance */ complexity: function() { return this.points.length; } }); /* _FROM_SVG_START_ */ /** * List of attribute names to account for when parsing SVG element (used by `fabric.Polygon.fromElement`) * @static * @memberOf fabric.Polygon * @see: http://www.w3.org/TR/SVG/shapes.html#PolygonElement */ fabric.Polygon.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat(); /** * Returns {@link fabric.Polygon} instance from an SVG element * @static * @memberOf fabric.Polygon * @param {SVGElement} element Element to parse * @param {Object} [options] Options object * @return {fabric.Polygon} Instance of fabric.Polygon */ fabric.Polygon.fromElement = function(element, options) { if (!element) { return null; } options || (options = { }); var points = fabric.parsePointsAttribute(element.getAttribute('points')), parsedAttributes = fabric.parseAttributes(element, fabric.Polygon.ATTRIBUTE_NAMES); if (points === null) { return null; } return new fabric.Polygon(points, extend(parsedAttributes, options)); }; /* _FROM_SVG_END_ */ /** * Returns fabric.Polygon instance from an object representation * @static * @memberOf fabric.Polygon * @param {Object} object Object to create an instance from * @return {fabric.Polygon} Instance of fabric.Polygon */ fabric.Polygon.fromObject = function(object) { return new fabric.Polygon(object.points, object, true); }; })(typeof exports !== 'undefined' ? exports : this); (function(global) { 'use strict'; var fabric = global.fabric || (global.fabric = { }), min = fabric.util.array.min, max = fabric.util.array.max, extend = fabric.util.object.extend, _toString = Object.prototype.toString, drawArc = fabric.util.drawArc, commandLengths = { m: 2, l: 2, h: 1, v: 1, c: 6, s: 4, q: 4, t: 2, a: 7 }, repeatedCommands = { m: 'l', M: 'L' }; if (fabric.Path) { fabric.warn('fabric.Path is already defined'); return; } /** * Path class * @class fabric.Path * @extends fabric.Object * @tutorial {@link http://fabricjs.com/fabric-intro-part-1/#path_and_pathgroup} * @see {@link fabric.Path#initialize} for constructor definition */ fabric.Path = fabric.util.createClass(fabric.Object, /** @lends fabric.Path.prototype */ { /** * Type of an object * @type String * @default */ type: 'path', /** * Array of path points * @type Array * @default */ path: null, /** * Minimum X from points values, necessary to offset points * @type Number * @default */ minX: 0, /** * Minimum Y from points values, necessary to offset points * @type Number * @default */ minY: 0, /** * Constructor * @param {Array|String} path Path data (sequence of coordinates and corresponding "command" tokens) * @param {Object} [options] Options object * @return {fabric.Path} thisArg */ initialize: function(path, options) { options = options || { }; this.setOptions(options); if (!path) { throw new Error('`path` argument is required'); } var fromArray = _toString.call(path) === '[object Array]'; this.path = fromArray ? path // one of commands (m,M,l,L,q,Q,c,C,etc.) followed by non-command characters (i.e. command values) : path.match && path.match(/[mzlhvcsqta][^mzlhvcsqta]*/gi); if (!this.path) { return; } if (!fromArray) { this.path = this._parsePath(); } var calcDim = this._parseDimensions(); this.minX = calcDim.left; this.minY = calcDim.top; this.width = calcDim.width; this.height = calcDim.height; this.top = this.top || this.minY; this.left = this.left || this.minX; this.pathOffset = this.pathOffset || { x: this.minX + this.width / 2, y: this.minY + this.height / 2 }; if (options.sourcePath) { this.setSourcePath(options.sourcePath); } }, /** * @private * @param {CanvasRenderingContext2D} ctx context to render path on */ _render: function(ctx) { var current, // current instruction previous = null, subpathStartX = 0, subpathStartY = 0, x = 0, // current x y = 0, // current y controlX = 0, // current control point x controlY = 0, // current control point y tempX, tempY, tempControlX, tempControlY, l = -this.pathOffset.x, t = -this.pathOffset.y; if (this.group && this.group.type === 'path-group') { l = 0; t = 0; } ctx.beginPath(); for (var i = 0, len = this.path.length; i < len; ++i) { current = this.path[i]; switch (current[0]) { // first letter case 'l': // lineto, relative x += current[1]; y += current[2]; ctx.lineTo(x + l, y + t); break; case 'L': // lineto, absolute x = current[1]; y = current[2]; ctx.lineTo(x + l, y + t); break; case 'h': // horizontal lineto, relative x += current[1]; ctx.lineTo(x + l, y + t); break; case 'H': // horizontal lineto, absolute x = current[1]; ctx.lineTo(x + l, y + t); break; case 'v': // vertical lineto, relative y += current[1]; ctx.lineTo(x + l, y + t); break; case 'V': // verical lineto, absolute y = current[1]; ctx.lineTo(x + l, y + t); break; case 'm': // moveTo, relative x += current[1]; y += current[2]; subpathStartX = x; subpathStartY = y; ctx.moveTo(x + l, y + t); break; case 'M': // moveTo, absolute x = current[1]; y = current[2]; subpathStartX = x; subpathStartY = y; ctx.moveTo(x + l, y + t); break; case 'c': // bezierCurveTo, relative tempX = x + current[5]; tempY = y + current[6]; controlX = x + current[3]; controlY = y + current[4]; ctx.bezierCurveTo( x + current[1] + l, // x1 y + current[2] + t, // y1 controlX + l, // x2 controlY + t, // y2 tempX + l, tempY + t ); x = tempX; y = tempY; break; case 'C': // bezierCurveTo, absolute x = current[5]; y = current[6]; controlX = current[3]; controlY = current[4]; ctx.bezierCurveTo( current[1] + l, current[2] + t, controlX + l, controlY + t, x + l, y + t ); break; case 's': // shorthand cubic bezierCurveTo, relative // transform to absolute x,y tempX = x + current[3]; tempY = y + current[4]; // calculate reflection of previous control points controlX = controlX ? (2 * x - controlX) : x; controlY = controlY ? (2 * y - controlY) : y; ctx.bezierCurveTo( controlX + l, controlY + t, x + current[1] + l, y + current[2] + t, tempX + l, tempY + t ); // set control point to 2nd one of this command // "... the first control point is assumed to be // the reflection of the second control point on // the previous command relative to the current point." controlX = x + current[1]; controlY = y + current[2]; x = tempX; y = tempY; break; case 'S': // shorthand cubic bezierCurveTo, absolute tempX = current[3]; tempY = current[4]; // calculate reflection of previous control points controlX = 2 * x - controlX; controlY = 2 * y - controlY; ctx.bezierCurveTo( controlX + l, controlY + t, current[1] + l, current[2] + t, tempX + l, tempY + t ); x = tempX; y = tempY; // set control point to 2nd one of this command // "... the first control point is assumed to be // the reflection of the second control point on // the previous command relative to the current point." controlX = current[1]; controlY = current[2]; break; case 'q': // quadraticCurveTo, relative // transform to absolute x,y tempX = x + current[3]; tempY = y + current[4]; controlX = x + current[1]; controlY = y + current[2]; ctx.quadraticCurveTo( controlX + l, controlY + t, tempX + l, tempY + t ); x = tempX; y = tempY; break; case 'Q': // quadraticCurveTo, absolute tempX = current[3]; tempY = current[4]; ctx.quadraticCurveTo( current[1] + l, current[2] + t, tempX + l, tempY + t ); x = tempX; y = tempY; controlX = current[1]; controlY = current[2]; break; case 't': // shorthand quadraticCurveTo, relative // transform to absolute x,y tempX = x + current[1]; tempY = y + current[2]; if (previous[0].match(/[QqTt]/) === null) { // If there is no previous command or if the previous command was not a Q, q, T or t, // assume the control point is coincident with the current point controlX = x; controlY = y; } else if (previous[0] === 't') { // calculate reflection of previous control points for t controlX = 2 * x - tempControlX; controlY = 2 * y - tempControlY; } else if (previous[0] === 'q') { // calculate reflection of previous control points for q controlX = 2 * x - controlX; controlY = 2 * y - controlY; } tempControlX = controlX; tempControlY = controlY; ctx.quadraticCurveTo( controlX + l, controlY + t, tempX + l, tempY + t ); x = tempX; y = tempY; controlX = x + current[1]; controlY = y + current[2]; break; case 'T': tempX = current[1]; tempY = current[2]; // calculate reflection of previous control points controlX = 2 * x - controlX; controlY = 2 * y - controlY; ctx.quadraticCurveTo( controlX + l, controlY + t, tempX + l, tempY + t ); x = tempX; y = tempY; break; case 'a': // TODO: optimize this drawArc(ctx, x + l, y + t, [ current[1], current[2], current[3], current[4], current[5], current[6] + x + l, current[7] + y + t ]); x += current[6]; y += current[7]; break; case 'A': // TODO: optimize this drawArc(ctx, x + l, y + t, [ current[1], current[2], current[3], current[4], current[5], current[6] + l, current[7] + t ]); x = current[6]; y = current[7]; break; case 'z': case 'Z': x = subpathStartX; y = subpathStartY; ctx.closePath(); break; } previous = current; } this._renderFill(ctx); this._renderStroke(ctx); }, /** * Renders path on a specified context * @param {CanvasRenderingContext2D} ctx context to render path on * @param {Boolean} [noTransform] When true, context is not transformed */ render: function(ctx, noTransform) { // do not render if width/height are zeros or object is not visible if (!this.visible) { return; } ctx.save(); this._setupCompositeOperation(ctx); if (!noTransform) { this.transform(ctx); } this._setStrokeStyles(ctx); this._setFillStyles(ctx); if (this.group && this.group.type === 'path-group') { ctx.translate(-this.group.width / 2, -this.group.height / 2); } if (this.transformMatrix) { ctx.transform.apply(ctx, this.transformMatrix); } this._setOpacity(ctx); this._setShadow(ctx); this.clipTo && fabric.util.clipContext(this, ctx); this._render(ctx, noTransform); this.clipTo && ctx.restore(); this._removeShadow(ctx); this._restoreCompositeOperation(ctx); ctx.restore(); }, /** * Returns string representation of an instance * @return {String} string representation of an instance */ toString: function() { return '#'; }, /** * Returns object representation of an instance * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output * @return {Object} object representation of an instance */ toObject: function(propertiesToInclude) { var o = extend(this.callSuper('toObject', propertiesToInclude), { path: this.path.map(function(item) { return item.slice() }), pathOffset: this.pathOffset }); if (this.sourcePath) { o.sourcePath = this.sourcePath; } if (this.transformMatrix) { o.transformMatrix = this.transformMatrix; } return o; }, /** * Returns dataless object representation of an instance * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output * @return {Object} object representation of an instance */ toDatalessObject: function(propertiesToInclude) { var o = this.toObject(propertiesToInclude); if (this.sourcePath) { o.path = this.sourcePath; } delete o.sourcePath; return o; }, /* _TO_SVG_START_ */ /** * Returns svg representation of an instance * @param {Function} [reviver] Method for further parsing of svg representation. * @return {String} svg representation of an instance */ toSVG: function(reviver) { var chunks = [], markup = this._createBaseSVGMarkup(), addTransform = ''; for (var i = 0, len = this.path.length; i < len; i++) { chunks.push(this.path[i].join(' ')); } var path = chunks.join(' '); if (!(this.group && this.group.type === 'path-group')) { addTransform = 'translate(' + (-this.pathOffset.x) + ', ' + (-this.pathOffset.y) + ')'; } markup.push( //jscs:disable validateIndentation '\n' //jscs:enable validateIndentation ); return reviver ? reviver(markup.join('')) : markup.join(''); }, /* _TO_SVG_END_ */ /** * Returns number representation of an instance complexity * @return {Number} complexity of this instance */ complexity: function() { return this.path.length; }, /** * @private */ _parsePath: function() { var result = [ ], coords = [ ], currentPath, parsed, re = /([-+]?((\d+\.\d+)|((\d+)|(\.\d+)))(?:e[-+]?\d+)?)/ig, match, coordsStr; for (var i = 0, coordsParsed, len = this.path.length; i < len; i++) { currentPath = this.path[i]; coordsStr = currentPath.slice(1).trim(); coords.length = 0; while ((match = re.exec(coordsStr))) { coords.push(match[0]); } coordsParsed = [ currentPath.charAt(0) ]; for (var j = 0, jlen = coords.length; j < jlen; j++) { parsed = parseFloat(coords[j]); if (!isNaN(parsed)) { coordsParsed.push(parsed); } } var command = coordsParsed[0], commandLength = commandLengths[command.toLowerCase()], repeatedCommand = repeatedCommands[command] || command; if (coordsParsed.length - 1 > commandLength) { for (var k = 1, klen = coordsParsed.length; k < klen; k += commandLength) { result.push([ command ].concat(coordsParsed.slice(k, k + commandLength))); command = repeatedCommand; } } else { result.push(coordsParsed); } } return result; }, /** * @private */ _parseDimensions: function() { var aX = [], aY = [], current, // current instruction previous = null, subpathStartX = 0, subpathStartY = 0, x = 0, // current x y = 0, // current y controlX = 0, // current control point x controlY = 0, // current control point y tempX, tempY, tempControlX, tempControlY, bounds; for (var i = 0, len = this.path.length; i < len; ++i) { current = this.path[i]; switch (current[0]) { // first letter case 'l': // lineto, relative x += current[1]; y += current[2]; bounds = [ ]; break; case 'L': // lineto, absolute x = current[1]; y = current[2]; bounds = [ ]; break; case 'h': // horizontal lineto, relative x += current[1]; bounds = [ ]; break; case 'H': // horizontal lineto, absolute x = current[1]; bounds = [ ]; break; case 'v': // vertical lineto, relative y += current[1]; bounds = [ ]; break; case 'V': // verical lineto, absolute y = current[1]; bounds = [ ]; break; case 'm': // moveTo, relative x += current[1]; y += current[2]; subpathStartX = x; subpathStartY = y; bounds = [ ]; break; case 'M': // moveTo, absolute x = current[1]; y = current[2]; subpathStartX = x; subpathStartY = y; bounds = [ ]; break; case 'c': // bezierCurveTo, relative tempX = x + current[5]; tempY = y + current[6]; controlX = x + current[3]; controlY = y + current[4]; bounds = fabric.util.getBoundsOfCurve(x, y, x + current[1], // x1 y + current[2], // y1 controlX, // x2 controlY, // y2 tempX, tempY ); x = tempX; y = tempY; break; case 'C': // bezierCurveTo, absolute x = current[5]; y = current[6]; controlX = current[3]; controlY = current[4]; bounds = fabric.util.getBoundsOfCurve(x, y, current[1], current[2], controlX, controlY, x, y ); break; case 's': // shorthand cubic bezierCurveTo, relative // transform to absolute x,y tempX = x + current[3]; tempY = y + current[4]; // calculate reflection of previous control points controlX = controlX ? (2 * x - controlX) : x; controlY = controlY ? (2 * y - controlY) : y; bounds = fabric.util.getBoundsOfCurve(x, y, controlX, controlY, x + current[1], y + current[2], tempX, tempY ); // set control point to 2nd one of this command // "... the first control point is assumed to be // the reflection of the second control point on // the previous command relative to the current point." controlX = x + current[1]; controlY = y + current[2]; x = tempX; y = tempY; break; case 'S': // shorthand cubic bezierCurveTo, absolute tempX = current[3]; tempY = current[4]; // calculate reflection of previous control points controlX = 2 * x - controlX; controlY = 2 * y - controlY; bounds = fabric.util.getBoundsOfCurve(x, y, controlX, controlY, current[1], current[2], tempX, tempY ); x = tempX; y = tempY; // set control point to 2nd one of this command // "... the first control point is assumed to be // the reflection of the second control point on // the previous command relative to the current point." controlX = current[1]; controlY = current[2]; break; case 'q': // quadraticCurveTo, relative // transform to absolute x,y tempX = x + current[3]; tempY = y + current[4]; controlX = x + current[1]; controlY = y + current[2]; bounds = fabric.util.getBoundsOfCurve(x, y, controlX, controlY, controlX, controlY, tempX, tempY ); x = tempX; y = tempY; break; case 'Q': // quadraticCurveTo, absolute controlX = current[1]; controlY = current[2]; bounds = fabric.util.getBoundsOfCurve(x, y, controlX, controlY, controlX, controlY, current[3], current[4] ); x = current[3]; y = current[4]; break; case 't': // shorthand quadraticCurveTo, relative // transform to absolute x,y tempX = x + current[1]; tempY = y + current[2]; if (previous[0].match(/[QqTt]/) === null) { // If there is no previous command or if the previous command was not a Q, q, T or t, // assume the control point is coincident with the current point controlX = x; controlY = y; } else if (previous[0] === 't') { // calculate reflection of previous control points for t controlX = 2 * x - tempControlX; controlY = 2 * y - tempControlY; } else if (previous[0] === 'q') { // calculate reflection of previous control points for q controlX = 2 * x - controlX; controlY = 2 * y - controlY; } tempControlX = controlX; tempControlY = controlY; bounds = fabric.util.getBoundsOfCurve(x, y, controlX, controlY, controlX, controlY, tempX, tempY ); x = tempX; y = tempY; controlX = x + current[1]; controlY = y + current[2]; break; case 'T': tempX = current[1]; tempY = current[2]; // calculate reflection of previous control points controlX = 2 * x - controlX; controlY = 2 * y - controlY; bounds = fabric.util.getBoundsOfCurve(x, y, controlX, controlY, controlX, controlY, tempX, tempY ); x = tempX; y = tempY; break; case 'a': // TODO: optimize this bounds = fabric.util.getBoundsOfArc(x, y, current[1], current[2], current[3], current[4], current[5], current[6] + x, current[7] + y ); x += current[6]; y += current[7]; break; case 'A': // TODO: optimize this bounds = fabric.util.getBoundsOfArc(x, y, current[1], current[2], current[3], current[4], current[5], current[6], current[7] ); x = current[6]; y = current[7]; break; case 'z': case 'Z': x = subpathStartX; y = subpathStartY; break; } previous = current; bounds.forEach(function (point) { aX.push(point.x); aY.push(point.y); }); aX.push(x); aY.push(y); } var minX = min(aX), minY = min(aY), maxX = max(aX), maxY = max(aY), deltaX = maxX - minX, deltaY = maxY - minY, o = { left: minX, top: minY, width: deltaX, height: deltaY }; return o; } }); /** * Creates an instance of fabric.Path from an object * @static * @memberOf fabric.Path * @param {Object} object * @param {Function} callback Callback to invoke when an fabric.Path instance is created */ fabric.Path.fromObject = function(object, callback) { if (typeof object.path === 'string') { fabric.loadSVGFromURL(object.path, function (elements) { var path = elements[0], pathUrl = object.path; delete object.path; fabric.util.object.extend(path, object); path.setSourcePath(pathUrl); callback(path); }); } else { callback(new fabric.Path(object.path, object)); } }; /* _FROM_SVG_START_ */ /** * List of attribute names to account for when parsing SVG element (used by `fabric.Path.fromElement`) * @static * @memberOf fabric.Path * @see http://www.w3.org/TR/SVG/paths.html#PathElement */ fabric.Path.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat(['d']); /** * Creates an instance of fabric.Path from an SVG element * @static * @memberOf fabric.Path * @param {SVGElement} element to parse * @param {Function} callback Callback to invoke when an fabric.Path instance is created * @param {Object} [options] Options object */ fabric.Path.fromElement = function(element, callback, options) { var parsedAttributes = fabric.parseAttributes(element, fabric.Path.ATTRIBUTE_NAMES); callback && callback(new fabric.Path(parsedAttributes.d, extend(parsedAttributes, options))); }; /* _FROM_SVG_END_ */ /** * Indicates that instances of this type are async * @static * @memberOf fabric.Path * @type Boolean * @default */ fabric.Path.async = true; })(typeof exports !== 'undefined' ? exports : this); (function(global) { 'use strict'; var fabric = global.fabric || (global.fabric = { }), extend = fabric.util.object.extend, invoke = fabric.util.array.invoke, parentToObject = fabric.Object.prototype.toObject; if (fabric.PathGroup) { fabric.warn('fabric.PathGroup is already defined'); return; } /** * Path group class * @class fabric.PathGroup * @extends fabric.Path * @tutorial {@link http://fabricjs.com/fabric-intro-part-1/#path_and_pathgroup} * @see {@link fabric.PathGroup#initialize} for constructor definition */ fabric.PathGroup = fabric.util.createClass(fabric.Path, /** @lends fabric.PathGroup.prototype */ { /** * Type of an object * @type String * @default */ type: 'path-group', /** * Fill value * @type String * @default */ fill: '', /** * Constructor * @param {Array} paths * @param {Object} [options] Options object * @return {fabric.PathGroup} thisArg */ initialize: function(paths, options) { options = options || { }; this.paths = paths || [ ]; for (var i = this.paths.length; i--; ) { this.paths[i].group = this; } this.setOptions(options); if (options.widthAttr) { this.scaleX = options.widthAttr / options.width; } if (options.heightAttr) { this.scaleY = options.heightAttr / options.height; } this.setCoords(); if (options.sourcePath) { this.setSourcePath(options.sourcePath); } }, /** * Renders this group on a specified context * @param {CanvasRenderingContext2D} ctx Context to render this instance on */ render: function(ctx) { // do not render if object is not visible if (!this.visible) { return; } ctx.save(); var m = this.transformMatrix; if (m) { ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]); } this.transform(ctx); this._setShadow(ctx); this.clipTo && fabric.util.clipContext(this, ctx); for (var i = 0, l = this.paths.length; i < l; ++i) { this.paths[i].render(ctx, true); } this.clipTo && ctx.restore(); this._removeShadow(ctx); ctx.restore(); }, /** * Sets certain property to a certain value * @param {String} prop * @param {Any} value * @return {fabric.PathGroup} thisArg */ _set: function(prop, value) { if (prop === 'fill' && value && this.isSameColor()) { var i = this.paths.length; while (i--) { this.paths[i]._set(prop, value); } } return this.callSuper('_set', prop, value); }, /** * Returns object representation of this path group * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output * @return {Object} object representation of an instance */ toObject: function(propertiesToInclude) { var o = extend(parentToObject.call(this, propertiesToInclude), { paths: invoke(this.getObjects(), 'toObject', propertiesToInclude) }); if (this.sourcePath) { o.sourcePath = this.sourcePath; } return o; }, /** * Returns dataless object representation of this path group * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output * @return {Object} dataless object representation of an instance */ toDatalessObject: function(propertiesToInclude) { var o = this.toObject(propertiesToInclude); if (this.sourcePath) { o.paths = this.sourcePath; } return o; }, /* _TO_SVG_START_ */ /** * Returns svg representation of an instance * @param {Function} [reviver] Method for further parsing of svg representation. * @return {String} svg representation of an instance */ toSVG: function(reviver) { var objects = this.getObjects(), translatePart = 'translate(' + this.left + ' ' + this.top + ')', markup = [ //jscs:disable validateIndentation '\n' //jscs:enable validateIndentation ]; for (var i = 0, len = objects.length; i < len; i++) { markup.push(objects[i].toSVG(reviver)); } markup.push('\n'); return reviver ? reviver(markup.join('')) : markup.join(''); }, /* _TO_SVG_END_ */ /** * Returns a string representation of this path group * @return {String} string representation of an object */ toString: function() { return '#'; }, /** * Returns true if all paths in this group are of same color * @return {Boolean} true if all paths are of the same color (`fill`) */ isSameColor: function() { var firstPathFill = (this.getObjects()[0].get('fill') || '').toLowerCase(); return this.getObjects().every(function(path) { return (path.get('fill') || '').toLowerCase() === firstPathFill; }); }, /** * Returns number representation of object's complexity * @return {Number} complexity */ complexity: function() { return this.paths.reduce(function(total, path) { return total + ((path && path.complexity) ? path.complexity() : 0); }, 0); }, /** * Returns all paths in this path group * @return {Array} array of path objects included in this path group */ getObjects: function() { return this.paths; } }); /** * Creates fabric.PathGroup instance from an object representation * @static * @memberOf fabric.PathGroup * @param {Object} object Object to create an instance from * @param {Function} callback Callback to invoke when an fabric.PathGroup instance is created */ fabric.PathGroup.fromObject = function(object, callback) { if (typeof object.paths === 'string') { fabric.loadSVGFromURL(object.paths, function (elements) { var pathUrl = object.paths; delete object.paths; var pathGroup = fabric.util.groupSVGElements(elements, object, pathUrl); callback(pathGroup); }); } else { fabric.util.enlivenObjects(object.paths, function(enlivenedObjects) { delete object.paths; callback(new fabric.PathGroup(enlivenedObjects, object)); }); } }; /** * Indicates that instances of this type are async * @static * @memberOf fabric.PathGroup * @type Boolean * @default */ fabric.PathGroup.async = true; })(typeof exports !== 'undefined' ? exports : this); (function(global) { 'use strict'; var fabric = global.fabric || (global.fabric = { }), extend = fabric.util.object.extend, min = fabric.util.array.min, max = fabric.util.array.max, invoke = fabric.util.array.invoke; if (fabric.Group) { return; } // lock-related properties, for use in fabric.Group#get // to enable locking behavior on group // when one of its objects has lock-related properties set var _lockProperties = { lockMovementX: true, lockMovementY: true, lockRotation: true, lockScalingX: true, lockScalingY: true, lockUniScaling: true }; /** * Group class * @class fabric.Group * @extends fabric.Object * @mixes fabric.Collection * @tutorial {@link http://fabricjs.com/fabric-intro-part-3/#groups} * @see {@link fabric.Group#initialize} for constructor definition */ fabric.Group = fabric.util.createClass(fabric.Object, fabric.Collection, /** @lends fabric.Group.prototype */ { /** * Type of an object * @type String * @default */ type: 'group', /** * Constructor * @param {Object} objects Group objects * @param {Object} [options] Options object * @return {Object} thisArg */ initialize: function(objects, options) { options = options || { }; this._objects = objects || []; for (var i = this._objects.length; i--; ) { this._objects[i].group = this; } this.originalState = { }; this.callSuper('initialize'); this._calcBounds(); this._updateObjectsCoords(); if (options) { extend(this, options); } this.setCoords(); this.saveCoords(); }, /** * @private */ _updateObjectsCoords: function() { this.forEachObject(this._updateObjectCoords, this); }, /** * @private */ _updateObjectCoords: function(object) { var objectLeft = object.getLeft(), objectTop = object.getTop(); object.set({ originalLeft: objectLeft, originalTop: objectTop, left: objectLeft - this.left, top: objectTop - this.top }); object.setCoords(); // do not display corners of objects enclosed in a group object.__origHasControls = object.hasControls; object.hasControls = false; }, /** * Returns string represenation of a group * @return {String} */ toString: function() { return '#'; }, /** * Adds an object to a group; Then recalculates group's dimension, position. * @param {Object} object * @return {fabric.Group} thisArg * @chainable */ addWithUpdate: function(object) { this._restoreObjectsState(); if (object) { this._objects.push(object); object.group = this; } // since _restoreObjectsState set objects inactive this.forEachObject(this._setObjectActive, this); this._calcBounds(); this._updateObjectsCoords(); return this; }, /** * @private */ _setObjectActive: function(object) { object.set('active', true); object.group = this; }, /** * Removes an object from a group; Then recalculates group's dimension, position. * @param {Object} object * @return {fabric.Group} thisArg * @chainable */ removeWithUpdate: function(object) { this._moveFlippedObject(object); this._restoreObjectsState(); // since _restoreObjectsState set objects inactive this.forEachObject(this._setObjectActive, this); this.remove(object); this._calcBounds(); this._updateObjectsCoords(); return this; }, /** * @private */ _onObjectAdded: function(object) { object.group = this; }, /** * @private */ _onObjectRemoved: function(object) { delete object.group; object.set('active', false); }, /** * Properties that are delegated to group objects when reading/writing * @param {Object} delegatedProperties */ delegatedProperties: { fill: true, opacity: true, fontFamily: true, fontWeight: true, fontSize: true, fontStyle: true, lineHeight: true, textDecoration: true, textAlign: true, backgroundColor: true }, /** * @private */ _set: function(key, value) { if (key in this.delegatedProperties) { var i = this._objects.length; this[key] = value; while (i--) { this._objects[i].set(key, value); } } else { this[key] = value; } }, /** * Returns object representation of an instance * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output * @return {Object} object representation of an instance */ toObject: function(propertiesToInclude) { return extend(this.callSuper('toObject', propertiesToInclude), { objects: invoke(this._objects, 'toObject', propertiesToInclude) }); }, /** * Renders instance on a given context * @param {CanvasRenderingContext2D} ctx context to render instance on */ render: function(ctx) { // do not render if object is not visible if (!this.visible) { return; } ctx.save(); this.clipTo && fabric.util.clipContext(this, ctx); // the array is now sorted in order of highest first, so start from end for (var i = 0, len = this._objects.length; i < len; i++) { this._renderObject(this._objects[i], ctx); } this.clipTo && ctx.restore(); ctx.restore(); }, /** * Renders controls and borders for the object * @param {CanvasRenderingContext2D} ctx Context to render on * @param {Boolean} [noTransform] When true, context is not transformed */ _renderControls: function(ctx, noTransform) { this.callSuper('_renderControls', ctx, noTransform); for (var i = 0, len = this._objects.length; i < len; i++) { this._objects[i]._renderControls(ctx); } }, /** * @private */ _renderObject: function(object, ctx) { var originalHasRotatingPoint = object.hasRotatingPoint; // do not render if object is not visible if (!object.visible) { return; } object.hasRotatingPoint = false; object.render(ctx); object.hasRotatingPoint = originalHasRotatingPoint; }, /** * Retores original state of each of group objects (original state is that which was before group was created). * @private * @return {fabric.Group} thisArg * @chainable */ _restoreObjectsState: function() { this._objects.forEach(this._restoreObjectState, this); return this; }, /** * Moves a flipped object to the position where it's displayed * @private * @param {fabric.Object} object * @return {fabric.Group} thisArg */ _moveFlippedObject: function(object) { var oldOriginX = object.get('originX'), oldOriginY = object.get('originY'), center = object.getCenterPoint(); object.set({ originX: 'center', originY: 'center', left: center.x, top: center.y }); this._toggleFlipping(object); var newOrigin = object.getPointByOrigin(oldOriginX, oldOriginY); object.set({ originX: oldOriginX, originY: oldOriginY, left: newOrigin.x, top: newOrigin.y }); return this; }, /** * @private */ _toggleFlipping: function(object) { if (this.flipX) { object.toggle('flipX'); object.set('left', -object.get('left')); object.setAngle(-object.getAngle()); } if (this.flipY) { object.toggle('flipY'); object.set('top', -object.get('top')); object.setAngle(-object.getAngle()); } }, /** * Restores original state of a specified object in group * @private * @param {fabric.Object} object * @return {fabric.Group} thisArg */ _restoreObjectState: function(object) { this._setObjectPosition(object); object.setCoords(); object.hasControls = object.__origHasControls; delete object.__origHasControls; object.set('active', false); object.setCoords(); delete object.group; return this; }, /** * @private */ _setObjectPosition: function(object) { var groupLeft = this.getLeft(), groupTop = this.getTop(), rotated = this._getRotatedLeftTop(object); object.set({ angle: object.getAngle() + this.getAngle(), left: groupLeft + rotated.left, top: groupTop + rotated.top, scaleX: object.get('scaleX') * this.get('scaleX'), scaleY: object.get('scaleY') * this.get('scaleY') }); }, /** * @private */ _getRotatedLeftTop: function(object) { var groupAngle = this.getAngle() * (Math.PI / 180); return { left: (-Math.sin(groupAngle) * object.getTop() * this.get('scaleY') + Math.cos(groupAngle) * object.getLeft() * this.get('scaleX')), top: (Math.cos(groupAngle) * object.getTop() * this.get('scaleY') + Math.sin(groupAngle) * object.getLeft() * this.get('scaleX')) }; }, /** * Destroys a group (restoring state of its objects) * @return {fabric.Group} thisArg * @chainable */ destroy: function() { this._objects.forEach(this._moveFlippedObject, this); return this._restoreObjectsState(); }, /** * Saves coordinates of this instance (to be used together with `hasMoved`) * @saveCoords * @return {fabric.Group} thisArg * @chainable */ saveCoords: function() { this._originalLeft = this.get('left'); this._originalTop = this.get('top'); return this; }, /** * Checks whether this group was moved (since `saveCoords` was called last) * @return {Boolean} true if an object was moved (since fabric.Group#saveCoords was called) */ hasMoved: function() { return this._originalLeft !== this.get('left') || this._originalTop !== this.get('top'); }, /** * Sets coordinates of all group objects * @return {fabric.Group} thisArg * @chainable */ setObjectsCoords: function() { this.forEachObject(function(object) { object.setCoords(); }); return this; }, /** * @private */ _calcBounds: function(onlyWidthHeight) { var aX = [], aY = [], o; for (var i = 0, len = this._objects.length; i < len; ++i) { o = this._objects[i]; o.setCoords(); for (var prop in o.oCoords) { aX.push(o.oCoords[prop].x); aY.push(o.oCoords[prop].y); } } this.set(this._getBounds(aX, aY, onlyWidthHeight)); }, /** * @private */ _getBounds: function(aX, aY, onlyWidthHeight) { var ivt = fabric.util.invertTransform(this.getViewportTransform()), minXY = fabric.util.transformPoint(new fabric.Point(min(aX), min(aY)), ivt), maxXY = fabric.util.transformPoint(new fabric.Point(max(aX), max(aY)), ivt), obj = { width: (maxXY.x - minXY.x) || 0, height: (maxXY.y - minXY.y) || 0 }; if (!onlyWidthHeight) { obj.left = (minXY.x + maxXY.x) / 2 || 0; obj.top = (minXY.y + maxXY.y) / 2 || 0; } return obj; }, /* _TO_SVG_START_ */ /** * Returns svg representation of an instance * @param {Function} [reviver] Method for further parsing of svg representation. * @return {String} svg representation of an instance */ toSVG: function(reviver) { var markup = [ //jscs:disable validateIndentation '\n' //jscs:enable validateIndentation ]; for (var i = 0, len = this._objects.length; i < len; i++) { markup.push(this._objects[i].toSVG(reviver)); } markup.push('\n'); return reviver ? reviver(markup.join('')) : markup.join(''); }, /* _TO_SVG_END_ */ /** * Returns requested property * @param {String} prop Property to get * @return {Any} */ get: function(prop) { if (prop in _lockProperties) { if (this[prop]) { return this[prop]; } else { for (var i = 0, len = this._objects.length; i < len; i++) { if (this._objects[i][prop]) { return true; } } return false; } } else { if (prop in this.delegatedProperties) { return this._objects[0] && this._objects[0].get(prop); } return this[prop]; } } }); /** * Returns {@link fabric.Group} instance from an object representation * @static * @memberOf fabric.Group * @param {Object} object Object to create a group from * @param {Function} [callback] Callback to invoke when an group instance is created * @return {fabric.Group} An instance of fabric.Group */ fabric.Group.fromObject = function(object, callback) { fabric.util.enlivenObjects(object.objects, function(enlivenedObjects) { delete object.objects; callback && callback(new fabric.Group(enlivenedObjects, object)); }); }; /** * Indicates that instances of this type are async * @static * @memberOf fabric.Group * @type Boolean * @default */ fabric.Group.async = true; })(typeof exports !== 'undefined' ? exports : this); (function(global) { 'use strict'; var extend = fabric.util.object.extend; if (!global.fabric) { global.fabric = { }; } if (global.fabric.Image) { fabric.warn('fabric.Image is already defined.'); return; } /** * Image class * @class fabric.Image * @extends fabric.Object * @tutorial {@link http://fabricjs.com/fabric-intro-part-1/#images} * @see {@link fabric.Image#initialize} for constructor definition */ fabric.Image = fabric.util.createClass(fabric.Object, /** @lends fabric.Image.prototype */ { /** * Type of an object * @type String * @default */ type: 'image', /** * crossOrigin value (one of "", "anonymous", "allow-credentials") * @see https://developer.mozilla.org/en-US/docs/HTML/CORS_settings_attributes * @type String * @default */ crossOrigin: '', /** * Constructor * @param {HTMLImageElement | String} element Image element * @param {Object} [options] Options object * @return {fabric.Image} thisArg */ initialize: function(element, options) { options || (options = { }); this.filters = [ ]; this.callSuper('initialize', options); this._initElement(element, options); this._initConfig(options); if (options.filters) { this.filters = options.filters; this.applyFilters(); } }, /** * Returns image element which this instance if based on * @return {HTMLImageElement} Image element */ getElement: function() { return this._element; }, /** * Sets image element for this instance to a specified one. * If filters defined they are applied to new image. * You might need to call `canvas.renderAll` and `object.setCoords` after replacing, to render new image and update controls area. * @param {HTMLImageElement} element * @param {Function} [callback] Callback is invoked when all filters have been applied and new image is generated * @return {fabric.Image} thisArg * @chainable */ setElement: function(element, callback) { this._element = element; this._originalElement = element; this._initConfig(); if (this.filters.length !== 0) { this.applyFilters(callback); } return this; }, /** * Sets crossOrigin value (on an instance and corresponding image element) * @return {fabric.Image} thisArg * @chainable */ setCrossOrigin: function(value) { this.crossOrigin = value; this._element.crossOrigin = value; return this; }, /** * Returns original size of an image * @return {Object} Object with "width" and "height" properties */ getOriginalSize: function() { var element = this.getElement(); return { width: element.width, height: element.height }; }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on */ _stroke: function(ctx) { ctx.save(); this._setStrokeStyles(ctx); ctx.beginPath(); ctx.strokeRect(-this.width / 2, -this.height / 2, this.width, this.height); ctx.closePath(); ctx.restore(); }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on */ _renderDashedStroke: function(ctx) { var x = -this.width / 2, y = -this.height / 2, w = this.width, h = this.height; ctx.save(); this._setStrokeStyles(ctx); ctx.beginPath(); fabric.util.drawDashedLine(ctx, x, y, x + w, y, this.strokeDashArray); fabric.util.drawDashedLine(ctx, x + w, y, x + w, y + h, this.strokeDashArray); fabric.util.drawDashedLine(ctx, x + w, y + h, x, y + h, this.strokeDashArray); fabric.util.drawDashedLine(ctx, x, y + h, x, y, this.strokeDashArray); ctx.closePath(); ctx.restore(); }, /** * Returns object representation of an instance * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output * @return {Object} Object representation of an instance */ toObject: function(propertiesToInclude) { return extend(this.callSuper('toObject', propertiesToInclude), { src: this._originalElement.src || this._originalElement._src, filters: this.filters.map(function(filterObj) { return filterObj && filterObj.toObject(); }), crossOrigin: this.crossOrigin }); }, /* _TO_SVG_START_ */ /** * Returns SVG representation of an instance * @param {Function} [reviver] Method for further parsing of svg representation. * @return {String} svg representation of an instance */ toSVG: function(reviver) { var markup = [], x = -this.width / 2, y = -this.height / 2; if (this.group && this.group.type === 'path-group') { x = this.left; y = this.top; } markup.push( '\n', '\n' ); if (this.stroke || this.strokeDashArray) { var origFill = this.fill; this.fill = null; markup.push( '\n' ); this.fill = origFill; } markup.push('\n'); return reviver ? reviver(markup.join('')) : markup.join(''); }, /* _TO_SVG_END_ */ /** * Returns source of an image * @return {String} Source of an image */ getSrc: function() { if (this.getElement()) { return this.getElement().src || this.getElement()._src; } }, /** * Returns string representation of an instance * @return {String} String representation of an instance */ toString: function() { return '#'; }, /** * Returns a clone of an instance * @param {Function} callback Callback is invoked with a clone as a first argument * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output */ clone: function(callback, propertiesToInclude) { this.constructor.fromObject(this.toObject(propertiesToInclude), callback); }, /** * Applies filters assigned to this image (from "filters" array) * @mthod applyFilters * @param {Function} callback Callback is invoked when all filters have been applied and new image is generated * @return {fabric.Image} thisArg * @chainable */ applyFilters: function(callback) { if (!this._originalElement) { return; } if (this.filters.length === 0) { this._element = this._originalElement; callback && callback(); return; } var imgEl = this._originalElement, canvasEl = fabric.util.createCanvasElement(), replacement = fabric.util.createImage(), _this = this; canvasEl.width = imgEl.width; canvasEl.height = imgEl.height; canvasEl.getContext('2d').drawImage(imgEl, 0, 0, imgEl.width, imgEl.height); this.filters.forEach(function(filter) { filter && filter.applyTo(canvasEl); }); /** @ignore */ replacement.width = imgEl.width; replacement.height = imgEl.height; if (fabric.isLikelyNode) { replacement.src = canvasEl.toBuffer(undefined, fabric.Image.pngCompression); // onload doesn't fire in some node versions, so we invoke callback manually _this._element = replacement; callback && callback(); } else { replacement.onload = function() { _this._element = replacement; callback && callback(); replacement.onload = canvasEl = imgEl = null; }; replacement.src = canvasEl.toDataURL('image/png'); } return this; }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on */ _render: function(ctx, noTransform) { this._element && ctx.drawImage( this._element, noTransform ? this.left : -this.width/2, noTransform ? this.top : -this.height/2, this.width, this.height ); this._renderStroke(ctx); }, /** * @private */ _resetWidthHeight: function() { var element = this.getElement(); this.set('width', element.width); this.set('height', element.height); }, /** * The Image class's initialization method. This method is automatically * called by the constructor. * @private * @param {HTMLImageElement|String} element The element representing the image */ _initElement: function(element) { this.setElement(fabric.util.getById(element)); fabric.util.addClass(this.getElement(), fabric.Image.CSS_CANVAS); }, /** * @private * @param {Object} [options] Options object */ _initConfig: function(options) { options || (options = { }); this.setOptions(options); this._setWidthHeight(options); if (this._element && this.crossOrigin) { this._element.crossOrigin = this.crossOrigin; } }, /** * @private * @param {Object} object Object with filters property * @param {Function} callback Callback to invoke when all fabric.Image.filters instances are created */ _initFilters: function(object, callback) { if (object.filters && object.filters.length) { fabric.util.enlivenObjects(object.filters, function(enlivenedObjects) { callback && callback(enlivenedObjects); }, 'fabric.Image.filters'); } else { callback && callback(); } }, /** * @private * @param {Object} [options] Object with width/height properties */ _setWidthHeight: function(options) { this.width = 'width' in options ? options.width : (this.getElement() ? this.getElement().width || 0 : 0); this.height = 'height' in options ? options.height : (this.getElement() ? this.getElement().height || 0 : 0); }, /** * Returns complexity of an instance * @return {Number} complexity of this instance */ complexity: function() { return 1; } }); /** * Default CSS class name for canvas * @static * @type String * @default */ fabric.Image.CSS_CANVAS = 'canvas-img'; /** * Alias for getSrc * @static */ fabric.Image.prototype.getSvgSrc = fabric.Image.prototype.getSrc; /** * Creates an instance of fabric.Image from its object representation * @static * @param {Object} object Object to create an instance from * @param {Function} [callback] Callback to invoke when an image instance is created */ fabric.Image.fromObject = function(object, callback) { fabric.util.loadImage(object.src, function(img) { fabric.Image.prototype._initFilters.call(object, object, function(filters) { object.filters = filters || [ ]; var instance = new fabric.Image(img, object); callback && callback(instance); }); }, null, object.crossOrigin); }; /** * Creates an instance of fabric.Image from an URL string * @static * @param {String} url URL to create an image from * @param {Function} [callback] Callback to invoke when image is created (newly created image is passed as a first argument) * @param {Object} [imgOptions] Options object */ fabric.Image.fromURL = function(url, callback, imgOptions) { fabric.util.loadImage(url, function(img) { callback(new fabric.Image(img, imgOptions)); }, null, imgOptions && imgOptions.crossOrigin); }; /* _FROM_SVG_START_ */ /** * List of attribute names to account for when parsing SVG element (used by {@link fabric.Image.fromElement}) * @static * @see {@link http://www.w3.org/TR/SVG/struct.html#ImageElement} */ fabric.Image.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat('x y width height xlink:href'.split(' ')); /** * Returns {@link fabric.Image} instance from an SVG element * @static * @param {SVGElement} element Element to parse * @param {Function} callback Callback to execute when fabric.Image object is created * @param {Object} [options] Options object * @return {fabric.Image} Instance of fabric.Image */ fabric.Image.fromElement = function(element, callback, options) { var parsedAttributes = fabric.parseAttributes(element, fabric.Image.ATTRIBUTE_NAMES); fabric.Image.fromURL(parsedAttributes['xlink:href'], callback, extend((options ? fabric.util.object.clone(options) : { }), parsedAttributes)); }; /* _FROM_SVG_END_ */ /** * Indicates that instances of this type are async * @static * @type Boolean * @default */ fabric.Image.async = true; /** * Indicates compression level used when generating PNG under Node (in applyFilters). Any of 0-9 * @static * @type Number * @default */ fabric.Image.pngCompression = 1; })(typeof exports !== 'undefined' ? exports : this); fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { /** * @private * @return {Number} angle value */ _getAngleValueForStraighten: function() { var angle = this.getAngle() % 360; if (angle > 0) { return Math.round((angle - 1) / 90) * 90; } return Math.round(angle / 90) * 90; }, /** * Straightens an object (rotating it from current angle to one of 0, 90, 180, 270, etc. depending on which is closer) * @return {fabric.Object} thisArg * @chainable */ straighten: function() { this.setAngle(this._getAngleValueForStraighten()); return this; }, /** * Same as {@link fabric.Object.prototype.straighten} but with animation * @param {Object} callbacks Object with callback functions * @param {Function} [callbacks.onComplete] Invoked on completion * @param {Function} [callbacks.onChange] Invoked on every step of animation * @return {fabric.Object} thisArg * @chainable */ fxStraighten: function(callbacks) { callbacks = callbacks || { }; var empty = function() { }, onComplete = callbacks.onComplete || empty, onChange = callbacks.onChange || empty, _this = this; fabric.util.animate({ startValue: this.get('angle'), endValue: this._getAngleValueForStraighten(), duration: this.FX_DURATION, onChange: function(value) { _this.setAngle(value); onChange(); }, onComplete: function() { _this.setCoords(); onComplete(); }, onStart: function() { _this.set('active', false); } }); return this; } }); fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.StaticCanvas.prototype */ { /** * Straightens object, then rerenders canvas * @param {fabric.Object} object Object to straighten * @return {fabric.Canvas} thisArg * @chainable */ straightenObject: function (object) { object.straighten(); this.renderAll(); return this; }, /** * Same as {@link fabric.Canvas.prototype.straightenObject}, but animated * @param {fabric.Object} object Object to straighten * @return {fabric.Canvas} thisArg * @chainable */ fxStraightenObject: function (object) { object.fxStraighten({ onChange: this.renderAll.bind(this) }); return this; } }); /** * @namespace fabric.Image.filters * @memberOf fabric.Image * @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#image_filters} * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} */ fabric.Image.filters = fabric.Image.filters || { }; /** * Root filter class from which all filter classes inherit from * @class fabric.Image.filters.BaseFilter * @memberOf fabric.Image.filters */ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Image.filters.BaseFilter.prototype */ { /** * Filter type * @param {String} type * @default */ type: 'BaseFilter', /** * Returns object representation of an instance * @return {Object} Object representation of an instance */ toObject: function() { return { type: this.type }; }, /** * Returns a JSON representation of an instance * @return {Object} JSON */ toJSON: function() { // delegate, not alias return this.toObject(); } }); (function(global) { 'use strict'; var fabric = global.fabric || (global.fabric = { }), extend = fabric.util.object.extend; /** * Brightness filter class * @class fabric.Image.filters.Brightness * @memberOf fabric.Image.filters * @extends fabric.Image.filters.BaseFilter * @see {@link fabric.Image.filters.Brightness#initialize} for constructor definition * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} * @example * var filter = new fabric.Image.filters.Brightness({ * brightness: 200 * }); * object.filters.push(filter); * object.applyFilters(canvas.renderAll.bind(canvas)); */ fabric.Image.filters.Brightness = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Brightness.prototype */ { /** * Filter type * @param {String} type * @default */ type: 'Brightness', /** * Constructor * @memberOf fabric.Image.filters.Brightness.prototype * @param {Object} [options] Options object * @param {Number} [options.brightness=0] Value to brighten the image up (0..255) */ initialize: function(options) { options = options || { }; this.brightness = options.brightness || 0; }, /** * Applies filter to canvas element * @param {Object} canvasEl Canvas element to apply filter to */ applyTo: function(canvasEl) { var context = canvasEl.getContext('2d'), imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), data = imageData.data, brightness = this.brightness; for (var i = 0, len = data.length; i < len; i += 4) { data[i] += brightness; data[i + 1] += brightness; data[i + 2] += brightness; } context.putImageData(imageData, 0, 0); }, /** * Returns object representation of an instance * @return {Object} Object representation of an instance */ toObject: function() { return extend(this.callSuper('toObject'), { brightness: this.brightness }); } }); /** * Returns filter instance from an object representation * @static * @param {Object} object Object to create an instance from * @return {fabric.Image.filters.Brightness} Instance of fabric.Image.filters.Brightness */ fabric.Image.filters.Brightness.fromObject = function(object) { return new fabric.Image.filters.Brightness(object); }; })(typeof exports !== 'undefined' ? exports : this); (function(global) { 'use strict'; var fabric = global.fabric || (global.fabric = { }), extend = fabric.util.object.extend; /** * Adapted from html5rocks article * @class fabric.Image.filters.Convolute * @memberOf fabric.Image.filters * @extends fabric.Image.filters.BaseFilter * @see {@link fabric.Image.filters.Convolute#initialize} for constructor definition * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} * @example Sharpen filter * var filter = new fabric.Image.filters.Convolute({ * matrix: [ 0, -1, 0, * -1, 5, -1, * 0, -1, 0 ] * }); * object.filters.push(filter); * object.applyFilters(canvas.renderAll.bind(canvas)); * @example Blur filter * var filter = new fabric.Image.filters.Convolute({ * matrix: [ 1/9, 1/9, 1/9, * 1/9, 1/9, 1/9, * 1/9, 1/9, 1/9 ] * }); * object.filters.push(filter); * object.applyFilters(canvas.renderAll.bind(canvas)); * @example Emboss filter * var filter = new fabric.Image.filters.Convolute({ * matrix: [ 1, 1, 1, * 1, 0.7, -1, * -1, -1, -1 ] * }); * object.filters.push(filter); * object.applyFilters(canvas.renderAll.bind(canvas)); * @example Emboss filter with opaqueness * var filter = new fabric.Image.filters.Convolute({ * opaque: true, * matrix: [ 1, 1, 1, * 1, 0.7, -1, * -1, -1, -1 ] * }); * object.filters.push(filter); * object.applyFilters(canvas.renderAll.bind(canvas)); */ fabric.Image.filters.Convolute = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Convolute.prototype */ { /** * Filter type * @param {String} type * @default */ type: 'Convolute', /** * Constructor * @memberOf fabric.Image.filters.Convolute.prototype * @param {Object} [options] Options object * @param {Boolean} [options.opaque=false] Opaque value (true/false) * @param {Array} [options.matrix] Filter matrix */ initialize: function(options) { options = options || { }; this.opaque = options.opaque; this.matrix = options.matrix || [ 0, 0, 0, 0, 1, 0, 0, 0, 0 ]; var canvasEl = fabric.util.createCanvasElement(); this.tmpCtx = canvasEl.getContext('2d'); }, /** * @private */ _createImageData: function(w, h) { return this.tmpCtx.createImageData(w, h); }, /** * Applies filter to canvas element * @param {Object} canvasEl Canvas element to apply filter to */ applyTo: function(canvasEl) { var weights = this.matrix, context = canvasEl.getContext('2d'), pixels = context.getImageData(0, 0, canvasEl.width, canvasEl.height), side = Math.round(Math.sqrt(weights.length)), halfSide = Math.floor(side/2), src = pixels.data, sw = pixels.width, sh = pixels.height, // pad output by the convolution matrix w = sw, h = sh, output = this._createImageData(w, h), dst = output.data, // go through the destination image pixels alphaFac = this.opaque ? 1 : 0; for (var y = 0; y < h; y++) { for (var x = 0; x < w; x++) { var sy = y, sx = x, dstOff = (y * w + x) * 4, // calculate the weighed sum of the source image pixels that // fall under the convolution matrix r = 0, g = 0, b = 0, a = 0; for (var cy = 0; cy < side; cy++) { for (var cx = 0; cx < side; cx++) { var scy = sy + cy - halfSide, scx = sx + cx - halfSide; /* jshint maxdepth:5 */ if (scy < 0 || scy > sh || scx < 0 || scx > sw) { continue; } var srcOff = (scy * sw + scx) * 4, wt = weights[cy * side + cx]; r += src[srcOff] * wt; g += src[srcOff + 1] * wt; b += src[srcOff + 2] * wt; a += src[srcOff + 3] * wt; } } dst[dstOff] = r; dst[dstOff + 1] = g; dst[dstOff + 2] = b; dst[dstOff + 3] = a + alphaFac * (255 - a); } } context.putImageData(output, 0, 0); }, /** * Returns object representation of an instance * @return {Object} Object representation of an instance */ toObject: function() { return extend(this.callSuper('toObject'), { opaque: this.opaque, matrix: this.matrix }); } }); /** * Returns filter instance from an object representation * @static * @param {Object} object Object to create an instance from * @return {fabric.Image.filters.Convolute} Instance of fabric.Image.filters.Convolute */ fabric.Image.filters.Convolute.fromObject = function(object) { return new fabric.Image.filters.Convolute(object); }; })(typeof exports !== 'undefined' ? exports : this); (function(global) { 'use strict'; var fabric = global.fabric || (global.fabric = { }), extend = fabric.util.object.extend; /** * GradientTransparency filter class * @class fabric.Image.filters.GradientTransparency * @memberOf fabric.Image.filters * @extends fabric.Image.filters.BaseFilter * @see {@link fabric.Image.filters.GradientTransparency#initialize} for constructor definition * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} * @example * var filter = new fabric.Image.filters.GradientTransparency({ * threshold: 200 * }); * object.filters.push(filter); * object.applyFilters(canvas.renderAll.bind(canvas)); */ fabric.Image.filters.GradientTransparency = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.GradientTransparency.prototype */ { /** * Filter type * @param {String} type * @default */ type: 'GradientTransparency', /** * Constructor * @memberOf fabric.Image.filters.GradientTransparency.prototype * @param {Object} [options] Options object * @param {Number} [options.threshold=100] Threshold value */ initialize: function(options) { options = options || { }; this.threshold = options.threshold || 100; }, /** * Applies filter to canvas element * @param {Object} canvasEl Canvas element to apply filter to */ applyTo: function(canvasEl) { var context = canvasEl.getContext('2d'), imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), data = imageData.data, threshold = this.threshold, total = data.length; for (var i = 0, len = data.length; i < len; i += 4) { data[i + 3] = threshold + 255 * (total - i) / total; } context.putImageData(imageData, 0, 0); }, /** * Returns object representation of an instance * @return {Object} Object representation of an instance */ toObject: function() { return extend(this.callSuper('toObject'), { threshold: this.threshold }); } }); /** * Returns filter instance from an object representation * @static * @param {Object} object Object to create an instance from * @return {fabric.Image.filters.GradientTransparency} Instance of fabric.Image.filters.GradientTransparency */ fabric.Image.filters.GradientTransparency.fromObject = function(object) { return new fabric.Image.filters.GradientTransparency(object); }; })(typeof exports !== 'undefined' ? exports : this); (function(global) { 'use strict'; var fabric = global.fabric || (global.fabric = { }); /** * Grayscale image filter class * @class fabric.Image.filters.Grayscale * @memberOf fabric.Image.filters * @extends fabric.Image.filters.BaseFilter * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} * @example * var filter = new fabric.Image.filters.Grayscale(); * object.filters.push(filter); * object.applyFilters(canvas.renderAll.bind(canvas)); */ fabric.Image.filters.Grayscale = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Grayscale.prototype */ { /** * Filter type * @param {String} type * @default */ type: 'Grayscale', /** * Applies filter to canvas element * @memberOf fabric.Image.filters.Grayscale.prototype * @param {Object} canvasEl Canvas element to apply filter to */ applyTo: function(canvasEl) { var context = canvasEl.getContext('2d'), imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), data = imageData.data, len = imageData.width * imageData.height * 4, index = 0, average; while (index < len) { average = (data[index] + data[index + 1] + data[index + 2]) / 3; data[index] = average; data[index + 1] = average; data[index + 2] = average; index += 4; } context.putImageData(imageData, 0, 0); } }); /** * Returns filter instance from an object representation * @static * @return {fabric.Image.filters.Grayscale} Instance of fabric.Image.filters.Grayscale */ fabric.Image.filters.Grayscale.fromObject = function() { return new fabric.Image.filters.Grayscale(); }; })(typeof exports !== 'undefined' ? exports : this); (function(global) { 'use strict'; var fabric = global.fabric || (global.fabric = { }); /** * Invert filter class * @class fabric.Image.filters.Invert * @memberOf fabric.Image.filters * @extends fabric.Image.filters.BaseFilter * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} * @example * var filter = new fabric.Image.filters.Invert(); * object.filters.push(filter); * object.applyFilters(canvas.renderAll.bind(canvas)); */ fabric.Image.filters.Invert = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Invert.prototype */ { /** * Filter type * @param {String} type * @default */ type: 'Invert', /** * Applies filter to canvas element * @memberOf fabric.Image.filters.Invert.prototype * @param {Object} canvasEl Canvas element to apply filter to */ applyTo: function(canvasEl) { var context = canvasEl.getContext('2d'), imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), data = imageData.data, iLen = data.length, i; for (i = 0; i < iLen; i+=4) { data[i] = 255 - data[i]; data[i + 1] = 255 - data[i + 1]; data[i + 2] = 255 - data[i + 2]; } context.putImageData(imageData, 0, 0); } }); /** * Returns filter instance from an object representation * @static * @return {fabric.Image.filters.Invert} Instance of fabric.Image.filters.Invert */ fabric.Image.filters.Invert.fromObject = function() { return new fabric.Image.filters.Invert(); }; })(typeof exports !== 'undefined' ? exports : this); (function(global) { 'use strict'; var fabric = global.fabric || (global.fabric = { }), extend = fabric.util.object.extend; /** * Mask filter class * See http://resources.aleph-1.com/mask/ * @class fabric.Image.filters.Mask * @memberOf fabric.Image.filters * @extends fabric.Image.filters.BaseFilter * @see {@link fabric.Image.filters.Mask#initialize} for constructor definition */ fabric.Image.filters.Mask = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Mask.prototype */ { /** * Filter type * @param {String} type * @default */ type: 'Mask', /** * Constructor * @memberOf fabric.Image.filters.Mask.prototype * @param {Object} [options] Options object * @param {fabric.Image} [options.mask] Mask image object * @param {Number} [options.channel=0] Rgb channel (0, 1, 2 or 3) */ initialize: function(options) { options = options || { }; this.mask = options.mask; this.channel = [ 0, 1, 2, 3 ].indexOf(options.channel) > -1 ? options.channel : 0; }, /** * Applies filter to canvas element * @param {Object} canvasEl Canvas element to apply filter to */ applyTo: function(canvasEl) { if (!this.mask) { return; } var context = canvasEl.getContext('2d'), imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), data = imageData.data, maskEl = this.mask.getElement(), maskCanvasEl = fabric.util.createCanvasElement(), channel = this.channel, i, iLen = imageData.width * imageData.height * 4; maskCanvasEl.width = maskEl.width; maskCanvasEl.height = maskEl.height; maskCanvasEl.getContext('2d').drawImage(maskEl, 0, 0, maskEl.width, maskEl.height); var maskImageData = maskCanvasEl.getContext('2d').getImageData(0, 0, maskEl.width, maskEl.height), maskData = maskImageData.data; for (i = 0; i < iLen; i += 4) { data[i + 3] = maskData[i + channel]; } context.putImageData(imageData, 0, 0); }, /** * Returns object representation of an instance * @return {Object} Object representation of an instance */ toObject: function() { return extend(this.callSuper('toObject'), { mask: this.mask.toObject(), channel: this.channel }); } }); /** * Returns filter instance from an object representation * @static * @param {Object} object Object to create an instance from * @param {Function} [callback] Callback to invoke when a mask filter instance is created */ fabric.Image.filters.Mask.fromObject = function(object, callback) { fabric.util.loadImage(object.mask.src, function(img) { object.mask = new fabric.Image(img, object.mask); callback && callback(new fabric.Image.filters.Mask(object)); }); }; /** * Indicates that instances of this type are async * @static * @type Boolean * @default */ fabric.Image.filters.Mask.async = true; })(typeof exports !== 'undefined' ? exports : this); (function(global) { 'use strict'; var fabric = global.fabric || (global.fabric = { }), extend = fabric.util.object.extend; /** * Noise filter class * @class fabric.Image.filters.Noise * @memberOf fabric.Image.filters * @extends fabric.Image.filters.BaseFilter * @see {@link fabric.Image.filters.Noise#initialize} for constructor definition * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} * @example * var filter = new fabric.Image.filters.Noise({ * noise: 700 * }); * object.filters.push(filter); * object.applyFilters(canvas.renderAll.bind(canvas)); */ fabric.Image.filters.Noise = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Noise.prototype */ { /** * Filter type * @param {String} type * @default */ type: 'Noise', /** * Constructor * @memberOf fabric.Image.filters.Noise.prototype * @param {Object} [options] Options object * @param {Number} [options.noise=0] Noise value */ initialize: function(options) { options = options || { }; this.noise = options.noise || 0; }, /** * Applies filter to canvas element * @param {Object} canvasEl Canvas element to apply filter to */ applyTo: function(canvasEl) { var context = canvasEl.getContext('2d'), imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), data = imageData.data, noise = this.noise, rand; for (var i = 0, len = data.length; i < len; i += 4) { rand = (0.5 - Math.random()) * noise; data[i] += rand; data[i + 1] += rand; data[i + 2] += rand; } context.putImageData(imageData, 0, 0); }, /** * Returns object representation of an instance * @return {Object} Object representation of an instance */ toObject: function() { return extend(this.callSuper('toObject'), { noise: this.noise }); } }); /** * Returns filter instance from an object representation * @static * @param {Object} object Object to create an instance from * @return {fabric.Image.filters.Noise} Instance of fabric.Image.filters.Noise */ fabric.Image.filters.Noise.fromObject = function(object) { return new fabric.Image.filters.Noise(object); }; })(typeof exports !== 'undefined' ? exports : this); (function(global) { 'use strict'; var fabric = global.fabric || (global.fabric = { }), extend = fabric.util.object.extend; /** * Pixelate filter class * @class fabric.Image.filters.Pixelate * @memberOf fabric.Image.filters * @extends fabric.Image.filters.BaseFilter * @see {@link fabric.Image.filters.Pixelate#initialize} for constructor definition * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} * @example * var filter = new fabric.Image.filters.Pixelate({ * blocksize: 8 * }); * object.filters.push(filter); * object.applyFilters(canvas.renderAll.bind(canvas)); */ fabric.Image.filters.Pixelate = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Pixelate.prototype */ { /** * Filter type * @param {String} type * @default */ type: 'Pixelate', /** * Constructor * @memberOf fabric.Image.filters.Pixelate.prototype * @param {Object} [options] Options object * @param {Number} [options.blocksize=4] Blocksize for pixelate */ initialize: function(options) { options = options || { }; this.blocksize = options.blocksize || 4; }, /** * Applies filter to canvas element * @param {Object} canvasEl Canvas element to apply filter to */ applyTo: function(canvasEl) { var context = canvasEl.getContext('2d'), imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), data = imageData.data, iLen = imageData.height, jLen = imageData.width, index, i, j, r, g, b, a; for (i = 0; i < iLen; i += this.blocksize) { for (j = 0; j < jLen; j += this.blocksize) { index = (i * 4) * jLen + (j * 4); r = data[index]; g = data[index + 1]; b = data[index + 2]; a = data[index + 3]; /* blocksize: 4 [1,x,x,x,1] [x,x,x,x,1] [x,x,x,x,1] [x,x,x,x,1] [1,1,1,1,1] */ for (var _i = i, _ilen = i + this.blocksize; _i < _ilen; _i++) { for (var _j = j, _jlen = j + this.blocksize; _j < _jlen; _j++) { index = (_i * 4) * jLen + (_j * 4); data[index] = r; data[index + 1] = g; data[index + 2] = b; data[index + 3] = a; } } } } context.putImageData(imageData, 0, 0); }, /** * Returns object representation of an instance * @return {Object} Object representation of an instance */ toObject: function() { return extend(this.callSuper('toObject'), { blocksize: this.blocksize }); } }); /** * Returns filter instance from an object representation * @static * @param {Object} object Object to create an instance from * @return {fabric.Image.filters.Pixelate} Instance of fabric.Image.filters.Pixelate */ fabric.Image.filters.Pixelate.fromObject = function(object) { return new fabric.Image.filters.Pixelate(object); }; })(typeof exports !== 'undefined' ? exports : this); (function(global) { 'use strict'; var fabric = global.fabric || (global.fabric = { }), extend = fabric.util.object.extend; /** * Remove white filter class * @class fabric.Image.filters.RemoveWhite * @memberOf fabric.Image.filters * @extends fabric.Image.filters.BaseFilter * @see {@link fabric.Image.filters.RemoveWhite#initialize} for constructor definition * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} * @example * var filter = new fabric.Image.filters.RemoveWhite({ * threshold: 40, * distance: 140 * }); * object.filters.push(filter); * object.applyFilters(canvas.renderAll.bind(canvas)); */ fabric.Image.filters.RemoveWhite = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.RemoveWhite.prototype */ { /** * Filter type * @param {String} type * @default */ type: 'RemoveWhite', /** * Constructor * @memberOf fabric.Image.filters.RemoveWhite.prototype * @param {Object} [options] Options object * @param {Number} [options.threshold=30] Threshold value * @param {Number} [options.distance=20] Distance value */ initialize: function(options) { options = options || { }; this.threshold = options.threshold || 30; this.distance = options.distance || 20; }, /** * Applies filter to canvas element * @param {Object} canvasEl Canvas element to apply filter to */ applyTo: function(canvasEl) { var context = canvasEl.getContext('2d'), imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), data = imageData.data, threshold = this.threshold, distance = this.distance, limit = 255 - threshold, abs = Math.abs, r, g, b; for (var i = 0, len = data.length; i < len; i += 4) { r = data[i]; g = data[i + 1]; b = data[i + 2]; if (r > limit && g > limit && b > limit && abs(r - g) < distance && abs(r - b) < distance && abs(g - b) < distance ) { data[i + 3] = 1; } } context.putImageData(imageData, 0, 0); }, /** * Returns object representation of an instance * @return {Object} Object representation of an instance */ toObject: function() { return extend(this.callSuper('toObject'), { threshold: this.threshold, distance: this.distance }); } }); /** * Returns filter instance from an object representation * @static * @param {Object} object Object to create an instance from * @return {fabric.Image.filters.RemoveWhite} Instance of fabric.Image.filters.RemoveWhite */ fabric.Image.filters.RemoveWhite.fromObject = function(object) { return new fabric.Image.filters.RemoveWhite(object); }; })(typeof exports !== 'undefined' ? exports : this); (function(global) { 'use strict'; var fabric = global.fabric || (global.fabric = { }); /** * Sepia filter class * @class fabric.Image.filters.Sepia * @memberOf fabric.Image.filters * @extends fabric.Image.filters.BaseFilter * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} * @example * var filter = new fabric.Image.filters.Sepia(); * object.filters.push(filter); * object.applyFilters(canvas.renderAll.bind(canvas)); */ fabric.Image.filters.Sepia = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Sepia.prototype */ { /** * Filter type * @param {String} type * @default */ type: 'Sepia', /** * Applies filter to canvas element * @memberOf fabric.Image.filters.Sepia.prototype * @param {Object} canvasEl Canvas element to apply filter to */ applyTo: function(canvasEl) { var context = canvasEl.getContext('2d'), imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), data = imageData.data, iLen = data.length, i, avg; for (i = 0; i < iLen; i+=4) { avg = 0.3 * data[i] + 0.59 * data[i + 1] + 0.11 * data[i + 2]; data[i] = avg + 100; data[i + 1] = avg + 50; data[i + 2] = avg + 255; } context.putImageData(imageData, 0, 0); } }); /** * Returns filter instance from an object representation * @static * @return {fabric.Image.filters.Sepia} Instance of fabric.Image.filters.Sepia */ fabric.Image.filters.Sepia.fromObject = function() { return new fabric.Image.filters.Sepia(); }; })(typeof exports !== 'undefined' ? exports : this); (function(global) { 'use strict'; var fabric = global.fabric || (global.fabric = { }); /** * Sepia2 filter class * @class fabric.Image.filters.Sepia2 * @memberOf fabric.Image.filters * @extends fabric.Image.filters.BaseFilter * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} * @example * var filter = new fabric.Image.filters.Sepia2(); * object.filters.push(filter); * object.applyFilters(canvas.renderAll.bind(canvas)); */ fabric.Image.filters.Sepia2 = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Sepia2.prototype */ { /** * Filter type * @param {String} type * @default */ type: 'Sepia2', /** * Applies filter to canvas element * @memberOf fabric.Image.filters.Sepia.prototype * @param {Object} canvasEl Canvas element to apply filter to */ applyTo: function(canvasEl) { var context = canvasEl.getContext('2d'), imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), data = imageData.data, iLen = data.length, i, r, g, b; for (i = 0; i < iLen; i+=4) { r = data[i]; g = data[i + 1]; b = data[i + 2]; data[i] = (r * 0.393 + g * 0.769 + b * 0.189 ) / 1.351; data[i + 1] = (r * 0.349 + g * 0.686 + b * 0.168 ) / 1.203; data[i + 2] = (r * 0.272 + g * 0.534 + b * 0.131 ) / 2.140; } context.putImageData(imageData, 0, 0); } }); /** * Returns filter instance from an object representation * @static * @return {fabric.Image.filters.Sepia2} Instance of fabric.Image.filters.Sepia2 */ fabric.Image.filters.Sepia2.fromObject = function() { return new fabric.Image.filters.Sepia2(); }; })(typeof exports !== 'undefined' ? exports : this); (function(global) { 'use strict'; var fabric = global.fabric || (global.fabric = { }), extend = fabric.util.object.extend; /** * Tint filter class * Adapted from https://github.com/mezzoblue/PaintbrushJS * @class fabric.Image.filters.Tint * @memberOf fabric.Image.filters * @extends fabric.Image.filters.BaseFilter * @see {@link fabric.Image.filters.Tint#initialize} for constructor definition * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} * @example Tint filter with hex color and opacity * var filter = new fabric.Image.filters.Tint({ * color: '#3513B0', * opacity: 0.5 * }); * object.filters.push(filter); * object.applyFilters(canvas.renderAll.bind(canvas)); * @example Tint filter with rgba color * var filter = new fabric.Image.filters.Tint({ * color: 'rgba(53, 21, 176, 0.5)' * }); * object.filters.push(filter); * object.applyFilters(canvas.renderAll.bind(canvas)); */ fabric.Image.filters.Tint = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Tint.prototype */ { /** * Filter type * @param {String} type * @default */ type: 'Tint', /** * Constructor * @memberOf fabric.Image.filters.Tint.prototype * @param {Object} [options] Options object * @param {String} [options.color=#000000] Color to tint the image with * @param {Number} [options.opacity] Opacity value that controls the tint effect's transparency (0..1) */ initialize: function(options) { options = options || { }; this.color = options.color || '#000000'; this.opacity = typeof options.opacity !== 'undefined' ? options.opacity : new fabric.Color(this.color).getAlpha(); }, /** * Applies filter to canvas element * @param {Object} canvasEl Canvas element to apply filter to */ applyTo: function(canvasEl) { var context = canvasEl.getContext('2d'), imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), data = imageData.data, iLen = data.length, i, tintR, tintG, tintB, r, g, b, alpha1, source; source = new fabric.Color(this.color).getSource(); tintR = source[0] * this.opacity; tintG = source[1] * this.opacity; tintB = source[2] * this.opacity; alpha1 = 1 - this.opacity; for (i = 0; i < iLen; i+=4) { r = data[i]; g = data[i + 1]; b = data[i + 2]; // alpha compositing data[i] = tintR + r * alpha1; data[i + 1] = tintG + g * alpha1; data[i + 2] = tintB + b * alpha1; } context.putImageData(imageData, 0, 0); }, /** * Returns object representation of an instance * @return {Object} Object representation of an instance */ toObject: function() { return extend(this.callSuper('toObject'), { color: this.color, opacity: this.opacity }); } }); /** * Returns filter instance from an object representation * @static * @param {Object} object Object to create an instance from * @return {fabric.Image.filters.Tint} Instance of fabric.Image.filters.Tint */ fabric.Image.filters.Tint.fromObject = function(object) { return new fabric.Image.filters.Tint(object); }; })(typeof exports !== 'undefined' ? exports : this); (function(global) { 'use strict'; var fabric = global.fabric || (global.fabric = { }), extend = fabric.util.object.extend; /** * Multiply filter class * Adapted from http://www.laurenscorijn.com/articles/colormath-basics * @class fabric.Image.filters.Multiply * @memberOf fabric.Image.filters * @extends fabric.Image.filters.BaseFilter * @example Multiply filter with hex color * var filter = new fabric.Image.filters.Multiply({ * color: '#F0F' * }); * object.filters.push(filter); * object.applyFilters(canvas.renderAll.bind(canvas)); * @example Multiply filter with rgb color * var filter = new fabric.Image.filters.Multiply({ * color: 'rgb(53, 21, 176)' * }); * object.filters.push(filter); * object.applyFilters(canvas.renderAll.bind(canvas)); */ fabric.Image.filters.Multiply = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Multiply.prototype */ { /** * Filter type * @param {String} type * @default */ type: 'Multiply', /** * Constructor * @memberOf fabric.Image.filters.Multiply.prototype * @param {Object} [options] Options object * @param {String} [options.color=#000000] Color to multiply the image pixels with */ initialize: function(options) { options = options || { }; this.color = options.color || '#000000'; }, /** * Applies filter to canvas element * @param {Object} canvasEl Canvas element to apply filter to */ applyTo: function(canvasEl) { var context = canvasEl.getContext('2d'), imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), data = imageData.data, iLen = data.length, i, source; source = new fabric.Color(this.color).getSource(); for (i = 0; i < iLen; i+=4) { data[i] *= source[0] / 255; data[i + 1] *= source[1] / 255; data[i + 2] *= source[2] / 255; } context.putImageData(imageData, 0, 0); }, /** * Returns object representation of an instance * @return {Object} Object representation of an instance */ toObject: function() { return extend(this.callSuper('toObject'), { color: this.color }); } }); /** * Returns filter instance from an object representation * @static * @param {Object} object Object to create an instance from * @return {fabric.Image.filters.Multiply} Instance of fabric.Image.filters.Multiply */ fabric.Image.filters.Multiply.fromObject = function(object) { return new fabric.Image.filters.Multiply(object); }; })(typeof exports !== 'undefined' ? exports : this); (function(global) { 'use strict'; var fabric = global.fabric; /** * Color Blend filter class * @class fabric.Image.filter.Blend * @memberOf fabric.Image.filters * @extends fabric.Image.filters.BaseFilter * @example * var filter = new fabric.Image.filters.Blend({ * color: '#000', * mode: 'multiply' * }); * * var filter = new fabric.Image.filters.Blend({ * image: fabricImageObject, * mode: 'multiply', * alpha: 0.5 * }); * object.filters.push(filter); * object.applyFilters(canvas.renderAll.bind(canvas)); */ fabric.Image.filters.Blend = fabric.util.createClass({ type: 'Blend', initialize: function(options) { options = options || {}; this.color = options.color || '#000'; this.image = options.image || false; this.mode = options.mode || 'multiply'; this.alpha = options.alpha || 1; }, applyTo: function(canvasEl) { var context = canvasEl.getContext('2d'), imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), data = imageData.data, tr, tg, tb, r, g, b, source, isImage = false; if (this.image) { // Blend images isImage = true; var _el = fabric.util.createCanvasElement(); _el.width = this.image.width; _el.height = this.image.height; var tmpCanvas = new fabric.StaticCanvas(_el); tmpCanvas.add(this.image); var context2 = tmpCanvas.getContext('2d'); source = context2.getImageData(0, 0, tmpCanvas.width, tmpCanvas.height).data; } else { // Blend color source = new fabric.Color(this.color).getSource(); tr = source[0] * this.alpha; tg = source[1] * this.alpha; tb = source[2] * this.alpha; } for (var i = 0, len = data.length; i < len; i += 4) { r = data[i]; g = data[i + 1]; b = data[i + 2]; if (isImage) { tr = source[i] * this.alpha; tg = source[i + 1] * this.alpha; tb = source[i + 2] * this.alpha; } switch (this.mode) { case 'multiply': data[i] = r * tr / 255; data[i + 1] = g * tg / 255; data[i + 2] = b * tb / 255; break; case 'screen': data[i] = 1 - (1 - r) * (1 - tr); data[i + 1] = 1 - (1 - g) * (1 - tg); data[i + 2] = 1 - (1 - b) * (1 - tb); break; case 'add': data[i] = Math.min(255, r + tr); data[i + 1] = Math.min(255, g + tg); data[i + 2] = Math.min(255, b + tb); break; case 'diff': case 'difference': data[i] = Math.abs(r - tr); data[i + 1] = Math.abs(g - tg); data[i + 2] = Math.abs(b - tb); break; case 'subtract': var _r = r - tr, _g = g - tg, _b = b - tb; data[i] = (_r < 0) ? 0 : _r; data[i + 1] = (_g < 0) ? 0 : _g; data[i + 2] = (_b < 0) ? 0 : _b; break; case 'darken': data[i] = Math.min(r, tr); data[i + 1] = Math.min(g, tg); data[i + 2] = Math.min(b, tb); break; case 'lighten': data[i] = Math.max(r, tr); data[i + 1] = Math.max(g, tg); data[i + 2] = Math.max(b, tb); break; } } context.putImageData(imageData, 0, 0); }, /** * Returns object representation of an instance * @return {Object} Object representation of an instance */ toObject: function() { return { color: this.color, image: this.image, mode: this.mode, alpha: this.alpha }; } }); fabric.Image.filters.Blend.fromObject = function(object) { return new fabric.Image.filters.Blend(object); }; })(typeof exports !== 'undefined' ? exports : this); (function(global) { 'use strict'; var fabric = global.fabric || (global.fabric = { }), extend = fabric.util.object.extend, clone = fabric.util.object.clone, toFixed = fabric.util.toFixed, supportsLineDash = fabric.StaticCanvas.supports('setLineDash'); if (fabric.Text) { fabric.warn('fabric.Text is already defined'); return; } var stateProperties = fabric.Object.prototype.stateProperties.concat(); stateProperties.push( 'fontFamily', 'fontWeight', 'fontSize', 'text', 'textDecoration', 'textAlign', 'fontStyle', 'lineHeight', 'textBackgroundColor', 'useNative', 'path' ); /** * Text class * @class fabric.Text * @extends fabric.Object * @return {fabric.Text} thisArg * @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#text} * @see {@link fabric.Text#initialize} for constructor definition */ fabric.Text = fabric.util.createClass(fabric.Object, /** @lends fabric.Text.prototype */ { /** * Properties which when set cause object to change dimensions * @type Object * @private */ _dimensionAffectingProps: { fontSize: true, fontWeight: true, fontFamily: true, textDecoration: true, fontStyle: true, lineHeight: true, stroke: true, strokeWidth: true, text: true }, /** * @private */ _reNewline: /\r?\n/, /** * Retrieves object's fontSize * @method getFontSize * @memberOf fabric.Text.prototype * @return {String} Font size (in pixels) */ /** * Sets object's fontSize * @method setFontSize * @memberOf fabric.Text.prototype * @param {Number} fontSize Font size (in pixels) * @return {fabric.Text} * @chainable */ /** * Retrieves object's fontWeight * @method getFontWeight * @memberOf fabric.Text.prototype * @return {(String|Number)} Font weight */ /** * Sets object's fontWeight * @method setFontWeight * @memberOf fabric.Text.prototype * @param {(Number|String)} fontWeight Font weight * @return {fabric.Text} * @chainable */ /** * Retrieves object's fontFamily * @method getFontFamily * @memberOf fabric.Text.prototype * @return {String} Font family */ /** * Sets object's fontFamily * @method setFontFamily * @memberOf fabric.Text.prototype * @param {String} fontFamily Font family * @return {fabric.Text} * @chainable */ /** * Retrieves object's text * @method getText * @memberOf fabric.Text.prototype * @return {String} text */ /** * Sets object's text * @method setText * @memberOf fabric.Text.prototype * @param {String} text Text * @return {fabric.Text} * @chainable */ /** * Retrieves object's textDecoration * @method getTextDecoration * @memberOf fabric.Text.prototype * @return {String} Text decoration */ /** * Sets object's textDecoration * @method setTextDecoration * @memberOf fabric.Text.prototype * @param {String} textDecoration Text decoration * @return {fabric.Text} * @chainable */ /** * Retrieves object's fontStyle * @method getFontStyle * @memberOf fabric.Text.prototype * @return {String} Font style */ /** * Sets object's fontStyle * @method setFontStyle * @memberOf fabric.Text.prototype * @param {String} fontStyle Font style * @return {fabric.Text} * @chainable */ /** * Retrieves object's lineHeight * @method getLineHeight * @memberOf fabric.Text.prototype * @return {Number} Line height */ /** * Sets object's lineHeight * @method setLineHeight * @memberOf fabric.Text.prototype * @param {Number} lineHeight Line height * @return {fabric.Text} * @chainable */ /** * Retrieves object's textAlign * @method getTextAlign * @memberOf fabric.Text.prototype * @return {String} Text alignment */ /** * Sets object's textAlign * @method setTextAlign * @memberOf fabric.Text.prototype * @param {String} textAlign Text alignment * @return {fabric.Text} * @chainable */ /** * Retrieves object's textBackgroundColor * @method getTextBackgroundColor * @memberOf fabric.Text.prototype * @return {String} Text background color */ /** * Sets object's textBackgroundColor * @method setTextBackgroundColor * @memberOf fabric.Text.prototype * @param {String} textBackgroundColor Text background color * @return {fabric.Text} * @chainable */ /** * Type of an object * @type String * @default */ type: 'text', /** * Font size (in pixels) * @type Number * @default */ fontSize: 40, /** * Font weight (e.g. bold, normal, 400, 600, 800) * @type {(Number|String)} * @default */ fontWeight: 'normal', /** * Font family * @type String * @default */ fontFamily: 'Times New Roman', /** * Text decoration Possible values: "", "underline", "overline" or "line-through". * @type String * @default */ textDecoration: '', /** * Text alignment. Possible values: "left", "center", or "right". * @type String * @default */ textAlign: 'left', /** * Font style . Possible values: "", "normal", "italic" or "oblique". * @type String * @default */ fontStyle: '', /** * Line height * @type Number * @default */ lineHeight: 1.3, /** * Background color of text lines * @type String * @default */ textBackgroundColor: '', /** * URL of a font file, when using Cufon * @type String | null * @default */ path: null, /** * Indicates whether canvas native text methods should be used to render text (otherwise, Cufon is used) * @type Boolean * @default */ useNative: true, /** * List of properties to consider when checking if * state of an object is changed ({@link fabric.Object#hasStateChanged}) * as well as for history (undo/redo) purposes * @type Array */ stateProperties: stateProperties, /** * When defined, an object is rendered via stroke and this property specifies its color. * Backwards incompatibility note: This property was named "strokeStyle" until v1.1.6 * @type String * @default */ stroke: null, /** * Shadow object representing shadow of this shape. * Backwards incompatibility note: This property was named "textShadow" (String) until v1.2.11 * @type fabric.Shadow * @default */ shadow: null, /** * Constructor * @param {String} text Text string * @param {Object} [options] Options object * @return {fabric.Text} thisArg */ initialize: function(text, options) { options = options || { }; this.text = text; this.__skipDimension = true; this.setOptions(options); this.__skipDimension = false; this._initDimensions(); }, /** * Renders text object on offscreen canvas, so that it would get dimensions * @private */ _initDimensions: function() { if (this.__skipDimension) { return; } var canvasEl = fabric.util.createCanvasElement(); this._render(canvasEl.getContext('2d')); }, /** * Returns string representation of an instance * @return {String} String representation of text object */ toString: function() { return '#'; }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on */ _render: function(ctx) { if (typeof Cufon === 'undefined' || this.useNative === true) { this._renderViaNative(ctx); } else { this._renderViaCufon(ctx); } }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on */ _renderViaNative: function(ctx) { var textLines = this.text.split(this._reNewline); this._setTextStyles(ctx); this.width = this._getTextWidth(ctx, textLines); this.height = this._getTextHeight(ctx, textLines); this.clipTo && fabric.util.clipContext(this, ctx); this._renderTextBackground(ctx, textLines); this._translateForTextAlign(ctx); this._renderText(ctx, textLines); if (this.textAlign !== 'left' && this.textAlign !== 'justify') { ctx.restore(); } this._renderTextDecoration(ctx, textLines); this.clipTo && ctx.restore(); this._setBoundaries(ctx, textLines); this._totalLineHeight = 0; }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on */ _renderText: function(ctx, textLines) { ctx.save(); this._setShadow(ctx); this._setupCompositeOperation(ctx); this._renderTextFill(ctx, textLines); this._renderTextStroke(ctx, textLines); this._restoreCompositeOperation(ctx); this._removeShadow(ctx); ctx.restore(); }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on */ _translateForTextAlign: function(ctx) { if (this.textAlign !== 'left' && this.textAlign !== 'justify') { ctx.save(); ctx.translate(this.textAlign === 'center' ? (this.width / 2) : this.width, 0); } }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on * @param {Array} textLines Array of all text lines */ _setBoundaries: function(ctx, textLines) { this._boundaries = [ ]; for (var i = 0, len = textLines.length; i < len; i++) { var lineWidth = this._getLineWidth(ctx, textLines[i]), lineLeftOffset = this._getLineLeftOffset(lineWidth); this._boundaries.push({ height: this.fontSize * this.lineHeight, width: lineWidth, left: lineLeftOffset }); } }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on */ _setTextStyles: function(ctx) { this._setFillStyles(ctx); this._setStrokeStyles(ctx); ctx.textBaseline = 'alphabetic'; if (!this.skipTextAlign) { ctx.textAlign = this.textAlign; } ctx.font = this._getFontDeclaration(); }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on * @param {Array} textLines Array of all text lines * @return {Number} Height of fabric.Text object */ _getTextHeight: function(ctx, textLines) { return this.fontSize * textLines.length * this.lineHeight; }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on * @param {Array} textLines Array of all text lines * @return {Number} Maximum width of fabric.Text object */ _getTextWidth: function(ctx, textLines) { var maxWidth = ctx.measureText(textLines[0] || '|').width; for (var i = 1, len = textLines.length; i < len; i++) { var currentLineWidth = ctx.measureText(textLines[i]).width; if (currentLineWidth > maxWidth) { maxWidth = currentLineWidth; } } return maxWidth; }, /** * @private * @param {String} method Method name ("fillText" or "strokeText") * @param {CanvasRenderingContext2D} ctx Context to render on * @param {String} chars Chars to render * @param {Number} left Left position of text * @param {Number} top Top position of text */ _renderChars: function(method, ctx, chars, left, top) { ctx[method](chars, left, top); }, /** * @private * @param {String} method Method name ("fillText" or "strokeText") * @param {CanvasRenderingContext2D} ctx Context to render on * @param {String} line Text to render * @param {Number} left Left position of text * @param {Number} top Top position of text * @param {Number} lineIndex Index of a line in a text */ _renderTextLine: function(method, ctx, line, left, top, lineIndex) { // lift the line by quarter of fontSize top -= this.fontSize / 4; // short-circuit if (this.textAlign !== 'justify') { this._renderChars(method, ctx, line, left, top, lineIndex); return; } var lineWidth = ctx.measureText(line).width, totalWidth = this.width; if (totalWidth > lineWidth) { // stretch the line var words = line.split(/\s+/), wordsWidth = ctx.measureText(line.replace(/\s+/g, '')).width, widthDiff = totalWidth - wordsWidth, numSpaces = words.length - 1, spaceWidth = widthDiff / numSpaces, leftOffset = 0; for (var i = 0, len = words.length; i < len; i++) { this._renderChars(method, ctx, words[i], left + leftOffset, top, lineIndex); leftOffset += ctx.measureText(words[i]).width + spaceWidth; } } else { this._renderChars(method, ctx, line, left, top, lineIndex); } }, /** * @private * @return {Number} Left offset */ _getLeftOffset: function() { if (fabric.isLikelyNode) { return 0; } return -this.width / 2; }, /** * @private * @return {Number} Top offset */ _getTopOffset: function() { return -this.height / 2; }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on * @param {Array} textLines Array of all text lines */ _renderTextFill: function(ctx, textLines) { if (!this.fill && !this._skipFillStrokeCheck) { return; } this._boundaries = [ ]; var lineHeights = 0; for (var i = 0, len = textLines.length; i < len; i++) { var heightOfLine = this._getHeightOfLine(ctx, i, textLines); lineHeights += heightOfLine; this._renderTextLine( 'fillText', ctx, textLines[i], this._getLeftOffset(), this._getTopOffset() + lineHeights, i ); } if (this.shadow && !this.shadow.affectStroke) { this._removeShadow(ctx); } }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on * @param {Array} textLines Array of all text lines */ _renderTextStroke: function(ctx, textLines) { if ((!this.stroke || this.strokeWidth === 0) && !this._skipFillStrokeCheck) { return; } var lineHeights = 0; ctx.save(); if (this.strokeDashArray) { // Spec requires the concatenation of two copies the dash list when the number of elements is odd if (1 & this.strokeDashArray.length) { this.strokeDashArray.push.apply(this.strokeDashArray, this.strokeDashArray); } supportsLineDash && ctx.setLineDash(this.strokeDashArray); } ctx.beginPath(); for (var i = 0, len = textLines.length; i < len; i++) { var heightOfLine = this._getHeightOfLine(ctx, i, textLines); lineHeights += heightOfLine; this._renderTextLine( 'strokeText', ctx, textLines[i], this._getLeftOffset(), this._getTopOffset() + lineHeights, i ); } ctx.closePath(); ctx.restore(); }, _getHeightOfLine: function() { return this.fontSize * this.lineHeight; }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on * @param {Array} textLines Array of all text lines */ _renderTextBackground: function(ctx, textLines) { this._renderTextBoxBackground(ctx); this._renderTextLinesBackground(ctx, textLines); }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on */ _renderTextBoxBackground: function(ctx) { if (!this.backgroundColor) { return; } ctx.save(); ctx.fillStyle = this.backgroundColor; ctx.fillRect( this._getLeftOffset(), this._getTopOffset(), this.width, this.height ); ctx.restore(); }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on * @param {Array} textLines Array of all text lines */ _renderTextLinesBackground: function(ctx, textLines) { if (!this.textBackgroundColor) { return; } ctx.save(); ctx.fillStyle = this.textBackgroundColor; for (var i = 0, len = textLines.length; i < len; i++) { if (textLines[i] !== '') { var lineWidth = this._getLineWidth(ctx, textLines[i]), lineLeftOffset = this._getLineLeftOffset(lineWidth); ctx.fillRect( this._getLeftOffset() + lineLeftOffset, this._getTopOffset() + (i * this.fontSize * this.lineHeight), lineWidth, this.fontSize * this.lineHeight ); } } ctx.restore(); }, /** * @private * @param {Number} lineWidth Width of text line * @return {Number} Line left offset */ _getLineLeftOffset: function(lineWidth) { if (this.textAlign === 'center') { return (this.width - lineWidth) / 2; } if (this.textAlign === 'right') { return this.width - lineWidth; } return 0; }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on * @param {String} line Text line * @return {Number} Line width */ _getLineWidth: function(ctx, line) { return this.textAlign === 'justify' ? this.width : ctx.measureText(line).width; }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on * @param {Array} textLines Array of all text lines */ _renderTextDecoration: function(ctx, textLines) { if (!this.textDecoration) { return; } // var halfOfVerticalBox = this.originY === 'top' ? 0 : this._getTextHeight(ctx, textLines) / 2; var halfOfVerticalBox = this._getTextHeight(ctx, textLines) / 2, _this = this; /** @ignore */ function renderLinesAtOffset(offset) { for (var i = 0, len = textLines.length; i < len; i++) { var lineWidth = _this._getLineWidth(ctx, textLines[i]), lineLeftOffset = _this._getLineLeftOffset(lineWidth); ctx.fillRect( _this._getLeftOffset() + lineLeftOffset, ~~((offset + (i * _this._getHeightOfLine(ctx, i, textLines))) - halfOfVerticalBox), lineWidth, 1); } } if (this.textDecoration.indexOf('underline') > -1) { renderLinesAtOffset(this.fontSize * this.lineHeight); } if (this.textDecoration.indexOf('line-through') > -1) { renderLinesAtOffset(this.fontSize * this.lineHeight - this.fontSize / 2); } if (this.textDecoration.indexOf('overline') > -1) { renderLinesAtOffset(this.fontSize * this.lineHeight - this.fontSize); } }, /** * @private */ _getFontDeclaration: function() { return [ // node-canvas needs "weight style", while browsers need "style weight" (fabric.isLikelyNode ? this.fontWeight : this.fontStyle), (fabric.isLikelyNode ? this.fontStyle : this.fontWeight), this.fontSize + 'px', (fabric.isLikelyNode ? ('"' + this.fontFamily + '"') : this.fontFamily) ].join(' '); }, /** * Renders text instance on a specified context * @param {CanvasRenderingContext2D} ctx Context to render on */ render: function(ctx, noTransform) { // do not render if object is not visible if (!this.visible) { return; } ctx.save(); if (!noTransform) { this.transform(ctx); } var isInPathGroup = this.group && this.group.type === 'path-group'; if (isInPathGroup) { ctx.translate(-this.group.width/2, -this.group.height/2); } if (this.transformMatrix) { ctx.transform.apply(ctx, this.transformMatrix); } if (isInPathGroup) { ctx.translate(this.left, this.top); } this._render(ctx); ctx.restore(); }, /** * Returns object representation of an instance * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output * @return {Object} Object representation of an instance */ toObject: function(propertiesToInclude) { var object = extend(this.callSuper('toObject', propertiesToInclude), { text: this.text, fontSize: this.fontSize, fontWeight: this.fontWeight, fontFamily: this.fontFamily, fontStyle: this.fontStyle, lineHeight: this.lineHeight, textDecoration: this.textDecoration, textAlign: this.textAlign, path: this.path, textBackgroundColor: this.textBackgroundColor, useNative: this.useNative }); if (!this.includeDefaultValues) { this._removeDefaultValues(object); } return object; }, /* _TO_SVG_START_ */ /** * Returns SVG representation of an instance * @param {Function} [reviver] Method for further parsing of svg representation. * @return {String} svg representation of an instance */ toSVG: function(reviver) { var markup = [ ], textLines = this.text.split(this._reNewline), offsets = this._getSVGLeftTopOffsets(textLines), textAndBg = this._getSVGTextAndBg(offsets.lineTop, offsets.textLeft, textLines), shadowSpans = this._getSVGShadows(offsets.lineTop, textLines); // move top offset by an ascent offsets.textTop += (this._fontAscent ? ((this._fontAscent / 5) * this.lineHeight) : 0); this._wrapSVGTextAndBg(markup, textAndBg, shadowSpans, offsets); return reviver ? reviver(markup.join('')) : markup.join(''); }, /** * @private */ _getSVGLeftTopOffsets: function(textLines) { var lineTop = this.useNative ? this.fontSize * this.lineHeight : (-this._fontAscent - ((this._fontAscent / 5) * this.lineHeight)), textLeft = -(this.width/2), textTop = this.useNative ? this.fontSize - 1 : (this.height/2) - (textLines.length * this.fontSize) - this._totalLineHeight; return { textLeft: textLeft + (this.group && this.group.type === 'path-group' ? this.left : 0), textTop: textTop + (this.group && this.group.type === 'path-group' ? this.top : 0), lineTop: lineTop }; }, /** * @private */ _wrapSVGTextAndBg: function(markup, textAndBg, shadowSpans, offsets) { markup.push( '\n', textAndBg.textBgRects.join(''), '', shadowSpans.join(''), textAndBg.textSpans.join(''), '\n', '\n' ); }, /** * @private * @param {Number} lineHeight * @param {Array} textLines Array of all text lines * @return {Array} */ _getSVGShadows: function(lineHeight, textLines) { var shadowSpans = [], i, len, lineTopOffsetMultiplier = 1; if (!this.shadow || !this._boundaries) { return shadowSpans; } for (i = 0, len = textLines.length; i < len; i++) { if (textLines[i] !== '') { var lineLeftOffset = (this._boundaries && this._boundaries[i]) ? this._boundaries[i].left : 0; shadowSpans.push( '', fabric.util.string.escapeXml(textLines[i]), ''); lineTopOffsetMultiplier = 1; } else { // in some environments (e.g. IE 7 & 8) empty tspans are completely ignored, using a lineTopOffsetMultiplier // prevents empty tspans lineTopOffsetMultiplier++; } } return shadowSpans; }, /** * @private * @param {Number} lineHeight * @param {Number} textLeftOffset Text left offset * @param {Array} textLines Array of all text lines * @return {Object} */ _getSVGTextAndBg: function(lineHeight, textLeftOffset, textLines) { var textSpans = [ ], textBgRects = [ ], lineTopOffsetMultiplier = 1; // bounding-box background this._setSVGBg(textBgRects); // text and text-background for (var i = 0, len = textLines.length; i < len; i++) { if (textLines[i] !== '') { this._setSVGTextLineText(textLines[i], i, textSpans, lineHeight, lineTopOffsetMultiplier, textBgRects); lineTopOffsetMultiplier = 1; } else { // in some environments (e.g. IE 7 & 8) empty tspans are completely ignored, using a lineTopOffsetMultiplier // prevents empty tspans lineTopOffsetMultiplier++; } if (!this.textBackgroundColor || !this._boundaries) { continue; } this._setSVGTextLineBg(textBgRects, i, textLeftOffset, lineHeight); } return { textSpans: textSpans, textBgRects: textBgRects }; }, _setSVGTextLineText: function(textLine, i, textSpans, lineHeight, lineTopOffsetMultiplier) { var lineLeftOffset = (this._boundaries && this._boundaries[i]) ? toFixed(this._boundaries[i].left, 2) : 0; textSpans.push( ' elements since setting opacity // on containing one doesn't work in Illustrator this._getFillAttributes(this.fill), '>', fabric.util.string.escapeXml(textLine), '' ); }, _setSVGTextLineBg: function(textBgRects, i, textLeftOffset, lineHeight) { textBgRects.push( '\n'); }, _setSVGBg: function(textBgRects) { if (this.backgroundColor && this._boundaries) { textBgRects.push( ''); } }, /** * Adobe Illustrator (at least CS5) is unable to render rgba()-based fill values * we work around it by "moving" alpha channel into opacity attribute and setting fill's alpha to 1 * * @private * @param {Any} value * @return {String} */ _getFillAttributes: function(value) { var fillColor = (value && typeof value === 'string') ? new fabric.Color(value) : ''; if (!fillColor || !fillColor.getSource() || fillColor.getAlpha() === 1) { return 'fill="' + value + '"'; } return 'opacity="' + fillColor.getAlpha() + '" fill="' + fillColor.setAlpha(1).toRgb() + '"'; }, /* _TO_SVG_END_ */ /** * Sets specified property to a specified value * @param {String} key * @param {Any} value * @return {fabric.Text} thisArg * @chainable */ _set: function(key, value) { if (key === 'fontFamily' && this.path) { this.path = this.path.replace(/(.*?)([^\/]*)(\.font\.js)/, '$1' + value + '$3'); } this.callSuper('_set', key, value); if (key in this._dimensionAffectingProps) { this._initDimensions(); this.setCoords(); } }, /** * Returns complexity of an instance * @return {Number} complexity */ complexity: function() { return 1; } }); /* _FROM_SVG_START_ */ /** * List of attribute names to account for when parsing SVG element (used by {@link fabric.Text.fromElement}) * @static * @memberOf fabric.Text * @see: http://www.w3.org/TR/SVG/text.html#TextElement */ fabric.Text.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat( 'x y dx dy font-family font-style font-weight font-size text-decoration text-anchor'.split(' ')); /** * Default SVG font size * @static * @memberOf fabric.Text */ fabric.Text.DEFAULT_SVG_FONT_SIZE = 16; /** * Returns fabric.Text instance from an SVG element (not yet implemented) * @static * @memberOf fabric.Text * @param {SVGElement} element Element to parse * @param {Object} [options] Options object * @return {fabric.Text} Instance of fabric.Text */ fabric.Text.fromElement = function(element, options) { if (!element) { return null; } var parsedAttributes = fabric.parseAttributes(element, fabric.Text.ATTRIBUTE_NAMES); options = fabric.util.object.extend((options ? fabric.util.object.clone(options) : { }), parsedAttributes); if ('dx' in parsedAttributes) { options.left += parsedAttributes.dx; } if ('dy' in parsedAttributes) { options.top += parsedAttributes.dy; } if (!('fontSize' in options)) { options.fontSize = fabric.Text.DEFAULT_SVG_FONT_SIZE; } if (!options.originX) { options.originX = 'left'; } var text = new fabric.Text(element.textContent, options), /* Adjust positioning: x/y attributes in SVG correspond to the bottom-left corner of text bounding box top/left properties in Fabric correspond to center point of text bounding box */ offX = 0; if (text.originX === 'left') { offX = text.getWidth() / 2; } if (text.originX === 'right') { offX = -text.getWidth() / 2; } text.set({ left: text.getLeft() + offX, top: text.getTop() - text.getHeight() / 2 }); return text; }; /* _FROM_SVG_END_ */ /** * Returns fabric.Text instance from an object representation * @static * @memberOf fabric.Text * @param {Object} object Object to create an instance from * @return {fabric.Text} Instance of fabric.Text */ fabric.Text.fromObject = function(object) { return new fabric.Text(object.text, clone(object)); }; fabric.util.createAccessors(fabric.Text); })(typeof exports !== 'undefined' ? exports : this); (function() { var clone = fabric.util.object.clone; /** * IText class (introduced in v1.4) Events are also fired with "text:" * prefix when observing canvas. * @class fabric.IText * @extends fabric.Text * @mixes fabric.Observable * * @fires changed * @fires selection:changed * @fires editing:entered * @fires editing:exited * * @return {fabric.IText} thisArg * @see {@link fabric.IText#initialize} for constructor definition * *

        Supported key combinations:

        *
             *   Move cursor:                    left, right, up, down
             *   Select character:               shift + left, shift + right
             *   Select text vertically:         shift + up, shift + down
             *   Move cursor by word:            alt + left, alt + right
             *   Select words:                   shift + alt + left, shift + alt + right
             *   Move cursor to line start/end:  cmd + left, cmd + right
             *   Select till start/end of line:  cmd + shift + left, cmd + shift + right
             *   Jump to start/end of text:      cmd + up, cmd + down
             *   Select till start/end of text:  cmd + shift + up, cmd + shift + down
             *   Delete character:               backspace
             *   Delete word:                    alt + backspace
             *   Delete line:                    cmd + backspace
             *   Forward delete:                 delete
             *   Copy text:                      ctrl/cmd + c
             *   Paste text:                     ctrl/cmd + v
             *   Cut text:                       ctrl/cmd + x
             *   Select entire text:             ctrl/cmd + a
             * 
        * *

        Supported mouse/touch combination

        *
             *   Position cursor:                click/touch
             *   Create selection:               click/touch & drag
             *   Create selection:               click & shift + click
             *   Select word:                    double click
             *   Select line:                    triple click
             * 
        */ fabric.IText = fabric.util.createClass(fabric.Text, fabric.Observable, /** @lends fabric.IText.prototype */ { /** * Type of an object * @type String * @default */ type: 'i-text', /** * Index where text selection starts (or where cursor is when there is no selection) * @type Nubmer * @default */ selectionStart: 0, /** * Index where text selection ends * @type Nubmer * @default */ selectionEnd: 0, /** * Color of text selection * @type String * @default */ selectionColor: 'rgba(17,119,255,0.3)', /** * Indicates whether text is in editing mode * @type Boolean * @default */ isEditing: false, /** * Indicates whether a text can be edited * @type Boolean * @default */ editable: true, /** * Border color of text object while it's in editing mode * @type String * @default */ editingBorderColor: 'rgba(102,153,255,0.25)', /** * Width of cursor (in px) * @type Number * @default */ cursorWidth: 2, /** * Color of default cursor (when not overwritten by character style) * @type String * @default */ cursorColor: '#333', /** * Delay between cursor blink (in ms) * @type Number * @default */ cursorDelay: 1000, /** * Duration of cursor fadein (in ms) * @type Number * @default */ cursorDuration: 600, /** * Object containing character styles * (where top-level properties corresponds to line number and 2nd-level properties -- to char number in a line) * @type Object * @default */ styles: null, /** * Indicates whether internal text char widths can be cached * @type Boolean * @default */ caching: true, /** * @private * @type Boolean * @default */ _skipFillStrokeCheck: true, /** * @private */ _reSpace: /\s|\n/, /** * @private */ _fontSizeFraction: 4, /** * @private */ _currentCursorOpacity: 0, /** * @private */ _selectionDirection: null, /** * @private */ _abortCursorAnimation: false, /** * @private */ _charWidthsCache: { }, /** * Constructor * @param {String} text Text string * @param {Object} [options] Options object * @return {fabric.IText} thisArg */ initialize: function(text, options) { this.styles = options ? (options.styles || { }) : { }; this.callSuper('initialize', text, options); this.initBehavior(); fabric.IText.instances.push(this); // caching this.__lineWidths = { }; this.__lineHeights = { }; this.__lineOffsets = { }; }, /** * Returns true if object has no styling */ isEmptyStyles: function() { if (!this.styles) { return true; } var obj = this.styles; for (var p1 in obj) { for (var p2 in obj[p1]) { /*jshint unused:false */ for (var p3 in obj[p1][p2]) { return false; } } } return true; }, /** * Sets selection start (left boundary of a selection) * @param {Number} index Index to set selection start to */ setSelectionStart: function(index) { if (this.selectionStart !== index) { this.fire('selection:changed'); this.canvas && this.canvas.fire('text:selection:changed', { target: this }); } this.selectionStart = index; this.hiddenTextarea && (this.hiddenTextarea.selectionStart = index); }, /** * Sets selection end (right boundary of a selection) * @param {Number} index Index to set selection end to */ setSelectionEnd: function(index) { if (this.selectionEnd !== index) { this.fire('selection:changed'); this.canvas && this.canvas.fire('text:selection:changed', { target: this }); } this.selectionEnd = index; this.hiddenTextarea && (this.hiddenTextarea.selectionEnd = index); }, /** * Gets style of a current selection/cursor (at the start position) * @param {Number} [startIndex] Start index to get styles at * @param {Number} [endIndex] End index to get styles at * @return {Object} styles Style object at a specified (or current) index */ getSelectionStyles: function(startIndex, endIndex) { if (arguments.length === 2) { var styles = [ ]; for (var i = startIndex; i < endIndex; i++) { styles.push(this.getSelectionStyles(i)); } return styles; } var loc = this.get2DCursorLocation(startIndex); if (this.styles[loc.lineIndex]) { return this.styles[loc.lineIndex][loc.charIndex] || { }; } return { }; }, /** * Sets style of a current selection * @param {Object} [styles] Styles object * @return {fabric.IText} thisArg * @chainable */ setSelectionStyles: function(styles) { if (this.selectionStart === this.selectionEnd) { this._extendStyles(this.selectionStart, styles); } else { for (var i = this.selectionStart; i < this.selectionEnd; i++) { this._extendStyles(i, styles); } } return this; }, /** * @private */ _extendStyles: function(index, styles) { var loc = this.get2DCursorLocation(index); if (!this.styles[loc.lineIndex]) { this.styles[loc.lineIndex] = { }; } if (!this.styles[loc.lineIndex][loc.charIndex]) { this.styles[loc.lineIndex][loc.charIndex] = { }; } fabric.util.object.extend(this.styles[loc.lineIndex][loc.charIndex], styles); }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on */ _render: function(ctx) { this.callSuper('_render', ctx); this.ctx = ctx; this.isEditing && this.renderCursorOrSelection(); }, /** * Renders cursor or selection (depending on what exists) */ renderCursorOrSelection: function() { if (!this.active) { return; } var chars = this.text.split(''), boundaries; if (this.selectionStart === this.selectionEnd) { boundaries = this._getCursorBoundaries(chars, 'cursor'); this.renderCursor(boundaries); } else { boundaries = this._getCursorBoundaries(chars, 'selection'); this.renderSelection(chars, boundaries); } }, /** * Returns 2d representation (lineIndex and charIndex) of cursor (or selection start) * @param {Number} [selectionStart] Optional index. When not given, current selectionStart is used. */ get2DCursorLocation: function(selectionStart) { if (typeof selectionStart === 'undefined') { selectionStart = this.selectionStart; } var textBeforeCursor = this.text.slice(0, selectionStart), linesBeforeCursor = textBeforeCursor.split(this._reNewline); return { lineIndex: linesBeforeCursor.length - 1, charIndex: linesBeforeCursor[linesBeforeCursor.length - 1].length }; }, /** * Returns complete style of char at the current cursor * @param {Number} lineIndex Line index * @param {Number} charIndex Char index * @return {Object} Character style */ getCurrentCharStyle: function(lineIndex, charIndex) { var style = this.styles[lineIndex] && this.styles[lineIndex][charIndex === 0 ? 0 : (charIndex - 1)]; return { fontSize: style && style.fontSize || this.fontSize, fill: style && style.fill || this.fill, textBackgroundColor: style && style.textBackgroundColor || this.textBackgroundColor, textDecoration: style && style.textDecoration || this.textDecoration, fontFamily: style && style.fontFamily || this.fontFamily, fontWeight: style && style.fontWeight || this.fontWeight, fontStyle: style && style.fontStyle || this.fontStyle, stroke: style && style.stroke || this.stroke, strokeWidth: style && style.strokeWidth || this.strokeWidth }; }, /** * Returns fontSize of char at the current cursor * @param {Number} lineIndex Line index * @param {Number} charIndex Char index * @return {Number} Character font size */ getCurrentCharFontSize: function(lineIndex, charIndex) { return ( this.styles[lineIndex] && this.styles[lineIndex][charIndex === 0 ? 0 : (charIndex - 1)] && this.styles[lineIndex][charIndex === 0 ? 0 : (charIndex - 1)].fontSize) || this.fontSize; }, /** * Returns color (fill) of char at the current cursor * @param {Number} lineIndex Line index * @param {Number} charIndex Char index * @return {String} Character color (fill) */ getCurrentCharColor: function(lineIndex, charIndex) { return ( this.styles[lineIndex] && this.styles[lineIndex][charIndex === 0 ? 0 : (charIndex - 1)] && this.styles[lineIndex][charIndex === 0 ? 0 : (charIndex - 1)].fill) || this.cursorColor; }, /** * Returns cursor boundaries (left, top, leftOffset, topOffset) * @private * @param {Array} chars Array of characters * @param {String} typeOfBoundaries */ _getCursorBoundaries: function(chars, typeOfBoundaries) { var cursorLocation = this.get2DCursorLocation(), textLines = this.text.split(this._reNewline), // left/top are left/top of entire text box // leftOffset/topOffset are offset from that left/top point of a text box left = Math.round(this._getLeftOffset()), top = this._getTopOffset(), offsets = this._getCursorBoundariesOffsets( chars, typeOfBoundaries, cursorLocation, textLines); return { left: left, top: top, leftOffset: offsets.left + offsets.lineLeft, topOffset: offsets.top }; }, /** * @private */ _getCursorBoundariesOffsets: function(chars, typeOfBoundaries, cursorLocation, textLines) { var lineLeftOffset = 0, lineIndex = 0, charIndex = 0, leftOffset = 0, topOffset = typeOfBoundaries === 'cursor' // selection starts at the very top of the line, // whereas cursor starts at the padding created by line height ? (this._getHeightOfLine(this.ctx, 0) - this.getCurrentCharFontSize(cursorLocation.lineIndex, cursorLocation.charIndex)) : 0; for (var i = 0; i < this.selectionStart; i++) { if (chars[i] === '\n') { leftOffset = 0; var index = lineIndex + (typeOfBoundaries === 'cursor' ? 1 : 0); topOffset += this._getCachedLineHeight(index); lineIndex++; charIndex = 0; } else { leftOffset += this._getWidthOfChar(this.ctx, chars[i], lineIndex, charIndex); charIndex++; } lineLeftOffset = this._getCachedLineOffset(lineIndex, textLines); } this._clearCache(); return { top: topOffset, left: leftOffset, lineLeft: lineLeftOffset }; }, /** * @private */ _clearCache: function() { this.__lineWidths = { }; this.__lineHeights = { }; this.__lineOffsets = { }; }, /** * @private */ _getCachedLineHeight: function(index) { return this.__lineHeights[index] || (this.__lineHeights[index] = this._getHeightOfLine(this.ctx, index)); }, /** * @private */ _getCachedLineWidth: function(lineIndex, textLines) { return this.__lineWidths[lineIndex] || (this.__lineWidths[lineIndex] = this._getWidthOfLine(this.ctx, lineIndex, textLines)); }, /** * @private */ _getCachedLineOffset: function(lineIndex, textLines) { var widthOfLine = this._getCachedLineWidth(lineIndex, textLines); return this.__lineOffsets[lineIndex] || (this.__lineOffsets[lineIndex] = this._getLineLeftOffset(widthOfLine)); }, /** * Renders cursor * @param {Object} boundaries */ renderCursor: function(boundaries) { var ctx = this.ctx; ctx.save(); var cursorLocation = this.get2DCursorLocation(), lineIndex = cursorLocation.lineIndex, charIndex = cursorLocation.charIndex, charHeight = this.getCurrentCharFontSize(lineIndex, charIndex), leftOffset = (lineIndex === 0 && charIndex === 0) ? this._getCachedLineOffset(lineIndex, this.text.split(this._reNewline)) : boundaries.leftOffset; ctx.fillStyle = this.getCurrentCharColor(lineIndex, charIndex); ctx.globalAlpha = this.__isMousedown ? 1 : this._currentCursorOpacity; ctx.fillRect( boundaries.left + leftOffset, boundaries.top + boundaries.topOffset, this.cursorWidth / this.scaleX, charHeight); ctx.restore(); }, /** * Renders text selection * @param {Array} chars Array of characters * @param {Object} boundaries Object with left/top/leftOffset/topOffset */ renderSelection: function(chars, boundaries) { var ctx = this.ctx; ctx.save(); ctx.fillStyle = this.selectionColor; var start = this.get2DCursorLocation(this.selectionStart), end = this.get2DCursorLocation(this.selectionEnd), startLine = start.lineIndex, endLine = end.lineIndex, textLines = this.text.split(this._reNewline); for (var i = startLine; i <= endLine; i++) { var lineOffset = this._getCachedLineOffset(i, textLines) || 0, lineHeight = this._getCachedLineHeight(i), boxWidth = 0; if (i === startLine) { for (var j = 0, len = textLines[i].length; j < len; j++) { if (j >= start.charIndex && (i !== endLine || j < end.charIndex)) { boxWidth += this._getWidthOfChar(ctx, textLines[i][j], i, j); } if (j < start.charIndex) { lineOffset += this._getWidthOfChar(ctx, textLines[i][j], i, j); } } } else if (i > startLine && i < endLine) { boxWidth += this._getCachedLineWidth(i, textLines) || 5; } else if (i === endLine) { for (var j2 = 0, j2len = end.charIndex; j2 < j2len; j2++) { boxWidth += this._getWidthOfChar(ctx, textLines[i][j2], i, j2); } } ctx.fillRect( boundaries.left + lineOffset, boundaries.top + boundaries.topOffset, boxWidth, lineHeight); boundaries.topOffset += lineHeight; } ctx.restore(); }, /** * @private * @param {String} method * @param {CanvasRenderingContext2D} ctx Context to render on */ _renderChars: function(method, ctx, line, left, top, lineIndex) { if (this.isEmptyStyles()) { return this._renderCharsFast(method, ctx, line, left, top); } this.skipTextAlign = true; // set proper box offset left -= this.textAlign === 'center' ? (this.width / 2) : (this.textAlign === 'right') ? this.width : 0; // set proper line offset var textLines = this.text.split(this._reNewline), lineWidth = this._getWidthOfLine(ctx, lineIndex, textLines), lineHeight = this._getHeightOfLine(ctx, lineIndex, textLines), lineLeftOffset = this._getLineLeftOffset(lineWidth), chars = line.split(''), prevStyle, charsToRender = ''; left += lineLeftOffset || 0; ctx.save(); for (var i = 0, len = chars.length; i <= len; i++) { prevStyle = prevStyle || this.getCurrentCharStyle(lineIndex, i); var thisStyle = this.getCurrentCharStyle(lineIndex, i + 1); if (this._hasStyleChanged(prevStyle, thisStyle) || i === len) { this._renderChar(method, ctx, lineIndex, i - 1, charsToRender, left, top, lineHeight); charsToRender = ''; prevStyle = thisStyle; } charsToRender += chars[i]; } ctx.restore(); }, /** * @private * @param {String} method * @param {CanvasRenderingContext2D} ctx Context to render on * @param {String} line Content of the line * @param {Number} left Left coordinate * @param {Number} top Top coordinate */ _renderCharsFast: function(method, ctx, line, left, top) { this.skipTextAlign = false; if (method === 'fillText' && this.fill) { this.callSuper('_renderChars', method, ctx, line, left, top); } if (method === 'strokeText' && this.stroke) { this.callSuper('_renderChars', method, ctx, line, left, top); } }, /** * @private * @param {String} method * @param {CanvasRenderingContext2D} ctx Context to render on * @param {Number} lineIndex * @param {Number} i * @param {String} _char * @param {Number} left Left coordinate * @param {Number} top Top coordinate * @param {Number} lineHeight Height of the line */ _renderChar: function(method, ctx, lineIndex, i, _char, left, top, lineHeight) { var decl, charWidth, charHeight; if (this.styles && this.styles[lineIndex] && (decl = this.styles[lineIndex][i])) { var shouldStroke = decl.stroke || this.stroke, shouldFill = decl.fill || this.fill; ctx.save(); charWidth = this._applyCharStylesGetWidth(ctx, _char, lineIndex, i, decl); charHeight = this._getHeightOfChar(ctx, _char, lineIndex, i); if (shouldFill) { ctx.fillText(_char, left, top); } if (shouldStroke) { ctx.strokeText(_char, left, top); } this._renderCharDecoration(ctx, decl, left, top, charWidth, lineHeight, charHeight); ctx.restore(); ctx.translate(charWidth, 0); } else { if (method === 'strokeText' && this.stroke) { ctx[method](_char, left, top); } if (method === 'fillText' && this.fill) { ctx[method](_char, left, top); } charWidth = this._applyCharStylesGetWidth(ctx, _char, lineIndex, i); this._renderCharDecoration(ctx, null, left, top, charWidth, lineHeight); ctx.translate(ctx.measureText(_char).width, 0); } }, /** * @private * @param {Object} prevStyle * @param {Object} thisStyle */ _hasStyleChanged: function(prevStyle, thisStyle) { return (prevStyle.fill !== thisStyle.fill || prevStyle.fontSize !== thisStyle.fontSize || prevStyle.textBackgroundColor !== thisStyle.textBackgroundColor || prevStyle.textDecoration !== thisStyle.textDecoration || prevStyle.fontFamily !== thisStyle.fontFamily || prevStyle.fontWeight !== thisStyle.fontWeight || prevStyle.fontStyle !== thisStyle.fontStyle || prevStyle.stroke !== thisStyle.stroke || prevStyle.strokeWidth !== thisStyle.strokeWidth ); }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on */ _renderCharDecoration: function(ctx, styleDeclaration, left, top, charWidth, lineHeight, charHeight) { var textDecoration = styleDeclaration ? (styleDeclaration.textDecoration || this.textDecoration) : this.textDecoration, fontSize = (styleDeclaration ? styleDeclaration.fontSize : null) || this.fontSize; if (!textDecoration) { return; } if (textDecoration.indexOf('underline') > -1) { this._renderCharDecorationAtOffset( ctx, left, top + (this.fontSize / this._fontSizeFraction), charWidth, 0, this.fontSize / 20 ); } if (textDecoration.indexOf('line-through') > -1) { this._renderCharDecorationAtOffset( ctx, left, top + (this.fontSize / this._fontSizeFraction), charWidth, charHeight / 2, fontSize / 20 ); } if (textDecoration.indexOf('overline') > -1) { this._renderCharDecorationAtOffset( ctx, left, top, charWidth, lineHeight - (this.fontSize / this._fontSizeFraction), this.fontSize / 20 ); } }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on */ _renderCharDecorationAtOffset: function(ctx, left, top, charWidth, offset, thickness) { ctx.fillRect(left, top - offset, charWidth, thickness); }, /** * @private * @param {String} method * @param {CanvasRenderingContext2D} ctx Context to render on * @param {String} line */ _renderTextLine: function(method, ctx, line, left, top, lineIndex) { // to "cancel" this.fontSize subtraction in fabric.Text#_renderTextLine top += this.fontSize / 4; this.callSuper('_renderTextLine', method, ctx, line, left, top, lineIndex); }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on * @param {Array} textLines */ _renderTextDecoration: function(ctx, textLines) { if (this.isEmptyStyles()) { return this.callSuper('_renderTextDecoration', ctx, textLines); } }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on * @param {Array} textLines Array of all text lines */ _renderTextLinesBackground: function(ctx, textLines) { if (!this.textBackgroundColor && !this.styles) { return; } ctx.save(); if (this.textBackgroundColor) { ctx.fillStyle = this.textBackgroundColor; } var lineHeights = 0, fractionOfFontSize = this.fontSize / this._fontSizeFraction; for (var i = 0, len = textLines.length; i < len; i++) { var heightOfLine = this._getHeightOfLine(ctx, i, textLines); if (textLines[i] === '') { lineHeights += heightOfLine; continue; } var lineWidth = this._getWidthOfLine(ctx, i, textLines), lineLeftOffset = this._getLineLeftOffset(lineWidth); if (this.textBackgroundColor) { ctx.fillStyle = this.textBackgroundColor; ctx.fillRect( this._getLeftOffset() + lineLeftOffset, this._getTopOffset() + lineHeights + fractionOfFontSize, lineWidth, heightOfLine ); } if (this.styles[i]) { for (var j = 0, jlen = textLines[i].length; j < jlen; j++) { if (this.styles[i] && this.styles[i][j] && this.styles[i][j].textBackgroundColor) { var _char = textLines[i][j]; ctx.fillStyle = this.styles[i][j].textBackgroundColor; ctx.fillRect( this._getLeftOffset() + lineLeftOffset + this._getWidthOfCharsAt(ctx, i, j, textLines), this._getTopOffset() + lineHeights + fractionOfFontSize, this._getWidthOfChar(ctx, _char, i, j, textLines) + 1, heightOfLine ); } } } lineHeights += heightOfLine; } ctx.restore(); }, /** * @private */ _getCacheProp: function(_char, styleDeclaration) { return _char + styleDeclaration.fontFamily + styleDeclaration.fontSize + styleDeclaration.fontWeight + styleDeclaration.fontStyle + styleDeclaration.shadow; }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on * @param {String} _char * @param {Number} lineIndex * @param {Number} charIndex * @param {Object} [decl] */ _applyCharStylesGetWidth: function(ctx, _char, lineIndex, charIndex, decl) { var styleDeclaration = decl || (this.styles[lineIndex] && this.styles[lineIndex][charIndex]); if (styleDeclaration) { // cloning so that original style object is not polluted with following font declarations styleDeclaration = clone(styleDeclaration); } else { styleDeclaration = { }; } this._applyFontStyles(styleDeclaration); var cacheProp = this._getCacheProp(_char, styleDeclaration); // short-circuit if no styles if (this.isEmptyStyles() && this._charWidthsCache[cacheProp] && this.caching) { return this._charWidthsCache[cacheProp]; } if (typeof styleDeclaration.shadow === 'string') { styleDeclaration.shadow = new fabric.Shadow(styleDeclaration.shadow); } var fill = styleDeclaration.fill || this.fill; ctx.fillStyle = fill.toLive ? fill.toLive(ctx) : fill; if (styleDeclaration.stroke) { ctx.strokeStyle = (styleDeclaration.stroke && styleDeclaration.stroke.toLive) ? styleDeclaration.stroke.toLive(ctx) : styleDeclaration.stroke; } ctx.lineWidth = styleDeclaration.strokeWidth || this.strokeWidth; ctx.font = this._getFontDeclaration.call(styleDeclaration); this._setShadow.call(styleDeclaration, ctx); if (!this.caching) { return ctx.measureText(_char).width; } if (!this._charWidthsCache[cacheProp]) { this._charWidthsCache[cacheProp] = ctx.measureText(_char).width; } return this._charWidthsCache[cacheProp]; }, /** * @private * @param {Object} styleDeclaration */ _applyFontStyles: function(styleDeclaration) { if (!styleDeclaration.fontFamily) { styleDeclaration.fontFamily = this.fontFamily; } if (!styleDeclaration.fontSize) { styleDeclaration.fontSize = this.fontSize; } if (!styleDeclaration.fontWeight) { styleDeclaration.fontWeight = this.fontWeight; } if (!styleDeclaration.fontStyle) { styleDeclaration.fontStyle = this.fontStyle; } }, /** * @private * @param {Number} lineIndex * @param {Number} charIndex */ _getStyleDeclaration: function(lineIndex, charIndex) { return (this.styles[lineIndex] && this.styles[lineIndex][charIndex]) ? clone(this.styles[lineIndex][charIndex]) : { }; }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on */ _getWidthOfChar: function(ctx, _char, lineIndex, charIndex) { if (this.textAlign === 'justify' && /\s/.test(_char)) { return this._getWidthOfSpace(ctx, lineIndex); } var styleDeclaration = this._getStyleDeclaration(lineIndex, charIndex); this._applyFontStyles(styleDeclaration); var cacheProp = this._getCacheProp(_char, styleDeclaration); if (this._charWidthsCache[cacheProp] && this.caching) { return this._charWidthsCache[cacheProp]; } else if (ctx) { ctx.save(); var width = this._applyCharStylesGetWidth(ctx, _char, lineIndex, charIndex); ctx.restore(); return width; } }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on */ _getHeightOfChar: function(ctx, _char, lineIndex, charIndex) { if (this.styles[lineIndex] && this.styles[lineIndex][charIndex]) { return this.styles[lineIndex][charIndex].fontSize || this.fontSize; } return this.fontSize; }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on */ _getWidthOfCharAt: function(ctx, lineIndex, charIndex, lines) { lines = lines || this.text.split(this._reNewline); var _char = lines[lineIndex].split('')[charIndex]; return this._getWidthOfChar(ctx, _char, lineIndex, charIndex); }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on */ _getHeightOfCharAt: function(ctx, lineIndex, charIndex, lines) { lines = lines || this.text.split(this._reNewline); var _char = lines[lineIndex].split('')[charIndex]; return this._getHeightOfChar(ctx, _char, lineIndex, charIndex); }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on */ _getWidthOfCharsAt: function(ctx, lineIndex, charIndex, lines) { var width = 0; for (var i = 0; i < charIndex; i++) { width += this._getWidthOfCharAt(ctx, lineIndex, i, lines); } return width; }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on */ _getWidthOfLine: function(ctx, lineIndex, textLines) { // if (!this.styles[lineIndex]) { // return this.callSuper('_getLineWidth', ctx, textLines[lineIndex]); // } return this._getWidthOfCharsAt(ctx, lineIndex, textLines[lineIndex].length, textLines); }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on * @param {Number} lineIndex */ _getWidthOfSpace: function (ctx, lineIndex) { var lines = this.text.split(this._reNewline), line = lines[lineIndex], words = line.split(/\s+/), wordsWidth = this._getWidthOfWords(ctx, line, lineIndex), widthDiff = this.width - wordsWidth, numSpaces = words.length - 1, width = widthDiff / numSpaces; return width; }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on * @param {Number} line * @param {Number} lineIndex */ _getWidthOfWords: function (ctx, line, lineIndex) { var width = 0; for (var charIndex = 0; charIndex < line.length; charIndex++) { var _char = line[charIndex]; if (!_char.match(/\s/)) { width += this._getWidthOfChar(ctx, _char, lineIndex, charIndex); } } return width; }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on */ _getTextWidth: function(ctx, textLines) { if (this.isEmptyStyles()) { return this.callSuper('_getTextWidth', ctx, textLines); } var maxWidth = this._getWidthOfLine(ctx, 0, textLines); for (var i = 1, len = textLines.length; i < len; i++) { var currentLineWidth = this._getWidthOfLine(ctx, i, textLines); if (currentLineWidth > maxWidth) { maxWidth = currentLineWidth; } } return maxWidth; }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on */ _getHeightOfLine: function(ctx, lineIndex, textLines) { textLines = textLines || this.text.split(this._reNewline); var maxHeight = this._getHeightOfChar(ctx, textLines[lineIndex][0], lineIndex, 0), line = textLines[lineIndex], chars = line.split(''); for (var i = 1, len = chars.length; i < len; i++) { var currentCharHeight = this._getHeightOfChar(ctx, chars[i], lineIndex, i); if (currentCharHeight > maxHeight) { maxHeight = currentCharHeight; } } return maxHeight * this.lineHeight; }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on * @param {Array} textLines Array of all text lines */ _getTextHeight: function(ctx, textLines) { var height = 0; for (var i = 0, len = textLines.length; i < len; i++) { height += this._getHeightOfLine(ctx, i, textLines); } return height; }, /** * @private */ _getTopOffset: function() { var topOffset = fabric.Text.prototype._getTopOffset.call(this); return topOffset - (this.fontSize / this._fontSizeFraction); }, /** * This method is overwritten to account for different top offset * @private */ _renderTextBoxBackground: function(ctx) { if (!this.backgroundColor) { return; } ctx.save(); ctx.fillStyle = this.backgroundColor; ctx.fillRect( this._getLeftOffset(), this._getTopOffset() + (this.fontSize / this._fontSizeFraction), this.width, this.height ); ctx.restore(); }, /** * Returns object representation of an instance * @method toObject * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output * @return {Object} object representation of an instance */ toObject: function(propertiesToInclude) { return fabric.util.object.extend(this.callSuper('toObject', propertiesToInclude), { styles: clone(this.styles) }); } }); /** * Returns fabric.IText instance from an object representation * @static * @memberOf fabric.IText * @param {Object} object Object to create an instance from * @return {fabric.IText} instance of fabric.IText */ fabric.IText.fromObject = function(object) { return new fabric.IText(object.text, clone(object)); }; /** * Contains all fabric.IText objects that have been created * @static * @memberof fabric.IText * @type Array */ fabric.IText.instances = [ ]; })(); (function() { var clone = fabric.util.object.clone; fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.prototype */ { /** * Initializes all the interactive behavior of IText */ initBehavior: function() { this.initAddedHandler(); this.initCursorSelectionHandlers(); this.initDoubleClickSimulation(); }, /** * Initializes "selected" event handler */ initSelectedHandler: function() { this.on('selected', function() { var _this = this; setTimeout(function() { _this.selected = true; }, 100); }); }, /** * Initializes "added" event handler */ initAddedHandler: function() { this.on('added', function() { if (this.canvas && !this.canvas._hasITextHandlers) { this.canvas._hasITextHandlers = true; this._initCanvasHandlers(); } }); }, /** * @private */ _initCanvasHandlers: function() { this.canvas.on('selection:cleared', function() { fabric.IText.prototype.exitEditingOnOthers.call(); }); this.canvas.on('mouse:up', function() { fabric.IText.instances.forEach(function(obj) { obj.__isMousedown = false; }); }); this.canvas.on('object:selected', function(options) { fabric.IText.prototype.exitEditingOnOthers.call(options.target); }); }, /** * @private */ _tick: function() { if (this._abortCursorAnimation) { return; } var _this = this; this.animate('_currentCursorOpacity', 1, { duration: this.cursorDuration, onComplete: function() { _this._onTickComplete(); }, onChange: function() { _this.canvas && _this.canvas.renderAll(); }, abort: function() { return _this._abortCursorAnimation; } }); }, /** * @private */ _onTickComplete: function() { if (this._abortCursorAnimation) { return; } var _this = this; if (this._cursorTimeout1) { clearTimeout(this._cursorTimeout1); } this._cursorTimeout1 = setTimeout(function() { _this.animate('_currentCursorOpacity', 0, { duration: this.cursorDuration / 2, onComplete: function() { _this._tick(); }, onChange: function() { _this.canvas && _this.canvas.renderAll(); }, abort: function() { return _this._abortCursorAnimation; } }); }, 100); }, /** * Initializes delayed cursor */ initDelayedCursor: function(restart) { var _this = this, delay = restart ? 0 : this.cursorDelay; if (restart) { this._abortCursorAnimation = true; clearTimeout(this._cursorTimeout1); this._currentCursorOpacity = 1; this.canvas && this.canvas.renderAll(); } if (this._cursorTimeout2) { clearTimeout(this._cursorTimeout2); } this._cursorTimeout2 = setTimeout(function() { _this._abortCursorAnimation = false; _this._tick(); }, delay); }, /** * Aborts cursor animation and clears all timeouts */ abortCursorAnimation: function() { this._abortCursorAnimation = true; clearTimeout(this._cursorTimeout1); clearTimeout(this._cursorTimeout2); this._currentCursorOpacity = 0; this.canvas && this.canvas.renderAll(); var _this = this; setTimeout(function() { _this._abortCursorAnimation = false; }, 10); }, /** * Selects entire text */ selectAll: function() { this.selectionStart = 0; this.selectionEnd = this.text.length; this.fire('selection:changed'); this.canvas && this.canvas.fire('text:selection:changed', { target: this }); }, /** * Returns selected text * @return {String} */ getSelectedText: function() { return this.text.slice(this.selectionStart, this.selectionEnd); }, /** * Find new selection index representing start of current word according to current selection index * @param {Number} startFrom Surrent selection index * @return {Number} New selection index */ findWordBoundaryLeft: function(startFrom) { var offset = 0, index = startFrom - 1; // remove space before cursor first if (this._reSpace.test(this.text.charAt(index))) { while (this._reSpace.test(this.text.charAt(index))) { offset++; index--; } } while (/\S/.test(this.text.charAt(index)) && index > -1) { offset++; index--; } return startFrom - offset; }, /** * Find new selection index representing end of current word according to current selection index * @param {Number} startFrom Current selection index * @return {Number} New selection index */ findWordBoundaryRight: function(startFrom) { var offset = 0, index = startFrom; // remove space after cursor first if (this._reSpace.test(this.text.charAt(index))) { while (this._reSpace.test(this.text.charAt(index))) { offset++; index++; } } while (/\S/.test(this.text.charAt(index)) && index < this.text.length) { offset++; index++; } return startFrom + offset; }, /** * Find new selection index representing start of current line according to current selection index * @param {Number} startFrom Current selection index * @return {Number} New selection index */ findLineBoundaryLeft: function(startFrom) { var offset = 0, index = startFrom - 1; while (!/\n/.test(this.text.charAt(index)) && index > -1) { offset++; index--; } return startFrom - offset; }, /** * Find new selection index representing end of current line according to current selection index * @param {Number} startFrom Current selection index * @return {Number} New selection index */ findLineBoundaryRight: function(startFrom) { var offset = 0, index = startFrom; while (!/\n/.test(this.text.charAt(index)) && index < this.text.length) { offset++; index++; } return startFrom + offset; }, /** * Returns number of newlines in selected text * @return {Number} Number of newlines in selected text */ getNumNewLinesInSelectedText: function() { var selectedText = this.getSelectedText(), numNewLines = 0; for (var i = 0, chars = selectedText.split(''), len = chars.length; i < len; i++) { if (chars[i] === '\n') { numNewLines++; } } return numNewLines; }, /** * Finds index corresponding to beginning or end of a word * @param {Number} selectionStart Index of a character * @param {Number} direction: 1 or -1 * @return {Number} Index of the beginning or end of a word */ searchWordBoundary: function(selectionStart, direction) { var index = this._reSpace.test(this.text.charAt(selectionStart)) ? selectionStart - 1 : selectionStart, _char = this.text.charAt(index), reNonWord = /[ \n\.,;!\?\-]/; while (!reNonWord.test(_char) && index > 0 && index < this.text.length) { index += direction; _char = this.text.charAt(index); } if (reNonWord.test(_char) && _char !== '\n') { index += direction === 1 ? 0 : 1; } return index; }, /** * Selects a word based on the index * @param {Number} selectionStart Index of a character */ selectWord: function(selectionStart) { var newSelectionStart = this.searchWordBoundary(selectionStart, -1), /* search backwards */ newSelectionEnd = this.searchWordBoundary(selectionStart, 1); /* search forward */ this.setSelectionStart(newSelectionStart); this.setSelectionEnd(newSelectionEnd); this.initDelayedCursor(true); }, /** * Selects a line based on the index * @param {Number} selectionStart Index of a character */ selectLine: function(selectionStart) { var newSelectionStart = this.findLineBoundaryLeft(selectionStart), newSelectionEnd = this.findLineBoundaryRight(selectionStart); this.setSelectionStart(newSelectionStart); this.setSelectionEnd(newSelectionEnd); this.initDelayedCursor(true); }, /** * Enters editing state * @return {fabric.IText} thisArg * @chainable */ enterEditing: function() { if (this.isEditing || !this.editable) { return; } this.exitEditingOnOthers(); this.isEditing = true; this.initHiddenTextarea(); this._updateTextarea(); this._saveEditingProps(); this._setEditingProps(); this._tick(); this.canvas && this.canvas.renderAll(); this.fire('editing:entered'); this.canvas && this.canvas.fire('text:editing:entered', { target: this }); return this; }, exitEditingOnOthers: function() { fabric.IText.instances.forEach(function(obj) { obj.selected = false; if (obj.isEditing) { obj.exitEditing(); } }, this); }, /** * @private */ _setEditingProps: function() { this.hoverCursor = 'text'; if (this.canvas) { this.canvas.defaultCursor = this.canvas.moveCursor = 'text'; } this.borderColor = this.editingBorderColor; this.hasControls = this.selectable = false; this.lockMovementX = this.lockMovementY = true; }, /** * @private */ _updateTextarea: function() { if (!this.hiddenTextarea) { return; } this.hiddenTextarea.value = this.text; this.hiddenTextarea.selectionStart = this.selectionStart; }, /** * @private */ _saveEditingProps: function() { this._savedProps = { hasControls: this.hasControls, borderColor: this.borderColor, lockMovementX: this.lockMovementX, lockMovementY: this.lockMovementY, hoverCursor: this.hoverCursor, defaultCursor: this.canvas && this.canvas.defaultCursor, moveCursor: this.canvas && this.canvas.moveCursor }; }, /** * @private */ _restoreEditingProps: function() { if (!this._savedProps) { return; } this.hoverCursor = this._savedProps.overCursor; this.hasControls = this._savedProps.hasControls; this.borderColor = this._savedProps.borderColor; this.lockMovementX = this._savedProps.lockMovementX; this.lockMovementY = this._savedProps.lockMovementY; if (this.canvas) { this.canvas.defaultCursor = this._savedProps.defaultCursor; this.canvas.moveCursor = this._savedProps.moveCursor; } }, /** * Exits from editing state * @return {fabric.IText} thisArg * @chainable */ exitEditing: function() { this.selected = false; this.isEditing = false; this.selectable = true; this.selectionEnd = this.selectionStart; this.hiddenTextarea && this.canvas && this.hiddenTextarea.parentNode.removeChild(this.hiddenTextarea); this.hiddenTextarea = null; this.abortCursorAnimation(); this._restoreEditingProps(); this._currentCursorOpacity = 0; this.fire('editing:exited'); this.canvas && this.canvas.fire('text:editing:exited', { target: this }); return this; }, /** * @private */ _removeExtraneousStyles: function() { var textLines = this.text.split(this._reNewline); for (var prop in this.styles) { if (!textLines[prop]) { delete this.styles[prop]; } } }, /** * @private */ _removeCharsFromTo: function(start, end) { var i = end; while (i !== start) { var prevIndex = this.get2DCursorLocation(i).charIndex; i--; var index = this.get2DCursorLocation(i).charIndex, isNewline = index > prevIndex; if (isNewline) { this.removeStyleObject(isNewline, i + 1); } else { this.removeStyleObject(this.get2DCursorLocation(i).charIndex === 0, i); } } this.text = this.text.slice(0, start) + this.text.slice(end); }, /** * Inserts a character where cursor is (replacing selection if one exists) * @param {String} _chars Characters to insert */ insertChars: function(_chars) { var isEndOfLine = this.text.slice(this.selectionStart, this.selectionStart + 1) === '\n'; this.text = this.text.slice(0, this.selectionStart) + _chars + this.text.slice(this.selectionEnd); if (this.selectionStart === this.selectionEnd) { this.insertStyleObjects(_chars, isEndOfLine, this.copiedStyles); } // else if (this.selectionEnd - this.selectionStart > 1) { // TODO: replace styles properly // console.log('replacing MORE than 1 char'); // } this.selectionStart += _chars.length; this.selectionEnd = this.selectionStart; if (this.canvas) { // TODO: double renderAll gets rid of text box shift happenning sometimes // need to find out what exactly causes it and fix it this.canvas.renderAll().renderAll(); } this.setCoords(); this.fire('changed'); this.canvas && this.canvas.fire('text:changed', { target: this }); }, /** * Inserts new style object * @param {Number} lineIndex Index of a line * @param {Number} charIndex Index of a char * @param {Boolean} isEndOfLine True if it's end of line */ insertNewlineStyleObject: function(lineIndex, charIndex, isEndOfLine) { this.shiftLineStyles(lineIndex, +1); if (!this.styles[lineIndex + 1]) { this.styles[lineIndex + 1] = { }; } var currentCharStyle = this.styles[lineIndex][charIndex - 1], newLineStyles = { }; // if there's nothing after cursor, // we clone current char style onto the next (otherwise empty) line if (isEndOfLine) { newLineStyles[0] = clone(currentCharStyle); this.styles[lineIndex + 1] = newLineStyles; } // otherwise we clone styles of all chars // after cursor onto the next line, from the beginning else { for (var index in this.styles[lineIndex]) { if (parseInt(index, 10) >= charIndex) { newLineStyles[parseInt(index, 10) - charIndex] = this.styles[lineIndex][index]; // remove lines from the previous line since they're on a new line now delete this.styles[lineIndex][index]; } } this.styles[lineIndex + 1] = newLineStyles; } }, /** * Inserts style object for a given line/char index * @param {Number} lineIndex Index of a line * @param {Number} charIndex Index of a char * @param {Object} [style] Style object to insert, if given */ insertCharStyleObject: function(lineIndex, charIndex, style) { var currentLineStyles = this.styles[lineIndex], currentLineStylesCloned = clone(currentLineStyles); if (charIndex === 0 && !style) { charIndex = 1; } // shift all char styles by 1 forward // 0,1,2,3 -> (charIndex=2) -> 0,1,3,4 -> (insert 2) -> 0,1,2,3,4 for (var index in currentLineStylesCloned) { var numericIndex = parseInt(index, 10); if (numericIndex >= charIndex) { currentLineStyles[numericIndex + 1] = currentLineStylesCloned[numericIndex]; //delete currentLineStyles[index]; } } this.styles[lineIndex][charIndex] = style || clone(currentLineStyles[charIndex - 1]); }, /** * Inserts style object(s) * @param {String} _chars Characters at the location where style is inserted * @param {Boolean} isEndOfLine True if it's end of line * @param {Array} [styles] Styles to insert */ insertStyleObjects: function(_chars, isEndOfLine, styles) { // short-circuit if (this.isEmptyStyles()) { return; } var cursorLocation = this.get2DCursorLocation(), lineIndex = cursorLocation.lineIndex, charIndex = cursorLocation.charIndex; if (!this.styles[lineIndex]) { this.styles[lineIndex] = { }; } if (_chars === '\n') { this.insertNewlineStyleObject(lineIndex, charIndex, isEndOfLine); } else { if (styles) { this._insertStyles(styles); } else { // TODO: support multiple style insertion if _chars.length > 1 this.insertCharStyleObject(lineIndex, charIndex); } } }, /** * @private */ _insertStyles: function(styles) { for (var i = 0, len = styles.length; i < len; i++) { var cursorLocation = this.get2DCursorLocation(this.selectionStart + i), lineIndex = cursorLocation.lineIndex, charIndex = cursorLocation.charIndex; this.insertCharStyleObject(lineIndex, charIndex, styles[i]); } }, /** * Shifts line styles up or down * @param {Number} lineIndex Index of a line * @param {Number} offset Can be -1 or +1 */ shiftLineStyles: function(lineIndex, offset) { // shift all line styles by 1 upward var clonedStyles = clone(this.styles); for (var line in this.styles) { var numericLine = parseInt(line, 10); if (numericLine > lineIndex) { this.styles[numericLine + offset] = clonedStyles[numericLine]; } } }, /** * Removes style object * @param {Boolean} isBeginningOfLine True if cursor is at the beginning of line * @param {Number} [index] Optional index. When not given, current selectionStart is used. */ removeStyleObject: function(isBeginningOfLine, index) { var cursorLocation = this.get2DCursorLocation(index), lineIndex = cursorLocation.lineIndex, charIndex = cursorLocation.charIndex; if (isBeginningOfLine) { var textLines = this.text.split(this._reNewline), textOnPreviousLine = textLines[lineIndex - 1], newCharIndexOnPrevLine = textOnPreviousLine ? textOnPreviousLine.length : 0; if (!this.styles[lineIndex - 1]) { this.styles[lineIndex - 1] = { }; } for (charIndex in this.styles[lineIndex]) { this.styles[lineIndex - 1][parseInt(charIndex, 10) + newCharIndexOnPrevLine] = this.styles[lineIndex][charIndex]; } this.shiftLineStyles(lineIndex, -1); } else { var currentLineStyles = this.styles[lineIndex]; if (currentLineStyles) { var offset = this.selectionStart === this.selectionEnd ? -1 : 0; delete currentLineStyles[charIndex + offset]; // console.log('deleting', lineIndex, charIndex + offset); } var currentLineStylesCloned = clone(currentLineStyles); // shift all styles by 1 backwards for (var i in currentLineStylesCloned) { var numericIndex = parseInt(i, 10); if (numericIndex >= charIndex && numericIndex !== 0) { currentLineStyles[numericIndex - 1] = currentLineStylesCloned[numericIndex]; delete currentLineStyles[numericIndex]; } } } }, /** * Inserts new line */ insertNewline: function() { this.insertChars('\n'); } }); })(); fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.prototype */ { /** * Initializes "dbclick" event handler */ initDoubleClickSimulation: function() { // for double click this.__lastClickTime = +new Date(); // for triple click this.__lastLastClickTime = +new Date(); this.__lastPointer = { }; this.on('mousedown', this.onMouseDown.bind(this)); }, onMouseDown: function(options) { this.__newClickTime = +new Date(); var newPointer = this.canvas.getPointer(options.e); if (this.isTripleClick(newPointer)) { this.fire('tripleclick', options); this._stopEvent(options.e); } else if (this.isDoubleClick(newPointer)) { this.fire('dblclick', options); this._stopEvent(options.e); } this.__lastLastClickTime = this.__lastClickTime; this.__lastClickTime = this.__newClickTime; this.__lastPointer = newPointer; this.__lastIsEditing = this.isEditing; this.__lastSelected = this.selected; }, isDoubleClick: function(newPointer) { return this.__newClickTime - this.__lastClickTime < 500 && this.__lastPointer.x === newPointer.x && this.__lastPointer.y === newPointer.y && this.__lastIsEditing; }, isTripleClick: function(newPointer) { return this.__newClickTime - this.__lastClickTime < 500 && this.__lastClickTime - this.__lastLastClickTime < 500 && this.__lastPointer.x === newPointer.x && this.__lastPointer.y === newPointer.y; }, /** * @private */ _stopEvent: function(e) { e.preventDefault && e.preventDefault(); e.stopPropagation && e.stopPropagation(); }, /** * Initializes event handlers related to cursor or selection */ initCursorSelectionHandlers: function() { this.initSelectedHandler(); this.initMousedownHandler(); this.initMousemoveHandler(); this.initMouseupHandler(); this.initClicks(); }, /** * Initializes double and triple click event handlers */ initClicks: function() { this.on('dblclick', function(options) { this.selectWord(this.getSelectionStartFromPointer(options.e)); }); this.on('tripleclick', function(options) { this.selectLine(this.getSelectionStartFromPointer(options.e)); }); }, /** * Initializes "mousedown" event handler */ initMousedownHandler: function() { this.on('mousedown', function(options) { var pointer = this.canvas.getPointer(options.e); this.__mousedownX = pointer.x; this.__mousedownY = pointer.y; this.__isMousedown = true; if (this.hiddenTextarea && this.canvas) { this.canvas.wrapperEl.appendChild(this.hiddenTextarea); } if (this.selected) { this.setCursorByClick(options.e); } if (this.isEditing) { this.__selectionStartOnMouseDown = this.selectionStart; this.initDelayedCursor(true); } }); }, /** * Initializes "mousemove" event handler */ initMousemoveHandler: function() { this.on('mousemove', function(options) { if (!this.__isMousedown || !this.isEditing) { return; } var newSelectionStart = this.getSelectionStartFromPointer(options.e); if (newSelectionStart >= this.__selectionStartOnMouseDown) { this.setSelectionStart(this.__selectionStartOnMouseDown); this.setSelectionEnd(newSelectionStart); } else { this.setSelectionStart(newSelectionStart); this.setSelectionEnd(this.__selectionStartOnMouseDown); } }); }, /** * @private */ _isObjectMoved: function(e) { var pointer = this.canvas.getPointer(e); return this.__mousedownX !== pointer.x || this.__mousedownY !== pointer.y; }, /** * Initializes "mouseup" event handler */ initMouseupHandler: function() { this.on('mouseup', function(options) { this.__isMousedown = false; if (this._isObjectMoved(options.e)) { return; } if (this.__lastSelected) { this.enterEditing(); this.initDelayedCursor(true); } this.selected = true; }); }, /** * Changes cursor location in a text depending on passed pointer (x/y) object * @param {Event} e Event object */ setCursorByClick: function(e) { var newSelectionStart = this.getSelectionStartFromPointer(e); if (e.shiftKey) { if (newSelectionStart < this.selectionStart) { this.setSelectionEnd(this.selectionStart); this.setSelectionStart(newSelectionStart); } else { this.setSelectionEnd(newSelectionStart); } } else { this.setSelectionStart(newSelectionStart); this.setSelectionEnd(newSelectionStart); } }, /** * @private * @param {Event} e Event object * @return {Object} Coordinates of a pointer (x, y) */ _getLocalRotatedPointer: function(e) { var pointer = this.canvas.getPointer(e), pClicked = new fabric.Point(pointer.x, pointer.y), pLeftTop = new fabric.Point(this.left, this.top), rotated = fabric.util.rotatePoint( pClicked, pLeftTop, fabric.util.degreesToRadians(-this.angle)); return this.getLocalPointer(e, rotated); }, /** * Returns index of a character corresponding to where an object was clicked * @param {Event} e Event object * @return {Number} Index of a character */ getSelectionStartFromPointer: function(e) { var mouseOffset = this._getLocalRotatedPointer(e), textLines = this.text.split(this._reNewline), prevWidth = 0, width = 0, height = 0, charIndex = 0, newSelectionStart; for (var i = 0, len = textLines.length; i < len; i++) { height += this._getHeightOfLine(this.ctx, i) * this.scaleY; var widthOfLine = this._getWidthOfLine(this.ctx, i, textLines), lineLeftOffset = this._getLineLeftOffset(widthOfLine); width = lineLeftOffset * this.scaleX; if (this.flipX) { // when oject is horizontally flipped we reverse chars textLines[i] = textLines[i].split('').reverse().join(''); } for (var j = 0, jlen = textLines[i].length; j < jlen; j++) { var _char = textLines[i][j]; prevWidth = width; width += this._getWidthOfChar(this.ctx, _char, i, this.flipX ? jlen - j : j) * this.scaleX; if (height <= mouseOffset.y || width <= mouseOffset.x) { charIndex++; continue; } return this._getNewSelectionStartFromOffset( mouseOffset, prevWidth, width, charIndex + i, jlen); } if (mouseOffset.y < height) { return this._getNewSelectionStartFromOffset( mouseOffset, prevWidth, width, charIndex + i, jlen); } } // clicked somewhere after all chars, so set at the end if (typeof newSelectionStart === 'undefined') { return this.text.length; } }, /** * @private */ _getNewSelectionStartFromOffset: function(mouseOffset, prevWidth, width, index, jlen) { var distanceBtwLastCharAndCursor = mouseOffset.x - prevWidth, distanceBtwNextCharAndCursor = width - mouseOffset.x, offset = distanceBtwNextCharAndCursor > distanceBtwLastCharAndCursor ? 0 : 1, newSelectionStart = index + offset; // if object is horizontally flipped, mirror cursor location from the end if (this.flipX) { newSelectionStart = jlen - newSelectionStart; } if (newSelectionStart > this.text.length) { newSelectionStart = this.text.length; } return newSelectionStart; } }); fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.prototype */ { /** * Initializes hidden textarea (needed to bring up keyboard in iOS) */ initHiddenTextarea: function() { this.hiddenTextarea = fabric.document.createElement('textarea'); this.hiddenTextarea.setAttribute('autocapitalize', 'off'); this.hiddenTextarea.style.cssText = 'position: absolute; top: 0; left: -9999px'; fabric.document.body.appendChild(this.hiddenTextarea); fabric.util.addListener(this.hiddenTextarea, 'keydown', this.onKeyDown.bind(this)); fabric.util.addListener(this.hiddenTextarea, 'keypress', this.onKeyPress.bind(this)); fabric.util.addListener(this.hiddenTextarea, 'copy', this.copy.bind(this)); fabric.util.addListener(this.hiddenTextarea, 'paste', this.paste.bind(this)); if (!this._clickHandlerInitialized && this.canvas) { fabric.util.addListener(this.canvas.upperCanvasEl, 'click', this.onClick.bind(this)); this._clickHandlerInitialized = true; } }, /** * @private */ _keysMap: { 8: 'removeChars', 9: 'exitEditing', 27: 'exitEditing', 13: 'insertNewline', 33: 'moveCursorUp', 34: 'moveCursorDown', 35: 'moveCursorRight', 36: 'moveCursorLeft', 37: 'moveCursorLeft', 38: 'moveCursorUp', 39: 'moveCursorRight', 40: 'moveCursorDown', 46: 'forwardDelete' }, /** * @private */ _ctrlKeysMap: { 65: 'selectAll', 88: 'cut' }, onClick: function() { // No need to trigger click event here, focus is enough to have the keyboard appear on Android this.hiddenTextarea && this.hiddenTextarea.focus(); }, /** * Handles keyup event * @param {Event} e Event object */ onKeyDown: function(e) { if (!this.isEditing) { return; } if (e.keyCode in this._keysMap) { this[this._keysMap[e.keyCode]](e); } else if ((e.keyCode in this._ctrlKeysMap) && (e.ctrlKey || e.metaKey)) { this[this._ctrlKeysMap[e.keyCode]](e); } else { return; } e.stopImmediatePropagation(); e.preventDefault(); this.canvas && this.canvas.renderAll(); }, /** * Forward delete */ forwardDelete: function(e) { if (this.selectionStart === this.selectionEnd) { this.moveCursorRight(e); } this.removeChars(e); }, /** * Copies selected text * @param {Event} e Event object */ copy: function(e) { var selectedText = this.getSelectedText(), clipboardData = this._getClipboardData(e); // Check for backward compatibility with old browsers if (clipboardData) { clipboardData.setData('text', selectedText); } this.copiedText = selectedText; this.copiedStyles = this.getSelectionStyles( this.selectionStart, this.selectionEnd); }, /** * Pastes text * @param {Event} e Event object */ paste: function(e) { var copiedText = null, clipboardData = this._getClipboardData(e); // Check for backward compatibility with old browsers if (clipboardData) { copiedText = clipboardData.getData('text'); } else { copiedText = this.copiedText; } if (copiedText) { this.insertChars(copiedText); } }, /** * Cuts text * @param {Event} e Event object */ cut: function(e) { if (this.selectionStart === this.selectionEnd) { return; } this.copy(); this.removeChars(e); }, /** * @private * @param {Event} e Event object * @return {Object} Clipboard data object */ _getClipboardData: function(e) { return e && (e.clipboardData || fabric.window.clipboardData); }, /** * Handles keypress event * @param {Event} e Event object */ onKeyPress: function(e) { if (!this.isEditing || e.metaKey || e.ctrlKey) { return; } if (e.which !== 0) { this.insertChars(String.fromCharCode(e.which)); } e.stopPropagation(); }, /** * Gets start offset of a selection * @param {Event} e Event object * @param {Boolean} isRight * @return {Number} */ getDownCursorOffset: function(e, isRight) { var selectionProp = isRight ? this.selectionEnd : this.selectionStart, textLines = this.text.split(this._reNewline), _char, lineLeftOffset, textBeforeCursor = this.text.slice(0, selectionProp), textAfterCursor = this.text.slice(selectionProp), textOnSameLineBeforeCursor = textBeforeCursor.slice(textBeforeCursor.lastIndexOf('\n') + 1), textOnSameLineAfterCursor = textAfterCursor.match(/(.*)\n?/)[1], textOnNextLine = (textAfterCursor.match(/.*\n(.*)\n?/) || { })[1] || '', cursorLocation = this.get2DCursorLocation(selectionProp); // if on last line, down cursor goes to end of line if (cursorLocation.lineIndex === textLines.length - 1 || e.metaKey || e.keyCode === 34) { // move to the end of a text return this.text.length - selectionProp; } var widthOfSameLineBeforeCursor = this._getWidthOfLine(this.ctx, cursorLocation.lineIndex, textLines); lineLeftOffset = this._getLineLeftOffset(widthOfSameLineBeforeCursor); var widthOfCharsOnSameLineBeforeCursor = lineLeftOffset, lineIndex = cursorLocation.lineIndex; for (var i = 0, len = textOnSameLineBeforeCursor.length; i < len; i++) { _char = textOnSameLineBeforeCursor[i]; widthOfCharsOnSameLineBeforeCursor += this._getWidthOfChar(this.ctx, _char, lineIndex, i); } var indexOnNextLine = this._getIndexOnNextLine( cursorLocation, textOnNextLine, widthOfCharsOnSameLineBeforeCursor, textLines); return textOnSameLineAfterCursor.length + 1 + indexOnNextLine; }, /** * @private */ _getIndexOnNextLine: function(cursorLocation, textOnNextLine, widthOfCharsOnSameLineBeforeCursor, textLines) { var lineIndex = cursorLocation.lineIndex + 1, widthOfNextLine = this._getWidthOfLine(this.ctx, lineIndex, textLines), lineLeftOffset = this._getLineLeftOffset(widthOfNextLine), widthOfCharsOnNextLine = lineLeftOffset, indexOnNextLine = 0, foundMatch; for (var j = 0, jlen = textOnNextLine.length; j < jlen; j++) { var _char = textOnNextLine[j], widthOfChar = this._getWidthOfChar(this.ctx, _char, lineIndex, j); widthOfCharsOnNextLine += widthOfChar; if (widthOfCharsOnNextLine > widthOfCharsOnSameLineBeforeCursor) { foundMatch = true; var leftEdge = widthOfCharsOnNextLine - widthOfChar, rightEdge = widthOfCharsOnNextLine, offsetFromLeftEdge = Math.abs(leftEdge - widthOfCharsOnSameLineBeforeCursor), offsetFromRightEdge = Math.abs(rightEdge - widthOfCharsOnSameLineBeforeCursor); indexOnNextLine = offsetFromRightEdge < offsetFromLeftEdge ? j + 1 : j; break; } } // reached end if (!foundMatch) { indexOnNextLine = textOnNextLine.length; } return indexOnNextLine; }, /** * Moves cursor down * @param {Event} e Event object */ moveCursorDown: function(e) { this.abortCursorAnimation(); this._currentCursorOpacity = 1; var offset = this.getDownCursorOffset(e, this._selectionDirection === 'right'); if (e.shiftKey) { this.moveCursorDownWithShift(offset); } else { this.moveCursorDownWithoutShift(offset); } this.initDelayedCursor(); }, /** * Moves cursor down without keeping selection * @param {Number} offset */ moveCursorDownWithoutShift: function(offset) { this._selectionDirection = 'right'; this.selectionStart += offset; if (this.selectionStart > this.text.length) { this.selectionStart = this.text.length; } this.selectionEnd = this.selectionStart; }, /** * private */ swapSelectionPoints: function() { var swapSel = this.selectionEnd; this.selectionEnd = this.selectionStart; this.selectionStart = swapSel; }, /** * Moves cursor down while keeping selection * @param {Number} offset */ moveCursorDownWithShift: function(offset) { if (this.selectionEnd === this.selectionStart) { this._selectionDirection = 'right'; } var prop = this._selectionDirection === 'right' ? 'selectionEnd' : 'selectionStart'; this[prop] += offset; if (this.selectionEnd < this.selectionStart && this._selectionDirection === 'left') { this.swapSelectionPoints(); this._selectionDirection = 'right'; } if (this.selectionEnd > this.text.length) { this.selectionEnd = this.text.length; } }, /** * @param {Event} e Event object * @param {Boolean} isRight * @return {Number} */ getUpCursorOffset: function(e, isRight) { var selectionProp = isRight ? this.selectionEnd : this.selectionStart, cursorLocation = this.get2DCursorLocation(selectionProp); // if on first line, up cursor goes to start of line if (cursorLocation.lineIndex === 0 || e.metaKey || e.keyCode === 33) { return selectionProp; } var textBeforeCursor = this.text.slice(0, selectionProp), textOnSameLineBeforeCursor = textBeforeCursor.slice(textBeforeCursor.lastIndexOf('\n') + 1), textOnPreviousLine = (textBeforeCursor.match(/\n?(.*)\n.*$/) || {})[1] || '', textLines = this.text.split(this._reNewline), _char, widthOfSameLineBeforeCursor = this._getWidthOfLine(this.ctx, cursorLocation.lineIndex, textLines), lineLeftOffset = this._getLineLeftOffset(widthOfSameLineBeforeCursor), widthOfCharsOnSameLineBeforeCursor = lineLeftOffset, lineIndex = cursorLocation.lineIndex; for (var i = 0, len = textOnSameLineBeforeCursor.length; i < len; i++) { _char = textOnSameLineBeforeCursor[i]; widthOfCharsOnSameLineBeforeCursor += this._getWidthOfChar(this.ctx, _char, lineIndex, i); } var indexOnPrevLine = this._getIndexOnPrevLine( cursorLocation, textOnPreviousLine, widthOfCharsOnSameLineBeforeCursor, textLines); return textOnPreviousLine.length - indexOnPrevLine + textOnSameLineBeforeCursor.length; }, /** * @private */ _getIndexOnPrevLine: function(cursorLocation, textOnPreviousLine, widthOfCharsOnSameLineBeforeCursor, textLines) { var lineIndex = cursorLocation.lineIndex - 1, widthOfPreviousLine = this._getWidthOfLine(this.ctx, lineIndex, textLines), lineLeftOffset = this._getLineLeftOffset(widthOfPreviousLine), widthOfCharsOnPreviousLine = lineLeftOffset, indexOnPrevLine = 0, foundMatch; for (var j = 0, jlen = textOnPreviousLine.length; j < jlen; j++) { var _char = textOnPreviousLine[j], widthOfChar = this._getWidthOfChar(this.ctx, _char, lineIndex, j); widthOfCharsOnPreviousLine += widthOfChar; if (widthOfCharsOnPreviousLine > widthOfCharsOnSameLineBeforeCursor) { foundMatch = true; var leftEdge = widthOfCharsOnPreviousLine - widthOfChar, rightEdge = widthOfCharsOnPreviousLine, offsetFromLeftEdge = Math.abs(leftEdge - widthOfCharsOnSameLineBeforeCursor), offsetFromRightEdge = Math.abs(rightEdge - widthOfCharsOnSameLineBeforeCursor); indexOnPrevLine = offsetFromRightEdge < offsetFromLeftEdge ? j : (j - 1); break; } } // reached end if (!foundMatch) { indexOnPrevLine = textOnPreviousLine.length - 1; } return indexOnPrevLine; }, /** * Moves cursor up * @param {Event} e Event object */ moveCursorUp: function(e) { this.abortCursorAnimation(); this._currentCursorOpacity = 1; var offset = this.getUpCursorOffset(e, this._selectionDirection === 'right'); if (e.shiftKey) { this.moveCursorUpWithShift(offset); } else { this.moveCursorUpWithoutShift(offset); } this.initDelayedCursor(); }, /** * Moves cursor up with shift * @param {Number} offset */ moveCursorUpWithShift: function(offset) { if (this.selectionEnd === this.selectionStart) { this._selectionDirection = 'left'; } var prop = this._selectionDirection === 'right' ? 'selectionEnd' : 'selectionStart'; this[prop] -= offset; if (this.selectionEnd < this.selectionStart && this._selectionDirection === 'right') { this.swapSelectionPoints(); this._selectionDirection = 'left'; } if (this.selectionStart < 0) { this.selectionStart = 0; } }, /** * Moves cursor up without shift * @param {Number} offset */ moveCursorUpWithoutShift: function(offset) { if (this.selectionStart === this.selectionEnd) { this.selectionStart -= offset; } if (this.selectionStart < 0) { this.selectionStart = 0; } this.selectionEnd = this.selectionStart; this._selectionDirection = 'left'; }, /** * Moves cursor left * @param {Event} e Event object */ moveCursorLeft: function(e) { if (this.selectionStart === 0 && this.selectionEnd === 0) { return; } this.abortCursorAnimation(); this._currentCursorOpacity = 1; if (e.shiftKey) { this.moveCursorLeftWithShift(e); } else { this.moveCursorLeftWithoutShift(e); } this.initDelayedCursor(); }, /** * @private */ _move: function(e, prop, direction) { if (e.altKey) { this[prop] = this['findWordBoundary' + direction](this[prop]); } else if (e.metaKey || e.keyCode === 35 || e.keyCode === 36 ) { this[prop] = this['findLineBoundary' + direction](this[prop]); } else { this[prop] += (direction === 'Left' ? -1 : 1); } }, /** * @private */ _moveLeft: function(e, prop) { this._move(e, prop, 'Left'); }, /** * @private */ _moveRight: function(e, prop) { this._move(e, prop, 'Right'); }, /** * Moves cursor left without keeping selection * @param {Event} e */ moveCursorLeftWithoutShift: function(e) { this._selectionDirection = 'left'; // only move cursor when there is no selection, // otherwise we discard it, and leave cursor on same place if (this.selectionEnd === this.selectionStart) { this._moveLeft(e, 'selectionStart'); } this.selectionEnd = this.selectionStart; }, /** * Moves cursor left while keeping selection * @param {Event} e */ moveCursorLeftWithShift: function(e) { if (this._selectionDirection === 'right' && this.selectionStart !== this.selectionEnd) { this._moveLeft(e, 'selectionEnd'); } else { this._selectionDirection = 'left'; this._moveLeft(e, 'selectionStart'); // increase selection by one if it's a newline if (this.text.charAt(this.selectionStart) === '\n') { this.selectionStart--; } if (this.selectionStart < 0) { this.selectionStart = 0; } } }, /** * Moves cursor right * @param {Event} e Event object */ moveCursorRight: function(e) { if (this.selectionStart >= this.text.length && this.selectionEnd >= this.text.length) { return; } this.abortCursorAnimation(); this._currentCursorOpacity = 1; if (e.shiftKey) { this.moveCursorRightWithShift(e); } else { this.moveCursorRightWithoutShift(e); } this.initDelayedCursor(); }, /** * Moves cursor right while keeping selection * @param {Event} e */ moveCursorRightWithShift: function(e) { if (this._selectionDirection === 'left' && this.selectionStart !== this.selectionEnd) { this._moveRight(e, 'selectionStart'); } else { this._selectionDirection = 'right'; this._moveRight(e, 'selectionEnd'); // increase selection by one if it's a newline if (this.text.charAt(this.selectionEnd - 1) === '\n') { this.selectionEnd++; } if (this.selectionEnd > this.text.length) { this.selectionEnd = this.text.length; } } }, /** * Moves cursor right without keeping selection * @param {Event} e Event object */ moveCursorRightWithoutShift: function(e) { this._selectionDirection = 'right'; if (this.selectionStart === this.selectionEnd) { this._moveRight(e, 'selectionStart'); this.selectionEnd = this.selectionStart; } else { this.selectionEnd += this.getNumNewLinesInSelectedText(); if (this.selectionEnd > this.text.length) { this.selectionEnd = this.text.length; } this.selectionStart = this.selectionEnd; } }, /** * Inserts a character where cursor is (replacing selection if one exists) * @param {Event} e Event object */ removeChars: function(e) { if (this.selectionStart === this.selectionEnd) { this._removeCharsNearCursor(e); } else { this._removeCharsFromTo(this.selectionStart, this.selectionEnd); } this.selectionEnd = this.selectionStart; this._removeExtraneousStyles(); if (this.canvas) { // TODO: double renderAll gets rid of text box shift happenning sometimes // need to find out what exactly causes it and fix it this.canvas.renderAll().renderAll(); } this.setCoords(); this.fire('changed'); this.canvas && this.canvas.fire('text:changed', { target: this }); }, /** * @private * @param {Event} e Event object */ _removeCharsNearCursor: function(e) { if (this.selectionStart !== 0) { if (e.metaKey) { // remove all till the start of current line var leftLineBoundary = this.findLineBoundaryLeft(this.selectionStart); this._removeCharsFromTo(leftLineBoundary, this.selectionStart); this.selectionStart = leftLineBoundary; } else if (e.altKey) { // remove all till the start of current word var leftWordBoundary = this.findWordBoundaryLeft(this.selectionStart); this._removeCharsFromTo(leftWordBoundary, this.selectionStart); this.selectionStart = leftWordBoundary; } else { var isBeginningOfLine = this.text.slice(this.selectionStart - 1, this.selectionStart) === '\n'; this.removeStyleObject(isBeginningOfLine); this.selectionStart--; this.text = this.text.slice(0, this.selectionStart) + this.text.slice(this.selectionStart + 1); } } } }); /* _TO_SVG_START_ */ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.prototype */ { /** * @private */ _setSVGTextLineText: function(textLine, lineIndex, textSpans, lineHeight, lineTopOffsetMultiplier, textBgRects) { if (!this.styles[lineIndex]) { this.callSuper('_setSVGTextLineText', textLine, lineIndex, textSpans, lineHeight, lineTopOffsetMultiplier); } else { this._setSVGTextLineChars( textLine, lineIndex, textSpans, lineHeight, lineTopOffsetMultiplier, textBgRects); } }, /** * @private */ _setSVGTextLineChars: function(textLine, lineIndex, textSpans, lineHeight, lineTopOffsetMultiplier, textBgRects) { var yProp = lineIndex === 0 || this.useNative ? 'y' : 'dy', chars = textLine.split(''), charOffset = 0, lineLeftOffset = this._getSVGLineLeftOffset(lineIndex), lineTopOffset = this._getSVGLineTopOffset(lineIndex), heightOfLine = this._getHeightOfLine(this.ctx, lineIndex); for (var i = 0, len = chars.length; i < len; i++) { var styleDecl = this.styles[lineIndex][i] || { }; textSpans.push( this._createTextCharSpan( chars[i], styleDecl, lineLeftOffset, lineTopOffset, yProp, charOffset)); var charWidth = this._getWidthOfChar(this.ctx, chars[i], lineIndex, i); if (styleDecl.textBackgroundColor) { textBgRects.push( this._createTextCharBg( styleDecl, lineLeftOffset, lineTopOffset, heightOfLine, charWidth, charOffset)); } charOffset += charWidth; } }, /** * @private */ _getSVGLineLeftOffset: function(lineIndex) { return (this._boundaries && this._boundaries[lineIndex]) ? fabric.util.toFixed(this._boundaries[lineIndex].left, 2) : 0; }, /** * @private */ _getSVGLineTopOffset: function(lineIndex) { var lineTopOffset = 0; for (var j = 0; j <= lineIndex; j++) { lineTopOffset += this._getHeightOfLine(this.ctx, j); } return lineTopOffset - this.height / 2; }, /** * @private */ _createTextCharBg: function(styleDecl, lineLeftOffset, lineTopOffset, heightOfLine, charWidth, charOffset) { return [ //jscs:disable validateIndentation '' //jscs:enable validateIndentation ].join(''); }, /** * @private */ _createTextCharSpan: function(_char, styleDecl, lineLeftOffset, lineTopOffset, yProp, charOffset) { var fillStyles = this.getSvgStyles.call(fabric.util.object.extend({ visible: true, fill: this.fill, stroke: this.stroke, type: 'text' }, styleDecl)); return [ //jscs:disable validateIndentation '', fabric.util.string.escapeXml(_char), '' //jscs:enable validateIndentation ].join(''); } }); /* _TO_SVG_END_ */ (function() { if (typeof document !== 'undefined' && typeof window !== 'undefined') { return; } var DOMParser = require('xmldom').DOMParser, URL = require('url'), HTTP = require('http'), HTTPS = require('https'), Canvas = require('canvas'), Image = require('canvas').Image; /** @private */ function request(url, encoding, callback) { var oURL = URL.parse(url); // detect if http or https is used if ( !oURL.port ) { oURL.port = ( oURL.protocol.indexOf('https:') === 0 ) ? 443 : 80; } // assign request handler based on protocol var reqHandler = ( oURL.port === 443 ) ? HTTPS : HTTP, req = reqHandler.request({ hostname: oURL.hostname, port: oURL.port, path: oURL.path, method: 'GET' }, function(response) { var body = ''; if (encoding) { response.setEncoding(encoding); } response.on('end', function () { callback(body); }); response.on('data', function (chunk) { if (response.statusCode === 200) { body += chunk; } }); }); req.on('error', function(err) { if (err.errno === process.ECONNREFUSED) { fabric.log('ECONNREFUSED: connection refused to ' + oURL.hostname + ':' + oURL.port); } else { fabric.log(err.message); } }); req.end(); } /** @private */ function requestFs(path, callback) { var fs = require('fs'); fs.readFile(path, function (err, data) { if (err) { fabric.log(err); throw err; } else { callback(data); } }); } fabric.util.loadImage = function(url, callback, context) { function createImageAndCallBack(data) { img.src = new Buffer(data, 'binary'); // preserving original url, which seems to be lost in node-canvas img._src = url; callback && callback.call(context, img); } var img = new Image(); if (url && (url instanceof Buffer || url.indexOf('data') === 0)) { img.src = img._src = url; callback && callback.call(context, img); } else if (url && url.indexOf('http') !== 0) { requestFs(url, createImageAndCallBack); } else if (url) { request(url, 'binary', createImageAndCallBack); } else { callback && callback.call(context, url); } }; fabric.loadSVGFromURL = function(url, callback, reviver) { url = url.replace(/^\n\s*/, '').replace(/\?.*$/, '').trim(); if (url.indexOf('http') !== 0) { requestFs(url, function(body) { fabric.loadSVGFromString(body.toString(), callback, reviver); }); } else { request(url, '', function(body) { fabric.loadSVGFromString(body, callback, reviver); }); } }; fabric.loadSVGFromString = function(string, callback, reviver) { var doc = new DOMParser().parseFromString(string); fabric.parseSVGDocument(doc.documentElement, function(results, options) { callback && callback(results, options); }, reviver); }; fabric.util.getScript = function(url, callback) { request(url, '', function(body) { eval(body); callback && callback(); }); }; fabric.Image.fromObject = function(object, callback) { fabric.util.loadImage(object.src, function(img) { var oImg = new fabric.Image(img); oImg._initConfig(object); oImg._initFilters(object, function(filters) { oImg.filters = filters || [ ]; callback && callback(oImg); }); }); }; /** * Only available when running fabric on node.js * @param {Number} width Canvas width * @param {Number} height Canvas height * @param {Object} [options] Options to pass to FabricCanvas. * @param {Object} [nodeCanvasOptions] Options to pass to NodeCanvas. * @return {Object} wrapped canvas instance */ fabric.createCanvasForNode = function(width, height, options, nodeCanvasOptions) { nodeCanvasOptions = nodeCanvasOptions || options; var canvasEl = fabric.document.createElement('canvas'), nodeCanvas = new Canvas(width || 600, height || 600, nodeCanvasOptions); // jsdom doesn't create style on canvas element, so here be temp. workaround canvasEl.style = { }; canvasEl.width = nodeCanvas.width; canvasEl.height = nodeCanvas.height; var FabricCanvas = fabric.Canvas || fabric.StaticCanvas, fabricCanvas = new FabricCanvas(canvasEl, options); fabricCanvas.contextContainer = nodeCanvas.getContext('2d'); fabricCanvas.nodeCanvas = nodeCanvas; fabricCanvas.Font = Canvas.Font; return fabricCanvas; }; /** @ignore */ fabric.StaticCanvas.prototype.createPNGStream = function() { return this.nodeCanvas.createPNGStream(); }; fabric.StaticCanvas.prototype.createJPEGStream = function(opts) { return this.nodeCanvas.createJPEGStream(opts); }; var origSetWidth = fabric.StaticCanvas.prototype.setWidth; fabric.StaticCanvas.prototype.setWidth = function(width, options) { origSetWidth.call(this, width, options); this.nodeCanvas.width = width; return this; }; if (fabric.Canvas) { fabric.Canvas.prototype.setWidth = fabric.StaticCanvas.prototype.setWidth; } var origSetHeight = fabric.StaticCanvas.prototype.setHeight; fabric.StaticCanvas.prototype.setHeight = function(height, options) { origSetHeight.call(this, height, options); this.nodeCanvas.height = height; return this; }; if (fabric.Canvas) { fabric.Canvas.prototype.setHeight = fabric.StaticCanvas.prototype.setHeight; } })(); ================================================ FILE: src/libs/fabric.require1-4-12.min.js ================================================ var fabric=fabric||{version:"1.4.12"};"undefined"!=typeof exports&&(exports.fabric=fabric),"undefined"!=typeof document&&"undefined"!=typeof window?(fabric.document=document,fabric.window=window):(fabric.document=require("jsdom").jsdom(""),fabric.window=fabric.document.createWindow()),fabric.isTouchSupported="ontouchstart"in fabric.document.documentElement,fabric.isLikelyNode="undefined"!=typeof Buffer&&"undefined"==typeof window,fabric.SHARED_ATTRIBUTES=["display","transform","fill","fill-opacity","fill-rule","opacity","stroke","stroke-dasharray","stroke-linecap","stroke-linejoin","stroke-miterlimit","stroke-opacity","stroke-width"],fabric.DPI=96,function(){function a(a,b){this.__eventListeners[a]&&(b?fabric.util.removeFromArray(this.__eventListeners[a],b):this.__eventListeners[a].length=0)}function b(a,b){if(this.__eventListeners||(this.__eventListeners={}),1===arguments.length)for(var c in a)this.on(c,a[c]);else this.__eventListeners[a]||(this.__eventListeners[a]=[]),this.__eventListeners[a].push(b);return this}function c(b,c){if(this.__eventListeners){if(0===arguments.length)this.__eventListeners={};else if(1===arguments.length&&"object"==typeof arguments[0])for(var d in b)a.call(this,d,b[d]);else a.call(this,b,c);return this}}function d(a,b){if(this.__eventListeners){var c=this.__eventListeners[a];if(c){for(var d=0,e=c.length;d-1},complexity:function(){return this.getObjects().reduce(function(a,b){return a+=b.complexity?b.complexity():0},0)}},function(a){var b=Math.sqrt,c=Math.atan2,d=Math.PI/180;fabric.util={removeFromArray:function(a,b){var c=a.indexOf(b);return c!==-1&&a.splice(c,1),a},getRandomInt:function(a,b){return Math.floor(Math.random()*(b-a+1))+a},degreesToRadians:function(a){return a*d},radiansToDegrees:function(a){return a/d},rotatePoint:function(a,b,c){var d=Math.sin(c),e=Math.cos(c);a.subtractEquals(b);var f=a.x*e-a.y*d,g=a.x*d+a.y*e;return new fabric.Point(f,g).addEquals(b)},transformPoint:function(a,b,c){return c?new fabric.Point(b[0]*a.x+b[1]*a.y,b[2]*a.x+b[3]*a.y):new fabric.Point(b[0]*a.x+b[1]*a.y+b[4],b[2]*a.x+b[3]*a.y+b[5])},invertTransform:function(a){var b=a.slice(),c=1/(a[0]*a[3]-a[1]*a[2]);b=[c*a[3],-c*a[1],-c*a[2],c*a[0],0,0];var d=fabric.util.transformPoint({x:a[4],y:a[5]},b);return b[4]=-d.x,b[5]=-d.y,b},toFixed:function(a,b){return parseFloat(Number(a).toFixed(b))},parseUnit:function(a){var b=/\D{0,2}$/.exec(a),c=parseFloat(a);switch(b[0]){case"mm":return c*fabric.DPI/25.4;case"cm":return c*fabric.DPI/2.54;case"in":return c*fabric.DPI;case"pt":return c*fabric.DPI/72;case"pc":return c*fabric.DPI/72*12;default:return c}},falseFunction:function(){return!1},getKlass:function(a,b){return a=fabric.util.string.camelize(a.charAt(0).toUpperCase()+a.slice(1)),fabric.util.resolveNamespace(b)[a]},resolveNamespace:function(b){if(!b)return fabric;for(var c=b.split("."),d=c.length,e=a||fabric.window,f=0;fd;)d+=h[n++%m],d>k&&(d=k),a[o?"lineTo":"moveTo"](d,0),o=!o;a.restore()},createCanvasElement:function(a){return a||(a=fabric.document.createElement("canvas")),a.getContext||"undefined"==typeof G_vmlCanvasManager||G_vmlCanvasManager.initElement(a),a},createImage:function(){return fabric.isLikelyNode?new(require("canvas").Image):fabric.document.createElement("img")},createAccessors:function(a){for(var b=a.prototype,c=b.stateProperties.length;c--;){var d=b.stateProperties[c],e=d.charAt(0).toUpperCase()+d.slice(1),f="set"+e,g="get"+e;b[g]||(b[g]=function(a){return new Function('return this.get("'+a+'")')}(d)),b[f]||(b[f]=function(a){return new Function("value",'return this.set("'+a+'", value)')}(d))}},clipContext:function(a,b){b.save(),b.beginPath(),a.clipTo(b),b.clip()},multiplyTransformMatrices:function(a,b){for(var c=[[a[0],a[2],a[4]],[a[1],a[3],a[5]],[0,0,1]],d=[[b[0],b[2],b[4]],[b[1],b[3],b[5]],[0,0,1]],e=[],f=0;f<3;f++){e[f]=[];for(var g=0;g<3;g++){for(var h=0,i=0;i<3;i++)h+=c[f][i]*d[i][g];e[f][g]=h}}return[e[0][0],e[1][0],e[0][1],e[1][1],e[0][2],e[1][2]]},getFunctionBody:function(a){return(String(a).match(/function[^{]*\{([\s\S]*)\}/)||{})[1]},isTransparent:function(a,b,c,d){d>0&&(b>d?b-=d:b=0,c>d?c-=d:c=0);for(var e=!0,f=a.getImageData(b,c,2*d||1,2*d||1),g=3,h=f.data.length;g0?G-=2*m:1===j&&G<0&&(G+=2*m);for(var H=Math.ceil(Math.abs(G/m*2)),I=[],J=G/H,K=8/3*Math.sin(J/4)*Math.sin(J/4)/Math.sin(J/2),L=F+J,M=0;M=e?f-e:2*Math.PI-(e-f)}function h(a,b,e,f,g,h,i,j){var k=d.call(arguments);if(c[k])return c[k];var r,s,t,u,v,w,x,y,l=Math.sqrt,m=Math.min,n=Math.max,o=Math.abs,p=[],q=[[],[]];s=6*a-12*e+6*g,r=-3*a+9*e-9*g+3*i,t=3*e-3*a;for(var z=0;z<2;++z)if(z>0&&(s=6*b-12*f+6*h,r=-3*b+9*f-9*h+3*j,t=3*f-3*b),o(r)<1e-12){if(o(s)<1e-12)continue;u=-t/s,0=b})}function d(a,b){return e(a,b,function(a,b){return a>>0;if(0===c)return-1;var d=0;if(arguments.length>0&&(d=Number(arguments[1]),d!==d?d=0:0!==d&&d!==Number.POSITIVE_INFINITY&&d!==Number.NEGATIVE_INFINITY&&(d=(d>0||-1)*Math.floor(Math.abs(d)))),d>=c)return-1;for(var e=d>=0?d:Math.max(c-Math.abs(d),0);e>>0;c>>0;d>>0;c>>0;c>>0;e>>0,c=0;if(arguments.length>1)d=arguments[1];else for(;;){if(c in this){d=this[c++];break}if(++c>=b)throw new TypeError}for(;c/g,">")}String.prototype.trim||(String.prototype.trim=function(){return this.replace(/^[\s\xA0]+/,"").replace(/[\s\xA0]+$/,"")}),fabric.util.string={camelize:a,capitalize:b,escapeXml:c}}(),function(){var a=Array.prototype.slice,b=Function.prototype.apply,c=function(){};Function.prototype.bind||(Function.prototype.bind=function(d){var g,e=this,f=a.call(arguments,1);return g=f.length?function(){return b.call(e,this instanceof c?this:d,f.concat(a.call(arguments)))}:function(){return b.call(e,this instanceof c?this:d,arguments)},c.prototype=this.prototype,g.prototype=new c,g})}(),function(){function e(){}function f(b){var c=this.constructor.superclass.prototype[b];return arguments.length>1?c.apply(this,a.call(arguments,1)):c.call(this)}function g(){function h(){this.initialize.apply(this,arguments)}var c=null,g=a.call(arguments,0);"function"==typeof g[0]&&(c=g.shift()),h.superclass=c,h.subclasses=[],c&&(e.prototype=c.prototype,h.prototype=new e,c.subclasses.push(h));for(var i=0,j=g.length;i-1?a.prototype[e]=function(a){return function(){var c=this.constructor.superclass;this.constructor.superclass=d;var e=b[a].apply(this,arguments);if(this.constructor.superclass=c,"initialize"!==a)return e}}(e):a.prototype[e]=b[e],c&&(b.toString!==Object.prototype.toString&&(a.prototype.toString=b.toString),b.valueOf!==Object.prototype.valueOf&&(a.prototype.valueOf=b.valueOf))};fabric.util.createClass=g}(),function(){function b(a){var c,d,b=Array.prototype.slice.call(arguments,1),e=b.length;for(d=0;d-1?f(a,b.match(/opacity:\s*(\d?\.?\d*)/)[1]):a;for(var d in b)if("opacity"===d)f(a,b[d]);else{var e="float"===d||"cssFloat"===d?"undefined"==typeof c.styleFloat?"cssFloat":"styleFloat":d;c[e]=b[d]}return a}var b=fabric.document.createElement("div"),c="string"==typeof b.style.opacity,d="string"==typeof b.style.filter,e=/alpha\s*\(\s*opacity\s*=\s*([^\)]+)\)/,f=function(a){return a};c?f=function(a,b){return a.style.opacity=b,a}:d&&(f=function(a,b){var c=a.style;return a.currentStyle&&!a.currentStyle.hasLayout&&(c.zoom=1),e.test(c.filter)?(b=b>=.9999?"":"alpha(opacity="+100*b+")",c.filter=c.filter.replace(e,b)):c.filter+=" alpha(opacity="+100*b+")",a}),fabric.util.setStyle=a}(),function(){function b(a){return"string"==typeof a?fabric.document.getElementById(a):a}function e(a,b){var c=fabric.document.createElement(a);for(var d in b)"class"===d?c.className=b[d]:"for"===d?c.htmlFor=b[d]:c.setAttribute(d,b[d]);return c}function f(a,b){a&&(" "+a.className+" ").indexOf(" "+b+" ")===-1&&(a.className+=(a.className?" ":"")+b)}function g(a,b,c){return"string"==typeof b&&(b=e(b,c)),a.parentNode&&a.parentNode.replaceChild(b,a),b.appendChild(a),b}function h(a,b){var c,d,e=0,f=0,g=fabric.document.documentElement,h=fabric.document.body||{scrollLeft:0,scrollTop:0};for(d=a;a&&a.parentNode&&!c;)a=a.parentNode,1===a.nodeType&&"fixed"===fabric.util.getElementStyle(a,"position")&&(c=a),1===a.nodeType&&d!==b&&"absolute"===fabric.util.getElementStyle(a,"position")?(e=0,f=0):a===fabric.document?(e=h.scrollLeft||g.scrollLeft||0,f=h.scrollTop||g.scrollTop||0):(e+=a.scrollLeft||0,f+=a.scrollTop||0);return{left:e,top:f}}function i(a){var b,f,c=a&&a.ownerDocument,d={left:0,top:0},e={left:0,top:0},g={borderLeftWidth:"left",borderTopWidth:"top",paddingLeft:"left",paddingTop:"top"};if(!c)return{left:0,top:0};for(var h in g)e[g[h]]+=parseInt(j(a,h),10)||0;return b=c.documentElement,"undefined"!=typeof a.getBoundingClientRect&&(d=a.getBoundingClientRect()),f=fabric.util.getScrollLeftTop(a,null),{left:d.left+f.left-(b.clientLeft||0)+e.left,top:d.top+f.top-(b.clientTop||0)+e.top}}var c,a=Array.prototype.slice,d=function(b){return a.call(b,0)};try{c=d(fabric.document.childNodes)instanceof Array}catch(a){}c||(d=function(a){for(var b=new Array(a.length),c=a.length;c--;)b[c]=a[c];return b});var j;j=fabric.document.defaultView&&fabric.document.defaultView.getComputedStyle?function(a,b){var c=fabric.document.defaultView.getComputedStyle(a,null);return c?c[b]:void 0}:function(a,b){var c=a.style[b];return!c&&a.currentStyle&&(c=a.currentStyle[b]),c},function(){function c(a){return"undefined"!=typeof a.onselectstart&&(a.onselectstart=fabric.util.falseFunction),b?a.style[b]="none":"string"==typeof a.unselectable&&(a.unselectable="on"),a}function d(a){return"undefined"!=typeof a.onselectstart&&(a.onselectstart=null),b?a.style[b]="":"string"==typeof a.unselectable&&(a.unselectable=""),a}var a=fabric.document.documentElement.style,b="userSelect"in a?"userSelect":"MozUserSelect"in a?"MozUserSelect":"WebkitUserSelect"in a?"WebkitUserSelect":"KhtmlUserSelect"in a?"KhtmlUserSelect":"";fabric.util.makeElementUnselectable=c,fabric.util.makeElementSelectable=d}(),function(){function a(a,b){var c=fabric.document.getElementsByTagName("head")[0],d=fabric.document.createElement("script"),e=!0;d.onload=d.onreadystatechange=function(a){if(e){if("string"==typeof this.readyState&&"loaded"!==this.readyState&&"complete"!==this.readyState)return;e=!1,b(a||fabric.window.event),d=d.onload=d.onreadystatechange=null}},d.src=a,c.appendChild(d)}fabric.util.getScript=a}(),fabric.util.getById=b,fabric.util.toArray=d,fabric.util.makeElement=e,fabric.util.addClass=f,fabric.util.wrapElement=g,fabric.util.getScrollLeftTop=h,fabric.util.getElementOffset=i,fabric.util.getElementStyle=j}(),function(){function a(a,b){return a+(/\?/.test(a)?"&":"?")+b}function c(){}function d(d,e){e||(e={});var i,f=e.method?e.method.toUpperCase():"GET",g=e.onComplete||function(){},h=b();return h.onreadystatechange=function(){4===h.readyState&&(g(h),h.onreadystatechange=c)},"GET"===f&&(i=null,"string"==typeof e.parameters&&(d=a(d,e.parameters))),h.open(f,d,!0),"POST"!==f&&"PUT"!==f||h.setRequestHeader("Content-Type","application/x-www-form-urlencoded"),h.send(i),h}var b=function(){for(var a=[function(){return new ActiveXObject("Microsoft.XMLHTTP")},function(){return new ActiveXObject("Msxml2.XMLHTTP")},function(){return new ActiveXObject("Msxml2.XMLHTTP.3.0")},function(){return new XMLHttpRequest}],b=a.length;b--;)try{var c=a[b]();if(c)return a[b]}catch(a){}}();fabric.util.request=d}(),fabric.log=function(){},fabric.warn=function(){},"undefined"!=typeof console&&["log","warn"].forEach(function(a){"undefined"!=typeof console[a]&&console[a].apply&&(fabric[a]=function(){return console[a].apply(console,arguments)})}),function(){function a(a){c(function(b){a||(a={});var g,d=b||+new Date,e=a.duration||500,f=d+e,h=a.onChange||function(){},i=a.abort||function(){return!1},j=a.easing||function(a,b,c,d){return-c*Math.cos(a/d*(Math.PI/2))+c+b},k="startValue"in a?a.startValue:0,l="endValue"in a?a.endValue:100,m=a.byValue||l-k;a.onStart&&a.onStart(),function b(l){g=l||+new Date;var n=g>f?e:g-d;return i()?void(a.onComplete&&a.onComplete()):(h(j(n,k,m,e)),g>f?void(a.onComplete&&a.onComplete()):void c(b))}(d)})}function c(){return b.apply(fabric.window,arguments)}var b=fabric.window.requestAnimationFrame||fabric.window.webkitRequestAnimationFrame||fabric.window.mozRequestAnimationFrame||fabric.window.oRequestAnimationFrame||fabric.window.msRequestAnimationFrame||function(a){fabric.window.setTimeout(a,1e3/60)};fabric.util.animate=a,fabric.util.requestAnimFrame=c}(),function(){function a(a,b,c,d){return ab[3]?b[3]:b[0],1!==b[0]||1!==b[3]||0!==b[4]||0!==b[5]){for(var c=a.ownerDocument.createElement("g");null!=a.firstChild;)c.appendChild(a.firstChild);c.setAttribute("transform","matrix("+b[0]+" "+b[1]+" "+b[2]+" "+b[3]+" "+b[4]+" "+b[5]+")"),a.appendChild(c)}}function x(a){var c=a.objects,e=a.options;return c=c.map(function(a){return b[d(a.type)].fromObject(a)}),{objects:c,options:e}}function y(a,b,c){b[c]&&b[c].toSVG&&a.push('','')}var b=a.fabric||(a.fabric={}),c=b.util.object.extend,d=b.util.string.capitalize,e=b.util.object.clone,f=b.util.toFixed,g=b.util.parseUnit,h=b.util.multiplyTransformMatrices,i={cx:"left",x:"left",r:"radius",cy:"top",y:"top",display:"visible",visibility:"visible",transform:"transformMatrix","fill-opacity":"fillOpacity","fill-rule":"fillRule","font-family":"fontFamily","font-size":"fontSize","font-style":"fontStyle","font-weight":"fontWeight","stroke-dasharray":"strokeDashArray","stroke-linecap":"strokeLineCap","stroke-linejoin":"strokeLineJoin","stroke-miterlimit":"strokeMiterLimit","stroke-opacity":"strokeOpacity","stroke-width":"strokeWidth","text-decoration":"textDecoration","text-anchor":"originX"},j={stroke:"strokeOpacity",fill:"fillOpacity"};b.cssRules={},b.gradientDefs={},b.parseTransformAttribute=function(){function a(a,b){var c=b[0];a[0]=Math.cos(c),a[1]=Math.sin(c),a[2]=-Math.sin(c),a[3]=Math.cos(c)}function c(a,b){var c=b[0],d=2===b.length?b[1]:b[0];a[0]=c,a[3]=d}function d(a,c){a[2]=Math.tan(b.util.degreesToRadians(c[0]))}function e(a,c){a[1]=Math.tan(b.util.degreesToRadians(c[0]))}function f(a,b){a[4]=b[0],2===b.length&&(a[5]=b[1])}var g=[1,0,0,1,0,0],h="(?:[-+]?(?:\\d+|\\d*\\.\\d+)(?:e[-+]?\\d+)?)",i="(?:\\s+,?\\s*|,\\s*)",j="(?:(skewX)\\s*\\(\\s*("+h+")\\s*\\))",k="(?:(skewY)\\s*\\(\\s*("+h+")\\s*\\))",l="(?:(rotate)\\s*\\(\\s*("+h+")(?:"+i+"("+h+")"+i+"("+h+"))?\\s*\\))",m="(?:(scale)\\s*\\(\\s*("+h+")(?:"+i+"("+h+"))?\\s*\\))",n="(?:(translate)\\s*\\(\\s*("+h+")(?:"+i+"("+h+"))?\\s*\\))",o="(?:(matrix)\\s*\\(\\s*("+h+")"+i+"("+h+")"+i+"("+h+")"+i+"("+h+")"+i+"("+h+")"+i+"("+h+")\\s*\\))",p="(?:"+o+"|"+n+"|"+m+"|"+l+"|"+j+"|"+k+")",q="(?:"+p+"(?:"+i+p+")*)",r="^\\s*(?:"+q+"?)\\s*$",s=new RegExp(r),t=new RegExp(p,"g");return function(h){var i=g.concat(),j=[];if(!h||h&&!s.test(h))return i;h.replace(t,function(h){var k=new RegExp(p).exec(h).filter(function(a){return""!==a&&null!=a}),l=k[1],m=k.slice(2).map(parseFloat);switch(l){case"translate":f(i,m);break;case"rotate":m[0]=b.util.degreesToRadians(m[0]),a(i,m);break;case"scale":c(i,m);break;case"skewX":d(i,m);break;case"skewY":e(i,m);break;case"matrix":i=m}j.push(i.concat()),i=g.concat()});for(var k=j[0];j.length>1;)j.shift(),k=b.util.multiplyTransformMatrices(k,j[0]);return k}}(),b.parseSVGDocument=function(){function f(a,b){for(;a&&(a=a.parentNode);)if(b.test(a.nodeName))return!0;return!1}var a=/^(path|circle|polygon|polyline|ellipse|rect|line|image|text)$/,c="(?:[-+]?(?:\\d+|\\d*\\.\\d+)(?:e[-+]?\\d+)?)",d=new RegExp("^\\s*("+c+"+)\\s*,?\\s*("+c+"+)\\s*,?\\s*("+c+"+)\\s*,?\\s*("+c+"+)\\s*$");return function(c,h,i){if(c){var j=new Date,k=b.Object.__uid++;u(c);var o,p,l=c.getAttribute("viewBox"),m=g(c.getAttribute("width")||"100%"),n=g(c.getAttribute("height")||"100%");if(l&&(l=l.match(d))){var q=parseFloat(l[1]),r=parseFloat(l[2]),s=1,t=1;o=parseFloat(l[3]),p=parseFloat(l[4]),m&&m!==o&&(s=m/o),n&&n!==p&&(t=n/p),v(c,[s,0,0,t,s*-q,t*-r])}var w=b.util.toArray(c.getElementsByTagName("*"));if(0===w.length&&b.isLikelyNode){w=c.selectNodes('//*[name(.)!="svg"]');for(var x=[],y=0,z=w.length;y/i,""))),f&&f.documentElement&&b.parseSVGDocument(f.documentElement,function(d,e){w.set(a,{objects:b.util.array.invoke(d,"toObject"),options:e}),c(d,e)},d)}a=a.replace(/^\n\s*/,"").trim(),w.has(a,function(d){d?w.get(a,function(a){var b=x(a);c(b.objects,b.options)}):new b.util.request(a,{method:"get",onComplete:e})})},loadSVGFromString:function(a,c,d){a=a.trim();var e;if("undefined"!=typeof DOMParser){var f=new DOMParser;f&&f.parseFromString&&(e=f.parseFromString(a,"text/xml"))}else b.window.ActiveXObject&&(e=new ActiveXObject("Microsoft.XMLDOM"),e.async="false",e.loadXML(a.replace(//i,"")));b.parseSVGDocument(e.documentElement,function(a,b){c(a,b)},d)},createSVGFontFacesMarkup:function(a){for(var b="",c=0,d=a.length;c',"",""].join("")),b},createSVGRefElementsMarkup:function(a){var b=[];return y(b,a,"backgroundColor"),y(b,a,"overlayColor"),b.join("")}})}("undefined"!=typeof exports?exports:this),fabric.ElementsParser=function(a,b,c,d){this.elements=a,this.callback=b,this.options=c,this.reviver=d,this.svgUid=c&&c.svgUid||0},fabric.ElementsParser.prototype.parse=function(){this.instances=new Array(this.elements.length),this.numElements=this.elements.length,this.createObjects()},fabric.ElementsParser.prototype.createObjects=function(){for(var a=0,b=this.elements.length;aa.x&&this.y>a.y},gte:function(a){return this.x>=a.x&&this.y>=a.y},lerp:function(a,b){return new c(this.x+(a.x-this.x)*b,this.y+(a.y-this.y)*b)},distanceFrom:function(a){var b=this.x-a.x,c=this.y-a.y;return Math.sqrt(b*b+c*c)},midPointFrom:function(a){return new c(this.x+(a.x-this.x)/2,this.y+(a.y-this.y)/2)},min:function(a){return new c(Math.min(this.x,a.x),Math.min(this.y,a.y))},max:function(a){return new c(Math.max(this.x,a.x),Math.max(this.y,a.y))},toString:function(){return this.x+","+this.y},setXY:function(a,b){this.x=a,this.y=b},setFromPoint:function(a){this.x=a.x,this.y=a.y},swap:function(a){var b=this.x,c=this.y;this.x=a.x,this.y=a.y,a.x=b,a.y=c}}))}("undefined"!=typeof exports?exports:this),function(a){"use strict";function c(a){this.status=a,this.points=[]}var b=a.fabric||(a.fabric={});return b.Intersection?void b.warn("fabric.Intersection is already defined"):(b.Intersection=c,b.Intersection.prototype={appendPoint:function(a){this.points.push(a)},appendPoints:function(a){this.points=this.points.concat(a)}},b.Intersection.intersectLineLine=function(a,d,e,f){var g,h=(f.x-e.x)*(a.y-e.y)-(f.y-e.y)*(a.x-e.x),i=(d.x-a.x)*(a.y-e.y)-(d.y-a.y)*(a.x-e.x),j=(f.y-e.y)*(d.x-a.x)-(f.x-e.x)*(d.y-a.y);if(0!==j){var k=h/j,l=i/j;0<=k&&k<=1&&0<=l&&l<=1?(g=new c("Intersection"),g.points.push(new b.Point(a.x+k*(d.x-a.x),a.y+k*(d.y-a.y)))):g=new c}else g=new c(0===h||0===i?"Coincident":"Parallel");return g},b.Intersection.intersectLinePolygon=function(a,b,d){for(var e=new c,f=d.length,g=0;g0&&(e.status="Intersection"),e},b.Intersection.intersectPolygonPolygon=function(a,b){for(var d=new c,e=a.length,f=0;f0&&(d.status="Intersection"),d},void(b.Intersection.intersectPolygonRectangle=function(a,d,e){var f=d.min(e),g=d.max(e),h=new b.Point(g.x,f.y),i=new b.Point(f.x,g.y),j=c.intersectLinePolygon(f,h,a),k=c.intersectLinePolygon(h,g,a),l=c.intersectLinePolygon(g,i,a),m=c.intersectLinePolygon(i,f,a),n=new c;return n.appendPoints(j.points),n.appendPoints(k.points),n.appendPoints(l.points),n.appendPoints(m.points),n.points.length>0&&(n.status="Intersection"),n}))}("undefined"!=typeof exports?exports:this),function(a){"use strict";function c(a){a?this._tryParsingColor(a):this.setSource([0,0,0,1])}function d(a,b,c){return c<0&&(c+=1),c>1&&(c-=1),c<1/6?a+6*(b-a)*c:c<.5?b:c<2/3?a+(b-a)*(2/3-c)*6:a}var b=a.fabric||(a.fabric={});return b.Color?void b.warn("fabric.Color is already defined."):(b.Color=c,b.Color.prototype={_tryParsingColor:function(a){var b;return a in c.colorNameMap&&(a=c.colorNameMap[a]),"transparent"===a?void this.setSource([255,255,255,0]):(b=c.sourceFromHex(a),b||(b=c.sourceFromRgb(a)),b||(b=c.sourceFromHsl(a)),void(b&&this.setSource(b)))},_rgbToHsl:function(a,c,d){a/=255,c/=255,d/=255;var e,f,g,h=b.util.array.max([a,c,d]),i=b.util.array.min([a,c,d]);if(g=(h+i)/2,h===i)e=f=0;else{var j=h-i;switch(f=g>.5?j/(2-h-i):j/(h+i),h){case a:e=(c-d)/j+(c1?1:c,b){var g=b.split(/\s*;\s*/);""===g[g.length-1]&&g.pop();for(var h=g.length;h--;){var i=g[h].split(/\s*:\s*/),j=i[0].trim(),k=i[1].trim();"stop-color"===j?d=k:"stop-opacity"===j&&(f=k)}}return d||(d=a.getAttribute("stop-color")||"rgb(0,0,0)"),f||(f=a.getAttribute("stop-opacity")),d=new fabric.Color(d),e=d.getAlpha(),f=isNaN(parseFloat(f))?1:parseFloat(f),f*=e,{offset:c,color:d.toRgb(),opacity:f}}function b(a){return{x1:a.getAttribute("x1")||0,y1:a.getAttribute("y1")||0,x2:a.getAttribute("x2")||"100%",y2:a.getAttribute("y2")||0}}function c(a){return{x1:a.getAttribute("fx")||a.getAttribute("cx")||"50%",y1:a.getAttribute("fy")||a.getAttribute("cy")||"50%",r1:0,x2:a.getAttribute("cx")||"50%",y2:a.getAttribute("cy")||"50%",r2:a.getAttribute("r")||"50%"}}function d(a,b,c){var d,e=0,f=1,g="";for(var h in b)d=parseFloat(b[h],10),f="string"==typeof b[h]&&/^\d+%$/.test(b[h])?.01:1,"x1"===h||"x2"===h||"r2"===h?(f*="objectBoundingBox"===c?a.width:1,e="objectBoundingBox"===c?a.left||0:0):"y1"!==h&&"y2"!==h||(f*="objectBoundingBox"===c?a.height:1,e="objectBoundingBox"===c?a.top||0:0),b[h]=d*f+e;if("ellipse"===a.type&&null!==b.r2&&"objectBoundingBox"===c&&a.rx!==a.ry){var i=a.ry/a.rx;g=" scale(1, "+i+")",b.y1&&(b.y1/=i),b.y2&&(b.y2/=i)}return g}fabric.Gradient=fabric.util.createClass({offsetX:0,offsetY:0,initialize:function(a){a||(a={});var b={};this.id=fabric.Object.__uid++,this.type=a.type||"linear",b={x1:a.coords.x1||0,y1:a.coords.y1||0,x2:a.coords.x2||0,y2:a.coords.y2||0},"radial"===this.type&&(b.r1=a.coords.r1||0,b.r2=a.coords.r2||0),this.coords=b,this.colorStops=a.colorStops.slice(),a.gradientTransform&&(this.gradientTransform=a.gradientTransform),this.offsetX=a.offsetX||this.offsetX,this.offsetY=a.offsetY||this.offsetY},addColorStop:function(a){for(var b in a){var c=new fabric.Color(a[b]);this.colorStops.push({offset:b,color:c.toRgb(),opacity:c.getAlpha()})}return this},toObject:function(){return{type:this.type,coords:this.coords,colorStops:this.colorStops,offsetX:this.offsetX,offsetY:this.offsetY}},toSVG:function(a){var c,d,b=fabric.util.object.clone(this.coords);if(this.colorStops.sort(function(a,b){return a.offset-b.offset}),!a.group||"path-group"!==a.group.type)for(var e in b)"x1"===e||"x2"===e||"r2"===e?b[e]+=this.offsetX-a.width/2:"y1"!==e&&"y2"!==e||(b[e]+=this.offsetY-a.height/2);d='id="SVGID_'+this.id+'" gradientUnits="userSpaceOnUse"',this.gradientTransform&&(d+=' gradientTransform="matrix('+this.gradientTransform.join(" ")+')" '),"linear"===this.type?c=["\n']:"radial"===this.type&&(c=["\n']);for(var f=0;f\n');return c.push("linear"===this.type?"\n":"\n"),c.join("")},toLive:function(a,b){var c,d=fabric.util.object.clone(this.coords);if(this.type){if(b.group&&"path-group"===b.group.type)for(var e in d)"x1"===e||"x2"===e?d[e]+=-this.offsetX+b.width/2:"y1"!==e&&"y2"!==e||(d[e]+=-this.offsetY+b.height/2);"linear"===this.type?c=a.createLinearGradient(d.x1,d.y1,d.x2,d.y2):"radial"===this.type&&(c=a.createRadialGradient(d.x1,d.y1,d.r1,d.x2,d.y2,d.r2));for(var f=0,g=this.colorStops.length;f'},toLive:function(a){var b="function"==typeof this.source?this.source():this.source;if(!b)return"";if("undefined"!=typeof b.src){if(!b.complete)return"";if(0===b.naturalWidth||0===b.naturalHeight)return""}return a.createPattern(b,this.repeat)}}),function(a){"use strict";var b=a.fabric||(a.fabric={});return b.Shadow?void b.warn("fabric.Shadow is already defined."):(b.Shadow=b.util.createClass({color:"rgb(0,0,0)",blur:0,offsetX:0,offsetY:0,affectStroke:!1,includeDefaultValues:!0,initialize:function(a){"string"==typeof a&&(a=this._parseShadow(a));for(var c in a)this[c]=a[c];this.id=b.Object.__uid++},_parseShadow:function(a){var c=a.trim(),d=b.Shadow.reOffsetsAndBlur.exec(c)||[],e=c.replace(b.Shadow.reOffsetsAndBlur,"")||"rgb(0,0,0)";return{color:e.trim(),offsetX:parseInt(d[1],10)||0,offsetY:parseInt(d[2],10)||0,blur:parseInt(d[3],10)||0}},toString:function(){return[this.offsetX,this.offsetY,this.blur,this.color].join("px ")},toSVG:function(a){var b="SourceAlpha";return!a||a.fill!==this.color&&a.stroke!==this.color||(b="SourceGraphic"),''},toObject:function(){if(this.includeDefaultValues)return{color:this.color,blur:this.blur,offsetX:this.offsetX,offsetY:this.offsetY};var a={},c=b.Shadow.prototype;return this.color!==c.color&&(a.color=this.color),this.blur!==c.blur&&(a.blur=this.blur),this.offsetX!==c.offsetX&&(a.offsetX=this.offsetX),this.offsetY!==c.offsetY&&(a.offsetY=this.offsetY),a}}),void(b.Shadow.reOffsetsAndBlur=/(?:\s|^)(-?\d+(?:px)?(?:\s?|$))?(-?\d+(?:px)?(?:\s?|$))?(\d+(?:px)?)?(?:\s?|$)(?:$|\s)/))}("undefined"!=typeof exports?exports:this),function(){"use strict";if(fabric.StaticCanvas)return void fabric.warn("fabric.StaticCanvas is already defined.");var a=fabric.util.object.extend,b=fabric.util.getElementOffset,c=fabric.util.removeFromArray,d=new Error("Could not initialize `canvas` element");fabric.StaticCanvas=fabric.util.createClass({initialize:function(a,b){b||(b={}),this._initStatic(a,b),fabric.StaticCanvas.activeInstance=this},backgroundColor:"",backgroundImage:null,overlayColor:"",overlayImage:null,includeDefaultValues:!0,stateful:!0,renderOnAddRemove:!0,clipTo:null,controlsAboveOverlay:!1,allowTouchScrolling:!1,imageSmoothingEnabled:!0,preserveObjectStacking:!1,viewportTransform:[1,0,0,1,0,0],onBeforeScaleRotate:function(){},_initStatic:function(a,b){this._objects=[],this._createLowerCanvas(a),this._initOptions(b),this._setImageSmoothing(),b.overlayImage&&this.setOverlayImage(b.overlayImage,this.renderAll.bind(this)),b.backgroundImage&&this.setBackgroundImage(b.backgroundImage,this.renderAll.bind(this)),b.backgroundColor&&this.setBackgroundColor(b.backgroundColor,this.renderAll.bind(this)),b.overlayColor&&this.setOverlayColor(b.overlayColor,this.renderAll.bind(this)),this.calcOffset()},calcOffset:function(){return this._offset=b(this.lowerCanvasEl),this},setOverlayImage:function(a,b,c){return this.__setBgOverlayImage("overlayImage",a,b,c)},setBackgroundImage:function(a,b,c){return this.__setBgOverlayImage("backgroundImage",a,b,c)},setOverlayColor:function(a,b){return this.__setBgOverlayColor("overlayColor",a,b)},setBackgroundColor:function(a,b){return this.__setBgOverlayColor("backgroundColor",a,b)},_setImageSmoothing:function(){var a=this.getContext();a.imageSmoothingEnabled=this.imageSmoothingEnabled,a.imageSmoothingEnabled=this.imageSmoothingEnabled,a.mozImageSmoothingEnabled=this.imageSmoothingEnabled,a.msImageSmoothingEnabled=this.imageSmoothingEnabled,a.oImageSmoothingEnabled=this.imageSmoothingEnabled},__setBgOverlayImage:function(a,b,c,d){return"string"==typeof b?fabric.util.loadImage(b,function(b){this[a]=new fabric.Image(b,d),c&&c()},this):(this[a]=b,c&&c()),this},__setBgOverlayColor:function(a,b,c){if(b&&b.source){var d=this;fabric.util.loadImage(b.source,function(e){d[a]=new fabric.Pattern({source:e,repeat:b.repeat,offsetX:b.offsetX,offsetY:b.offsetY}),c&&c()})}else this[a]=b,c&&c();return this},_createCanvasElement:function(){var a=fabric.document.createElement("canvas");if(a.style||(a.style={}),!a)throw d;return this._initCanvasElement(a),a},_initCanvasElement:function(a){if(fabric.util.createCanvasElement(a),"undefined"==typeof a.getContext)throw d},_initOptions:function(a){for(var b in a)this[b]=a[b];this.width=this.width||parseInt(this.lowerCanvasEl.width,10)||0,this.height=this.height||parseInt(this.lowerCanvasEl.height,10)||0,this.lowerCanvasEl.style&&(this.lowerCanvasEl.width=this.width,this.lowerCanvasEl.height=this.height,this.lowerCanvasEl.style.width=this.width+"px",this.lowerCanvasEl.style.height=this.height+"px",this.viewportTransform=this.viewportTransform.slice())},_createLowerCanvas:function(a){this.lowerCanvasEl=fabric.util.getById(a)||this._createCanvasElement(),this._initCanvasElement(this.lowerCanvasEl),fabric.util.addClass(this.lowerCanvasEl,"lower-canvas"),this.interactive&&this._applyCanvasStyle(this.lowerCanvasEl),this.contextContainer=this.lowerCanvasEl.getContext("2d")},getWidth:function(){return this.width},getHeight:function(){return this.height},setWidth:function(a,b){return this.setDimensions({width:a},b)},setHeight:function(a,b){return this.setDimensions({height:a},b)},setDimensions:function(a,b){var c;b=b||{};for(var d in a)c=a[d],b.cssOnly||(this._setBackstoreDimension(d,a[d]),c+="px"),b.backstoreOnly||this._setCssDimension(d,c);return b.cssOnly||this.renderAll(),this.calcOffset(),this},_setBackstoreDimension:function(a,b){return this.lowerCanvasEl[a]=b,this.upperCanvasEl&&(this.upperCanvasEl[a]=b),this.cacheCanvasEl&&(this.cacheCanvasEl[a]=b),this[a]=b,this},_setCssDimension:function(a,b){return this.lowerCanvasEl.style[a]=b,this.upperCanvasEl&&(this.upperCanvasEl.style[a]=b),this.wrapperEl&&(this.wrapperEl.style[a]=b),this},getZoom:function(){return Math.sqrt(this.viewportTransform[0]*this.viewportTransform[3])},setViewportTransform:function(a){this.viewportTransform=a,this.renderAll();for(var b=0,c=this._objects.length;b"),c.join("")},_setSVGPreamble:function(a,b){b.suppressPreamble||a.push('','\n')},_setSVGHeader:function(a,b){var c,d,e;b.viewBox?(c=b.viewBox.width,d=b.viewBox.height):(c=this.width,d=this.height,this.svgViewportTransformation||(e=this.viewportTransform,c/=e[0],d/=e[3])),a.push("',"Created with Fabric.js ",fabric.version,"","",fabric.createSVGFontFacesMarkup(this.getObjects()),fabric.createSVGRefElementsMarkup(this),"")},_setSVGObjects:function(a,b){var c=this.getActiveGroup();c&&this.discardActiveGroup();for(var d=0,e=this.getObjects(),f=e.length;d
        "):this[b]&&"overlayColor"===b&&a.push('")},sendToBack:function(a){return c(this._objects,a),this._objects.unshift(a),this.renderAll&&this.renderAll()},bringToFront:function(a){return c(this._objects,a),this._objects.push(a),this.renderAll&&this.renderAll()},sendBackwards:function(a,b){var d=this._objects.indexOf(a);if(0!==d){var e=this._findNewLowerIndex(a,d,b);c(this._objects,a),this._objects.splice(e,0,a),this.renderAll&&this.renderAll()}return this}, _findNewLowerIndex:function(a,b,c){var d;if(c){d=b;for(var e=b-1;e>=0;--e){var f=a.intersectsWithObject(this._objects[e])||a.isContainedWithinObject(this._objects[e])||this._objects[e].isContainedWithinObject(a);if(f){d=e;break}}}else d=b-1;return d},bringForward:function(a,b){var d=this._objects.indexOf(a);if(d!==this._objects.length-1){var e=this._findNewUpperIndex(a,d,b);c(this._objects,a),this._objects.splice(e,0,a),this.renderAll&&this.renderAll()}return this},_findNewUpperIndex:function(a,b,c){var d;if(c){d=b;for(var e=b+1;e"}}),a(fabric.StaticCanvas.prototype,fabric.Observable),a(fabric.StaticCanvas.prototype,fabric.Collection),a(fabric.StaticCanvas.prototype,fabric.DataURLExporter),a(fabric.StaticCanvas,{EMPTY_JSON:'{"objects": [], "background": "white"}',supports:function(a){var b=fabric.util.createCanvasElement();if(!b||!b.getContext)return null;var c=b.getContext("2d");if(!c)return null;switch(a){case"getImageData":return"undefined"!=typeof c.getImageData;case"setLineDash":return"undefined"!=typeof c.setLineDash;case"toDataURL":return"undefined"!=typeof b.toDataURL;case"toDataURLWithQuality":try{return b.toDataURL("image/jpeg",0),!0}catch(a){}return!1;default:return null}}}),fabric.StaticCanvas.prototype.toJSON=fabric.StaticCanvas.prototype.toObject}(),fabric.BaseBrush=fabric.util.createClass({color:"rgb(0, 0, 0)",width:1,shadow:null,strokeLineCap:"round",strokeLineJoin:"round",setShadow:function(a){return this.shadow=new fabric.Shadow(a),this},_setBrushStyles:function(){var a=this.canvas.contextTop;a.strokeStyle=this.color,a.lineWidth=this.width,a.lineCap=this.strokeLineCap,a.lineJoin=this.strokeLineJoin},_setShadow:function(){if(this.shadow){var a=this.canvas.contextTop;a.shadowColor=this.shadow.color,a.shadowBlur=this.shadow.blur,a.shadowOffsetX=this.shadow.offsetX,a.shadowOffsetY=this.shadow.offsetY}},_resetShadow:function(){var a=this.canvas.contextTop;a.shadowColor="",a.shadowBlur=a.shadowOffsetX=a.shadowOffsetY=0}}),function(){fabric.PencilBrush=fabric.util.createClass(fabric.BaseBrush,{initialize:function(a){this.canvas=a,this._points=[]},onMouseDown:function(a){this._prepareForDrawing(a),this._captureDrawingPath(a),this._render()},onMouseMove:function(a){this._captureDrawingPath(a),this.canvas.clearContext(this.canvas.contextTop),this._render()},onMouseUp:function(){this._finalizeAndAddPath()},_prepareForDrawing:function(a){var b=new fabric.Point(a.x,a.y);this._reset(),this._addPoint(b),this.canvas.contextTop.moveTo(b.x,b.y)},_addPoint:function(a){this._points.push(a)},_reset:function(){this._points.length=0,this._setBrushStyles(),this._setShadow()},_captureDrawingPath:function(a){var b=new fabric.Point(a.x,a.y);this._addPoint(b)},_render:function(){var a=this.canvas.contextTop,b=this.canvas.viewportTransform,c=this._points[0],d=this._points[1];a.save(),a.transform(b[0],b[1],b[2],b[3],b[4],b[5]),a.beginPath(),2===this._points.length&&c.x===d.x&&c.y===d.y&&(c.x-=.5,d.x+=.5),a.moveTo(c.x,c.y);for(var e=1,f=this._points.length;ec.padding?a.x<0?a.x+=c.padding:a.x-=c.padding:a.x=0,e(a.y)>c.padding?a.y<0?a.y+=c.padding:a.y-=c.padding:a.y=0},_rotateObject:function(a,b){var e=this._currentTransform;if(!e.target.get("lockRotation")){var f=d(e.ey-e.top,e.ex-e.left),g=d(b-e.top,a-e.left),h=c(g-f+e.theta);h<0&&(h=360+h),e.target.angle=h}},setCursor:function(a){this.upperCanvasEl.style.cursor=a},_resetObjectTransform:function(a){a.scaleX=1,a.scaleY=1,a.setAngle(0)},_drawSelection:function(){var a=this.contextTop,b=this._groupSelector,c=b.left,d=b.top,g=e(c),h=e(d);if(a.fillStyle=this.selectionColor,a.fillRect(b.ex-(c>0?0:-c),b.ey-(d>0?0:-d),g,h),a.lineWidth=this.selectionLineWidth,a.strokeStyle=this.selectionBorderColor,this.selectionDashArray.length>1){var i=b.ex+f-(c>0?0:g),j=b.ey+f-(d>0?0:h);a.beginPath(),fabric.util.drawDashedLine(a,i,j,i+g,j,this.selectionDashArray),fabric.util.drawDashedLine(a,i,j+h-1,i+g,j+h-1,this.selectionDashArray),fabric.util.drawDashedLine(a,i,j,i,j+h,this.selectionDashArray),fabric.util.drawDashedLine(a,i+g-1,j,i+g-1,j+h,this.selectionDashArray),a.closePath(),a.stroke()}else a.strokeRect(b.ex+f-(c>0?0:g),b.ey+f-(d>0?0:h),g,h)},_isLastRenderedObject:function(a){return this.controlsAboveOverlay&&this.lastRenderedObjectWithControlsAboveOverlay&&this.lastRenderedObjectWithControlsAboveOverlay.visible&&this.containsPoint(a,this.lastRenderedObjectWithControlsAboveOverlay)&&this.lastRenderedObjectWithControlsAboveOverlay._findTargetCorner(this.getPointer(a,!0))},findTarget:function(a,b){if(!this.skipTargetFind){if(this._isLastRenderedObject(a))return this.lastRenderedObjectWithControlsAboveOverlay;var c=this.getActiveGroup();if(c&&!b&&this.containsPoint(a,c))return c;var d=this._searchPossibleTargets(a);return this._fireOverOutEvents(d),d}},_fireOverOutEvents:function(a){if(a){if(this._hoveredTarget!==a){if(this.fire("mouse:over",{target:a}),a.fire("mouseover"),this._hoveredTarget){if(this.fire("mouse:out",{target:this._hoveredTarget}),!this._hoveredTarget.fire)return;this._hoveredTarget.fire("mouseout")}this._hoveredTarget=a}}else if(this._hoveredTarget){if(this.fire("mouse:out",{target:this._hoveredTarget}),!this._hoveredTarget.fire)return;this._hoveredTarget.fire("mouseout"),this._hoveredTarget=null}},_checkTarget:function(a,b,c){if(b&&b.visible&&b.evented&&this.containsPoint(a,b)){if(!this.perPixelTargetFind&&!b.perPixelTargetFind||b.isEditing)return!0;var d=this.isTargetTransparent(b,c.x,c.y);if(!d)return!0}},_searchPossibleTargets:function(a){for(var b,c=this.getPointer(a,!0),d=this._objects.length;d--;)if(this._checkTarget(a,this._objects[d],c)){this.relatedTarget=this._objects[d],b=this._objects[d];break}return b},getPointer:function(b,c,d){d||(d=this.upperCanvasEl);var i,e=a(b,d),f=d.getBoundingClientRect(),g=f.width||0,h=f.height||0;return g&&h||("top"in f&&"bottom"in f&&(h=Math.abs(f.top-f.bottom)),"right"in f&&"left"in f&&(g=Math.abs(f.right-f.left))),this.calcOffset(),e.x=e.x-this._offset.left,e.y=e.y-this._offset.top,c||(e=fabric.util.transformPoint(e,fabric.util.invertTransform(this.viewportTransform))),i=0===g||0===h?{width:1,height:1}:{width:d.width/g,height:d.height/h},{x:e.x*i.width,y:e.y*i.height}},_createUpperCanvas:function(){var a=this.lowerCanvasEl.className.replace(/\s*lower-canvas\s*/,"");this.upperCanvasEl=this._createCanvasElement(),fabric.util.addClass(this.upperCanvasEl,"upper-canvas "+a),this.wrapperEl.appendChild(this.upperCanvasEl),this._copyCanvasStyle(this.lowerCanvasEl,this.upperCanvasEl),this._applyCanvasStyle(this.upperCanvasEl),this.contextTop=this.upperCanvasEl.getContext("2d")},_createCacheCanvas:function(){this.cacheCanvasEl=this._createCanvasElement(),this.cacheCanvasEl.setAttribute("width",this.width),this.cacheCanvasEl.setAttribute("height",this.height),this.contextCache=this.cacheCanvasEl.getContext("2d")},_initWrapperElement:function(){this.wrapperEl=fabric.util.wrapElement(this.lowerCanvasEl,"div",{class:this.containerClass}),fabric.util.setStyle(this.wrapperEl,{width:this.getWidth()+"px",height:this.getHeight()+"px",position:"relative"}),fabric.util.makeElementUnselectable(this.wrapperEl)},_applyCanvasStyle:function(a){var b=this.getWidth()||a.width,c=this.getHeight()||a.height;fabric.util.setStyle(a,{position:"absolute",width:b+"px",height:c+"px",left:0,top:0}),a.width=b,a.height=c,fabric.util.makeElementUnselectable(a)},_copyCanvasStyle:function(a,b){b.style.cssText=a.style.cssText},getSelectionContext:function(){return this.contextTop},getSelectionElement:function(){return this.upperCanvasEl},_setActiveObject:function(a){this._activeObject&&this._activeObject.set("active",!1),this._activeObject=a,a.set("active",!0)},setActiveObject:function(a,b){return this._setActiveObject(a),this.renderAll(),this.fire("object:selected",{target:a,e:b}),a.fire("selected",{e:b}),this},getActiveObject:function(){return this._activeObject},_discardActiveObject:function(){this._activeObject&&this._activeObject.set("active",!1),this._activeObject=null},discardActiveObject:function(a){return this._discardActiveObject(),this.renderAll(),this.fire("selection:cleared",{e:a}),this},_setActiveGroup:function(a){this._activeGroup=a,a&&a.set("active",!0)},setActiveGroup:function(a,b){return this._setActiveGroup(a),a&&(this.fire("object:selected",{target:a,e:b}),a.fire("selected",{e:b})),this},getActiveGroup:function(){return this._activeGroup},_discardActiveGroup:function(){var a=this.getActiveGroup();a&&a.destroy(),this.setActiveGroup(null)},discardActiveGroup:function(a){return this._discardActiveGroup(),this.fire("selection:cleared",{e:a}),this},deactivateAll:function(){for(var a=this.getObjects(),b=0,c=a.length;b1&&(b=new fabric.Group(b.reverse(),{originX:"center", originY:"center",canvas:this}),b.addWithUpdate(),this.setActiveGroup(b,a),b.saveCoords(),this.fire("selection:created",{target:b}),this.renderAll())},_collectObjects:function(){for(var d,c=[],e=this._groupSelector.ex,f=this._groupSelector.ey,g=e+this._groupSelector.left,h=f+this._groupSelector.top,i=new fabric.Point(a(e,g),a(f,h)),j=new fabric.Point(b(e,g),b(f,h)),k=e===g&&f===h,l=this._objects.length;l--&&(d=this._objects[l],!(d&&d.selectable&&d.visible&&(d.intersectsWithRect(i,j)||d.isContainedWithinRect(i,j)||d.containsPoint(i)||d.containsPoint(j))&&(d.set("active",!0),c.push(d),k))););return c},_maybeGroupObjects:function(a){this.selection&&this._groupSelector&&this._groupSelectedObjects(a);var b=this.getActiveGroup();b&&(b.setObjectsCoords().setCoords(),b.isMoving=!1,this.setCursor(this.defaultCursor)),this._groupSelector=null,this._currentTransform=null}})}(),fabric.util.object.extend(fabric.StaticCanvas.prototype,{toDataURL:function(a){a||(a={});var b=a.format||"png",c=a.quality||1,d=a.multiplier||1,e={left:a.left,top:a.top,width:a.width,height:a.height};return 1!==d?this.__toDataURLWithMultiplier(b,c,e,d):this.__toDataURL(b,c,e)},__toDataURL:function(a,b,c){this.renderAll(!0);var d=this.upperCanvasEl||this.lowerCanvasEl,e=this.__getCroppedCanvas(d,c);"jpg"===a&&(a="jpeg");var f=fabric.StaticCanvas.supports("toDataURLWithQuality")?(e||d).toDataURL("image/"+a,b):(e||d).toDataURL("image/"+a);return this.contextTop&&this.clearContext(this.contextTop),this.renderAll(),e&&(e=null),f},__getCroppedCanvas:function(a,b){var c,d,e="left"in b||"top"in b||"width"in b||"height"in b;return e&&(c=fabric.util.createCanvasElement(),d=c.getContext("2d"),c.width=b.width||this.width,c.height=b.height||this.height,d.drawImage(a,-b.left||0,-b.top||0)),c},__toDataURLWithMultiplier:function(a,b,c,d){var e=this.getWidth(),f=this.getHeight(),g=e*d,h=f*d,i=this.getActiveObject(),j=this.getActiveGroup(),k=this.contextTop||this.contextContainer;d>1&&this.setWidth(g).setHeight(h),k.scale(d,d),c.left&&(c.left*=d),c.top&&(c.top*=d),c.width?c.width*=d:d<1&&(c.width=g),c.height?c.height*=d:d<1&&(c.height=h),j?this._tempRemoveBordersControlsFromGroup(j):i&&this.deactivateAll&&this.deactivateAll(),this.renderAll(!0);var l=this.__toDataURL(a,b,c);return this.width=e,this.height=f,k.scale(1/d,1/d),this.setWidth(e).setHeight(f),j?this._restoreBordersControlsOnGroup(j):i&&this.setActiveObject&&this.setActiveObject(i),this.contextTop&&this.clearContext(this.contextTop),this.renderAll(),l},toDataURLWithMultiplier:function(a,b,c){return this.toDataURL({format:a,multiplier:b,quality:c})},_tempRemoveBordersControlsFromGroup:function(a){a.origHasControls=a.hasControls,a.origBorderColor=a.borderColor,a.hasControls=!0,a.borderColor="rgba(0,0,0,0)",a.forEachObject(function(a){a.origBorderColor=a.borderColor,a.borderColor="rgba(0,0,0,0)"})},_restoreBordersControlsOnGroup:function(a){a.hideControls=a.origHideControls,a.borderColor=a.origBorderColor,a.forEachObject(function(a){a.borderColor=a.origBorderColor,delete a.origBorderColor})}}),fabric.util.object.extend(fabric.StaticCanvas.prototype,{loadFromDatalessJSON:function(a,b,c){return this.loadFromJSON(a,b,c)},loadFromJSON:function(a,b,c){if(a){var d="string"==typeof a?JSON.parse(a):a;this.clear();var e=this;return this._enlivenObjects(d.objects,function(){e._setBgOverlay(d,b)},c),this}},_setBgOverlay:function(a,b){var c=this,d={backgroundColor:!1,overlayColor:!1,backgroundImage:!1,overlayImage:!1};if(!(a.backgroundImage||a.overlayImage||a.background||a.overlay))return void(b&&b());var e=function(){d.backgroundImage&&d.overlayImage&&d.backgroundColor&&d.overlayColor&&(c.renderAll(),b&&b())};this.__setBgOverlay("backgroundImage",a.backgroundImage,d,e),this.__setBgOverlay("overlayImage",a.overlayImage,d,e),this.__setBgOverlay("backgroundColor",a.background,d,e),this.__setBgOverlay("overlayColor",a.overlay,d,e),e()},__setBgOverlay:function(a,b,c,d){var e=this;return b?void("backgroundImage"===a||"overlayImage"===a?fabric.Image.fromObject(b,function(b){e[a]=b,c[a]=!0,d&&d()}):this["set"+fabric.util.string.capitalize(a,!0)](b,function(){c[a]=!0,d&&d()})):void(c[a]=!0)},_enlivenObjects:function(a,b,c){var d=this;if(!a||0===a.length)return void(b&&b());var e=this.renderOnAddRemove;this.renderOnAddRemove=!1,fabric.util.enlivenObjects(a,function(a){a.forEach(function(a,b){d.insertAt(a,b,!0)}),d.renderOnAddRemove=e,b&&b()},null,c)},_toDataURL:function(a,b){this.clone(function(c){b(c.toDataURL(a))})},_toDataURLWithMultiplier:function(a,b,c){this.clone(function(d){c(d.toDataURLWithMultiplier(a,b))})},clone:function(a,b){var c=JSON.stringify(this.toJSON(b));this.cloneWithoutData(function(b){b.loadFromJSON(c,function(){a&&a(b)})})},cloneWithoutData:function(a){var b=fabric.document.createElement("canvas");b.width=this.getWidth(),b.height=this.getHeight();var c=new fabric.Canvas(b);c.clipTo=this.clipTo,this.backgroundImage?(c.setBackgroundImage(this.backgroundImage.src,function(){c.renderAll(),a&&a(c)}),c.backgroundImageOpacity=this.backgroundImageOpacity,c.backgroundImageStretch=this.backgroundImageStretch):a&&a(c)}}),function(a){"use strict";var b=a.fabric||(a.fabric={}),c=b.util.object.extend,d=b.util.toFixed,e=b.util.string.capitalize,f=b.util.degreesToRadians,g=b.StaticCanvas.supports("setLineDash");b.Object||(b.Object=b.util.createClass({type:"object",originX:"left",originY:"top",top:0,left:0,width:0,height:0,scaleX:1,scaleY:1,flipX:!1,flipY:!1,opacity:1,angle:0,cornerSize:12,transparentCorners:!0,hoverCursor:null,padding:0,borderColor:"rgba(102,153,255,0.75)",cornerColor:"rgba(102,153,255,0.5)",centeredScaling:!1,centeredRotation:!0,fill:"rgb(0,0,0)",fillRule:"nonzero",globalCompositeOperation:"source-over",backgroundColor:"",stroke:null,strokeWidth:1,strokeDashArray:null,strokeLineCap:"butt",strokeLineJoin:"miter",strokeMiterLimit:10,shadow:null,borderOpacityWhenMoving:.4,borderScaleFactor:1,transformMatrix:null,minScaleLimit:.01,selectable:!0,evented:!0,visible:!0,hasControls:!0,hasBorders:!0,hasRotatingPoint:!0,rotatingPointOffset:40,perPixelTargetFind:!1,includeDefaultValues:!0,clipTo:null,lockMovementX:!1,lockMovementY:!1,lockRotation:!1,lockScalingX:!1,lockScalingY:!1,lockUniScaling:!1,lockScalingFlip:!1,stateProperties:"top left width height scaleX scaleY flipX flipY originX originY transformMatrix stroke strokeWidth strokeDashArray strokeLineCap strokeLineJoin strokeMiterLimit angle opacity fill fillRule globalCompositeOperation shadow clipTo visible backgroundColor".split(" "),initialize:function(a){a&&this.setOptions(a)},_initGradient:function(a){!a.fill||!a.fill.colorStops||a.fill instanceof b.Gradient||this.set("fill",new b.Gradient(a.fill))},_initPattern:function(a){!a.fill||!a.fill.source||a.fill instanceof b.Pattern||this.set("fill",new b.Pattern(a.fill)),!a.stroke||!a.stroke.source||a.stroke instanceof b.Pattern||this.set("stroke",new b.Pattern(a.stroke))},_initClipping:function(a){if(a.clipTo&&"string"==typeof a.clipTo){var c=b.util.getFunctionBody(a.clipTo);"undefined"!=typeof c&&(this.clipTo=new Function("ctx",c))}},setOptions:function(a){for(var b in a)this.set(b,a[b]);this._initGradient(a),this._initPattern(a),this._initClipping(a)},transform:function(a,b){this.group&&this.group.transform(a,b);var c=b?this._getLeftTopCoords():this.getCenterPoint();a.translate(c.x,c.y),a.rotate(f(this.angle)),a.scale(this.scaleX*(this.flipX?-1:1),this.scaleY*(this.flipY?-1:1))},toObject:function(a){var c=b.Object.NUM_FRACTION_DIGITS,e={type:this.type,originX:this.originX,originY:this.originY,left:d(this.left,c),top:d(this.top,c),width:d(this.width,c),height:d(this.height,c),fill:this.fill&&this.fill.toObject?this.fill.toObject():this.fill,stroke:this.stroke&&this.stroke.toObject?this.stroke.toObject():this.stroke,strokeWidth:d(this.strokeWidth,c),strokeDashArray:this.strokeDashArray,strokeLineCap:this.strokeLineCap,strokeLineJoin:this.strokeLineJoin,strokeMiterLimit:d(this.strokeMiterLimit,c),scaleX:d(this.scaleX,c),scaleY:d(this.scaleY,c),angle:d(this.getAngle(),c),flipX:this.flipX,flipY:this.flipY,opacity:d(this.opacity,c),shadow:this.shadow&&this.shadow.toObject?this.shadow.toObject():this.shadow,visible:this.visible,clipTo:this.clipTo&&String(this.clipTo),backgroundColor:this.backgroundColor,fillRule:this.fillRule,globalCompositeOperation:this.globalCompositeOperation};return this.includeDefaultValues||(e=this._removeDefaultValues(e)),b.util.populateWithProperties(this,e,a),e},toDatalessObject:function(a){return this.toObject(a)},_removeDefaultValues:function(a){var c=b.util.getKlass(a.type).prototype,d=c.stateProperties;return d.forEach(function(b){a[b]===c[b]&&delete a[b]}),a},toString:function(){return"#"},get:function(a){return this[a]},_setObject:function(a){for(var b in a)this._set(b,a[b])},set:function(a,b){return"object"==typeof a?this._setObject(a):"function"==typeof b&&"clipTo"!==a?this._set(a,b(this.get(a))):this._set(a,b),this},_set:function(a,c){var e="scaleX"===a||"scaleY"===a;return e&&(c=this._constrainScale(c)),"scaleX"===a&&c<0?(this.flipX=!this.flipX,c*=-1):"scaleY"===a&&c<0?(this.flipY=!this.flipY,c*=-1):"width"===a||"height"===a?this.minScaleLimit=d(Math.min(.1,1/Math.max(this.width,this.height)),2):"shadow"!==a||!c||c instanceof b.Shadow||(c=new b.Shadow(c)),this[a]=c,this},toggle:function(a){var b=this.get(a);return"boolean"==typeof b&&this.set(a,!b),this},setSourcePath:function(a){return this.sourcePath=a,this},getViewportTransform:function(){return this.canvas&&this.canvas.viewportTransform?this.canvas.viewportTransform:[1,0,0,1,0,0]},render:function(a,c){0!==this.width&&0!==this.height&&this.visible&&(a.save(),this._setupCompositeOperation(a),c||this.transform(a),this._setStrokeStyles(a),this._setFillStyles(a),this.group&&"path-group"===this.group.type&&a.translate(-this.group.width/2,-this.group.height/2),this.transformMatrix&&a.transform.apply(a,this.transformMatrix),this._setOpacity(a),this._setShadow(a),this.clipTo&&b.util.clipContext(this,a),this._render(a,c),this.clipTo&&a.restore(),this._removeShadow(a),this._restoreCompositeOperation(a),a.restore())},_setOpacity:function(a){this.group&&this.group._setOpacity(a),a.globalAlpha*=this.opacity},_setStrokeStyles:function(a){this.stroke&&(a.lineWidth=this.strokeWidth,a.lineCap=this.strokeLineCap,a.lineJoin=this.strokeLineJoin,a.miterLimit=this.strokeMiterLimit,a.strokeStyle=this.stroke.toLive?this.stroke.toLive(a,this):this.stroke)},_setFillStyles:function(a){this.fill&&(a.fillStyle=this.fill.toLive?this.fill.toLive(a,this):this.fill)},_renderControls:function(a,c){var d=this.getViewportTransform();if(a.save(),this.active&&!c){var e;this.group&&(e=b.util.transformPoint(this.group.getCenterPoint(),d),a.translate(e.x,e.y),a.rotate(f(this.group.angle))),e=b.util.transformPoint(this.getCenterPoint(),d,null!=this.group),this.group&&(e.x*=this.group.scaleX,e.y*=this.group.scaleY),a.translate(e.x,e.y),a.rotate(f(this.angle)),this.drawBorders(a),this.drawControls(a)}a.restore()},_setShadow:function(a){this.shadow&&(a.shadowColor=this.shadow.color,a.shadowBlur=this.shadow.blur,a.shadowOffsetX=this.shadow.offsetX,a.shadowOffsetY=this.shadow.offsetY)},_removeShadow:function(a){this.shadow&&(a.shadowColor="",a.shadowBlur=a.shadowOffsetX=a.shadowOffsetY=0)},_renderFill:function(a){if(this.fill){if(a.save(),this.fill.gradientTransform){var b=this.fill.gradientTransform;a.transform.apply(a,b)}this.fill.toLive&&a.translate(-this.width/2+this.fill.offsetX||0,-this.height/2+this.fill.offsetY||0),"evenodd"===this.fillRule?a.fill("evenodd"):a.fill(),a.restore(),this.shadow&&!this.shadow.affectStroke&&this._removeShadow(a)}},_renderStroke:function(a){if(this.stroke&&0!==this.strokeWidth){if(a.save(),this.strokeDashArray)1&this.strokeDashArray.length&&this.strokeDashArray.push.apply(this.strokeDashArray,this.strokeDashArray),g?(a.setLineDash(this.strokeDashArray),this._stroke&&this._stroke(a)):this._renderDashedStroke&&this._renderDashedStroke(a),a.stroke();else{if(this.stroke.gradientTransform){var b=this.stroke.gradientTransform;a.transform.apply(a,b)}this._stroke?this._stroke(a):a.stroke()}this._removeShadow(a),a.restore()}},clone:function(a,c){return this.constructor.fromObject?this.constructor.fromObject(this.toObject(c),a):new b.Object(this.toObject(c))},cloneAsImage:function(a){var c=this.toDataURL();return b.util.loadImage(c,function(c){a&&a(new b.Image(c))}),this},toDataURL:function(a){a||(a={});var c=b.util.createCanvasElement(),d=this.getBoundingRect();c.width=d.width,c.height=d.height,b.util.wrapElement(c,"div");var e=new b.Canvas(c);"jpg"===a.format&&(a.format="jpeg"),"jpeg"===a.format&&(e.backgroundColor="#fff");var f={active:this.get("active"),left:this.getLeft(),top:this.getTop()};this.set("active",!1),this.setPositionByOrigin(new b.Point(c.width/2,c.height/2),"center","center");var g=this.canvas;e.add(this);var h=e.toDataURL(a);return this.set(f).setCoords(),this.canvas=g,e.dispose(),e=null,h},isType:function(a){return this.type===a},complexity:function(){return 0},toJSON:function(a){return this.toObject(a)},setGradient:function(a,c){c||(c={});var d={colorStops:[]};d.type=c.type||(c.r1||c.r2?"radial":"linear"),d.coords={x1:c.x1,y1:c.y1,x2:c.x2,y2:c.y2},(c.r1||c.r2)&&(d.coords.r1=c.r1,d.coords.r2=c.r2);for(var e in c.colorStops){var f=new b.Color(c.colorStops[e]);d.colorStops.push({offset:e,color:f.toRgb(),opacity:f.getAlpha()})}return this.set(a,b.Gradient.forObject(this,d))},setPatternFill:function(a){return this.set("fill",new b.Pattern(a))},setShadow:function(a){return this.set("shadow",a?new b.Shadow(a):null)},setColor:function(a){return this.set("fill",a),this},setAngle:function(a){var b=("center"!==this.originX||"center"!==this.originY)&&this.centeredRotation;return b&&this._setOriginToCenter(),this.set("angle",a),b&&this._resetOrigin(),this},centerH:function(){return this.canvas.centerObjectH(this),this},centerV:function(){return this.canvas.centerObjectV(this),this},center:function(){return this.canvas.centerObject(this),this},remove:function(){return this.canvas.remove(this),this},getLocalPointer:function(a,b){b=b||this.canvas.getPointer(a);var c=this.translateToOriginPoint(this.getCenterPoint(),"left","top");return{x:b.x-c.x,y:b.y-c.y}},_setupCompositeOperation:function(a){this.globalCompositeOperation&&(this._prevGlobalCompositeOperation=a.globalCompositeOperation,a.globalCompositeOperation=this.globalCompositeOperation)},_restoreCompositeOperation:function(a){this.globalCompositeOperation&&this._prevGlobalCompositeOperation&&(a.globalCompositeOperation=this._prevGlobalCompositeOperation)}}),b.util.createAccessors(b.Object),b.Object.prototype.rotate=b.Object.prototype.setAngle,c(b.Object.prototype,b.Observable),b.Object.NUM_FRACTION_DIGITS=2,b.Object.__uid=0)}("undefined"!=typeof exports?exports:this),function(){var a=fabric.util.degreesToRadians;fabric.util.object.extend(fabric.Object.prototype,{translateToCenterPoint:function(b,c,d){var e=b.x,f=b.y,g=this.stroke?this.strokeWidth:0;return"left"===c?e=b.x+(this.getWidth()+g*this.scaleX)/2:"right"===c&&(e=b.x-(this.getWidth()+g*this.scaleX)/2),"top"===d?f=b.y+(this.getHeight()+g*this.scaleY)/2:"bottom"===d&&(f=b.y-(this.getHeight()+g*this.scaleY)/2),fabric.util.rotatePoint(new fabric.Point(e,f),b,a(this.angle))},translateToOriginPoint:function(b,c,d){var e=b.x,f=b.y,g=this.stroke?this.strokeWidth:0;return"left"===c?e=b.x-(this.getWidth()+g*this.scaleX)/2:"right"===c&&(e=b.x+(this.getWidth()+g*this.scaleX)/2),"top"===d?f=b.y-(this.getHeight()+g*this.scaleY)/2:"bottom"===d&&(f=b.y+(this.getHeight()+g*this.scaleY)/2),fabric.util.rotatePoint(new fabric.Point(e,f),b,a(this.angle))},getCenterPoint:function(){var a=new fabric.Point(this.left,this.top);return this.translateToCenterPoint(a,this.originX,this.originY)},getPointByOrigin:function(a,b){var c=this.getCenterPoint();return this.translateToOriginPoint(c,a,b)},toLocalPoint:function(b,c,d){var g,h,e=this.getCenterPoint(),f=this.stroke?this.strokeWidth:0;return c&&d?(g="left"===c?e.x-(this.getWidth()+f*this.scaleX)/2:"right"===c?e.x+(this.getWidth()+f*this.scaleX)/2:e.x,h="top"===d?e.y-(this.getHeight()+f*this.scaleY)/2:"bottom"===d?e.y+(this.getHeight()+f*this.scaleY)/2:e.y):(g=this.left,h=this.top),fabric.util.rotatePoint(new fabric.Point(b.x,b.y),e,-a(this.angle)).subtractEquals(new fabric.Point(g,h))},setPositionByOrigin:function(a,b,c){var d=this.translateToCenterPoint(a,b,c),e=this.translateToOriginPoint(d,this.originX,this.originY);this.set("left",e.x),this.set("top",e.y)},adjustPosition:function(b){var c=a(this.angle),d=this.getWidth()/2,e=Math.cos(c)*d,f=Math.sin(c)*d,g=this.getWidth(),h=Math.cos(c)*g,i=Math.sin(c)*g;"center"===this.originX&&"left"===b||"right"===this.originX&&"center"===b?(this.left-=e,this.top-=f):"left"===this.originX&&"center"===b||"center"===this.originX&&"right"===b?(this.left+=e,this.top+=f):"left"===this.originX&&"right"===b?(this.left+=h,this.top+=i):"right"===this.originX&&"left"===b&&(this.left-=h,this.top-=i),this.setCoords(),this.originX=b},_setOriginToCenter:function(){this._originalOriginX=this.originX,this._originalOriginY=this.originY;var a=this.getCenterPoint();this.originX="center",this.originY="center",this.left=a.x,this.top=a.y},_resetOrigin:function(){var a=this.translateToOriginPoint(this.getCenterPoint(),this._originalOriginX,this._originalOriginY);this.originX=this._originalOriginX,this.originY=this._originalOriginY,this.left=a.x,this.top=a.y,this._originalOriginX=null,this._originalOriginY=null},_getLeftTopCoords:function(){return this.translateToOriginPoint(this.getCenterPoint(),"left","center")}})}(),function(){var a=fabric.util.degreesToRadians;fabric.util.object.extend(fabric.Object.prototype,{oCoords:null,intersectsWithRect:function(a,b){var c=this.oCoords,d=new fabric.Point(c.tl.x,c.tl.y),e=new fabric.Point(c.tr.x,c.tr.y),f=new fabric.Point(c.bl.x,c.bl.y),g=new fabric.Point(c.br.x,c.br.y),h=fabric.Intersection.intersectPolygonRectangle([d,e,g,f],a,b);return"Intersection"===h.status},intersectsWithObject:function(a){function b(a){return{tl:new fabric.Point(a.tl.x,a.tl.y),tr:new fabric.Point(a.tr.x,a.tr.y),bl:new fabric.Point(a.bl.x,a.bl.y),br:new fabric.Point(a.br.x,a.br.y)}}var c=b(this.oCoords),d=b(a.oCoords),e=fabric.Intersection.intersectPolygonPolygon([c.tl,c.tr,c.br,c.bl],[d.tl,d.tr,d.br,d.bl]);return"Intersection"===e.status},isContainedWithinObject:function(a){var b=a.getBoundingRect(),c=new fabric.Point(b.left,b.top),d=new fabric.Point(b.left+b.width,b.top+b.height);return this.isContainedWithinRect(c,d)},isContainedWithinRect:function(a,b){var c=this.getBoundingRect();return c.left>=a.x&&c.left+c.width<=b.x&&c.top>=a.y&&c.top+c.height<=b.y},containsPoint:function(a){var b=this._getImageLines(this.oCoords),c=this._findCrossPoints(a,b);return 0!==c&&c%2===1},_getImageLines:function(a){return{topline:{o:a.tl,d:a.tr},rightline:{o:a.tr,d:a.br},bottomline:{o:a.br,d:a.bl},leftline:{o:a.bl,d:a.tl}}},_findCrossPoints:function(a,b){var c,d,e,f,g,h,j,i=0;for(var k in b)if(j=b[k],!(j.o.y=a.y&&j.d.y>=a.y||(j.o.x===j.d.x&&j.o.x>=a.x?(g=j.o.x,h=a.y):(c=0,d=(j.d.y-j.o.y)/(j.d.x-j.o.x),e=a.y-c*a.x,f=j.o.y-d*j.o.x,g=-(e-f)/(c-d),h=e+c*g),g>=a.x&&(i+=1),2!==i)))break;return i},getBoundingRectWidth:function(){return this.getBoundingRect().width},getBoundingRectHeight:function(){return this.getBoundingRect().height},getBoundingRect:function(){this.oCoords||this.setCoords();var a=[this.oCoords.tl.x,this.oCoords.tr.x,this.oCoords.br.x,this.oCoords.bl.x],b=fabric.util.array.min(a),c=fabric.util.array.max(a),d=Math.abs(b-c),e=[this.oCoords.tl.y,this.oCoords.tr.y,this.oCoords.br.y,this.oCoords.bl.y],f=fabric.util.array.min(e),g=fabric.util.array.max(e),h=Math.abs(f-g);return{left:b,top:f,width:d,height:h}},getWidth:function(){return this.width*this.scaleX},getHeight:function(){return this.height*this.scaleY},_constrainScale:function(a){return Math.abs(a)1?this.strokeWidth:0,c=a(this.angle),d=this.getViewportTransform(),e=function(a){return fabric.util.transformPoint(a,d)},f=this.width,g=this.height,h="round"===this.strokeLineCap||"square"===this.strokeLineCap,i="line"===this.type&&1===this.width,j="line"===this.type&&1===this.height,k=h&&j||"line"!==this.type,l=h&&i||"line"!==this.type;i?f=b:j&&(g=b),k&&(f+=b),l&&(g+=b),this.currentWidth=f*this.scaleX,this.currentHeight=g*this.scaleY,this.currentWidth<0&&(this.currentWidth=Math.abs(this.currentWidth));var m=Math.sqrt(Math.pow(this.currentWidth/2,2)+Math.pow(this.currentHeight/2,2)),n=Math.atan(isFinite(this.currentHeight/this.currentWidth)?this.currentHeight/this.currentWidth:0),o=Math.cos(n+c)*m,p=Math.sin(n+c)*m,q=Math.sin(c),r=Math.cos(c),s=this.getCenterPoint(),t=new fabric.Point(this.currentWidth,this.currentHeight),u=new fabric.Point(s.x-o,s.y-p),v=new fabric.Point(u.x+t.x*r,u.y+t.x*q),w=new fabric.Point(u.x-t.y*q,u.y+t.y*r),x=new fabric.Point(u.x+t.x/2*r,u.y+t.x/2*q),y=e(u),z=e(v),A=e(new fabric.Point(v.x-t.y*q,v.y+t.y*r)),B=e(w),C=e(new fabric.Point(u.x-t.y/2*q,u.y+t.y/2*r)),D=e(x),E=e(new fabric.Point(v.x-t.y/2*q,v.y+t.y/2*r)),F=e(new fabric.Point(w.x+t.x/2*r,w.y+t.x/2*q)),G=e(new fabric.Point(x.x,x.y)),H=Math.cos(n+c)*this.padding*Math.sqrt(2),I=Math.sin(n+c)*this.padding*Math.sqrt(2);return y=y.add(new fabric.Point(-H,-I)),z=z.add(new fabric.Point(I,-H)),A=A.add(new fabric.Point(H,I)),B=B.add(new fabric.Point(-I,H)),C=C.add(new fabric.Point((-H-I)/2,(-I+H)/2)),D=D.add(new fabric.Point((I-H)/2,-(I+H)/2)),E=E.add(new fabric.Point((I+H)/2,(I-H)/2)),F=F.add(new fabric.Point((H-I)/2,(H+I)/2)),G=G.add(new fabric.Point((I-H)/2,-(I+H)/2)),this.oCoords={tl:y,tr:z,br:A,bl:B,ml:C,mt:D,mr:E,mb:F,mtr:G},this._setCornerCoords&&this._setCornerCoords(),this}})}(),fabric.util.object.extend(fabric.Object.prototype,{sendToBack:function(){return this.group?fabric.StaticCanvas.prototype.sendToBack.call(this.group,this):this.canvas.sendToBack(this),this},bringToFront:function(){return this.group?fabric.StaticCanvas.prototype.bringToFront.call(this.group,this):this.canvas.bringToFront(this),this},sendBackwards:function(a){return this.group?fabric.StaticCanvas.prototype.sendBackwards.call(this.group,this,a):this.canvas.sendBackwards(this,a),this},bringForward:function(a){return this.group?fabric.StaticCanvas.prototype.bringForward.call(this.group,this,a):this.canvas.bringForward(this,a),this},moveTo:function(a){return this.group?fabric.StaticCanvas.prototype.moveTo.call(this.group,this,a):this.canvas.moveTo(this,a),this}}),fabric.util.object.extend(fabric.Object.prototype,{getSvgStyles:function(){var a=this.fill?this.fill.toLive?"url(#SVGID_"+this.fill.id+")":this.fill:"none",b=this.fillRule,c=this.stroke?this.stroke.toLive?"url(#SVGID_"+this.stroke.id+")":this.stroke:"none",d=this.strokeWidth?this.strokeWidth:"0",e=this.strokeDashArray?this.strokeDashArray.join(" "):"",f=this.strokeLineCap?this.strokeLineCap:"butt",g=this.strokeLineJoin?this.strokeLineJoin:"miter",h=this.strokeMiterLimit?this.strokeMiterLimit:"4",i="undefined"!=typeof this.opacity?this.opacity:"1",j=this.visible?"":" visibility: hidden;",k=this.shadow&&"text"!==this.type?"filter: url(#SVGID_"+this.shadow.id+");":"";return["stroke: ",c,"; ","stroke-width: ",d,"; ","stroke-dasharray: ",e,"; ","stroke-linecap: ",f,"; ","stroke-linejoin: ",g,"; ","stroke-miterlimit: ",h,"; ","fill: ",a,"; ","fill-rule: ",b,"; ","opacity: ",i,";",k,j].join("")},getSvgTransform:function(){if(this.group&&"path-group"===this.group.type)return"";var a=fabric.util.toFixed,b=this.getAngle(),c=!this.canvas||this.canvas.svgViewportTransformation?this.getViewportTransform():[1,0,0,1,0,0],d=fabric.util.transformPoint(this.getCenterPoint(),c),e=fabric.Object.NUM_FRACTION_DIGITS,f="path-group"===this.type?"":"translate("+a(d.x,e)+" "+a(d.y,e)+")",g=0!==b?" rotate("+a(b,e)+")":"",h=1===this.scaleX&&1===this.scaleY&&1===c[0]&&1===c[3]?"":" scale("+a(this.scaleX*c[0],e)+" "+a(this.scaleY*c[3],e)+")",i="path-group"===this.type?this.width*c[0]:0,j=this.flipX?" matrix(-1 0 0 1 "+i+" 0) ":"",k="path-group"===this.type?this.height*c[3]:0,l=this.flipY?" matrix(1 0 0 -1 0 "+k+")":"";return[f,g,h,j,l].join("")},getSvgTransformMatrix:function(){return this.transformMatrix?" matrix("+this.transformMatrix.join(" ")+")":""},_createBaseSVGMarkup:function(){var a=[];return this.fill&&this.fill.toLive&&a.push(this.fill.toSVG(this,!1)),this.stroke&&this.stroke.toLive&&a.push(this.stroke.toSVG(this,!1)),this.shadow&&a.push(this.shadow.toSVG(this)),a}}),fabric.util.object.extend(fabric.Object.prototype,{hasStateChanged:function(){return this.stateProperties.some(function(a){return this.get(a)!==this.originalState[a]},this)},saveState:function(a){return this.stateProperties.forEach(function(a){this.originalState[a]=this.get(a)},this),a&&a.stateProperties&&a.stateProperties.forEach(function(a){this.originalState[a]=this.get(a)},this),this},setupState:function(){return this.originalState={},this.saveState(),this}}),function(){var a=fabric.util.degreesToRadians,b=function(){return"undefined"!=typeof G_vmlCanvasManager};fabric.util.object.extend(fabric.Object.prototype,{_controlsVisibility:null,_findTargetCorner:function(a){if(!this.hasControls||!this.active)return!1;var d,e,b=a.x,c=a.y;for(var f in this.oCoords)if(this.isControlVisible(f)&&("mtr"!==f||this.hasRotatingPoint)&&(!this.get("lockUniScaling")||"mt"!==f&&"mr"!==f&&"mb"!==f&&"ml"!==f)&&(e=this._getImageLines(this.oCoords[f].corner),d=this._findCrossPoints({x:b,y:c},e),0!==d&&d%2===1))return this.__corner=f,f;return!1},_setCornerCoords:function(){var b=this.oCoords,c=a(this.angle),d=a(45-this.angle),e=Math.sqrt(2*Math.pow(this.cornerSize,2))/2,f=e*Math.cos(d),g=e*Math.sin(d),h=Math.sin(c),i=Math.cos(c);b.tl.corner={tl:{x:b.tl.x-g,y:b.tl.y-f},tr:{x:b.tl.x+f,y:b.tl.y-g},bl:{x:b.tl.x-f,y:b.tl.y+g},br:{x:b.tl.x+g,y:b.tl.y+f}},b.tr.corner={tl:{x:b.tr.x-g,y:b.tr.y-f},tr:{x:b.tr.x+f,y:b.tr.y-g},br:{x:b.tr.x+g,y:b.tr.y+f},bl:{x:b.tr.x-f,y:b.tr.y+g}},b.bl.corner={tl:{x:b.bl.x-g,y:b.bl.y-f},bl:{x:b.bl.x-f,y:b.bl.y+g},br:{x:b.bl.x+g,y:b.bl.y+f},tr:{x:b.bl.x+f,y:b.bl.y-g}},b.br.corner={tr:{x:b.br.x+f,y:b.br.y-g},bl:{x:b.br.x-f,y:b.br.y+g},br:{x:b.br.x+g,y:b.br.y+f},tl:{x:b.br.x-g,y:b.br.y-f}},b.ml.corner={tl:{x:b.ml.x-g,y:b.ml.y-f},tr:{x:b.ml.x+f,y:b.ml.y-g},bl:{x:b.ml.x-f,y:b.ml.y+g},br:{x:b.ml.x+g,y:b.ml.y+f}},b.mt.corner={tl:{x:b.mt.x-g,y:b.mt.y-f},tr:{x:b.mt.x+f,y:b.mt.y-g},bl:{x:b.mt.x-f,y:b.mt.y+g},br:{x:b.mt.x+g,y:b.mt.y+f}},b.mr.corner={tl:{x:b.mr.x-g,y:b.mr.y-f},tr:{x:b.mr.x+f,y:b.mr.y-g},bl:{x:b.mr.x-f,y:b.mr.y+g},br:{x:b.mr.x+g,y:b.mr.y+f}},b.mb.corner={tl:{x:b.mb.x-g,y:b.mb.y-f},tr:{x:b.mb.x+f,y:b.mb.y-g},bl:{x:b.mb.x-f,y:b.mb.y+g},br:{x:b.mb.x+g,y:b.mb.y+f}},b.mtr.corner={tl:{x:b.mtr.x-g+h*this.rotatingPointOffset,y:b.mtr.y-f-i*this.rotatingPointOffset},tr:{x:b.mtr.x+f+h*this.rotatingPointOffset,y:b.mtr.y-g-i*this.rotatingPointOffset},bl:{x:b.mtr.x-f+h*this.rotatingPointOffset,y:b.mtr.y+g-i*this.rotatingPointOffset},br:{x:b.mtr.x+g+h*this.rotatingPointOffset,y:b.mtr.y+f-i*this.rotatingPointOffset}}},drawBorders:function(a){if(!this.hasBorders)return this;var b=this.padding,c=2*b,d=this.getViewportTransform();a.save(),a.globalAlpha=this.isMoving?this.borderOpacityWhenMoving:1,a.strokeStyle=this.borderColor;var e=1/this._constrainScale(this.scaleX),f=1/this._constrainScale(this.scaleY);a.lineWidth=1/this.borderScaleFactor;var g=this.getWidth(),h=this.getHeight(),i=this.strokeWidth>1?this.strokeWidth:0,j="round"===this.strokeLineCap||"square"===this.strokeLineCap,k="line"===this.type&&1===this.width,l="line"===this.type&&1===this.height,m=j&&l||"line"!==this.type,n=j&&k||"line"!==this.type;k?g=i/e:l&&(h=i/f),m&&(g+=i/e),n&&(h+=i/f);var o=fabric.util.transformPoint(new fabric.Point(g,h),d,!0),p=o.x,q=o.y;if(this.group&&(p*=this.group.scaleX,q*=this.group.scaleY),a.strokeRect(~~(-(p/2)-b)-.5,~~(-(q/2)-b)-.5,~~(p+c)+1,~~(q+c)+1),this.hasRotatingPoint&&this.isControlVisible("mtr")&&!this.get("lockRotation")&&this.hasControls){var r=(-q-2*b)/2;a.beginPath(),a.moveTo(0,r),a.lineTo(0,r-this.rotatingPointOffset),a.closePath(),a.stroke()}return a.restore(),this},drawControls:function(a){if(!this.hasControls)return this;var b=this.cornerSize,c=b/2,d=this.getViewportTransform(),e=this.strokeWidth>1?this.strokeWidth:0,f=this.width,g=this.height,h="round"===this.strokeLineCap||"square"===this.strokeLineCap,i="line"===this.type&&1===this.width,j="line"===this.type&&1===this.height,k=h&&j||"line"!==this.type,l=h&&i||"line"!==this.type;i?f=e:j&&(g=e),k&&(f+=e),l&&(g+=e),f*=this.scaleX,g*=this.scaleY;var m=fabric.util.transformPoint(new fabric.Point(f,g),d,!0),n=m.x,o=m.y,p=-(n/2),q=-(o/2),r=this.padding,s=c,t=c-b,u=this.transparentCorners?"strokeRect":"fillRect";return a.save(),a.lineWidth=1,a.globalAlpha=this.isMoving?this.borderOpacityWhenMoving:1,a.strokeStyle=a.fillStyle=this.cornerColor,this._drawControl("tl",a,u,p-s-r,q-s-r),this._drawControl("tr",a,u,p+n-s+r,q-s-r),this._drawControl("bl",a,u,p-s-r,q+o+t+r),this._drawControl("br",a,u,p+n+t+r,q+o+t+r),this.get("lockUniScaling")||(this._drawControl("mt",a,u,p+n/2-s,q-s-r),this._drawControl("mb",a,u,p+n/2-s,q+o+t+r),this._drawControl("mr",a,u,p+n+t+r,q+o/2-s),this._drawControl("ml",a,u,p-s-r,q+o/2-s)),this.hasRotatingPoint&&this._drawControl("mtr",a,u,p+n/2-s,q-this.rotatingPointOffset-this.cornerSize/2-r),a.restore(),this},_drawControl:function(a,c,d,e,f){var g=this.cornerSize;this.isControlVisible(a)&&(b()||this.transparentCorners||c.clearRect(e,f,g,g),c[d](e,f,g,g))},isControlVisible:function(a){return this._getControlsVisibility()[a]},setControlVisible:function(a,b){return this._getControlsVisibility()[a]=b,this},setControlsVisibility:function(a){a||(a={});for(var b in a)this.setControlVisible(b,a[b]);return this},_getControlsVisibility:function(){return this._controlsVisibility||(this._controlsVisibility={tl:!0,tr:!0,br:!0,bl:!0,ml:!0,mt:!0,mr:!0,mb:!0,mtr:!0}),this._controlsVisibility}})}(),fabric.util.object.extend(fabric.StaticCanvas.prototype,{FX_DURATION:500,fxCenterObjectH:function(a,b){b=b||{};var c=function(){},d=b.onComplete||c,e=b.onChange||c,f=this;return fabric.util.animate({startValue:a.get("left"),endValue:this.getCenter().left,duration:this.FX_DURATION,onChange:function(b){a.set("left",b),f.renderAll(),e()},onComplete:function(){a.setCoords(),d()}}),this},fxCenterObjectV:function(a,b){b=b||{};var c=function(){},d=b.onComplete||c,e=b.onChange||c,f=this;return fabric.util.animate({startValue:a.get("top"),endValue:this.getCenter().top,duration:this.FX_DURATION,onChange:function(b){a.set("top",b),f.renderAll(),e()},onComplete:function(){a.setCoords(),d()}}),this},fxRemove:function(a,b){b=b||{};var c=function(){},d=b.onComplete||c,e=b.onChange||c,f=this;return fabric.util.animate({startValue:a.get("opacity"),endValue:0,duration:this.FX_DURATION,onStart:function(){a.set("active",!1)},onChange:function(b){a.set("opacity",b),f.renderAll(),e()},onComplete:function(){f.remove(a),d()}}),this}}),fabric.util.object.extend(fabric.Object.prototype,{animate:function(){if(arguments[0]&&"object"==typeof arguments[0]){var b,c,a=[];for(b in arguments[0])a.push(b);for(var d=0,e=a.length;d\n'),a?a(b.join("")):b.join("")},complexity:function(){return 1}}),b.Line.ATTRIBUTE_NAMES=b.SHARED_ATTRIBUTES.concat("x1 y1 x2 y2".split(" ")),b.Line.fromElement=function(a,d){var e=b.parseAttributes(a,b.Line.ATTRIBUTE_NAMES),f=[e.x1||0,e.y1||0,e.x2||0,e.y2||0];return new b.Line(f,c(e,d))},void(b.Line.fromObject=function(a){var c=[a.x1,a.y1,a.x2,a.y2];return new b.Line(c,a)}))}("undefined"!=typeof exports?exports:this),function(a){"use strict";function e(a){return"radius"in a&&a.radius>0}var b=a.fabric||(a.fabric={}),c=Math.PI,d=b.util.object.extend;return b.Circle?void b.warn("fabric.Circle is already defined."):(b.Circle=b.util.createClass(b.Object,{type:"circle",radius:0,startAngle:0,endAngle:2*c,initialize:function(a){a=a||{},this.callSuper("initialize",a),this.set("radius",a.radius||0),this.startAngle=a.startAngle||this.startAngle,this.endAngle=a.endAngle||this.endAngle},_set:function(a,b){return this.callSuper("_set",a,b),"radius"===a&&this.setRadius(b),this},toObject:function(a){return d(this.callSuper("toObject",a),{radius:this.get("radius"),startAngle:this.startAngle,endAngle:this.endAngle})},toSVG:function(a){var b=this._createBaseSVGMarkup(),d=0,e=0,f=(this.endAngle-this.startAngle)%(2*c);if(0===f)this.group&&"path-group"===this.group.type&&(d=this.left+this.radius,e=this.top+this.radius),b.push("\n');else{var g=Math.cos(this.startAngle)*this.radius,h=Math.sin(this.startAngle)*this.radius,i=Math.cos(this.endAngle)*this.radius,j=Math.sin(this.endAngle)*this.radius,k=f>c?"1":"0";b.push('\n')}return a?a(b.join("")):b.join("")},_render:function(a,b){a.beginPath(),a.arc(b?this.left+this.radius:0,b?this.top+this.radius:0,this.radius,this.startAngle,this.endAngle,!1),this._renderFill(a),this._renderStroke(a)},getRadiusX:function(){return this.get("radius")*this.get("scaleX")},getRadiusY:function(){return this.get("radius")*this.get("scaleY")},setRadius:function(a){this.radius=a,this.set("width",2*a).set("height",2*a)},complexity:function(){return 1}}),b.Circle.ATTRIBUTE_NAMES=b.SHARED_ATTRIBUTES.concat("cx cy r".split(" ")),b.Circle.fromElement=function(a,c){c||(c={});var f=b.parseAttributes(a,b.Circle.ATTRIBUTE_NAMES);if(!e(f))throw new Error("value of `r` attribute is required and can not be negative");f.left=f.left||0,f.top=f.top||0;var g=new b.Circle(d(f,c));return g.left-=g.radius,g.top-=g.radius,g},void(b.Circle.fromObject=function(a){return new b.Circle(a)}))}("undefined"!=typeof exports?exports:this),function(a){"use strict";var b=a.fabric||(a.fabric={});return b.Triangle?void b.warn("fabric.Triangle is already defined"):(b.Triangle=b.util.createClass(b.Object,{type:"triangle",initialize:function(a){a=a||{},this.callSuper("initialize",a),this.set("width",a.width||100).set("height",a.height||100)},_render:function(a){var b=this.width/2,c=this.height/2;a.beginPath(),a.moveTo(-b,c),a.lineTo(0,-c),a.lineTo(b,c),a.closePath(),this._renderFill(a),this._renderStroke(a)},_renderDashedStroke:function(a){var c=this.width/2,d=this.height/2;a.beginPath(),b.util.drawDashedLine(a,-c,d,0,-d,this.strokeDashArray),b.util.drawDashedLine(a,0,-d,c,d,this.strokeDashArray),b.util.drawDashedLine(a,c,d,-c,d,this.strokeDashArray),a.closePath()},toSVG:function(a){var b=this._createBaseSVGMarkup(),c=this.width/2,d=this.height/2,e=[-c+" "+d,"0 "+-d,c+" "+d].join(",");return b.push("'),a?a(b.join("")):b.join("")},complexity:function(){return 1}}),void(b.Triangle.fromObject=function(a){return new b.Triangle(a)}))}("undefined"!=typeof exports?exports:this),function(a){"use strict";var b=a.fabric||(a.fabric={}),c=2*Math.PI,d=b.util.object.extend;return b.Ellipse?void b.warn("fabric.Ellipse is already defined."):(b.Ellipse=b.util.createClass(b.Object,{type:"ellipse",rx:0,ry:0,initialize:function(a){a=a||{},this.callSuper("initialize",a),this.set("rx",a.rx||0),this.set("ry",a.ry||0)},_set:function(a,b){switch(this.callSuper("_set",a,b),a){case"rx":this.rx=b,this.set("width",2*b);break;case"ry":this.ry=b,this.set("height",2*b)}return this},getRx:function(){return this.get("rx")*this.get("scaleX")},getRy:function(){return this.get("ry")*this.get("scaleY")},toObject:function(a){return d(this.callSuper("toObject",a),{rx:this.get("rx"),ry:this.get("ry")})},toSVG:function(a){var b=this._createBaseSVGMarkup(),c=0,d=0;return this.group&&"path-group"===this.group.type&&(c=this.left+this.rx,d=this.top+this.ry),b.push("\n'),a?a(b.join("")):b.join("")},_render:function(a,b){a.beginPath(),a.save(),a.transform(1,0,0,this.ry/this.rx,0,0),a.arc(b?this.left+this.rx:0,b?(this.top+this.ry)*this.rx/this.ry:0,this.rx,0,c,!1),a.restore(),this._renderFill(a),this._renderStroke(a)},complexity:function(){return 1}}),b.Ellipse.ATTRIBUTE_NAMES=b.SHARED_ATTRIBUTES.concat("cx cy rx ry".split(" ")),b.Ellipse.fromElement=function(a,c){c||(c={});var e=b.parseAttributes(a,b.Ellipse.ATTRIBUTE_NAMES);e.left=e.left||0,e.top=e.top||0;var f=new b.Ellipse(d(e,c));return f.top-=f.ry,f.left-=f.rx,f},void(b.Ellipse.fromObject=function(a){return new b.Ellipse(a)}))}("undefined"!=typeof exports?exports:this),function(a){"use strict";var b=a.fabric||(a.fabric={}),c=b.util.object.extend;if(b.Rect)return void console.warn("fabric.Rect is already defined");var d=b.Object.prototype.stateProperties.concat();d.push("rx","ry","x","y"),b.Rect=b.util.createClass(b.Object,{stateProperties:d,type:"rect",rx:0,ry:0,strokeDashArray:null,initialize:function(a){a=a||{},this.callSuper("initialize",a),this._initRxRy()},_initRxRy:function(){this.rx&&!this.ry?this.ry=this.rx:this.ry&&!this.rx&&(this.rx=this.ry)},_render:function(a,b){if(1===this.width&&1===this.height)return void a.fillRect(0,0,1,1);var c=this.rx?Math.min(this.rx,this.width/2):0,d=this.ry?Math.min(this.ry,this.height/2):0,e=this.width,f=this.height,g=b?this.left:-this.width/2,h=b?this.top:-this.height/2,i=0!==c||0!==d,j=.4477152502;a.beginPath(),a.moveTo(g+c,h),a.lineTo(g+e-c,h),i&&a.bezierCurveTo(g+e-j*c,h,g+e,h+j*d,g+e,h+d),a.lineTo(g+e,h+f-d),i&&a.bezierCurveTo(g+e,h+f-j*d,g+e-j*c,h+f,g+e-c,h+f),a.lineTo(g+c,h+f),i&&a.bezierCurveTo(g+j*c,h+f,g,h+f-j*d,g,h+f-d),a.lineTo(g,h+d),i&&a.bezierCurveTo(g,h+j*d,g+j*c,h,g+c,h),a.closePath(),this._renderFill(a),this._renderStroke(a)},_renderDashedStroke:function(a){var c=-this.width/2,d=-this.height/2,e=this.width,f=this.height;a.beginPath(),b.util.drawDashedLine(a,c,d,c+e,d,this.strokeDashArray),b.util.drawDashedLine(a,c+e,d,c+e,d+f,this.strokeDashArray),b.util.drawDashedLine(a,c+e,d+f,c,d+f,this.strokeDashArray),b.util.drawDashedLine(a,c,d+f,c,d,this.strokeDashArray),a.closePath()},toObject:function(a){var b=c(this.callSuper("toObject",a),{rx:this.get("rx")||0,ry:this.get("ry")||0});return this.includeDefaultValues||this._removeDefaultValues(b),b},toSVG:function(a){var b=this._createBaseSVGMarkup(),c=this.left,d=this.top;return this.group&&"path-group"===this.group.type||(c=-this.width/2,d=-this.height/2),b.push("\n'),a?a(b.join("")):b.join("")},complexity:function(){return 1}}),b.Rect.ATTRIBUTE_NAMES=b.SHARED_ATTRIBUTES.concat("x y rx ry width height".split(" ")),b.Rect.fromElement=function(a,d){if(!a)return null;d=d||{};var e=b.parseAttributes(a,b.Rect.ATTRIBUTE_NAMES);return e.left=e.left||0,e.top=e.top||0,new b.Rect(c(d?b.util.object.clone(d):{},e))},b.Rect.fromObject=function(a){return new b.Rect(a)}}("undefined"!=typeof exports?exports:this),function(a){"use strict";var b=a.fabric||(a.fabric={});return b.Polyline?void b.warn("fabric.Polyline is already defined"):(b.Polyline=b.util.createClass(b.Object,{type:"polyline",points:null,minX:0,minY:0,initialize:function(a,c){return b.Polygon.prototype.initialize.call(this,a,c)},_calcDimensions:function(){return b.Polygon.prototype._calcDimensions.call(this)},_applyPointOffset:function(){return b.Polygon.prototype._applyPointOffset.call(this)},toObject:function(a){return b.Polygon.prototype.toObject.call(this,a)},toSVG:function(a){return b.Polygon.prototype.toSVG.call(this,a)},_render:function(a){b.Polygon.prototype.commonRender.call(this,a),this._renderFill(a),this._renderStroke(a)},_renderDashedStroke:function(a){var c,d;a.beginPath();for(var e=0,f=this.points.length;e\n'),a?a(c.join("")):c.join("")},_render:function(a){this.commonRender(a),this._renderFill(a),(this.stroke||this.strokeDashArray)&&(a.closePath(),this._renderStroke(a))},commonRender:function(a){var b;a.beginPath(),this._applyPointOffset&&(this.group&&"path-group"===this.group.type||this._applyPointOffset(),this._applyPointOffset=null),a.moveTo(this.points[0].x,this.points[0].y);for(var c=0,d=this.points.length;c"},toObject:function(a){var b=e(this.callSuper("toObject",a),{path:this.path.map(function(a){return a.slice()}),pathOffset:this.pathOffset});return this.sourcePath&&(b.sourcePath=this.sourcePath),this.transformMatrix&&(b.transformMatrix=this.transformMatrix),b},toDatalessObject:function(a){var b=this.toObject(a);return this.sourcePath&&(b.path=this.sourcePath),delete b.sourcePath,b},toSVG:function(a){for(var b=[],c=this._createBaseSVGMarkup(),d="",e=0,f=this.path.length;e\n"),a?a(c.join("")):c.join("")},complexity:function(){return this.path.length},_parsePath:function(){for(var c,d,f,g,k,a=[],b=[],e=/([-+]?((\d+\.\d+)|((\d+)|(\.\d+)))(?:e[-+]?\d+)?)/gi,j=0,l=this.path.length;jp)for(var r=1,s=k.length;r\n"],e=0,f=b.length;e\n"),a?a(d.join("")):d.join("")},toString:function(){return"#"},isSameColor:function(){var a=(this.getObjects()[0].get("fill")||"").toLowerCase();return this.getObjects().every(function(b){return(b.get("fill")||"").toLowerCase()===a})},complexity:function(){return this.paths.reduce(function(a,b){return a+(b&&b.complexity?b.complexity():0)},0)},getObjects:function(){return this.paths}}),b.PathGroup.fromObject=function(a,c){"string"==typeof a.paths?b.loadSVGFromURL(a.paths,function(d){var e=a.paths;delete a.paths;var f=b.util.groupSVGElements(d,a,e);c(f)}):b.util.enlivenObjects(a.paths,function(d){delete a.paths,c(new b.PathGroup(d,a))})},void(b.PathGroup.async=!0))}("undefined"!=typeof exports?exports:this),function(a){"use strict";var b=a.fabric||(a.fabric={}),c=b.util.object.extend,d=b.util.array.min,e=b.util.array.max,f=b.util.array.invoke;if(!b.Group){var g={lockMovementX:!0,lockMovementY:!0,lockRotation:!0,lockScalingX:!0,lockScalingY:!0,lockUniScaling:!0};b.Group=b.util.createClass(b.Object,b.Collection,{type:"group",initialize:function(a,b){b=b||{},this._objects=a||[];for(var d=this._objects.length;d--;)this._objects[d].group=this;this.originalState={},this.callSuper("initialize"),this._calcBounds(),this._updateObjectsCoords(),b&&c(this,b),this.setCoords(),this.saveCoords()},_updateObjectsCoords:function(){this.forEachObject(this._updateObjectCoords,this)},_updateObjectCoords:function(a){var b=a.getLeft(),c=a.getTop();a.set({originalLeft:b,originalTop:c,left:b-this.left,top:c-this.top}),a.setCoords(),a.__origHasControls=a.hasControls,a.hasControls=!1},toString:function(){return"#"},addWithUpdate:function(a){return this._restoreObjectsState(),a&&(this._objects.push(a),a.group=this),this.forEachObject(this._setObjectActive,this),this._calcBounds(),this._updateObjectsCoords(),this},_setObjectActive:function(a){a.set("active",!0),a.group=this},removeWithUpdate:function(a){return this._moveFlippedObject(a),this._restoreObjectsState(),this.forEachObject(this._setObjectActive,this),this.remove(a),this._calcBounds(),this._updateObjectsCoords(),this},_onObjectAdded:function(a){a.group=this},_onObjectRemoved:function(a){delete a.group,a.set("active",!1)},delegatedProperties:{fill:!0,opacity:!0,fontFamily:!0,fontWeight:!0,fontSize:!0,fontStyle:!0,lineHeight:!0,textDecoration:!0,textAlign:!0,backgroundColor:!0},_set:function(a,b){if(a in this.delegatedProperties){var c=this._objects.length;for(this[a]=b;c--;)this._objects[c].set(a,b)}else this[a]=b},toObject:function(a){return c(this.callSuper("toObject",a),{objects:f(this._objects,"toObject",a)})},render:function(a){if(this.visible){a.save(),this.clipTo&&b.util.clipContext(this,a);for(var c=0,d=this._objects.length;c\n'],c=0,d=this._objects.length;c\n"),a?a(b.join("")):b.join("")},get:function(a){if(a in g){if(this[a])return this[a];for(var b=0,c=this._objects.length;b\n','\n"),this.stroke||this.strokeDashArray){var e=this.fill;this.fill=null,b.push("\n'),this.fill=e}return b.push("
        \n"),a?a(b.join("")):b.join("")},getSrc:function(){if(this.getElement())return this.getElement().src||this.getElement()._src},toString:function(){return'#'},clone:function(a,b){this.constructor.fromObject(this.toObject(b),a)},applyFilters:function(a){if(this._originalElement){if(0===this.filters.length)return this._element=this._originalElement,void(a&&a());var b=this._originalElement,c=fabric.util.createCanvasElement(),d=fabric.util.createImage(),e=this;return c.width=b.width,c.height=b.height,c.getContext("2d").drawImage(b,0,0,b.width,b.height),this.filters.forEach(function(a){a&&a.applyTo(c)}),d.width=b.width,d.height=b.height,fabric.isLikelyNode?(d.src=c.toBuffer(void 0,fabric.Image.pngCompression),e._element=d,a&&a()):(d.onload=function(){e._element=d,a&&a(),d.onload=c=b=null},d.src=c.toDataURL("image/png")),this}},_render:function(a,b){ this._element&&a.drawImage(this._element,b?this.left:-this.width/2,b?this.top:-this.height/2,this.width,this.height),this._renderStroke(a)},_resetWidthHeight:function(){var a=this.getElement();this.set("width",a.width),this.set("height",a.height)},_initElement:function(a){this.setElement(fabric.util.getById(a)),fabric.util.addClass(this.getElement(),fabric.Image.CSS_CANVAS)},_initConfig:function(a){a||(a={}),this.setOptions(a),this._setWidthHeight(a),this._element&&this.crossOrigin&&(this._element.crossOrigin=this.crossOrigin)},_initFilters:function(a,b){a.filters&&a.filters.length?fabric.util.enlivenObjects(a.filters,function(a){b&&b(a)},"fabric.Image.filters"):b&&b()},_setWidthHeight:function(a){this.width="width"in a?a.width:this.getElement()?this.getElement().width||0:0,this.height="height"in a?a.height:this.getElement()?this.getElement().height||0:0},complexity:function(){return 1}}),fabric.Image.CSS_CANVAS="canvas-img",fabric.Image.prototype.getSvgSrc=fabric.Image.prototype.getSrc,fabric.Image.fromObject=function(a,b){fabric.util.loadImage(a.src,function(c){fabric.Image.prototype._initFilters.call(a,a,function(d){a.filters=d||[];var e=new fabric.Image(c,a);b&&b(e)})},null,a.crossOrigin)},fabric.Image.fromURL=function(a,b,c){fabric.util.loadImage(a,function(a){b(new fabric.Image(a,c))},null,c&&c.crossOrigin)},fabric.Image.ATTRIBUTE_NAMES=fabric.SHARED_ATTRIBUTES.concat("x y width height xlink:href".split(" ")),fabric.Image.fromElement=function(a,c,d){var e=fabric.parseAttributes(a,fabric.Image.ATTRIBUTE_NAMES);fabric.Image.fromURL(e["xlink:href"],c,b(d?fabric.util.object.clone(d):{},e))},fabric.Image.async=!0,void(fabric.Image.pngCompression=1))}("undefined"!=typeof exports?exports:this),fabric.util.object.extend(fabric.Object.prototype,{_getAngleValueForStraighten:function(){var a=this.getAngle()%360;return a>0?90*Math.round((a-1)/90):90*Math.round(a/90)},straighten:function(){return this.setAngle(this._getAngleValueForStraighten()),this},fxStraighten:function(a){a=a||{};var b=function(){},c=a.onComplete||b,d=a.onChange||b,e=this;return fabric.util.animate({startValue:this.get("angle"),endValue:this._getAngleValueForStraighten(),duration:this.FX_DURATION,onChange:function(a){e.setAngle(a),d()},onComplete:function(){e.setCoords(),c()},onStart:function(){e.set("active",!1)}}),this}}),fabric.util.object.extend(fabric.StaticCanvas.prototype,{straightenObject:function(a){return a.straighten(),this.renderAll(),this},fxStraightenObject:function(a){return a.fxStraighten({onChange:this.renderAll.bind(this)}),this}}),fabric.Image.filters=fabric.Image.filters||{},fabric.Image.filters.BaseFilter=fabric.util.createClass({type:"BaseFilter",toObject:function(){return{type:this.type}},toJSON:function(){return this.toObject()}}),function(a){"use strict";var b=a.fabric||(a.fabric={}),c=b.util.object.extend;b.Image.filters.Brightness=b.util.createClass(b.Image.filters.BaseFilter,{type:"Brightness",initialize:function(a){a=a||{},this.brightness=a.brightness||0},applyTo:function(a){for(var b=a.getContext("2d"),c=b.getImageData(0,0,a.width,a.height),d=c.data,e=this.brightness,f=0,g=d.length;fi||A<0||A>h)){var B=4*(z*h+A),C=b[x*e+y];t+=g[B]*C,u+=g[B+1]*C,v+=g[B+2]*C,w+=g[B+3]*C}}m[s]=t,m[s+1]=u,m[s+2]=v,m[s+3]=w+n*(255-w)}c.putImageData(l,0,0)},toObject:function(){return c(this.callSuper("toObject"),{opaque:this.opaque,matrix:this.matrix})}}),b.Image.filters.Convolute.fromObject=function(a){return new b.Image.filters.Convolute(a)}}("undefined"!=typeof exports?exports:this),function(a){"use strict";var b=a.fabric||(a.fabric={}),c=b.util.object.extend;b.Image.filters.GradientTransparency=b.util.createClass(b.Image.filters.BaseFilter,{type:"GradientTransparency",initialize:function(a){a=a||{},this.threshold=a.threshold||100},applyTo:function(a){for(var b=a.getContext("2d"),c=b.getImageData(0,0,a.width,a.height),d=c.data,e=this.threshold,f=d.length,g=0,h=d.length;g-1?a.channel:0},applyTo:function(a){if(this.mask){var i,c=a.getContext("2d"),d=c.getImageData(0,0,a.width,a.height),e=d.data,f=this.mask.getElement(),g=b.util.createCanvasElement(),h=this.channel,j=d.width*d.height*4;g.width=f.width,g.height=f.height,g.getContext("2d").drawImage(f,0,0,f.width,f.height);var k=g.getContext("2d").getImageData(0,0,f.width,f.height),l=k.data;for(i=0;ig&&j>g&&k>g&&h(i-j)'},_render:function(a){"undefined"==typeof Cufon||this.useNative===!0?this._renderViaNative(a):this._renderViaCufon(a)},_renderViaNative:function(a){var c=this.text.split(this._reNewline);this._setTextStyles(a),this.width=this._getTextWidth(a,c),this.height=this._getTextHeight(a,c),this.clipTo&&b.util.clipContext(this,a),this._renderTextBackground(a,c),this._translateForTextAlign(a),this._renderText(a,c),"left"!==this.textAlign&&"justify"!==this.textAlign&&a.restore(),this._renderTextDecoration(a,c),this.clipTo&&a.restore(),this._setBoundaries(a,c),this._totalLineHeight=0},_renderText:function(a,b){a.save(),this._setShadow(a),this._setupCompositeOperation(a),this._renderTextFill(a,b),this._renderTextStroke(a,b),this._restoreCompositeOperation(a),this._removeShadow(a),a.restore()},_translateForTextAlign:function(a){"left"!==this.textAlign&&"justify"!==this.textAlign&&(a.save(),a.translate("center"===this.textAlign?this.width/2:this.width,0))},_setBoundaries:function(a,b){this._boundaries=[];for(var c=0,d=b.length;cc&&(c=f)}return c},_renderChars:function(a,b,c,d,e){b[a](c,d,e)},_renderTextLine:function(a,b,c,d,e,f){if(e-=this.fontSize/4,"justify"!==this.textAlign)return void this._renderChars(a,b,c,d,e,f);var g=b.measureText(c).width,h=this.width;if(h>g)for(var i=c.split(/\s+/),j=b.measureText(c.replace(/\s+/g,"")).width,k=h-j,l=i.length-1,m=k/l,n=0,o=0,p=i.length;o-1&&e(this.fontSize*this.lineHeight),this.textDecoration.indexOf("line-through")>-1&&e(this.fontSize*this.lineHeight-this.fontSize/2),this.textDecoration.indexOf("overline")>-1&&e(this.fontSize*this.lineHeight-this.fontSize)}},_getFontDeclaration:function(){return[b.isLikelyNode?this.fontWeight:this.fontStyle,b.isLikelyNode?this.fontStyle:this.fontWeight,this.fontSize+"px",b.isLikelyNode?'"'+this.fontFamily+'"':this.fontFamily].join(" ")},render:function(a,b){if(this.visible){a.save(),b||this.transform(a);var c=this.group&&"path-group"===this.group.type;c&&a.translate(-this.group.width/2,-this.group.height/2),this.transformMatrix&&a.transform.apply(a,this.transformMatrix),c&&a.translate(this.left,this.top),this._render(a),a.restore()}},toObject:function(a){var b=c(this.callSuper("toObject",a),{text:this.text,fontSize:this.fontSize,fontWeight:this.fontWeight,fontFamily:this.fontFamily,fontStyle:this.fontStyle,lineHeight:this.lineHeight,textDecoration:this.textDecoration,textAlign:this.textAlign,path:this.path,textBackgroundColor:this.textBackgroundColor,useNative:this.useNative});return this.includeDefaultValues||this._removeDefaultValues(b),b},toSVG:function(a){var b=[],c=this.text.split(this._reNewline),d=this._getSVGLeftTopOffsets(c),e=this._getSVGTextAndBg(d.lineTop,d.textLeft,c),f=this._getSVGShadows(d.lineTop,c);return d.textTop+=this._fontAscent?this._fontAscent/5*this.lineHeight:0,this._wrapSVGTextAndBg(b,e,f,d),a?a(b.join("")):b.join("")},_getSVGLeftTopOffsets:function(a){var b=this.useNative?this.fontSize*this.lineHeight:-this._fontAscent-this._fontAscent/5*this.lineHeight,c=-(this.width/2),d=this.useNative?this.fontSize-1:this.height/2-a.length*this.fontSize-this._totalLineHeight;return{textLeft:c+(this.group&&"path-group"===this.group.type?this.left:0),textTop:d+(this.group&&"path-group"===this.group.type?this.top:0),lineTop:b}},_wrapSVGTextAndBg:function(a,b,c,d){a.push('\n',b.textBgRects.join(""),"',c.join(""),b.textSpans.join(""),"\n","\n")},_getSVGShadows:function(a,c){var f,g,d=[],h=1;if(!this.shadow||!this._boundaries)return d;for(f=0,g=c.length;f",b.util.string.escapeXml(c[f]),""),h=1}else h++;return d},_getSVGTextAndBg:function(a,b,c){var d=[],e=[],f=1;this._setSVGBg(e);for(var g=0,h=c.length;g",b.util.string.escapeXml(a),"")},_setSVGTextLineBg:function(a,b,c,d){a.push("\n')},_setSVGBg:function(a){this.backgroundColor&&this._boundaries&&a.push("')},_getFillAttributes:function(a){var c=a&&"string"==typeof a?new b.Color(a):"";return c&&c.getSource()&&1!==c.getAlpha()?'opacity="'+c.getAlpha()+'" fill="'+c.setAlpha(1).toRgb()+'"':'fill="'+a+'"'},_set:function(a,b){"fontFamily"===a&&this.path&&(this.path=this.path.replace(/(.*?)([^\/]*)(\.font\.js)/,"$1"+b+"$3")),this.callSuper("_set",a,b),a in this._dimensionAffectingProps&&(this._initDimensions(),this.setCoords())},complexity:function(){return 1}}),b.Text.ATTRIBUTE_NAMES=b.SHARED_ATTRIBUTES.concat("x y dx dy font-family font-style font-weight font-size text-decoration text-anchor".split(" ")),b.Text.DEFAULT_SVG_FONT_SIZE=16,b.Text.fromElement=function(a,c){if(!a)return null;var d=b.parseAttributes(a,b.Text.ATTRIBUTE_NAMES);c=b.util.object.extend(c?b.util.object.clone(c):{},d),"dx"in d&&(c.left+=d.dx),"dy"in d&&(c.top+=d.dy),"fontSize"in c||(c.fontSize=b.Text.DEFAULT_SVG_FONT_SIZE),c.originX||(c.originX="left");var e=new b.Text(a.textContent,c),f=0;return"left"===e.originX&&(f=e.getWidth()/2),"right"===e.originX&&(f=-e.getWidth()/2),e.set({left:e.getLeft()+f,top:e.getTop()-e.getHeight()/2}),e},b.Text.fromObject=function(a){return new b.Text(a.text,d(a))},b.util.createAccessors(b.Text)}("undefined"!=typeof exports?exports:this),function(){var a=fabric.util.object.clone;fabric.IText=fabric.util.createClass(fabric.Text,fabric.Observable,{type:"i-text",selectionStart:0,selectionEnd:0,selectionColor:"rgba(17,119,255,0.3)",isEditing:!1,editable:!0,editingBorderColor:"rgba(102,153,255,0.25)",cursorWidth:2,cursorColor:"#333",cursorDelay:1e3,cursorDuration:600,styles:null,caching:!0,_skipFillStrokeCheck:!0,_reSpace:/\s|\n/,_fontSizeFraction:4,_currentCursorOpacity:0,_selectionDirection:null,_abortCursorAnimation:!1,_charWidthsCache:{},initialize:function(a,b){this.styles=b?b.styles||{}:{},this.callSuper("initialize",a,b),this.initBehavior(),fabric.IText.instances.push(this),this.__lineWidths={},this.__lineHeights={},this.__lineOffsets={}},isEmptyStyles:function(){if(!this.styles)return!0;var a=this.styles;for(var b in a)for(var c in a[b])for(var d in a[b][c])return!1;return!0},setSelectionStart:function(a){this.selectionStart!==a&&(this.fire("selection:changed"),this.canvas&&this.canvas.fire("text:selection:changed",{target:this})),this.selectionStart=a,this.hiddenTextarea&&(this.hiddenTextarea.selectionStart=a)},setSelectionEnd:function(a){this.selectionEnd!==a&&(this.fire("selection:changed"),this.canvas&&this.canvas.fire("text:selection:changed",{target:this})),this.selectionEnd=a,this.hiddenTextarea&&(this.hiddenTextarea.selectionEnd=a)},getSelectionStyles:function(a,b){if(2===arguments.length){for(var c=[],d=a;d=d.charIndex&&(i!==g||mf&&i-1&&this._renderCharDecorationAtOffset(a,c,d+this.fontSize/this._fontSizeFraction,e,0,this.fontSize/20), h.indexOf("line-through")>-1&&this._renderCharDecorationAtOffset(a,c,d+this.fontSize/this._fontSizeFraction,e,g/2,i/20),h.indexOf("overline")>-1&&this._renderCharDecorationAtOffset(a,c,d,e,f-this.fontSize/this._fontSizeFraction,this.fontSize/20))},_renderCharDecorationAtOffset:function(a,b,c,d,e,f){a.fillRect(b,c-e,d,f)},_renderTextLine:function(a,b,c,d,e,f){e+=this.fontSize/4,this.callSuper("_renderTextLine",a,b,c,d,e,f)},_renderTextDecoration:function(a,b){if(this.isEmptyStyles())return this.callSuper("_renderTextDecoration",a,b)},_renderTextLinesBackground:function(a,b){if(this.textBackgroundColor||this.styles){a.save(),this.textBackgroundColor&&(a.fillStyle=this.textBackgroundColor);for(var c=0,d=this.fontSize/this._fontSizeFraction,e=0,f=b.length;ec&&(c=f)}return c},_getHeightOfLine:function(a,b,c){c=c||this.text.split(this._reNewline);for(var d=this._getHeightOfChar(a,c[b][0],b,0),e=c[b],f=e.split(""),g=1,h=f.length;gd&&(d=i)}return d*this.lineHeight},_getTextHeight:function(a,b){for(var c=0,d=0,e=b.length;d-1;)b++,c--;return a-b},findWordBoundaryRight:function(a){var b=0,c=a;if(this._reSpace.test(this.text.charAt(c)))for(;this._reSpace.test(this.text.charAt(c));)b++,c++;for(;/\S/.test(this.text.charAt(c))&&c-1;)b++,c--;return a-b},findLineBoundaryRight:function(a){for(var b=0,c=a;!/\n/.test(this.text.charAt(c))&&c0&&cd;f?this.removeStyleObject(f,c+1):this.removeStyleObject(0===this.get2DCursorLocation(c).charIndex,c)}this.text=this.text.slice(0,a)+this.text.slice(b)},insertChars:function(a){var b="\n"===this.text.slice(this.selectionStart,this.selectionStart+1);this.text=this.text.slice(0,this.selectionStart)+a+this.text.slice(this.selectionEnd),this.selectionStart===this.selectionEnd&&this.insertStyleObjects(a,b,this.copiedStyles),this.selectionStart+=a.length,this.selectionEnd=this.selectionStart,this.canvas&&this.canvas.renderAll().renderAll(),this.setCoords(),this.fire("changed"),this.canvas&&this.canvas.fire("text:changed",{target:this})},insertNewlineStyleObject:function(b,c,d){this.shiftLineStyles(b,1),this.styles[b+1]||(this.styles[b+1]={});var e=this.styles[b][c-1],f={};if(d)f[0]=a(e),this.styles[b+1]=f;else{for(var g in this.styles[b])parseInt(g,10)>=c&&(f[parseInt(g,10)-c]=this.styles[b][g],delete this.styles[b][g]);this.styles[b+1]=f}},insertCharStyleObject:function(b,c,d){var e=this.styles[b],f=a(e);0!==c||d||(c=1);for(var g in f){var h=parseInt(g,10);h>=c&&(e[h+1]=f[h])}this.styles[b][c]=d||a(e[c-1])},insertStyleObjects:function(a,b,c){if(!this.isEmptyStyles()){var d=this.get2DCursorLocation(),e=d.lineIndex,f=d.charIndex;this.styles[e]||(this.styles[e]={}),"\n"===a?this.insertNewlineStyleObject(e,f,b):c?this._insertStyles(c):this.insertCharStyleObject(e,f)}},_insertStyles:function(a){for(var b=0,c=a.length;bb&&(this.styles[f+c]=d[f])}},removeStyleObject:function(b,c){var d=this.get2DCursorLocation(c),e=d.lineIndex,f=d.charIndex;if(b){var g=this.text.split(this._reNewline),h=g[e-1],i=h?h.length:0;this.styles[e-1]||(this.styles[e-1]={});for(f in this.styles[e])this.styles[e-1][parseInt(f,10)+i]=this.styles[e][f];this.shiftLineStyles(e,-1)}else{var j=this.styles[e];if(j){var k=this.selectionStart===this.selectionEnd?-1:0;delete j[f+k]}var l=a(j);for(var m in l){var n=parseInt(m,10);n>=f&&0!==n&&(j[n-1]=l[n],delete j[n])}}},insertNewline:function(){this.insertChars("\n")}})}(),fabric.util.object.extend(fabric.IText.prototype,{initDoubleClickSimulation:function(){this.__lastClickTime=+new Date,this.__lastLastClickTime=+new Date,this.__lastPointer={},this.on("mousedown",this.onMouseDown.bind(this))},onMouseDown:function(a){this.__newClickTime=+new Date;var b=this.canvas.getPointer(a.e);this.isTripleClick(b)?(this.fire("tripleclick",a),this._stopEvent(a.e)):this.isDoubleClick(b)&&(this.fire("dblclick",a),this._stopEvent(a.e)),this.__lastLastClickTime=this.__lastClickTime,this.__lastClickTime=this.__newClickTime,this.__lastPointer=b,this.__lastIsEditing=this.isEditing,this.__lastSelected=this.selected},isDoubleClick:function(a){return this.__newClickTime-this.__lastClickTime<500&&this.__lastPointer.x===a.x&&this.__lastPointer.y===a.y&&this.__lastIsEditing},isTripleClick:function(a){return this.__newClickTime-this.__lastClickTime<500&&this.__lastClickTime-this.__lastLastClickTime<500&&this.__lastPointer.x===a.x&&this.__lastPointer.y===a.y},_stopEvent:function(a){a.preventDefault&&a.preventDefault(),a.stopPropagation&&a.stopPropagation()},initCursorSelectionHandlers:function(){this.initSelectedHandler(),this.initMousedownHandler(),this.initMousemoveHandler(),this.initMouseupHandler(),this.initClicks()},initClicks:function(){this.on("dblclick",function(a){this.selectWord(this.getSelectionStartFromPointer(a.e))}),this.on("tripleclick",function(a){this.selectLine(this.getSelectionStartFromPointer(a.e))})},initMousedownHandler:function(){this.on("mousedown",function(a){var b=this.canvas.getPointer(a.e);this.__mousedownX=b.x,this.__mousedownY=b.y,this.__isMousedown=!0,this.hiddenTextarea&&this.canvas&&this.canvas.wrapperEl.appendChild(this.hiddenTextarea),this.selected&&this.setCursorByClick(a.e),this.isEditing&&(this.__selectionStartOnMouseDown=this.selectionStart,this.initDelayedCursor(!0))})},initMousemoveHandler:function(){this.on("mousemove",function(a){if(this.__isMousedown&&this.isEditing){var b=this.getSelectionStartFromPointer(a.e);b>=this.__selectionStartOnMouseDown?(this.setSelectionStart(this.__selectionStartOnMouseDown),this.setSelectionEnd(b)):(this.setSelectionStart(b),this.setSelectionEnd(this.__selectionStartOnMouseDown))}})},_isObjectMoved:function(a){var b=this.canvas.getPointer(a);return this.__mousedownX!==b.x||this.__mousedownY!==b.y},initMouseupHandler:function(){this.on("mouseup",function(a){this.__isMousedown=!1,this._isObjectMoved(a.e)||(this.__lastSelected&&(this.enterEditing(),this.initDelayedCursor(!0)),this.selected=!0)})},setCursorByClick:function(a){var b=this.getSelectionStartFromPointer(a);a.shiftKey?bf?0:1,i=d+h;return this.flipX&&(i=e-i),i>this.text.length&&(i=this.text.length),i}}),fabric.util.object.extend(fabric.IText.prototype,{initHiddenTextarea:function(){this.hiddenTextarea=fabric.document.createElement("textarea"),this.hiddenTextarea.setAttribute("autocapitalize","off"),this.hiddenTextarea.style.cssText="position: absolute; top: 0; left: -9999px",fabric.document.body.appendChild(this.hiddenTextarea),fabric.util.addListener(this.hiddenTextarea,"keydown",this.onKeyDown.bind(this)),fabric.util.addListener(this.hiddenTextarea,"keypress",this.onKeyPress.bind(this)),fabric.util.addListener(this.hiddenTextarea,"copy",this.copy.bind(this)),fabric.util.addListener(this.hiddenTextarea,"paste",this.paste.bind(this)),!this._clickHandlerInitialized&&this.canvas&&(fabric.util.addListener(this.canvas.upperCanvasEl,"click",this.onClick.bind(this)),this._clickHandlerInitialized=!0)},_keysMap:{8:"removeChars",9:"exitEditing",27:"exitEditing",13:"insertNewline",33:"moveCursorUp",34:"moveCursorDown",35:"moveCursorRight",36:"moveCursorLeft",37:"moveCursorLeft",38:"moveCursorUp",39:"moveCursorRight",40:"moveCursorDown",46:"forwardDelete"},_ctrlKeysMap:{65:"selectAll",88:"cut"},onClick:function(){this.hiddenTextarea&&this.hiddenTextarea.focus()},onKeyDown:function(a){if(this.isEditing){if(a.keyCode in this._keysMap)this[this._keysMap[a.keyCode]](a);else{if(!(a.keyCode in this._ctrlKeysMap&&(a.ctrlKey||a.metaKey)))return;this[this._ctrlKeysMap[a.keyCode]](a)}a.stopImmediatePropagation(),a.preventDefault(),this.canvas&&this.canvas.renderAll()}},forwardDelete:function(a){this.selectionStart===this.selectionEnd&&this.moveCursorRight(a),this.removeChars(a)},copy:function(a){var b=this.getSelectedText(),c=this._getClipboardData(a);c&&c.setData("text",b),this.copiedText=b,this.copiedStyles=this.getSelectionStyles(this.selectionStart,this.selectionEnd)},paste:function(a){var b=null,c=this._getClipboardData(a);b=c?c.getData("text"):this.copiedText,b&&this.insertChars(b)},cut:function(a){this.selectionStart!==this.selectionEnd&&(this.copy(),this.removeChars(a))},_getClipboardData:function(a){return a&&(a.clipboardData||fabric.window.clipboardData)},onKeyPress:function(a){!this.isEditing||a.metaKey||a.ctrlKey||(0!==a.which&&this.insertChars(String.fromCharCode(a.which)),a.stopPropagation())},getDownCursorOffset:function(a,b){var e,f,c=b?this.selectionEnd:this.selectionStart,d=this.text.split(this._reNewline),g=this.text.slice(0,c),h=this.text.slice(c),i=g.slice(g.lastIndexOf("\n")+1),j=h.match(/(.*)\n?/)[1],k=(h.match(/.*\n(.*)\n?/)||{})[1]||"",l=this.get2DCursorLocation(c);if(l.lineIndex===d.length-1||a.metaKey||34===a.keyCode)return this.text.length-c;var m=this._getWidthOfLine(this.ctx,l.lineIndex,d);f=this._getLineLeftOffset(m);for(var n=f,o=l.lineIndex,p=0,q=i.length;pc){j=!0;var o=h-n,p=h,q=Math.abs(o-c),r=Math.abs(p-c);i=rthis.text.length&&(this.selectionStart=this.text.length),this.selectionEnd=this.selectionStart},swapSelectionPoints:function(){var a=this.selectionEnd;this.selectionEnd=this.selectionStart,this.selectionStart=a},moveCursorDownWithShift:function(a){this.selectionEnd===this.selectionStart&&(this._selectionDirection="right");var b="right"===this._selectionDirection?"selectionEnd":"selectionStart";this[b]+=a,this.selectionEndthis.text.length&&(this.selectionEnd=this.text.length)},getUpCursorOffset:function(a,b){var c=b?this.selectionEnd:this.selectionStart,d=this.get2DCursorLocation(c);if(0===d.lineIndex||a.metaKey||33===a.keyCode)return c;for(var i,e=this.text.slice(0,c),f=e.slice(e.lastIndexOf("\n")+1),g=(e.match(/\n?(.*)\n.*$/)||{})[1]||"",h=this.text.split(this._reNewline),j=this._getWidthOfLine(this.ctx,d.lineIndex,h),k=this._getLineLeftOffset(j),l=k,m=d.lineIndex,n=0,o=f.length;nc){j=!0;var o=h-n,p=h,q=Math.abs(o-c),r=Math.abs(p-c);i=r=this.text.length&&this.selectionEnd>=this.text.length||(this.abortCursorAnimation(),this._currentCursorOpacity=1,a.shiftKey?this.moveCursorRightWithShift(a):this.moveCursorRightWithoutShift(a),this.initDelayedCursor())},moveCursorRightWithShift:function(a){"left"===this._selectionDirection&&this.selectionStart!==this.selectionEnd?this._moveRight(a,"selectionStart"):(this._selectionDirection="right",this._moveRight(a,"selectionEnd"),"\n"===this.text.charAt(this.selectionEnd-1)&&this.selectionEnd++,this.selectionEnd>this.text.length&&(this.selectionEnd=this.text.length))},moveCursorRightWithoutShift:function(a){this._selectionDirection="right",this.selectionStart===this.selectionEnd?(this._moveRight(a,"selectionStart"),this.selectionEnd=this.selectionStart):(this.selectionEnd+=this.getNumNewLinesInSelectedText(),this.selectionEnd>this.text.length&&(this.selectionEnd=this.text.length),this.selectionStart=this.selectionEnd)},removeChars:function(a){this.selectionStart===this.selectionEnd?this._removeCharsNearCursor(a):this._removeCharsFromTo(this.selectionStart,this.selectionEnd),this.selectionEnd=this.selectionStart,this._removeExtraneousStyles(),this.canvas&&this.canvas.renderAll().renderAll(),this.setCoords(),this.fire("changed"),this.canvas&&this.canvas.fire("text:changed",{target:this})},_removeCharsNearCursor:function(a){if(0!==this.selectionStart)if(a.metaKey){var b=this.findLineBoundaryLeft(this.selectionStart);this._removeCharsFromTo(b,this.selectionStart),this.selectionStart=b}else if(a.altKey){var c=this.findWordBoundaryLeft(this.selectionStart);this._removeCharsFromTo(c,this.selectionStart),this.selectionStart=c}else{var d="\n"===this.text.slice(this.selectionStart-1,this.selectionStart);this.removeStyleObject(d),this.selectionStart--,this.text=this.text.slice(0,this.selectionStart)+this.text.slice(this.selectionStart+1)}}}),fabric.util.object.extend(fabric.IText.prototype,{_setSVGTextLineText:function(a,b,c,d,e,f){this.styles[b]?this._setSVGTextLineChars(a,b,c,d,e,f):this.callSuper("_setSVGTextLineText",a,b,c,d,e)},_setSVGTextLineChars:function(a,b,c,d,e,f){for(var g=0===b||this.useNative?"y":"dy",h=a.split(""),i=0,j=this._getSVGLineLeftOffset(b),k=this._getSVGLineTopOffset(b),l=this._getHeightOfLine(this.ctx,b),m=0,n=h.length;m'].join("")},_createTextCharSpan:function(a,b,c,d,e,f){var g=this.getSvgStyles.call(fabric.util.object.extend({visible:!0,fill:this.fill,stroke:this.stroke,type:"text"},b));return['',fabric.util.string.escapeXml(a),""].join("")}}),function(){function request(a,b,c){var d=URL.parse(a);d.port||(d.port=0===d.protocol.indexOf("https:")?443:80);var e=443===d.port?HTTPS:HTTP,f=e.request({hostname:d.hostname,port:d.port,path:d.path,method:"GET"},function(a){var d="";b&&a.setEncoding(b),a.on("end",function(){c(d)}),a.on("data",function(b){200===a.statusCode&&(d+=b)})});f.on("error",function(a){a.errno===process.ECONNREFUSED?fabric.log("ECONNREFUSED: connection refused to "+d.hostname+":"+d.port):fabric.log(a.message)}),f.end()}function requestFs(a,b){var c=require("fs");c.readFile(a,function(a,c){if(a)throw fabric.log(a),a;b(c)})}if("undefined"==typeof document||"undefined"==typeof window){var DOMParser=require("xmldom").DOMParser,URL=require("url"),HTTP=require("http"),HTTPS=require("https"),Canvas=require("canvas"),Image=require("canvas").Image;fabric.util.loadImage=function(a,b,c){function d(d){e.src=new Buffer(d,"binary"),e._src=a,b&&b.call(c,e)}var e=new Image;a&&(a instanceof Buffer||0===a.indexOf("data"))?(e.src=e._src=a,b&&b.call(c,e)):a&&0!==a.indexOf("http")?requestFs(a,d):a?request(a,"binary",d):b&&b.call(c,a)},fabric.loadSVGFromURL=function(a,b,c){a=a.replace(/^\n\s*/,"").replace(/\?.*$/,"").trim(),0!==a.indexOf("http")?requestFs(a,function(a){fabric.loadSVGFromString(a.toString(),b,c)}):request(a,"",function(a){fabric.loadSVGFromString(a,b,c)})},fabric.loadSVGFromString=function(a,b,c){var d=(new DOMParser).parseFromString(a);fabric.parseSVGDocument(d.documentElement,function(a,c){b&&b(a,c)},c)},fabric.util.getScript=function(url,callback){request(url,"",function(body){eval(body),callback&&callback()})},fabric.Image.fromObject=function(a,b){fabric.util.loadImage(a.src,function(c){var d=new fabric.Image(c);d._initConfig(a),d._initFilters(a,function(a){d.filters=a||[],b&&b(d)})})},fabric.createCanvasForNode=function(a,b,c,d){d=d||c;var e=fabric.document.createElement("canvas"),f=new Canvas(a||600,b||600,d);e.style={},e.width=f.width,e.height=f.height;var g=fabric.Canvas||fabric.StaticCanvas,h=new g(e,c);return h.contextContainer=f.getContext("2d"),h.nodeCanvas=f,h.Font=Canvas.Font,h},fabric.StaticCanvas.prototype.createPNGStream=function(){return this.nodeCanvas.createPNGStream()},fabric.StaticCanvas.prototype.createJPEGStream=function(a){return this.nodeCanvas.createJPEGStream(a)};var origSetWidth=fabric.StaticCanvas.prototype.setWidth;fabric.StaticCanvas.prototype.setWidth=function(a,b){return origSetWidth.call(this,a,b),this.nodeCanvas.width=a,this},fabric.Canvas&&(fabric.Canvas.prototype.setWidth=fabric.StaticCanvas.prototype.setWidth);var origSetHeight=fabric.StaticCanvas.prototype.setHeight;fabric.StaticCanvas.prototype.setHeight=function(a,b){return origSetHeight.call(this,a,b),this.nodeCanvas.height=a,this},fabric.Canvas&&(fabric.Canvas.prototype.setHeight=fabric.StaticCanvas.prototype.setHeight)}}(); ================================================ FILE: src/libs/fabric1-4-2.min.js ================================================ /* build: `node build.js modules=ALL exclude=gestures,cufon,json minifier=uglifyjs` *//*! Fabric.js Copyright 2008-2014, Printio (Juriy Zaytsev, Maxim Chernyak) */var fabric=fabric||{version:"1.4.12"};typeof exports!="undefined"&&(exports.fabric=fabric),typeof document!="undefined"&&typeof window!="undefined"?(fabric.document=document,fabric.window=window):(fabric.document=require("jsdom").jsdom(""),fabric.window=fabric.document.createWindow()),fabric.isTouchSupported="ontouchstart"in fabric.document.documentElement,fabric.isLikelyNode=typeof Buffer!="undefined"&&typeof window=="undefined",fabric.SHARED_ATTRIBUTES=["display","transform","fill","fill-opacity","fill-rule","opacity","stroke","stroke-dasharray","stroke-linecap","stroke-linejoin","stroke-miterlimit","stroke-opacity","stroke-width"],fabric.DPI=96,function(){function e(e,t){if(!this.__eventListeners[e])return;t?fabric.util.removeFromArray(this.__eventListeners[e],t):this.__eventListeners[e].length=0}function t(e,t){this.__eventListeners||(this.__eventListeners={});if(arguments.length===1)for(var n in e)this.on(n,e[n]);else this.__eventListeners[e]||(this.__eventListeners[e]=[]),this.__eventListeners[e].push(t);return this}function n(t,n){if(!this.__eventListeners)return;if(arguments.length===0)this.__eventListeners={};else if(arguments.length===1&&typeof arguments[0]=="object")for(var r in t)e.call(this,r,t[r]);else e.call(this,t,n);return this}function r(e,t){if(!this.__eventListeners)return;var n=this.__eventListeners[e];if(!n)return;for(var r=0,i=n.length;r-1},complexity:function(){return this.getObjects().reduce(function(e,t){return e+=t.complexity?t.complexity():0,e},0)}},function(e){var t=Math.sqrt,n=Math.atan2,r=Math.PI/180;fabric.util={removeFromArray:function(e,t){var n=e.indexOf(t);return n!==-1&&e.splice(n,1),e},getRandomInt:function(e,t){return Math.floor(Math.random()*(t-e+1))+e},degreesToRadians:function(e){return e*r},radiansToDegrees:function(e){return e/r},rotatePoint:function(e,t,n){var r=Math.sin(n),i=Math.cos(n);e.subtractEquals(t);var s=e.x*i-e.y*r,o=e.x*r+e.y*i;return(new fabric.Point(s,o)).addEquals(t)},transformPoint:function(e,t,n){return n?new fabric.Point(t[0]*e.x+t[1]*e.y,t[2]*e.x+t[3]*e.y):new fabric.Point(t[0]*e.x+t[1]*e.y+t[4],t[2]*e.x+t[3]*e.y+t[5])},invertTransform:function(e){var t=e.slice(),n=1/(e[0]*e[3]-e[1]*e[2]);t=[n*e[3],-n*e[1],-n*e[2],n*e[0],0,0];var r=fabric.util.transformPoint({x:e[4],y:e[5]},t);return t[4]=-r.x,t[5]=-r.y,t},toFixed:function(e,t){return parseFloat(Number(e).toFixed(t))},parseUnit:function(e){var t=/\D{0,2}$/.exec(e),n=parseFloat(e);switch(t[0]){case"mm":return n*fabric.DPI/25.4;case"cm":return n*fabric.DPI/2.54;case"in":return n*fabric.DPI;case"pt":return n*fabric.DPI/72;case"pc":return n*fabric.DPI/72*12;default:return n}},falseFunction:function(){return!1},getKlass:function(e,t){return e=fabric.util.string.camelize(e.charAt(0).toUpperCase()+e.slice(1)),fabric.util.resolveNamespace(t)[e]},resolveNamespace:function(t){if(!t)return fabric;var n=t.split("."),r=n.length,i=e||fabric.window;for(var s=0;sr)r+=u[p++%h],r>l&&(r=l),e[d?"lineTo":"moveTo"](r,0),d=!d;e.restore()},createCanvasElement:function(e){return e||(e=fabric.document.createElement("canvas")),!e.getContext&&typeof G_vmlCanvasManager!="undefined"&&G_vmlCanvasManager.initElement(e),e},createImage:function(){return fabric.isLikelyNode?new(require("canvas").Image):fabric.document.createElement("img")},createAccessors:function(e){var t=e.prototype;for(var n=t.stateProperties.length;n--;){var r=t.stateProperties[n],i=r.charAt(0).toUpperCase()+r.slice(1),s="set"+i,o="get"+i;t[o]||(t[o]=function(e){return new Function('return this.get("'+e+'")')}(r)),t[s]||(t[s]=function(e){return new Function("value",'return this.set("'+e+'", value)')}(r))}},clipContext:function(e,t){t.save(),t.beginPath(),e.clipTo(t),t.clip()},multiplyTransformMatrices:function(e,t){var n=[[e[0],e[2],e[4]],[e[1],e[3],e[5]],[0,0,1]],r=[[t[0],t[2],t[4]],[t[1],t[3],t[5]],[0,0,1]],i=[];for(var s=0;s<3;s++){i[s]=[];for(var o=0;o<3;o++){var u=0;for(var a=0;a<3;a++)u+=n[s][a]*r[a][o];i[s][o]=u}}return[i[0][0],i[1][0],i[0][1],i[1][1],i[0][2],i[1][2]]},getFunctionBody:function(e){return(String(e).match(/function[^{]*\{([\s\S]*)\}/)||{})[1]},isTransparent:function(e,t,n,r){r>0&&(t>r?t-=r:t=0,n>r?n-=r:n=0);var i=!0,s=e.getImageData(t,n,r*2||1,r*2||1);for(var o=3,u=s.data.length;o0?_-=2*h:f===1&&_<0&&(_+=2*h);var D=Math.ceil(Math.abs(_/h*2)),P=[],H=_/D,B=8/3*Math.sin(H/4)*Math.sin(H/4)/Math.sin(H/2),j=M+H;for(var F=0;F=i?s-i:2*Math.PI-(i-s)}function u(e,t,i,s,o,u,a,f){var l=r.call(arguments);if(n[l])return n[l];var c=Math.sqrt,h=Math.min,p=Math.max,d=Math.abs,v=[],m=[[],[]],g,y,b,w,E,S,x,T;y=6*e-12*i+6*o,g=-3*e+9*i-9*o+3*a,b=3*i-3*e;for(var N=0;N<2;++N){N>0&&(y=6*t-12*s+6*u,g=-3*t+9*s-9*u+3*f,b=3*s-3*t);if(d(g)<1e-12){if(d(y)<1e-12)continue;w=-b/y,0=t})}function r(e,t){return i(e,t,function(e,t){return e>>0;if(n===0)return-1;var r=0;arguments.length>0&&(r=Number(arguments[1]),r!==r?r=0:r!==0&&r!==Number.POSITIVE_INFINITY&&r!==Number.NEGATIVE_INFINITY&&(r=(r>0||-1)*Math.floor(Math.abs(r))));if(r>=n)return-1;var i=r>=0?r:Math.max(n-Math.abs(r),0);for(;i>>0;n>>0;r>>0;n>>0;n>>0;i>>0,n=0,r;if(arguments.length>1)r=arguments[1];else do{if(n in this){r=this[n++];break}if(++n>=t)throw new TypeError}while(!0);for(;n/g,">")}String.prototype.trim||(String.prototype.trim=function(){return this.replace(/^[\s\xA0]+/,"").replace(/[\s\xA0]+$/,"")}),fabric.util.string={camelize:e,capitalize:t,escapeXml:n}}(),function(){var e=Array.prototype.slice,t=Function.prototype.apply,n=function(){};Function.prototype.bind||(Function.prototype.bind=function(r){var i=this,s=e.call(arguments,1),o;return s.length?o=function(){return t.call(i,this instanceof n?this:r,s.concat(e.call(arguments)))}:o=function(){return t.call(i,this instanceof n?this:r,arguments)},n.prototype=this.prototype,o.prototype=new n,o})}(),function(){function i(){}function s(t){var n=this.constructor.superclass.prototype[t];return arguments.length>1?n.apply(this,e.call(arguments,1)):n.call(this)}function o(){function u(){this.initialize.apply(this,arguments)}var n=null,o=e.call(arguments,0);typeof o[0]=="function"&&(n=o.shift()),u.superclass=n,u.subclasses=[],n&&(i.prototype=n.prototype,u.prototype=new i,n.subclasses.push(u));for(var a=0,f=o.length;a-1?e.prototype[i]=function(e){return function(){var n=this.constructor.superclass;this.constructor.superclass=r;var i=t[e].apply(this,arguments);this.constructor.superclass=n;if(e!=="initialize")return i}}(i):e.prototype[i]=t[i],n&&(t.toString!==Object.prototype.toString&&(e.prototype.toString=t.toString),t.valueOf!==Object.prototype.valueOf&&(e.prototype.valueOf=t.valueOf))};fabric.util.createClass=o}(),function(){function t(e){var t=Array.prototype.slice.call(arguments,1),n,r,i=t.length;for(r=0;r-1?s(e,t.match(/opacity:\s*(\d?\.?\d*)/)[1]):e;for(var r in t)if(r==="opacity")s(e,t[r]);else{var i=r==="float"||r==="cssFloat"?typeof n.styleFloat=="undefined"?"cssFloat":"styleFloat":r;n[i]=t[r]}return e}var t=fabric.document.createElement("div"),n=typeof t.style.opacity=="string",r=typeof t.style.filter=="string",i=/alpha\s*\(\s*opacity\s*=\s*([^\)]+)\)/,s=function(e){return e};n?s=function(e,t){return e.style.opacity=t,e}:r&&(s=function(e,t){var n=e.style;return e.currentStyle&&!e.currentStyle.hasLayout&&(n.zoom=1),i.test(n.filter)?(t=t>=.9999?"":"alpha(opacity="+t*100+")",n.filter=n.filter.replace(i,t)):n.filter+=" alpha(opacity="+t*100+")",e}),fabric.util.setStyle=e}(),function(){function t(e){return typeof e=="string"?fabric.document.getElementById(e):e}function s(e,t){var n=fabric.document.createElement(e);for(var r in t)r==="class"?n.className=t[r]:r==="for"?n.htmlFor=t[r]:n.setAttribute(r,t[r]);return n}function o(e,t){e&&(" "+e.className+" ").indexOf(" "+t+" ")===-1&&(e.className+=(e.className?" ":"")+t)}function u(e,t,n){return typeof t=="string"&&(t=s(t,n)),e.parentNode&&e.parentNode.replaceChild(t,e),t.appendChild(e),t}function a(e,t){var n,r,i=0,s=0,o=fabric.document.documentElement,u=fabric.document.body||{scrollLeft:0,scrollTop:0};r=e;while(e&&e.parentNode&&!n)e=e.parentNode,e.nodeType===1&&fabric.util.getElementStyle(e,"position")==="fixed"&&(n=e),e.nodeType===1&&r!==t&&fabric.util.getElementStyle(e,"position")==="absolute"?(i=0,s=0):e===fabric.document?(i=u.scrollLeft||o.scrollLeft||0,s=u.scrollTop||o.scrollTop||0):(i+=e.scrollLeft||0,s+=e.scrollTop||0);return{left:i,top:s}}function f(e){var t,n=e&&e.ownerDocument,r={left:0,top:0},i={left:0,top:0},s,o={borderLeftWidth:"left",borderTopWidth:"top",paddingLeft:"left",paddingTop:"top"};if(!n)return{left:0,top:0};for(var u in o)i[o[u]]+=parseInt(l(e,u),10)||0;return t=n.documentElement,typeof e.getBoundingClientRect!="undefined"&&(r=e.getBoundingClientRect()),s=fabric.util.getScrollLeftTop(e,null),{left:r.left+s.left-(t.clientLeft||0)+i.left,top:r.top+s.top-(t.clientTop||0)+i.top}}var e=Array.prototype.slice,n,r=function(t){return e.call(t,0)};try{n=r(fabric.document.childNodes)instanceof Array}catch(i){}n||(r=function(e){var t=new Array(e.length),n=e.length;while(n--)t[n]=e[n];return t});var l;fabric.document.defaultView&&fabric.document.defaultView.getComputedStyle?l=function(e,t){var n=fabric.document.defaultView.getComputedStyle(e,null);return n?n[t]:undefined}:l=function(e,t){var n=e.style[t];return!n&&e.currentStyle&&(n=e.currentStyle[t]),n},function(){function n(e){return typeof e.onselectstart!="undefined"&&(e.onselectstart=fabric.util.falseFunction),t?e.style[t]="none":typeof e.unselectable=="string"&&(e.unselectable="on"),e}function r(e){return typeof e.onselectstart!="undefined"&&(e.onselectstart=null),t?e.style[t]="":typeof e.unselectable=="string"&&(e.unselectable=""),e}var e=fabric.document.documentElement.style,t="userSelect"in e?"userSelect":"MozUserSelect"in e?"MozUserSelect":"WebkitUserSelect"in e?"WebkitUserSelect":"KhtmlUserSelect"in e?"KhtmlUserSelect":"";fabric.util.makeElementUnselectable=n,fabric.util.makeElementSelectable=r}(),function(){function e(e,t){var n=fabric.document.getElementsByTagName("head")[0],r=fabric.document.createElement("script"),i=!0;r.onload=r.onreadystatechange=function(e){if(i){if(typeof this.readyState=="string"&&this.readyState!=="loaded"&&this.readyState!=="complete")return;i=!1,t(e||fabric.window.event),r=r.onload=r.onreadystatechange=null}},r.src=e,n.appendChild(r)}fabric.util.getScript=e}(),fabric.util.getById=t,fabric.util.toArray=r,fabric.util.makeElement=s,fabric.util.addClass=o,fabric.util.wrapElement=u,fabric.util.getScrollLeftTop=a,fabric.util.getElementOffset=f,fabric.util.getElementStyle=l}(),function(){function e(e,t){return e+(/\?/.test(e)?"&":"?")+t}function n(){}function r(r,i){i||(i={});var s=i.method?i.method.toUpperCase():"GET",o=i.onComplete||function(){},u=t(),a;return u.onreadystatechange=function(){u.readyState===4&&(o(u),u.onreadystatechange=n)},s==="GET"&&(a=null,typeof i.parameters=="string"&&(r=e(r,i.parameters))),u.open(s,r,!0),(s==="POST"||s==="PUT")&&u.setRequestHeader("Content-Type","application/x-www-form-urlencoded"),u.send(a),u}var t=function(){var e=[function(){return new ActiveXObject("Microsoft.XMLHTTP")},function(){return new ActiveXObject("Msxml2.XMLHTTP")},function(){return new ActiveXObject("Msxml2.XMLHTTP.3.0")},function(){return new XMLHttpRequest}];for(var t=e.length;t--;)try{var n=e[t]();if(n)return e[t]}catch(r){}}();fabric.util.request=r}(),fabric.log=function(){},fabric.warn=function(){},typeof console!="undefined"&&["log","warn"].forEach(function(e){typeof console[e]!="undefined"&&console[e].apply&&(fabric[e]=function(){return console[e].apply(console,arguments)})}),function(){function e(e){n(function(t){e||(e={});var r=t||+(new Date),i=e.duration||500,s=r+i,o,u=e.onChange||function(){},a=e.abort||function(){return!1},f=e.easing||function(e,t,n,r){return-n*Math.cos(e/r*(Math.PI/2))+n+t},l="startValue"in e?e.startValue:0,c="endValue"in e?e.endValue:100,h=e.byValue||c-l;e.onStart&&e.onStart(),function p(t){o=t||+(new Date);var c=o>s?i:o-r;if(a()){e.onComplete&&e.onComplete();return}u(f(c,l,h,i));if(o>s){e.onComplete&&e.onComplete();return}n(p)}(r)})}function n(){return t.apply(fabric.window,arguments)}var t=fabric.window.requestAnimationFrame||fabric.window.webkitRequestAnimationFrame||fabric.window.mozRequestAnimationFrame||fabric.window.oRequestAnimationFrame||fabric.window.msRequestAnimationFrame||function(e){fabric.window.setTimeout(e,1e3/60)};fabric.util.animate=e,fabric.util.requestAnimFrame=n}(),function(){function e(e,t,n,r){return et[3]?t[3]:t[0];if(t[0]===1&&t[3]===1&&t[4]===0&&t[5]===0)return;var n=e.ownerDocument.createElement("g");while(e.firstChild!=null)n.appendChild(e.firstChild);n.setAttribute("transform","matrix("+t[0]+" "+t[1]+" "+t[2]+" "+t[3]+" "+t[4]+" "+t[5]+")"),e.appendChild(n)}function x(e){var n=e.objects,i=e.options;return n=n.map(function(e){return t[r(e.type)].fromObject(e)}),{objects:n,options:i}}function T(e,t,n){t[n]&&t[n].toSVG&&e.push('','')}var t=e.fabric||(e.fabric={}),n=t.util.object.extend,r=t.util.string.capitalize,i=t.util.object.clone,s=t.util.toFixed,o=t.util.parseUnit,u=t.util.multiplyTransformMatrices,a={cx:"left",x:"left",r:"radius",cy:"top",y:"top",display:"visible",visibility:"visible",transform:"transformMatrix","fill-opacity":"fillOpacity","fill-rule":"fillRule","font-family":"fontFamily","font-size":"fontSize","font-style":"fontStyle","font-weight":"fontWeight","stroke-dasharray":"strokeDashArray","stroke-linecap":"strokeLineCap","stroke-linejoin":"strokeLineJoin","stroke-miterlimit":"strokeMiterLimit","stroke-opacity":"strokeOpacity","stroke-width":"strokeWidth","text-decoration":"textDecoration","text-anchor":"originX"},f={stroke:"strokeOpacity",fill:"fillOpacity"};t.cssRules={},t.gradientDefs={},t.parseTransformAttribute=function(){function e(e,t){var n=t[0];e[0]=Math.cos(n),e[1]=Math.sin(n),e[2]=-Math.sin(n),e[3]=Math.cos(n)}function n(e,t){var n=t[0],r=t.length===2?t[1]:t[0];e[0]=n,e[3]=r}function r(e,n){e[2]=Math.tan(t.util.degreesToRadians(n[0]))}function i(e,n){e[1]=Math.tan(t.util.degreesToRadians(n[0]))}function s(e,t){e[4]=t[0],t.length===2&&(e[5]=t[1])}var o=[1,0,0,1,0,0],u="(?:[-+]?(?:\\d+|\\d*\\.\\d+)(?:e[-+]?\\d+)?)",a="(?:\\s+,?\\s*|,\\s*)",f="(?:(skewX)\\s*\\(\\s*("+u+")\\s*\\))",l="(?:(skewY)\\s*\\(\\s*("+u+")\\s*\\))",c="(?:(rotate)\\s*\\(\\s*("+u+")(?:"+a+"("+u+")"+a+"("+u+"))?\\s*\\))",h="(?:(scale)\\s*\\(\\s*("+u+")(?:"+a+"("+u+"))?\\s*\\))",p="(?:(translate)\\s*\\(\\s*("+u+")(?:"+a+"("+u+"))?\\s*\\))",d="(?:(matrix)\\s*\\(\\s*("+u+")"+a+"("+u+")"+a+"("+u+")"+a+"("+u+")"+a+"("+u+")"+a+"("+u+")"+"\\s*\\))",v="(?:"+d+"|"+p+"|"+h+"|"+c+"|"+f+"|"+l+")",m="(?:"+v+"(?:"+a+v+")*"+")",g="^\\s*(?:"+m+"?)\\s*$",y=new RegExp(g),b=new RegExp(v,"g");return function(u){var a=o.concat(),f=[];if(!u||u&&!y.test(u))return a;u.replace(b,function(u){var l=(new RegExp(v)).exec(u).filter(function(e){return e!==""&&e!=null}),c=l[1],h=l.slice(2).map(parseFloat);switch(c){case"translate":s(a,h);break;case"rotate":h[0]=t.util.degreesToRadians(h[0]),e(a,h);break;case"scale":n(a,h);break;case"skewX":r(a,h);break;case"skewY":i(a,h);break;case"matrix":a=h}f.push(a.concat()),a=o.concat()});var l=f[0];while(f.length>1)f.shift(),l=t.util.multiplyTransformMatrices(l,f[0]);return l}}(),t.parseSVGDocument=function(){function s(e,t){while(e&&(e=e.parentNode))if(t.test(e.nodeName))return!0;return!1}var e=/^(path|circle|polygon|polyline|ellipse|rect|line|image|text)$/,n="(?:[-+]?(?:\\d+|\\d*\\.\\d+)(?:e[-+]?\\d+)?)",r=new RegExp("^\\s*("+n+"+)\\s*,?"+"\\s*("+n+"+)\\s*,?"+"\\s*("+n+"+)\\s*,?"+"\\s*("+n+"+)\\s*"+"$");return function(n,u,a){if(!n)return;var f=new Date,l=t.Object.__uid++;w(n);var c=n.getAttribute("viewBox"),h=o(n.getAttribute("width")||"100%"),p=o(n.getAttribute("height")||"100%"),d,v;if(c&&(c=c.match(r))){var m=parseFloat(c[1]),g=parseFloat(c[2]),y=1,b=1;d=parseFloat(c[3]),v=parseFloat(c[4]),h&&h!==d&&(y=h/d),p&&p!==v&&(b=p/v),E(n,[y,0,0,b,y*-m,b*-g])}var S=t.util.toArray(n.getElementsByTagName("*"));if(S.length===0&&t.isLikelyNode){S=n.selectNodes('//*[name(.)!="svg"]');var x=[];for(var T=0,N=S.length;T/i,"")));if(!s||!s.documentElement)return;t.parseSVGDocument(s.documentElement,function(r,i){S.set(e,{objects:t.util.array.invoke(r,"toObject"),options:i}),n(r,i)},r)}e=e.replace(/^\n\s*/,"").trim(),S.has(e,function(r){r?S.get(e,function(e){var t=x(e);n(t.objects,t.options)}):new t.util.request(e,{method:"get",onComplete:i})})},loadSVGFromString:function(e,n,r){e=e.trim();var i;if(typeof DOMParser!="undefined"){var s=new DOMParser;s&&s.parseFromString&&(i=s.parseFromString(e,"text/xml"))}else t.window.ActiveXObject&&(i=new ActiveXObject("Microsoft.XMLDOM"),i.async="false",i.loadXML(e.replace(//i,"")));t.parseSVGDocument(i.documentElement,function(e,t){n(e,t)},r)},createSVGFontFacesMarkup:function(e){var t="";for(var n=0,r=e.length;n',"",""].join("")),t},createSVGRefElementsMarkup:function(e){var t=[];return T(t,e,"backgroundColor"),T(t,e,"overlayColor"),t.join("")}})}(typeof exports!="undefined"?exports:this),fabric.ElementsParser=function(e,t,n,r){this.elements=e,this.callback=t,this.options=n,this.reviver=r,this.svgUid=n&&n.svgUid||0},fabric.ElementsParser.prototype.parse=function(){this.instances=new Array(this.elements.length),this.numElements=this.elements.length,this.createObjects()},fabric.ElementsParser.prototype.createObjects=function(){for(var e=0,t=this.elements.length;ee.x&&this.y>e.y},gte:function(e){return this.x>=e.x&&this.y>=e.y},lerp:function(e,t){return new n(this.x+(e.x-this.x)*t,this.y+(e.y-this.y)*t)},distanceFrom:function(e){var t=this.x-e.x,n=this.y-e.y;return Math.sqrt(t*t+n*n)},midPointFrom:function(e){return new n(this.x+(e.x-this.x)/2,this.y+(e.y-this.y)/2)},min:function(e){return new n(Math.min(this.x,e.x),Math.min(this.y,e.y))},max:function(e){return new n(Math.max(this.x,e.x),Math.max(this.y,e.y))},toString:function(){return this.x+","+this.y},setXY:function(e,t){this.x=e,this.y=t},setFromPoint:function(e){this.x=e.x,this.y=e.y},swap:function(e){var t=this.x,n=this.y;this.x=e.x,this.y=e.y,e.x=t,e.y=n}}}(typeof exports!="undefined"?exports:this),function(e){"use strict";function n(e){this.status=e,this.points=[]}var t=e.fabric||(e.fabric={});if(t.Intersection){t.warn("fabric.Intersection is already defined");return}t.Intersection=n,t.Intersection.prototype={appendPoint:function(e){this.points.push(e)},appendPoints:function(e){this.points=this.points.concat(e)}},t.Intersection.intersectLineLine=function(e,r,i,s){var o,u=(s.x-i.x)*(e.y-i.y)-(s.y-i.y)*(e.x-i.x),a=(r.x-e.x)*(e.y-i.y)-(r.y-e.y)*(e.x-i.x),f=(s.y-i.y)*(r.x-e.x)-(s.x-i.x)*(r.y-e.y);if(f!==0){var l=u/f,c=a/f;0<=l&&l<=1&&0<=c&&c<=1?(o=new n("Intersection"),o.points.push(new t.Point(e.x+l*(r.x-e.x),e.y+l*(r.y-e.y)))):o=new n}else u===0||a===0?o=new n("Coincident"):o=new n("Parallel");return o},t.Intersection.intersectLinePolygon=function(e,t,r){var i=new n,s=r.length;for(var o=0;o0&&(i.status="Intersection"),i},t.Intersection.intersectPolygonPolygon=function(e,t){var r=new n,i=e.length;for(var s=0;s0&&(r.status="Intersection"),r},t.Intersection.intersectPolygonRectangle=function(e,r,i){var s=r.min(i),o=r.max(i),u=new t.Point(o.x,s.y),a=new t.Point(s.x,o.y),f=n.intersectLinePolygon(s,u,e),l=n.intersectLinePolygon(u,o,e),c=n.intersectLinePolygon(o,a,e),h=n.intersectLinePolygon(a,s,e),p=new n;return p.appendPoints(f.points),p.appendPoints(l.points),p.appendPoints(c.points),p.appendPoints(h.points),p.points.length>0&&(p.status="Intersection"),p}}(typeof exports!="undefined"?exports:this),function(e){"use strict";function n(e){e?this._tryParsingColor(e):this.setSource([0,0,0,1])}function r(e,t,n){return n<0&&(n+=1),n>1&&(n-=1),n<1/6?e+(t-e)*6*n:n<.5?t:n<2/3?e+(t-e)*(2/3-n)*6:e}var t=e.fabric||(e.fabric={});if(t.Color){t.warn("fabric.Color is already defined.");return}t.Color=n,t.Color.prototype={_tryParsingColor:function(e){var t;e in n.colorNameMap&&(e=n.colorNameMap[e]);if(e==="transparent"){this.setSource([255,255,255,0]);return}t=n.sourceFromHex(e),t||(t=n.sourceFromRgb(e)),t||(t=n.sourceFromHsl(e)),t&&this.setSource(t)},_rgbToHsl:function(e,n,r){e/=255,n/=255,r/=255;var i,s,o,u=t.util.array.max([e,n,r]),a=t.util.array.min([e,n,r]);o=(u+a)/2;if(u===a)i=s=0;else{var f=u-a;s=o>.5?f/(2-u-a):f/(u+a);switch(u){case e:i=(n-r)/f+(n1?1:n;if(t){var o=t.split(/\s*;\s*/);o[o.length-1]===""&&o.pop();for(var u=o.length;u--;){var a=o[u].split(/\s*:\s*/),f=a[0].trim(),l=a[1].trim();f==="stop-color"?r=l:f==="stop-opacity"&&(s=l)}}return r||(r=e.getAttribute("stop-color")||"rgb(0,0,0)"),s||(s=e.getAttribute("stop-opacity")),r=new fabric.Color(r),i=r.getAlpha(),s=isNaN(parseFloat(s))?1:parseFloat(s),s*=i,{offset:n,color:r.toRgb(),opacity:s}}function t(e){return{x1:e.getAttribute("x1")||0,y1:e.getAttribute("y1")||0,x2:e.getAttribute("x2")||"100%",y2:e.getAttribute("y2")||0}}function n(e){return{x1:e.getAttribute("fx")||e.getAttribute("cx")||"50%",y1:e.getAttribute("fy")||e.getAttribute("cy")||"50%",r1:0,x2:e.getAttribute("cx")||"50%",y2:e.getAttribute("cy")||"50%",r2:e.getAttribute("r")||"50%"}}function r(e,t,n){var r,i=0,s=1,o="";for(var u in t){r=parseFloat(t[u],10),typeof t[u]=="string"&&/^\d+%$/.test(t[u])?s=.01:s=1;if(u==="x1"||u==="x2"||u==="r2")s*=n==="objectBoundingBox"?e.width:1,i=n==="objectBoundingBox"?e.left||0:0;else if(u==="y1"||u==="y2")s*=n==="objectBoundingBox"?e.height:1,i=n==="objectBoundingBox"?e.top||0:0;t[u]=r*s+i}if(e.type==="ellipse"&&t.r2!==null&&n==="objectBoundingBox"&&e.rx!==e.ry){var a=e.ry/e.rx;o=" scale(1, "+a+")",t.y1&&(t.y1/=a),t.y2&&(t.y2/=a)}return o}fabric.Gradient=fabric.util.createClass({offsetX:0,offsetY:0,initialize:function(e){e||(e={});var t={};this.id=fabric.Object.__uid++,this.type=e.type||"linear",t={x1:e.coords.x1||0,y1:e.coords.y1||0,x2:e.coords.x2||0,y2:e.coords.y2||0},this.type==="radial"&&(t.r1=e.coords.r1||0,t.r2=e.coords.r2||0),this.coords=t,this.colorStops=e.colorStops.slice(),e.gradientTransform&&(this.gradientTransform=e.gradientTransform),this.offsetX=e.offsetX||this.offsetX,this.offsetY=e.offsetY||this.offsetY},addColorStop:function(e){for(var t in e){var n=new fabric.Color(e[t]);this.colorStops.push({offset:t,color:n.toRgb(),opacity:n.getAlpha()})}return this},toObject:function(){return{type:this.type,coords:this.coords,colorStops:this.colorStops,offsetX:this.offsetX,offsetY:this.offsetY}},toSVG:function(e){var t=fabric.util.object.clone(this.coords),n,r;this.colorStops.sort(function(e,t){return e.offset-t.offset});if(!e.group||e.group.type!=="path-group")for(var i in t)if(i==="x1"||i==="x2"||i==="r2")t[i]+=this.offsetX-e.width/2;else if(i==="y1"||i==="y2")t[i]+=this.offsetY-e.height/2;r='id="SVGID_'+this.id+'" gradientUnits="userSpaceOnUse"',this.gradientTransform&&(r+=' gradientTransform="matrix('+this.gradientTransform.join(" ")+')" '),this.type==="linear"?n=["\n']:this.type==="radial"&&(n=["\n']);for(var s=0;s\n');return n.push(this.type==="linear"?"\n":"\n"),n.join("")},toLive:function(e,t){var n,r=fabric.util.object.clone(this.coords);if(!this.type)return;if(t.group&&t.group.type==="path-group")for(var i in r)if(i==="x1"||i==="x2")r[i]+=-this.offsetX+t.width/2;else if(i==="y1"||i==="y2")r[i]+=-this.offsetY+t.height/2;this.type==="linear"?n=e.createLinearGradient(r.x1,r.y1,r.x2,r.y2):this.type==="radial"&&(n=e.createRadialGradient(r.x1,r.y1,r.r1,r.x2,r.y2,r.r2));for(var s=0,o=this.colorStops.length;s'+''+""},toLive:function(e){var t=typeof this.source=="function"?this.source():this.source;if(!t)return"";if(typeof t.src!="undefined"){if(!t.complete)return"";if(t.naturalWidth===0||t.naturalHeight===0)return""}return e.createPattern(t,this.repeat)}}),function(e){"use strict";var t=e.fabric||(e.fabric={});if(t.Shadow){t.warn("fabric.Shadow is already defined.");return}t.Shadow=t.util.createClass({color:"rgb(0,0,0)",blur:0,offsetX:0,offsetY:0,affectStroke:!1,includeDefaultValues:!0,initialize:function(e){typeof e=="string"&&(e=this._parseShadow(e));for(var n in e)this[n]=e[n];this.id=t.Object.__uid++},_parseShadow:function(e){var n=e.trim(),r=t.Shadow.reOffsetsAndBlur.exec(n)||[],i=n.replace(t.Shadow.reOffsetsAndBlur,"")||"rgb(0,0,0)";return{color:i.trim(),offsetX:parseInt(r[1],10)||0,offsetY:parseInt(r[2],10)||0,blur:parseInt(r[3],10)||0}},toString:function(){return[this.offsetX,this.offsetY,this.blur,this.color].join("px ")},toSVG:function(e){var t="SourceAlpha";return e&&(e.fill===this.color||e.stroke===this.color)&&(t="SourceGraphic"),''+''+''+""+""+''+""+""},toObject:function(){if(this.includeDefaultValues)return{color:this.color,blur:this.blur,offsetX:this.offsetX,offsetY:this.offsetY};var e={},n=t.Shadow.prototype;return this.color!==n.color&&(e.color=this.color),this.blur!==n.blur&&(e.blur=this.blur),this.offsetX!==n.offsetX&&(e.offsetX=this.offsetX),this.offsetY!==n.offsetY&&(e.offsetY=this.offsetY),e}}),t.Shadow.reOffsetsAndBlur=/(?:\s|^)(-?\d+(?:px)?(?:\s?|$))?(-?\d+(?:px)?(?:\s?|$))?(\d+(?:px)?)?(?:\s?|$)(?:$|\s)/}(typeof exports!="undefined"?exports:this),function(){"use strict";if(fabric.StaticCanvas){fabric.warn("fabric.StaticCanvas is already defined.");return}var e=fabric.util.object.extend,t=fabric.util.getElementOffset,n=fabric.util.removeFromArray,r=new Error("Could not initialize `canvas` element");fabric.StaticCanvas=fabric.util.createClass({initialize:function(e,t){t||(t={}),this._initStatic(e,t),fabric.StaticCanvas.activeInstance=this},backgroundColor:"",backgroundImage:null,overlayColor:"",overlayImage:null,includeDefaultValues:!0,stateful:!0,renderOnAddRemove:!0,clipTo:null,controlsAboveOverlay:!1,allowTouchScrolling:!1,imageSmoothingEnabled:!0,preserveObjectStacking:!1,viewportTransform:[1,0,0,1,0,0],onBeforeScaleRotate:function(){},_initStatic:function(e,t){this._objects=[],this._createLowerCanvas(e),this._initOptions(t),this._setImageSmoothing(),t.overlayImage&&this.setOverlayImage(t.overlayImage,this.renderAll.bind(this)),t.backgroundImage&&this.setBackgroundImage(t.backgroundImage,this.renderAll.bind(this)),t.backgroundColor&&this.setBackgroundColor(t.backgroundColor,this.renderAll.bind(this)),t.overlayColor&&this.setOverlayColor(t.overlayColor,this.renderAll.bind(this)),this.calcOffset()},calcOffset:function(){return this._offset=t(this.lowerCanvasEl),this},setOverlayImage:function(e,t,n){return this.__setBgOverlayImage("overlayImage",e,t,n)},setBackgroundImage:function(e,t,n){return this.__setBgOverlayImage("backgroundImage",e,t,n)},setOverlayColor:function(e,t){return this.__setBgOverlayColor("overlayColor",e,t)},setBackgroundColor:function(e,t){return this.__setBgOverlayColor("backgroundColor",e,t)},_setImageSmoothing:function(){var e=this.getContext();e.imageSmoothingEnabled=this.imageSmoothingEnabled,e.webkitImageSmoothingEnabled=this.imageSmoothingEnabled,e.mozImageSmoothingEnabled=this.imageSmoothingEnabled,e.msImageSmoothingEnabled=this.imageSmoothingEnabled,e.oImageSmoothingEnabled=this.imageSmoothingEnabled},__setBgOverlayImage:function(e,t,n,r){return typeof t=="string"?fabric.util.loadImage(t,function(t){this[e]=new fabric.Image(t,r),n&&n()},this):(this[e]=t,n&&n()),this},__setBgOverlayColor:function(e,t,n){if(t&&t.source){var r=this;fabric.util.loadImage(t.source,function(i){r[e]=new fabric.Pattern({source:i,repeat:t.repeat,offsetX:t.offsetX,offsetY:t.offsetY}),n&&n()})}else this[e]=t,n&&n();return this},_createCanvasElement:function(){var e=fabric.document.createElement("canvas");e.style||(e.style={});if(!e)throw r;return this._initCanvasElement(e),e},_initCanvasElement:function(e){fabric.util.createCanvasElement(e);if(typeof e.getContext=="undefined")throw r},_initOptions:function(e){for(var t in e)this[t]=e[t];this.width=this.width||parseInt(this.lowerCanvasEl.width,10)||0,this.height=this.height||parseInt(this.lowerCanvasEl.height,10)||0;if(!this.lowerCanvasEl.style)return;this.lowerCanvasEl.width=this.width,this.lowerCanvasEl.height=this.height,this.lowerCanvasEl.style.width=this.width+"px",this.lowerCanvasEl.style.height=this.height+"px",this.viewportTransform=this.viewportTransform.slice()},_createLowerCanvas:function(e){this.lowerCanvasEl=fabric.util.getById(e)||this._createCanvasElement(),this._initCanvasElement(this.lowerCanvasEl),fabric.util.addClass(this.lowerCanvasEl,"lower-canvas"),this.interactive&&this._applyCanvasStyle(this.lowerCanvasEl),this.contextContainer=this.lowerCanvasEl.getContext("2d")},getWidth:function(){return this.width},getHeight:function(){return this.height},setWidth:function(e,t){return this.setDimensions({width:e},t)},setHeight:function(e,t){return this.setDimensions({height:e},t)},setDimensions:function(e,t){var n;t=t||{};for(var r in e)n=e[r],t.cssOnly||(this._setBackstoreDimension(r,e[r]),n+="px"),t.backstoreOnly||this._setCssDimension(r,n);return t.cssOnly||this.renderAll(),this.calcOffset(),this},_setBackstoreDimension:function(e,t){return this.lowerCanvasEl[e]=t,this.upperCanvasEl&&(this.upperCanvasEl[e]=t),this.cacheCanvasEl&&(this.cacheCanvasEl[e]=t),this[e]=t,this},_setCssDimension:function(e,t){return this.lowerCanvasEl.style[e]=t,this.upperCanvasEl&&(this.upperCanvasEl.style[e]=t),this.wrapperEl&&(this.wrapperEl.style[e]=t),this},getZoom:function(){return Math.sqrt(this.viewportTransform[0]*this.viewportTransform[3])},setViewportTransform:function(e){this.viewportTransform=e,this.renderAll();for(var t=0,n=this._objects.length;t"),n.join("")},_setSVGPreamble:function(e,t){t.suppressPreamble||e.push('','\n')},_setSVGHeader:function(e,t){var n,r,i;t.viewBox?(n=t.viewBox.width,r=t.viewBox.height):(n=this.width,r=this.height,this.svgViewportTransformation||(i=this.viewportTransform,n/=i[0],r/=i[3])),e.push("',"Created with Fabric.js ",fabric.version,"","",fabric.createSVGFontFacesMarkup(this.getObjects()),fabric.createSVGRefElementsMarkup(this),"")},_setSVGObjects:function(e,t){var n=this.getActiveGroup();n&&this.discardActiveGroup();for(var r=0,i=this.getObjects(),s=i.length;r"):this[t]&&t==="overlayColor"&&e.push('")},sendToBack:function(e){return n(this._objects,e),this._objects.unshift(e),this.renderAll&&this.renderAll()},bringToFront:function(e){return n(this._objects,e),this._objects.push(e),this.renderAll&&this.renderAll()},sendBackwards:function(e,t){var r=this._objects.indexOf(e);if(r!==0){var i=this._findNewLowerIndex(e,r,t);n(this._objects,e),this._objects.splice(i,0,e),this.renderAll&&this.renderAll()}return this},_findNewLowerIndex:function(e,t,n){var r;if(n){r=t;for(var i=t-1;i>=0;--i){var s=e.intersectsWithObject(this._objects[i])||e.isContainedWithinObject(this._objects[i])||this._objects[i].isContainedWithinObject(e);if(s){r=i;break}}}else r=t-1;return r},bringForward:function(e,t){var r=this._objects.indexOf(e);if(r!==this._objects.length-1){var i=this._findNewUpperIndex(e,r,t);n(this._objects,e),this._objects.splice(i,0,e),this.renderAll&&this.renderAll()}return this},_findNewUpperIndex:function(e,t,n){var r;if(n){r=t;for(var i=t+1;i"}}),e(fabric.StaticCanvas.prototype,fabric.Observable),e(fabric.StaticCanvas.prototype,fabric.Collection),e(fabric.StaticCanvas.prototype,fabric.DataURLExporter),e(fabric.StaticCanvas,{EMPTY_JSON:'{"objects": [], "background": "white"}',supports:function(e){var t=fabric.util.createCanvasElement();if(!t||!t.getContext)return null;var n=t.getContext("2d");if(!n)return null;switch(e){case"getImageData":return typeof n.getImageData!="undefined";case"setLineDash":return typeof n.setLineDash!="undefined";case"toDataURL":return typeof t.toDataURL!="undefined";case"toDataURLWithQuality":try{return t.toDataURL("image/jpeg",0),!0}catch(r){}return!1;default:return null}}}),fabric.StaticCanvas.prototype.toJSON=fabric.StaticCanvas.prototype.toObject}(),fabric.BaseBrush=fabric.util.createClass({color:"rgb(0, 0, 0)",width:1,shadow:null,strokeLineCap:"round",strokeLineJoin:"round",setShadow:function(e){return this.shadow=new fabric.Shadow(e),this},_setBrushStyles:function(){var e=this.canvas.contextTop;e.strokeStyle=this.color,e.lineWidth=this.width,e.lineCap=this.strokeLineCap,e.lineJoin=this.strokeLineJoin},_setShadow:function(){if(!this.shadow)return;var e=this.canvas.contextTop;e.shadowColor=this.shadow.color,e.shadowBlur=this.shadow.blur,e.shadowOffsetX=this.shadow.offsetX,e.shadowOffsetY=this.shadow.offsetY},_resetShadow:function(){var e=this.canvas.contextTop;e.shadowColor="",e.shadowBlur=e.shadowOffsetX=e.shadowOffsetY=0}}),function(){fabric.PencilBrush=fabric.util.createClass(fabric.BaseBrush,{initialize:function(e){this.canvas=e,this._points=[]},onMouseDown:function(e){this._prepareForDrawing(e),this._captureDrawingPath(e),this._render()},onMouseMove:function(e){this._captureDrawingPath(e),this.canvas.clearContext(this.canvas.contextTop),this._render()},onMouseUp:function(){this._finalizeAndAddPath()},_prepareForDrawing:function(e){var t=new fabric.Point(e.x,e.y);this._reset(),this._addPoint(t),this.canvas.contextTop.moveTo(t.x,t.y)},_addPoint:function(e){this._points.push(e)},_reset:function(){this._points.length=0,this._setBrushStyles(),this._setShadow()},_captureDrawingPath:function(e){var t=new fabric.Point(e.x,e.y);this._addPoint(t)},_render:function(){var e=this.canvas.contextTop,t=this.canvas.viewportTransform,n=this._points[0],r=this._points[1];e.save(),e.transform(t[0],t[1],t[2],t[3],t[4],t[5]),e.beginPath(),this._points.length===2&&n.x===r.x&&n.y===r.y&&(n.x-=.5,r.x+=.5),e.moveTo(n.x,n.y);for(var i=1,s=this._points.length;in.padding?e.x<0?e.x+=n.padding:e.x-=n.padding:e.x=0,i(e.y)>n.padding?e.y<0?e.y+=n.padding:e.y-=n.padding:e.y=0},_rotateObject:function(e,t){var i=this._currentTransform;if(i.target.get("lockRotation"))return;var s=r(i.ey-i.top,i.ex-i.left),o=r(t-i.top,e-i.left),u=n(o-s+i.theta);u<0&&(u=360+u),i.target.angle=u},setCursor:function(e){this.upperCanvasEl.style.cursor=e},_resetObjectTransform:function(e){e.scaleX=1,e.scaleY=1,e.setAngle(0)},_drawSelection:function(){var e=this.contextTop,t=this._groupSelector,n=t.left,r=t.top,o=i(n),u=i(r);e.fillStyle=this.selectionColor,e.fillRect(t.ex-(n>0?0:-n),t.ey-(r>0?0:-r),o,u),e.lineWidth=this.selectionLineWidth,e.strokeStyle=this.selectionBorderColor;if(this.selectionDashArray.length>1){var a=t.ex+s-(n>0?0:o),f=t.ey+s-(r>0?0:u);e.beginPath(),fabric.util.drawDashedLine(e,a,f,a+o,f,this.selectionDashArray),fabric.util.drawDashedLine(e,a,f+u-1,a+o,f+u-1,this.selectionDashArray),fabric.util.drawDashedLine(e,a,f,a,f+u,this.selectionDashArray),fabric.util.drawDashedLine(e,a+o-1,f,a+o-1,f+u,this.selectionDashArray),e.closePath(),e.stroke()}else e.strokeRect(t.ex+s-(n>0?0:o),t.ey+s-(r>0?0:u),o,u)},_isLastRenderedObject:function(e){return this.controlsAboveOverlay&&this.lastRenderedObjectWithControlsAboveOverlay&&this.lastRenderedObjectWithControlsAboveOverlay.visible&&this.containsPoint(e,this.lastRenderedObjectWithControlsAboveOverlay)&&this.lastRenderedObjectWithControlsAboveOverlay._findTargetCorner(this.getPointer(e,!0))},findTarget:function(e,t){if(this.skipTargetFind)return;if(this._isLastRenderedObject(e))return this.lastRenderedObjectWithControlsAboveOverlay;var n=this.getActiveGroup();if(n&&!t&&this.containsPoint(e,n))return n;var r=this._searchPossibleTargets(e);return this._fireOverOutEvents(r),r},_fireOverOutEvents:function(e){e?this._hoveredTarget!==e&&(this.fire("mouse:over",{target:e}),e.fire("mouseover"),this._hoveredTarget&&(this.fire("mouse:out",{target:this._hoveredTarget}),this._hoveredTarget.fire("mouseout")),this._hoveredTarget=e):this._hoveredTarget&&(this.fire("mouse:out",{target:this._hoveredTarget}),this._hoveredTarget.fire("mouseout"),this._hoveredTarget=null)},_checkTarget:function(e,t,n){if(t&&t.visible&&t.evented&&this.containsPoint(e,t)){if(!this.perPixelTargetFind&&!t.perPixelTargetFind||!!t.isEditing)return!0;var r=this.isTargetTransparent(t,n.x,n.y);if(!r)return!0}},_searchPossibleTargets:function(e){var t,n=this.getPointer(e,!0),r=this._objects.length;while(r--)if(this._checkTarget(e,this._objects[r],n)){this.relatedTarget=this._objects[r],t=this._objects[r];break}return t},getPointer:function(t,n,r){r||(r=this.upperCanvasEl);var i=e(t,r),s=r.getBoundingClientRect(),o=s.width||0,u=s.height||0,a;if(!o||!u)"top"in s&&"bottom"in s&&(u=Math.abs(s.top-s.bottom)),"right"in s&&"left"in s&&(o=Math.abs(s.right-s.left));return this.calcOffset(),i.x=i.x-this._offset.left,i.y=i.y-this._offset.top,n||(i=fabric.util.transformPoint(i,fabric.util.invertTransform(this.viewportTransform))),o===0||u===0?a={width:1,height:1}:a={width:r.width/o,height:r.height/u},{x:i.x*a.width,y:i.y*a.height}},_createUpperCanvas:function(){var e=this.lowerCanvasEl.className.replace(/\s*lower-canvas\s*/,"");this.upperCanvasEl=this._createCanvasElement(),fabric.util.addClass(this.upperCanvasEl,"upper-canvas "+e),this.wrapperEl.appendChild(this.upperCanvasEl),this._copyCanvasStyle(this.lowerCanvasEl,this.upperCanvasEl),this._applyCanvasStyle(this.upperCanvasEl),this.contextTop=this.upperCanvasEl.getContext("2d")},_createCacheCanvas:function(){this.cacheCanvasEl=this._createCanvasElement(),this.cacheCanvasEl.setAttribute("width",this.width),this.cacheCanvasEl.setAttribute("height",this.height),this.contextCache=this.cacheCanvasEl.getContext("2d")},_initWrapperElement:function(){this.wrapperEl=fabric.util.wrapElement(this.lowerCanvasEl,"div",{"class":this.containerClass}),fabric.util.setStyle(this.wrapperEl,{width:this.getWidth()+"px",height:this.getHeight()+"px",position:"relative"}),fabric.util.makeElementUnselectable(this.wrapperEl)},_applyCanvasStyle:function(e){var t=this.getWidth()||e.width,n=this.getHeight()||e.height;fabric.util.setStyle(e,{position:"absolute",width:t+"px",height:n+"px",left:0,top:0}),e.width=t,e.height=n,fabric.util.makeElementUnselectable(e)},_copyCanvasStyle:function(e,t){t.style.cssText=e.style.cssText},getSelectionContext:function(){return this.contextTop},getSelectionElement:function(){return this.upperCanvasEl},_setActiveObject:function(e){this._activeObject&&this._activeObject.set("active",!1),this._activeObject=e,e.set("active",!0)},setActiveObject:function(e,t){return this._setActiveObject(e),this.renderAll(),this.fire("object:selected",{target:e,e:t}),e.fire("selected",{e:t}),this},getActiveObject:function(){return this._activeObject},_discardActiveObject:function(){this._activeObject&&this._activeObject.set("active",!1),this._activeObject=null},discardActiveObject:function(e){return this._discardActiveObject(),this.renderAll(),this.fire("selection:cleared",{e:e}),this},_setActiveGroup:function(e){this._activeGroup=e,e&&e.set("active",!0)},setActiveGroup:function(e,t){return this._setActiveGroup(e),e&&(this.fire("object:selected",{target:e,e:t}),e.fire("selected",{e:t})),this},getActiveGroup:function(){return this._activeGroup},_discardActiveGroup:function(){var e=this.getActiveGroup();e&&e.destroy(),this.setActiveGroup(null)},discardActiveGroup:function(e){return this._discardActiveGroup(),this.fire("selection:cleared",{e:e}),this},deactivateAll:function(){var e=this.getObjects(),t=0,n=e.length;for(;t1&&(t=new fabric.Group(t.reverse(),{originX:"center",originY:"center",canvas:this}),t.addWithUpdate(),this.setActiveGroup(t,e),t.saveCoords(),this.fire("selection:created",{target:t}),this.renderAll())},_collectObjects:function(){var n=[],r,i=this._groupSelector.ex,s=this._groupSelector.ey,o=i+this._groupSelector.left,u=s+this._groupSelector.top,a=new fabric.Point(e(i,o),e(s,u)),f=new fabric.Point(t(i,o),t(s,u)),l=i===o&&s===u;for(var c=this._objects.length;c--;){r=this._objects[c];if(!r||!r.selectable||!r.visible)continue;if(r.intersectsWithRect(a,f)||r.isContainedWithinRect(a,f)||r.containsPoint(a)||r.containsPoint(f)){r.set("active",!0),n.push(r);if(l)break}}return n},_maybeGroupObjects:function(e){this.selection&&this._groupSelector&&this._groupSelectedObjects(e);var t=this.getActiveGroup();t&&(t.setObjectsCoords().setCoords(),t.isMoving=!1,this.setCursor(this.defaultCursor)),this._groupSelector=null,this._currentTransform=null}})}(),fabric.util.object.extend(fabric.StaticCanvas.prototype,{toDataURL:function(e){e||(e={});var t=e.format||"png",n=e.quality||1,r=e.multiplier||1,i={left:e.left,top:e.top,width:e.width,height:e.height};return r!==1?this.__toDataURLWithMultiplier(t,n,i,r):this.__toDataURL(t,n,i)},__toDataURL:function(e,t,n){this.renderAll(!0);var r=this.upperCanvasEl||this.lowerCanvasEl,i=this.__getCroppedCanvas(r,n);e==="jpg"&&(e="jpeg");var s=fabric.StaticCanvas.supports("toDataURLWithQuality")?(i||r).toDataURL("image/"+e,t):(i||r).toDataURL("image/"+e);return this.contextTop&&this.clearContext(this.contextTop),this.renderAll(),i&&(i=null),s},__getCroppedCanvas:function(e,t){var n,r,i="left"in t||"top"in t||"width"in t||"height"in t;return i&&(n=fabric.util.createCanvasElement(),r=n.getContext("2d"),n.width=t.width||this.width,n.height=t.height||this.height,r.drawImage (e,-t.left||0,-t.top||0)),n},__toDataURLWithMultiplier:function(e,t,n,r){var i=this.getWidth(),s=this.getHeight(),o=i*r,u=s*r,a=this.getActiveObject(),f=this.getActiveGroup(),l=this.contextTop||this.contextContainer;r>1&&this.setWidth(o).setHeight(u),l.scale(r,r),n.left&&(n.left*=r),n.top&&(n.top*=r),n.width?n.width*=r:r<1&&(n.width=o),n.height?n.height*=r:r<1&&(n.height=u),f?this._tempRemoveBordersControlsFromGroup(f):a&&this.deactivateAll&&this.deactivateAll(),this.renderAll(!0);var c=this.__toDataURL(e,t,n);return this.width=i,this.height=s,l.scale(1/r,1/r),this.setWidth(i).setHeight(s),f?this._restoreBordersControlsOnGroup(f):a&&this.setActiveObject&&this.setActiveObject(a),this.contextTop&&this.clearContext(this.contextTop),this.renderAll(),c},toDataURLWithMultiplier:function(e,t,n){return this.toDataURL({format:e,multiplier:t,quality:n})},_tempRemoveBordersControlsFromGroup:function(e){e.origHasControls=e.hasControls,e.origBorderColor=e.borderColor,e.hasControls=!0,e.borderColor="rgba(0,0,0,0)",e.forEachObject(function(e){e.origBorderColor=e.borderColor,e.borderColor="rgba(0,0,0,0)"})},_restoreBordersControlsOnGroup:function(e){e.hideControls=e.origHideControls,e.borderColor=e.origBorderColor,e.forEachObject(function(e){e.borderColor=e.origBorderColor,delete e.origBorderColor})}}),fabric.util.object.extend(fabric.StaticCanvas.prototype,{loadFromDatalessJSON:function(e,t,n){return this.loadFromJSON(e,t,n)},loadFromJSON:function(e,t,n){if(!e)return;var r=typeof e=="string"?JSON.parse(e):e;this.clear();var i=this;return this._enlivenObjects(r.objects,function(){i._setBgOverlay(r,t)},n),this},_setBgOverlay:function(e,t){var n=this,r={backgroundColor:!1,overlayColor:!1,backgroundImage:!1,overlayImage:!1};if(!e.backgroundImage&&!e.overlayImage&&!e.background&&!e.overlay){t&&t();return}var i=function(){r.backgroundImage&&r.overlayImage&&r.backgroundColor&&r.overlayColor&&(n.renderAll(),t&&t())};this.__setBgOverlay("backgroundImage",e.backgroundImage,r,i),this.__setBgOverlay("overlayImage",e.overlayImage,r,i),this.__setBgOverlay("backgroundColor",e.background,r,i),this.__setBgOverlay("overlayColor",e.overlay,r,i),i()},__setBgOverlay:function(e,t,n,r){var i=this;if(!t){n[e]=!0;return}e==="backgroundImage"||e==="overlayImage"?fabric.Image.fromObject(t,function(t){i[e]=t,n[e]=!0,r&&r()}):this["set"+fabric.util.string.capitalize(e,!0)](t,function(){n[e]=!0,r&&r()})},_enlivenObjects:function(e,t,n){var r=this;if(!e||e.length===0){t&&t();return}var i=this.renderOnAddRemove;this.renderOnAddRemove=!1,fabric.util.enlivenObjects(e,function(e){e.forEach(function(e,t){r.insertAt(e,t,!0)}),r.renderOnAddRemove=i,t&&t()},null,n)},_toDataURL:function(e,t){this.clone(function(n){t(n.toDataURL(e))})},_toDataURLWithMultiplier:function(e,t,n){this.clone(function(r){n(r.toDataURLWithMultiplier(e,t))})},clone:function(e,t){var n=JSON.stringify(this.toJSON(t));this.cloneWithoutData(function(t){t.loadFromJSON(n,function(){e&&e(t)})})},cloneWithoutData:function(e){var t=fabric.document.createElement("canvas");t.width=this.getWidth(),t.height=this.getHeight();var n=new fabric.Canvas(t);n.clipTo=this.clipTo,this.backgroundImage?(n.setBackgroundImage(this.backgroundImage.src,function(){n.renderAll(),e&&e(n)}),n.backgroundImageOpacity=this.backgroundImageOpacity,n.backgroundImageStretch=this.backgroundImageStretch):e&&e(n)}}),function(e){"use strict";var t=e.fabric||(e.fabric={}),n=t.util.object.extend,r=t.util.toFixed,i=t.util.string.capitalize,s=t.util.degreesToRadians,o=t.StaticCanvas.supports("setLineDash");if(t.Object)return;t.Object=t.util.createClass({type:"object",originX:"left",originY:"top",top:0,left:0,width:0,height:0,scaleX:1,scaleY:1,flipX:!1,flipY:!1,opacity:1,angle:0,cornerSize:12,transparentCorners:!0,hoverCursor:null,padding:0,borderColor:"rgba(102,153,255,0.75)",cornerColor:"rgba(102,153,255,0.5)",centeredScaling:!1,centeredRotation:!0,fill:"rgb(0,0,0)",fillRule:"nonzero",globalCompositeOperation:"source-over",backgroundColor:"",stroke:null,strokeWidth:1,strokeDashArray:null,strokeLineCap:"butt",strokeLineJoin:"miter",strokeMiterLimit:10,shadow:null,borderOpacityWhenMoving:.4,borderScaleFactor:1,transformMatrix:null,minScaleLimit:.01,selectable:!0,evented:!0,visible:!0,hasControls:!0,hasBorders:!0,hasRotatingPoint:!0,rotatingPointOffset:40,perPixelTargetFind:!1,includeDefaultValues:!0,clipTo:null,lockMovementX:!1,lockMovementY:!1,lockRotation:!1,lockScalingX:!1,lockScalingY:!1,lockUniScaling:!1,lockScalingFlip:!1,stateProperties:"top left width height scaleX scaleY flipX flipY originX originY transformMatrix stroke strokeWidth strokeDashArray strokeLineCap strokeLineJoin strokeMiterLimit angle opacity fill fillRule globalCompositeOperation shadow clipTo visible backgroundColor".split(" "),initialize:function(e){e&&this.setOptions(e)},_initGradient:function(e){e.fill&&e.fill.colorStops&&!(e.fill instanceof t.Gradient)&&this.set("fill",new t.Gradient(e.fill))},_initPattern:function(e){e.fill&&e.fill.source&&!(e.fill instanceof t.Pattern)&&this.set("fill",new t.Pattern(e.fill)),e.stroke&&e.stroke.source&&!(e.stroke instanceof t.Pattern)&&this.set("stroke",new t.Pattern(e.stroke))},_initClipping:function(e){if(!e.clipTo||typeof e.clipTo!="string")return;var n=t.util.getFunctionBody(e.clipTo);typeof n!="undefined"&&(this.clipTo=new Function("ctx",n))},setOptions:function(e){for(var t in e)this.set(t,e[t]);this._initGradient(e),this._initPattern(e),this._initClipping(e)},transform:function(e,t){this.group&&this.group.transform(e,t);var n=t?this._getLeftTopCoords():this.getCenterPoint();e.translate(n.x,n.y),e.rotate(s(this.angle)),e.scale(this.scaleX*(this.flipX?-1:1),this.scaleY*(this.flipY?-1:1))},toObject:function(e){var n=t.Object.NUM_FRACTION_DIGITS,i={type:this.type,originX:this.originX,originY:this.originY,left:r(this.left,n),top:r(this.top,n),width:r(this.width,n),height:r(this.height,n),fill:this.fill&&this.fill.toObject?this.fill.toObject():this.fill,stroke:this.stroke&&this.stroke.toObject?this.stroke.toObject():this.stroke,strokeWidth:r(this.strokeWidth,n),strokeDashArray:this.strokeDashArray,strokeLineCap:this.strokeLineCap,strokeLineJoin:this.strokeLineJoin,strokeMiterLimit:r(this.strokeMiterLimit,n),scaleX:r(this.scaleX,n),scaleY:r(this.scaleY,n),angle:r(this.getAngle(),n),flipX:this.flipX,flipY:this.flipY,opacity:r(this.opacity,n),shadow:this.shadow&&this.shadow.toObject?this.shadow.toObject():this.shadow,visible:this.visible,clipTo:this.clipTo&&String(this.clipTo),backgroundColor:this.backgroundColor,fillRule:this.fillRule,globalCompositeOperation:this.globalCompositeOperation};return this.includeDefaultValues||(i=this._removeDefaultValues(i)),t.util.populateWithProperties(this,i,e),i},toDatalessObject:function(e){return this.toObject(e)},_removeDefaultValues:function(e){var n=t.util.getKlass(e.type).prototype,r=n.stateProperties;return r.forEach(function(t){e[t]===n[t]&&delete e[t]}),e},toString:function(){return"#"},get:function(e){return this[e]},_setObject:function(e){for(var t in e)this._set(t,e[t])},set:function(e,t){return typeof e=="object"?this._setObject(e):typeof t=="function"&&e!=="clipTo"?this._set(e,t(this.get(e))):this._set(e,t),this},_set:function(e,n){var i=e==="scaleX"||e==="scaleY";return i&&(n=this._constrainScale(n)),e==="scaleX"&&n<0?(this.flipX=!this.flipX,n*=-1):e==="scaleY"&&n<0?(this.flipY=!this.flipY,n*=-1):e==="width"||e==="height"?this.minScaleLimit=r(Math.min(.1,1/Math.max(this.width,this.height)),2):e==="shadow"&&n&&!(n instanceof t.Shadow)&&(n=new t.Shadow(n)),this[e]=n,this},toggle:function(e){var t=this.get(e);return typeof t=="boolean"&&this.set(e,!t),this},setSourcePath:function(e){return this.sourcePath=e,this},getViewportTransform:function(){return this.canvas&&this.canvas.viewportTransform?this.canvas.viewportTransform:[1,0,0,1,0,0]},render:function(e,n){if(this.width===0||this.height===0||!this.visible)return;e.save(),this._setupCompositeOperation(e),n||this.transform(e),this._setStrokeStyles(e),this._setFillStyles(e),this.group&&this.group.type==="path-group"&&e.translate(-this.group.width/2,-this.group.height/2),this.transformMatrix&&e.transform.apply(e,this.transformMatrix),this._setOpacity(e),this._setShadow(e),this.clipTo&&t.util.clipContext(this,e),this._render(e,n),this.clipTo&&e.restore(),this._removeShadow(e),this._restoreCompositeOperation(e),e.restore()},_setOpacity:function(e){this.group&&this.group._setOpacity(e),e.globalAlpha*=this.opacity},_setStrokeStyles:function(e){this.stroke&&(e.lineWidth=this.strokeWidth,e.lineCap=this.strokeLineCap,e.lineJoin=this.strokeLineJoin,e.miterLimit=this.strokeMiterLimit,e.strokeStyle=this.stroke.toLive?this.stroke.toLive(e,this):this.stroke)},_setFillStyles:function(e){this.fill&&(e.fillStyle=this.fill.toLive?this.fill.toLive(e,this):this.fill)},_renderControls:function(e,n){var r=this.getViewportTransform();e.save();if(this.active&&!n){var i;this.group&&(i=t.util.transformPoint(this.group.getCenterPoint(),r),e.translate(i.x,i.y),e.rotate(s(this.group.angle))),i=t.util.transformPoint(this.getCenterPoint(),r,null!=this.group),this.group&&(i.x*=this.group.scaleX,i.y*=this.group.scaleY),e.translate(i.x,i.y),e.rotate(s(this.angle)),this.drawBorders(e),this.drawControls(e)}e.restore()},_setShadow:function(e){if(!this.shadow)return;e.shadowColor=this.shadow.color,e.shadowBlur=this.shadow.blur,e.shadowOffsetX=this.shadow.offsetX,e.shadowOffsetY=this.shadow.offsetY},_removeShadow:function(e){if(!this.shadow)return;e.shadowColor="",e.shadowBlur=e.shadowOffsetX=e.shadowOffsetY=0},_renderFill:function(e){if(!this.fill)return;e.save();if(this.fill.gradientTransform){var t=this.fill.gradientTransform;e.transform.apply(e,t)}this.fill.toLive&&e.translate(-this.width/2+this.fill.offsetX||0,-this.height/2+this.fill.offsetY||0),this.fillRule==="evenodd"?e.fill("evenodd"):e.fill(),e.restore(),this.shadow&&!this.shadow.affectStroke&&this._removeShadow(e)},_renderStroke:function(e){if(!this.stroke||this.strokeWidth===0)return;e.save();if(this.strokeDashArray)1&this.strokeDashArray.length&&this.strokeDashArray.push.apply(this.strokeDashArray,this.strokeDashArray),o?(e.setLineDash(this.strokeDashArray),this._stroke&&this._stroke(e)):this._renderDashedStroke&&this._renderDashedStroke(e),e.stroke();else{if(this.stroke.gradientTransform){var t=this.stroke.gradientTransform;e.transform.apply(e,t)}this._stroke?this._stroke(e):e.stroke()}this._removeShadow(e),e.restore()},clone:function(e,n){return this.constructor.fromObject?this.constructor.fromObject(this.toObject(n),e):new t.Object(this.toObject(n))},cloneAsImage:function(e){var n=this.toDataURL();return t.util.loadImage(n,function(n){e&&e(new t.Image(n))}),this},toDataURL:function(e){e||(e={});var n=t.util.createCanvasElement(),r=this.getBoundingRect();n.width=r.width,n.height=r.height,t.util.wrapElement(n,"div");var i=new t.Canvas(n);e.format==="jpg"&&(e.format="jpeg"),e.format==="jpeg"&&(i.backgroundColor="#fff");var s={active:this.get("active"),left:this.getLeft(),top:this.getTop()};this.set("active",!1),this.setPositionByOrigin(new t.Point(n.width/2,n.height/2),"center","center");var o=this.canvas;i.add(this);var u=i.toDataURL(e);return this.set(s).setCoords(),this.canvas=o,i.dispose(),i=null,u},isType:function(e){return this.type===e},complexity:function(){return 0},toJSON:function(e){return this.toObject(e)},setGradient:function(e,n){n||(n={});var r={colorStops:[]};r.type=n.type||(n.r1||n.r2?"radial":"linear"),r.coords={x1:n.x1,y1:n.y1,x2:n.x2,y2:n.y2};if(n.r1||n.r2)r.coords.r1=n.r1,r.coords.r2=n.r2;for(var i in n.colorStops){var s=new t.Color(n.colorStops[i]);r.colorStops.push({offset:i,color:s.toRgb(),opacity:s.getAlpha()})}return this.set(e,t.Gradient.forObject(this,r))},setPatternFill:function(e){return this.set("fill",new t.Pattern(e))},setShadow:function(e){return this.set("shadow",e?new t.Shadow(e):null)},setColor:function(e){return this.set("fill",e),this},setAngle:function(e){var t=(this.originX!=="center"||this.originY!=="center")&&this.centeredRotation;return t&&this._setOriginToCenter(),this.set("angle",e),t&&this._resetOrigin(),this},centerH:function(){return this.canvas.centerObjectH(this),this},centerV:function(){return this.canvas.centerObjectV(this),this},center:function(){return this.canvas.centerObject(this),this},remove:function(){return this.canvas.remove(this),this},getLocalPointer:function(e,t){t=t||this.canvas.getPointer(e);var n=this.translateToOriginPoint(this.getCenterPoint(),"left","top");return{x:t.x-n.x,y:t.y-n.y}},_setupCompositeOperation:function(e){this.globalCompositeOperation&&(this._prevGlobalCompositeOperation=e.globalCompositeOperation,e.globalCompositeOperation=this.globalCompositeOperation)},_restoreCompositeOperation:function(e){this.globalCompositeOperation&&this._prevGlobalCompositeOperation&&(e.globalCompositeOperation=this._prevGlobalCompositeOperation)}}),t.util.createAccessors(t.Object),t.Object.prototype.rotate=t.Object.prototype.setAngle,n(t.Object.prototype,t.Observable),t.Object.NUM_FRACTION_DIGITS=2,t.Object.__uid=0}(typeof exports!="undefined"?exports:this),function(){var e=fabric.util.degreesToRadians;fabric.util.object.extend(fabric.Object.prototype,{translateToCenterPoint:function(t,n,r){var i=t.x,s=t.y,o=this.stroke?this.strokeWidth:0;return n==="left"?i=t.x+(this.getWidth()+o*this.scaleX)/2:n==="right"&&(i=t.x-(this.getWidth()+o*this.scaleX)/2),r==="top"?s=t.y+(this.getHeight()+o*this.scaleY)/2:r==="bottom"&&(s=t.y-(this.getHeight()+o*this.scaleY)/2),fabric.util.rotatePoint(new fabric.Point(i,s),t,e(this.angle))},translateToOriginPoint:function(t,n,r){var i=t.x,s=t.y,o=this.stroke?this.strokeWidth:0;return n==="left"?i=t.x-(this.getWidth()+o*this.scaleX)/2:n==="right"&&(i=t.x+(this.getWidth()+o*this.scaleX)/2),r==="top"?s=t.y-(this.getHeight()+o*this.scaleY)/2:r==="bottom"&&(s=t.y+(this.getHeight()+o*this.scaleY)/2),fabric.util.rotatePoint(new fabric.Point(i,s),t,e(this.angle))},getCenterPoint:function(){var e=new fabric.Point(this.left,this.top);return this.translateToCenterPoint(e,this.originX,this.originY)},getPointByOrigin:function(e,t){var n=this.getCenterPoint();return this.translateToOriginPoint(n,e,t)},toLocalPoint:function(t,n,r){var i=this.getCenterPoint(),s=this.stroke?this.strokeWidth:0,o,u;return n&&r?(n==="left"?o=i.x-(this.getWidth()+s*this.scaleX)/2:n==="right"?o=i.x+(this.getWidth()+s*this.scaleX)/2:o=i.x,r==="top"?u=i.y-(this.getHeight()+s*this.scaleY)/2:r==="bottom"?u=i.y+(this.getHeight()+s*this.scaleY)/2:u=i.y):(o=this.left,u=this.top),fabric.util.rotatePoint(new fabric.Point(t.x,t.y),i,-e(this.angle)).subtractEquals(new fabric.Point(o,u))},setPositionByOrigin:function(e,t,n){var r=this.translateToCenterPoint(e,t,n),i=this.translateToOriginPoint(r,this.originX,this.originY);this.set("left",i.x),this.set("top",i.y)},adjustPosition:function(t){var n=e(this.angle),r=this.getWidth()/2,i=Math.cos(n)*r,s=Math.sin(n)*r,o=this.getWidth(),u=Math.cos(n)*o,a=Math.sin(n)*o;this.originX==="center"&&t==="left"||this.originX==="right"&&t==="center"?(this.left-=i,this.top-=s):this.originX==="left"&&t==="center"||this.originX==="center"&&t==="right"?(this.left+=i,this.top+=s):this.originX==="left"&&t==="right"?(this.left+=u,this.top+=a):this.originX==="right"&&t==="left"&&(this.left-=u,this.top-=a),this.setCoords(),this.originX=t},_setOriginToCenter:function(){this._originalOriginX=this.originX,this._originalOriginY=this.originY;var e=this.getCenterPoint();this.originX="center",this.originY="center",this.left=e.x,this.top=e.y},_resetOrigin:function(){var e=this.translateToOriginPoint(this.getCenterPoint(),this._originalOriginX,this._originalOriginY);this.originX=this._originalOriginX,this.originY=this._originalOriginY,this.left=e.x,this.top=e.y,this._originalOriginX=null,this._originalOriginY=null},_getLeftTopCoords:function(){return this.translateToOriginPoint(this.getCenterPoint(),"left","center")}})}(),function(){var e=fabric.util.degreesToRadians;fabric.util.object.extend(fabric.Object.prototype,{oCoords:null,intersectsWithRect:function(e,t){var n=this.oCoords,r=new fabric.Point(n.tl.x,n.tl.y),i=new fabric.Point(n.tr.x,n.tr.y),s=new fabric.Point(n.bl.x,n.bl.y),o=new fabric.Point(n.br.x,n.br.y),u=fabric.Intersection.intersectPolygonRectangle([r,i,o,s],e,t);return u.status==="Intersection"},intersectsWithObject:function(e){function t(e){return{tl:new fabric.Point(e.tl.x,e.tl.y),tr:new fabric.Point(e.tr.x,e.tr.y),bl:new fabric.Point(e.bl.x,e.bl.y),br:new fabric.Point(e.br.x,e.br.y)}}var n=t(this.oCoords),r=t(e.oCoords),i=fabric.Intersection.intersectPolygonPolygon([n.tl,n.tr,n.br,n.bl],[r.tl,r.tr,r.br,r.bl]);return i.status==="Intersection"},isContainedWithinObject:function(e){var t=e.getBoundingRect(),n=new fabric.Point(t.left,t.top),r=new fabric.Point(t.left+t.width,t.top+t.height);return this.isContainedWithinRect(n,r)},isContainedWithinRect:function(e,t){var n=this.getBoundingRect();return n.left>=e.x&&n.left+n.width<=t.x&&n.top>=e.y&&n.top+n.height<=t.y},containsPoint:function(e){var t=this._getImageLines(this.oCoords),n=this._findCrossPoints(e,t);return n!==0&&n%2===1},_getImageLines:function(e){return{topline:{o:e.tl,d:e.tr},rightline:{o:e.tr,d:e.br},bottomline:{o:e.br,d:e.bl},leftline:{o:e.bl,d:e.tl}}},_findCrossPoints:function(e,t){var n,r,i,s,o,u,a=0,f;for(var l in t){f=t[l];if(f.o.y=e.y&&f.d.y>=e.y)continue;f.o.x===f.d.x&&f.o.x>=e.x?(o=f.o.x,u=e.y):(n=0,r=(f.d.y-f.o.y)/(f.d.x-f.o.x),i=e.y-n*e.x,s=f.o.y-r*f.o.x,o=-(i-s)/(n-r),u=i+n*o),o>=e.x&&(a+=1);if(a===2)break}return a},getBoundingRectWidth:function(){return this.getBoundingRect().width},getBoundingRectHeight:function(){return this.getBoundingRect().height},getBoundingRect:function(){this.oCoords||this.setCoords();var e=[this.oCoords.tl.x,this.oCoords.tr.x,this.oCoords.br.x,this.oCoords.bl.x],t=fabric.util.array.min(e),n=fabric.util.array.max(e),r=Math.abs(t-n),i=[this.oCoords.tl.y,this.oCoords.tr.y,this.oCoords.br.y,this.oCoords.bl.y],s=fabric.util.array.min(i),o=fabric.util.array.max(i),u=Math.abs(s-o);return{left:t,top:s,width:r,height:u}},getWidth:function(){return this.width*this.scaleX},getHeight:function(){return this.height*this.scaleY},_constrainScale:function(e){return Math.abs(e)1?this.strokeWidth:0,n=e(this.angle),r=this.getViewportTransform(),i=function(e){return fabric.util.transformPoint(e,r)},s=this.width,o=this.height,u=this.strokeLineCap==="round"||this.strokeLineCap==="square",a=this.type==="line"&&this.width===1,f=this.type==="line"&&this.height===1,l=u&&f||this.type!=="line",c=u&&a||this.type!=="line";a?s=t:f&&(o=t),l&&(s+=t),c&&(o+=t),this.currentWidth=s*this.scaleX,this.currentHeight=o*this.scaleY,this.currentWidth<0&&(this.currentWidth=Math.abs(this.currentWidth));var h=Math.sqrt(Math.pow(this.currentWidth/2,2)+Math.pow(this.currentHeight/2,2)),p=Math.atan(isFinite(this.currentHeight/this.currentWidth)?this.currentHeight/this.currentWidth:0),d=Math.cos(p+n)*h,v=Math.sin(p+n)*h,m=Math.sin(n),g=Math.cos(n),y=this.getCenterPoint(),b=new fabric.Point(this.currentWidth,this.currentHeight),w=new fabric.Point(y.x-d,y.y-v),E=new fabric.Point(w.x+b.x*g,w.y+b.x*m),S=new fabric.Point(w.x-b.y*m,w.y+b.y*g),x=new fabric.Point(w.x+b.x/2*g,w.y+b.x/2*m),T=i(w),N=i(E),C=i(new fabric.Point(E.x-b.y*m,E.y+b.y*g)),k=i(S),L=i(new fabric.Point(w.x-b.y/2*m,w.y+b.y/2*g)),A=i(x),O=i(new fabric.Point(E.x-b.y/2*m,E.y+b.y/2*g)),M=i(new fabric.Point(S.x+b.x/2*g,S.y+b.x/2*m)),_=i(new fabric.Point(x.x,x.y)),D=Math.cos(p+n)*this.padding*Math.sqrt(2),P=Math.sin(p+n)*this.padding*Math.sqrt(2);return T=T.add(new fabric.Point(-D,-P)),N=N.add(new fabric.Point(P,-D)),C=C.add(new fabric.Point(D,P)),k=k.add(new fabric.Point(-P,D)),L=L.add(new fabric.Point((-D-P)/2,(-P+D)/2)),A=A.add(new fabric.Point((P-D)/2,-(P+D)/2)),O=O.add(new fabric.Point((P+D)/2,(P-D)/2)),M=M.add(new fabric.Point((D-P)/2,(D+P)/2)),_=_.add(new fabric.Point((P-D)/2,-(P+D)/2)),this.oCoords={tl:T,tr:N,br:C,bl:k,ml:L,mt:A,mr:O,mb:M,mtr:_},this._setCornerCoords&&this._setCornerCoords(),this}})}(),fabric.util.object.extend(fabric.Object.prototype,{sendToBack:function(){return this.group?fabric.StaticCanvas.prototype.sendToBack.call(this.group,this):this.canvas.sendToBack(this),this},bringToFront:function(){return this.group?fabric.StaticCanvas.prototype.bringToFront.call(this.group,this):this.canvas.bringToFront(this),this},sendBackwards:function(e){return this.group?fabric.StaticCanvas.prototype.sendBackwards.call(this.group,this,e):this.canvas.sendBackwards(this,e),this},bringForward:function(e){return this.group?fabric.StaticCanvas.prototype.bringForward.call(this.group,this,e):this.canvas.bringForward(this,e),this},moveTo:function(e){return this.group?fabric.StaticCanvas.prototype.moveTo.call(this.group,this,e):this.canvas.moveTo(this,e),this}}),fabric.util.object.extend(fabric.Object.prototype,{getSvgStyles:function(){var e=this.fill?this.fill.toLive?"url(#SVGID_"+this.fill.id+")":this.fill:"none",t=this.fillRule,n=this.stroke?this.stroke.toLive?"url(#SVGID_"+this.stroke.id+")":this.stroke:"none",r=this.strokeWidth?this.strokeWidth:"0",i=this.strokeDashArray?this.strokeDashArray.join(" "):"",s=this.strokeLineCap?this.strokeLineCap:"butt",o=this.strokeLineJoin?this.strokeLineJoin:"miter",u=this.strokeMiterLimit?this.strokeMiterLimit:"4",a=typeof this.opacity!="undefined"?this.opacity:"1",f=this.visible?"":" visibility: hidden;",l=this.shadow&&this.type!=="text"?"filter: url(#SVGID_"+this.shadow.id+");":"";return["stroke: ",n,"; ","stroke-width: ",r,"; ","stroke-dasharray: ",i,"; ","stroke-linecap: ",s,"; ","stroke-linejoin: ",o,"; ","stroke-miterlimit: ",u,"; ","fill: ",e,"; ","fill-rule: ",t,"; ","opacity: ",a,";",l,f].join("")},getSvgTransform:function(){if(this.group&&this.group.type==="path-group")return"";var e=fabric.util.toFixed,t=this.getAngle(),n=!this.canvas||this.canvas.svgViewportTransformation?this.getViewportTransform():[1,0,0,1,0,0],r=fabric.util.transformPoint(this.getCenterPoint(),n),i=fabric.Object.NUM_FRACTION_DIGITS,s=this.type==="path-group"?"":"translate("+e(r.x,i)+" "+e(r.y,i)+")",o=t!==0?" rotate("+e(t,i)+")":"",u=this.scaleX===1&&this.scaleY===1&&n[0]===1&&n[3]===1?"":" scale("+e(this.scaleX*n[0],i)+" "+e(this.scaleY*n[3],i)+")",a=this.type==="path-group"?this.width*n[0]:0,f=this.flipX?" matrix(-1 0 0 1 "+a+" 0) ":"",l=this.type==="path-group"?this.height*n[3]:0,c=this.flipY?" matrix(1 0 0 -1 0 "+l+")":"";return[s,o,u,f,c].join("")},getSvgTransformMatrix:function(){return this.transformMatrix?" matrix("+this.transformMatrix.join(" ")+")":""},_createBaseSVGMarkup:function(){var e=[];return this.fill&&this.fill.toLive&&e.push(this.fill.toSVG(this,!1)),this.stroke&&this.stroke.toLive&&e.push(this.stroke.toSVG(this,!1)),this.shadow&&e.push(this.shadow.toSVG(this)),e}}),fabric.util.object.extend(fabric.Object.prototype,{hasStateChanged:function(){return this.stateProperties.some(function(e){return this.get(e)!==this.originalState[e]},this)},saveState:function(e){return this.stateProperties.forEach(function(e){this.originalState[e]=this.get(e)},this),e&&e.stateProperties&&e.stateProperties.forEach(function(e){this.originalState[e]=this.get(e)},this),this},setupState:function(){return this.originalState={},this.saveState(),this}}),function(){var e=fabric.util.degreesToRadians,t=function(){return typeof G_vmlCanvasManager!="undefined"};fabric.util.object.extend(fabric.Object.prototype,{_controlsVisibility:null,_findTargetCorner:function(e){if(!this.hasControls||!this.active)return!1;var t=e.x,n=e.y,r,i;for(var s in this.oCoords){if(!this.isControlVisible(s))continue;if(s==="mtr"&&!this.hasRotatingPoint)continue;if(!(!this.get("lockUniScaling")||s!=="mt"&&s!=="mr"&&s!=="mb"&&s!=="ml"))continue;i=this._getImageLines(this.oCoords[s].corner),r=this._findCrossPoints({x:t,y:n},i);if(r!==0&&r%2===1)return this.__corner=s,s}return!1},_setCornerCoords:function(){var t=this.oCoords,n=e(this.angle),r=e(45-this.angle),i=Math.sqrt(2*Math.pow(this.cornerSize,2))/2,s=i*Math.cos(r),o=i*Math.sin(r),u=Math.sin(n),a=Math.cos(n);t.tl.corner={tl:{x:t.tl.x-o,y:t.tl.y-s},tr:{x:t.tl.x+s,y:t.tl.y-o},bl:{x:t.tl.x-s,y:t.tl.y+o},br:{x:t.tl.x+o,y:t.tl.y+s}},t.tr.corner={tl:{x:t.tr.x-o,y:t.tr.y-s},tr:{x:t.tr.x+s,y:t.tr.y-o},br:{x:t.tr.x+o,y:t.tr.y+s},bl:{x:t.tr.x-s,y:t.tr.y+o}},t.bl.corner={tl:{x:t.bl.x-o,y:t.bl.y-s},bl:{x:t.bl.x-s,y:t.bl.y+o},br:{x:t.bl.x+o,y:t.bl.y+s},tr:{x:t.bl.x+s,y:t.bl.y-o}},t.br.corner={tr:{x:t.br.x+s,y:t.br.y-o},bl:{x:t.br.x-s,y:t.br.y+o},br:{x:t.br.x+o,y:t.br.y+s},tl:{x:t.br.x-o,y:t.br.y-s}},t.ml.corner={tl:{x:t.ml.x-o,y:t.ml.y-s},tr:{x:t.ml.x+s,y:t.ml.y-o},bl:{x:t.ml.x-s,y:t.ml.y+o},br:{x:t.ml.x+o,y:t.ml.y+s}},t.mt.corner={tl:{x:t.mt.x-o,y:t.mt.y-s},tr:{x:t.mt.x+s,y:t.mt.y-o},bl:{x:t.mt.x-s,y:t.mt.y+o},br:{x:t.mt.x+o,y:t.mt.y+s}},t.mr.corner={tl:{x:t.mr.x-o,y:t.mr.y-s},tr:{x:t.mr.x+s,y:t.mr.y-o},bl:{x:t.mr.x-s,y:t.mr.y+o},br:{x:t.mr.x+o,y:t.mr.y+s}},t.mb.corner={tl:{x:t.mb.x-o,y:t.mb.y-s},tr:{x:t.mb.x+s,y:t.mb.y-o},bl:{x:t.mb.x-s,y:t.mb.y+o},br:{x:t.mb.x+o,y:t.mb.y+s}},t.mtr.corner={tl:{x:t.mtr.x-o+u*this.rotatingPointOffset,y:t.mtr.y-s-a*this.rotatingPointOffset},tr:{x:t.mtr.x+s+u*this.rotatingPointOffset,y:t.mtr.y-o-a*this.rotatingPointOffset},bl:{x:t.mtr.x-s+u*this.rotatingPointOffset,y:t.mtr.y+o-a*this.rotatingPointOffset},br:{x:t.mtr.x+o+u*this.rotatingPointOffset,y:t.mtr.y+s-a*this.rotatingPointOffset}}},drawBorders:function(e){if(!this.hasBorders)return this;var t=this.padding,n=t*2,r=this.getViewportTransform();e.save(),e.globalAlpha=this.isMoving?this.borderOpacityWhenMoving:1,e.strokeStyle=this.borderColor;var i=1/this._constrainScale(this.scaleX),s=1/this._constrainScale(this.scaleY);e.lineWidth=1/this.borderScaleFactor;var o=this.getWidth(),u=this.getHeight(),a=this.strokeWidth>1?this.strokeWidth:0,f=this.strokeLineCap==="round"||this.strokeLineCap==="square",l=this.type==="line"&&this.width===1,c=this.type==="line"&&this.height===1,h=f&&c||this.type!=="line",p=f&&l||this.type!=="line";l?o=a/i:c&&(u=a/s),h&&(o+=a/i),p&&(u+=a/s);var d=fabric.util.transformPoint(new fabric.Point(o,u),r,!0),v=d.x,m=d.y;this.group&&(v*=this.group.scaleX,m*=this.group.scaleY),e.strokeRect(~~(-(v/2)-t)-.5,~~(-(m/2)-t)-.5,~~(v+n)+1,~~(m+n)+1);if(this.hasRotatingPoint&&this.isControlVisible("mtr")&&!this.get("lockRotation")&&this.hasControls){var g=(-m-t*2)/2;e.beginPath(),e.moveTo(0,g),e.lineTo(0,g-this.rotatingPointOffset),e.closePath(),e.stroke()}return e.restore(),this},drawControls:function(e){if(!this.hasControls)return this;var t=this.cornerSize,n=t/2,r=this.getViewportTransform(),i=this.strokeWidth>1?this.strokeWidth:0,s=this.width,o=this.height,u=this.strokeLineCap==="round"||this.strokeLineCap==="square",a=this.type==="line"&&this.width===1,f=this.type==="line"&&this.height===1,l=u&&f||this.type!=="line",c=u&&a||this.type!=="line";a?s=i:f&&(o=i),l&&(s+=i),c&&(o+=i),s*=this.scaleX,o*=this.scaleY;var h=fabric.util.transformPoint(new fabric.Point(s,o),r,!0),p=h.x,d=h.y,v=-(p/2),m=-(d/2),g=this.padding,y=n,b=n-t,w=this.transparentCorners?"strokeRect":"fillRect";return e.save(),e.lineWidth=1,e.globalAlpha=this.isMoving?this.borderOpacityWhenMoving:1,e.strokeStyle=e.fillStyle=this.cornerColor,this._drawControl("tl",e,w,v-y-g,m-y-g),this._drawControl("tr",e,w,v+p-y+g,m-y-g),this._drawControl("bl",e,w,v-y-g,m+d+b+g),this._drawControl("br",e,w,v+p+b+g,m+d+b+g),this.get("lockUniScaling")||(this._drawControl("mt",e,w,v+p/2-y,m-y-g),this._drawControl("mb",e,w,v+p/2-y,m+d+b+g),this._drawControl("mr",e,w,v+p+b+g,m+d/2-y),this._drawControl("ml",e,w,v-y-g,m+d/2-y)),this.hasRotatingPoint&&this._drawControl("mtr",e,w,v+p/2-y,m-this.rotatingPointOffset-this.cornerSize/2-g),e.restore(),this},_drawControl:function(e,n,r,i,s){var o=this.cornerSize;this.isControlVisible(e)&&(t()||this.transparentCorners||n.clearRect(i,s,o,o),n[r](i,s,o,o))},isControlVisible:function(e){return this._getControlsVisibility()[e]},setControlVisible:function(e,t){return this._getControlsVisibility()[e]=t,this},setControlsVisibility:function(e){e||(e={});for(var t in e)this.setControlVisible(t,e[t]);return this},_getControlsVisibility:function(){return this._controlsVisibility||(this._controlsVisibility={tl:!0,tr:!0,br:!0,bl:!0,ml:!0,mt:!0,mr:!0,mb:!0,mtr:!0}),this._controlsVisibility}})}(),fabric.util.object.extend(fabric.StaticCanvas.prototype,{FX_DURATION:500,fxCenterObjectH:function(e,t){t=t||{};var n=function(){},r=t.onComplete||n,i=t.onChange||n,s=this;return fabric.util.animate({startValue:e.get("left"),endValue:this.getCenter().left,duration:this.FX_DURATION,onChange:function(t){e.set("left",t),s.renderAll(),i()},onComplete:function(){e.setCoords(),r()}}),this},fxCenterObjectV:function(e,t){t=t||{};var n=function(){},r=t.onComplete||n,i=t.onChange||n,s=this;return fabric.util.animate({startValue:e.get("top"),endValue:this.getCenter().top,duration:this.FX_DURATION,onChange:function(t){e.set("top",t),s.renderAll(),i()},onComplete:function(){e.setCoords(),r()}}),this},fxRemove:function(e,t){t=t||{};var n=function(){},r=t.onComplete||n,i=t.onChange||n,s=this;return fabric.util.animate({startValue:e.get("opacity"),endValue:0,duration:this.FX_DURATION,onStart:function(){e.set("active",!1)},onChange:function(t){e.set("opacity",t),s.renderAll(),i()},onComplete:function(){s.remove(e),r()}}),this}}),fabric.util.object.extend(fabric.Object.prototype,{animate:function(){if(arguments[0]&&typeof arguments[0]=="object"){var e=[],t,n;for(t in arguments[0])e.push(t);for(var r=0,i=e.length;r\n'),e?e(t.join("")):t.join("")},complexity:function(){return 1}}),t.Line.ATTRIBUTE_NAMES=t.SHARED_ATTRIBUTES.concat("x1 y1 x2 y2".split(" ")),t.Line.fromElement=function(e,r){var i=t.parseAttributes(e,t.Line.ATTRIBUTE_NAMES),s=[i.x1||0,i.y1||0,i.x2||0,i.y2||0];return new t.Line(s,n(i,r))},t.Line.fromObject=function(e){var n=[e.x1,e.y1,e.x2,e.y2];return new t.Line(n,e)}}(typeof exports!="undefined"?exports:this),function(e){"use strict";function i(e){return"radius"in e&&e.radius>0}var t=e.fabric||(e.fabric={}),n=Math.PI,r=t.util.object.extend;if(t.Circle){t.warn("fabric.Circle is already defined.");return}t.Circle=t.util.createClass(t.Object,{type:"circle",radius:0,startAngle:0,endAngle:n*2,initialize:function(e){e=e||{},this.callSuper("initialize",e),this.set("radius",e.radius||0),this.startAngle=e.startAngle||this.startAngle,this.endAngle=e.endAngle||this.endAngle},_set:function(e,t){return this.callSuper("_set",e,t),e==="radius"&&this.setRadius(t),this},toObject:function(e){return r(this.callSuper("toObject",e),{radius:this.get("radius"),startAngle:this.startAngle,endAngle:this.endAngle})},toSVG:function(e){var t=this._createBaseSVGMarkup(),r=0,i=0,s=(this.endAngle-this.startAngle)%(2*n);if(s===0)this.group&&this.group.type==="path-group"&&(r=this.left+this.radius,i=this.top+this.radius),t.push("\n');else{var o=Math.cos(this.startAngle)*this.radius,u=Math.sin(this.startAngle)*this.radius,a=Math.cos(this.endAngle)*this.radius,f=Math.sin(this.endAngle)*this.radius,l=s>n?"1":"0";t.push('\n')}return e?e(t.join("")):t.join("")},_render:function(e,t){e.beginPath(),e.arc(t?this.left+this.radius:0,t?this.top+this.radius:0,this.radius,this.startAngle,this.endAngle,!1),this._renderFill(e),this._renderStroke(e)},getRadiusX:function(){return this.get("radius")*this.get("scaleX")},getRadiusY:function(){return this.get("radius")*this.get("scaleY")},setRadius:function(e){this.radius=e,this.set("width",e*2).set("height",e*2)},complexity:function(){return 1}}),t.Circle.ATTRIBUTE_NAMES=t.SHARED_ATTRIBUTES.concat("cx cy r".split(" ")),t.Circle.fromElement=function(e,n){n||(n={});var s=t.parseAttributes(e,t.Circle.ATTRIBUTE_NAMES);if(!i(s))throw new Error("value of `r` attribute is required and can not be negative");s.left=s.left||0,s.top=s.top||0;var o=new t.Circle(r(s,n));return o.left-=o.radius,o.top-=o.radius,o},t.Circle.fromObject=function(e){return new t.Circle(e)}}(typeof exports!="undefined"?exports:this),function(e){"use strict";var t=e.fabric||(e.fabric={});if(t.Triangle){t.warn("fabric.Triangle is already defined");return}t.Triangle=t.util.createClass(t.Object,{type:"triangle",initialize:function(e){e=e||{},this.callSuper("initialize",e),this.set("width",e.width||100).set("height",e.height||100)},_render:function(e){var t=this.width/2,n=this.height/2;e.beginPath(),e.moveTo(-t,n),e.lineTo(0,-n),e.lineTo(t,n),e.closePath(),this._renderFill(e),this._renderStroke(e)},_renderDashedStroke:function(e){var n=this.width/2,r=this.height/2;e.beginPath(),t.util.drawDashedLine(e,-n,r,0,-r,this.strokeDashArray),t.util.drawDashedLine(e,0,-r,n,r,this.strokeDashArray),t.util.drawDashedLine(e,n,r,-n,r,this.strokeDashArray),e.closePath()},toSVG:function(e){var t=this._createBaseSVGMarkup(),n=this.width/2,r=this.height/2,i=[-n+" "+r,"0 "+ -r,n+" "+r].join(",");return t.push("'),e?e(t.join("")):t.join("")},complexity:function(){return 1}}),t.Triangle.fromObject=function(e){return new t.Triangle(e)}}(typeof exports!="undefined"?exports:this),function(e){"use strict";var t=e.fabric||(e.fabric={}),n=Math.PI*2,r=t.util.object.extend;if(t.Ellipse){t.warn("fabric.Ellipse is already defined.");return}t.Ellipse=t.util.createClass(t.Object,{type:"ellipse",rx:0,ry:0,initialize:function(e){e=e||{},this.callSuper("initialize",e),this.set("rx",e.rx||0),this.set("ry",e.ry||0)},_set:function(e,t){this.callSuper("_set",e,t);switch(e){case"rx":this.rx=t,this.set("width",t*2);break;case"ry":this.ry=t,this.set("height",t*2)}return this},getRx:function(){return this.get("rx")*this.get("scaleX")},getRy:function(){return this.get("ry")*this.get("scaleY")},toObject:function(e){return r(this.callSuper("toObject",e),{rx:this.get("rx"),ry:this.get("ry")})},toSVG:function(e){var t=this._createBaseSVGMarkup(),n=0,r=0;return this.group&&this.group.type==="path-group"&&(n=this.left+this.rx,r=this.top+this.ry),t.push("\n'),e?e(t.join("")):t.join("")},_render:function(e,t){e.beginPath(),e.save(),e.transform(1,0,0,this.ry/this.rx,0,0),e.arc(t?this.left+this.rx:0,t?(this.top+this.ry)*this.rx/this.ry:0,this.rx,0,n,!1),e.restore(),this._renderFill(e),this._renderStroke(e)},complexity:function(){return 1}}),t.Ellipse.ATTRIBUTE_NAMES=t.SHARED_ATTRIBUTES.concat("cx cy rx ry".split(" ")),t.Ellipse.fromElement=function(e,n){n||(n={});var i=t.parseAttributes(e,t.Ellipse.ATTRIBUTE_NAMES);i.left=i.left||0,i.top=i.top||0;var s=new t.Ellipse(r(i,n));return s.top-=s.ry,s.left-=s.rx,s},t.Ellipse.fromObject=function(e){return new t.Ellipse(e)}}(typeof exports!="undefined"?exports:this),function(e){"use strict";var t=e.fabric||(e.fabric={}),n=t.util.object.extend;if(t.Rect){console.warn("fabric.Rect is already defined");return}var r=t.Object.prototype.stateProperties.concat();r.push("rx","ry","x","y"),t.Rect=t.util.createClass(t.Object,{stateProperties:r,type:"rect",rx:0,ry:0,strokeDashArray:null,initialize:function(e){e=e||{},this.callSuper("initialize",e),this._initRxRy()},_initRxRy:function(){this.rx&&!this.ry?this.ry=this.rx:this.ry&&!this.rx&&(this.rx=this.ry)},_render:function(e,t){if(this.width===1&&this.height===1){e.fillRect(0,0,1,1);return}var n=this.rx?Math.min(this.rx,this.width/2):0,r=this.ry?Math.min(this.ry,this.height/2):0,i=this.width,s=this.height,o=t?this.left:-this.width/2,u=t?this.top:-this.height/2,a=n!==0||r!==0,f=.4477152502;e.beginPath(),e.moveTo(o+n,u),e.lineTo(o+i-n,u),a&&e.bezierCurveTo(o+i-f*n,u,o+i,u+f*r,o+i,u+r),e.lineTo(o+i,u+s-r),a&&e.bezierCurveTo(o+i,u+s-f*r,o+i-f*n,u+s,o+i-n,u+s),e.lineTo(o+n,u+s),a&&e.bezierCurveTo(o+f*n,u+s,o,u+s-f*r,o,u+s-r),e.lineTo(o,u+r),a&&e.bezierCurveTo(o,u+f*r,o+f*n,u,o+n,u),e.closePath(),this._renderFill(e),this._renderStroke(e)},_renderDashedStroke:function(e){var n=-this.width/2,r=-this.height/2,i=this.width,s=this.height;e.beginPath(),t.util.drawDashedLine(e,n,r,n+i,r,this.strokeDashArray),t.util.drawDashedLine(e,n+i,r,n+i,r+s,this.strokeDashArray),t.util.drawDashedLine(e,n+i,r+s,n,r+s,this.strokeDashArray),t.util.drawDashedLine(e,n,r+s,n,r,this.strokeDashArray),e.closePath()},toObject:function(e){var t=n(this.callSuper("toObject",e),{rx:this.get("rx")||0,ry:this.get("ry")||0});return this.includeDefaultValues||this._removeDefaultValues(t),t},toSVG:function(e){var t=this._createBaseSVGMarkup(),n=this.left,r=this.top;if(!this.group||this.group.type!=="path-group")n=-this.width/2,r=-this.height/2;return t.push("\n'),e?e(t.join("")):t.join("")},complexity:function(){return 1}}),t.Rect.ATTRIBUTE_NAMES=t.SHARED_ATTRIBUTES.concat("x y rx ry width height".split(" ")),t.Rect.fromElement=function(e,r){if(!e)return null;r=r||{};var i=t.parseAttributes(e,t.Rect.ATTRIBUTE_NAMES);return i.left=i.left||0,i.top=i.top||0,new t.Rect(n(r?t.util.object.clone(r):{},i))},t.Rect.fromObject=function(e){return new t.Rect(e)}}(typeof exports!="undefined"?exports:this),function(e){"use strict";var t=e.fabric||(e.fabric={});if(t.Polyline){t.warn("fabric.Polyline is already defined");return}t.Polyline=t.util.createClass(t.Object,{type:"polyline",points:null,minX:0,minY:0,initialize:function(e,n){return t.Polygon.prototype.initialize.call(this,e,n)},_calcDimensions:function(){return t.Polygon.prototype._calcDimensions.call(this)},_applyPointOffset:function(){return t.Polygon.prototype._applyPointOffset.call(this)},toObject:function(e){return t.Polygon.prototype.toObject.call(this,e)},toSVG:function(e){return t.Polygon.prototype.toSVG.call(this,e)},_render:function(e){t.Polygon.prototype.commonRender.call(this,e),this._renderFill(e),this._renderStroke(e)},_renderDashedStroke:function(e){var n,r;e.beginPath();for(var i=0,s=this.points.length;i\n'),e?e(n.join("")):n.join("")},_render:function(e){this.commonRender(e),this._renderFill(e);if(this.stroke||this.strokeDashArray)e.closePath(),this._renderStroke(e)},commonRender:function(e){var t;e.beginPath(),this._applyPointOffset&&((!this.group||this.group.type!=="path-group")&&this._applyPointOffset(),this._applyPointOffset=null),e.moveTo(this.points[0].x,this.points[0].y);for(var n=0,r=this.points.length;n"},toObject:function(e){var t=i(this.callSuper("toObject",e),{path:this.path.map(function(e){return e.slice()}),pathOffset:this.pathOffset});return this.sourcePath&&(t.sourcePath=this.sourcePath),this.transformMatrix&&(t.transformMatrix=this.transformMatrix),t},toDatalessObject:function(e){var t=this.toObject(e);return this.sourcePath&&(t.path=this.sourcePath),delete t.sourcePath,t},toSVG:function(e){var t=[],n=this._createBaseSVGMarkup(),r="";for(var i=0,s=this.path.length;i\n"),e?e(n.join("")):n.join("")},complexity:function(){return this.path.length},_parsePath:function(){var e=[],t=[],n,r,i=/([-+]?((\d+\.\d+)|((\d+)|(\.\d+)))(?:e[-+]?\d+)?)/ig,s,o;for(var f=0,l,c=this.path.length;fv)for(var g=1,y=l.length;g\n"];for(var i=0,s=t.length;i\n"),e?e(r.join("")):r.join("")},toString:function(){return"#"},isSameColor:function(){var e=(this.getObjects()[0].get("fill")||"").toLowerCase();return this.getObjects().every(function(t){return(t.get("fill")||"").toLowerCase()===e})},complexity:function(){return this.paths.reduce(function(e,t){return e+(t&&t.complexity?t.complexity():0)},0)},getObjects:function(){return this.paths}}),t.PathGroup.fromObject=function(e,n){typeof e.paths=="string"?t.loadSVGFromURL(e.paths,function(r){var i=e.paths;delete e.paths;var s=t.util.groupSVGElements(r,e,i);n(s)}):t.util.enlivenObjects(e.paths,function(r){delete e.paths,n(new t.PathGroup(r,e))})},t.PathGroup.async=!0}(typeof exports!="undefined"?exports:this),function(e){"use strict";var t=e.fabric||(e.fabric={}),n=t.util.object.extend,r=t.util.array.min,i=t.util.array.max,s=t.util.array.invoke;if(t.Group)return;var o={lockMovementX:!0,lockMovementY:!0,lockRotation:!0,lockScalingX:!0,lockScalingY:!0,lockUniScaling:!0};t.Group=t.util.createClass(t.Object,t.Collection,{type:"group",initialize:function(e,t){t=t||{},this._objects=e||[];for(var r=this._objects.length;r--;)this._objects[r].group=this;this.originalState={},this.callSuper("initialize"),this._calcBounds(),this._updateObjectsCoords(),t&&n(this,t),this.setCoords(),this.saveCoords()},_updateObjectsCoords:function(){this.forEachObject(this._updateObjectCoords,this)},_updateObjectCoords:function(e){var t=e.getLeft(),n=e.getTop();e.set({originalLeft:t,originalTop:n,left:t-this.left,top:n-this.top}),e.setCoords(),e.__origHasControls=e.hasControls,e.hasControls=!1},toString:function(){return"#"},addWithUpdate:function(e){return this._restoreObjectsState(),e&&(this._objects.push(e),e.group=this),this.forEachObject(this._setObjectActive,this),this._calcBounds(),this._updateObjectsCoords(),this},_setObjectActive:function(e){e.set("active",!0),e.group=this},removeWithUpdate:function(e){return this._moveFlippedObject(e),this._restoreObjectsState(),this.forEachObject(this._setObjectActive,this),this.remove(e),this._calcBounds(),this._updateObjectsCoords(),this},_onObjectAdded:function(e){e.group=this},_onObjectRemoved:function(e){delete e.group,e.set("active",!1)},delegatedProperties:{fill:!0,opacity:!0,fontFamily:!0,fontWeight:!0,fontSize:!0,fontStyle:!0,lineHeight:!0,textDecoration:!0,textAlign:!0,backgroundColor:!0},_set:function(e,t){if(e in this.delegatedProperties){var n=this._objects.length;this[e]=t;while(n--)this._objects[n].set(e,t)}else this[e]=t},toObject:function(e){return n(this.callSuper("toObject",e),{objects:s(this._objects,"toObject",e)})},render:function(e){if(!this.visible)return;e.save(),this.clipTo&&t.util.clipContext(this,e);for(var n=0,r=this._objects.length;n\n'];for(var n=0,r=this._objects.length;n\n"),e?e(t.join("")):t.join("")},get:function(e){if(e in o){if(this[e])return this[e];for(var t=0,n=this._objects.length;t\n','\n");if(this.stroke||this.strokeDashArray){var i=this.fill;this.fill=null,t.push("\n'),this.fill=i}return t.push("\n"),e?e(t.join("")):t.join("")},getSrc:function(){if(this.getElement())return this.getElement().src||this.getElement()._src},toString:function(){return'#'},clone:function(e,t){this.constructor.fromObject(this.toObject(t),e)},applyFilters:function(e){if(!this._originalElement)return;if(this.filters.length===0){this._element=this._originalElement,e&&e();return}var t=this._originalElement,n=fabric.util.createCanvasElement(),r=fabric.util.createImage(),i=this;return n.width=t.width,n.height=t.height,n.getContext("2d").drawImage(t,0,0,t.width,t.height),this.filters.forEach(function(e){e&&e.applyTo(n)}),r.width=t.width,r.height=t.height,fabric.isLikelyNode?(r.src=n.toBuffer(undefined,fabric.Image.pngCompression),i._element=r,e&&e()):(r.onload=function(){i._element=r,e&&e(),r.onload=n=t=null},r.src=n.toDataURL("image/png")),this},_render:function(e,t){this._element&&e.drawImage(this._element,t?this.left:-this.width/2,t?this.top:-this.height/2,this.width,this.height),this._renderStroke(e)},_resetWidthHeight:function(){var e=this.getElement();this.set("width",e.width),this.set("height",e.height)},_initElement:function(e){this.setElement(fabric.util.getById(e)),fabric.util.addClass(this.getElement(),fabric.Image.CSS_CANVAS)},_initConfig:function(e){e||(e={}),this.setOptions(e),this._setWidthHeight(e),this._element&&this.crossOrigin&&(this._element.crossOrigin=this.crossOrigin)},_initFilters:function(e,t){e.filters&&e.filters.length?fabric.util.enlivenObjects(e.filters,function(e){t&&t(e)},"fabric.Image.filters"):t&&t()},_setWidthHeight:function(e){this.width="width"in e?e.width:this.getElement()?this.getElement().width||0:0,this.height="height"in e?e.height:this.getElement()?this.getElement().height||0:0},complexity:function(){return 1}}),fabric.Image.CSS_CANVAS="canvas-img",fabric.Image.prototype.getSvgSrc=fabric.Image.prototype.getSrc,fabric.Image.fromObject=function(e,t){fabric.util.loadImage(e.src,function(n){fabric.Image.prototype._initFilters.call(e,e,function(r){e.filters=r||[];var i=new fabric.Image(n,e);t&&t(i)})},null,e.crossOrigin)},fabric.Image.fromURL=function(e,t,n){fabric.util.loadImage(e,function(e){t(new fabric.Image(e,n))},null,n&&n.crossOrigin)},fabric.Image.ATTRIBUTE_NAMES=fabric.SHARED_ATTRIBUTES.concat("x y width height xlink:href".split(" ")),fabric.Image.fromElement=function(e,n,r){var i=fabric.parseAttributes(e,fabric.Image.ATTRIBUTE_NAMES);fabric.Image.fromURL(i["xlink:href"],n,t(r?fabric.util.object.clone(r):{},i))},fabric.Image.async=!0,fabric.Image.pngCompression=1}(typeof exports!="undefined"?exports:this),fabric.util.object.extend(fabric.Object.prototype,{_getAngleValueForStraighten:function(){var e=this.getAngle()%360;return e>0?Math.round((e-1)/90)*90:Math.round(e/90)*90},straighten:function(){return this.setAngle(this._getAngleValueForStraighten()),this},fxStraighten:function(e){e=e||{};var t=function(){},n=e.onComplete||t,r=e.onChange||t,i=this;return fabric.util.animate({startValue:this.get("angle"),endValue:this._getAngleValueForStraighten(),duration:this.FX_DURATION,onChange:function(e){i.setAngle(e),r()},onComplete:function(){i.setCoords(),n()},onStart:function(){i.set("active",!1)}}),this}}),fabric.util.object.extend(fabric.StaticCanvas.prototype,{straightenObject:function(e){return e.straighten(),this.renderAll(),this},fxStraightenObject:function(e){return e.fxStraighten({onChange:this.renderAll.bind(this)}),this}}),fabric.Image.filters=fabric.Image.filters||{},fabric.Image.filters.BaseFilter=fabric.util.createClass({type:"BaseFilter",toObject:function(){return{type:this.type}},toJSON:function(){return this.toObject()}}),function(e){"use strict";var t=e.fabric||(e.fabric={}),n=t.util.object.extend;t.Image.filters.Brightness=t.util.createClass(t.Image.filters.BaseFilter,{type:"Brightness",initialize:function(e){e=e||{},this.brightness=e.brightness||0},applyTo:function(e){var t=e.getContext("2d"),n=t.getImageData(0,0,e.width,e.height),r=n.data,i=this.brightness;for(var s=0,o=r.length ;sa||C<0||C>u)continue;var k=(N*u+C)*4,L=t[x*i+T];b+=o[k]*L,w+=o[k+1]*L,E+=o[k+2]*L,S+=o[k+3]*L}h[y]=b,h[y+1]=w,h[y+2]=E,h[y+3]=S+p*(255-S)}n.putImageData(c,0,0)},toObject:function(){return n(this.callSuper("toObject"),{opaque:this.opaque,matrix:this.matrix})}}),t.Image.filters.Convolute.fromObject=function(e){return new t.Image.filters.Convolute(e)}}(typeof exports!="undefined"?exports:this),function(e){"use strict";var t=e.fabric||(e.fabric={}),n=t.util.object.extend;t.Image.filters.GradientTransparency=t.util.createClass(t.Image.filters.BaseFilter,{type:"GradientTransparency",initialize:function(e){e=e||{},this.threshold=e.threshold||100},applyTo:function(e){var t=e.getContext("2d"),n=t.getImageData(0,0,e.width,e.height),r=n.data,i=this.threshold,s=r.length;for(var o=0,u=r.length;o-1?e.channel:0},applyTo:function(e){if(!this.mask)return;var n=e.getContext("2d"),r=n.getImageData(0,0,e.width,e.height),i=r.data,s=this.mask.getElement(),o=t.util.createCanvasElement(),u=this.channel,a,f=r.width*r.height*4;o.width=s.width,o.height=s.height,o.getContext("2d").drawImage(s,0,0,s.width,s.height);var l=o.getContext("2d").getImageData(0,0,s.width,s.height),c=l.data;for(a=0;ao&&f>o&&l>o&&u(a-f)'},_render:function(e){typeof Cufon=="undefined"||this.useNative===!0?this._renderViaNative(e):this._renderViaCufon(e)},_renderViaNative:function(e){var n=this.text.split(this._reNewline);this._setTextStyles(e),this.width=this._getTextWidth(e,n),this.height=this._getTextHeight(e,n),this.clipTo&&t.util.clipContext(this,e),this._renderTextBackground(e,n),this._translateForTextAlign(e),this._renderText(e,n),this.textAlign!=="left"&&this.textAlign!=="justify"&&e.restore(),this._renderTextDecoration(e,n),this.clipTo&&e.restore(),this._setBoundaries(e,n),this._totalLineHeight=0},_renderText:function(e,t){e.save(),this._setShadow(e),this._setupCompositeOperation(e),this._renderTextFill(e,t),this._renderTextStroke(e,t),this._restoreCompositeOperation(e),this._removeShadow(e),e.restore()},_translateForTextAlign:function(e){this.textAlign!=="left"&&this.textAlign!=="justify"&&(e.save(),e.translate(this.textAlign==="center"?this.width/2:this.width,0))},_setBoundaries:function(e,t){this._boundaries=[];for(var n=0,r=t.length;nn&&(n=s)}return n},_renderChars:function(e,t,n,r,i){t[e](n,r,i)},_renderTextLine:function(e,t,n,r,i,s){i-=this.fontSize/4;if(this.textAlign!=="justify"){this._renderChars(e,t,n,r,i,s);return}var o=t.measureText(n).width,u=this.width;if(u>o){var a=n.split(/\s+/),f=t.measureText(n.replace(/\s+/g,"")).width,l=u-f,c=a.length-1,h=l/c,p=0;for(var d=0,v=a.length;d-1&&i(this.fontSize*this.lineHeight),this.textDecoration.indexOf("line-through")>-1&&i(this.fontSize*this.lineHeight-this.fontSize/2),this.textDecoration.indexOf("overline")>-1&&i(this.fontSize*this.lineHeight-this.fontSize)},_getFontDeclaration:function(){return[t.isLikelyNode?this.fontWeight:this.fontStyle,t.isLikelyNode?this.fontStyle:this.fontWeight,this.fontSize+"px",t.isLikelyNode?'"'+this.fontFamily+'"':this.fontFamily].join(" ")},render:function(e,t){if(!this.visible)return;e.save(),t||this.transform(e);var n=this.group&&this.group.type==="path-group";n&&e.translate(-this.group.width/2,-this.group.height/2),this.transformMatrix&&e.transform.apply(e,this.transformMatrix),n&&e.translate(this.left,this.top),this._render(e),e.restore()},toObject:function(e){var t=n(this.callSuper("toObject",e),{text:this.text,fontSize:this.fontSize,fontWeight:this.fontWeight,fontFamily:this.fontFamily,fontStyle:this.fontStyle,lineHeight:this.lineHeight,textDecoration:this.textDecoration,textAlign:this.textAlign,path:this.path,textBackgroundColor:this.textBackgroundColor,useNative:this.useNative});return this.includeDefaultValues||this._removeDefaultValues(t),t},toSVG:function(e){var t=[],n=this.text.split(this._reNewline),r=this._getSVGLeftTopOffsets(n),i=this._getSVGTextAndBg(r.lineTop,r.textLeft,n),s=this._getSVGShadows(r.lineTop,n);return r.textTop+=this._fontAscent?this._fontAscent/5*this.lineHeight:0,this._wrapSVGTextAndBg(t,i,s,r),e?e(t.join("")):t.join("")},_getSVGLeftTopOffsets:function(e){var t=this.useNative?this.fontSize*this.lineHeight:-this._fontAscent-this._fontAscent/5*this.lineHeight,n=-(this.width/2),r=this.useNative?this.fontSize-1:this.height/2-e.length*this.fontSize-this._totalLineHeight;return{textLeft:n+(this.group&&this.group.type==="path-group"?this.left:0),textTop:r+(this.group&&this.group.type==="path-group"?this.top:0),lineTop:t}},_wrapSVGTextAndBg:function(e,t,n,r){e.push('\n',t.textBgRects.join(""),"',n.join(""),t.textSpans.join(""),"\n","\n")},_getSVGShadows:function(e,n){var r=[],s,o,u=1;if(!this.shadow||!this._boundaries)return r;for(s=0,o=n.length;s",t.util.string.escapeXml(n[s]),""),u=1}else u++;return r},_getSVGTextAndBg:function(e,t,n){var r=[],i=[],s=1;this._setSVGBg(i);for(var o=0,u=n.length;o",t.util.string.escapeXml(e),"")},_setSVGTextLineBg:function(e,t,n,r){e.push("\n')},_setSVGBg:function(e){this.backgroundColor&&this._boundaries&&e.push("')},_getFillAttributes:function(e){var n=e&&typeof e=="string"?new t.Color(e):"";return!n||!n.getSource()||n.getAlpha()===1?'fill="'+e+'"':'opacity="'+n.getAlpha()+'" fill="'+n.setAlpha(1).toRgb()+'"'},_set:function(e,t){e==="fontFamily"&&this.path&&(this.path=this.path.replace(/(.*?)([^\/]*)(\.font\.js)/,"$1"+t+"$3")),this.callSuper("_set",e,t),e in this._dimensionAffectingProps&&(this._initDimensions(),this.setCoords())},complexity:function(){return 1}}),t.Text.ATTRIBUTE_NAMES=t.SHARED_ATTRIBUTES.concat("x y dx dy font-family font-style font-weight font-size text-decoration text-anchor".split(" ")),t.Text.DEFAULT_SVG_FONT_SIZE=16,t.Text.fromElement=function(e,n){if(!e)return null;var r=t.parseAttributes(e,t.Text.ATTRIBUTE_NAMES);n=t.util.object.extend(n?t.util.object.clone(n):{},r),"dx"in r&&(n.left+=r.dx),"dy"in r&&(n.top+=r.dy),"fontSize"in n||(n.fontSize=t.Text.DEFAULT_SVG_FONT_SIZE),n.originX||(n.originX="left");var i=new t.Text(e.textContent,n),s=0;return i.originX==="left"&&(s=i.getWidth()/2),i.originX==="right"&&(s=-i.getWidth()/2),i.set({left:i.getLeft()+s,top:i.getTop()-i.getHeight()/2}),i},t.Text.fromObject=function(e){return new t.Text(e.text,r(e))},t.util.createAccessors(t.Text)}(typeof exports!="undefined"?exports:this),function(){var e=fabric.util.object.clone;fabric.IText=fabric.util.createClass(fabric.Text,fabric.Observable,{type:"i-text",selectionStart:0,selectionEnd:0,selectionColor:"rgba(17,119,255,0.3)",isEditing:!1,editable:!0,editingBorderColor:"rgba(102,153,255,0.25)",cursorWidth:2,cursorColor:"#333",cursorDelay:1e3,cursorDuration:600,styles:null,caching:!0,_skipFillStrokeCheck:!0,_reSpace:/\s|\n/,_fontSizeFraction:4,_currentCursorOpacity:0,_selectionDirection:null,_abortCursorAnimation:!1,_charWidthsCache:{},initialize:function(e,t){this.styles=t?t.styles||{}:{},this.callSuper("initialize",e,t),this.initBehavior(),fabric.IText.instances.push(this),this.__lineWidths={},this.__lineHeights={},this.__lineOffsets={}},isEmptyStyles:function(){if(!this.styles)return!0;var e=this.styles;for(var t in e)for(var n in e[t])for(var r in e[t][n])return!1;return!0},setSelectionStart:function(e){this.selectionStart!==e&&(this.fire("selection:changed"),this.canvas&&this.canvas.fire("text:selection:changed",{target:this})),this.selectionStart=e,this.hiddenTextarea&&(this.hiddenTextarea.selectionStart=e)},setSelectionEnd:function(e){this.selectionEnd!==e&&(this.fire("selection:changed"),this.canvas&&this.canvas.fire("text:selection:changed",{target:this})),this.selectionEnd=e,this.hiddenTextarea&&(this.hiddenTextarea.selectionEnd=e)},getSelectionStyles:function(e,t){if(arguments.length===2){var n=[];for(var r=e;r=r.charIndex&&(a!==o||hs&&a-1&&this._renderCharDecorationAtOffset(e,n,r+this.fontSize/this._fontSizeFraction,i,0,this.fontSize/20),u.indexOf("line-through")>-1&&this._renderCharDecorationAtOffset(e,n,r+this.fontSize/this._fontSizeFraction,i,o/2,a/20),u.indexOf("overline")>-1&&this._renderCharDecorationAtOffset(e,n,r,i,s-this.fontSize/this._fontSizeFraction,this.fontSize/20)},_renderCharDecorationAtOffset:function(e,t,n,r,i,s){e.fillRect(t,n-i,r,s)},_renderTextLine:function(e,t,n,r,i,s){i+=this.fontSize/4,this.callSuper("_renderTextLine",e,t,n,r,i,s)},_renderTextDecoration:function(e,t){if(this.isEmptyStyles())return this.callSuper("_renderTextDecoration",e,t)},_renderTextLinesBackground:function(e,t){if(!this.textBackgroundColor&&!this.styles)return;e.save(),this.textBackgroundColor&&(e.fillStyle=this.textBackgroundColor);var n=0,r=this.fontSize/this._fontSizeFraction;for(var i=0,s=t.length;in&&(n=s)}return n},_getHeightOfLine:function(e,t,n){n=n||this.text.split(this._reNewline);var r=this._getHeightOfChar(e,n[t][0],t,0),i=n[t],s=i.split("");for(var o=1,u=s.length;or&&(r=a)}return r*this.lineHeight},_getTextHeight:function(e,t){var n=0;for(var r=0,i=t.length;r-1)t++,n--;return e-t},findWordBoundaryRight:function(e){var t=0,n=e;if(this._reSpace.test(this.text.charAt(n)))while(this._reSpace.test(this.text.charAt(n)))t++,n++;while(/\S/.test(this.text.charAt(n))&&n-1)t++,n--;return e-t},findLineBoundaryRight:function(e){var t=0,n=e;while(!/\n/.test(this.text.charAt(n))&&n0&&nr;s?this.removeStyleObject(s,n+1):this.removeStyleObject(this.get2DCursorLocation(n).charIndex===0,n)}this.text=this.text.slice(0,e)+this.text.slice(t)},insertChars:function(e){var t=this.text.slice(this.selectionStart,this.selectionStart+1)==="\n";this.text=this.text.slice(0,this.selectionStart)+e+this.text.slice(this.selectionEnd),this.selectionStart===this.selectionEnd&&this.insertStyleObjects(e,t,this.copiedStyles),this.selectionStart+=e.length,this.selectionEnd=this.selectionStart,this.canvas&&this.canvas.renderAll().renderAll(),this.setCoords(),this.fire("changed"),this.canvas&&this.canvas.fire("text:changed",{target:this})},insertNewlineStyleObject:function(t,n,r){this.shiftLineStyles(t,1),this.styles[t+1]||(this.styles[t+1]={});var i=this.styles[t][n-1],s={};if(r)s[0]=e(i),this.styles[t+1]=s;else{for(var o in this.styles[t])parseInt(o,10)>=n&&(s[parseInt(o,10)-n]=this.styles[t][o],delete this.styles[t][o]);this.styles[t+1]=s}},insertCharStyleObject:function(t,n,r){var i=this.styles[t],s=e(i);n===0&&!r&&(n=1);for(var o in s){var u=parseInt(o,10);u>=n&&(i[u+1]=s[u])}this.styles[t][n]=r||e(i[n-1])},insertStyleObjects:function(e,t,n){if(this.isEmptyStyles())return;var r=this.get2DCursorLocation(),i=r.lineIndex,s=r.charIndex;this.styles[i]||(this.styles[i]={}),e==="\n"?this.insertNewlineStyleObject(i,s,t):n?this._insertStyles(n):this.insertCharStyleObject(i,s)},_insertStyles:function(e){for(var t=0,n=e.length;tt&&(this.styles[s+n]=r[s])}},removeStyleObject:function(t,n){var r=this.get2DCursorLocation(n),i=r.lineIndex,s=r.charIndex;if(t){var o=this.text.split(this._reNewline),u=o[i-1],a=u?u.length:0;this.styles[i-1]||(this.styles[i-1]={});for(s in this.styles[i])this.styles[i-1][parseInt(s,10)+a]=this.styles[i][s];this.shiftLineStyles(i,-1)}else{var f=this.styles[i];if(f){var l=this.selectionStart===this.selectionEnd?-1:0;delete f[s+l]}var c=e(f);for(var h in c){var p=parseInt(h,10);p>=s&&p!==0&&(f[p-1]=c[p],delete f[p])}}},insertNewline:function(){this.insertChars("\n")}})}(),fabric.util.object.extend(fabric.IText.prototype,{initDoubleClickSimulation:function(){this.__lastClickTime=+(new Date),this.__lastLastClickTime=+(new Date),this.__lastPointer={},this.on("mousedown",this.onMouseDown.bind(this))},onMouseDown:function(e){this.__newClickTime=+(new Date);var t=this.canvas.getPointer(e.e);this.isTripleClick(t)?(this.fire("tripleclick",e),this._stopEvent(e.e)):this.isDoubleClick(t)&&(this.fire("dblclick",e),this._stopEvent(e.e)),this.__lastLastClickTime=this.__lastClickTime,this.__lastClickTime=this.__newClickTime,this.__lastPointer=t,this.__lastIsEditing=this.isEditing,this.__lastSelected=this.selected},isDoubleClick:function(e){return this.__newClickTime-this.__lastClickTime<500&&this.__lastPointer.x===e.x&&this.__lastPointer.y===e.y&&this.__lastIsEditing},isTripleClick:function(e){return this.__newClickTime-this.__lastClickTime<500&&this.__lastClickTime-this.__lastLastClickTime<500&&this.__lastPointer.x===e.x&&this.__lastPointer.y===e.y},_stopEvent:function(e){e.preventDefault&&e.preventDefault(),e.stopPropagation&&e.stopPropagation()},initCursorSelectionHandlers:function(){this.initSelectedHandler(),this.initMousedownHandler(),this.initMousemoveHandler(),this.initMouseupHandler(),this.initClicks()},initClicks:function(){this.on("dblclick",function(e){this.selectWord(this.getSelectionStartFromPointer(e.e))}),this.on("tripleclick",function(e){this.selectLine(this.getSelectionStartFromPointer(e.e))})},initMousedownHandler:function(){this.on("mousedown",function(e){var t=this.canvas.getPointer(e.e);this.__mousedownX=t.x,this.__mousedownY=t.y,this.__isMousedown=!0,this.hiddenTextarea&&this.canvas&&this.canvas.wrapperEl.appendChild(this.hiddenTextarea),this.selected&&this.setCursorByClick(e.e),this.isEditing&&(this.__selectionStartOnMouseDown=this.selectionStart,this.initDelayedCursor(!0))})},initMousemoveHandler:function(){this.on("mousemove",function(e){if(!this.__isMousedown||!this.isEditing)return;var t=this.getSelectionStartFromPointer(e.e);t>=this.__selectionStartOnMouseDown?(this.setSelectionStart(this.__selectionStartOnMouseDown),this.setSelectionEnd(t)):(this.setSelectionStart(t),this.setSelectionEnd(this.__selectionStartOnMouseDown))})},_isObjectMoved:function(e){var t=this.canvas.getPointer(e);return this.__mousedownX!==t.x||this.__mousedownY!==t.y},initMouseupHandler:function(){this.on("mouseup",function(e){this.__isMousedown=!1;if(this._isObjectMoved(e.e))return;this.__lastSelected&&(this.enterEditing(),this.initDelayedCursor(!0)),this.selected=!0})},setCursorByClick:function(e){var t=this.getSelectionStartFromPointer(e);e.shiftKey?ts?0:1,a=r+u;return this.flipX&&(a=i-a),a>this.text.length&&(a=this.text.length),a}}),fabric.util.object.extend(fabric.IText.prototype,{initHiddenTextarea:function(){this.hiddenTextarea=fabric.document.createElement("textarea"),this.hiddenTextarea.setAttribute("autocapitalize","off"),this.hiddenTextarea.style.cssText="position: absolute; top: 0; left: -9999px",fabric.document.body.appendChild(this.hiddenTextarea),fabric.util.addListener(this.hiddenTextarea,"keydown",this.onKeyDown.bind(this)),fabric.util.addListener(this.hiddenTextarea,"keypress",this.onKeyPress.bind(this)),fabric.util.addListener(this.hiddenTextarea,"copy",this.copy.bind(this)),fabric.util.addListener(this.hiddenTextarea,"paste",this.paste.bind(this)),!this._clickHandlerInitialized&&this.canvas&&(fabric.util.addListener(this.canvas.upperCanvasEl,"click",this.onClick.bind(this)),this._clickHandlerInitialized=!0)},_keysMap:{8:"removeChars",9:"exitEditing",27:"exitEditing",13:"insertNewline",33:"moveCursorUp",34:"moveCursorDown",35:"moveCursorRight",36:"moveCursorLeft",37:"moveCursorLeft",38:"moveCursorUp",39:"moveCursorRight",40:"moveCursorDown",46:"forwardDelete"},_ctrlKeysMap:{65:"selectAll",88:"cut"},onClick:function(){this.hiddenTextarea&&this.hiddenTextarea.focus()},onKeyDown:function(e){if(!this.isEditing)return;if(e.keyCode in this._keysMap)this[this._keysMap[e.keyCode]](e);else{if(!(e.keyCode in this._ctrlKeysMap&&(e.ctrlKey||e.metaKey)))return;this[this._ctrlKeysMap[e.keyCode]](e)}e.stopImmediatePropagation(),e.preventDefault(),this.canvas&&this.canvas.renderAll()},forwardDelete:function(e){this.selectionStart===this.selectionEnd&&this.moveCursorRight(e),this.removeChars(e)},copy:function(e){var t=this.getSelectedText(),n=this._getClipboardData(e);n&&n.setData("text",t),this.copiedText=t,this.copiedStyles=this.getSelectionStyles(this.selectionStart,this.selectionEnd)},paste:function(e){var t=null,n=this._getClipboardData(e);n?t=n.getData("text"):t=this.copiedText,t&&this.insertChars(t)},cut:function(e){if(this.selectionStart===this.selectionEnd)return;this.copy(),this.removeChars(e)},_getClipboardData:function(e){return e&&(e.clipboardData||fabric.window.clipboardData)},onKeyPress:function(e){if(!this.isEditing||e.metaKey||e.ctrlKey)return;e.which!==0&&this.insertChars(String.fromCharCode(e.which)),e.stopPropagation()},getDownCursorOffset:function(e,t){var n=t?this.selectionEnd:this.selectionStart,r=this.text.split(this._reNewline),i,s,o=this.text.slice(0,n),u=this.text.slice(n),a=o.slice(o.lastIndexOf("\n")+1),f=u.match(/(.*)\n?/)[1],l=(u.match(/.*\n(.*)\n?/)||{})[1]||"",c=this.get2DCursorLocation(n);if(c.lineIndex===r.length-1||e.metaKey||e.keyCode===34)return this.text.length-n;var h=this._getWidthOfLine(this.ctx,c.lineIndex,r);s=this._getLineLeftOffset(h);var p=s,d=c.lineIndex;for(var v=0,m=a.length;vn){f=!0;var d=u-p,v=u,m=Math.abs(d-n),g=Math.abs(v-n);a=gthis.text.length&&(this.selectionStart=this.text.length),this.selectionEnd=this.selectionStart},swapSelectionPoints:function(){var e=this.selectionEnd;this.selectionEnd=this.selectionStart,this.selectionStart=e},moveCursorDownWithShift:function(e){this.selectionEnd===this.selectionStart&&(this._selectionDirection="right");var t=this._selectionDirection==="right"?"selectionEnd":"selectionStart";this[t]+=e,this.selectionEndthis.text.length&&(this.selectionEnd=this.text.length)},getUpCursorOffset:function(e,t){var n=t?this.selectionEnd:this.selectionStart,r=this.get2DCursorLocation(n);if(r.lineIndex===0||e.metaKey||e.keyCode===33)return n;var i=this.text.slice(0,n),s=i.slice(i.lastIndexOf("\n")+1),o=(i.match(/\n?(.*)\n.*$/)||{})[1]||"",u=this.text.split(this._reNewline),a,f=this._getWidthOfLine(this.ctx,r.lineIndex,u),l=this._getLineLeftOffset(f),c=l,h=r.lineIndex;for(var p=0,d=s.length;pn){f=!0;var d=u-p,v=u,m=Math.abs(d-n),g=Math.abs(v-n);a=g=this.text.length&&this.selectionEnd>=this.text.length)return;this.abortCursorAnimation(),this._currentCursorOpacity=1,e.shiftKey?this.moveCursorRightWithShift(e):this.moveCursorRightWithoutShift(e),this.initDelayedCursor()},moveCursorRightWithShift:function(e){this._selectionDirection==="left"&&this.selectionStart!==this.selectionEnd?this._moveRight(e,"selectionStart"):(this._selectionDirection="right",this._moveRight(e,"selectionEnd"),this.text.charAt(this.selectionEnd-1)==="\n"&&this.selectionEnd++,this.selectionEnd>this.text.length&&(this.selectionEnd=this.text.length))},moveCursorRightWithoutShift:function(e){this._selectionDirection="right",this.selectionStart===this.selectionEnd?(this._moveRight(e,"selectionStart"),this.selectionEnd=this.selectionStart):(this.selectionEnd+=this.getNumNewLinesInSelectedText(),this.selectionEnd>this.text.length&&(this.selectionEnd=this.text.length),this.selectionStart=this.selectionEnd)},removeChars:function(e){this.selectionStart===this.selectionEnd?this._removeCharsNearCursor(e):this._removeCharsFromTo(this.selectionStart,this.selectionEnd),this.selectionEnd=this.selectionStart,this._removeExtraneousStyles(),this.canvas&&this.canvas.renderAll().renderAll(),this.setCoords(),this.fire("changed"),this.canvas&&this.canvas.fire("text:changed",{target:this})},_removeCharsNearCursor:function(e){if(this.selectionStart!==0)if(e.metaKey){var t=this.findLineBoundaryLeft(this.selectionStart);this._removeCharsFromTo(t,this.selectionStart),this.selectionStart=t}else if(e.altKey){var n=this.findWordBoundaryLeft(this.selectionStart);this._removeCharsFromTo(n,this.selectionStart),this.selectionStart=n}else{var r=this.text.slice(this.selectionStart-1,this.selectionStart)==="\n";this.removeStyleObject(r),this.selectionStart--,this.text=this.text.slice(0,this.selectionStart)+this.text.slice(this.selectionStart+1)}}}),fabric.util.object.extend(fabric.IText.prototype,{_setSVGTextLineText:function(e,t,n,r,i,s){this.styles[t]?this._setSVGTextLineChars(e,t,n,r,i,s):this.callSuper("_setSVGTextLineText",e,t,n,r,i)},_setSVGTextLineChars:function(e,t,n,r,i,s){var o=t===0||this.useNative?"y":"dy",u=e.split(""),a=0,f=this._getSVGLineLeftOffset(t),l=this._getSVGLineTopOffset(t),c=this._getHeightOfLine(this.ctx,t);for(var h=0,p=u.length;h'].join("")},_createTextCharSpan:function(e,t,n,r,i,s){var o=this.getSvgStyles.call(fabric.util.object.extend({visible:!0,fill:this.fill,stroke:this.stroke,type:"text"},t));return['',fabric.util.string.escapeXml(e),""].join("")}}),function(){function request(e,t,n){var r=URL.parse(e);r.port||(r.port=r.protocol.indexOf("https:")===0?443:80);var i=r.port===443?HTTPS:HTTP,s=i.request({hostname:r.hostname,port:r.port,path:r.path,method:"GET"},function(e){var r="";t&&e.setEncoding(t),e.on("end",function(){n(r)}),e.on("data",function(t){e.statusCode===200&&(r+=t)})});s.on("error",function(e){e.errno===process.ECONNREFUSED?fabric.log("ECONNREFUSED: connection refused to "+r.hostname+":"+r.port):fabric.log(e.message)}),s.end()}function requestFs(e,t){var n=require("fs");n.readFile(e,function(e,n){if(e)throw fabric.log(e),e;t(n)})}if(typeof document!="undefined"&&typeof window!="undefined")return;var DOMParser=require("xmldom").DOMParser,URL=require("url"),HTTP=require("http"),HTTPS=require("https"),Canvas=require("canvas"),Image=require("canvas").Image;fabric.util.loadImage=function(e,t,n){function r(r){i.src=new Buffer(r,"binary"),i._src=e,t&&t.call(n,i)}var i=new Image;e&&(e instanceof Buffer||e.indexOf("data")===0)?(i.src=i._src=e,t&&t.call(n,i)):e&&e.indexOf("http")!==0?requestFs(e,r):e?request(e,"binary",r):t&&t.call(n,e)},fabric.loadSVGFromURL=function(e,t,n){e=e.replace(/^\n\s*/,"").replace(/\?.*$/,"").trim(),e.indexOf("http")!==0?requestFs(e,function(e){fabric.loadSVGFromString(e.toString(),t,n)}):request(e,"",function(e){fabric.loadSVGFromString(e,t,n)})},fabric.loadSVGFromString=function(e,t,n){var r=(new DOMParser).parseFromString(e);fabric.parseSVGDocument(r.documentElement,function(e,n){t&&t(e,n)},n)},fabric.util.getScript=function(url,callback){request(url,"",function(body){eval(body),callback&&callback()})},fabric.Image.fromObject=function(e,t){fabric.util.loadImage(e.src,function(n){var r=new fabric.Image(n);r._initConfig(e),r._initFilters(e,function(e){r.filters=e||[],t&&t(r)})})},fabric.createCanvasForNode=function(e,t,n,r){r=r||n;var i=fabric.document.createElement("canvas"),s=new Canvas(e||600,t||600,r);i.style={},i.width=s.width,i.height=s.height;var o=fabric.Canvas||fabric.StaticCanvas,u=new o(i,n);return u.contextContainer=s.getContext("2d"),u.nodeCanvas=s,u.Font=Canvas.Font,u},fabric.StaticCanvas.prototype.createPNGStream=function(){return this.nodeCanvas.createPNGStream()},fabric.StaticCanvas.prototype.createJPEGStream=function(e){return this.nodeCanvas.createJPEGStream(e)};var origSetWidth=fabric.StaticCanvas.prototype.setWidth;fabric.StaticCanvas.prototype.setWidth=function(e,t){return origSetWidth.call(this,e,t),this.nodeCanvas.width=e,this},fabric.Canvas&&(fabric.Canvas.prototype.setWidth=fabric.StaticCanvas.prototype.setWidth);var origSetHeight=fabric.StaticCanvas.prototype.setHeight;fabric.StaticCanvas.prototype.setHeight=function(e,t){return origSetHeight.call(this,e,t),this.nodeCanvas.height=e,this},fabric.Canvas&&(fabric.Canvas.prototype.setHeight=fabric.StaticCanvas.prototype.setHeight)}(); ================================================ FILE: src/libs/flashdetect/flashdetect.js ================================================ /* Copyright (c) Copyright (c) 2007, Carl S. Yestrau All rights reserved. Code licensed under the BSD License: http://www.featureblend.com/license.txt Version: 1.0.4 */ var FlashDetect = new function(){ var self = this; self.installed = false; self.raw = ""; self.major = -1; self.minor = -1; self.revision = -1; self.revisionStr = ""; var activeXDetectRules = [ { "name":"ShockwaveFlash.ShockwaveFlash.7", "version":function(obj){ return getActiveXVersion(obj); } }, { "name":"ShockwaveFlash.ShockwaveFlash.6", "version":function(obj){ var version = "6,0,21"; try{ obj.AllowScriptAccess = "always"; version = getActiveXVersion(obj); }catch(err){} return version; } }, { "name":"ShockwaveFlash.ShockwaveFlash", "version":function(obj){ return getActiveXVersion(obj); } } ]; /** * Extract the ActiveX version of the plugin. * * @param {Object} The flash ActiveX object. * @type String */ var getActiveXVersion = function(activeXObj){ var version = -1; try{ version = activeXObj.GetVariable("$version"); }catch(err){} return version; }; /** * Try and retrieve an ActiveX object having a specified name. * * @param {String} name The ActiveX object name lookup. * @return One of ActiveX object or a simple object having an attribute of activeXError with a value of true. * @type Object */ var getActiveXObject = function(name){ var obj = -1; try{ obj = new ActiveXObject(name); }catch(err){ obj = {activeXError:true}; } return obj; }; /** * Parse an ActiveX $version string into an object. * * @param {String} str The ActiveX Object GetVariable($version) return value. * @return An object having raw, major, minor, revision and revisionStr attributes. * @type Object */ var parseActiveXVersion = function(str){ var versionArray = str.split(",");//replace with regex return { "raw":str, "major":parseInt(versionArray[0].split(" ")[1], 10), "minor":parseInt(versionArray[1], 10), "revision":parseInt(versionArray[2], 10), "revisionStr":versionArray[2] }; }; /** * Parse a standard enabledPlugin.description into an object. * * @param {String} str The enabledPlugin.description value. * @return An object having raw, major, minor, revision and revisionStr attributes. * @type Object */ var parseStandardVersion = function(str){ var descParts = str.split(/ +/); var majorMinor = descParts[2].split(/\./); var revisionStr = descParts[3]; return { "raw":str, "major":parseInt(majorMinor[0], 10), "minor":parseInt(majorMinor[1], 10), "revisionStr":revisionStr, "revision":parseRevisionStrToInt(revisionStr) }; }; /** * Parse the plugin revision string into an integer. * * @param {String} The revision in string format. * @type Number */ var parseRevisionStrToInt = function(str){ return parseInt(str.replace(/[a-zA-Z]/g, ""), 10) || self.revision; }; /** * Is the major version greater than or equal to a specified version. * * @param {Number} version The minimum required major version. * @type Boolean */ self.majorAtLeast = function(version){ return self.major >= version; }; /** * Is the minor version greater than or equal to a specified version. * * @param {Number} version The minimum required minor version. * @type Boolean */ self.minorAtLeast = function(version){ return self.minor >= version; }; /** * Is the revision version greater than or equal to a specified version. * * @param {Number} version The minimum required revision version. * @type Boolean */ self.revisionAtLeast = function(version){ return self.revision >= version; }; /** * Is the version greater than or equal to a specified major, minor and revision. * * @param {Number} major The minimum required major version. * @param {Number} (Optional) minor The minimum required minor version. * @param {Number} (Optional) revision The minimum required revision version. * @type Boolean */ self.versionAtLeast = function(major){ var properties = [self.major, self.minor, self.revision]; var len = Math.min(properties.length, arguments.length); for(i=0; i=arguments[i]){ if(i+10){ var type = 'application/x-shockwave-flash'; var mimeTypes = navigator.mimeTypes; if(mimeTypes && mimeTypes[type] && mimeTypes[type].enabledPlugin && mimeTypes[type].enabledPlugin.description){ var version = mimeTypes[type].enabledPlugin.description; var versionObj = parseStandardVersion(version); self.raw = versionObj.raw; self.major = versionObj.major; self.minor = versionObj.minor; self.revisionStr = versionObj.revisionStr; self.revision = versionObj.revision; self.installed = true; } }else if(navigator.appVersion.indexOf("Mac")==-1 && window.execScript){ var version = -1; for(var i=0; i charMin && pressedKey <= 90) || pressedKey == 32) { return false; } var cal = $(this).parent().parent(); if (cal.data('colorpicker').livePreview === true) { change.apply(this); } }, change = function (ev) { var cal = $(this).parent().parent(), col; if (this.parentNode.className.indexOf('_hex') > 0) { cal.data('colorpicker').color = col = HexToHSB(fixHex(this.value)); } else if (this.parentNode.className.indexOf('_hsb') > 0) { cal.data('colorpicker').color = col = fixHSB({ h: parseInt(cal.data('colorpicker').fields.eq(4).val(), 10), s: parseInt(cal.data('colorpicker').fields.eq(5).val(), 10), b: parseInt(cal.data('colorpicker').fields.eq(6).val(), 10) }); } else { cal.data('colorpicker').color = col = RGBToHSB(fixRGB({ r: parseInt(cal.data('colorpicker').fields.eq(1).val(), 10), g: parseInt(cal.data('colorpicker').fields.eq(2).val(), 10), b: parseInt(cal.data('colorpicker').fields.eq(3).val(), 10) })); } if (ev) { fillRGBFields(col, cal.get(0)); fillHexFields(col, cal.get(0)); fillHSBFields(col, cal.get(0)); } setSelector(col, cal.get(0)); setHue(col, cal.get(0)); setNewColor(col, cal.get(0)); cal.data('colorpicker').onChange.apply(cal, [col, HSBToHex(col), HSBToRGB(col)]); }, blur = function (ev) { var cal = $(this).parent().parent(); cal.data('colorpicker').fields.parent().removeClass('colorpicker_focus'); }, focus = function () { charMin = this.parentNode.className.indexOf('_hex') > 0 ? 70 : 65; $(this).parent().parent().data('colorpicker').fields.parent().removeClass('colorpicker_focus'); $(this).parent().addClass('colorpicker_focus'); }, downIncrement = function (ev) { var field = $(this).parent().find('input').focus(); var current = { el: $(this).parent().addClass('colorpicker_slider'), max: this.parentNode.className.indexOf('_hsb_h') > 0 ? 360 : (this.parentNode.className.indexOf('_hsb') > 0 ? 100 : 255), y: ev.pageY, field: field, val: parseInt(field.val(), 10), preview: $(this).parent().parent().data('colorpicker').livePreview }; $(document).bind('mouseup', current, upIncrement); $(document).bind('mousemove', current, moveIncrement); }, moveIncrement = function (ev) { ev.data.field.val(Math.max(0, Math.min(ev.data.max, parseInt(ev.data.val + ev.pageY - ev.data.y, 10)))); if (ev.data.preview) { change.apply(ev.data.field.get(0), [true]); } return false; }, upIncrement = function (ev) { change.apply(ev.data.field.get(0), [true]); ev.data.el.removeClass('colorpicker_slider').find('input').focus(); $(document).unbind('mouseup', upIncrement); $(document).unbind('mousemove', moveIncrement); return false; }, downHue = function (ev) { var current = { cal: $(this).parent(), y: $(this).offset().top }; current.preview = current.cal.data('colorpicker').livePreview; $(document).bind('mouseup', current, upHue); $(document).bind('mousemove', current, moveHue); }, moveHue = function (ev) { change.apply( ev.data.cal.data('colorpicker') .fields .eq(4) .val(parseInt(360*(150 - Math.max(0,Math.min(150,(ev.pageY - ev.data.y))))/150, 10)) .get(0), [ev.data.preview] ); return false; }, upHue = function (ev) { fillRGBFields(ev.data.cal.data('colorpicker').color, ev.data.cal.get(0)); fillHexFields(ev.data.cal.data('colorpicker').color, ev.data.cal.get(0)); $(document).unbind('mouseup', upHue); $(document).unbind('mousemove', moveHue); return false; }, downSelector = function (ev) { var current = { cal: $(this).parent(), pos: $(this).offset() }; current.preview = current.cal.data('colorpicker').livePreview; $(document).bind('mouseup', current, upSelector); $(document).bind('mousemove', current, moveSelector); }, moveSelector = function (ev) { change.apply( ev.data.cal.data('colorpicker') .fields .eq(6) .val(parseInt(100*(150 - Math.max(0,Math.min(150,(ev.pageY - ev.data.pos.top))))/150, 10)) .end() .eq(5) .val(parseInt(100*(Math.max(0,Math.min(150,(ev.pageX - ev.data.pos.left))))/150, 10)) .get(0), [ev.data.preview] ); return false; }, upSelector = function (ev) { fillRGBFields(ev.data.cal.data('colorpicker').color, ev.data.cal.get(0)); fillHexFields(ev.data.cal.data('colorpicker').color, ev.data.cal.get(0)); $(document).unbind('mouseup', upSelector); $(document).unbind('mousemove', moveSelector); return false; }, enterSubmit = function (ev) { $(this).addClass('colorpicker_focus'); }, leaveSubmit = function (ev) { $(this).removeClass('colorpicker_focus'); }, clickSubmit = function (ev) { var cal = $(this).parent(); var col = cal.data('colorpicker').color; cal.data('colorpicker').origColor = col; setCurrentColor(col, cal.get(0)); cal.data('colorpicker').onSubmit(col, HSBToHex(col), HSBToRGB(col), cal.data('colorpicker').el); }, show = function (ev) { var cal = $('#' + $(this).data('colorpickerId')); cal.data('colorpicker').onBeforeShow.apply(this, [cal.get(0)]); var pos = $(this).offset(); var viewPort = getViewport(); var top = pos.top + this.offsetHeight; var left = pos.left; if (top + 176 > viewPort.t + viewPort.h) { top -= this.offsetHeight + 176; } if (left + 356 > viewPort.l + viewPort.w) { left -= 356; } cal.css({left: left + 'px', top: top + 'px'}); if (cal.data('colorpicker').onShow.apply(this, [cal.get(0)]) != false) { cal.show(); } $(document).bind('mousedown', {cal: cal}, hide); return false; }, hide = function (ev) { if (!isChildOf(ev.data.cal.get(0), ev.target, ev.data.cal.get(0))) { if (ev.data.cal.data('colorpicker').onHide.apply(this, [ev.data.cal.get(0)]) != false) { ev.data.cal.hide(); } $(document).unbind('mousedown', hide); } }, isChildOf = function(parentEl, el, container) { if (parentEl == el) { return true; } if (parentEl.contains) { return parentEl.contains(el); } if ( parentEl.compareDocumentPosition ) { return !!(parentEl.compareDocumentPosition(el) & 16); } var prEl = el.parentNode; while(prEl && prEl != container) { if (prEl == parentEl) return true; prEl = prEl.parentNode; } return false; }, getViewport = function () { var m = document.compatMode == 'CSS1Compat'; return { l : window.pageXOffset || (m ? document.documentElement.scrollLeft : document.body.scrollLeft), t : window.pageYOffset || (m ? document.documentElement.scrollTop : document.body.scrollTop), w : window.innerWidth || (m ? document.documentElement.clientWidth : document.body.clientWidth), h : window.innerHeight || (m ? document.documentElement.clientHeight : document.body.clientHeight) }; }, fixHSB = function (hsb) { return { h: Math.min(360, Math.max(0, hsb.h)), s: Math.min(100, Math.max(0, hsb.s)), b: Math.min(100, Math.max(0, hsb.b)) }; }, fixRGB = function (rgb) { return { r: Math.min(255, Math.max(0, rgb.r)), g: Math.min(255, Math.max(0, rgb.g)), b: Math.min(255, Math.max(0, rgb.b)) }; }, fixHex = function (hex) { var len = 6 - hex.length; if (len > 0) { var o = []; for (var i=0; i -1) ? hex.substring(1) : hex), 16); return {r: hex >> 16, g: (hex & 0x00FF00) >> 8, b: (hex & 0x0000FF)}; }, HexToHSB = function (hex) { return RGBToHSB(HexToRGB(hex)); }, RGBToHSB = function (rgb) { var hsb = { h: 0, s: 0, b: 0 }; var min = Math.min(rgb.r, rgb.g, rgb.b); var max = Math.max(rgb.r, rgb.g, rgb.b); var delta = max - min; hsb.b = max; if (max != 0) { } hsb.s = max != 0 ? 255 * delta / max : 0; if (hsb.s != 0) { if (rgb.r == max) { hsb.h = (rgb.g - rgb.b) / delta; } else if (rgb.g == max) { hsb.h = 2 + (rgb.b - rgb.r) / delta; } else { hsb.h = 4 + (rgb.r - rgb.g) / delta; } } else { hsb.h = -1; } hsb.h *= 60; if (hsb.h < 0) { hsb.h += 360; } hsb.s *= 100/255; hsb.b *= 100/255; return hsb; }, HSBToRGB = function (hsb) { var rgb = {}; var h = Math.round(hsb.h); var s = Math.round(hsb.s*255/100); var v = Math.round(hsb.b*255/100); if(s == 0) { rgb.r = rgb.g = rgb.b = v; } else { var t1 = v; var t2 = (255-s)*v/255; var t3 = (t1-t2)*(h%60)/60; if(h==360) h = 0; if(h<60) {rgb.r=t1; rgb.b=t2; rgb.g=t2+t3} else if(h<120) {rgb.g=t1; rgb.b=t2; rgb.r=t1-t3} else if(h<180) {rgb.g=t1; rgb.r=t2; rgb.b=t2+t3} else if(h<240) {rgb.b=t1; rgb.r=t2; rgb.g=t1-t3} else if(h<300) {rgb.b=t1; rgb.g=t2; rgb.r=t2+t3} else if(h<360) {rgb.r=t1; rgb.g=t2; rgb.b=t1-t3} else {rgb.r=0; rgb.g=0; rgb.b=0} } return {r:Math.round(rgb.r), g:Math.round(rgb.g), b:Math.round(rgb.b)}; }, RGBToHex = function (rgb) { var hex = [ rgb.r.toString(16), rgb.g.toString(16), rgb.b.toString(16) ]; $.each(hex, function (nr, val) { if (val.length == 1) { hex[nr] = '0' + val; } }); return hex.join(''); }, HSBToHex = function (hsb) { return RGBToHex(HSBToRGB(hsb)); }, restoreOriginal = function () { var cal = $(this).parent(); var col = cal.data('colorpicker').origColor; cal.data('colorpicker').color = col; fillRGBFields(col, cal.get(0)); fillHexFields(col, cal.get(0)); fillHSBFields(col, cal.get(0)); setSelector(col, cal.get(0)); setHue(col, cal.get(0)); setNewColor(col, cal.get(0)); }; return { init: function (opt) { opt = $.extend({}, defaults, opt||{}); if (typeof opt.color == 'string') { opt.color = HexToHSB(opt.color); } else if (opt.color.r != undefined && opt.color.g != undefined && opt.color.b != undefined) { opt.color = RGBToHSB(opt.color); } else if (opt.color.h != undefined && opt.color.s != undefined && opt.color.b != undefined) { opt.color = fixHSB(opt.color); } else { return this; } return this.each(function () { if (!$(this).data('colorpickerId')) { var options = $.extend({}, opt); options.origColor = opt.color; var id = 'collorpicker_' + parseInt(Math.random() * 1000); $(this).data('colorpickerId', id); var cal = $(tpl).attr('id', id); if (options.flat) { cal.appendTo(this).show(); } else { cal.appendTo(document.body); } options.fields = cal .find('input') .bind('keyup', keyDown) .bind('change', change) .bind('blur', blur) .bind('focus', focus); cal .find('span').bind('mousedown', downIncrement).end() .find('>div.colorpicker_current_color').bind('click', restoreOriginal); options.selector = cal.find('div.colorpicker_color').bind('mousedown', downSelector); options.selectorIndic = options.selector.find('div div'); options.el = this; options.hue = cal.find('div.colorpicker_hue div'); cal.find('div.colorpicker_hue').bind('mousedown', downHue); options.newColor = cal.find('div.colorpicker_new_color'); options.currentColor = cal.find('div.colorpicker_current_color'); cal.data('colorpicker', options); cal.find('div.colorpicker_submit') .bind('mouseenter', enterSubmit) .bind('mouseleave', leaveSubmit) .bind('click', clickSubmit); fillRGBFields(options.color, cal.get(0)); fillHSBFields(options.color, cal.get(0)); fillHexFields(options.color, cal.get(0)); setHue(options.color, cal.get(0)); setSelector(options.color, cal.get(0)); setCurrentColor(options.color, cal.get(0)); setNewColor(options.color, cal.get(0)); if (options.flat) { cal.css({ position: 'relative', display: 'block' }); } else { $(this).bind(options.eventName, show); } } }); }, showPicker: function() { return this.each( function () { if ($(this).data('colorpickerId')) { show.apply(this); } }); }, hidePicker: function() { return this.each( function () { if ($(this).data('colorpickerId')) { $('#' + $(this).data('colorpickerId')).hide(); } }); }, setColor: function(col) { if (typeof col == 'string') { col = HexToHSB(col); } else if (col.r != undefined && col.g != undefined && col.b != undefined) { col = RGBToHSB(col); } else if (col.h != undefined && col.s != undefined && col.b != undefined) { col = fixHSB(col); } else { return this; } return this.each(function(){ if ($(this).data('colorpickerId')) { var cal = $('#' + $(this).data('colorpickerId')); cal.data('colorpicker').color = col; cal.data('colorpicker').origColor = col; fillRGBFields(col, cal.get(0)); fillHSBFields(col, cal.get(0)); fillHexFields(col, cal.get(0)); setHue(col, cal.get(0)); setSelector(col, cal.get(0)); setCurrentColor(col, cal.get(0)); setNewColor(col, cal.get(0)); } }); } }; }(); $.fn.extend({ ColorPicker: ColorPicker.init, ColorPickerHide: ColorPicker.hidePicker, ColorPickerShow: ColorPicker.showPicker, ColorPickerSetColor: ColorPicker.setColor }); })(jQuery) ================================================ FILE: src/libs/gradient/jquery.gradientpicker.js ================================================ /** @author Matt Crinklaw-Vogt (tantaman) https://github.com/tantaman/jquery-gradient-picker */ (function ($) { if (!$.event.special.destroyed) { $.event.special.destroyed = { remove: function (o) { if (o.handler) { o.handler(); } } } } function ctrlPtComparator(l, r) { return l.position - r.position; } function bind(fn, ctx) { if (typeof fn.bind === "function") { return fn.bind(ctx); } else { return function () { fn.apply(ctx, arguments); } } } var browserPrefix = ""; var agent = window.navigator.userAgent; if (agent.indexOf('WebKit') >= 0) browserPrefix = "-webkit-" else if (agent.indexOf('Mozilla') >= 0) browserPrefix = "-moz-" else if (agent.indexOf('Microsoft') >= 0) browserPrefix = "-ms-" else browserPrefix = "" function GradientSelection($el, opts) { this.$el = $el; this.$el.css("position", "relative"); this.opts = opts; var $preview = $(""); this.$el.append($preview); var canvas = $preview[0]; $preview.css({ width: $preview.parent().width(), height: $preview.parent().height() }); // canvas.width = canvas.clientWidth; // canvas.height = canvas.clientHeight; this.g2d = canvas.getContext("2d"); var $ctrlPtContainer = $("
        "); this.$el.append($ctrlPtContainer) this.$ctrlPtContainer = $ctrlPtContainer; this.updatePreview = bind(this.updatePreview, this); this.controlPoints = []; this.ctrlPtConfig = new ControlPtConfig(this.$el, opts); for (var i = 0; i < opts.controlPoints.length; ++i) { var ctrlPt = this.createCtrlPt(opts.controlPoints[i]); this.controlPoints.push(ctrlPt); } this.docClicked = bind(this.docClicked, this); this.destroyed = bind(this.destroyed, this); $(document).bind("click", this.docClicked); this.$el.bind("destroyed", this.destroyed); this.previewClicked = bind(this.previewClicked, this); $preview.click(this.previewClicked); this.updatePreview(); } GradientSelection.prototype = { docClicked: function () { this.ctrlPtConfig.hide(); }, createCtrlPt: function (ctrlPtSetup) { return new ControlPoint(this.$ctrlPtContainer, ctrlPtSetup, this.opts.orientation, this, this.ctrlPtConfig) }, destroyed: function () { $(document).unbind("click", this.docClicked); }, updateOptions: function (opts) { $.extend(this.opts, opts); this.updatePreview(); }, colorToHex: function (color) { if (color.match('#')) { return color; } if (color.match('rgb')) { return '#' + this.rgbToHex(color); } return '#' + color; }, rgbToHex: function (rgb) { function componentFromStr(numStr, percent) { var num = Math.max(0, parseInt(numStr, 10)); return percent ? Math.floor(255 * Math.min(100, num) / 100) : Math.min(255, num); } var rgbRegex = /^rgb\(\s*(-?\d+)(%?)\s*,\s*(-?\d+)(%?)\s*,\s*(-?\d+)(%?)\s*\)$/; var result, r, g, b, hex = ""; if ((result = rgbRegex.exec(rgb))) { r = componentFromStr(result[1], result[2]); g = componentFromStr(result[3], result[4]); b = componentFromStr(result[5], result[6]); hex = (0x1000000 + (r << 16) + (g << 8) + b).toString(16).slice(1); } return hex; }, updatePreview: function (quiet) { var result = []; this.controlPoints.sort(ctrlPtComparator); if (this.opts.orientation == "horizontal") { var grad = this.g2d.createLinearGradient(0, 0, this.g2d.canvas.width, 0); for (var i = 0; i < this.controlPoints.length; ++i) { var pt = this.controlPoints[i]; pt.color = this.colorToHex(pt.color); grad.addColorStop(pt.position, pt.color); result.push({ position: pt.position, color: pt.color }); } } this.g2d.fillStyle = grad; this.g2d.fillRect(0, 0, this.g2d.canvas.width, this.g2d.canvas.height); if (this.opts.generateStyles) var styles = this._generatePreviewStyles(); if (quiet) return; this.opts.change(result, styles); // this.opts.closed(result, styles); return { result: result, styles: styles } }, removeControlPoint: function (ctrlPt) { var cpidx = this.controlPoints.indexOf(ctrlPt); if (cpidx != -1) { this.controlPoints.splice(cpidx, 1); ctrlPt.$el.remove(); } }, clear: function () { var context = this.g2d.canvas.getContext('2d'); context.clearRect(0, 0, this.g2d.canvas.width,this.g2d.canvas.height); }, /** webNeat modifications starts here */ addPoint: function (percent, colorStr, quiet) { colorStr = this.colorToHex(colorStr); var cp = this.createCtrlPt({ position: percent, color: colorStr }); this.controlPoints.push(cp); this.controlPoints.sort(ctrlPtComparator); this.updatePreview(quiet); }, // removeAllPointsAAAA: function () { // this.controlPoints.forEach(function (point) { // point.$el.remove(); // }); // this.controlPoints = []; // this.addPoint(0,'#fff'); // this.addPoint(1,'#fff'); // this.updatePreview(); // this.controlPoints.forEach(function (point) { // point.$el.remove(); // }); // }, removeAllPoints: function () { this.controlPoints.forEach(function (point) { point.$el.remove(); ; }); this.controlPoints = []; // this.addPoint(0,'#fff'); // this.addPoint(1,'#fff'); this.updatePreview(); }, changeFillDirection: function (fd) { this.opts.fillDirection = fd; this.updatePreview(); }, /** webNeat modifications ends here */ previewClicked: function (e) { var offset = $(e.target).offset(); var x = e.pageX - offset.left; var y = e.pageY - offset.top; var imgData = this.g2d.getImageData(x, y, 1, 1); var colorStr = "rgb(" + imgData.data[0] + "," + imgData.data[1] + "," + imgData.data[2] + ")"; var cp = this.createCtrlPt({ position: x / this.g2d.canvas.width, color: colorStr }); this.controlPoints.push(cp); this.controlPoints.sort(ctrlPtComparator); }, getChanges: function () { this.updatePreview(); }, _generatePreviewStyles: function () { //linear-gradient(top, rgb(217,230,163) 86%, rgb(227,249,159) 9%) var str = this.opts.type + "-gradient(" + ((this.opts.type == "linear") ? (this.opts.fillDirection + ", ") : ""); var first = true; for (var i = 0; i < this.controlPoints.length; ++i) { var pt = this.controlPoints[i]; if (!first) { str += ", "; } else { first = false; } str += pt.color + " " + ((pt.position * 100) | 0) + "%"; } str = str + ")" var styles = [str, browserPrefix + str]; return styles; } }; function hexToRgb(hex) { var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); return result ? { r: parseInt(result[1], 16), g: parseInt(result[2], 16), b: parseInt(result[3], 16) } : null; } function ControlPoint($parentEl, initialState, orientation, listener, ctrlPtConfig) { this.$el = $("
        "); $parentEl.append(this.$el); this.$parentEl = $parentEl; this.configView = ctrlPtConfig; if (typeof initialState === "string") { initialState = initialState.split(" "); this.position = parseFloat(initialState[1]) / 100; this.color = initialState[0]; } else { this.position = initialState.position; this.color = initialState.color; } this.listener = listener; this.outerWidth = this.$el.outerWidth(); this.$el.css("background-color", '#' + this.color); /* if (this.color.match('rgb')){ console.log('rgb color ' + this.color); this.$el.css("background-color", this.color); } else { var rgbc = hexToRgb(this.color); var rgbv = 'rgb(' + rgbc.r + ',' + rgbc.g + ',' + rgbc.b + ')'; this.$el.css("background-color", rgbv); } */ if (orientation == "horizontal") { var pxLeft = ($parentEl.width() - this.$el.outerWidth()) * (this.position); this.$el.css("left", pxLeft); } else { var pxTop = ($parentEl.height() - this.$el.outerHeight()) * (this.position); this.$el.css("top", pxTop); } this.drag = bind(this.drag, this); this.stop = bind(this.stop, this); this.clicked = bind(this.clicked, this); this.colorChanged = bind(this.colorChanged, this); this.colorClosed = bind(this.colorClosed, this); this.$el.draggable({ axis: (orientation == "horizontal") ? "x" : "y", drag: this.drag, stop: this.stop, containment: $parentEl }); this.$el.css("position", 'absolute'); this.$el.click(this.clicked); } ControlPoint.prototype = { drag: function (e, ui) { // convert position to a % var left = ui.position.left; this.position = (left / (this.$parentEl.width() - this.outerWidth)); this.listener.updatePreview(); }, stop: function (e, ui) { this.listener.updatePreview(); this.configView.show(this.$el.position(), this.color, this); this.colorClosed(); }, clicked: function (e) { this.configView.show(this.$el.position(), this.color, this); console.log(this.color); e.stopPropagation(); return false; }, colorClosed: function () { this.listener.ctrlPtConfig.opts.closed(); }, colorChanged: function (c) { this.color = c; // log(c); this.$el.css("background-color", this.color); this.listener.updatePreview(); }, removeClicked: function () { this.listener.removeControlPoint(this); this.listener.updatePreview(); } }; function ControlPtConfig($parent, opts) { //color-chooser this.$el = $(''); $parent.append(this.$el); var $cpicker = $('
        '); this.$el.append($cpicker); var $rmEl = $("
        "); this.$el.append($rmEl); this.colorChanged = bind(this.colorChanged, this); this.colorClosed = bind(this.colorClosed, this); this.removeClicked = bind(this.removeClicked, this); $cpicker.ColorPicker({ onChange: this.colorChanged, onHide: this.colorClosed }); this.$cpicker = $cpicker; this.opts = opts; this.visible = false; $rmEl.click(this.removeClicked); } ControlPtConfig.prototype = { show: function (position, color, listener) { this.visible = true; this.listener = listener; this.$el.css("visibility", "visible"); this.$cpicker.ColorPickerSetColor(color); this.$cpicker.css("background-color", '#' + color); if (this.opts.orientation === "horizontal") { this.$el.css("left", position.left); } else { this.$el.css("top", position.top); } //else { // this.visible = false; //this.$el.css("visibility", "hidden"); //} }, hide: function () { if (this.visible) { this.$el.css("visibility", "hidden"); this.visible = false; } }, /*colorClosed: function(hsb, hex, rgb) { if (this.opts.closed){ var changes = this.listener.listener.updatePreview(false); this.opts.closed(changes.result, changes.styles) } },*/ colorClosed: function (hsb, hex, rgb) { if (this.opts.closed) { this.opts.closed(); } }, colorChanged: function (hsb, hex, rgb) { hex = "#" + hex; this.listener.colorChanged(hex); this.$cpicker.css("background-color", hex) }, removeClicked: function () { this.listener.removeClicked(); this.hide(); } }; var methods = { init: function (opts) { opts = $.extend({ //controlPoints: ["#FFF 0%", "#000 100%"], controlPoints: [], orientation: "horizontal", type: "linear", fillDirection: "left", generateStyles: true, change: function () { } }, opts); this.each(function () { var $this = $(this); var gradSel = new GradientSelection($this, opts); $this.data("gradientPicker-sel", gradSel); }); }, update: function (opts) { this.each(function () { var $this = $(this); var gradSel = $this.data("gradientPicker-sel"); if (gradSel != null) { gradSel.updateOptions(opts); } }); } }; $.fn.gradientPicker = function (method, opts) { if (typeof method === "string" && method !== "init") { methods[method].call(this, opts); } else { opts = method; methods.init.call(this, opts); } }; })(jQuery); ================================================ FILE: src/libs/jquery-ui.js ================================================ /*! jQuery UI - v1.10.1 - 2013-02-15 * http://jqueryui.com * Includes: jquery.ui.core.js, jquery.ui.widget.js, jquery.ui.mouse.js, jquery.ui.draggable.js, jquery.ui.droppable.js, jquery.ui.resizable.js, jquery.ui.selectable.js, jquery.ui.sortable.js, jquery.ui.effect.js, jquery.ui.accordion.js, jquery.ui.autocomplete.js, jquery.ui.button.js, jquery.ui.datepicker.js, jquery.ui.dialog.js, jquery.ui.effect-blind.js, jquery.ui.effect-bounce.js, jquery.ui.effect-clip.js, jquery.ui.effect-drop.js, jquery.ui.effect-explode.js, jquery.ui.effect-fade.js, jquery.ui.effect-fold.js, jquery.ui.effect-highlight.js, jquery.ui.effect-pulsate.js, jquery.ui.effect-scale.js, jquery.ui.effect-shake.js, jquery.ui.effect-slide.js, jquery.ui.effect-transfer.js, jquery.ui.menu.js, jquery.ui.position.js, jquery.ui.progressbar.js, jquery.ui.slider.js, jquery.ui.spinner.js, jquery.ui.tabs.js, jquery.ui.tooltip.js * Copyright 2013 jQuery Foundation and other contributors; Licensed MIT */ (function( $, undefined ) { var uuid = 0, runiqueId = /^ui-id-\d+$/; // prevent duplicate loading // this is only a problem because we proxy existing functions // and we don't want to double proxy them $.ui = $.ui || {}; if ( $.ui.version ) { return; } $.extend( $.ui, { version: "1.10.1", keyCode: { BACKSPACE: 8, COMMA: 188, DELETE: 46, DOWN: 40, END: 35, ENTER: 13, ESCAPE: 27, HOME: 36, LEFT: 37, NUMPAD_ADD: 107, NUMPAD_DECIMAL: 110, NUMPAD_DIVIDE: 111, NUMPAD_ENTER: 108, NUMPAD_MULTIPLY: 106, NUMPAD_SUBTRACT: 109, PAGE_DOWN: 34, PAGE_UP: 33, PERIOD: 190, RIGHT: 39, SPACE: 32, TAB: 9, UP: 38 } }); // plugins $.fn.extend({ _focus: $.fn.focus, focus: function( delay, fn ) { return typeof delay === "number" ? this.each(function() { var elem = this; setTimeout(function() { $( elem ).focus(); if ( fn ) { fn.call( elem ); } }, delay ); }) : this._focus.apply( this, arguments ); }, scrollParent: function() { var scrollParent; if (($.ui.ie && (/(static|relative)/).test(this.css("position"))) || (/absolute/).test(this.css("position"))) { scrollParent = this.parents().filter(function() { return (/(relative|absolute|fixed)/).test($.css(this,"position")) && (/(auto|scroll)/).test($.css(this,"overflow")+$.css(this,"overflow-y")+$.css(this,"overflow-x")); }).eq(0); } else { scrollParent = this.parents().filter(function() { return (/(auto|scroll)/).test($.css(this,"overflow")+$.css(this,"overflow-y")+$.css(this,"overflow-x")); }).eq(0); } return (/fixed/).test(this.css("position")) || !scrollParent.length ? $(document) : scrollParent; }, zIndex: function( zIndex ) { if ( zIndex !== undefined ) { return this.css( "zIndex", zIndex ); } if ( this.length ) { var elem = $( this[ 0 ] ), position, value; while ( elem.length && elem[ 0 ] !== document ) { // Ignore z-index if position is set to a value where z-index is ignored by the browser // This makes behavior of this function consistent across browsers // WebKit always returns auto if the element is positioned position = elem.css( "position" ); if ( position === "absolute" || position === "relative" || position === "fixed" ) { // IE returns 0 when zIndex is not specified // other browsers return a string // we ignore the case of nested elements with an explicit value of 0 //
        value = parseInt( elem.css( "zIndex" ), 10 ); if ( !isNaN( value ) && value !== 0 ) { return value; } } elem = elem.parent(); } } return 0; }, uniqueId: function() { return this.each(function() { if ( !this.id ) { this.id = "ui-id-" + (++uuid); } }); }, removeUniqueId: function() { return this.each(function() { if ( runiqueId.test( this.id ) ) { $( this ).removeAttr( "id" ); } }); } }); // selectors function focusable( element, isTabIndexNotNaN ) { var map, mapName, img, nodeName = element.nodeName.toLowerCase(); if ( "area" === nodeName ) { map = element.parentNode; mapName = map.name; if ( !element.href || !mapName || map.nodeName.toLowerCase() !== "map" ) { return false; } img = $( "img[usemap=#" + mapName + "]" )[0]; return !!img && visible( img ); } return ( /input|select|textarea|button|object/.test( nodeName ) ? !element.disabled : "a" === nodeName ? element.href || isTabIndexNotNaN : isTabIndexNotNaN) && // the element and all of its ancestors must be visible visible( element ); } function visible( element ) { return $.expr.filters.visible( element ) && !$( element ).parents().addBack().filter(function() { return $.css( this, "visibility" ) === "hidden"; }).length; } $.extend( $.expr[ ":" ], { data: $.expr.createPseudo ? $.expr.createPseudo(function( dataName ) { return function( elem ) { return !!$.data( elem, dataName ); }; }) : // support: jQuery <1.8 function( elem, i, match ) { return !!$.data( elem, match[ 3 ] ); }, focusable: function( element ) { return focusable( element, !isNaN( $.attr( element, "tabindex" ) ) ); }, tabbable: function( element ) { var tabIndex = $.attr( element, "tabindex" ), isTabIndexNaN = isNaN( tabIndex ); return ( isTabIndexNaN || tabIndex >= 0 ) && focusable( element, !isTabIndexNaN ); } }); // support: jQuery <1.8 if ( !$( "" ).outerWidth( 1 ).jquery ) { $.each( [ "Width", "Height" ], function( i, name ) { var side = name === "Width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ], type = name.toLowerCase(), orig = { innerWidth: $.fn.innerWidth, innerHeight: $.fn.innerHeight, outerWidth: $.fn.outerWidth, outerHeight: $.fn.outerHeight }; function reduce( elem, size, border, margin ) { $.each( side, function() { size -= parseFloat( $.css( elem, "padding" + this ) ) || 0; if ( border ) { size -= parseFloat( $.css( elem, "border" + this + "Width" ) ) || 0; } if ( margin ) { size -= parseFloat( $.css( elem, "margin" + this ) ) || 0; } }); return size; } $.fn[ "inner" + name ] = function( size ) { if ( size === undefined ) { return orig[ "inner" + name ].call( this ); } return this.each(function() { $( this ).css( type, reduce( this, size ) + "px" ); }); }; $.fn[ "outer" + name] = function( size, margin ) { if ( typeof size !== "number" ) { return orig[ "outer" + name ].call( this, size ); } return this.each(function() { $( this).css( type, reduce( this, size, true, margin ) + "px" ); }); }; }); } // support: jQuery <1.8 if ( !$.fn.addBack ) { $.fn.addBack = function( selector ) { return this.add( selector == null ? this.prevObject : this.prevObject.filter( selector ) ); }; } // support: jQuery 1.6.1, 1.6.2 (http://bugs.jquery.com/ticket/9413) if ( $( "" ).data( "a-b", "a" ).removeData( "a-b" ).data( "a-b" ) ) { $.fn.removeData = (function( removeData ) { return function( key ) { if ( arguments.length ) { return removeData.call( this, $.camelCase( key ) ); } else { return removeData.call( this ); } }; })( $.fn.removeData ); } // deprecated $.ui.ie = !!/msie [\w.]+/.exec( navigator.userAgent.toLowerCase() ); $.support.selectstart = "onselectstart" in document.createElement( "div" ); $.fn.extend({ disableSelection: function() { return this.bind( ( $.support.selectstart ? "selectstart" : "mousedown" ) + ".ui-disableSelection", function( event ) { event.preventDefault(); }); }, enableSelection: function() { return this.unbind( ".ui-disableSelection" ); } }); $.extend( $.ui, { // $.ui.plugin is deprecated. Use the proxy pattern instead. plugin: { add: function( module, option, set ) { var i, proto = $.ui[ module ].prototype; for ( i in set ) { proto.plugins[ i ] = proto.plugins[ i ] || []; proto.plugins[ i ].push( [ option, set[ i ] ] ); } }, call: function( instance, name, args ) { var i, set = instance.plugins[ name ]; if ( !set || !instance.element[ 0 ].parentNode || instance.element[ 0 ].parentNode.nodeType === 11 ) { return; } for ( i = 0; i < set.length; i++ ) { if ( instance.options[ set[ i ][ 0 ] ] ) { set[ i ][ 1 ].apply( instance.element, args ); } } } }, // only used by resizable hasScroll: function( el, a ) { //If overflow is hidden, the element might have extra content, but the user wants to hide it if ( $( el ).css( "overflow" ) === "hidden") { return false; } var scroll = ( a && a === "left" ) ? "scrollLeft" : "scrollTop", has = false; if ( el[ scroll ] > 0 ) { return true; } // TODO: determine which cases actually cause this to happen // if the element doesn't have the scroll set, see if it's possible to // set the scroll el[ scroll ] = 1; has = ( el[ scroll ] > 0 ); el[ scroll ] = 0; return has; } }); })( jQuery ); (function( $, undefined ) { var uuid = 0, slice = Array.prototype.slice, _cleanData = $.cleanData; $.cleanData = function( elems ) { for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) { try { $( elem ).triggerHandler( "remove" ); // http://bugs.jquery.com/ticket/8235 } catch( e ) {} } _cleanData( elems ); }; $.widget = function( name, base, prototype ) { var fullName, existingConstructor, constructor, basePrototype, // proxiedPrototype allows the provided prototype to remain unmodified // so that it can be used as a mixin for multiple widgets (#8876) proxiedPrototype = {}, namespace = name.split( "." )[ 0 ]; name = name.split( "." )[ 1 ]; fullName = namespace + "-" + name; if ( !prototype ) { prototype = base; base = $.Widget; } // create selector for plugin $.expr[ ":" ][ fullName.toLowerCase() ] = function( elem ) { return !!$.data( elem, fullName ); }; $[ namespace ] = $[ namespace ] || {}; existingConstructor = $[ namespace ][ name ]; constructor = $[ namespace ][ name ] = function( options, element ) { // allow instantiation without "new" keyword if ( !this._createWidget ) { return new constructor( options, element ); } // allow instantiation without initializing for simple inheritance // must use "new" keyword (the code above always passes args) if ( arguments.length ) { this._createWidget( options, element ); } }; // extend with the existing constructor to carry over any static properties $.extend( constructor, existingConstructor, { version: prototype.version, // copy the object used to create the prototype in case we need to // redefine the widget later _proto: $.extend( {}, prototype ), // track widgets that inherit from this widget in case this widget is // redefined after a widget inherits from it _childConstructors: [] }); basePrototype = new base(); // we need to make the options hash a property directly on the new instance // otherwise we'll modify the options hash on the prototype that we're // inheriting from basePrototype.options = $.widget.extend( {}, basePrototype.options ); $.each( prototype, function( prop, value ) { if ( !$.isFunction( value ) ) { proxiedPrototype[ prop ] = value; return; } proxiedPrototype[ prop ] = (function() { var _super = function() { return base.prototype[ prop ].apply( this, arguments ); }, _superApply = function( args ) { return base.prototype[ prop ].apply( this, args ); }; return function() { var __super = this._super, __superApply = this._superApply, returnValue; this._super = _super; this._superApply = _superApply; returnValue = value.apply( this, arguments ); this._super = __super; this._superApply = __superApply; return returnValue; }; })(); }); constructor.prototype = $.widget.extend( basePrototype, { // TODO: remove support for widgetEventPrefix // always use the name + a colon as the prefix, e.g., draggable:start // don't prefix for widgets that aren't DOM-based widgetEventPrefix: existingConstructor ? basePrototype.widgetEventPrefix : name }, proxiedPrototype, { constructor: constructor, namespace: namespace, widgetName: name, widgetFullName: fullName }); // If this widget is being redefined then we need to find all widgets that // are inheriting from it and redefine all of them so that they inherit from // the new version of this widget. We're essentially trying to replace one // level in the prototype chain. if ( existingConstructor ) { $.each( existingConstructor._childConstructors, function( i, child ) { var childPrototype = child.prototype; // redefine the child widget using the same prototype that was // originally used, but inherit from the new version of the base $.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor, child._proto ); }); // remove the list of existing child constructors from the old constructor // so the old child constructors can be garbage collected delete existingConstructor._childConstructors; } else { base._childConstructors.push( constructor ); } $.widget.bridge( name, constructor ); }; $.widget.extend = function( target ) { var input = slice.call( arguments, 1 ), inputIndex = 0, inputLength = input.length, key, value; for ( ; inputIndex < inputLength; inputIndex++ ) { for ( key in input[ inputIndex ] ) { value = input[ inputIndex ][ key ]; if ( input[ inputIndex ].hasOwnProperty( key ) && value !== undefined ) { // Clone objects if ( $.isPlainObject( value ) ) { target[ key ] = $.isPlainObject( target[ key ] ) ? $.widget.extend( {}, target[ key ], value ) : // Don't extend strings, arrays, etc. with objects $.widget.extend( {}, value ); // Copy everything else by reference } else { target[ key ] = value; } } } } return target; }; $.widget.bridge = function( name, object ) { var fullName = object.prototype.widgetFullName || name; $.fn[ name ] = function( options ) { var isMethodCall = typeof options === "string", args = slice.call( arguments, 1 ), returnValue = this; // allow multiple hashes to be passed on init options = !isMethodCall && args.length ? $.widget.extend.apply( null, [ options ].concat(args) ) : options; if ( isMethodCall ) { this.each(function() { var methodValue, instance = $.data( this, fullName ); if ( !instance ) { return $.error( "cannot call methods on " + name + " prior to initialization; " + "attempted to call method '" + options + "'" ); } if ( !$.isFunction( instance[options] ) || options.charAt( 0 ) === "_" ) { return $.error( "no such method '" + options + "' for " + name + " widget instance" ); } methodValue = instance[ options ].apply( instance, args ); if ( methodValue !== instance && methodValue !== undefined ) { returnValue = methodValue && methodValue.jquery ? returnValue.pushStack( methodValue.get() ) : methodValue; return false; } }); } else { this.each(function() { var instance = $.data( this, fullName ); if ( instance ) { instance.option( options || {} )._init(); } else { $.data( this, fullName, new object( options, this ) ); } }); } return returnValue; }; }; $.Widget = function( /* options, element */ ) {}; $.Widget._childConstructors = []; $.Widget.prototype = { widgetName: "widget", widgetEventPrefix: "", defaultElement: "
        ", options: { disabled: false, // callbacks create: null }, _createWidget: function( options, element ) { element = $( element || this.defaultElement || this )[ 0 ]; this.element = $( element ); this.uuid = uuid++; this.eventNamespace = "." + this.widgetName + this.uuid; this.options = $.widget.extend( {}, this.options, this._getCreateOptions(), options ); this.bindings = $(); this.hoverable = $(); this.focusable = $(); if ( element !== this ) { $.data( element, this.widgetFullName, this ); this._on( true, this.element, { remove: function( event ) { if ( event.target === element ) { this.destroy(); } } }); this.document = $( element.style ? // element within the document element.ownerDocument : // element is window or document element.document || element ); this.window = $( this.document[0].defaultView || this.document[0].parentWindow ); } this._create(); this._trigger( "create", null, this._getCreateEventData() ); this._init(); }, _getCreateOptions: $.noop, _getCreateEventData: $.noop, _create: $.noop, _init: $.noop, destroy: function() { this._destroy(); // we can probably remove the unbind calls in 2.0 // all event bindings should go through this._on() this.element .unbind( this.eventNamespace ) // 1.9 BC for #7810 // TODO remove dual storage .removeData( this.widgetName ) .removeData( this.widgetFullName ) // support: jquery <1.6.3 // http://bugs.jquery.com/ticket/9413 .removeData( $.camelCase( this.widgetFullName ) ); this.widget() .unbind( this.eventNamespace ) .removeAttr( "aria-disabled" ) .removeClass( this.widgetFullName + "-disabled " + "ui-state-disabled" ); // clean up events and states this.bindings.unbind( this.eventNamespace ); this.hoverable.removeClass( "ui-state-hover" ); this.focusable.removeClass( "ui-state-focus" ); }, _destroy: $.noop, widget: function() { return this.element; }, option: function( key, value ) { var options = key, parts, curOption, i; if ( arguments.length === 0 ) { // don't return a reference to the internal hash return $.widget.extend( {}, this.options ); } if ( typeof key === "string" ) { // handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } } options = {}; parts = key.split( "." ); key = parts.shift(); if ( parts.length ) { curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] ); for ( i = 0; i < parts.length - 1; i++ ) { curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {}; curOption = curOption[ parts[ i ] ]; } key = parts.pop(); if ( value === undefined ) { return curOption[ key ] === undefined ? null : curOption[ key ]; } curOption[ key ] = value; } else { if ( value === undefined ) { return this.options[ key ] === undefined ? null : this.options[ key ]; } options[ key ] = value; } } this._setOptions( options ); return this; }, _setOptions: function( options ) { var key; for ( key in options ) { this._setOption( key, options[ key ] ); } return this; }, _setOption: function( key, value ) { this.options[ key ] = value; if ( key === "disabled" ) { this.widget() .toggleClass( this.widgetFullName + "-disabled ui-state-disabled", !!value ) .attr( "aria-disabled", value ); this.hoverable.removeClass( "ui-state-hover" ); this.focusable.removeClass( "ui-state-focus" ); } return this; }, enable: function() { return this._setOption( "disabled", false ); }, disable: function() { return this._setOption( "disabled", true ); }, _on: function( suppressDisabledCheck, element, handlers ) { var delegateElement, instance = this; // no suppressDisabledCheck flag, shuffle arguments if ( typeof suppressDisabledCheck !== "boolean" ) { handlers = element; element = suppressDisabledCheck; suppressDisabledCheck = false; } // no element argument, shuffle and use this.element if ( !handlers ) { handlers = element; element = this.element; delegateElement = this.widget(); } else { // accept selectors, DOM elements element = delegateElement = $( element ); this.bindings = this.bindings.add( element ); } $.each( handlers, function( event, handler ) { function handlerProxy() { // allow widgets to customize the disabled handling // - disabled as an array instead of boolean // - disabled class as method for disabling individual parts if ( !suppressDisabledCheck && ( instance.options.disabled === true || $( this ).hasClass( "ui-state-disabled" ) ) ) { return; } return ( typeof handler === "string" ? instance[ handler ] : handler ) .apply( instance, arguments ); } // copy the guid so direct unbinding works if ( typeof handler !== "string" ) { handlerProxy.guid = handler.guid = handler.guid || handlerProxy.guid || $.guid++; } var match = event.match( /^(\w+)\s*(.*)$/ ), eventName = match[1] + instance.eventNamespace, selector = match[2]; if ( selector ) { delegateElement.delegate( selector, eventName, handlerProxy ); } else { element.bind( eventName, handlerProxy ); } }); }, _off: function( element, eventName ) { eventName = (eventName || "").split( " " ).join( this.eventNamespace + " " ) + this.eventNamespace; element.unbind( eventName ).undelegate( eventName ); }, _delay: function( handler, delay ) { function handlerProxy() { return ( typeof handler === "string" ? instance[ handler ] : handler ) .apply( instance, arguments ); } var instance = this; return setTimeout( handlerProxy, delay || 0 ); }, _hoverable: function( element ) { this.hoverable = this.hoverable.add( element ); this._on( element, { mouseenter: function( event ) { $( event.currentTarget ).addClass( "ui-state-hover" ); }, mouseleave: function( event ) { $( event.currentTarget ).removeClass( "ui-state-hover" ); } }); }, _focusable: function( element ) { this.focusable = this.focusable.add( element ); this._on( element, { focusin: function( event ) { $( event.currentTarget ).addClass( "ui-state-focus" ); }, focusout: function( event ) { $( event.currentTarget ).removeClass( "ui-state-focus" ); } }); }, _trigger: function( type, event, data ) { var prop, orig, callback = this.options[ type ]; data = data || {}; event = $.Event( event ); event.type = ( type === this.widgetEventPrefix ? type : this.widgetEventPrefix + type ).toLowerCase(); // the original event may come from any element // so we need to reset the target on the new event event.target = this.element[ 0 ]; // copy original event properties over to the new event orig = event.originalEvent; if ( orig ) { for ( prop in orig ) { if ( !( prop in event ) ) { event[ prop ] = orig[ prop ]; } } } this.element.trigger( event, data ); return !( $.isFunction( callback ) && callback.apply( this.element[0], [ event ].concat( data ) ) === false || event.isDefaultPrevented() ); } }; $.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) { $.Widget.prototype[ "_" + method ] = function( element, options, callback ) { if ( typeof options === "string" ) { options = { effect: options }; } var hasOptions, effectName = !options ? method : options === true || typeof options === "number" ? defaultEffect : options.effect || defaultEffect; options = options || {}; if ( typeof options === "number" ) { options = { duration: options }; } hasOptions = !$.isEmptyObject( options ); options.complete = callback; if ( options.delay ) { element.delay( options.delay ); } if ( hasOptions && $.effects && $.effects.effect[ effectName ] ) { element[ method ]( options ); } else if ( effectName !== method && element[ effectName ] ) { element[ effectName ]( options.duration, options.easing, callback ); } else { element.queue(function( next ) { $( this )[ method ](); if ( callback ) { callback.call( element[ 0 ] ); } next(); }); } }; }); })( jQuery ); (function( $, undefined ) { var mouseHandled = false; $( document ).mouseup( function() { mouseHandled = false; }); $.widget("ui.mouse", { version: "1.10.1", options: { cancel: "input,textarea,button,select,option", distance: 1, delay: 0 }, _mouseInit: function() { var that = this; this.element .bind("mousedown."+this.widgetName, function(event) { return that._mouseDown(event); }) .bind("click."+this.widgetName, function(event) { if (true === $.data(event.target, that.widgetName + ".preventClickEvent")) { $.removeData(event.target, that.widgetName + ".preventClickEvent"); event.stopImmediatePropagation(); return false; } }); this.started = false; }, // TODO: make sure destroying one instance of mouse doesn't mess with // other instances of mouse _mouseDestroy: function() { this.element.unbind("."+this.widgetName); if ( this._mouseMoveDelegate ) { $(document) .unbind("mousemove."+this.widgetName, this._mouseMoveDelegate) .unbind("mouseup."+this.widgetName, this._mouseUpDelegate); } }, _mouseDown: function(event) { // don't let more than one widget handle mouseStart if( mouseHandled ) { return; } // we may have missed mouseup (out of window) (this._mouseStarted && this._mouseUp(event)); this._mouseDownEvent = event; var that = this, btnIsLeft = (event.which === 1), // event.target.nodeName works around a bug in IE 8 with // disabled inputs (#7620) elIsCancel = (typeof this.options.cancel === "string" && event.target.nodeName ? $(event.target).closest(this.options.cancel).length : false); if (!btnIsLeft || elIsCancel || !this._mouseCapture(event)) { return true; } this.mouseDelayMet = !this.options.delay; if (!this.mouseDelayMet) { this._mouseDelayTimer = setTimeout(function() { that.mouseDelayMet = true; }, this.options.delay); } if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) { this._mouseStarted = (this._mouseStart(event) !== false); if (!this._mouseStarted) { event.preventDefault(); return true; } } // Click event may never have fired (Gecko & Opera) if (true === $.data(event.target, this.widgetName + ".preventClickEvent")) { $.removeData(event.target, this.widgetName + ".preventClickEvent"); } // these delegates are required to keep context this._mouseMoveDelegate = function(event) { return that._mouseMove(event); }; this._mouseUpDelegate = function(event) { return that._mouseUp(event); }; $(document) .bind("mousemove."+this.widgetName, this._mouseMoveDelegate) .bind("mouseup."+this.widgetName, this._mouseUpDelegate); event.preventDefault(); mouseHandled = true; return true; }, _mouseMove: function(event) { // IE mouseup check - mouseup happened when mouse was out of window if ($.ui.ie && ( !document.documentMode || document.documentMode < 9 ) && !event.button) { return this._mouseUp(event); } if (this._mouseStarted) { this._mouseDrag(event); return event.preventDefault(); } if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) { this._mouseStarted = (this._mouseStart(this._mouseDownEvent, event) !== false); (this._mouseStarted ? this._mouseDrag(event) : this._mouseUp(event)); } return !this._mouseStarted; }, _mouseUp: function(event) { $(document) .unbind("mousemove."+this.widgetName, this._mouseMoveDelegate) .unbind("mouseup."+this.widgetName, this._mouseUpDelegate); if (this._mouseStarted) { this._mouseStarted = false; if (event.target === this._mouseDownEvent.target) { $.data(event.target, this.widgetName + ".preventClickEvent", true); } this._mouseStop(event); } return false; }, _mouseDistanceMet: function(event) { return (Math.max( Math.abs(this._mouseDownEvent.pageX - event.pageX), Math.abs(this._mouseDownEvent.pageY - event.pageY) ) >= this.options.distance ); }, _mouseDelayMet: function(/* event */) { return this.mouseDelayMet; }, // These are placeholder methods, to be overriden by extending plugin _mouseStart: function(/* event */) {}, _mouseDrag: function(/* event */) {}, _mouseStop: function(/* event */) {}, _mouseCapture: function(/* event */) { return true; } }); })(jQuery); (function( $, undefined ) { $.widget("ui.draggable", $.ui.mouse, { version: "1.10.1", widgetEventPrefix: "drag", options: { addClasses: true, appendTo: "parent", axis: false, connectToSortable: false, containment: false, cursor: "auto", cursorAt: false, grid: false, handle: false, helper: "original", iframeFix: false, opacity: false, refreshPositions: false, revert: false, revertDuration: 500, scope: "default", scroll: true, scrollSensitivity: 20, scrollSpeed: 20, snap: false, snapMode: "both", snapTolerance: 20, stack: false, zIndex: false, // callbacks drag: null, start: null, stop: null }, _create: function() { if (this.options.helper === "original" && !(/^(?:r|a|f)/).test(this.element.css("position"))) { this.element[0].style.position = "relative"; } if (this.options.addClasses){ this.element.addClass("ui-draggable"); } if (this.options.disabled){ this.element.addClass("ui-draggable-disabled"); } this._mouseInit(); }, _destroy: function() { this.element.removeClass( "ui-draggable ui-draggable-dragging ui-draggable-disabled" ); this._mouseDestroy(); }, _mouseCapture: function(event) { var o = this.options; // among others, prevent a drag on a resizable-handle if (this.helper || o.disabled || $(event.target).closest(".ui-resizable-handle").length > 0) { return false; } //Quit if we're not on a valid handle this.handle = this._getHandle(event); if (!this.handle) { return false; } $(o.iframeFix === true ? "iframe" : o.iframeFix).each(function() { $("
        ") .css({ width: this.offsetWidth+"px", height: this.offsetHeight+"px", position: "absolute", opacity: "0.001", zIndex: 1000 }) .css($(this).offset()) .appendTo("body"); }); return true; }, _mouseStart: function(event) { var o = this.options; //Create and append the visible helper this.helper = this._createHelper(event); this.helper.addClass("ui-draggable-dragging"); //Cache the helper size this._cacheHelperProportions(); //If ddmanager is used for droppables, set the global draggable if($.ui.ddmanager) { $.ui.ddmanager.current = this; } /* * - Position generation - * This block generates everything position related - it's the core of draggables. */ //Cache the margins of the original element this._cacheMargins(); //Store the helper's css position this.cssPosition = this.helper.css("position"); this.scrollParent = this.helper.scrollParent(); //The element's absolute position on the page minus margins this.offset = this.positionAbs = this.element.offset(); this.offset = { top: this.offset.top - this.margins.top, left: this.offset.left - this.margins.left }; $.extend(this.offset, { click: { //Where the click happened, relative to the element left: event.pageX - this.offset.left, top: event.pageY - this.offset.top }, parent: this._getParentOffset(), relative: this._getRelativeOffset() //This is a relative to absolute position minus the actual position calculation - only used for relative positioned helper }); //Generate the original position this.originalPosition = this.position = this._generatePosition(event); this.originalPageX = event.pageX; this.originalPageY = event.pageY; //Adjust the mouse offset relative to the helper if "cursorAt" is supplied (o.cursorAt && this._adjustOffsetFromHelper(o.cursorAt)); //Set a containment if given in the options if(o.containment) { this._setContainment(); } //Trigger event + callbacks if(this._trigger("start", event) === false) { this._clear(); return false; } //Recache the helper size this._cacheHelperProportions(); //Prepare the droppable offsets if ($.ui.ddmanager && !o.dropBehaviour) { $.ui.ddmanager.prepareOffsets(this, event); } this._mouseDrag(event, true); //Execute the drag once - this causes the helper not to be visible before getting its correct position //If the ddmanager is used for droppables, inform the manager that dragging has started (see #5003) if ( $.ui.ddmanager ) { $.ui.ddmanager.dragStart(this, event); } return true; }, _mouseDrag: function(event, noPropagation) { //Compute the helpers position this.position = this._generatePosition(event); this.positionAbs = this._convertPositionTo("absolute"); //Call plugins and callbacks and use the resulting position if something is returned if (!noPropagation) { var ui = this._uiHash(); if(this._trigger("drag", event, ui) === false) { this._mouseUp({}); return false; } this.position = ui.position; } if(!this.options.axis || this.options.axis !== "y") { this.helper[0].style.left = this.position.left+"px"; } if(!this.options.axis || this.options.axis !== "x") { this.helper[0].style.top = this.position.top+"px"; } if($.ui.ddmanager) { $.ui.ddmanager.drag(this, event); } return false; }, _mouseStop: function(event) { //If we are using droppables, inform the manager about the drop var element, that = this, elementInDom = false, dropped = false; if ($.ui.ddmanager && !this.options.dropBehaviour) { dropped = $.ui.ddmanager.drop(this, event); } //if a drop comes from outside (a sortable) if(this.dropped) { dropped = this.dropped; this.dropped = false; } //if the original element is no longer in the DOM don't bother to continue (see #8269) element = this.element[0]; while ( element && (element = element.parentNode) ) { if (element === document ) { elementInDom = true; } } if ( !elementInDom && this.options.helper === "original" ) { return false; } if((this.options.revert === "invalid" && !dropped) || (this.options.revert === "valid" && dropped) || this.options.revert === true || ($.isFunction(this.options.revert) && this.options.revert.call(this.element, dropped))) { $(this.helper).animate(this.originalPosition, parseInt(this.options.revertDuration, 10), function() { if(that._trigger("stop", event) !== false) { that._clear(); } }); } else { if(this._trigger("stop", event) !== false) { this._clear(); } } return false; }, _mouseUp: function(event) { //Remove frame helpers $("div.ui-draggable-iframeFix").each(function() { this.parentNode.removeChild(this); }); //If the ddmanager is used for droppables, inform the manager that dragging has stopped (see #5003) if( $.ui.ddmanager ) { $.ui.ddmanager.dragStop(this, event); } return $.ui.mouse.prototype._mouseUp.call(this, event); }, cancel: function() { if(this.helper.is(".ui-draggable-dragging")) { this._mouseUp({}); } else { this._clear(); } return this; }, _getHandle: function(event) { var handle = !this.options.handle || !$(this.options.handle, this.element).length ? true : false; $(this.options.handle, this.element) .find("*") .addBack() .each(function() { if(this === event.target) { handle = true; } }); return handle; }, _createHelper: function(event) { var o = this.options, helper = $.isFunction(o.helper) ? $(o.helper.apply(this.element[0], [event])) : (o.helper === "clone" ? this.element.clone().removeAttr("id") : this.element); if(!helper.parents("body").length) { helper.appendTo((o.appendTo === "parent" ? this.element[0].parentNode : o.appendTo)); } if(helper[0] !== this.element[0] && !(/(fixed|absolute)/).test(helper.css("position"))) { helper.css("position", "absolute"); } return helper; }, _adjustOffsetFromHelper: function(obj) { if (typeof obj === "string") { obj = obj.split(" "); } if ($.isArray(obj)) { obj = {left: +obj[0], top: +obj[1] || 0}; } if ("left" in obj) { this.offset.click.left = obj.left + this.margins.left; } if ("right" in obj) { this.offset.click.left = this.helperProportions.width - obj.right + this.margins.left; } if ("top" in obj) { this.offset.click.top = obj.top + this.margins.top; } if ("bottom" in obj) { this.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top; } }, _getParentOffset: function() { //Get the offsetParent and cache its position this.offsetParent = this.helper.offsetParent(); var po = this.offsetParent.offset(); // This is a special case where we need to modify a offset calculated on start, since the following happened: // 1. The position of the helper is absolute, so it's position is calculated based on the next positioned parent // 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't the document, which means that // the scroll is included in the initial calculation of the offset of the parent, and never recalculated upon drag if(this.cssPosition === "absolute" && this.scrollParent[0] !== document && $.contains(this.scrollParent[0], this.offsetParent[0])) { po.left += this.scrollParent.scrollLeft(); po.top += this.scrollParent.scrollTop(); } //This needs to be actually done for all browsers, since pageX/pageY includes this information //Ugly IE fix if((this.offsetParent[0] === document.body) || (this.offsetParent[0].tagName && this.offsetParent[0].tagName.toLowerCase() === "html" && $.ui.ie)) { po = { top: 0, left: 0 }; } return { top: po.top + (parseInt(this.offsetParent.css("borderTopWidth"),10) || 0), left: po.left + (parseInt(this.offsetParent.css("borderLeftWidth"),10) || 0) }; }, _getRelativeOffset: function() { if(this.cssPosition === "relative") { var p = this.element.position(); return { top: p.top - (parseInt(this.helper.css("top"),10) || 0) + this.scrollParent.scrollTop(), left: p.left - (parseInt(this.helper.css("left"),10) || 0) + this.scrollParent.scrollLeft() }; } else { return { top: 0, left: 0 }; } }, _cacheMargins: function() { this.margins = { left: (parseInt(this.element.css("marginLeft"),10) || 0), top: (parseInt(this.element.css("marginTop"),10) || 0), right: (parseInt(this.element.css("marginRight"),10) || 0), bottom: (parseInt(this.element.css("marginBottom"),10) || 0) }; }, _cacheHelperProportions: function() { this.helperProportions = { width: this.helper.outerWidth(), height: this.helper.outerHeight() }; }, _setContainment: function() { var over, c, ce, o = this.options; if(o.containment === "parent") { o.containment = this.helper[0].parentNode; } if(o.containment === "document" || o.containment === "window") { this.containment = [ o.containment === "document" ? 0 : $(window).scrollLeft() - this.offset.relative.left - this.offset.parent.left, o.containment === "document" ? 0 : $(window).scrollTop() - this.offset.relative.top - this.offset.parent.top, (o.containment === "document" ? 0 : $(window).scrollLeft()) + $(o.containment === "document" ? document : window).width() - this.helperProportions.width - this.margins.left, (o.containment === "document" ? 0 : $(window).scrollTop()) + ($(o.containment === "document" ? document : window).height() || document.body.parentNode.scrollHeight) - this.helperProportions.height - this.margins.top ]; } if(!(/^(document|window|parent)$/).test(o.containment) && o.containment.constructor !== Array) { c = $(o.containment); ce = c[0]; if(!ce) { return; } over = ($(ce).css("overflow") !== "hidden"); this.containment = [ (parseInt($(ce).css("borderLeftWidth"),10) || 0) + (parseInt($(ce).css("paddingLeft"),10) || 0), (parseInt($(ce).css("borderTopWidth"),10) || 0) + (parseInt($(ce).css("paddingTop"),10) || 0), (over ? Math.max(ce.scrollWidth,ce.offsetWidth) : ce.offsetWidth) - (parseInt($(ce).css("borderLeftWidth"),10) || 0) - (parseInt($(ce).css("paddingRight"),10) || 0) - this.helperProportions.width - this.margins.left - this.margins.right, (over ? Math.max(ce.scrollHeight,ce.offsetHeight) : ce.offsetHeight) - (parseInt($(ce).css("borderTopWidth"),10) || 0) - (parseInt($(ce).css("paddingBottom"),10) || 0) - this.helperProportions.height - this.margins.top - this.margins.bottom ]; this.relative_container = c; } else if(o.containment.constructor === Array) { this.containment = o.containment; } }, _convertPositionTo: function(d, pos) { if(!pos) { pos = this.position; } var mod = d === "absolute" ? 1 : -1, scroll = this.cssPosition === "absolute" && !(this.scrollParent[0] !== document && $.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent, scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName); return { top: ( pos.top + // The absolute mouse position this.offset.relative.top * mod + // Only for relative positioned nodes: Relative offset from element to offset parent this.offset.parent.top * mod - // The offsetParent's offset without borders (offset + border) ( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ) * mod) ), left: ( pos.left + // The absolute mouse position this.offset.relative.left * mod + // Only for relative positioned nodes: Relative offset from element to offset parent this.offset.parent.left * mod - // The offsetParent's offset without borders (offset + border) ( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ) * mod) ) }; }, _generatePosition: function(event) { var containment, co, top, left, o = this.options, scroll = this.cssPosition === "absolute" && !(this.scrollParent[0] !== document && $.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent, scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName), pageX = event.pageX, pageY = event.pageY; /* * - Position constraining - * Constrain the position to a mix of grid, containment. */ if(this.originalPosition) { //If we are not dragging yet, we won't check for options if(this.containment) { if (this.relative_container){ co = this.relative_container.offset(); containment = [ this.containment[0] + co.left, this.containment[1] + co.top, this.containment[2] + co.left, this.containment[3] + co.top ]; } else { containment = this.containment; } if(event.pageX - this.offset.click.left < containment[0]) { pageX = containment[0] + this.offset.click.left; } if(event.pageY - this.offset.click.top < containment[1]) { pageY = containment[1] + this.offset.click.top; } if(event.pageX - this.offset.click.left > containment[2]) { pageX = containment[2] + this.offset.click.left; } if(event.pageY - this.offset.click.top > containment[3]) { pageY = containment[3] + this.offset.click.top; } } if(o.grid) { //Check for grid elements set to 0 to prevent divide by 0 error causing invalid argument errors in IE (see ticket #6950) top = o.grid[1] ? this.originalPageY + Math.round((pageY - this.originalPageY) / o.grid[1]) * o.grid[1] : this.originalPageY; pageY = containment ? ((top - this.offset.click.top >= containment[1] || top - this.offset.click.top > containment[3]) ? top : ((top - this.offset.click.top >= containment[1]) ? top - o.grid[1] : top + o.grid[1])) : top; left = o.grid[0] ? this.originalPageX + Math.round((pageX - this.originalPageX) / o.grid[0]) * o.grid[0] : this.originalPageX; pageX = containment ? ((left - this.offset.click.left >= containment[0] || left - this.offset.click.left > containment[2]) ? left : ((left - this.offset.click.left >= containment[0]) ? left - o.grid[0] : left + o.grid[0])) : left; } } return { top: ( pageY - // The absolute mouse position this.offset.click.top - // Click offset (relative to the element) this.offset.relative.top - // Only for relative positioned nodes: Relative offset from element to offset parent this.offset.parent.top + // The offsetParent's offset without borders (offset + border) ( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) )) ), left: ( pageX - // The absolute mouse position this.offset.click.left - // Click offset (relative to the element) this.offset.relative.left - // Only for relative positioned nodes: Relative offset from element to offset parent this.offset.parent.left + // The offsetParent's offset without borders (offset + border) ( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() )) ) }; }, _clear: function() { this.helper.removeClass("ui-draggable-dragging"); if(this.helper[0] !== this.element[0] && !this.cancelHelperRemoval) { this.helper.remove(); } this.helper = null; this.cancelHelperRemoval = false; }, // From now on bulk stuff - mainly helpers _trigger: function(type, event, ui) { ui = ui || this._uiHash(); $.ui.plugin.call(this, type, [event, ui]); //The absolute position has to be recalculated after plugins if(type === "drag") { this.positionAbs = this._convertPositionTo("absolute"); } return $.Widget.prototype._trigger.call(this, type, event, ui); }, plugins: {}, _uiHash: function() { return { helper: this.helper, position: this.position, originalPosition: this.originalPosition, offset: this.positionAbs }; } }); $.ui.plugin.add("draggable", "connectToSortable", { start: function(event, ui) { var inst = $(this).data("ui-draggable"), o = inst.options, uiSortable = $.extend({}, ui, { item: inst.element }); inst.sortables = []; $(o.connectToSortable).each(function() { var sortable = $.data(this, "ui-sortable"); if (sortable && !sortable.options.disabled) { inst.sortables.push({ instance: sortable, shouldRevert: sortable.options.revert }); sortable.refreshPositions(); // Call the sortable's refreshPositions at drag start to refresh the containerCache since the sortable container cache is used in drag and needs to be up to date (this will ensure it's initialised as well as being kept in step with any changes that might have happened on the page). sortable._trigger("activate", event, uiSortable); } }); }, stop: function(event, ui) { //If we are still over the sortable, we fake the stop event of the sortable, but also remove helper var inst = $(this).data("ui-draggable"), uiSortable = $.extend({}, ui, { item: inst.element }); $.each(inst.sortables, function() { if(this.instance.isOver) { this.instance.isOver = 0; inst.cancelHelperRemoval = true; //Don't remove the helper in the draggable instance this.instance.cancelHelperRemoval = false; //Remove it in the sortable instance (so sortable plugins like revert still work) //The sortable revert is supported, and we have to set a temporary dropped variable on the draggable to support revert: "valid/invalid" if(this.shouldRevert) { this.instance.options.revert = true; } //Trigger the stop of the sortable this.instance._mouseStop(event); this.instance.options.helper = this.instance.options._helper; //If the helper has been the original item, restore properties in the sortable if(inst.options.helper === "original") { this.instance.currentItem.css({ top: "auto", left: "auto" }); } } else { this.instance.cancelHelperRemoval = false; //Remove the helper in the sortable instance this.instance._trigger("deactivate", event, uiSortable); } }); }, drag: function(event, ui) { var inst = $(this).data("ui-draggable"), that = this; $.each(inst.sortables, function() { var innermostIntersecting = false, thisSortable = this; //Copy over some variables to allow calling the sortable's native _intersectsWith this.instance.positionAbs = inst.positionAbs; this.instance.helperProportions = inst.helperProportions; this.instance.offset.click = inst.offset.click; if(this.instance._intersectsWith(this.instance.containerCache)) { innermostIntersecting = true; $.each(inst.sortables, function () { this.instance.positionAbs = inst.positionAbs; this.instance.helperProportions = inst.helperProportions; this.instance.offset.click = inst.offset.click; if (this !== thisSortable && this.instance._intersectsWith(this.instance.containerCache) && $.contains(thisSortable.instance.element[0], this.instance.element[0]) ) { innermostIntersecting = false; } return innermostIntersecting; }); } if(innermostIntersecting) { //If it intersects, we use a little isOver variable and set it once, so our move-in stuff gets fired only once if(!this.instance.isOver) { this.instance.isOver = 1; //Now we fake the start of dragging for the sortable instance, //by cloning the list group item, appending it to the sortable and using it as inst.currentItem //We can then fire the start event of the sortable with our passed browser event, and our own helper (so it doesn't create a new one) this.instance.currentItem = $(that).clone().removeAttr("id").appendTo(this.instance.element).data("ui-sortable-item", true); this.instance.options._helper = this.instance.options.helper; //Store helper option to later restore it this.instance.options.helper = function() { return ui.helper[0]; }; event.target = this.instance.currentItem[0]; this.instance._mouseCapture(event, true); this.instance._mouseStart(event, true, true); //Because the browser event is way off the new appended portlet, we modify a couple of variables to reflect the changes this.instance.offset.click.top = inst.offset.click.top; this.instance.offset.click.left = inst.offset.click.left; this.instance.offset.parent.left -= inst.offset.parent.left - this.instance.offset.parent.left; this.instance.offset.parent.top -= inst.offset.parent.top - this.instance.offset.parent.top; inst._trigger("toSortable", event); inst.dropped = this.instance.element; //draggable revert needs that //hack so receive/update callbacks work (mostly) inst.currentItem = inst.element; this.instance.fromOutside = inst; } //Provided we did all the previous steps, we can fire the drag event of the sortable on every draggable drag, when it intersects with the sortable if(this.instance.currentItem) { this.instance._mouseDrag(event); } } else { //If it doesn't intersect with the sortable, and it intersected before, //we fake the drag stop of the sortable, but make sure it doesn't remove the helper by using cancelHelperRemoval if(this.instance.isOver) { this.instance.isOver = 0; this.instance.cancelHelperRemoval = true; //Prevent reverting on this forced stop this.instance.options.revert = false; // The out event needs to be triggered independently this.instance._trigger("out", event, this.instance._uiHash(this.instance)); this.instance._mouseStop(event, true); this.instance.options.helper = this.instance.options._helper; //Now we remove our currentItem, the list group clone again, and the placeholder, and animate the helper back to it's original size this.instance.currentItem.remove(); if(this.instance.placeholder) { this.instance.placeholder.remove(); } inst._trigger("fromSortable", event); inst.dropped = false; //draggable revert needs that } } }); } }); $.ui.plugin.add("draggable", "cursor", { start: function() { var t = $("body"), o = $(this).data("ui-draggable").options; if (t.css("cursor")) { o._cursor = t.css("cursor"); } t.css("cursor", o.cursor); }, stop: function() { var o = $(this).data("ui-draggable").options; if (o._cursor) { $("body").css("cursor", o._cursor); } } }); $.ui.plugin.add("draggable", "opacity", { start: function(event, ui) { var t = $(ui.helper), o = $(this).data("ui-draggable").options; if(t.css("opacity")) { o._opacity = t.css("opacity"); } t.css("opacity", o.opacity); }, stop: function(event, ui) { var o = $(this).data("ui-draggable").options; if(o._opacity) { $(ui.helper).css("opacity", o._opacity); } } }); $.ui.plugin.add("draggable", "scroll", { start: function() { var i = $(this).data("ui-draggable"); if(i.scrollParent[0] !== document && i.scrollParent[0].tagName !== "HTML") { i.overflowOffset = i.scrollParent.offset(); } }, drag: function( event ) { var i = $(this).data("ui-draggable"), o = i.options, scrolled = false; if(i.scrollParent[0] !== document && i.scrollParent[0].tagName !== "HTML") { if(!o.axis || o.axis !== "x") { if((i.overflowOffset.top + i.scrollParent[0].offsetHeight) - event.pageY < o.scrollSensitivity) { i.scrollParent[0].scrollTop = scrolled = i.scrollParent[0].scrollTop + o.scrollSpeed; } else if(event.pageY - i.overflowOffset.top < o.scrollSensitivity) { i.scrollParent[0].scrollTop = scrolled = i.scrollParent[0].scrollTop - o.scrollSpeed; } } if(!o.axis || o.axis !== "y") { if((i.overflowOffset.left + i.scrollParent[0].offsetWidth) - event.pageX < o.scrollSensitivity) { i.scrollParent[0].scrollLeft = scrolled = i.scrollParent[0].scrollLeft + o.scrollSpeed; } else if(event.pageX - i.overflowOffset.left < o.scrollSensitivity) { i.scrollParent[0].scrollLeft = scrolled = i.scrollParent[0].scrollLeft - o.scrollSpeed; } } } else { if(!o.axis || o.axis !== "x") { if(event.pageY - $(document).scrollTop() < o.scrollSensitivity) { scrolled = $(document).scrollTop($(document).scrollTop() - o.scrollSpeed); } else if($(window).height() - (event.pageY - $(document).scrollTop()) < o.scrollSensitivity) { scrolled = $(document).scrollTop($(document).scrollTop() + o.scrollSpeed); } } if(!o.axis || o.axis !== "y") { if(event.pageX - $(document).scrollLeft() < o.scrollSensitivity) { scrolled = $(document).scrollLeft($(document).scrollLeft() - o.scrollSpeed); } else if($(window).width() - (event.pageX - $(document).scrollLeft()) < o.scrollSensitivity) { scrolled = $(document).scrollLeft($(document).scrollLeft() + o.scrollSpeed); } } } if(scrolled !== false && $.ui.ddmanager && !o.dropBehaviour) { $.ui.ddmanager.prepareOffsets(i, event); } } }); $.ui.plugin.add("draggable", "snap", { start: function() { var i = $(this).data("ui-draggable"), o = i.options; i.snapElements = []; $(o.snap.constructor !== String ? ( o.snap.items || ":data(ui-draggable)" ) : o.snap).each(function() { var $t = $(this), $o = $t.offset(); if(this !== i.element[0]) { i.snapElements.push({ item: this, width: $t.outerWidth(), height: $t.outerHeight(), top: $o.top, left: $o.left }); } }); }, drag: function(event, ui) { var ts, bs, ls, rs, l, r, t, b, i, first, inst = $(this).data("ui-draggable"), o = inst.options, d = o.snapTolerance, x1 = ui.offset.left, x2 = x1 + inst.helperProportions.width, y1 = ui.offset.top, y2 = y1 + inst.helperProportions.height; for (i = inst.snapElements.length - 1; i >= 0; i--){ l = inst.snapElements[i].left; r = l + inst.snapElements[i].width; t = inst.snapElements[i].top; b = t + inst.snapElements[i].height; //Yes, I know, this is insane ;) if(!((l-d < x1 && x1 < r+d && t-d < y1 && y1 < b+d) || (l-d < x1 && x1 < r+d && t-d < y2 && y2 < b+d) || (l-d < x2 && x2 < r+d && t-d < y1 && y1 < b+d) || (l-d < x2 && x2 < r+d && t-d < y2 && y2 < b+d))) { if(inst.snapElements[i].snapping) { (inst.options.snap.release && inst.options.snap.release.call(inst.element, event, $.extend(inst._uiHash(), { snapItem: inst.snapElements[i].item }))); } inst.snapElements[i].snapping = false; continue; } if(o.snapMode !== "inner") { ts = Math.abs(t - y2) <= d; bs = Math.abs(b - y1) <= d; ls = Math.abs(l - x2) <= d; rs = Math.abs(r - x1) <= d; if(ts) { ui.position.top = inst._convertPositionTo("relative", { top: t - inst.helperProportions.height, left: 0 }).top - inst.margins.top; } if(bs) { ui.position.top = inst._convertPositionTo("relative", { top: b, left: 0 }).top - inst.margins.top; } if(ls) { ui.position.left = inst._convertPositionTo("relative", { top: 0, left: l - inst.helperProportions.width }).left - inst.margins.left; } if(rs) { ui.position.left = inst._convertPositionTo("relative", { top: 0, left: r }).left - inst.margins.left; } } first = (ts || bs || ls || rs); if(o.snapMode !== "outer") { ts = Math.abs(t - y1) <= d; bs = Math.abs(b - y2) <= d; ls = Math.abs(l - x1) <= d; rs = Math.abs(r - x2) <= d; if(ts) { ui.position.top = inst._convertPositionTo("relative", { top: t, left: 0 }).top - inst.margins.top; } if(bs) { ui.position.top = inst._convertPositionTo("relative", { top: b - inst.helperProportions.height, left: 0 }).top - inst.margins.top; } if(ls) { ui.position.left = inst._convertPositionTo("relative", { top: 0, left: l }).left - inst.margins.left; } if(rs) { ui.position.left = inst._convertPositionTo("relative", { top: 0, left: r - inst.helperProportions.width }).left - inst.margins.left; } } if(!inst.snapElements[i].snapping && (ts || bs || ls || rs || first)) { (inst.options.snap.snap && inst.options.snap.snap.call(inst.element, event, $.extend(inst._uiHash(), { snapItem: inst.snapElements[i].item }))); } inst.snapElements[i].snapping = (ts || bs || ls || rs || first); } } }); $.ui.plugin.add("draggable", "stack", { start: function() { var min, o = this.data("ui-draggable").options, group = $.makeArray($(o.stack)).sort(function(a,b) { return (parseInt($(a).css("zIndex"),10) || 0) - (parseInt($(b).css("zIndex"),10) || 0); }); if (!group.length) { return; } min = parseInt($(group[0]).css("zIndex"), 10) || 0; $(group).each(function(i) { $(this).css("zIndex", min + i); }); this.css("zIndex", (min + group.length)); } }); $.ui.plugin.add("draggable", "zIndex", { start: function(event, ui) { var t = $(ui.helper), o = $(this).data("ui-draggable").options; if(t.css("zIndex")) { o._zIndex = t.css("zIndex"); } t.css("zIndex", o.zIndex); }, stop: function(event, ui) { var o = $(this).data("ui-draggable").options; if(o._zIndex) { $(ui.helper).css("zIndex", o._zIndex); } } }); })(jQuery); (function( $, undefined ) { function isOverAxis( x, reference, size ) { return ( x > reference ) && ( x < ( reference + size ) ); } $.widget("ui.droppable", { version: "1.10.1", widgetEventPrefix: "drop", options: { accept: "*", activeClass: false, addClasses: true, greedy: false, hoverClass: false, scope: "default", tolerance: "intersect", // callbacks activate: null, deactivate: null, drop: null, out: null, over: null }, _create: function() { var o = this.options, accept = o.accept; this.isover = false; this.isout = true; this.accept = $.isFunction(accept) ? accept : function(d) { return d.is(accept); }; //Store the droppable's proportions this.proportions = { width: this.element[0].offsetWidth, height: this.element[0].offsetHeight }; // Add the reference and positions to the manager $.ui.ddmanager.droppables[o.scope] = $.ui.ddmanager.droppables[o.scope] || []; $.ui.ddmanager.droppables[o.scope].push(this); (o.addClasses && this.element.addClass("ui-droppable")); }, _destroy: function() { var i = 0, drop = $.ui.ddmanager.droppables[this.options.scope]; for ( ; i < drop.length; i++ ) { if ( drop[i] === this ) { drop.splice(i, 1); } } this.element.removeClass("ui-droppable ui-droppable-disabled"); }, _setOption: function(key, value) { if(key === "accept") { this.accept = $.isFunction(value) ? value : function(d) { return d.is(value); }; } $.Widget.prototype._setOption.apply(this, arguments); }, _activate: function(event) { var draggable = $.ui.ddmanager.current; if(this.options.activeClass) { this.element.addClass(this.options.activeClass); } if(draggable){ this._trigger("activate", event, this.ui(draggable)); } }, _deactivate: function(event) { var draggable = $.ui.ddmanager.current; if(this.options.activeClass) { this.element.removeClass(this.options.activeClass); } if(draggable){ this._trigger("deactivate", event, this.ui(draggable)); } }, _over: function(event) { var draggable = $.ui.ddmanager.current; // Bail if draggable and droppable are same element if (!draggable || (draggable.currentItem || draggable.element)[0] === this.element[0]) { return; } if (this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) { if(this.options.hoverClass) { this.element.addClass(this.options.hoverClass); } this._trigger("over", event, this.ui(draggable)); } }, _out: function(event) { var draggable = $.ui.ddmanager.current; // Bail if draggable and droppable are same element if (!draggable || (draggable.currentItem || draggable.element)[0] === this.element[0]) { return; } if (this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) { if(this.options.hoverClass) { this.element.removeClass(this.options.hoverClass); } this._trigger("out", event, this.ui(draggable)); } }, _drop: function(event,custom) { var draggable = custom || $.ui.ddmanager.current, childrenIntersection = false; // Bail if draggable and droppable are same element if (!draggable || (draggable.currentItem || draggable.element)[0] === this.element[0]) { return false; } this.element.find(":data(ui-droppable)").not(".ui-draggable-dragging").each(function() { var inst = $.data(this, "ui-droppable"); if( inst.options.greedy && !inst.options.disabled && inst.options.scope === draggable.options.scope && inst.accept.call(inst.element[0], (draggable.currentItem || draggable.element)) && $.ui.intersect(draggable, $.extend(inst, { offset: inst.element.offset() }), inst.options.tolerance) ) { childrenIntersection = true; return false; } }); if(childrenIntersection) { return false; } if(this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) { if(this.options.activeClass) { this.element.removeClass(this.options.activeClass); } if(this.options.hoverClass) { this.element.removeClass(this.options.hoverClass); } this._trigger("drop", event, this.ui(draggable)); return this.element; } return false; }, ui: function(c) { return { draggable: (c.currentItem || c.element), helper: c.helper, position: c.position, offset: c.positionAbs }; } }); $.ui.intersect = function(draggable, droppable, toleranceMode) { if (!droppable.offset) { return false; } var draggableLeft, draggableTop, x1 = (draggable.positionAbs || draggable.position.absolute).left, x2 = x1 + draggable.helperProportions.width, y1 = (draggable.positionAbs || draggable.position.absolute).top, y2 = y1 + draggable.helperProportions.height, l = droppable.offset.left, r = l + droppable.proportions.width, t = droppable.offset.top, b = t + droppable.proportions.height; switch (toleranceMode) { case "fit": return (l <= x1 && x2 <= r && t <= y1 && y2 <= b); case "intersect": return (l < x1 + (draggable.helperProportions.width / 2) && // Right Half x2 - (draggable.helperProportions.width / 2) < r && // Left Half t < y1 + (draggable.helperProportions.height / 2) && // Bottom Half y2 - (draggable.helperProportions.height / 2) < b ); // Top Half case "pointer": draggableLeft = ((draggable.positionAbs || draggable.position.absolute).left + (draggable.clickOffset || draggable.offset.click).left); draggableTop = ((draggable.positionAbs || draggable.position.absolute).top + (draggable.clickOffset || draggable.offset.click).top); return isOverAxis( draggableTop, t, droppable.proportions.height ) && isOverAxis( draggableLeft, l, droppable.proportions.width ); case "touch": return ( (y1 >= t && y1 <= b) || // Top edge touching (y2 >= t && y2 <= b) || // Bottom edge touching (y1 < t && y2 > b) // Surrounded vertically ) && ( (x1 >= l && x1 <= r) || // Left edge touching (x2 >= l && x2 <= r) || // Right edge touching (x1 < l && x2 > r) // Surrounded horizontally ); default: return false; } }; /* This manager tracks offsets of draggables and droppables */ $.ui.ddmanager = { current: null, droppables: { "default": [] }, prepareOffsets: function(t, event) { var i, j, m = $.ui.ddmanager.droppables[t.options.scope] || [], type = event ? event.type : null, // workaround for #2317 list = (t.currentItem || t.element).find(":data(ui-droppable)").addBack(); droppablesLoop: for (i = 0; i < m.length; i++) { //No disabled and non-accepted if(m[i].options.disabled || (t && !m[i].accept.call(m[i].element[0],(t.currentItem || t.element)))) { continue; } // Filter out elements in the current dragged item for (j=0; j < list.length; j++) { if(list[j] === m[i].element[0]) { m[i].proportions.height = 0; continue droppablesLoop; } } m[i].visible = m[i].element.css("display") !== "none"; if(!m[i].visible) { continue; } //Activate the droppable if used directly from draggables if(type === "mousedown") { m[i]._activate.call(m[i], event); } m[i].offset = m[i].element.offset(); m[i].proportions = { width: m[i].element[0].offsetWidth, height: m[i].element[0].offsetHeight }; } }, drop: function(draggable, event) { var dropped = false; $.each($.ui.ddmanager.droppables[draggable.options.scope] || [], function() { if(!this.options) { return; } if (!this.options.disabled && this.visible && $.ui.intersect(draggable, this, this.options.tolerance)) { dropped = this._drop.call(this, event) || dropped; } if (!this.options.disabled && this.visible && this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) { this.isout = true; this.isover = false; this._deactivate.call(this, event); } }); return dropped; }, dragStart: function( draggable, event ) { //Listen for scrolling so that if the dragging causes scrolling the position of the droppables can be recalculated (see #5003) draggable.element.parentsUntil( "body" ).bind( "scroll.droppable", function() { if( !draggable.options.refreshPositions ) { $.ui.ddmanager.prepareOffsets( draggable, event ); } }); }, drag: function(draggable, event) { //If you have a highly dynamic page, you might try this option. It renders positions every time you move the mouse. if(draggable.options.refreshPositions) { $.ui.ddmanager.prepareOffsets(draggable, event); } //Run through all droppables and check their positions based on specific tolerance options $.each($.ui.ddmanager.droppables[draggable.options.scope] || [], function() { if(this.options.disabled || this.greedyChild || !this.visible) { return; } var parentInstance, scope, parent, intersects = $.ui.intersect(draggable, this, this.options.tolerance), c = !intersects && this.isover ? "isout" : (intersects && !this.isover ? "isover" : null); if(!c) { return; } if (this.options.greedy) { // find droppable parents with same scope scope = this.options.scope; parent = this.element.parents(":data(ui-droppable)").filter(function () { return $.data(this, "ui-droppable").options.scope === scope; }); if (parent.length) { parentInstance = $.data(parent[0], "ui-droppable"); parentInstance.greedyChild = (c === "isover"); } } // we just moved into a greedy child if (parentInstance && c === "isover") { parentInstance.isover = false; parentInstance.isout = true; parentInstance._out.call(parentInstance, event); } this[c] = true; this[c === "isout" ? "isover" : "isout"] = false; this[c === "isover" ? "_over" : "_out"].call(this, event); // we just moved out of a greedy child if (parentInstance && c === "isout") { parentInstance.isout = false; parentInstance.isover = true; parentInstance._over.call(parentInstance, event); } }); }, dragStop: function( draggable, event ) { draggable.element.parentsUntil( "body" ).unbind( "scroll.droppable" ); //Call prepareOffsets one final time since IE does not fire return scroll events when overflow was caused by drag (see #5003) if( !draggable.options.refreshPositions ) { $.ui.ddmanager.prepareOffsets( draggable, event ); } } }; })(jQuery); (function( $, undefined ) { function num(v) { return parseInt(v, 10) || 0; } function isNumber(value) { return !isNaN(parseInt(value, 10)); } $.widget("ui.resizable", $.ui.mouse, { version: "1.10.1", widgetEventPrefix: "resize", options: { alsoResize: false, animate: false, animateDuration: "slow", animateEasing: "swing", aspectRatio: false, autoHide: false, containment: false, ghost: false, grid: false, handles: "e,s,se", helper: false, maxHeight: null, maxWidth: null, minHeight: 10, minWidth: 10, // See #7960 zIndex: 90, // callbacks resize: null, start: null, stop: null }, _create: function() { var n, i, handle, axis, hname, that = this, o = this.options; this.element.addClass("ui-resizable"); $.extend(this, { _aspectRatio: !!(o.aspectRatio), aspectRatio: o.aspectRatio, originalElement: this.element, _proportionallyResizeElements: [], _helper: o.helper || o.ghost || o.animate ? o.helper || "ui-resizable-helper" : null }); //Wrap the element if it cannot hold child nodes if(this.element[0].nodeName.match(/canvas|textarea|input|select|button|img/i)) { //Create a wrapper element and set the wrapper to the new current internal element this.element.wrap( $("
        ").css({ position: this.element.css("position"), width: this.element.outerWidth(), height: this.element.outerHeight(), top: this.element.css("top"), left: this.element.css("left") }) ); //Overwrite the original this.element this.element = this.element.parent().data( "ui-resizable", this.element.data("ui-resizable") ); this.elementIsWrapper = true; //Move margins to the wrapper this.element.css({ marginLeft: this.originalElement.css("marginLeft"), marginTop: this.originalElement.css("marginTop"), marginRight: this.originalElement.css("marginRight"), marginBottom: this.originalElement.css("marginBottom") }); this.originalElement.css({ marginLeft: 0, marginTop: 0, marginRight: 0, marginBottom: 0}); //Prevent Safari textarea resize this.originalResizeStyle = this.originalElement.css("resize"); this.originalElement.css("resize", "none"); //Push the actual element to our proportionallyResize internal array this._proportionallyResizeElements.push(this.originalElement.css({ position: "static", zoom: 1, display: "block" })); // avoid IE jump (hard set the margin) this.originalElement.css({ margin: this.originalElement.css("margin") }); // fix handlers offset this._proportionallyResize(); } this.handles = o.handles || (!$(".ui-resizable-handle", this.element).length ? "e,s,se" : { n: ".ui-resizable-n", e: ".ui-resizable-e", s: ".ui-resizable-s", w: ".ui-resizable-w", se: ".ui-resizable-se", sw: ".ui-resizable-sw", ne: ".ui-resizable-ne", nw: ".ui-resizable-nw" }); if(this.handles.constructor === String) { if ( this.handles === "all") { this.handles = "n,e,s,w,se,sw,ne,nw"; } n = this.handles.split(","); this.handles = {}; for(i = 0; i < n.length; i++) { handle = $.trim(n[i]); hname = "ui-resizable-"+handle; axis = $("
        "); // Apply zIndex to all handles - see #7960 axis.css({ zIndex: o.zIndex }); //TODO : What's going on here? if ("se" === handle) { axis.addClass("ui-icon ui-icon-gripsmall-diagonal-se"); } //Insert into internal handles object and append to element this.handles[handle] = ".ui-resizable-"+handle; this.element.append(axis); } } this._renderAxis = function(target) { var i, axis, padPos, padWrapper; target = target || this.element; for(i in this.handles) { if(this.handles[i].constructor === String) { this.handles[i] = $(this.handles[i], this.element).show(); } //Apply pad to wrapper element, needed to fix axis position (textarea, inputs, scrolls) if (this.elementIsWrapper && this.originalElement[0].nodeName.match(/textarea|input|select|button/i)) { axis = $(this.handles[i], this.element); //Checking the correct pad and border padWrapper = /sw|ne|nw|se|n|s/.test(i) ? axis.outerHeight() : axis.outerWidth(); //The padding type i have to apply... padPos = [ "padding", /ne|nw|n/.test(i) ? "Top" : /se|sw|s/.test(i) ? "Bottom" : /^e$/.test(i) ? "Right" : "Left" ].join(""); target.css(padPos, padWrapper); this._proportionallyResize(); } //TODO: What's that good for? There's not anything to be executed left if(!$(this.handles[i]).length) { continue; } } }; //TODO: make renderAxis a prototype function this._renderAxis(this.element); this._handles = $(".ui-resizable-handle", this.element) .disableSelection(); //Matching axis name this._handles.mouseover(function() { if (!that.resizing) { if (this.className) { axis = this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i); } //Axis, default = se that.axis = axis && axis[1] ? axis[1] : "se"; } }); //If we want to auto hide the elements if (o.autoHide) { this._handles.hide(); $(this.element) .addClass("ui-resizable-autohide") .mouseenter(function() { if (o.disabled) { return; } $(this).removeClass("ui-resizable-autohide"); that._handles.show(); }) .mouseleave(function(){ if (o.disabled) { return; } if (!that.resizing) { $(this).addClass("ui-resizable-autohide"); that._handles.hide(); } }); } //Initialize the mouse interaction this._mouseInit(); }, _destroy: function() { this._mouseDestroy(); var wrapper, _destroy = function(exp) { $(exp).removeClass("ui-resizable ui-resizable-disabled ui-resizable-resizing") .removeData("resizable").removeData("ui-resizable").unbind(".resizable").find(".ui-resizable-handle").remove(); }; //TODO: Unwrap at same DOM position if (this.elementIsWrapper) { _destroy(this.element); wrapper = this.element; this.originalElement.css({ position: wrapper.css("position"), width: wrapper.outerWidth(), height: wrapper.outerHeight(), top: wrapper.css("top"), left: wrapper.css("left") }).insertAfter( wrapper ); wrapper.remove(); } this.originalElement.css("resize", this.originalResizeStyle); _destroy(this.originalElement); return this; }, _mouseCapture: function(event) { var i, handle, capture = false; for (i in this.handles) { handle = $(this.handles[i])[0]; if (handle === event.target || $.contains(handle, event.target)) { capture = true; } } return !this.options.disabled && capture; }, _mouseStart: function(event) { var curleft, curtop, cursor, o = this.options, iniPos = this.element.position(), el = this.element; this.resizing = true; // bugfix for http://dev.jquery.com/ticket/1749 if ( (/absolute/).test( el.css("position") ) ) { el.css({ position: "absolute", top: el.css("top"), left: el.css("left") }); } else if (el.is(".ui-draggable")) { el.css({ position: "absolute", top: iniPos.top, left: iniPos.left }); } this._renderProxy(); curleft = num(this.helper.css("left")); curtop = num(this.helper.css("top")); if (o.containment) { curleft += $(o.containment).scrollLeft() || 0; curtop += $(o.containment).scrollTop() || 0; } //Store needed variables this.offset = this.helper.offset(); this.position = { left: curleft, top: curtop }; this.size = this._helper ? { width: el.outerWidth(), height: el.outerHeight() } : { width: el.width(), height: el.height() }; this.originalSize = this._helper ? { width: el.outerWidth(), height: el.outerHeight() } : { width: el.width(), height: el.height() }; this.originalPosition = { left: curleft, top: curtop }; this.sizeDiff = { width: el.outerWidth() - el.width(), height: el.outerHeight() - el.height() }; this.originalMousePosition = { left: event.pageX, top: event.pageY }; //Aspect Ratio this.aspectRatio = (typeof o.aspectRatio === "number") ? o.aspectRatio : ((this.originalSize.width / this.originalSize.height) || 1); cursor = $(".ui-resizable-" + this.axis).css("cursor"); $("body").css("cursor", cursor === "auto" ? this.axis + "-resize" : cursor); el.addClass("ui-resizable-resizing"); this._propagate("start", event); return true; }, _mouseDrag: function(event) { //Increase performance, avoid regex var data, el = this.helper, props = {}, smp = this.originalMousePosition, a = this.axis, prevTop = this.position.top, prevLeft = this.position.left, prevWidth = this.size.width, prevHeight = this.size.height, dx = (event.pageX-smp.left)||0, dy = (event.pageY-smp.top)||0, trigger = this._change[a]; if (!trigger) { return false; } // Calculate the attrs that will be change data = trigger.apply(this, [event, dx, dy]); // Put this in the mouseDrag handler since the user can start pressing shift while resizing this._updateVirtualBoundaries(event.shiftKey); if (this._aspectRatio || event.shiftKey) { data = this._updateRatio(data, event); } data = this._respectSize(data, event); this._updateCache(data); // plugins callbacks need to be called first this._propagate("resize", event); if (this.position.top !== prevTop) { props.top = this.position.top + "px"; } if (this.position.left !== prevLeft) { props.left = this.position.left + "px"; } if (this.size.width !== prevWidth) { props.width = this.size.width + "px"; } if (this.size.height !== prevHeight) { props.height = this.size.height + "px"; } el.css(props); if (!this._helper && this._proportionallyResizeElements.length) { this._proportionallyResize(); } // Call the user callback if the element was resized if ( ! $.isEmptyObject(props) ) { this._trigger("resize", event, this.ui()); } return false; }, _mouseStop: function(event) { this.resizing = false; var pr, ista, soffseth, soffsetw, s, left, top, o = this.options, that = this; if(this._helper) { pr = this._proportionallyResizeElements; ista = pr.length && (/textarea/i).test(pr[0].nodeName); soffseth = ista && $.ui.hasScroll(pr[0], "left") /* TODO - jump height */ ? 0 : that.sizeDiff.height; soffsetw = ista ? 0 : that.sizeDiff.width; s = { width: (that.helper.width() - soffsetw), height: (that.helper.height() - soffseth) }; left = (parseInt(that.element.css("left"), 10) + (that.position.left - that.originalPosition.left)) || null; top = (parseInt(that.element.css("top"), 10) + (that.position.top - that.originalPosition.top)) || null; if (!o.animate) { this.element.css($.extend(s, { top: top, left: left })); } that.helper.height(that.size.height); that.helper.width(that.size.width); if (this._helper && !o.animate) { this._proportionallyResize(); } } $("body").css("cursor", "auto"); this.element.removeClass("ui-resizable-resizing"); this._propagate("stop", event); if (this._helper) { this.helper.remove(); } return false; }, _updateVirtualBoundaries: function(forceAspectRatio) { var pMinWidth, pMaxWidth, pMinHeight, pMaxHeight, b, o = this.options; b = { minWidth: isNumber(o.minWidth) ? o.minWidth : 0, maxWidth: isNumber(o.maxWidth) ? o.maxWidth : Infinity, minHeight: isNumber(o.minHeight) ? o.minHeight : 0, maxHeight: isNumber(o.maxHeight) ? o.maxHeight : Infinity }; if(this._aspectRatio || forceAspectRatio) { // We want to create an enclosing box whose aspect ration is the requested one // First, compute the "projected" size for each dimension based on the aspect ratio and other dimension pMinWidth = b.minHeight * this.aspectRatio; pMinHeight = b.minWidth / this.aspectRatio; pMaxWidth = b.maxHeight * this.aspectRatio; pMaxHeight = b.maxWidth / this.aspectRatio; if(pMinWidth > b.minWidth) { b.minWidth = pMinWidth; } if(pMinHeight > b.minHeight) { b.minHeight = pMinHeight; } if(pMaxWidth < b.maxWidth) { b.maxWidth = pMaxWidth; } if(pMaxHeight < b.maxHeight) { b.maxHeight = pMaxHeight; } } this._vBoundaries = b; }, _updateCache: function(data) { this.offset = this.helper.offset(); if (isNumber(data.left)) { this.position.left = data.left; } if (isNumber(data.top)) { this.position.top = data.top; } if (isNumber(data.height)) { this.size.height = data.height; } if (isNumber(data.width)) { this.size.width = data.width; } }, _updateRatio: function( data ) { var cpos = this.position, csize = this.size, a = this.axis; if (isNumber(data.height)) { data.width = (data.height * this.aspectRatio); } else if (isNumber(data.width)) { data.height = (data.width / this.aspectRatio); } if (a === "sw") { data.left = cpos.left + (csize.width - data.width); data.top = null; } if (a === "nw") { data.top = cpos.top + (csize.height - data.height); data.left = cpos.left + (csize.width - data.width); } return data; }, _respectSize: function( data ) { var o = this._vBoundaries, a = this.axis, ismaxw = isNumber(data.width) && o.maxWidth && (o.maxWidth < data.width), ismaxh = isNumber(data.height) && o.maxHeight && (o.maxHeight < data.height), isminw = isNumber(data.width) && o.minWidth && (o.minWidth > data.width), isminh = isNumber(data.height) && o.minHeight && (o.minHeight > data.height), dw = this.originalPosition.left + this.originalSize.width, dh = this.position.top + this.size.height, cw = /sw|nw|w/.test(a), ch = /nw|ne|n/.test(a); if (isminw) { data.width = o.minWidth; } if (isminh) { data.height = o.minHeight; } if (ismaxw) { data.width = o.maxWidth; } if (ismaxh) { data.height = o.maxHeight; } if (isminw && cw) { data.left = dw - o.minWidth; } if (ismaxw && cw) { data.left = dw - o.maxWidth; } if (isminh && ch) { data.top = dh - o.minHeight; } if (ismaxh && ch) { data.top = dh - o.maxHeight; } // fixing jump error on top/left - bug #2330 if (!data.width && !data.height && !data.left && data.top) { data.top = null; } else if (!data.width && !data.height && !data.top && data.left) { data.left = null; } return data; }, _proportionallyResize: function() { if (!this._proportionallyResizeElements.length) { return; } var i, j, borders, paddings, prel, element = this.helper || this.element; for ( i=0; i < this._proportionallyResizeElements.length; i++) { prel = this._proportionallyResizeElements[i]; if (!this.borderDif) { this.borderDif = []; borders = [prel.css("borderTopWidth"), prel.css("borderRightWidth"), prel.css("borderBottomWidth"), prel.css("borderLeftWidth")]; paddings = [prel.css("paddingTop"), prel.css("paddingRight"), prel.css("paddingBottom"), prel.css("paddingLeft")]; for ( j = 0; j < borders.length; j++ ) { this.borderDif[ j ] = ( parseInt( borders[ j ], 10 ) || 0 ) + ( parseInt( paddings[ j ], 10 ) || 0 ); } } prel.css({ height: (element.height() - this.borderDif[0] - this.borderDif[2]) || 0, width: (element.width() - this.borderDif[1] - this.borderDif[3]) || 0 }); } }, _renderProxy: function() { var el = this.element, o = this.options; this.elementOffset = el.offset(); if(this._helper) { this.helper = this.helper || $("
        "); this.helper.addClass(this._helper).css({ width: this.element.outerWidth() - 1, height: this.element.outerHeight() - 1, position: "absolute", left: this.elementOffset.left +"px", top: this.elementOffset.top +"px", zIndex: ++o.zIndex //TODO: Don't modify option }); this.helper .appendTo("body") .disableSelection(); } else { this.helper = this.element; } }, _change: { e: function(event, dx) { return { width: this.originalSize.width + dx }; }, w: function(event, dx) { var cs = this.originalSize, sp = this.originalPosition; return { left: sp.left + dx, width: cs.width - dx }; }, n: function(event, dx, dy) { var cs = this.originalSize, sp = this.originalPosition; return { top: sp.top + dy, height: cs.height - dy }; }, s: function(event, dx, dy) { return { height: this.originalSize.height + dy }; }, se: function(event, dx, dy) { return $.extend(this._change.s.apply(this, arguments), this._change.e.apply(this, [event, dx, dy])); }, sw: function(event, dx, dy) { return $.extend(this._change.s.apply(this, arguments), this._change.w.apply(this, [event, dx, dy])); }, ne: function(event, dx, dy) { return $.extend(this._change.n.apply(this, arguments), this._change.e.apply(this, [event, dx, dy])); }, nw: function(event, dx, dy) { return $.extend(this._change.n.apply(this, arguments), this._change.w.apply(this, [event, dx, dy])); } }, _propagate: function(n, event) { $.ui.plugin.call(this, n, [event, this.ui()]); (n !== "resize" && this._trigger(n, event, this.ui())); }, plugins: {}, ui: function() { return { originalElement: this.originalElement, element: this.element, helper: this.helper, position: this.position, size: this.size, originalSize: this.originalSize, originalPosition: this.originalPosition }; } }); /* * Resizable Extensions */ $.ui.plugin.add("resizable", "animate", { stop: function( event ) { var that = $(this).data("ui-resizable"), o = that.options, pr = that._proportionallyResizeElements, ista = pr.length && (/textarea/i).test(pr[0].nodeName), soffseth = ista && $.ui.hasScroll(pr[0], "left") /* TODO - jump height */ ? 0 : that.sizeDiff.height, soffsetw = ista ? 0 : that.sizeDiff.width, style = { width: (that.size.width - soffsetw), height: (that.size.height - soffseth) }, left = (parseInt(that.element.css("left"), 10) + (that.position.left - that.originalPosition.left)) || null, top = (parseInt(that.element.css("top"), 10) + (that.position.top - that.originalPosition.top)) || null; that.element.animate( $.extend(style, top && left ? { top: top, left: left } : {}), { duration: o.animateDuration, easing: o.animateEasing, step: function() { var data = { width: parseInt(that.element.css("width"), 10), height: parseInt(that.element.css("height"), 10), top: parseInt(that.element.css("top"), 10), left: parseInt(that.element.css("left"), 10) }; if (pr && pr.length) { $(pr[0]).css({ width: data.width, height: data.height }); } // propagating resize, and updating values for each animation step that._updateCache(data); that._propagate("resize", event); } } ); } }); $.ui.plugin.add("resizable", "containment", { start: function() { var element, p, co, ch, cw, width, height, that = $(this).data("ui-resizable"), o = that.options, el = that.element, oc = o.containment, ce = (oc instanceof $) ? oc.get(0) : (/parent/.test(oc)) ? el.parent().get(0) : oc; if (!ce) { return; } that.containerElement = $(ce); if (/document/.test(oc) || oc === document) { that.containerOffset = { left: 0, top: 0 }; that.containerPosition = { left: 0, top: 0 }; that.parentData = { element: $(document), left: 0, top: 0, width: $(document).width(), height: $(document).height() || document.body.parentNode.scrollHeight }; } // i'm a node, so compute top, left, right, bottom else { element = $(ce); p = []; $([ "Top", "Right", "Left", "Bottom" ]).each(function(i, name) { p[i] = num(element.css("padding" + name)); }); that.containerOffset = element.offset(); that.containerPosition = element.position(); that.containerSize = { height: (element.innerHeight() - p[3]), width: (element.innerWidth() - p[1]) }; co = that.containerOffset; ch = that.containerSize.height; cw = that.containerSize.width; width = ($.ui.hasScroll(ce, "left") ? ce.scrollWidth : cw ); height = ($.ui.hasScroll(ce) ? ce.scrollHeight : ch); that.parentData = { element: ce, left: co.left, top: co.top, width: width, height: height }; } }, resize: function( event ) { var woset, hoset, isParent, isOffsetRelative, that = $(this).data("ui-resizable"), o = that.options, co = that.containerOffset, cp = that.position, pRatio = that._aspectRatio || event.shiftKey, cop = { top:0, left:0 }, ce = that.containerElement; if (ce[0] !== document && (/static/).test(ce.css("position"))) { cop = co; } if (cp.left < (that._helper ? co.left : 0)) { that.size.width = that.size.width + (that._helper ? (that.position.left - co.left) : (that.position.left - cop.left)); if (pRatio) { that.size.height = that.size.width / that.aspectRatio; } that.position.left = o.helper ? co.left : 0; } if (cp.top < (that._helper ? co.top : 0)) { that.size.height = that.size.height + (that._helper ? (that.position.top - co.top) : that.position.top); if (pRatio) { that.size.width = that.size.height * that.aspectRatio; } that.position.top = that._helper ? co.top : 0; } that.offset.left = that.parentData.left+that.position.left; that.offset.top = that.parentData.top+that.position.top; woset = Math.abs( (that._helper ? that.offset.left - cop.left : (that.offset.left - cop.left)) + that.sizeDiff.width ); hoset = Math.abs( (that._helper ? that.offset.top - cop.top : (that.offset.top - co.top)) + that.sizeDiff.height ); isParent = that.containerElement.get(0) === that.element.parent().get(0); isOffsetRelative = /relative|absolute/.test(that.containerElement.css("position")); if(isParent && isOffsetRelative) { woset -= that.parentData.left; } if (woset + that.size.width >= that.parentData.width) { that.size.width = that.parentData.width - woset; if (pRatio) { that.size.height = that.size.width / that.aspectRatio; } } if (hoset + that.size.height >= that.parentData.height) { that.size.height = that.parentData.height - hoset; if (pRatio) { that.size.width = that.size.height * that.aspectRatio; } } }, stop: function(){ var that = $(this).data("ui-resizable"), o = that.options, co = that.containerOffset, cop = that.containerPosition, ce = that.containerElement, helper = $(that.helper), ho = helper.offset(), w = helper.outerWidth() - that.sizeDiff.width, h = helper.outerHeight() - that.sizeDiff.height; if (that._helper && !o.animate && (/relative/).test(ce.css("position"))) { $(this).css({ left: ho.left - cop.left - co.left, width: w, height: h }); } if (that._helper && !o.animate && (/static/).test(ce.css("position"))) { $(this).css({ left: ho.left - cop.left - co.left, width: w, height: h }); } } }); $.ui.plugin.add("resizable", "alsoResize", { start: function () { var that = $(this).data("ui-resizable"), o = that.options, _store = function (exp) { $(exp).each(function() { var el = $(this); el.data("ui-resizable-alsoresize", { width: parseInt(el.width(), 10), height: parseInt(el.height(), 10), left: parseInt(el.css("left"), 10), top: parseInt(el.css("top"), 10) }); }); }; if (typeof(o.alsoResize) === "object" && !o.alsoResize.parentNode) { if (o.alsoResize.length) { o.alsoResize = o.alsoResize[0]; _store(o.alsoResize); } else { $.each(o.alsoResize, function (exp) { _store(exp); }); } }else{ _store(o.alsoResize); } }, resize: function (event, ui) { var that = $(this).data("ui-resizable"), o = that.options, os = that.originalSize, op = that.originalPosition, delta = { height: (that.size.height - os.height) || 0, width: (that.size.width - os.width) || 0, top: (that.position.top - op.top) || 0, left: (that.position.left - op.left) || 0 }, _alsoResize = function (exp, c) { $(exp).each(function() { var el = $(this), start = $(this).data("ui-resizable-alsoresize"), style = {}, css = c && c.length ? c : el.parents(ui.originalElement[0]).length ? ["width", "height"] : ["width", "height", "top", "left"]; $.each(css, function (i, prop) { var sum = (start[prop]||0) + (delta[prop]||0); if (sum && sum >= 0) { style[prop] = sum || null; } }); el.css(style); }); }; if (typeof(o.alsoResize) === "object" && !o.alsoResize.nodeType) { $.each(o.alsoResize, function (exp, c) { _alsoResize(exp, c); }); }else{ _alsoResize(o.alsoResize); } }, stop: function () { $(this).removeData("resizable-alsoresize"); } }); $.ui.plugin.add("resizable", "ghost", { start: function() { var that = $(this).data("ui-resizable"), o = that.options, cs = that.size; that.ghost = that.originalElement.clone(); that.ghost .css({ opacity: 0.25, display: "block", position: "relative", height: cs.height, width: cs.width, margin: 0, left: 0, top: 0 }) .addClass("ui-resizable-ghost") .addClass(typeof o.ghost === "string" ? o.ghost : ""); that.ghost.appendTo(that.helper); }, resize: function(){ var that = $(this).data("ui-resizable"); if (that.ghost) { that.ghost.css({ position: "relative", height: that.size.height, width: that.size.width }); } }, stop: function() { var that = $(this).data("ui-resizable"); if (that.ghost && that.helper) { that.helper.get(0).removeChild(that.ghost.get(0)); } } }); $.ui.plugin.add("resizable", "grid", { resize: function() { var that = $(this).data("ui-resizable"), o = that.options, cs = that.size, os = that.originalSize, op = that.originalPosition, a = that.axis, grid = typeof o.grid === "number" ? [o.grid, o.grid] : o.grid, gridX = (grid[0]||1), gridY = (grid[1]||1), ox = Math.round((cs.width - os.width) / gridX) * gridX, oy = Math.round((cs.height - os.height) / gridY) * gridY, newWidth = os.width + ox, newHeight = os.height + oy, isMaxWidth = o.maxWidth && (o.maxWidth < newWidth), isMaxHeight = o.maxHeight && (o.maxHeight < newHeight), isMinWidth = o.minWidth && (o.minWidth > newWidth), isMinHeight = o.minHeight && (o.minHeight > newHeight); o.grid = grid; if (isMinWidth) { newWidth = newWidth + gridX; } if (isMinHeight) { newHeight = newHeight + gridY; } if (isMaxWidth) { newWidth = newWidth - gridX; } if (isMaxHeight) { newHeight = newHeight - gridY; } if (/^(se|s|e)$/.test(a)) { that.size.width = newWidth; that.size.height = newHeight; } else if (/^(ne)$/.test(a)) { that.size.width = newWidth; that.size.height = newHeight; that.position.top = op.top - oy; } else if (/^(sw)$/.test(a)) { that.size.width = newWidth; that.size.height = newHeight; that.position.left = op.left - ox; } else { that.size.width = newWidth; that.size.height = newHeight; that.position.top = op.top - oy; that.position.left = op.left - ox; } } }); })(jQuery); (function( $, undefined ) { $.widget("ui.selectable", $.ui.mouse, { version: "1.10.1", options: { appendTo: "body", autoRefresh: true, distance: 0, filter: "*", tolerance: "touch", // callbacks selected: null, selecting: null, start: null, stop: null, unselected: null, unselecting: null }, _create: function() { var selectees, that = this; this.element.addClass("ui-selectable"); this.dragged = false; // cache selectee children based on filter this.refresh = function() { selectees = $(that.options.filter, that.element[0]); selectees.addClass("ui-selectee"); selectees.each(function() { var $this = $(this), pos = $this.offset(); $.data(this, "selectable-item", { element: this, $element: $this, left: pos.left, top: pos.top, right: pos.left + $this.outerWidth(), bottom: pos.top + $this.outerHeight(), startselected: false, selected: $this.hasClass("ui-selected"), selecting: $this.hasClass("ui-selecting"), unselecting: $this.hasClass("ui-unselecting") }); }); }; this.refresh(); this.selectees = selectees.addClass("ui-selectee"); this._mouseInit(); this.helper = $("
        "); }, _destroy: function() { this.selectees .removeClass("ui-selectee") .removeData("selectable-item"); this.element .removeClass("ui-selectable ui-selectable-disabled"); this._mouseDestroy(); }, _mouseStart: function(event) { var that = this, options = this.options; this.opos = [event.pageX, event.pageY]; if (this.options.disabled) { return; } this.selectees = $(options.filter, this.element[0]); this._trigger("start", event); $(options.appendTo).append(this.helper); // position helper (lasso) this.helper.css({ "left": event.pageX, "top": event.pageY, "width": 0, "height": 0 }); if (options.autoRefresh) { this.refresh(); } this.selectees.filter(".ui-selected").each(function() { var selectee = $.data(this, "selectable-item"); selectee.startselected = true; if (!event.metaKey && !event.ctrlKey) { selectee.$element.removeClass("ui-selected"); selectee.selected = false; selectee.$element.addClass("ui-unselecting"); selectee.unselecting = true; // selectable UNSELECTING callback that._trigger("unselecting", event, { unselecting: selectee.element }); } }); $(event.target).parents().addBack().each(function() { var doSelect, selectee = $.data(this, "selectable-item"); if (selectee) { doSelect = (!event.metaKey && !event.ctrlKey) || !selectee.$element.hasClass("ui-selected"); selectee.$element .removeClass(doSelect ? "ui-unselecting" : "ui-selected") .addClass(doSelect ? "ui-selecting" : "ui-unselecting"); selectee.unselecting = !doSelect; selectee.selecting = doSelect; selectee.selected = doSelect; // selectable (UN)SELECTING callback if (doSelect) { that._trigger("selecting", event, { selecting: selectee.element }); } else { that._trigger("unselecting", event, { unselecting: selectee.element }); } return false; } }); }, _mouseDrag: function(event) { this.dragged = true; if (this.options.disabled) { return; } var tmp, that = this, options = this.options, x1 = this.opos[0], y1 = this.opos[1], x2 = event.pageX, y2 = event.pageY; if (x1 > x2) { tmp = x2; x2 = x1; x1 = tmp; } if (y1 > y2) { tmp = y2; y2 = y1; y1 = tmp; } this.helper.css({left: x1, top: y1, width: x2-x1, height: y2-y1}); this.selectees.each(function() { var selectee = $.data(this, "selectable-item"), hit = false; //prevent helper from being selected if appendTo: selectable if (!selectee || selectee.element === that.element[0]) { return; } if (options.tolerance === "touch") { hit = ( !(selectee.left > x2 || selectee.right < x1 || selectee.top > y2 || selectee.bottom < y1) ); } else if (options.tolerance === "fit") { hit = (selectee.left > x1 && selectee.right < x2 && selectee.top > y1 && selectee.bottom < y2); } if (hit) { // SELECT if (selectee.selected) { selectee.$element.removeClass("ui-selected"); selectee.selected = false; } if (selectee.unselecting) { selectee.$element.removeClass("ui-unselecting"); selectee.unselecting = false; } if (!selectee.selecting) { selectee.$element.addClass("ui-selecting"); selectee.selecting = true; // selectable SELECTING callback that._trigger("selecting", event, { selecting: selectee.element }); } } else { // UNSELECT if (selectee.selecting) { if ((event.metaKey || event.ctrlKey) && selectee.startselected) { selectee.$element.removeClass("ui-selecting"); selectee.selecting = false; selectee.$element.addClass("ui-selected"); selectee.selected = true; } else { selectee.$element.removeClass("ui-selecting"); selectee.selecting = false; if (selectee.startselected) { selectee.$element.addClass("ui-unselecting"); selectee.unselecting = true; } // selectable UNSELECTING callback that._trigger("unselecting", event, { unselecting: selectee.element }); } } if (selectee.selected) { if (!event.metaKey && !event.ctrlKey && !selectee.startselected) { selectee.$element.removeClass("ui-selected"); selectee.selected = false; selectee.$element.addClass("ui-unselecting"); selectee.unselecting = true; // selectable UNSELECTING callback that._trigger("unselecting", event, { unselecting: selectee.element }); } } } }); return false; }, _mouseStop: function(event) { var that = this; this.dragged = false; $(".ui-unselecting", this.element[0]).each(function() { var selectee = $.data(this, "selectable-item"); selectee.$element.removeClass("ui-unselecting"); selectee.unselecting = false; selectee.startselected = false; that._trigger("unselected", event, { unselected: selectee.element }); }); $(".ui-selecting", this.element[0]).each(function() { var selectee = $.data(this, "selectable-item"); selectee.$element.removeClass("ui-selecting").addClass("ui-selected"); selectee.selecting = false; selectee.selected = true; selectee.startselected = true; that._trigger("selected", event, { selected: selectee.element }); }); this._trigger("stop", event); this.helper.remove(); return false; } }); })(jQuery); (function( $, undefined ) { /*jshint loopfunc: true */ function isOverAxis( x, reference, size ) { return ( x > reference ) && ( x < ( reference + size ) ); } $.widget("ui.sortable", $.ui.mouse, { version: "1.10.1", widgetEventPrefix: "sort", ready: false, options: { appendTo: "parent", axis: false, connectWith: false, containment: false, cursor: "auto", cursorAt: false, dropOnEmpty: true, forcePlaceholderSize: false, forceHelperSize: false, grid: false, handle: false, helper: "original", items: "> *", opacity: false, placeholder: false, revert: false, scroll: true, scrollSensitivity: 20, scrollSpeed: 20, scope: "default", tolerance: "intersect", zIndex: 1000, // callbacks activate: null, beforeStop: null, change: null, deactivate: null, out: null, over: null, receive: null, remove: null, sort: null, start: null, stop: null, update: null }, _create: function() { var o = this.options; this.containerCache = {}; this.element.addClass("ui-sortable"); //Get the items this.refresh(); //Let's determine if the items are being displayed horizontally this.floating = this.items.length ? o.axis === "x" || (/left|right/).test(this.items[0].item.css("float")) || (/inline|table-cell/).test(this.items[0].item.css("display")) : false; //Let's determine the parent's offset this.offset = this.element.offset(); //Initialize mouse events for interaction this._mouseInit(); //We're ready to go this.ready = true; }, _destroy: function() { this.element .removeClass("ui-sortable ui-sortable-disabled"); this._mouseDestroy(); for ( var i = this.items.length - 1; i >= 0; i-- ) { this.items[i].item.removeData(this.widgetName + "-item"); } return this; }, _setOption: function(key, value){ if ( key === "disabled" ) { this.options[ key ] = value; this.widget().toggleClass( "ui-sortable-disabled", !!value ); } else { // Don't call widget base _setOption for disable as it adds ui-state-disabled class $.Widget.prototype._setOption.apply(this, arguments); } }, _mouseCapture: function(event, overrideHandle) { var currentItem = null, validHandle = false, that = this; if (this.reverting) { return false; } if(this.options.disabled || this.options.type === "static") { return false; } //We have to refresh the items data once first this._refreshItems(event); //Find out if the clicked node (or one of its parents) is a actual item in this.items $(event.target).parents().each(function() { if($.data(this, that.widgetName + "-item") === that) { currentItem = $(this); return false; } }); if($.data(event.target, that.widgetName + "-item") === that) { currentItem = $(event.target); } if(!currentItem) { return false; } if(this.options.handle && !overrideHandle) { $(this.options.handle, currentItem).find("*").addBack().each(function() { if(this === event.target) { validHandle = true; } }); if(!validHandle) { return false; } } this.currentItem = currentItem; this._removeCurrentsFromItems(); return true; }, _mouseStart: function(event, overrideHandle, noActivation) { var i, o = this.options; this.currentContainer = this; //We only need to call refreshPositions, because the refreshItems call has been moved to mouseCapture this.refreshPositions(); //Create and append the visible helper this.helper = this._createHelper(event); //Cache the helper size this._cacheHelperProportions(); /* * - Position generation - * This block generates everything position related - it's the core of draggables. */ //Cache the margins of the original element this._cacheMargins(); //Get the next scrolling parent this.scrollParent = this.helper.scrollParent(); //The element's absolute position on the page minus margins this.offset = this.currentItem.offset(); this.offset = { top: this.offset.top - this.margins.top, left: this.offset.left - this.margins.left }; $.extend(this.offset, { click: { //Where the click happened, relative to the element left: event.pageX - this.offset.left, top: event.pageY - this.offset.top }, parent: this._getParentOffset(), relative: this._getRelativeOffset() //This is a relative to absolute position minus the actual position calculation - only used for relative positioned helper }); // Only after we got the offset, we can change the helper's position to absolute // TODO: Still need to figure out a way to make relative sorting possible this.helper.css("position", "absolute"); this.cssPosition = this.helper.css("position"); //Generate the original position this.originalPosition = this._generatePosition(event); this.originalPageX = event.pageX; this.originalPageY = event.pageY; //Adjust the mouse offset relative to the helper if "cursorAt" is supplied (o.cursorAt && this._adjustOffsetFromHelper(o.cursorAt)); //Cache the former DOM position this.domPosition = { prev: this.currentItem.prev()[0], parent: this.currentItem.parent()[0] }; //If the helper is not the original, hide the original so it's not playing any role during the drag, won't cause anything bad this way if(this.helper[0] !== this.currentItem[0]) { this.currentItem.hide(); } //Create the placeholder this._createPlaceholder(); //Set a containment if given in the options if(o.containment) { this._setContainment(); } if(o.cursor) { // cursor option if ($("body").css("cursor")) { this._storedCursor = $("body").css("cursor"); } $("body").css("cursor", o.cursor); } if(o.opacity) { // opacity option if (this.helper.css("opacity")) { this._storedOpacity = this.helper.css("opacity"); } this.helper.css("opacity", o.opacity); } if(o.zIndex) { // zIndex option if (this.helper.css("zIndex")) { this._storedZIndex = this.helper.css("zIndex"); } this.helper.css("zIndex", o.zIndex); } //Prepare scrolling if(this.scrollParent[0] !== document && this.scrollParent[0].tagName !== "HTML") { this.overflowOffset = this.scrollParent.offset(); } //Call callbacks this._trigger("start", event, this._uiHash()); //Recache the helper size if(!this._preserveHelperProportions) { this._cacheHelperProportions(); } //Post "activate" events to possible containers if( !noActivation ) { for ( i = this.containers.length - 1; i >= 0; i-- ) { this.containers[ i ]._trigger( "activate", event, this._uiHash( this ) ); } } //Prepare possible droppables if($.ui.ddmanager) { $.ui.ddmanager.current = this; } if ($.ui.ddmanager && !o.dropBehaviour) { $.ui.ddmanager.prepareOffsets(this, event); } this.dragging = true; this.helper.addClass("ui-sortable-helper"); this._mouseDrag(event); //Execute the drag once - this causes the helper not to be visible before getting its correct position return true; }, _mouseDrag: function(event) { var i, item, itemElement, intersection, o = this.options, scrolled = false; //Compute the helpers position this.position = this._generatePosition(event); this.positionAbs = this._convertPositionTo("absolute"); if (!this.lastPositionAbs) { this.lastPositionAbs = this.positionAbs; } //Do scrolling if(this.options.scroll) { if(this.scrollParent[0] !== document && this.scrollParent[0].tagName !== "HTML") { if((this.overflowOffset.top + this.scrollParent[0].offsetHeight) - event.pageY < o.scrollSensitivity) { this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop + o.scrollSpeed; } else if(event.pageY - this.overflowOffset.top < o.scrollSensitivity) { this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop - o.scrollSpeed; } if((this.overflowOffset.left + this.scrollParent[0].offsetWidth) - event.pageX < o.scrollSensitivity) { this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft + o.scrollSpeed; } else if(event.pageX - this.overflowOffset.left < o.scrollSensitivity) { this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft - o.scrollSpeed; } } else { if(event.pageY - $(document).scrollTop() < o.scrollSensitivity) { scrolled = $(document).scrollTop($(document).scrollTop() - o.scrollSpeed); } else if($(window).height() - (event.pageY - $(document).scrollTop()) < o.scrollSensitivity) { scrolled = $(document).scrollTop($(document).scrollTop() + o.scrollSpeed); } if(event.pageX - $(document).scrollLeft() < o.scrollSensitivity) { scrolled = $(document).scrollLeft($(document).scrollLeft() - o.scrollSpeed); } else if($(window).width() - (event.pageX - $(document).scrollLeft()) < o.scrollSensitivity) { scrolled = $(document).scrollLeft($(document).scrollLeft() + o.scrollSpeed); } } if(scrolled !== false && $.ui.ddmanager && !o.dropBehaviour) { $.ui.ddmanager.prepareOffsets(this, event); } } //Regenerate the absolute position used for position checks this.positionAbs = this._convertPositionTo("absolute"); //Set the helper position if(!this.options.axis || this.options.axis !== "y") { this.helper[0].style.left = this.position.left+"px"; } if(!this.options.axis || this.options.axis !== "x") { this.helper[0].style.top = this.position.top+"px"; } //Rearrange for (i = this.items.length - 1; i >= 0; i--) { //Cache variables and intersection, continue if no intersection item = this.items[i]; itemElement = item.item[0]; intersection = this._intersectsWithPointer(item); if (!intersection) { continue; } // Only put the placeholder inside the current Container, skip all // items form other containers. This works because when moving // an item from one container to another the // currentContainer is switched before the placeholder is moved. // // Without this moving items in "sub-sortables" can cause the placeholder to jitter // beetween the outer and inner container. if (item.instance !== this.currentContainer) { continue; } // cannot intersect with itself // no useless actions that have been done before // no action if the item moved is the parent of the item checked if (itemElement !== this.currentItem[0] && this.placeholder[intersection === 1 ? "next" : "prev"]()[0] !== itemElement && !$.contains(this.placeholder[0], itemElement) && (this.options.type === "semi-dynamic" ? !$.contains(this.element[0], itemElement) : true) ) { this.direction = intersection === 1 ? "down" : "up"; if (this.options.tolerance === "pointer" || this._intersectsWithSides(item)) { this._rearrange(event, item); } else { break; } this._trigger("change", event, this._uiHash()); break; } } //Post events to containers this._contactContainers(event); //Interconnect with droppables if($.ui.ddmanager) { $.ui.ddmanager.drag(this, event); } //Call callbacks this._trigger("sort", event, this._uiHash()); this.lastPositionAbs = this.positionAbs; return false; }, _mouseStop: function(event, noPropagation) { if(!event) { return; } //If we are using droppables, inform the manager about the drop if ($.ui.ddmanager && !this.options.dropBehaviour) { $.ui.ddmanager.drop(this, event); } if(this.options.revert) { var that = this, cur = this.placeholder.offset(); this.reverting = true; $(this.helper).animate({ left: cur.left - this.offset.parent.left - this.margins.left + (this.offsetParent[0] === document.body ? 0 : this.offsetParent[0].scrollLeft), top: cur.top - this.offset.parent.top - this.margins.top + (this.offsetParent[0] === document.body ? 0 : this.offsetParent[0].scrollTop) }, parseInt(this.options.revert, 10) || 500, function() { that._clear(event); }); } else { this._clear(event, noPropagation); } return false; }, cancel: function() { if(this.dragging) { this._mouseUp({ target: null }); if(this.options.helper === "original") { this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper"); } else { this.currentItem.show(); } //Post deactivating events to containers for (var i = this.containers.length - 1; i >= 0; i--){ this.containers[i]._trigger("deactivate", null, this._uiHash(this)); if(this.containers[i].containerCache.over) { this.containers[i]._trigger("out", null, this._uiHash(this)); this.containers[i].containerCache.over = 0; } } } if (this.placeholder) { //$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately, it unbinds ALL events from the original node! if(this.placeholder[0].parentNode) { this.placeholder[0].parentNode.removeChild(this.placeholder[0]); } if(this.options.helper !== "original" && this.helper && this.helper[0].parentNode) { this.helper.remove(); } $.extend(this, { helper: null, dragging: false, reverting: false, _noFinalSort: null }); if(this.domPosition.prev) { $(this.domPosition.prev).after(this.currentItem); } else { $(this.domPosition.parent).prepend(this.currentItem); } } return this; }, serialize: function(o) { var items = this._getItemsAsjQuery(o && o.connected), str = []; o = o || {}; $(items).each(function() { var res = ($(o.item || this).attr(o.attribute || "id") || "").match(o.expression || (/(.+)[\-=_](.+)/)); if (res) { str.push((o.key || res[1]+"[]")+"="+(o.key && o.expression ? res[1] : res[2])); } }); if(!str.length && o.key) { str.push(o.key + "="); } return str.join("&"); }, toArray: function(o) { var items = this._getItemsAsjQuery(o && o.connected), ret = []; o = o || {}; items.each(function() { ret.push($(o.item || this).attr(o.attribute || "id") || ""); }); return ret; }, /* Be careful with the following core functions */ _intersectsWith: function(item) { var x1 = this.positionAbs.left, x2 = x1 + this.helperProportions.width, y1 = this.positionAbs.top, y2 = y1 + this.helperProportions.height, l = item.left, r = l + item.width, t = item.top, b = t + item.height, dyClick = this.offset.click.top, dxClick = this.offset.click.left, isOverElement = (y1 + dyClick) > t && (y1 + dyClick) < b && (x1 + dxClick) > l && (x1 + dxClick) < r; if ( this.options.tolerance === "pointer" || this.options.forcePointerForContainers || (this.options.tolerance !== "pointer" && this.helperProportions[this.floating ? "width" : "height"] > item[this.floating ? "width" : "height"]) ) { return isOverElement; } else { return (l < x1 + (this.helperProportions.width / 2) && // Right Half x2 - (this.helperProportions.width / 2) < r && // Left Half t < y1 + (this.helperProportions.height / 2) && // Bottom Half y2 - (this.helperProportions.height / 2) < b ); // Top Half } }, _intersectsWithPointer: function(item) { var isOverElementHeight = (this.options.axis === "x") || isOverAxis(this.positionAbs.top + this.offset.click.top, item.top, item.height), isOverElementWidth = (this.options.axis === "y") || isOverAxis(this.positionAbs.left + this.offset.click.left, item.left, item.width), isOverElement = isOverElementHeight && isOverElementWidth, verticalDirection = this._getDragVerticalDirection(), horizontalDirection = this._getDragHorizontalDirection(); if (!isOverElement) { return false; } return this.floating ? ( ((horizontalDirection && horizontalDirection === "right") || verticalDirection === "down") ? 2 : 1 ) : ( verticalDirection && (verticalDirection === "down" ? 2 : 1) ); }, _intersectsWithSides: function(item) { var isOverBottomHalf = isOverAxis(this.positionAbs.top + this.offset.click.top, item.top + (item.height/2), item.height), isOverRightHalf = isOverAxis(this.positionAbs.left + this.offset.click.left, item.left + (item.width/2), item.width), verticalDirection = this._getDragVerticalDirection(), horizontalDirection = this._getDragHorizontalDirection(); if (this.floating && horizontalDirection) { return ((horizontalDirection === "right" && isOverRightHalf) || (horizontalDirection === "left" && !isOverRightHalf)); } else { return verticalDirection && ((verticalDirection === "down" && isOverBottomHalf) || (verticalDirection === "up" && !isOverBottomHalf)); } }, _getDragVerticalDirection: function() { var delta = this.positionAbs.top - this.lastPositionAbs.top; return delta !== 0 && (delta > 0 ? "down" : "up"); }, _getDragHorizontalDirection: function() { var delta = this.positionAbs.left - this.lastPositionAbs.left; return delta !== 0 && (delta > 0 ? "right" : "left"); }, refresh: function(event) { this._refreshItems(event); this.refreshPositions(); return this; }, _connectWith: function() { var options = this.options; return options.connectWith.constructor === String ? [options.connectWith] : options.connectWith; }, _getItemsAsjQuery: function(connected) { var i, j, cur, inst, items = [], queries = [], connectWith = this._connectWith(); if(connectWith && connected) { for (i = connectWith.length - 1; i >= 0; i--){ cur = $(connectWith[i]); for ( j = cur.length - 1; j >= 0; j--){ inst = $.data(cur[j], this.widgetFullName); if(inst && inst !== this && !inst.options.disabled) { queries.push([$.isFunction(inst.options.items) ? inst.options.items.call(inst.element) : $(inst.options.items, inst.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"), inst]); } } } } queries.push([$.isFunction(this.options.items) ? this.options.items.call(this.element, null, { options: this.options, item: this.currentItem }) : $(this.options.items, this.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"), this]); for (i = queries.length - 1; i >= 0; i--){ queries[i][0].each(function() { items.push(this); }); } return $(items); }, _removeCurrentsFromItems: function() { var list = this.currentItem.find(":data(" + this.widgetName + "-item)"); this.items = $.grep(this.items, function (item) { for (var j=0; j < list.length; j++) { if(list[j] === item.item[0]) { return false; } } return true; }); }, _refreshItems: function(event) { this.items = []; this.containers = [this]; var i, j, cur, inst, targetData, _queries, item, queriesLength, items = this.items, queries = [[$.isFunction(this.options.items) ? this.options.items.call(this.element[0], event, { item: this.currentItem }) : $(this.options.items, this.element), this]], connectWith = this._connectWith(); if(connectWith && this.ready) { //Shouldn't be run the first time through due to massive slow-down for (i = connectWith.length - 1; i >= 0; i--){ cur = $(connectWith[i]); for (j = cur.length - 1; j >= 0; j--){ inst = $.data(cur[j], this.widgetFullName); if(inst && inst !== this && !inst.options.disabled) { queries.push([$.isFunction(inst.options.items) ? inst.options.items.call(inst.element[0], event, { item: this.currentItem }) : $(inst.options.items, inst.element), inst]); this.containers.push(inst); } } } } for (i = queries.length - 1; i >= 0; i--) { targetData = queries[i][1]; _queries = queries[i][0]; for (j=0, queriesLength = _queries.length; j < queriesLength; j++) { item = $(_queries[j]); item.data(this.widgetName + "-item", targetData); // Data for target checking (mouse manager) items.push({ item: item, instance: targetData, width: 0, height: 0, left: 0, top: 0 }); } } }, refreshPositions: function(fast) { //This has to be redone because due to the item being moved out/into the offsetParent, the offsetParent's position will change if(this.offsetParent && this.helper) { this.offset.parent = this._getParentOffset(); } var i, item, t, p; for (i = this.items.length - 1; i >= 0; i--){ item = this.items[i]; //We ignore calculating positions of all connected containers when we're not over them if(item.instance !== this.currentContainer && this.currentContainer && item.item[0] !== this.currentItem[0]) { continue; } t = this.options.toleranceElement ? $(this.options.toleranceElement, item.item) : item.item; if (!fast) { item.width = t.outerWidth(); item.height = t.outerHeight(); } p = t.offset(); item.left = p.left; item.top = p.top; } if(this.options.custom && this.options.custom.refreshContainers) { this.options.custom.refreshContainers.call(this); } else { for (i = this.containers.length - 1; i >= 0; i--){ p = this.containers[i].element.offset(); this.containers[i].containerCache.left = p.left; this.containers[i].containerCache.top = p.top; this.containers[i].containerCache.width = this.containers[i].element.outerWidth(); this.containers[i].containerCache.height = this.containers[i].element.outerHeight(); } } return this; }, _createPlaceholder: function(that) { that = that || this; var className, o = that.options; if(!o.placeholder || o.placeholder.constructor === String) { className = o.placeholder; o.placeholder = { element: function() { var el = $(document.createElement(that.currentItem[0].nodeName)) .addClass(className || that.currentItem[0].className+" ui-sortable-placeholder") .removeClass("ui-sortable-helper")[0]; if(!className) { el.style.visibility = "hidden"; } return el; }, update: function(container, p) { // 1. If a className is set as 'placeholder option, we don't force sizes - the class is responsible for that // 2. The option 'forcePlaceholderSize can be enabled to force it even if a class name is specified if(className && !o.forcePlaceholderSize) { return; } //If the element doesn't have a actual height by itself (without styles coming from a stylesheet), it receives the inline height from the dragged item if(!p.height()) { p.height(that.currentItem.innerHeight() - parseInt(that.currentItem.css("paddingTop")||0, 10) - parseInt(that.currentItem.css("paddingBottom")||0, 10)); } if(!p.width()) { p.width(that.currentItem.innerWidth() - parseInt(that.currentItem.css("paddingLeft")||0, 10) - parseInt(that.currentItem.css("paddingRight")||0, 10)); } } }; } //Create the placeholder that.placeholder = $(o.placeholder.element.call(that.element, that.currentItem)); //Append it after the actual current item that.currentItem.after(that.placeholder); //Update the size of the placeholder (TODO: Logic to fuzzy, see line 316/317) o.placeholder.update(that, that.placeholder); }, _contactContainers: function(event) { var i, j, dist, itemWithLeastDistance, posProperty, sizeProperty, base, cur, nearBottom, innermostContainer = null, innermostIndex = null; // get innermost container that intersects with item for (i = this.containers.length - 1; i >= 0; i--) { // never consider a container that's located within the item itself if($.contains(this.currentItem[0], this.containers[i].element[0])) { continue; } if(this._intersectsWith(this.containers[i].containerCache)) { // if we've already found a container and it's more "inner" than this, then continue if(innermostContainer && $.contains(this.containers[i].element[0], innermostContainer.element[0])) { continue; } innermostContainer = this.containers[i]; innermostIndex = i; } else { // container doesn't intersect. trigger "out" event if necessary if(this.containers[i].containerCache.over) { this.containers[i]._trigger("out", event, this._uiHash(this)); this.containers[i].containerCache.over = 0; } } } // if no intersecting containers found, return if(!innermostContainer) { return; } // move the item into the container if it's not there already if(this.containers.length === 1) { this.containers[innermostIndex]._trigger("over", event, this._uiHash(this)); this.containers[innermostIndex].containerCache.over = 1; } else { //When entering a new container, we will find the item with the least distance and append our item near it dist = 10000; itemWithLeastDistance = null; posProperty = this.containers[innermostIndex].floating ? "left" : "top"; sizeProperty = this.containers[innermostIndex].floating ? "width" : "height"; base = this.positionAbs[posProperty] + this.offset.click[posProperty]; for (j = this.items.length - 1; j >= 0; j--) { if(!$.contains(this.containers[innermostIndex].element[0], this.items[j].item[0])) { continue; } if(this.items[j].item[0] === this.currentItem[0]) { continue; } cur = this.items[j].item.offset()[posProperty]; nearBottom = false; if(Math.abs(cur - base) > Math.abs(cur + this.items[j][sizeProperty] - base)){ nearBottom = true; cur += this.items[j][sizeProperty]; } if(Math.abs(cur - base) < dist) { dist = Math.abs(cur - base); itemWithLeastDistance = this.items[j]; this.direction = nearBottom ? "up": "down"; } } //Check if dropOnEmpty is enabled if(!itemWithLeastDistance && !this.options.dropOnEmpty) { return; } this.currentContainer = this.containers[innermostIndex]; itemWithLeastDistance ? this._rearrange(event, itemWithLeastDistance, null, true) : this._rearrange(event, null, this.containers[innermostIndex].element, true); this._trigger("change", event, this._uiHash()); this.containers[innermostIndex]._trigger("change", event, this._uiHash(this)); //Update the placeholder this.options.placeholder.update(this.currentContainer, this.placeholder); this.containers[innermostIndex]._trigger("over", event, this._uiHash(this)); this.containers[innermostIndex].containerCache.over = 1; } }, _createHelper: function(event) { var o = this.options, helper = $.isFunction(o.helper) ? $(o.helper.apply(this.element[0], [event, this.currentItem])) : (o.helper === "clone" ? this.currentItem.clone() : this.currentItem); //Add the helper to the DOM if that didn't happen already if(!helper.parents("body").length) { $(o.appendTo !== "parent" ? o.appendTo : this.currentItem[0].parentNode)[0].appendChild(helper[0]); } if(helper[0] === this.currentItem[0]) { this._storedCSS = { width: this.currentItem[0].style.width, height: this.currentItem[0].style.height, position: this.currentItem.css("position"), top: this.currentItem.css("top"), left: this.currentItem.css("left") }; } if(!helper[0].style.width || o.forceHelperSize) { helper.width(this.currentItem.width()); } if(!helper[0].style.height || o.forceHelperSize) { helper.height(this.currentItem.height()); } return helper; }, _adjustOffsetFromHelper: function(obj) { if (typeof obj === "string") { obj = obj.split(" "); } if ($.isArray(obj)) { obj = {left: +obj[0], top: +obj[1] || 0}; } if ("left" in obj) { this.offset.click.left = obj.left + this.margins.left; } if ("right" in obj) { this.offset.click.left = this.helperProportions.width - obj.right + this.margins.left; } if ("top" in obj) { this.offset.click.top = obj.top + this.margins.top; } if ("bottom" in obj) { this.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top; } }, _getParentOffset: function() { //Get the offsetParent and cache its position this.offsetParent = this.helper.offsetParent(); var po = this.offsetParent.offset(); // This is a special case where we need to modify a offset calculated on start, since the following happened: // 1. The position of the helper is absolute, so it's position is calculated based on the next positioned parent // 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't the document, which means that // the scroll is included in the initial calculation of the offset of the parent, and never recalculated upon drag if(this.cssPosition === "absolute" && this.scrollParent[0] !== document && $.contains(this.scrollParent[0], this.offsetParent[0])) { po.left += this.scrollParent.scrollLeft(); po.top += this.scrollParent.scrollTop(); } // This needs to be actually done for all browsers, since pageX/pageY includes this information // with an ugly IE fix if( this.offsetParent[0] === document.body || (this.offsetParent[0].tagName && this.offsetParent[0].tagName.toLowerCase() === "html" && $.ui.ie)) { po = { top: 0, left: 0 }; } return { top: po.top + (parseInt(this.offsetParent.css("borderTopWidth"),10) || 0), left: po.left + (parseInt(this.offsetParent.css("borderLeftWidth"),10) || 0) }; }, _getRelativeOffset: function() { if(this.cssPosition === "relative") { var p = this.currentItem.position(); return { top: p.top - (parseInt(this.helper.css("top"),10) || 0) + this.scrollParent.scrollTop(), left: p.left - (parseInt(this.helper.css("left"),10) || 0) + this.scrollParent.scrollLeft() }; } else { return { top: 0, left: 0 }; } }, _cacheMargins: function() { this.margins = { left: (parseInt(this.currentItem.css("marginLeft"),10) || 0), top: (parseInt(this.currentItem.css("marginTop"),10) || 0) }; }, _cacheHelperProportions: function() { this.helperProportions = { width: this.helper.outerWidth(), height: this.helper.outerHeight() }; }, _setContainment: function() { var ce, co, over, o = this.options; if(o.containment === "parent") { o.containment = this.helper[0].parentNode; } if(o.containment === "document" || o.containment === "window") { this.containment = [ 0 - this.offset.relative.left - this.offset.parent.left, 0 - this.offset.relative.top - this.offset.parent.top, $(o.containment === "document" ? document : window).width() - this.helperProportions.width - this.margins.left, ($(o.containment === "document" ? document : window).height() || document.body.parentNode.scrollHeight) - this.helperProportions.height - this.margins.top ]; } if(!(/^(document|window|parent)$/).test(o.containment)) { ce = $(o.containment)[0]; co = $(o.containment).offset(); over = ($(ce).css("overflow") !== "hidden"); this.containment = [ co.left + (parseInt($(ce).css("borderLeftWidth"),10) || 0) + (parseInt($(ce).css("paddingLeft"),10) || 0) - this.margins.left, co.top + (parseInt($(ce).css("borderTopWidth"),10) || 0) + (parseInt($(ce).css("paddingTop"),10) || 0) - this.margins.top, co.left+(over ? Math.max(ce.scrollWidth,ce.offsetWidth) : ce.offsetWidth) - (parseInt($(ce).css("borderLeftWidth"),10) || 0) - (parseInt($(ce).css("paddingRight"),10) || 0) - this.helperProportions.width - this.margins.left, co.top+(over ? Math.max(ce.scrollHeight,ce.offsetHeight) : ce.offsetHeight) - (parseInt($(ce).css("borderTopWidth"),10) || 0) - (parseInt($(ce).css("paddingBottom"),10) || 0) - this.helperProportions.height - this.margins.top ]; } }, _convertPositionTo: function(d, pos) { if(!pos) { pos = this.position; } var mod = d === "absolute" ? 1 : -1, scroll = this.cssPosition === "absolute" && !(this.scrollParent[0] !== document && $.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent, scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName); return { top: ( pos.top + // The absolute mouse position this.offset.relative.top * mod + // Only for relative positioned nodes: Relative offset from element to offset parent this.offset.parent.top * mod - // The offsetParent's offset without borders (offset + border) ( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ) * mod) ), left: ( pos.left + // The absolute mouse position this.offset.relative.left * mod + // Only for relative positioned nodes: Relative offset from element to offset parent this.offset.parent.left * mod - // The offsetParent's offset without borders (offset + border) ( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ) * mod) ) }; }, _generatePosition: function(event) { var top, left, o = this.options, pageX = event.pageX, pageY = event.pageY, scroll = this.cssPosition === "absolute" && !(this.scrollParent[0] !== document && $.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent, scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName); // This is another very weird special case that only happens for relative elements: // 1. If the css position is relative // 2. and the scroll parent is the document or similar to the offset parent // we have to refresh the relative offset during the scroll so there are no jumps if(this.cssPosition === "relative" && !(this.scrollParent[0] !== document && this.scrollParent[0] !== this.offsetParent[0])) { this.offset.relative = this._getRelativeOffset(); } /* * - Position constraining - * Constrain the position to a mix of grid, containment. */ if(this.originalPosition) { //If we are not dragging yet, we won't check for options if(this.containment) { if(event.pageX - this.offset.click.left < this.containment[0]) { pageX = this.containment[0] + this.offset.click.left; } if(event.pageY - this.offset.click.top < this.containment[1]) { pageY = this.containment[1] + this.offset.click.top; } if(event.pageX - this.offset.click.left > this.containment[2]) { pageX = this.containment[2] + this.offset.click.left; } if(event.pageY - this.offset.click.top > this.containment[3]) { pageY = this.containment[3] + this.offset.click.top; } } if(o.grid) { top = this.originalPageY + Math.round((pageY - this.originalPageY) / o.grid[1]) * o.grid[1]; pageY = this.containment ? ( (top - this.offset.click.top >= this.containment[1] && top - this.offset.click.top <= this.containment[3]) ? top : ((top - this.offset.click.top >= this.containment[1]) ? top - o.grid[1] : top + o.grid[1])) : top; left = this.originalPageX + Math.round((pageX - this.originalPageX) / o.grid[0]) * o.grid[0]; pageX = this.containment ? ( (left - this.offset.click.left >= this.containment[0] && left - this.offset.click.left <= this.containment[2]) ? left : ((left - this.offset.click.left >= this.containment[0]) ? left - o.grid[0] : left + o.grid[0])) : left; } } return { top: ( pageY - // The absolute mouse position this.offset.click.top - // Click offset (relative to the element) this.offset.relative.top - // Only for relative positioned nodes: Relative offset from element to offset parent this.offset.parent.top + // The offsetParent's offset without borders (offset + border) ( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) )) ), left: ( pageX - // The absolute mouse position this.offset.click.left - // Click offset (relative to the element) this.offset.relative.left - // Only for relative positioned nodes: Relative offset from element to offset parent this.offset.parent.left + // The offsetParent's offset without borders (offset + border) ( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() )) ) }; }, _rearrange: function(event, i, a, hardRefresh) { a ? a[0].appendChild(this.placeholder[0]) : i.item[0].parentNode.insertBefore(this.placeholder[0], (this.direction === "down" ? i.item[0] : i.item[0].nextSibling)); //Various things done here to improve the performance: // 1. we create a setTimeout, that calls refreshPositions // 2. on the instance, we have a counter variable, that get's higher after every append // 3. on the local scope, we copy the counter variable, and check in the timeout, if it's still the same // 4. this lets only the last addition to the timeout stack through this.counter = this.counter ? ++this.counter : 1; var counter = this.counter; this._delay(function() { if(counter === this.counter) { this.refreshPositions(!hardRefresh); //Precompute after each DOM insertion, NOT on mousemove } }); }, _clear: function(event, noPropagation) { this.reverting = false; // We delay all events that have to be triggered to after the point where the placeholder has been removed and // everything else normalized again var i, delayedTriggers = []; // We first have to update the dom position of the actual currentItem // Note: don't do it if the current item is already removed (by a user), or it gets reappended (see #4088) if(!this._noFinalSort && this.currentItem.parent().length) { this.placeholder.before(this.currentItem); } this._noFinalSort = null; if(this.helper[0] === this.currentItem[0]) { for(i in this._storedCSS) { if(this._storedCSS[i] === "auto" || this._storedCSS[i] === "static") { this._storedCSS[i] = ""; } } this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper"); } else { this.currentItem.show(); } if(this.fromOutside && !noPropagation) { delayedTriggers.push(function(event) { this._trigger("receive", event, this._uiHash(this.fromOutside)); }); } if((this.fromOutside || this.domPosition.prev !== this.currentItem.prev().not(".ui-sortable-helper")[0] || this.domPosition.parent !== this.currentItem.parent()[0]) && !noPropagation) { delayedTriggers.push(function(event) { this._trigger("update", event, this._uiHash()); }); //Trigger update callback if the DOM position has changed } // Check if the items Container has Changed and trigger appropriate // events. if (this !== this.currentContainer) { if(!noPropagation) { delayedTriggers.push(function(event) { this._trigger("remove", event, this._uiHash()); }); delayedTriggers.push((function(c) { return function(event) { c._trigger("receive", event, this._uiHash(this)); }; }).call(this, this.currentContainer)); delayedTriggers.push((function(c) { return function(event) { c._trigger("update", event, this._uiHash(this)); }; }).call(this, this.currentContainer)); } } //Post events to containers for (i = this.containers.length - 1; i >= 0; i--){ if(!noPropagation) { delayedTriggers.push((function(c) { return function(event) { c._trigger("deactivate", event, this._uiHash(this)); }; }).call(this, this.containers[i])); } if(this.containers[i].containerCache.over) { delayedTriggers.push((function(c) { return function(event) { c._trigger("out", event, this._uiHash(this)); }; }).call(this, this.containers[i])); this.containers[i].containerCache.over = 0; } } //Do what was originally in plugins if(this._storedCursor) { $("body").css("cursor", this._storedCursor); } if(this._storedOpacity) { this.helper.css("opacity", this._storedOpacity); } if(this._storedZIndex) { this.helper.css("zIndex", this._storedZIndex === "auto" ? "" : this._storedZIndex); } this.dragging = false; if(this.cancelHelperRemoval) { if(!noPropagation) { this._trigger("beforeStop", event, this._uiHash()); for (i=0; i < delayedTriggers.length; i++) { delayedTriggers[i].call(this, event); } //Trigger all delayed events this._trigger("stop", event, this._uiHash()); } this.fromOutside = false; return false; } if(!noPropagation) { this._trigger("beforeStop", event, this._uiHash()); } //$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately, it unbinds ALL events from the original node! this.placeholder[0].parentNode.removeChild(this.placeholder[0]); if(this.helper[0] !== this.currentItem[0]) { this.helper.remove(); } this.helper = null; if(!noPropagation) { for (i=0; i < delayedTriggers.length; i++) { delayedTriggers[i].call(this, event); } //Trigger all delayed events this._trigger("stop", event, this._uiHash()); } this.fromOutside = false; return true; }, _trigger: function() { if ($.Widget.prototype._trigger.apply(this, arguments) === false) { this.cancel(); } }, _uiHash: function(_inst) { var inst = _inst || this; return { helper: inst.helper, placeholder: inst.placeholder || $([]), position: inst.position, originalPosition: inst.originalPosition, offset: inst.positionAbs, item: inst.currentItem, sender: _inst ? _inst.element : null }; } }); })(jQuery); ;(jQuery.effects || (function($, undefined) { var dataSpace = "ui-effects-"; $.effects = { effect: {} }; /*! * jQuery Color Animations v2.1.2 * https://github.com/jquery/jquery-color * * Copyright 2013 jQuery Foundation and other contributors * Released under the MIT license. * http://jquery.org/license * * Date: Wed Jan 16 08:47:09 2013 -0600 */ (function( jQuery, undefined ) { var stepHooks = "backgroundColor borderBottomColor borderLeftColor borderRightColor borderTopColor color columnRuleColor outlineColor textDecorationColor textEmphasisColor", // plusequals test for += 100 -= 100 rplusequals = /^([\-+])=\s*(\d+\.?\d*)/, // a set of RE's that can match strings and generate color tuples. stringParsers = [{ re: /rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/, parse: function( execResult ) { return [ execResult[ 1 ], execResult[ 2 ], execResult[ 3 ], execResult[ 4 ] ]; } }, { re: /rgba?\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/, parse: function( execResult ) { return [ execResult[ 1 ] * 2.55, execResult[ 2 ] * 2.55, execResult[ 3 ] * 2.55, execResult[ 4 ] ]; } }, { // this regex ignores A-F because it's compared against an already lowercased string re: /#([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})/, parse: function( execResult ) { return [ parseInt( execResult[ 1 ], 16 ), parseInt( execResult[ 2 ], 16 ), parseInt( execResult[ 3 ], 16 ) ]; } }, { // this regex ignores A-F because it's compared against an already lowercased string re: /#([a-f0-9])([a-f0-9])([a-f0-9])/, parse: function( execResult ) { return [ parseInt( execResult[ 1 ] + execResult[ 1 ], 16 ), parseInt( execResult[ 2 ] + execResult[ 2 ], 16 ), parseInt( execResult[ 3 ] + execResult[ 3 ], 16 ) ]; } }, { re: /hsla?\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/, space: "hsla", parse: function( execResult ) { return [ execResult[ 1 ], execResult[ 2 ] / 100, execResult[ 3 ] / 100, execResult[ 4 ] ]; } }], // jQuery.Color( ) color = jQuery.Color = function( color, green, blue, alpha ) { return new jQuery.Color.fn.parse( color, green, blue, alpha ); }, spaces = { rgba: { props: { red: { idx: 0, type: "byte" }, green: { idx: 1, type: "byte" }, blue: { idx: 2, type: "byte" } } }, hsla: { props: { hue: { idx: 0, type: "degrees" }, saturation: { idx: 1, type: "percent" }, lightness: { idx: 2, type: "percent" } } } }, propTypes = { "byte": { floor: true, max: 255 }, "percent": { max: 1 }, "degrees": { mod: 360, floor: true } }, support = color.support = {}, // element for support tests supportElem = jQuery( "

        " )[ 0 ], // colors = jQuery.Color.names colors, // local aliases of functions called often each = jQuery.each; // determine rgba support immediately supportElem.style.cssText = "background-color:rgba(1,1,1,.5)"; support.rgba = supportElem.style.backgroundColor.indexOf( "rgba" ) > -1; // define cache name and alpha properties // for rgba and hsla spaces each( spaces, function( spaceName, space ) { space.cache = "_" + spaceName; space.props.alpha = { idx: 3, type: "percent", def: 1 }; }); function clamp( value, prop, allowEmpty ) { var type = propTypes[ prop.type ] || {}; if ( value == null ) { return (allowEmpty || !prop.def) ? null : prop.def; } // ~~ is an short way of doing floor for positive numbers value = type.floor ? ~~value : parseFloat( value ); // IE will pass in empty strings as value for alpha, // which will hit this case if ( isNaN( value ) ) { return prop.def; } if ( type.mod ) { // we add mod before modding to make sure that negatives values // get converted properly: -10 -> 350 return (value + type.mod) % type.mod; } // for now all property types without mod have min and max return 0 > value ? 0 : type.max < value ? type.max : value; } function stringParse( string ) { var inst = color(), rgba = inst._rgba = []; string = string.toLowerCase(); each( stringParsers, function( i, parser ) { var parsed, match = parser.re.exec( string ), values = match && parser.parse( match ), spaceName = parser.space || "rgba"; if ( values ) { parsed = inst[ spaceName ]( values ); // if this was an rgba parse the assignment might happen twice // oh well.... inst[ spaces[ spaceName ].cache ] = parsed[ spaces[ spaceName ].cache ]; rgba = inst._rgba = parsed._rgba; // exit each( stringParsers ) here because we matched return false; } }); // Found a stringParser that handled it if ( rgba.length ) { // if this came from a parsed string, force "transparent" when alpha is 0 // chrome, (and maybe others) return "transparent" as rgba(0,0,0,0) if ( rgba.join() === "0,0,0,0" ) { jQuery.extend( rgba, colors.transparent ); } return inst; } // named colors return colors[ string ]; } color.fn = jQuery.extend( color.prototype, { parse: function( red, green, blue, alpha ) { if ( red === undefined ) { this._rgba = [ null, null, null, null ]; return this; } if ( red.jquery || red.nodeType ) { red = jQuery( red ).css( green ); green = undefined; } var inst = this, type = jQuery.type( red ), rgba = this._rgba = []; // more than 1 argument specified - assume ( red, green, blue, alpha ) if ( green !== undefined ) { red = [ red, green, blue, alpha ]; type = "array"; } if ( type === "string" ) { return this.parse( stringParse( red ) || colors._default ); } if ( type === "array" ) { each( spaces.rgba.props, function( key, prop ) { rgba[ prop.idx ] = clamp( red[ prop.idx ], prop ); }); return this; } if ( type === "object" ) { if ( red instanceof color ) { each( spaces, function( spaceName, space ) { if ( red[ space.cache ] ) { inst[ space.cache ] = red[ space.cache ].slice(); } }); } else { each( spaces, function( spaceName, space ) { var cache = space.cache; each( space.props, function( key, prop ) { // if the cache doesn't exist, and we know how to convert if ( !inst[ cache ] && space.to ) { // if the value was null, we don't need to copy it // if the key was alpha, we don't need to copy it either if ( key === "alpha" || red[ key ] == null ) { return; } inst[ cache ] = space.to( inst._rgba ); } // this is the only case where we allow nulls for ALL properties. // call clamp with alwaysAllowEmpty inst[ cache ][ prop.idx ] = clamp( red[ key ], prop, true ); }); // everything defined but alpha? if ( inst[ cache ] && jQuery.inArray( null, inst[ cache ].slice( 0, 3 ) ) < 0 ) { // use the default of 1 inst[ cache ][ 3 ] = 1; if ( space.from ) { inst._rgba = space.from( inst[ cache ] ); } } }); } return this; } }, is: function( compare ) { var is = color( compare ), same = true, inst = this; each( spaces, function( _, space ) { var localCache, isCache = is[ space.cache ]; if (isCache) { localCache = inst[ space.cache ] || space.to && space.to( inst._rgba ) || []; each( space.props, function( _, prop ) { if ( isCache[ prop.idx ] != null ) { same = ( isCache[ prop.idx ] === localCache[ prop.idx ] ); return same; } }); } return same; }); return same; }, _space: function() { var used = [], inst = this; each( spaces, function( spaceName, space ) { if ( inst[ space.cache ] ) { used.push( spaceName ); } }); return used.pop(); }, transition: function( other, distance ) { var end = color( other ), spaceName = end._space(), space = spaces[ spaceName ], startColor = this.alpha() === 0 ? color( "transparent" ) : this, start = startColor[ space.cache ] || space.to( startColor._rgba ), result = start.slice(); end = end[ space.cache ]; each( space.props, function( key, prop ) { var index = prop.idx, startValue = start[ index ], endValue = end[ index ], type = propTypes[ prop.type ] || {}; // if null, don't override start value if ( endValue === null ) { return; } // if null - use end if ( startValue === null ) { result[ index ] = endValue; } else { if ( type.mod ) { if ( endValue - startValue > type.mod / 2 ) { startValue += type.mod; } else if ( startValue - endValue > type.mod / 2 ) { startValue -= type.mod; } } result[ index ] = clamp( ( endValue - startValue ) * distance + startValue, prop ); } }); return this[ spaceName ]( result ); }, blend: function( opaque ) { // if we are already opaque - return ourself if ( this._rgba[ 3 ] === 1 ) { return this; } var rgb = this._rgba.slice(), a = rgb.pop(), blend = color( opaque )._rgba; return color( jQuery.map( rgb, function( v, i ) { return ( 1 - a ) * blend[ i ] + a * v; })); }, toRgbaString: function() { var prefix = "rgba(", rgba = jQuery.map( this._rgba, function( v, i ) { return v == null ? ( i > 2 ? 1 : 0 ) : v; }); if ( rgba[ 3 ] === 1 ) { rgba.pop(); prefix = "rgb("; } return prefix + rgba.join() + ")"; }, toHslaString: function() { var prefix = "hsla(", hsla = jQuery.map( this.hsla(), function( v, i ) { if ( v == null ) { v = i > 2 ? 1 : 0; } // catch 1 and 2 if ( i && i < 3 ) { v = Math.round( v * 100 ) + "%"; } return v; }); if ( hsla[ 3 ] === 1 ) { hsla.pop(); prefix = "hsl("; } return prefix + hsla.join() + ")"; }, toHexString: function( includeAlpha ) { var rgba = this._rgba.slice(), alpha = rgba.pop(); if ( includeAlpha ) { rgba.push( ~~( alpha * 255 ) ); } return "#" + jQuery.map( rgba, function( v ) { // default to 0 when nulls exist v = ( v || 0 ).toString( 16 ); return v.length === 1 ? "0" + v : v; }).join(""); }, toString: function() { return this._rgba[ 3 ] === 0 ? "transparent" : this.toRgbaString(); } }); color.fn.parse.prototype = color.fn; // hsla conversions adapted from: // https://code.google.com/p/maashaack/source/browse/packages/graphics/trunk/src/graphics/colors/HUE2RGB.as?r=5021 function hue2rgb( p, q, h ) { h = ( h + 1 ) % 1; if ( h * 6 < 1 ) { return p + (q - p) * h * 6; } if ( h * 2 < 1) { return q; } if ( h * 3 < 2 ) { return p + (q - p) * ((2/3) - h) * 6; } return p; } spaces.hsla.to = function ( rgba ) { if ( rgba[ 0 ] == null || rgba[ 1 ] == null || rgba[ 2 ] == null ) { return [ null, null, null, rgba[ 3 ] ]; } var r = rgba[ 0 ] / 255, g = rgba[ 1 ] / 255, b = rgba[ 2 ] / 255, a = rgba[ 3 ], max = Math.max( r, g, b ), min = Math.min( r, g, b ), diff = max - min, add = max + min, l = add * 0.5, h, s; if ( min === max ) { h = 0; } else if ( r === max ) { h = ( 60 * ( g - b ) / diff ) + 360; } else if ( g === max ) { h = ( 60 * ( b - r ) / diff ) + 120; } else { h = ( 60 * ( r - g ) / diff ) + 240; } // chroma (diff) == 0 means greyscale which, by definition, saturation = 0% // otherwise, saturation is based on the ratio of chroma (diff) to lightness (add) if ( diff === 0 ) { s = 0; } else if ( l <= 0.5 ) { s = diff / add; } else { s = diff / ( 2 - add ); } return [ Math.round(h) % 360, s, l, a == null ? 1 : a ]; }; spaces.hsla.from = function ( hsla ) { if ( hsla[ 0 ] == null || hsla[ 1 ] == null || hsla[ 2 ] == null ) { return [ null, null, null, hsla[ 3 ] ]; } var h = hsla[ 0 ] / 360, s = hsla[ 1 ], l = hsla[ 2 ], a = hsla[ 3 ], q = l <= 0.5 ? l * ( 1 + s ) : l + s - l * s, p = 2 * l - q; return [ Math.round( hue2rgb( p, q, h + ( 1 / 3 ) ) * 255 ), Math.round( hue2rgb( p, q, h ) * 255 ), Math.round( hue2rgb( p, q, h - ( 1 / 3 ) ) * 255 ), a ]; }; each( spaces, function( spaceName, space ) { var props = space.props, cache = space.cache, to = space.to, from = space.from; // makes rgba() and hsla() color.fn[ spaceName ] = function( value ) { // generate a cache for this space if it doesn't exist if ( to && !this[ cache ] ) { this[ cache ] = to( this._rgba ); } if ( value === undefined ) { return this[ cache ].slice(); } var ret, type = jQuery.type( value ), arr = ( type === "array" || type === "object" ) ? value : arguments, local = this[ cache ].slice(); each( props, function( key, prop ) { var val = arr[ type === "object" ? key : prop.idx ]; if ( val == null ) { val = local[ prop.idx ]; } local[ prop.idx ] = clamp( val, prop ); }); if ( from ) { ret = color( from( local ) ); ret[ cache ] = local; return ret; } else { return color( local ); } }; // makes red() green() blue() alpha() hue() saturation() lightness() each( props, function( key, prop ) { // alpha is included in more than one space if ( color.fn[ key ] ) { return; } color.fn[ key ] = function( value ) { var vtype = jQuery.type( value ), fn = ( key === "alpha" ? ( this._hsla ? "hsla" : "rgba" ) : spaceName ), local = this[ fn ](), cur = local[ prop.idx ], match; if ( vtype === "undefined" ) { return cur; } if ( vtype === "function" ) { value = value.call( this, cur ); vtype = jQuery.type( value ); } if ( value == null && prop.empty ) { return this; } if ( vtype === "string" ) { match = rplusequals.exec( value ); if ( match ) { value = cur + parseFloat( match[ 2 ] ) * ( match[ 1 ] === "+" ? 1 : -1 ); } } local[ prop.idx ] = value; return this[ fn ]( local ); }; }); }); // add cssHook and .fx.step function for each named hook. // accept a space separated string of properties color.hook = function( hook ) { var hooks = hook.split( " " ); each( hooks, function( i, hook ) { jQuery.cssHooks[ hook ] = { set: function( elem, value ) { var parsed, curElem, backgroundColor = ""; if ( value !== "transparent" && ( jQuery.type( value ) !== "string" || ( parsed = stringParse( value ) ) ) ) { value = color( parsed || value ); if ( !support.rgba && value._rgba[ 3 ] !== 1 ) { curElem = hook === "backgroundColor" ? elem.parentNode : elem; while ( (backgroundColor === "" || backgroundColor === "transparent") && curElem && curElem.style ) { try { backgroundColor = jQuery.css( curElem, "backgroundColor" ); curElem = curElem.parentNode; } catch ( e ) { } } value = value.blend( backgroundColor && backgroundColor !== "transparent" ? backgroundColor : "_default" ); } value = value.toRgbaString(); } try { elem.style[ hook ] = value; } catch( e ) { // wrapped to prevent IE from throwing errors on "invalid" values like 'auto' or 'inherit' } } }; jQuery.fx.step[ hook ] = function( fx ) { if ( !fx.colorInit ) { fx.start = color( fx.elem, hook ); fx.end = color( fx.end ); fx.colorInit = true; } jQuery.cssHooks[ hook ].set( fx.elem, fx.start.transition( fx.end, fx.pos ) ); }; }); }; color.hook( stepHooks ); jQuery.cssHooks.borderColor = { expand: function( value ) { var expanded = {}; each( [ "Top", "Right", "Bottom", "Left" ], function( i, part ) { expanded[ "border" + part + "Color" ] = value; }); return expanded; } }; // Basic color names only. // Usage of any of the other color names requires adding yourself or including // jquery.color.svg-names.js. colors = jQuery.Color.names = { // 4.1. Basic color keywords aqua: "#00ffff", black: "#000000", blue: "#0000ff", fuchsia: "#ff00ff", gray: "#808080", green: "#008000", lime: "#00ff00", maroon: "#800000", navy: "#000080", olive: "#808000", purple: "#800080", red: "#ff0000", silver: "#c0c0c0", teal: "#008080", white: "#ffffff", yellow: "#ffff00", // 4.2.3. "transparent" color keyword transparent: [ null, null, null, 0 ], _default: "#ffffff" }; })( jQuery ); /******************************************************************************/ /****************************** CLASS ANIMATIONS ******************************/ /******************************************************************************/ (function() { var classAnimationActions = [ "add", "remove", "toggle" ], shorthandStyles = { border: 1, borderBottom: 1, borderColor: 1, borderLeft: 1, borderRight: 1, borderTop: 1, borderWidth: 1, margin: 1, padding: 1 }; $.each([ "borderLeftStyle", "borderRightStyle", "borderBottomStyle", "borderTopStyle" ], function( _, prop ) { $.fx.step[ prop ] = function( fx ) { if ( fx.end !== "none" && !fx.setAttr || fx.pos === 1 && !fx.setAttr ) { jQuery.style( fx.elem, prop, fx.end ); fx.setAttr = true; } }; }); function getElementStyles( elem ) { var key, len, style = elem.ownerDocument.defaultView ? elem.ownerDocument.defaultView.getComputedStyle( elem, null ) : elem.currentStyle, styles = {}; if ( style && style.length && style[ 0 ] && style[ style[ 0 ] ] ) { len = style.length; while ( len-- ) { key = style[ len ]; if ( typeof style[ key ] === "string" ) { styles[ $.camelCase( key ) ] = style[ key ]; } } // support: Opera, IE <9 } else { for ( key in style ) { if ( typeof style[ key ] === "string" ) { styles[ key ] = style[ key ]; } } } return styles; } function styleDifference( oldStyle, newStyle ) { var diff = {}, name, value; for ( name in newStyle ) { value = newStyle[ name ]; if ( oldStyle[ name ] !== value ) { if ( !shorthandStyles[ name ] ) { if ( $.fx.step[ name ] || !isNaN( parseFloat( value ) ) ) { diff[ name ] = value; } } } } return diff; } // support: jQuery <1.8 if ( !$.fn.addBack ) { $.fn.addBack = function( selector ) { return this.add( selector == null ? this.prevObject : this.prevObject.filter( selector ) ); }; } $.effects.animateClass = function( value, duration, easing, callback ) { var o = $.speed( duration, easing, callback ); return this.queue( function() { var animated = $( this ), baseClass = animated.attr( "class" ) || "", applyClassChange, allAnimations = o.children ? animated.find( "*" ).addBack() : animated; // map the animated objects to store the original styles. allAnimations = allAnimations.map(function() { var el = $( this ); return { el: el, start: getElementStyles( this ) }; }); // apply class change applyClassChange = function() { $.each( classAnimationActions, function(i, action) { if ( value[ action ] ) { animated[ action + "Class" ]( value[ action ] ); } }); }; applyClassChange(); // map all animated objects again - calculate new styles and diff allAnimations = allAnimations.map(function() { this.end = getElementStyles( this.el[ 0 ] ); this.diff = styleDifference( this.start, this.end ); return this; }); // apply original class animated.attr( "class", baseClass ); // map all animated objects again - this time collecting a promise allAnimations = allAnimations.map(function() { var styleInfo = this, dfd = $.Deferred(), opts = $.extend({}, o, { queue: false, complete: function() { dfd.resolve( styleInfo ); } }); this.el.animate( this.diff, opts ); return dfd.promise(); }); // once all animations have completed: $.when.apply( $, allAnimations.get() ).done(function() { // set the final class applyClassChange(); // for each animated element, // clear all css properties that were animated $.each( arguments, function() { var el = this.el; $.each( this.diff, function(key) { el.css( key, "" ); }); }); // this is guarnteed to be there if you use jQuery.speed() // it also handles dequeuing the next anim... o.complete.call( animated[ 0 ] ); }); }); }; $.fn.extend({ _addClass: $.fn.addClass, addClass: function( classNames, speed, easing, callback ) { return speed ? $.effects.animateClass.call( this, { add: classNames }, speed, easing, callback ) : this._addClass( classNames ); }, _removeClass: $.fn.removeClass, removeClass: function( classNames, speed, easing, callback ) { return arguments.length > 1 ? $.effects.animateClass.call( this, { remove: classNames }, speed, easing, callback ) : this._removeClass.apply( this, arguments ); }, _toggleClass: $.fn.toggleClass, toggleClass: function( classNames, force, speed, easing, callback ) { if ( typeof force === "boolean" || force === undefined ) { if ( !speed ) { // without speed parameter return this._toggleClass( classNames, force ); } else { return $.effects.animateClass.call( this, (force ? { add: classNames } : { remove: classNames }), speed, easing, callback ); } } else { // without force parameter return $.effects.animateClass.call( this, { toggle: classNames }, force, speed, easing ); } }, switchClass: function( remove, add, speed, easing, callback) { return $.effects.animateClass.call( this, { add: add, remove: remove }, speed, easing, callback ); } }); })(); /******************************************************************************/ /*********************************** EFFECTS **********************************/ /******************************************************************************/ (function() { $.extend( $.effects, { version: "1.10.1", // Saves a set of properties in a data storage save: function( element, set ) { for( var i=0; i < set.length; i++ ) { if ( set[ i ] !== null ) { element.data( dataSpace + set[ i ], element[ 0 ].style[ set[ i ] ] ); } } }, // Restores a set of previously saved properties from a data storage restore: function( element, set ) { var val, i; for( i=0; i < set.length; i++ ) { if ( set[ i ] !== null ) { val = element.data( dataSpace + set[ i ] ); // support: jQuery 1.6.2 // http://bugs.jquery.com/ticket/9917 // jQuery 1.6.2 incorrectly returns undefined for any falsy value. // We can't differentiate between "" and 0 here, so we just assume // empty string since it's likely to be a more common value... if ( val === undefined ) { val = ""; } element.css( set[ i ], val ); } } }, setMode: function( el, mode ) { if (mode === "toggle") { mode = el.is( ":hidden" ) ? "show" : "hide"; } return mode; }, // Translates a [top,left] array into a baseline value // this should be a little more flexible in the future to handle a string & hash getBaseline: function( origin, original ) { var y, x; switch ( origin[ 0 ] ) { case "top": y = 0; break; case "middle": y = 0.5; break; case "bottom": y = 1; break; default: y = origin[ 0 ] / original.height; } switch ( origin[ 1 ] ) { case "left": x = 0; break; case "center": x = 0.5; break; case "right": x = 1; break; default: x = origin[ 1 ] / original.width; } return { x: x, y: y }; }, // Wraps the element around a wrapper that copies position properties createWrapper: function( element ) { // if the element is already wrapped, return it if ( element.parent().is( ".ui-effects-wrapper" )) { return element.parent(); } // wrap the element var props = { width: element.outerWidth(true), height: element.outerHeight(true), "float": element.css( "float" ) }, wrapper = $( "

        " ) .addClass( "ui-effects-wrapper" ) .css({ fontSize: "100%", background: "transparent", border: "none", margin: 0, padding: 0 }), // Store the size in case width/height are defined in % - Fixes #5245 size = { width: element.width(), height: element.height() }, active = document.activeElement; // support: Firefox // Firefox incorrectly exposes anonymous content // https://bugzilla.mozilla.org/show_bug.cgi?id=561664 try { active.id; } catch( e ) { active = document.body; } element.wrap( wrapper ); // Fixes #7595 - Elements lose focus when wrapped. if ( element[ 0 ] === active || $.contains( element[ 0 ], active ) ) { $( active ).focus(); } wrapper = element.parent(); //Hotfix for jQuery 1.4 since some change in wrap() seems to actually lose the reference to the wrapped element // transfer positioning properties to the wrapper if ( element.css( "position" ) === "static" ) { wrapper.css({ position: "relative" }); element.css({ position: "relative" }); } else { $.extend( props, { position: element.css( "position" ), zIndex: element.css( "z-index" ) }); $.each([ "top", "left", "bottom", "right" ], function(i, pos) { props[ pos ] = element.css( pos ); if ( isNaN( parseInt( props[ pos ], 10 ) ) ) { props[ pos ] = "auto"; } }); element.css({ position: "relative", top: 0, left: 0, right: "auto", bottom: "auto" }); } element.css(size); return wrapper.css( props ).show(); }, removeWrapper: function( element ) { var active = document.activeElement; if ( element.parent().is( ".ui-effects-wrapper" ) ) { element.parent().replaceWith( element ); // Fixes #7595 - Elements lose focus when wrapped. if ( element[ 0 ] === active || $.contains( element[ 0 ], active ) ) { $( active ).focus(); } } return element; }, setTransition: function( element, list, factor, value ) { value = value || {}; $.each( list, function( i, x ) { var unit = element.cssUnit( x ); if ( unit[ 0 ] > 0 ) { value[ x ] = unit[ 0 ] * factor + unit[ 1 ]; } }); return value; } }); // return an effect options object for the given parameters: function _normalizeArguments( effect, options, speed, callback ) { // allow passing all options as the first parameter if ( $.isPlainObject( effect ) ) { options = effect; effect = effect.effect; } // convert to an object effect = { effect: effect }; // catch (effect, null, ...) if ( options == null ) { options = {}; } // catch (effect, callback) if ( $.isFunction( options ) ) { callback = options; speed = null; options = {}; } // catch (effect, speed, ?) if ( typeof options === "number" || $.fx.speeds[ options ] ) { callback = speed; speed = options; options = {}; } // catch (effect, options, callback) if ( $.isFunction( speed ) ) { callback = speed; speed = null; } // add options to effect if ( options ) { $.extend( effect, options ); } speed = speed || options.duration; effect.duration = $.fx.off ? 0 : typeof speed === "number" ? speed : speed in $.fx.speeds ? $.fx.speeds[ speed ] : $.fx.speeds._default; effect.complete = callback || options.complete; return effect; } function standardSpeed( speed ) { // valid standard speeds if ( !speed || typeof speed === "number" || $.fx.speeds[ speed ] ) { return true; } // invalid strings - treat as "normal" speed return typeof speed === "string" && !$.effects.effect[ speed ]; } $.fn.extend({ effect: function( /* effect, options, speed, callback */ ) { var args = _normalizeArguments.apply( this, arguments ), mode = args.mode, queue = args.queue, effectMethod = $.effects.effect[ args.effect ]; if ( $.fx.off || !effectMethod ) { // delegate to the original method (e.g., .show()) if possible if ( mode ) { return this[ mode ]( args.duration, args.complete ); } else { return this.each( function() { if ( args.complete ) { args.complete.call( this ); } }); } } function run( next ) { var elem = $( this ), complete = args.complete, mode = args.mode; function done() { if ( $.isFunction( complete ) ) { complete.call( elem[0] ); } if ( $.isFunction( next ) ) { next(); } } // if the element is hiddden and mode is hide, // or element is visible and mode is show if ( elem.is( ":hidden" ) ? mode === "hide" : mode === "show" ) { done(); } else { effectMethod.call( elem[0], args, done ); } } return queue === false ? this.each( run ) : this.queue( queue || "fx", run ); }, _show: $.fn.show, show: function( speed ) { if ( standardSpeed( speed ) ) { return this._show.apply( this, arguments ); } else { var args = _normalizeArguments.apply( this, arguments ); args.mode = "show"; return this.effect.call( this, args ); } }, _hide: $.fn.hide, hide: function( speed ) { if ( standardSpeed( speed ) ) { return this._hide.apply( this, arguments ); } else { var args = _normalizeArguments.apply( this, arguments ); args.mode = "hide"; return this.effect.call( this, args ); } }, // jQuery core overloads toggle and creates _toggle __toggle: $.fn.toggle, toggle: function( speed ) { if ( standardSpeed( speed ) || typeof speed === "boolean" || $.isFunction( speed ) ) { return this.__toggle.apply( this, arguments ); } else { var args = _normalizeArguments.apply( this, arguments ); args.mode = "toggle"; return this.effect.call( this, args ); } }, // helper functions cssUnit: function(key) { var style = this.css( key ), val = []; $.each( [ "em", "px", "%", "pt" ], function( i, unit ) { if ( style.indexOf( unit ) > 0 ) { val = [ parseFloat( style ), unit ]; } }); return val; } }); })(); /******************************************************************************/ /*********************************** EASING ***********************************/ /******************************************************************************/ (function() { // based on easing equations from Robert Penner (http://www.robertpenner.com/easing) var baseEasings = {}; $.each( [ "Quad", "Cubic", "Quart", "Quint", "Expo" ], function( i, name ) { baseEasings[ name ] = function( p ) { return Math.pow( p, i + 2 ); }; }); $.extend( baseEasings, { Sine: function ( p ) { return 1 - Math.cos( p * Math.PI / 2 ); }, Circ: function ( p ) { return 1 - Math.sqrt( 1 - p * p ); }, Elastic: function( p ) { return p === 0 || p === 1 ? p : -Math.pow( 2, 8 * (p - 1) ) * Math.sin( ( (p - 1) * 80 - 7.5 ) * Math.PI / 15 ); }, Back: function( p ) { return p * p * ( 3 * p - 2 ); }, Bounce: function ( p ) { var pow2, bounce = 4; while ( p < ( ( pow2 = Math.pow( 2, --bounce ) ) - 1 ) / 11 ) {} return 1 / Math.pow( 4, 3 - bounce ) - 7.5625 * Math.pow( ( pow2 * 3 - 2 ) / 22 - p, 2 ); } }); $.each( baseEasings, function( name, easeIn ) { $.easing[ "easeIn" + name ] = easeIn; $.easing[ "easeOut" + name ] = function( p ) { return 1 - easeIn( 1 - p ); }; $.easing[ "easeInOut" + name ] = function( p ) { return p < 0.5 ? easeIn( p * 2 ) / 2 : 1 - easeIn( p * -2 + 2 ) / 2; }; }); })(); })(jQuery)); (function( $, undefined ) { var uid = 0, hideProps = {}, showProps = {}; hideProps.height = hideProps.paddingTop = hideProps.paddingBottom = hideProps.borderTopWidth = hideProps.borderBottomWidth = "hide"; showProps.height = showProps.paddingTop = showProps.paddingBottom = showProps.borderTopWidth = showProps.borderBottomWidth = "show"; $.widget( "ui.accordion", { version: "1.10.1", options: { active: 0, animate: {}, collapsible: false, event: "click", header: "> li > :first-child,> :not(li):even", heightStyle: "auto", icons: { activeHeader: "ui-icon-triangle-1-s", header: "ui-icon-triangle-1-e" }, // callbacks activate: null, beforeActivate: null }, _create: function() { var options = this.options; this.prevShow = this.prevHide = $(); this.element.addClass( "ui-accordion ui-widget ui-helper-reset" ) // ARIA .attr( "role", "tablist" ); // don't allow collapsible: false and active: false / null if ( !options.collapsible && (options.active === false || options.active == null) ) { options.active = 0; } this._processPanels(); // handle negative values if ( options.active < 0 ) { options.active += this.headers.length; } this._refresh(); }, _getCreateEventData: function() { return { header: this.active, panel: !this.active.length ? $() : this.active.next(), content: !this.active.length ? $() : this.active.next() }; }, _createIcons: function() { var icons = this.options.icons; if ( icons ) { $( "" ) .addClass( "ui-accordion-header-icon ui-icon " + icons.header ) .prependTo( this.headers ); this.active.children( ".ui-accordion-header-icon" ) .removeClass( icons.header ) .addClass( icons.activeHeader ); this.headers.addClass( "ui-accordion-icons" ); } }, _destroyIcons: function() { this.headers .removeClass( "ui-accordion-icons" ) .children( ".ui-accordion-header-icon" ) .remove(); }, _destroy: function() { var contents; // clean up main element this.element .removeClass( "ui-accordion ui-widget ui-helper-reset" ) .removeAttr( "role" ); // clean up headers this.headers .removeClass( "ui-accordion-header ui-accordion-header-active ui-helper-reset ui-state-default ui-corner-all ui-state-active ui-state-disabled ui-corner-top" ) .removeAttr( "role" ) .removeAttr( "aria-selected" ) .removeAttr( "aria-controls" ) .removeAttr( "tabIndex" ) .each(function() { if ( /^ui-accordion/.test( this.id ) ) { this.removeAttribute( "id" ); } }); this._destroyIcons(); // clean up content panels contents = this.headers.next() .css( "display", "" ) .removeAttr( "role" ) .removeAttr( "aria-expanded" ) .removeAttr( "aria-hidden" ) .removeAttr( "aria-labelledby" ) .removeClass( "ui-helper-reset ui-widget-content ui-corner-bottom ui-accordion-content ui-accordion-content-active ui-state-disabled" ) .each(function() { if ( /^ui-accordion/.test( this.id ) ) { this.removeAttribute( "id" ); } }); if ( this.options.heightStyle !== "content" ) { contents.css( "height", "" ); } }, _setOption: function( key, value ) { if ( key === "active" ) { // _activate() will handle invalid values and update this.options this._activate( value ); return; } if ( key === "event" ) { if ( this.options.event ) { this._off( this.headers, this.options.event ); } this._setupEvents( value ); } this._super( key, value ); // setting collapsible: false while collapsed; open first panel if ( key === "collapsible" && !value && this.options.active === false ) { this._activate( 0 ); } if ( key === "icons" ) { this._destroyIcons(); if ( value ) { this._createIcons(); } } // #5332 - opacity doesn't cascade to positioned elements in IE // so we need to add the disabled class to the headers and panels if ( key === "disabled" ) { this.headers.add( this.headers.next() ) .toggleClass( "ui-state-disabled", !!value ); } }, _keydown: function( event ) { /*jshint maxcomplexity:15*/ if ( event.altKey || event.ctrlKey ) { return; } var keyCode = $.ui.keyCode, length = this.headers.length, currentIndex = this.headers.index( event.target ), toFocus = false; switch ( event.keyCode ) { case keyCode.RIGHT: case keyCode.DOWN: toFocus = this.headers[ ( currentIndex + 1 ) % length ]; break; case keyCode.LEFT: case keyCode.UP: toFocus = this.headers[ ( currentIndex - 1 + length ) % length ]; break; case keyCode.SPACE: case keyCode.ENTER: this._eventHandler( event ); break; case keyCode.HOME: toFocus = this.headers[ 0 ]; break; case keyCode.END: toFocus = this.headers[ length - 1 ]; break; } if ( toFocus ) { $( event.target ).attr( "tabIndex", -1 ); $( toFocus ).attr( "tabIndex", 0 ); toFocus.focus(); event.preventDefault(); } }, _panelKeyDown : function( event ) { if ( event.keyCode === $.ui.keyCode.UP && event.ctrlKey ) { $( event.currentTarget ).prev().focus(); } }, refresh: function() { var options = this.options; this._processPanels(); // was collapsed or no panel if ( ( options.active === false && options.collapsible === true ) || !this.headers.length ) { options.active = false; this.active = $(); // active false only when collapsible is true } if ( options.active === false ) { this._activate( 0 ); // was active, but active panel is gone } else if ( this.active.length && !$.contains( this.element[ 0 ], this.active[ 0 ] ) ) { // all remaining panel are disabled if ( this.headers.length === this.headers.find(".ui-state-disabled").length ) { options.active = false; this.active = $(); // activate previous panel } else { this._activate( Math.max( 0, options.active - 1 ) ); } // was active, active panel still exists } else { // make sure active index is correct options.active = this.headers.index( this.active ); } this._destroyIcons(); this._refresh(); }, _processPanels: function() { this.headers = this.element.find( this.options.header ) .addClass( "ui-accordion-header ui-helper-reset ui-state-default ui-corner-all" ); this.headers.next() .addClass( "ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom" ) .filter(":not(.ui-accordion-content-active)") .hide(); }, _refresh: function() { var maxHeight, options = this.options, heightStyle = options.heightStyle, parent = this.element.parent(), accordionId = this.accordionId = "ui-accordion-" + (this.element.attr( "id" ) || ++uid); this.active = this._findActive( options.active ) .addClass( "ui-accordion-header-active ui-state-active ui-corner-top" ) .removeClass( "ui-corner-all" ); this.active.next() .addClass( "ui-accordion-content-active" ) .show(); this.headers .attr( "role", "tab" ) .each(function( i ) { var header = $( this ), headerId = header.attr( "id" ), panel = header.next(), panelId = panel.attr( "id" ); if ( !headerId ) { headerId = accordionId + "-header-" + i; header.attr( "id", headerId ); } if ( !panelId ) { panelId = accordionId + "-panel-" + i; panel.attr( "id", panelId ); } header.attr( "aria-controls", panelId ); panel.attr( "aria-labelledby", headerId ); }) .next() .attr( "role", "tabpanel" ); this.headers .not( this.active ) .attr({ "aria-selected": "false", tabIndex: -1 }) .next() .attr({ "aria-expanded": "false", "aria-hidden": "true" }) .hide(); // make sure at least one header is in the tab order if ( !this.active.length ) { this.headers.eq( 0 ).attr( "tabIndex", 0 ); } else { this.active.attr({ "aria-selected": "true", tabIndex: 0 }) .next() .attr({ "aria-expanded": "true", "aria-hidden": "false" }); } this._createIcons(); this._setupEvents( options.event ); if ( heightStyle === "fill" ) { maxHeight = parent.height(); this.element.siblings( ":visible" ).each(function() { var elem = $( this ), position = elem.css( "position" ); if ( position === "absolute" || position === "fixed" ) { return; } maxHeight -= elem.outerHeight( true ); }); this.headers.each(function() { maxHeight -= $( this ).outerHeight( true ); }); this.headers.next() .each(function() { $( this ).height( Math.max( 0, maxHeight - $( this ).innerHeight() + $( this ).height() ) ); }) .css( "overflow", "auto" ); } else if ( heightStyle === "auto" ) { maxHeight = 0; this.headers.next() .each(function() { maxHeight = Math.max( maxHeight, $( this ).css( "height", "" ).height() ); }) .height( maxHeight ); } }, _activate: function( index ) { var active = this._findActive( index )[ 0 ]; // trying to activate the already active panel if ( active === this.active[ 0 ] ) { return; } // trying to collapse, simulate a click on the currently active header active = active || this.active[ 0 ]; this._eventHandler({ target: active, currentTarget: active, preventDefault: $.noop }); }, _findActive: function( selector ) { return typeof selector === "number" ? this.headers.eq( selector ) : $(); }, _setupEvents: function( event ) { var events = { keydown: "_keydown" }; if ( event ) { $.each( event.split(" "), function( index, eventName ) { events[ eventName ] = "_eventHandler"; }); } this._off( this.headers.add( this.headers.next() ) ); this._on( this.headers, events ); this._on( this.headers.next(), { keydown: "_panelKeyDown" }); this._hoverable( this.headers ); this._focusable( this.headers ); }, _eventHandler: function( event ) { var options = this.options, active = this.active, clicked = $( event.currentTarget ), clickedIsActive = clicked[ 0 ] === active[ 0 ], collapsing = clickedIsActive && options.collapsible, toShow = collapsing ? $() : clicked.next(), toHide = active.next(), eventData = { oldHeader: active, oldPanel: toHide, newHeader: collapsing ? $() : clicked, newPanel: toShow }; event.preventDefault(); if ( // click on active header, but not collapsible ( clickedIsActive && !options.collapsible ) || // allow canceling activation ( this._trigger( "beforeActivate", event, eventData ) === false ) ) { return; } options.active = collapsing ? false : this.headers.index( clicked ); // when the call to ._toggle() comes after the class changes // it causes a very odd bug in IE 8 (see #6720) this.active = clickedIsActive ? $() : clicked; this._toggle( eventData ); // switch classes // corner classes on the previously active header stay after the animation active.removeClass( "ui-accordion-header-active ui-state-active" ); if ( options.icons ) { active.children( ".ui-accordion-header-icon" ) .removeClass( options.icons.activeHeader ) .addClass( options.icons.header ); } if ( !clickedIsActive ) { clicked .removeClass( "ui-corner-all" ) .addClass( "ui-accordion-header-active ui-state-active ui-corner-top" ); if ( options.icons ) { clicked.children( ".ui-accordion-header-icon" ) .removeClass( options.icons.header ) .addClass( options.icons.activeHeader ); } clicked .next() .addClass( "ui-accordion-content-active" ); } }, _toggle: function( data ) { var toShow = data.newPanel, toHide = this.prevShow.length ? this.prevShow : data.oldPanel; // handle activating a panel during the animation for another activation this.prevShow.add( this.prevHide ).stop( true, true ); this.prevShow = toShow; this.prevHide = toHide; if ( this.options.animate ) { this._animate( toShow, toHide, data ); } else { toHide.hide(); toShow.show(); this._toggleComplete( data ); } toHide.attr({ "aria-expanded": "false", "aria-hidden": "true" }); toHide.prev().attr( "aria-selected", "false" ); // if we're switching panels, remove the old header from the tab order // if we're opening from collapsed state, remove the previous header from the tab order // if we're collapsing, then keep the collapsing header in the tab order if ( toShow.length && toHide.length ) { toHide.prev().attr( "tabIndex", -1 ); } else if ( toShow.length ) { this.headers.filter(function() { return $( this ).attr( "tabIndex" ) === 0; }) .attr( "tabIndex", -1 ); } toShow .attr({ "aria-expanded": "true", "aria-hidden": "false" }) .prev() .attr({ "aria-selected": "true", tabIndex: 0 }); }, _animate: function( toShow, toHide, data ) { var total, easing, duration, that = this, adjust = 0, down = toShow.length && ( !toHide.length || ( toShow.index() < toHide.index() ) ), animate = this.options.animate || {}, options = down && animate.down || animate, complete = function() { that._toggleComplete( data ); }; if ( typeof options === "number" ) { duration = options; } if ( typeof options === "string" ) { easing = options; } // fall back from options to animation in case of partial down settings easing = easing || options.easing || animate.easing; duration = duration || options.duration || animate.duration; if ( !toHide.length ) { return toShow.animate( showProps, duration, easing, complete ); } if ( !toShow.length ) { return toHide.animate( hideProps, duration, easing, complete ); } total = toShow.show().outerHeight(); toHide.animate( hideProps, { duration: duration, easing: easing, step: function( now, fx ) { fx.now = Math.round( now ); } }); toShow .hide() .animate( showProps, { duration: duration, easing: easing, complete: complete, step: function( now, fx ) { fx.now = Math.round( now ); if ( fx.prop !== "height" ) { adjust += fx.now; } else if ( that.options.heightStyle !== "content" ) { fx.now = Math.round( total - toHide.outerHeight() - adjust ); adjust = 0; } } }); }, _toggleComplete: function( data ) { var toHide = data.oldPanel; toHide .removeClass( "ui-accordion-content-active" ) .prev() .removeClass( "ui-corner-top" ) .addClass( "ui-corner-all" ); // Work around for rendering bug in IE (#5421) if ( toHide.length ) { toHide.parent()[0].className = toHide.parent()[0].className; } this._trigger( "activate", null, data ); } }); })( jQuery ); (function( $, undefined ) { // used to prevent race conditions with remote data sources var requestIndex = 0; $.widget( "ui.autocomplete", { version: "1.10.1", defaultElement: "", options: { appendTo: null, autoFocus: false, delay: 300, minLength: 1, position: { my: "left top", at: "left bottom", collision: "none" }, source: null, // callbacks change: null, close: null, focus: null, open: null, response: null, search: null, select: null }, pending: 0, _create: function() { // Some browsers only repeat keydown events, not keypress events, // so we use the suppressKeyPress flag to determine if we've already // handled the keydown event. #7269 // Unfortunately the code for & in keypress is the same as the up arrow, // so we use the suppressKeyPressRepeat flag to avoid handling keypress // events when we know the keydown event was used to modify the // search term. #7799 var suppressKeyPress, suppressKeyPressRepeat, suppressInput, nodeName = this.element[0].nodeName.toLowerCase(), isTextarea = nodeName === "textarea", isInput = nodeName === "input"; this.isMultiLine = // Textareas are always multi-line isTextarea ? true : // Inputs are always single-line, even if inside a contentEditable element // IE also treats inputs as contentEditable isInput ? false : // All other element types are determined by whether or not they're contentEditable this.element.prop( "isContentEditable" ); this.valueMethod = this.element[ isTextarea || isInput ? "val" : "text" ]; this.isNewMenu = true; this.element .addClass( "ui-autocomplete-input" ) .attr( "autocomplete", "off" ); this._on( this.element, { keydown: function( event ) { /*jshint maxcomplexity:15*/ if ( this.element.prop( "readOnly" ) ) { suppressKeyPress = true; suppressInput = true; suppressKeyPressRepeat = true; return; } suppressKeyPress = false; suppressInput = false; suppressKeyPressRepeat = false; var keyCode = $.ui.keyCode; switch( event.keyCode ) { case keyCode.PAGE_UP: suppressKeyPress = true; this._move( "previousPage", event ); break; case keyCode.PAGE_DOWN: suppressKeyPress = true; this._move( "nextPage", event ); break; case keyCode.UP: suppressKeyPress = true; this._keyEvent( "previous", event ); break; case keyCode.DOWN: suppressKeyPress = true; this._keyEvent( "next", event ); break; case keyCode.ENTER: case keyCode.NUMPAD_ENTER: // when menu is open and has focus if ( this.menu.active ) { // #6055 - Opera still allows the keypress to occur // which causes forms to submit suppressKeyPress = true; event.preventDefault(); this.menu.select( event ); } break; case keyCode.TAB: if ( this.menu.active ) { this.menu.select( event ); } break; case keyCode.ESCAPE: if ( this.menu.element.is( ":visible" ) ) { this._value( this.term ); this.close( event ); // Different browsers have different default behavior for escape // Single press can mean undo or clear // Double press in IE means clear the whole form event.preventDefault(); } break; default: suppressKeyPressRepeat = true; // search timeout should be triggered before the input value is changed this._searchTimeout( event ); break; } }, keypress: function( event ) { if ( suppressKeyPress ) { suppressKeyPress = false; event.preventDefault(); return; } if ( suppressKeyPressRepeat ) { return; } // replicate some key handlers to allow them to repeat in Firefox and Opera var keyCode = $.ui.keyCode; switch( event.keyCode ) { case keyCode.PAGE_UP: this._move( "previousPage", event ); break; case keyCode.PAGE_DOWN: this._move( "nextPage", event ); break; case keyCode.UP: this._keyEvent( "previous", event ); break; case keyCode.DOWN: this._keyEvent( "next", event ); break; } }, input: function( event ) { if ( suppressInput ) { suppressInput = false; event.preventDefault(); return; } this._searchTimeout( event ); }, focus: function() { this.selectedItem = null; this.previous = this._value(); }, blur: function( event ) { if ( this.cancelBlur ) { delete this.cancelBlur; return; } clearTimeout( this.searching ); this.close( event ); this._change( event ); } }); this._initSource(); this.menu = $( "