├── .dockerignore ├── .editorconfig ├── .eslintrc.json ├── .github ├── CODEOWNERS └── workflows │ └── ci.yaml ├── .gitignore ├── .npmignore ├── .whitesource ├── Dockerfile ├── LICENSE ├── MiQ-UI-Architecture.jpg ├── README.md ├── application-settings.js ├── bin ├── before_install ├── build ├── ci └── setup ├── demo ├── assets │ ├── 100 │ │ └── vendor-hawkular.png │ ├── manageiq.svg │ ├── protected.svg │ ├── vendor-centos.svg │ ├── vendor-chrome.svg │ ├── vendor-openshift.svg │ ├── vendor-redhat.svg │ ├── vendor-ubuntu.svg │ └── vendor-vmware.svg ├── controllers │ ├── availableComponentsController.ts │ ├── dialogEditorController.ts │ ├── dialogUserController.ts │ ├── fonticonPickerController.ts │ ├── index.ts │ ├── treeSelectorController.ts │ └── treeViewController.ts ├── data │ ├── data-table.json │ ├── dialog-data-refresh.json │ ├── dialog-data.json │ ├── dialog_editor.json │ ├── lazyTree.json │ └── tree.json ├── index.ts ├── services │ ├── DialogEditorHttpService.ts │ ├── availableComponentBuilder.ts │ ├── availableComponentsService.ts │ └── translateFilter.ts ├── styles │ └── demo-app.scss ├── template-index.ejs └── views │ ├── dialog │ ├── editor.html │ └── user.html │ ├── fonticon-picker │ └── basic.html │ ├── index.ts │ ├── main.html │ └── tree-view │ ├── basic.html │ └── tree-selector.html ├── jsdoc-conf.json ├── karma.conf.js ├── locale ├── en │ └── ui-components.po ├── es │ └── ui-components.po ├── fr │ └── ui-components.po ├── ja │ └── ui-components.po ├── pt_BR │ └── ui-components.po ├── ui-components.pot ├── zanata.xml └── zh_CN │ └── ui-components.po ├── package.json ├── pkg └── .gitkeep ├── renovate.json ├── scripts ├── .coverage.sh └── validate-gettext-catalog.js ├── src ├── common │ ├── components │ │ ├── index.ts │ │ ├── miqPfSort.html │ │ ├── miqPfSort.js │ │ ├── sortItemsComponent.spec.ts │ │ └── sortItemsComponent.ts │ ├── filters │ │ ├── abbrNumberFilter.spec.ts │ │ ├── abbrNumberFilter.ts │ │ ├── adjustColorFilter.spec.ts │ │ ├── adjustColorFilter.ts │ │ └── index.ts │ ├── index.ts │ ├── interfaces │ │ └── endpoints.ts │ ├── services │ │ ├── endpointsService.ts │ │ ├── enpointsService.spec.ts │ │ ├── index.ts │ │ └── translateService.ts │ └── translateFunction.ts ├── dialog-editor │ ├── README.md │ ├── components │ │ ├── abstractModal.ts │ │ ├── box │ │ │ ├── box.html │ │ │ ├── boxComponent.ts │ │ │ └── index.ts │ │ ├── dialog-editor │ │ │ ├── dialog-editor.html │ │ │ ├── dialogEditorComponent.ts │ │ │ └── index.ts │ │ ├── field │ │ │ ├── field.html │ │ │ ├── fieldComponent.ts │ │ │ └── index.ts │ │ ├── index.ts │ │ ├── modal-box │ │ │ ├── box.html │ │ │ ├── index.ts │ │ │ └── modalBoxComponent.ts │ │ ├── modal-field-template │ │ │ ├── check-box.html │ │ │ ├── date-control.html │ │ │ ├── date-time-control.html │ │ │ ├── drop-down-list.html │ │ │ ├── dynamic-values.html │ │ │ ├── fields-to-refresh.html │ │ │ ├── index.ts │ │ │ ├── modalFieldTemplateComponent.spec.ts │ │ │ ├── modalFieldTemplateComponent.ts │ │ │ ├── radio-button.html │ │ │ ├── tag-control.html │ │ │ ├── text-area-box.html │ │ │ └── text-box.html │ │ ├── modal-field │ │ │ ├── field.html │ │ │ ├── index.ts │ │ │ └── modalFieldComponent.ts │ │ ├── modal-tab │ │ │ ├── index.ts │ │ │ ├── modalTabComponent.ts │ │ │ └── tab.html │ │ ├── modal │ │ │ ├── index.ts │ │ │ ├── modalComponent.spec.ts │ │ │ └── modalComponent.ts │ │ ├── tab-list │ │ │ ├── index.ts │ │ │ ├── tab-list.html │ │ │ └── tabListComponent.ts │ │ ├── toolbox │ │ │ ├── index.ts │ │ │ ├── toolbox.html │ │ │ └── toolboxComponent.ts │ │ ├── tree-selector │ │ │ ├── index.ts │ │ │ ├── tree-selector.html │ │ │ └── treeSelector.ts │ │ └── validation │ │ │ ├── index.ts │ │ │ ├── validation.html │ │ │ └── validation.ts │ ├── index.ts │ └── services │ │ ├── dialogEditorService.spec.ts │ │ ├── dialogEditorService.ts │ │ ├── dialogValidationService.spec.ts │ │ ├── dialogValidationService.ts │ │ └── index.ts ├── dialog-user │ ├── components │ │ ├── dialog-user │ │ │ ├── dialog.html │ │ │ ├── dialogField.html │ │ │ ├── dialogField.spec.ts │ │ │ ├── dialogField.ts │ │ │ ├── dialogUser.spec.ts │ │ │ ├── dialogUser.ts │ │ │ └── index.ts │ │ └── index.ts │ ├── index.ts │ ├── interfaces │ │ ├── abstractDialogClass.ts │ │ └── dialog.ts │ └── services │ │ ├── dialogData.spec.ts │ │ ├── dialogData.ts │ │ └── index.ts ├── fonticon-picker │ ├── components │ │ ├── fonticon-family │ │ │ ├── fonticonFamilyComponent.ts │ │ │ └── index.ts │ │ ├── fonticon-picker │ │ │ ├── fonticon-modal.html │ │ │ ├── fonticon-picker.html │ │ │ ├── fonticonPickerComponent.ts │ │ │ └── index.ts │ │ └── index.ts │ ├── index.ts │ └── services │ │ ├── fonticonService.ts │ │ └── index.ts ├── index.ts ├── miq-select │ ├── index.ts │ ├── miqOptions.js │ ├── miqSelect.js │ └── miqSelect.spec.js ├── styles │ ├── colors.scss │ ├── dialog-editor-boxes.scss │ ├── dialog-editor-toolbox.scss │ ├── dialog-editor.scss │ ├── ui-components.scss │ └── vendor.scss ├── tree-selector │ ├── index.ts │ ├── treeSelector.html │ └── treeSelectorComponent.ts ├── tree-view │ ├── index.ts │ ├── treeViewComponent.spec.ts │ └── treeViewComponent.ts └── vendor.ts ├── tsconfig.json ├── tslint.json ├── webpack.config.js ├── webpack.vendor.config.js └── yarn.lock /.dockerignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | /pkg/*.tgz 3 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root= true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | [*] 8 | end_of_line = lf 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | 12 | [*.{js,ts,html,less,css,json}] 13 | indent_style = space 14 | indent_size = 2 15 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "env": { 4 | "browser": true, 5 | "jquery": true 6 | }, 7 | "parser": "babel-eslint", 8 | "parserOptions": { 9 | "ecmaVersion": 5, 10 | "sourceType": "module", 11 | "ecmaFeatures": { 12 | "globalReturn": false, 13 | "impliedStrict": false 14 | } 15 | }, 16 | 17 | "plugins": [], 18 | "extends": [], 19 | 20 | "globals": { 21 | "Promise": false, 22 | "_": false, 23 | "__": false, 24 | "angular": false 25 | }, 26 | 27 | "rules": { 28 | // {{{ this section comes from eslint-config-airbnb-es5 1.0.9 29 | "strict": 0, 30 | "no-shadow": 2, 31 | "no-shadow-restricted-names": 2, 32 | "no-cond-assign": [2, "always"], 33 | "no-console": 1, 34 | "no-constant-condition": 1, 35 | "no-dupe-keys": 2, 36 | "no-duplicate-case": 2, 37 | "no-empty": 2, 38 | "no-empty-character-class": 2, 39 | "no-ex-assign": 2, 40 | "no-extra-semi": 2, 41 | "no-func-assign": 2, 42 | "no-inner-declarations": 2, 43 | "no-invalid-regexp": 2, 44 | "no-irregular-whitespace": 2, 45 | "no-negated-in-lhs": 2, 46 | "no-new-require": 2, 47 | "no-obj-calls": 2, 48 | "no-path-concat": 2, 49 | "no-regex-spaces": 2, 50 | "no-sparse-arrays": 2, 51 | "no-unreachable": 2, 52 | "use-isnan": 2, 53 | "valid-jsdoc": 2, 54 | "valid-typeof": 2, 55 | "dot-notation": [2, { 56 | "allowKeywords": true 57 | }], 58 | "guard-for-in": 2, 59 | "no-caller": 2, 60 | "no-div-regex": 2, 61 | "no-labels": 2, 62 | "no-eval": 2, 63 | "no-extend-native": 2, 64 | "no-extra-bind": 2, 65 | "no-floating-decimal": 2, 66 | "no-implied-eval": 2, 67 | "no-lone-blocks": 2, 68 | "no-loop-func": 2, 69 | "no-multi-str": 2, 70 | "no-native-reassign": 2, 71 | "no-new": 2, 72 | "no-new-func": 2, 73 | "no-new-wrappers": 2, 74 | "no-octal": 2, 75 | "no-octal-escape": 2, 76 | "no-process-exit": 2, 77 | "no-proto": 2, 78 | "no-redeclare": 2, 79 | "no-return-assign": 2, 80 | "no-script-url": 2, 81 | "no-self-compare": 2, 82 | "no-sequences": 2, 83 | "no-throw-literal": 2, 84 | "no-undef-init": 2, 85 | "no-undefined": 1, 86 | "no-with": 2, 87 | "handle-callback-err": 1, 88 | "radix": 2, 89 | "wrap-iife": [2, "any"], 90 | "yoda": 2, 91 | "brace-style": [2, 92 | "1tbs", { 93 | "allowSingleLine": true 94 | }], 95 | "camelcase": [2, { 96 | "properties": "never" 97 | }], 98 | "comma-spacing": [2, { 99 | "before": false, 100 | "after": true 101 | }], 102 | "comma-style": [2, "last"], 103 | "eol-last": 2, 104 | "key-spacing": [2, { 105 | "beforeColon": false, 106 | "afterColon": true 107 | }], 108 | "new-cap": [2, { 109 | "newIsCap": true 110 | }], 111 | "new-parens": 2, 112 | "no-array-constructor": 2, 113 | "no-lonely-if": 1, 114 | "no-multiple-empty-lines": [2, { 115 | "max": 2 116 | }], 117 | "no-nested-ternary": 2, 118 | "no-new-object": 2, 119 | "no-spaced-func": 2, 120 | "no-trailing-spaces": 2, 121 | "no-extra-parens": [2, "functions"], 122 | "one-var": [2, "never"], 123 | "padded-blocks": [2, "never"], 124 | "semi": [2, "always"], 125 | "semi-spacing": [2, { 126 | "before": false, 127 | "after": true 128 | }], 129 | "keyword-spacing": 2, 130 | "space-before-blocks": 2, 131 | "space-before-function-paren": [2, "never"], 132 | "space-infix-ops": 2, 133 | "spaced-comment": [2, "always", { 134 | "exceptions": ["-", "+"], 135 | "markers": ["=", "!"] // space here to support sprockets directives 136 | }], 137 | // }}} (except for removing the JSX rules, and anything we override) 138 | 139 | "indent": [ "error", 2, { 140 | "SwitchCase": 1, 141 | "VariableDeclarator": 1 142 | }], 143 | "consistent-return": 1, 144 | "default-case": 1, 145 | "vars-on-top": 0, 146 | "no-var": 0, 147 | "linebreak-style": [ "error", "unix" ], 148 | "comma-dangle": [ "warn", "always-multiline" ], 149 | "space-unary-ops": [ "error", { 150 | "words": true, 151 | "nonwords": false, 152 | "overrides": { 153 | "!": true 154 | } 155 | }], 156 | "no-unused-vars": [ "error", { 157 | "args": "all", 158 | "argsIgnorePattern": "^_", 159 | "vars": "local", 160 | "caughtErrors": "all", 161 | "caughtErrorsIgnorePattern": "^_" 162 | }], 163 | "no-alert": 2, 164 | "no-debugger": 2, 165 | "no-else-return": 1, 166 | "no-undef": [ "warn", { 167 | "typeof": true 168 | }], 169 | "eqeqeq": [ "error", "smart" ], 170 | "quotes": [ "warn", "single", { 171 | "avoidEscape": true, 172 | "allowTemplateLiterals": true 173 | }], 174 | "func-names": 0, 175 | "no-mixed-spaces-and-tabs": 2, 176 | "camelcase": [ "warn", { 177 | "properties": "never" 178 | }], 179 | 180 | "curly": [ "warn", "all" ], 181 | "no-eq-null": 0, 182 | "no-param-reassign": 1, 183 | "no-fallthrough": [ "error", { 184 | "commentPattern": "fall.*through|pass" 185 | }], 186 | "no-use-before-define": [ "error", { 187 | "functions": false, 188 | "classes": true 189 | }] 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # dialog editor 2 | src/dialog-editor @romanblanco 3 | src/dialog-user @chalettu 4 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | pull_request: 6 | workflow_dispatch: 7 | schedule: 8 | - cron: '0 0 * * *' 9 | 10 | jobs: 11 | ci: 12 | runs-on: ubuntu-latest 13 | strategy: 14 | matrix: 15 | node-version: 16 | - '14' 17 | steps: 18 | - uses: actions/checkout@v4 19 | - name: Set up system 20 | run: bin/before_install 21 | - name: Set up Node 22 | uses: actions/setup-node@v4 23 | with: 24 | node-version: ${{ matrix.node-version }} 25 | # TODO: Uncomment this for yarn 3 26 | # cache: yarn 27 | # registry-url: https://npm.manageiq.org/ 28 | - name: Prepare tests 29 | run: bin/setup 30 | - name: Run tests 31 | run: bin/ci 32 | - name: Report code coverage 33 | if: ${{ github.ref == 'refs/heads/master' && matrix.node-version == '14' }} 34 | continue-on-error: true 35 | run: scripts/.coverage.sh 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs/ 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Users Environment Variables 11 | .lock-wscript 12 | 13 | dist/assets/ 14 | dist/css 15 | dist/data/ 16 | dist/docs/ 17 | dist/js 18 | dist/**/demo-app.* 19 | dist/*.ttf 20 | dist/*.eot 21 | dist/*.svg 22 | dist/*.woff 23 | dist/*.woff2 24 | .tmp/ 25 | libs/ 26 | node_modules/ 27 | dist/index.html 28 | tsd/ 29 | typings/ 30 | vendor.js 31 | vendor.css 32 | *.css.map 33 | *.js.map 34 | 35 | ._* 36 | .DS_Store 37 | 38 | # idea files 39 | .idea/ 40 | *.ipr 41 | *.iws 42 | *.iml 43 | 44 | coverage 45 | yarn.lock 46 | package-lock.json 47 | pkg/ 48 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs/ 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Users Environment Variables 11 | .lock-wscript 12 | 13 | dist/assets/ 14 | dist/data/ 15 | dist/docs/ 16 | dist/**/demo-app.* 17 | .tmp/ 18 | libs/ 19 | node_modules/ 20 | dist/index.html 21 | tsd/ 22 | typings/ 23 | vendor.js 24 | vendor.css 25 | 26 | ._* 27 | .DS_Store 28 | 29 | # idea files 30 | .idea/ 31 | *.ipr 32 | *.iws 33 | *.iml 34 | 35 | coverage 36 | -------------------------------------------------------------------------------- /.whitesource: -------------------------------------------------------------------------------- 1 | { 2 | "settingsInheritedFrom": "ManageIQ/whitesource-config@master" 3 | } 4 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:2.7-buster 2 | 3 | SHELL ["/bin/bash", "--login", "-c"] 4 | 5 | RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/master/install.sh | bash 6 | RUN nvm install 14 7 | RUN npm install --global yarn 8 | 9 | WORKDIR /ui-components 10 | COPY . /ui-components 11 | RUN git clean -fdx 12 | RUN yarn install 13 | RUN yarn pack 14 | -------------------------------------------------------------------------------- /MiQ-UI-Architecture.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ManageIQ/ui-components/297eaf92aa8e19056ace8e704a7477073f4f1d4e/MiQ-UI-Architecture.jpg -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Angular UI Components for ManageIQ 2 | 3 | [![CI](https://github.com/ManageIQ/ui-components/actions/workflows/ci.yaml/badge.svg)](https://github.com/ManageIQ/ui-components/actions/workflows/ci.yaml) 4 | [![Coverage Status](https://coveralls.io/repos/github/ManageIQ/ui-components/badge.svg)](https://coveralls.io/github/ManageIQ/ui-components) 5 | [![Chat](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/ManageIQ/manageiq/ui?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 6 | 7 | [![score](https://www.bithound.io/github/ManageIQ/ui-components/badges/score.svg)](https://www.bithound.io/github/ManageIQ/ui-components) 8 | [![dependencies](https://www.bithound.io/github/ManageIQ/ui-components/badges/dependencies.svg)](https://www.bithound.io/github/ManageIQ/ui-components/master/dependencies/npm) 9 | [![bitHound Dev Dependencies](https://www.bithound.io/github/ManageIQ/ui-components/badges/devDependencies.svg)](https://www.bithound.io/github/ManageIQ/ui-components/master/dependencies/npm) 10 | [![Known Vulnerabilities](https://snyk.io/test/github/mtho11/ui-components/badge.svg)](https://snyk.io/test/github/mtho11/ui-components) 11 | 12 | ## Purpose 13 | 14 | The purpose of this repository is to provide reusable components for the [ManageIQ](http:github.com/manageiq/manageiq) 15 | project. These are not general purpose components, but specific to ManageIQ, however, reusable across all of 16 | ManageIQ (providers). The intention is to provide components that are reusable in various ways. Many of these components 17 | are 'Smart Components' that know how to communicate to backend endpoints(data-driven by provider) and retrieve relevant data for 18 | the component's configuration. 19 | 20 | As we achieve greater reuse, the idea is to move more and more components to this repository. Creating a repository for 21 | *smart* reusable components (specific to a domain) across providers. 22 | 23 | ## Architectural Goals 24 | 25 | * Separate git repository from ManageIQ 26 | * Components communicate via REST with ManageIQ API 27 | * Maintain routing inside ManageIQ (routes.rb) 28 | 29 | ## Technologies 30 | 31 | * Angular 1.5+ (soon to be Angular 2.x) 32 | * Typescript 33 | * Webpack 34 | * Yarn 35 | 36 | ## Architecture 37 | 38 | ![ManageIQ UI Components Architecture](MiQ-UI-Architecture.jpg) 39 | 40 | ## Angular 1.5 Components 41 | 42 | We are recommending [Angular 1.5 Components](https://docs.angularjs.org/guide/component) instead of Angular Directives 43 | for better compatibility and easier upgrade to Angular 2.0. 44 | 45 | For a great overview of using Angular 1.5.x Components please see: [NG-Conf 2016: Components, Components, Components!...and Angular 1.5 - Pete Bacon Darwin](https://www.youtube.com/watch?list=PLOETEcp3DkCq788xapkP_OU-78jhTf68j&v=AMwjDibFxno&ab_channel=ng-conf) 46 | 47 | 48 | ## Development Environment 49 | 50 | You need to have installed [Node.js >= 14 and npm >= 6](https://docs.npmjs.com/getting-started/installing-node) on your system. 51 | It is recommended to use a node version manager such as [n](https://www.npmjs.com/package/n). If you have node installed then it is 52 | just `yarn global add n` and then `n lts` to use the latest LTS version of node (see the docs for switching versions). 53 | 54 | Install these node packages globally in the system 55 | ``` 56 | npm install -g yarn 57 | yarn global add webpack wiredep-cli typescript typescript-formatter 58 | ``` 59 | 60 | After [yarn](http://yarn.io) is installed, it is pretty much a replacement for npm, with faster, more dependable builds 61 | but still utilizing the npm packages. 62 | 63 | See comparison: [npm vs. yarn commands](https://yarnpkg.com/en/docs/migrating-from-npm) 64 | 65 | Install local node dependencies 66 | ``` 67 | yarn 68 | ``` 69 | 70 | Create library dependencies (run this every time you make any changes to `vendor.ts`) - no need to worry about any TS 71 | errors. Also, if you are pushing some changes please run this command so you will push minifed version of JS and CSS. 72 | ``` 73 | yarn run build 74 | ``` 75 | 76 | To run: 77 | ``` 78 | yarn start 79 | ``` 80 | 81 | To run tests: 82 | ``` 83 | yarn 84 | yarn run build-dev 85 | yarn run test 86 | ``` 87 | 88 | Before submitting code, run the following command to format the code according to the tslint rules: 89 | ``` 90 | tsmft -r 91 | ``` 92 | 93 | This formats the code according to the tslint rules. 94 | 95 | #### Documentation 96 | 97 | If you want to see documentation for each component, controller, filter, etc. run 98 | ``` 99 | yarn run-script build-docs 100 | ``` 101 | This will generate docs from JS docs and after running `yarn start` this documentation will be available on `localhost:4000/docs` 102 | 103 | If you want to release ui-components look at documentation in Wiki of this repository. 104 | 105 | #### ManageIQ version mapping 106 | 107 | 1.6 - master, radjabov 108 | 1.5 - petrosian, quinteros 109 | 1.4 - kasparov, lasker, morphy, najdorf, oparin 110 | 1.3 - jansa 111 | 1.2 - ivanchuk 112 | 1.1 - hammer 113 | 1.0 - gaprindashvili 114 | -------------------------------------------------------------------------------- /application-settings.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | module.exports = { 3 | stylesFolder: '/styles', 4 | sourceFolder: '/src', 5 | distFolder: '/dist', 6 | javascriptFolder: 'js', 7 | stylesheetFolder: 'css', 8 | appName: 'ui-components', 9 | modules: { 10 | common: '/common', 11 | dialogEditor: '/dialog-editor', 12 | fonticonPicker: '/fonticon-picker', 13 | dialogs: '/dialog-user', 14 | treeView: '/tree-view', 15 | treeSelector: '/tree-selector', 16 | miqSelect: '/miq-select', 17 | }, 18 | nodePackages: 'node_modules/', 19 | get stylesheetPath() { 20 | return this.stylesheetFolder + '/[name]' + '.css'; 21 | }, 22 | get indexLocation() { 23 | return __dirname + '/demo/index.html'; 24 | }, 25 | isMinified: function (production) { 26 | return (!production ? '.js' : '.min.js'); 27 | }, 28 | get sassRootFolder() { 29 | return '.' + this.sourceFolder + this.stylesFolder; 30 | }, 31 | get sassEntryPoint() { 32 | return this.sassRootFolder + '/' + this.appName + '.scss' 33 | }, 34 | get tsEntryPoint() { 35 | return '.' + this.sourceFolder + '/index.ts' 36 | }, 37 | get tsModules() { 38 | let availableObjects = []; 39 | Object.keys(this.modules).forEach(key => { 40 | availableObjects.push('.' + this.sourceFolder + this.modules[key]); 41 | }); 42 | return availableObjects; 43 | }, 44 | get outputFolder() { 45 | return __dirname + this.distFolder 46 | } 47 | }; 48 | -------------------------------------------------------------------------------- /bin/before_install: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | if [ -n "$CI" ]; then 6 | npm install --global yarn 7 | fi 8 | -------------------------------------------------------------------------------- /bin/build: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | image="docker.io/manageiq/ui-components:latest" 6 | 7 | # Build the image, which will build the package 8 | docker build . -t $image --no-cache 9 | 10 | # Extract the package from the image 11 | container_id=$(docker create $image) 12 | package=$(docker run --rm -it --entrypoint /bin/bash $image -c "ls -1 manageiq-ui-components-*.tgz" | tr -d '\r') 13 | docker cp "$container_id:/ui-components/$package" pkg 14 | docker rm "$container_id" 15 | 16 | echo 17 | echo "Package 'pkg/$package' has been built." 18 | echo 19 | 20 | # Optionally publish the image 21 | read -r -p "Publish the package now? (y/N) " -n 1 22 | echo 23 | echo 24 | if [[ "$REPLY" =~ ^[Yy]$ ]]; then 25 | pushd pkg >/dev/null 26 | npm login 27 | npm publish --access public $package 28 | popd >/dev/null 29 | else 30 | echo "You can manually publish the package with:" 31 | echo " cd pkg" 32 | echo " npm login" 33 | echo " npm publish --access public $package" 34 | fi 35 | -------------------------------------------------------------------------------- /bin/ci: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | yarn run build-dev 6 | yarn run test 7 | yarn run gettext:extract 8 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | if [ -n "$CI" ]; then 6 | yarn global add typings webpack karma typescript 7 | fi 8 | 9 | yarn install 10 | -------------------------------------------------------------------------------- /demo/assets/100/vendor-hawkular.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ManageIQ/ui-components/297eaf92aa8e19056ace8e704a7477073f4f1d4e/demo/assets/100/vendor-hawkular.png -------------------------------------------------------------------------------- /demo/assets/manageiq.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12 | 13 | miq-logo 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /demo/assets/vendor-centos.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /demo/assets/vendor-chrome.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /demo/assets/vendor-openshift.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 10 | 11 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /demo/assets/vendor-redhat.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 10 | 19 | 20 | 27 | 29 | 31 | 32 | -------------------------------------------------------------------------------- /demo/assets/vendor-ubuntu.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | ]> 6 | 10 | 11 | 12 | 14 | 27 | 28 | -------------------------------------------------------------------------------- /demo/assets/vendor-vmware.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 9 | 12 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /demo/controllers/availableComponentsController.ts: -------------------------------------------------------------------------------- 1 | import AvailableComponentsService from './../services/availableComponentsService'; 2 | import {IAvailableGroup} from '../services/availableComponentsService'; 3 | 4 | export default class AvailableComponentsController { 5 | public availableComponents: IAvailableGroup[]; 6 | 7 | /* @ngInject */ 8 | public constructor() { 9 | this.availableComponents = (new AvailableComponentsService()).availableComponents; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /demo/controllers/dialogEditorController.ts: -------------------------------------------------------------------------------- 1 | import DialogEditorService from '../../src/dialog-editor/services/dialogEditorService'; 2 | import {ComponentDemo} from '../services/availableComponentBuilder'; 3 | @ComponentDemo({ 4 | name: 'editor', 5 | title: 'Dialog editor', 6 | template: require('./../views/dialog/editor.html'), 7 | group: 'dialog', 8 | controller: 'demoDialogEditor as vm' 9 | }) 10 | export default class DialogEditorController { 11 | public dialog: any; 12 | public modalOptions: any; 13 | public elementInfo: any; 14 | 15 | /* @ngInject */ 16 | constructor(private DialogEditor: DialogEditorService) { 17 | this.init({ 18 | 'content': [{ 19 | 'dialog_tabs': [{ 20 | 'label': 'New Tab', 21 | 'position': 0, 22 | 'dialog_groups': [{ 23 | 'label': 'New Section', 24 | 'position': 0, 25 | 'dialog_fields': [] 26 | }], 27 | }], 28 | }], 29 | }); 30 | } 31 | 32 | public init(dialog) { 33 | this.DialogEditor.setData(dialog); 34 | this.dialog = dialog; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /demo/controllers/dialogUserController.ts: -------------------------------------------------------------------------------- 1 | import {ComponentDemo} from '../services/availableComponentBuilder'; 2 | @ComponentDemo({ 3 | name: 'user', 4 | title: 'Dialog user', 5 | template: require('./../views/dialog/user.html'), 6 | group: 'dialog', 7 | controller: 'demoDialogUser as vm' 8 | }) 9 | export default class DialogUserController { 10 | public dialog: any; 11 | public showDialogData: boolean; 12 | public dialogDataResults: any; 13 | 14 | /* @ngInject */ 15 | constructor() { 16 | const dialogFile = require('../data/dialog-data.json'); 17 | this.dialog = dialogFile.resources[0].content[0]; 18 | this.showDialogData = false; 19 | } 20 | public refreshField(field) { 21 | let Promise: any; 22 | return new Promise((resolve, reject) => { 23 | resolve({ 'status': 'success' }); 24 | }); 25 | } 26 | public dialogData(data) { 27 | this.dialogDataResults = data; 28 | } 29 | public showDialogDataResults() { 30 | this.showDialogData = true; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /demo/controllers/fonticonPickerController.ts: -------------------------------------------------------------------------------- 1 | import {ComponentDemo} from '../services/availableComponentBuilder'; 2 | @ComponentDemo({ 3 | name: 'basic', 4 | title: 'Fonticon picker', 5 | template: require('./../views/fonticon-picker/basic.html'), 6 | group: 'fonticon-picker', 7 | controller: 'demoFonticonPicker as vm' 8 | }) 9 | export default class FonticonPickerController { 10 | } 11 | -------------------------------------------------------------------------------- /demo/controllers/index.ts: -------------------------------------------------------------------------------- 1 | import AvailableComponentsController from './availableComponentsController'; 2 | import FonticonPickerController from './fonticonPickerController'; 3 | import DialogUserController from './dialogUserController'; 4 | import DialogEditorController from './dialogEditorController'; 5 | import TreeViewController from './treeViewController'; 6 | import TreeSelectorController from './treeSelectorController'; 7 | import * as ng from 'angular'; 8 | 9 | export default (module: ng.IModule) => { 10 | module.controller('demoAvailableComponents', AvailableComponentsController); 11 | module.controller('demoFonticonPicker', FonticonPickerController); 12 | module.controller('demoDialogUser', DialogUserController); 13 | module.controller('demoDialogEditor', DialogEditorController); 14 | module.controller('demoTreeView', TreeViewController); 15 | module.controller('demoTreeSelector', TreeSelectorController); 16 | }; 17 | -------------------------------------------------------------------------------- /demo/controllers/treeSelectorController.ts: -------------------------------------------------------------------------------- 1 | import * as ng from 'angular'; 2 | import {ComponentDemo} from '../services/availableComponentBuilder'; 3 | 4 | @ComponentDemo({ 5 | name: 'tree-selector', 6 | title: 'TreeSelector', 7 | template: require('./../views/tree-view/tree-selector.html'), 8 | group: 'tree-view', 9 | controller: 'demoTreeSelector as vm' 10 | }) 11 | export default class TreeSelectorController { 12 | public display = false; 13 | public node; 14 | public data = require('../data/tree.json'); 15 | 16 | /*@ngInject*/ 17 | constructor(private $timeout : ng.ITimeoutService) {} 18 | 19 | public toggleTree() { 20 | this.display = !this.display; 21 | }; 22 | 23 | public nodeSelect(node) { 24 | this.node = node; 25 | this.display = false; 26 | } 27 | 28 | public lazyLoad(node) { 29 | let data = require('../data/lazyTree.json'); 30 | // Wait to simulate HTTP delay 31 | return new Promise(resolve => this.$timeout(() => resolve(data), 1500)); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /demo/controllers/treeViewController.ts: -------------------------------------------------------------------------------- 1 | import * as ng from 'angular'; 2 | import {ComponentDemo} from '../services/availableComponentBuilder'; 3 | @ComponentDemo({ 4 | name: 'tree-view', 5 | title: 'TreView', 6 | template: require('./../views/tree-view/basic.html'), 7 | group: 'tree-view', 8 | controller: 'demoTreeView as vm' 9 | }) 10 | export default class TreeViewController { 11 | public node; 12 | public data = require('../data/tree.json'); 13 | public selectNode; 14 | 15 | /*@ngInject*/ 16 | constructor(private $scope : ng.IScope, private $timeout : ng.ITimeoutService, private $window : ng.IWindowService) { 17 | }; 18 | 19 | public resetState() { 20 | sessionStorage.clear(); 21 | this.$window.location.reload(); 22 | } 23 | 24 | public selectRandom() { 25 | let keys = JSON.stringify(this.data) 26 | .match(/\"key\":\"[^\"]+\"/g) 27 | .map((item) => item.replace(/\"key\":\"([^\"]+)\"/, '$1')); 28 | let key = keys[Math.floor(Math.random() * keys.length)]; 29 | 30 | this.selectNode = { key: key }; 31 | } 32 | 33 | public selectLazy() { 34 | this.selectNode = [{key: 'lp-1'}, {key: 'lc-2'}, {key: 'lgc-1'}]; 35 | } 36 | 37 | public lazyLoad(node) { 38 | let data = require('../data/lazyTree.json'); 39 | // Wait to simulate HTTP delay 40 | return new Promise(resolve => this.$timeout(() => resolve(data), 1500)); 41 | } 42 | 43 | public reloadData() { 44 | this.data = require('../data/lazyTree.json'); 45 | } 46 | 47 | public nodeSelect(node) { 48 | // Drop some attributes to keep the output short 49 | delete node.$el; 50 | delete node.nodes; 51 | delete node.searchResult; 52 | 53 | this.node = node; 54 | this.$scope.$apply(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /demo/data/data-table.json: -------------------------------------------------------------------------------- 1 | { 2 | "settings": { 3 | "perpage": 20, 4 | "current": 1, 5 | "items": 6, 6 | "total": 1 7 | }, 8 | "data": { 9 | "head": [ 10 | { 11 | "is_narrow": true 12 | }, 13 | { 14 | "is_narrow": true 15 | }, 16 | { 17 | "header_text": "Messaging Name", 18 | "sort": "str", 19 | "col_idx": 0, 20 | "align": "left" 21 | }, 22 | { 23 | "header_text": "Messaging Type", 24 | "sort": "str", 25 | "col_idx": 1, 26 | "align": "left" 27 | }, 28 | { 29 | "header_text": "Server", 30 | "sort": "str", 31 | "col_idx": 2, 32 | "align": "left" 33 | } 34 | ], 35 | "rows": [ 36 | { 37 | "id": "16", 38 | "cells": [ 39 | { 40 | "is_checkbox": true 41 | }, 42 | { 43 | "title": "View this item", 44 | "image": "assets/100/vendor-hawkular.png", 45 | "icon": "fa fa-exchange" 46 | }, 47 | { 48 | "text": "JMS Queue [DLQ]" 49 | }, 50 | { 51 | "text": "JMS Queue" 52 | }, 53 | { 54 | "text": "server-one" 55 | } 56 | ], 57 | "quad": { 58 | "topLeft": { 59 | "fileicon": "/assets/vendor-centos.svg", 60 | "tooltip": "Hello, I am a very useful tooltip in the first quadrant!" 61 | }, 62 | "topRight": { 63 | "text": "T", 64 | "background": "#336699", 65 | "tooltip": "Hello, I am a very useful tooltip in the second quadrant!" 66 | }, 67 | "bottomLeft": { 68 | "fileicon": "/assets/vendor-vmware.svg", 69 | "tooltip": "Hello, I am a very useful tooltip in the third quadrant!" 70 | }, 71 | "bottomRight": { 72 | "text": "0", 73 | "tooltip": "Hello, I am a very useful tooltip in the fourth quadrant!" 74 | } 75 | } 76 | }, 77 | { 78 | "id": "14", 79 | "cells": [ 80 | { 81 | "is_checkbox": true 82 | }, 83 | { 84 | "title": "View this item", 85 | "image": "assets/100/vendor-hawkular.png" 86 | }, 87 | { 88 | "text": "JMS Queue [DLQ]" 89 | }, 90 | { 91 | "text": "JMS Queue" 92 | }, 93 | { 94 | "text": "Local" 95 | } 96 | ], 97 | "quad": { 98 | "fonticon": "pficon pficon-server", 99 | "color": "#0099cc", 100 | "tooltip": "Hello, I am a very useful tooltip!" 101 | } 102 | }, 103 | { 104 | "id": "10", 105 | "cells": [ 106 | { 107 | "is_checkbox": true 108 | }, 109 | { 110 | "title": "View this item", 111 | "picture": "assets/100/vendor-hawkular.png", 112 | "icon": "fa fa-exchange" 113 | }, 114 | { 115 | "text": "JMS Queue [ExpiryQueue]" 116 | }, 117 | { 118 | "text": "JMS Queue" 119 | }, 120 | { 121 | "text": "Local" 122 | } 123 | ] 124 | }, 125 | { 126 | "id": "17", 127 | "cells": [ 128 | { 129 | "is_checkbox": true 130 | }, 131 | { 132 | "title": "View this item", 133 | "image": "assets/100/vendor-hawkular.png", 134 | "icon": "fa fa-exchange" 135 | }, 136 | { 137 | "text": "JMS Queue [ExpiryQueue]" 138 | }, 139 | { 140 | "text": "JMS Queue" 141 | }, 142 | { 143 | "text": "server-one" 144 | } 145 | ] 146 | }, 147 | { 148 | "id": "12", 149 | "cells": [ 150 | { 151 | "is_checkbox": true 152 | }, 153 | { 154 | "title": "View this item", 155 | "image": "assets/100/vendor-hawkular.png", 156 | "icon": "fa fa-exchange" 157 | }, 158 | { 159 | "text": "JMS Queue [HawkularAlertsActionsResponseQueue]" 160 | }, 161 | { 162 | "text": "JMS Queue" 163 | }, 164 | { 165 | "text": "Local" 166 | } 167 | ] 168 | }, 169 | { 170 | "id": "13", 171 | "cells": [ 172 | { 173 | "is_checkbox": true 174 | }, 175 | { 176 | "title": "View this item", 177 | "image": "assets/100/vendor-hawkular.png", 178 | "icon": "fa fa-exchange" 179 | }, 180 | { 181 | "text": "JMS Queue [HawkularAlertsPluginsQueue]" 182 | }, 183 | { 184 | "text": "JMS Queue" 185 | }, 186 | { 187 | "text": "Local" 188 | } 189 | ] 190 | } 191 | ] 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /demo/data/dialog_editor.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 10000000000001, 3 | "description": "Dialog for testing", 4 | "buttons": "submit,cancel", 5 | "created_at": "2012-06-13T22:17:08Z", 6 | "updated_at": "2012-06-19T19:14:09Z", 7 | "label": "Dialog", 8 | "content": [{ 9 | "description": "Dialog for testing", 10 | "buttons": "submit,cancel", 11 | "label": "Dialog", 12 | "dialog_tabs": [{ 13 | "description": "First testing tab", 14 | "display": "edit", 15 | "label": "Tab 1", 16 | "position": 0, 17 | "dialog_groups": [{ 18 | "description": "First box in first tab", 19 | "display": "edit", 20 | "label": "Tab 1 - Box 1", 21 | "position": 0, 22 | "dialog_fields": [{ 23 | "name": "textbox1", 24 | "type": "DialogFieldTextBox", 25 | "display": "edit", 26 | "display_method_options": {}, 27 | "required": false, 28 | "required_method_options": {}, 29 | "values_method_options": {}, 30 | "options": {}, 31 | "label": "Tab 1 - Box 1 - Element 1", 32 | "position": 0, 33 | "resource_action": { 34 | "resource_type": "DialogField", 35 | "ae_attributes": {} 36 | } 37 | }, { 38 | "name": "textbox2", 39 | "type": "DialogFieldTextBox", 40 | "display": "edit", 41 | "display_method_options": {}, 42 | "required": false, 43 | "required_method_options": {}, 44 | "values_method_options": {}, 45 | "options": {}, 46 | "label": "Tab 1 - Box 1 - Element 2", 47 | "position": 1, 48 | "resource_action": { 49 | "resource_type": "DialogField", 50 | "ae_attributes": {} 51 | } 52 | }, { 53 | "name": "textareabox1", 54 | "type": "DialogFieldTextAreaBox", 55 | "display": "edit", 56 | "display_method_options": {}, 57 | "required": false, 58 | "required_method_options": {}, 59 | "values_method_options": {}, 60 | "options": {}, 61 | "label": "Tab 1 - Box 1 - Element 3", 62 | "position": 2, 63 | "resource_action": { 64 | "resource_type": "DialogField", 65 | "ae_attributes": {} 66 | } 67 | }] 68 | }] 69 | }, { 70 | "description": "Second testing tab", 71 | "display": "edit", 72 | "label": "Tab 2", 73 | "position": 1, 74 | "dialog_groups": [{ 75 | "description": "First box in second tab", 76 | "display": "edit", 77 | "label": "Tab 2 - Box 1", 78 | "position": 0, 79 | "dialog_fields": [{ 80 | "name": "dropdownlist1", 81 | "description": "First drop down list", 82 | "type": "DialogFieldDropDownList", 83 | "data_type": "string", 84 | "display": "edit", 85 | "display_method_options": {}, 86 | "required": true, 87 | "required_method_options": {}, 88 | "values": [["Yes", "yes"], ["No", "no"], ["Maybe", "maybe"]], 89 | "values_method_options": {}, 90 | "options": { 91 | "sort_by": "description", 92 | "sort_order": "descending" 93 | }, 94 | "label": "Tab 2 - Box 1 - Element 1", 95 | "position": 0, 96 | "resource_action": { 97 | "resource_type": "DialogField", 98 | "ae_attributes": {} 99 | } 100 | }] 101 | }] 102 | }] 103 | }] 104 | } 105 | -------------------------------------------------------------------------------- /demo/data/lazyTree.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "key": "lc-1", 4 | "icon": "ff ff-diamond", 5 | "text": "Lazy Child 1" 6 | }, 7 | { 8 | "key": "lc-2", 9 | "icon": "ff ff-diamond", 10 | "text": "Lazy Child 2", 11 | "nodes": [ 12 | { 13 | "key": "lgc-1", 14 | "icon": "ff ff-diamond", 15 | "text": "Lazy grandchild 1" 16 | } 17 | ] 18 | } 19 | ] 20 | -------------------------------------------------------------------------------- /demo/data/tree.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "key": "p-1", 4 | "icon": "ff ff-diamond", 5 | "text": "Parent", 6 | "nodes": [ 7 | { 8 | "key": "c-1", 9 | "icon": "ff ff-diamond", 10 | "text": "Child 1", 11 | "nodes": [ 12 | { 13 | "key": "gc-1", 14 | "icon": "ff ff-diamond", 15 | "text": "Grandchild 1" 16 | }, 17 | { 18 | "key": "gc-2", 19 | "icon": "ff ff-diamond", 20 | "text": "Grandchild 2" 21 | } 22 | ] 23 | }, 24 | { 25 | "key": "c-2", 26 | "icon": "ff ff-diamond", 27 | "text": "Child 2" 28 | } 29 | ] 30 | }, 31 | { 32 | "key": "lp-1", 33 | "icon": "ff ff-diamond", 34 | "text": "Lazy Parent", 35 | "lazyLoad": true 36 | } 37 | ] 38 | -------------------------------------------------------------------------------- /demo/index.ts: -------------------------------------------------------------------------------- 1 | import views from './views'; 2 | import controllers from './controllers'; 3 | import * as angular from 'angular'; 4 | import TranslateFilter from './services/translateFilter'; 5 | import DialogEditorHttpService from './services/DialogEditorHttpService'; 6 | 7 | export const app = angular.module('demoApp', [ 8 | 'ui.sortable', 'ngDragDrop', 'frapontillo.bootstrap-switch', 'miqStaticAssets', 'ui.bootstrap', 'ui.router', 9 | 'patternfly.form', 'patternfly.select', 'ui.bootstrap.tabs', 'patternfly.views', 'ngAnimate' 10 | ]); 11 | controllers(app); 12 | views(app); 13 | app.filter('translate', TranslateFilter.filter); 14 | app.service('DialogEditorHttp', DialogEditorHttpService); 15 | -------------------------------------------------------------------------------- /demo/services/DialogEditorHttpService.ts: -------------------------------------------------------------------------------- 1 | export default class DialogEditorHttpService { 2 | public loadCategories(): Promise { 3 | return new Promise(resolve => resolve([])); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /demo/services/availableComponentBuilder.ts: -------------------------------------------------------------------------------- 1 | import {AvailableComponent, default as AvailableComponentsService} from './availableComponentsService'; 2 | 3 | function checkProperties(settings, fields, target?) { 4 | for (let field of fields) { 5 | if (!settings.hasOwnProperty(field)) { 6 | let suffix = target ? `Called on class ${target['name']}.` : ''; 7 | throw new Error(`Available component has to have ${field} set up! ${suffix}`); 8 | } 9 | } 10 | } 11 | 12 | export function ComponentDemo(settings: any) { 13 | function componentFactory() { 14 | return new AvailableComponent( 15 | settings.name, 16 | settings.title || settings.name, 17 | settings.location || `/${settings.name}`, 18 | settings.template, 19 | settings.controller 20 | ); 21 | } 22 | 23 | return function(target: Function) { 24 | checkProperties(settings, ['controller', 'name', 'template', 'group'], target); 25 | let availCmps = new AvailableComponentsService(); 26 | const currentGroup = availCmps.availableComponents.filter(group => group.name === settings.group); 27 | if (!currentGroup) { 28 | throw new Error(`Selected group '${settings.group}' does not exists!`); 29 | } 30 | currentGroup.forEach(oneGroup => oneGroup.components.push(componentFactory())); 31 | }; 32 | } 33 | -------------------------------------------------------------------------------- /demo/services/availableComponentsService.ts: -------------------------------------------------------------------------------- 1 | export interface IAvailComponent { 2 | name: string; 3 | title: string; 4 | location: string; 5 | template: string; 6 | controller: string; 7 | } 8 | 9 | export interface IAvailableGroup { 10 | name: string; 11 | title: string; 12 | location: string; 13 | components: IAvailComponent[]; 14 | } 15 | 16 | export class AvailableGroup implements IAvailableGroup { 17 | public constructor(public name: string, 18 | public title: string, 19 | public location: string, 20 | public components: IAvailComponent[]) { } 21 | } 22 | 23 | export class AvailableComponent implements IAvailComponent { 24 | public constructor(public name: string, 25 | public title: string, 26 | public location: string, 27 | public template: string, 28 | public controller: string) { } 29 | } 30 | 31 | export default class AvailableComponentsService { 32 | public static instance: AvailableComponentsService; 33 | public availableComponents: IAvailableGroup[]; 34 | 35 | public constructor() { 36 | if (AvailableComponentsService.instance) { 37 | return AvailableComponentsService.instance; 38 | } 39 | this.initComponents(); 40 | AvailableComponentsService.instance = this; 41 | } 42 | 43 | public initComponents() { 44 | this.availableComponents = [ 45 | new AvailableGroup('fonticon-picker', 'Fonticon Picker Components', '/fonticon-picker', []), 46 | new AvailableGroup('dialog', 'Dialog Components', '/dialog', []), 47 | new AvailableGroup('tree-view', 'Tree Components', '/tree', []) 48 | ]; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /demo/services/translateFilter.ts: -------------------------------------------------------------------------------- 1 | export default class TranslateFilter { 2 | public static filter() { 3 | return (value) => { 4 | return value; 5 | }; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /demo/styles/demo-app.scss: -------------------------------------------------------------------------------- 1 | ul.nav.nav-list.well { 2 | padding: 0; 3 | 4 | li.nav-header { 5 | background: #d3d3d3; 6 | 7 | a { 8 | color: black; 9 | font-size: larger; 10 | margin: 0; 11 | } 12 | } 13 | } 14 | 15 | table.table tbody td img { 16 | height: 20px; 17 | width: 20px; 18 | } 19 | 20 | table .narrow { 21 | width: 1%; 22 | white-space: nowrap; 23 | } 24 | 25 | .clear-navbar { 26 | margin-top: 60px; 27 | } 28 | 29 | .dl-horizontal.tile dt { 30 | float: left; 31 | width: 100px; 32 | clear: left; 33 | text-align: right; 34 | overflow: hidden; 35 | text-overflow: ellipsis; 36 | white-space: nowrap; 37 | } 38 | 39 | .demo-dialog-container { 40 | height: 550px; 41 | } 42 | .demo-dialog { 43 | display:block; 44 | width: 80%; 45 | padding-bottom: 90px; 46 | } 47 | .demo-dialog-results { 48 | padding-top: 10px; 49 | } 50 | .demo-dialog-submit-button { 51 | margin-left: 147px; 52 | margin-top: 25px; 53 | } 54 | 55 | .piechart { 56 | $chart-empty: yellowgreen !default; 57 | $chart-fill: blue !default; 58 | 59 | border-radius: 50%; 60 | background: $chart-empty; 61 | background-image: linear-gradient(to right, transparent 50%, $chart-fill 0); 62 | 63 | &::before { 64 | content: ''; 65 | display: block; 66 | margin-left: 50%; 67 | height: 100%; 68 | border-radius: 0 100% 100% 0 / 50%; 69 | background-color: inherit; 70 | transform-origin: left; 71 | } 72 | 73 | @for $i from 0 through 10 { 74 | &.fill-#{$i}::before { 75 | transform: rotate(#{$i / 20}turn); 76 | } 77 | } 78 | 79 | @for $i from 11 through 20 { 80 | &.fill-#{$i}::before { 81 | background: $chart-fill; 82 | transform: rotate(#{($i - 10) / 20}turn); 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /demo/template-index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | <%= htmlWebpackPlugin.options.title %> 15 | 16 | 17 |
18 |
27 |
28 |
29 |
30 |
31 | 39 |
40 |
41 |
42 |
43 |
44 |
45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /demo/views/dialog/editor.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

General

4 |
5 |
6 |
7 |
8 |
9 |
10 | 11 |
12 |
13 | 16 |
17 |
18 |
19 |
20 | 21 | 22 |
23 |
{{ vm.DialogEditor.data.content[0] | json }}
24 |
25 | -------------------------------------------------------------------------------- /demo/views/dialog/user.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 |
6 |
7 |
8 | 9 |
10 |
{{vm.dialogDataResults}}
11 |
12 |
13 |
14 |
15 | -------------------------------------------------------------------------------- /demo/views/fonticon-picker/basic.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /demo/views/index.ts: -------------------------------------------------------------------------------- 1 | import AvailableComponentsService from './../services/availableComponentsService'; 2 | import * as angular from 'angular'; 3 | 4 | export default (module: angular.IModule) => { 5 | /* @ngInject */ 6 | module.config(($stateProvider: any, 7 | $urlRouterProvider: any) => { 8 | const allComponents = new AvailableComponentsService(); 9 | angular.forEach(allComponents.availableComponents, (oneGroup) => { 10 | angular.forEach(oneGroup.components, (oneComp) => { 11 | $stateProvider.state(oneGroup.name + oneComp.name, { 12 | url: oneGroup.location + oneComp.location, 13 | template: oneComp.template, 14 | controller: oneComp.controller 15 | }); 16 | }); 17 | }); 18 | $urlRouterProvider.otherwise('/'); 19 | 20 | $stateProvider.state('main', { 21 | url: '/', 22 | template: require('./main.html') 23 | }); 24 | }); 25 | }; 26 | -------------------------------------------------------------------------------- /demo/views/main.html: -------------------------------------------------------------------------------- 1 |
2 |

3 | Welcome to ManageIQ Common UI Components. This is a collection of Angular components for cross-provider use in the 4 | ManageIQ UI (some of the components are even data-driven by the Rails tables). 5 |

6 |
7 | -------------------------------------------------------------------------------- /demo/views/tree-view/basic.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

Note: programmatically selecting a node is not firing the onSelect event on purpose.

6 | 7 | 15 | 16 |
17 |

Selected node:

18 |
{{ vm.node | json }}
19 |
20 | -------------------------------------------------------------------------------- /demo/views/tree-view/tree-selector.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 | 6 | 7 |
8 |
9 |
10 | 17 | 18 |
19 | -------------------------------------------------------------------------------- /jsdoc-conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "source": { 3 | "include" : ["dist/js/ui-components.js"] 4 | }, 5 | "opts": { 6 | "template": "node_modules/angular-jsdoc/angular-template", 7 | "destination": "dist/docs", 8 | "readme": "README.md", 9 | "recourse": true 10 | }, 11 | "plugins": [ 12 | "node_modules/jsdoc/plugins/markdown", 13 | "node_modules/angular-jsdoc/common/plugins/ngdoc" 14 | ], 15 | "templates": { 16 | "cleverLinks": true, 17 | "monospaceLinks": true 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | 2 | const webpackConfig = require('./webpack.config'); 3 | const fileGlob = 'src/**/*.spec.[jt]s'; 4 | const vendor = 'dist/js/vendor.js'; 5 | const applicationFile = 'dist/js/ui-components.js'; 6 | const jsonGlob = {pattern: 'src/**/*.json', watched: true, served: true, included: false}; 7 | module.exports = function(config) { 8 | config.set({ 9 | 10 | // base path that will be used to resolve all patterns (eg. files, exclude) 11 | basePath: '', 12 | 13 | // frameworks to use 14 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 15 | frameworks: ['jasmine'], 16 | 17 | // list of files / patterns to load in the browser 18 | files: [vendor, 'node_modules/angular-mocks/angular-mocks.js', applicationFile, fileGlob, jsonGlob], 19 | 20 | // list of files to exclude 21 | exclude: [ 22 | ], 23 | client: { 24 | captureConsole: true 25 | }, 26 | 27 | // test results reporter to use 28 | // possible values: 'dots', 'progress' 29 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 30 | reporters: ['progress', 'coverage'], 31 | 32 | // web server port 33 | port: 9876, 34 | 35 | preprocessors: { 36 | [fileGlob]: ['webpack'], 37 | [jsonGlob]: ['webpack'], 38 | [applicationFile]: ['webpack', 'coverage'] 39 | }, 40 | webpack: webpackConfig({test: true}), 41 | webpackMiddleware: {noInfo: true}, 42 | 43 | // enable / disable colors in the output (reporters addnd logs) 44 | colors: true, 45 | 46 | // level of logging 47 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 48 | logLevel: config.LOG_WARN, 49 | 50 | // enable / disable watching file and executing tests whenever any file changes 51 | autoWatch: false, 52 | 53 | // start these browsers 54 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 55 | browsers: ['jsdom'], 56 | 57 | // Continuous Integration mode 58 | // if true, Karma captures browsers, runs the tests and exits 59 | singleRun: true, 60 | coverageReporter : { 61 | type : 'lcov', 62 | dir : 'coverage/', 63 | subdir: '.' 64 | }, 65 | 66 | mime: { 67 | 'text/x-typescript': ['ts','tsx'] 68 | }, 69 | // Concurrency level 70 | // how many browser should be started simultaneous 71 | concurrency: Infinity 72 | }) 73 | }; 74 | -------------------------------------------------------------------------------- /locale/zanata.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | https://translate.zanata.org/ 4 | manageiq-ui-angular-components 5 | master 6 | gettext 7 | 8 | 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@manageiq/ui-components", 3 | "version": "1.6.0", 4 | "description": "UI components for ManageIQ project", 5 | "main": "dist/js/ui-components.js", 6 | "scripts": { 7 | "start": "yarn run install-vendor && webpack --watch", 8 | "test": "yarn run install-vendor && karma start", 9 | "prepublish": "webpack || true", 10 | "build": "yarn run install-vendor && webpack -p", 11 | "build-dev": "yarn run install-vendor && webpack", 12 | "gettext:extract": "yarn run build && angular-gettext-cli --files './+(src|dist)/**/+(*.html|ui-components.js)' --dest './locale/ui-components.pot' --marker-names '__,N_' && yarn run gettext:validate", 13 | "gettext:validate": "node scripts/validate-gettext-catalog.js", 14 | "install-vendor": "webpack --config webpack.vendor.config.js", 15 | "build-docs": "jsdoc -c jsdoc-conf.json" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "https://github.com/ManageIQ/ui-components.git" 20 | }, 21 | "authors": [], 22 | "license": "Apache-2.0", 23 | "engines": { 24 | "node": ">= 14.0.0", 25 | "npm": ">= 6.0.0" 26 | }, 27 | "devDependencies": { 28 | "@manageiq/font-fabulous": "^1.0.0", 29 | "@types/angular-mocks": "^1.5.7", 30 | "@types/angular-ui-router": "^1.1.35", 31 | "@types/jasmine": "^2.5.38", 32 | "@types/node": "13.13.4", 33 | "@types/rx": "^2.5.34", 34 | "angular": "~1.5.9", 35 | "angular-animate": "~1.5.9", 36 | "angular-bootstrap": "^0.12.2", 37 | "angular-gettext-cli": "^1.2.0", 38 | "angular-jsdoc": "~1.5.0", 39 | "angular-mocks": "~1.5.9", 40 | "angular-patternfly": "^3.15.0", 41 | "angular-sanitize": "~1.5.9", 42 | "awesome-typescript-loader": "~4.0.1", 43 | "babel-eslint": "^7.2.3", 44 | "bootstrap-combobox": "^1.0.2", 45 | "bootstrap-datepicker": "^1.6.4", 46 | "bootstrap-select": "~1.12.1", 47 | "bootstrap-switch": "^3.3.4", 48 | "browser-sync": "^2.18.2", 49 | "browser-sync-spa": "^1.0.3", 50 | "browser-sync-webpack-plugin": "^1.1.3", 51 | "c3": "^0.4.11", 52 | "copy-webpack-plugin": "4.0.1", 53 | "coveralls": "^2.11.15", 54 | "css-loader": "^0.26.0", 55 | "d3": "^4.4.1", 56 | "eonasdan-bootstrap-datetimepicker": "^4.17.43", 57 | "eslint": "~3.9.1", 58 | "expose-loader": "^0.7.1", 59 | "extract-text-webpack-plugin": "^2.0.0-beta.5", 60 | "file-loader": "^0.9.0", 61 | "google-code-prettify": "^1.0.5", 62 | "html-webpack-plugin": "^2.24.1", 63 | "jasmine": "2.5.2", 64 | "jquery-match-height": "^0.7.0", 65 | "jquery-ui-bundle": "^1.12.1", 66 | "jsdoc": "^3.4.3", 67 | "jsdom": "~13.2.0", 68 | "json-loader": "^0.5.4", 69 | "karma": "1.3.0", 70 | "karma-coverage": "^1.1.1", 71 | "karma-jasmine": "1.0.2", 72 | "karma-jsdom-launcher": "~7.0.0", 73 | "karma-webpack": "1.8.0", 74 | "lodash": "~4.17.20", 75 | "ng-annotate-webpack-plugin": "0.1.3", 76 | "node-sass": "^4.12.0", 77 | "numeral": "^2.0.6", 78 | "patternfly": "^3.15.0", 79 | "patternfly-bootstrap-treeview": "^2.1.5", 80 | "raw-loader": "0.5.1", 81 | "sass": "^0.5.0", 82 | "sass-loader": "^4.0.2", 83 | "style-loader": "^0.13.1", 84 | "tslint": "4.0.2", 85 | "tslint-loader": "3.2.0", 86 | "typescript": "~2.8.3", 87 | "ui-router": "^1.0.0-alpha0", 88 | "url-loader": "^0.5.7", 89 | "webpack": "3.11.0" 90 | }, 91 | "dependencies": { 92 | "angular-bootstrap-switch": "^0.5.1", 93 | "angular-dragdrop": "^1.0.13", 94 | "angular-ui-sortable": "^0.16.1", 95 | "es6-shim": "^0.35.3", 96 | "es7-shim": "^6.0.0", 97 | "sprintf-js": "^1.1.1" 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /pkg/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ManageIQ/ui-components/297eaf92aa8e19056ace8e704a7477073f4f1d4e/pkg/.gitkeep -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "inheritConfig": true, 4 | "inheritConfigRepoName": "manageiq/renovate-config" 5 | } 6 | -------------------------------------------------------------------------------- /scripts/.coverage.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | cd "`dirname "$0"`" 3 | cat ../coverage/lcov.info | ../node_modules/coveralls/bin/coveralls.js 4 | -------------------------------------------------------------------------------- /scripts/validate-gettext-catalog.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | const potFile = path.join(__dirname, '../locale/ui-components.pot') 4 | const contents = fs.readFileSync(potFile, 'utf8') 5 | const re = /<(.|\n)*>/gim // checks for html 6 | const matches = contents.match(re) 7 | 8 | if (matches != null) { 9 | console.log('Errors exist in language file') 10 | console.log(matches) 11 | process.exit(1) 12 | } else { 13 | process.exit(0) 14 | } 15 | -------------------------------------------------------------------------------- /src/common/components/index.ts: -------------------------------------------------------------------------------- 1 | import SortItems from './sortItemsComponent'; 2 | import miqPfSort from './miqPfSort'; 3 | 4 | export default (module: ng.IModule) => { 5 | module.component('miqSortItems', new SortItems); 6 | module.directive('miqPfSort', miqPfSort); 7 | }; 8 | -------------------------------------------------------------------------------- /src/common/components/miqPfSort.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 7 | 14 |
15 | 18 |
19 | -------------------------------------------------------------------------------- /src/common/components/miqPfSort.js: -------------------------------------------------------------------------------- 1 | export default function miqPfSort() { 2 | 'use strict'; 3 | return { 4 | restrict: 'A', 5 | scope: { 6 | config: '=' 7 | }, 8 | template: require('./miqPfSort.html'), 9 | controller: ['$scope', function ($scope) { 10 | 11 | $scope.setupConfig = function () { 12 | var updated = false; 13 | 14 | if ($scope.config.fields === undefined) { 15 | $scope.config.fields = []; 16 | } 17 | 18 | if ($scope.config.fields.length > 0) { 19 | if ($scope.config.currentField === undefined) { 20 | $scope.config.currentField = $scope.config.fields[0]; 21 | updated = true; 22 | } 23 | if ($scope.config.isAscending === undefined) { 24 | $scope.config.isAscending = true; 25 | updated = true; 26 | } 27 | } 28 | 29 | if (updated === true && $scope.config.onSortChange) { 30 | $scope.config.onSortChange($scope.config.currentField, $scope.config.isAscending); 31 | } 32 | }; 33 | 34 | $scope.selectField = function (field) { 35 | $scope.config.currentField = field; 36 | 37 | if ($scope.config.onSortChange) { 38 | $scope.config.onSortChange($scope.config.currentField, $scope.config.isAscending); 39 | } 40 | }; 41 | 42 | $scope.changeDirection = function () { 43 | $scope.config.isAscending = !$scope.config.isAscending; 44 | 45 | if ($scope.config.onSortChange) { 46 | $scope.config.onSortChange($scope.config.currentField, $scope.config.isAscending); 47 | } 48 | }; 49 | 50 | $scope.getSortIconClass = function () { 51 | var iconClass; 52 | 53 | if ($scope.config.isAscending) { 54 | iconClass = 'fa fa-sort-amount-asc'; 55 | } else { 56 | iconClass = 'fa fa-sort-amount-desc'; 57 | } 58 | 59 | return iconClass; 60 | }; 61 | 62 | $scope.setupConfig(); 63 | }], 64 | 65 | link: function (scope, element, attrs) { 66 | scope.$watch('config', function () { 67 | scope.setupConfig(); 68 | }, true); 69 | } 70 | }; 71 | } 72 | -------------------------------------------------------------------------------- /src/common/components/sortItemsComponent.spec.ts: -------------------------------------------------------------------------------- 1 | import SortItems from './sortItemsComponent'; 2 | import * as angular from 'angular'; 3 | 4 | describe('Sort items test', () => { 5 | let headers = [ 6 | {is_narrow: true}, 7 | {col_id: 2, text: 'something', header_text: 'something'}, 8 | {col_id: 3, text: 'something2', header_text: 'something2'} 9 | ]; 10 | let sortObject = {sortObject: {col_id: 3, text: 'something2'}, isAscending: true}; 11 | 12 | it('should create component', () => { 13 | let sortItems = new SortItems; 14 | expect(sortItems).toBeDefined(); 15 | }); 16 | 17 | describe('controller', () => { 18 | let sortItemsCtrl; 19 | const onSort = jasmine.createSpy('onSort'); 20 | 21 | beforeEach(() => { 22 | let bindings = { 23 | onSort: onSort, 24 | headers: headers, 25 | sortObject: sortObject, 26 | dropdownClass: ['someClass'] 27 | }; 28 | angular.mock.module('miqStaticAssets.common'); 29 | angular.mock.inject(($componentController) => { 30 | sortItemsCtrl = $componentController('miqSortItems', {$element: angular.element('')}, bindings); 31 | }); 32 | }); 33 | 34 | it('should init options', () => { 35 | expect(sortItemsCtrl.options.fields.length).toBe(0); 36 | expect(sortItemsCtrl.options.currentField).toBeDefined(); 37 | expect(sortItemsCtrl.options.hasOwnProperty('onSortChange')).toBeTruthy(); 38 | }); 39 | 40 | it('should set sort item', () => { 41 | sortItemsCtrl.headers = headers; 42 | sortItemsCtrl.sortObject = { 43 | sortObject: { 44 | col_id: 3, 45 | text: 'something2', 46 | header_text: 'something2' 47 | }, 48 | isAscending: true 49 | }; 50 | sortItemsCtrl.setSortItem(); 51 | expect(sortItemsCtrl.options.currentField.colId).toBe(2); 52 | expect(sortItemsCtrl.options.currentField.id).toBe(sortItemsCtrl.sortObject.sortObject.text); 53 | expect(sortItemsCtrl.options.currentField.title).toBe(sortItemsCtrl.sortObject.sortObject.text); 54 | }); 55 | }); 56 | 57 | describe('component', () => { 58 | let scope, compile, compiledElement; 59 | 60 | beforeEach(() => { 61 | angular.mock.module('miqStaticAssets.common'); 62 | angular.mock.inject(($rootScope, $compile: ng.ICompileService) => { 63 | scope = $rootScope.$new(); 64 | compile = $compile; 65 | }); 66 | 67 | scope.headers = headers; 68 | compiledElement = compile( 69 | angular.element( 70 | `` 73 | ))(scope); 74 | scope.$digest(); 75 | }); 76 | 77 | it('should compile component', () => { 78 | expect(compiledElement.html()).toContain('miq-pf-sort'); 79 | expect(compiledElement.html()).toContain('config="vm.options"'); 80 | }); 81 | }); 82 | }); 83 | -------------------------------------------------------------------------------- /src/common/components/sortItemsComponent.ts: -------------------------------------------------------------------------------- 1 | import * as _ from 'lodash'; 2 | import * as ng from 'angular'; 3 | /** 4 | * Controller for sort items component, it filters headers to fit config object of `pf-sort`. 5 | * @memberof miqStaticAssets.common 6 | * @ngdoc controller 7 | * @name SortItemsController 8 | */ 9 | export class SortItemsController { 10 | public headers: any; 11 | public options: any; 12 | public sortObject: any; 13 | public dropdownClass: any[]; 14 | public onSort: (args: {sortObject: any, isAscending: boolean}) => void; 15 | 16 | /* @ngInject */ 17 | constructor(private $element: any, private $timeout: any) { 18 | this.initOptions(); 19 | } 20 | 21 | /** 22 | * Angular's method for checking one way data bounded properties changes. 23 | * @memberof SortItemsController 24 | * @function $onChanges 25 | * @param changesObj {Object} angular changes object. 26 | */ 27 | public $onChanges(changesObj: any) { 28 | if (changesObj.headers) { 29 | this.options.fields = []; 30 | this.fillFields(); 31 | if (this.sortObject) { 32 | this.setSortItem(); 33 | } 34 | } 35 | if (changesObj.dropdownClass) { 36 | this.applyClass(); 37 | } 38 | } 39 | 40 | public $postLink() { 41 | //we have to wait for rendering of components, hence $timeout 42 | this.$timeout(() => this.applyClass()); 43 | } 44 | 45 | /** 46 | * Public method for setting item which is currently sorted by. It will take id of object in `headers` as `colId`, 47 | * it's text as actual Id and same applies to `title`. 48 | * @memberof SortItemsController 49 | * @function setSortItem 50 | */ 51 | public setSortItem() { 52 | if (this.sortObject && this.sortObject.sortObject && this.sortObject && this.sortObject.sortObject.text) { 53 | this.options.currentField = { 54 | colId: _.findIndex(this.headers, this.sortObject.sortObject), 55 | id: this.sortObject.sortObject.text.toLowerCase(), 56 | title: this.sortObject.sortObject.header_text 57 | }; 58 | this.options.isAscending = this.sortObject.isAscending; 59 | } 60 | } 61 | 62 | /** 63 | * Public method which is called after constructing this controller. It will set default values for config object, 64 | * along side with sort method. 65 | * @memberof SortItemsController 66 | * @function initOptions 67 | */ 68 | public initOptions() { 69 | this.options = { 70 | fields: [], 71 | onSortChange: (item: any, isAscending: boolean) => this.onSort({sortObject: item, isAscending: isAscending}), 72 | currentField: {} 73 | }; 74 | } 75 | 76 | /** 77 | * Private method which will filter out and transform headers to config object. This function will filter out all 78 | * columns which has `is_narrow` and no `text` is set fot them. Also it will use each header key as `colId`, 79 | * text as `id` and again text as `title`. 80 | * @memberof SortItemsController 81 | * @function fillFields 82 | */ 83 | private fillFields() { 84 | _.each(this.headers, (oneCol: any, key) => { 85 | if (!oneCol.hasOwnProperty('is_narrow') && oneCol.hasOwnProperty('text')) { 86 | this.options.fields.push({ 87 | colId: key, 88 | id: oneCol.text.toLowerCase(), 89 | title: oneCol.header_text 90 | }); 91 | } 92 | }); 93 | } 94 | 95 | /** 96 | * Method for applying additional class for dropdown. 97 | * dropdownClass can be either string of classes, or array. 98 | */ 99 | private applyClass() { 100 | if (this.dropdownClass) { 101 | Array.isArray(this.dropdownClass) ? 102 | this.$element.find('.uib-dropdown').addClass(...this.dropdownClass) : 103 | this.$element.find('.uib-dropdown').addClass(this.dropdownClass); 104 | } 105 | } 106 | } 107 | /** 108 | * @description 109 | * Component for showing sort component. See {@link miqStaticAssets.common.SortItemsController} on how functions 110 | * and properties are handled, This component requires `pf-sort` (see 111 | * patternfly's 112 | * implemetnation) component to be part of application scope. 113 | * If you do not provide such component no sort will be show. `pf-sort` requires `config` property which consists of: 114 | * ```javascript 115 | * config = { 116 | * fields: [], 117 | * onSortChange: (item: any, isAscending: boolean) => void, 118 | * currentField: {} 119 | * } 120 | * ``` 121 | * @memberof miqStaticAssets.common 122 | * @ngdoc component 123 | * @name miqSortItems 124 | * @attr {Expression} onSort function which is called after sorting has changed. 125 | * @attr {Object} headers items which will be present in sort chooser. 126 | * @attr {Object} sortObject object which is currently sorted by. 127 | * @example 128 | * 131 | * 132 | */ 133 | export default class SortItems implements ng.IComponentOptions { 134 | public replace: boolean = true; 135 | public template = `
`; 136 | public controller = SortItemsController; 137 | public controllerAs = 'vm'; 138 | public bindings: any = { 139 | onSort: '&', 140 | headers: '<', 141 | sortObject: '<', 142 | dropdownClass: '<' 143 | }; 144 | } 145 | -------------------------------------------------------------------------------- /src/common/filters/abbrNumberFilter.spec.ts: -------------------------------------------------------------------------------- 1 | import AbbrNumber from './abbrNumberFilter'; 2 | 3 | describe('Filter: abbrNumber', function() { 4 | let filter = AbbrNumber.filter(); 5 | 6 | it('abbreviates numbers', function() { 7 | expect(filter('4')).toBe('4'); 8 | expect(filter('42')).toBe('42'); 9 | expect(filter('420')).toBe('420'); 10 | expect(filter('4200')).toBe('4.2K'); 11 | }); 12 | 13 | it('returns with the same text', function() { 14 | expect(filter('text')).toBe('text'); 15 | expect(filter('123text')).toBe('123text'); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /src/common/filters/abbrNumberFilter.ts: -------------------------------------------------------------------------------- 1 | let numeral = require('numeral'); 2 | 3 | export default class AbbrNumber { 4 | public static filter() { 5 | return (value) => { 6 | let num = numeral(value); 7 | // Return with the input if it is not a number 8 | if (!num.value() || num.value().toString() !== value.toString()) { 9 | return value; 10 | } 11 | 12 | let abbr = num.format('0.0a'); 13 | 14 | if (abbr.match(/\d\.0[a-z]?$/) || abbr.length > 5) { 15 | // Drop the .0 as we want to save the space 16 | abbr = num.format('0a'); 17 | } 18 | 19 | return abbr.toUpperCase(); 20 | }; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/common/filters/adjustColorFilter.spec.ts: -------------------------------------------------------------------------------- 1 | import AdjustColor from './adjustColorFilter'; 2 | 3 | describe('Filter: adjustColor', function() { 4 | let filter = AdjustColor.filter(); 5 | 6 | it('does not alter if enabled or undefined', function() { 7 | expect(filter(undefined, true)).toBe(undefined); 8 | expect(filter(undefined, false)).toBe(undefined); 9 | expect(filter('#123456', true)).toBe('#123456'); 10 | }); 11 | 12 | it('returns with an rgba call', function() { 13 | expect(filter('#123456', false)).toBe('rgba(18, 52, 86, 0.5)'); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /src/common/filters/adjustColorFilter.ts: -------------------------------------------------------------------------------- 1 | export default class AdjustColor { 2 | public static filter() { 3 | return (value, enabled) => { 4 | // Don't touch the color if it's enabled or unset 5 | if (enabled || !value) { 6 | return value; 7 | } else { 8 | let r = parseInt(value.substring(1,3), 16); 9 | let g = parseInt(value.substring(3,5), 16); 10 | let b = parseInt(value.substring(5,7), 16); 11 | 12 | return `rgba(${r}, ${g}, ${b}, 0.5)`; 13 | } 14 | }; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/common/filters/index.ts: -------------------------------------------------------------------------------- 1 | import AbbrNumber from './abbrNumberFilter'; 2 | import AdjustColor from './adjustColorFilter'; 3 | 4 | export default (module: ng.IModule) => { 5 | module.filter('abbrNumber', AbbrNumber.filter); 6 | module.filter('adjustColor', AdjustColor.filter); 7 | }; 8 | -------------------------------------------------------------------------------- /src/common/index.ts: -------------------------------------------------------------------------------- 1 | import services from './services'; 2 | import filters from './filters'; 3 | import components from './components'; 4 | import * as angular from 'angular'; 5 | 6 | module common { 7 | export const app: ng.IModule = angular.module('miqStaticAssets.common', []); 8 | /*@ngInject*/ 9 | app.config(($windowProvider: any) => { 10 | let windowService = $windowProvider.$get(); 11 | if (!windowService.hasOwnProperty('__')) { 12 | windowService.__ = translateString => translateString; 13 | } 14 | }); 15 | services(app); 16 | filters(app); 17 | components(app); 18 | } 19 | -------------------------------------------------------------------------------- /src/common/interfaces/endpoints.ts: -------------------------------------------------------------------------------- 1 | export interface IEndpoints { 2 | listDataTable: string; 3 | deleteItemDataTable: string; 4 | validateItem: string; 5 | createItem: string; 6 | providerSettings: string; 7 | toolbarSettings: string; 8 | } 9 | -------------------------------------------------------------------------------- /src/common/services/endpointsService.ts: -------------------------------------------------------------------------------- 1 | import {IEndpoints} from '../interfaces/endpoints'; 2 | export class DefaultEndpoints implements IEndpoints { 3 | public listDataTable: string; 4 | public deleteItemDataTable: string; 5 | public validateItem: string; 6 | public createItem: string; 7 | public providerSettings: string; 8 | public toolbarSettings: string; 9 | constructor() { 10 | this.listDataTable = '/list'; 11 | this.deleteItemDataTable = '/delete'; 12 | this.validateItem = '/validate'; 13 | this.createItem = '/create'; 14 | this.providerSettings = '/list_providers_settings'; 15 | this.toolbarSettings = '/toolbar'; 16 | } 17 | } 18 | 19 | export default class EndpointsService { 20 | public endpoints: IEndpoints; 21 | public rootPoint: string = ''; 22 | 23 | constructor() { 24 | this.endpoints = new DefaultEndpoints; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/common/services/enpointsService.spec.ts: -------------------------------------------------------------------------------- 1 | import EndpointsService from './endpointsService'; 2 | 3 | describe('EndpointsService test', () => { 4 | it('should create service', () => { 5 | let endpointsService = new EndpointsService; 6 | expect(endpointsService).toBeDefined(); 7 | }); 8 | 9 | it('should have default values', () => { 10 | let endpointsService = new EndpointsService; 11 | expect(endpointsService.rootPoint).toBe(''); 12 | expect(endpointsService.endpoints.listDataTable).toBe('/list'); 13 | expect(endpointsService.endpoints.deleteItemDataTable).toBe('/delete'); 14 | expect(endpointsService.endpoints.validateItem).toBe('/validate'); 15 | expect(endpointsService.endpoints.createItem).toBe('/create'); 16 | expect(endpointsService.endpoints.providerSettings).toBe('/list_providers_settings'); 17 | expect(endpointsService.endpoints.toolbarSettings).toBe('/toolbar'); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /src/common/services/index.ts: -------------------------------------------------------------------------------- 1 | import EndpointsService from './endpointsService'; 2 | import TranslateService from './translateService'; 3 | 4 | export default (module: ng.IModule) => { 5 | module.service('MiQEndpointsService', EndpointsService); 6 | module.service('MiQTranslateService', TranslateService); 7 | }; 8 | -------------------------------------------------------------------------------- /src/common/services/translateService.ts: -------------------------------------------------------------------------------- 1 | export default class TranslateService { 2 | /*@ngInject*/ 3 | constructor(private $window: any) {} 4 | 5 | public translateString(stringToTranslate) { 6 | return this.$window.__(stringToTranslate); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/common/translateFunction.ts: -------------------------------------------------------------------------------- 1 | export function __(translateString) { 2 | if (window.hasOwnProperty('__')) { 3 | return window['__'](translateString); 4 | } else { 5 | return translateString; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/dialog-editor/README.md: -------------------------------------------------------------------------------- 1 | The dialog editor hierarchy: 2 | 3 | %dialog-editor 4 | %dialog-editor-tabs 5 | %dialog-editor-boxes (in current tab) 6 | %dialog-editor-modal - modal for editing properties of field/tab/box 7 | | %dialog-editor-modal-field 8 | %dialog-editor-modal-field-template - per field type 9 | | %dialog-editor-modal-tab 10 | | %dialog-editor-modal-box 11 | 12 | -------------------------------------------------------------------------------- /src/dialog-editor/components/abstractModal.ts: -------------------------------------------------------------------------------- 1 | import * as _ from 'lodash'; 2 | 3 | export class ModalController { 4 | private uibModalInstance: any; 5 | private saveModal: any; 6 | 7 | /*@ngInject*/ 8 | constructor(private DialogEditor: any) { 9 | } 10 | 11 | public closeModal(save: boolean) { 12 | if (save) { 13 | this.saveModal(); 14 | } 15 | this.uibModalInstance.close(); 16 | } 17 | } 18 | 19 | export class AbstractModal { 20 | public controller = ModalController; 21 | public controllerAs: string = 'vm'; 22 | public bindings: any = { 23 | modalData: '=', 24 | elementInfo: '<', 25 | categories: '=?', 26 | addEntry: '=?', 27 | removeEntry: '=?', 28 | currentCategoryEntries: '=?', 29 | setupCategoryOptions: '=?', 30 | updateDialogFieldResponders: '=?', 31 | resolveCategories: '=?', 32 | modalTabIsSet: '<', 33 | modalTabSet: '<', 34 | modalTab: '=', 35 | saveModal: '<', 36 | uibModalInstance: '<', 37 | treeOptions: '<', 38 | modalUnchanged: '<', 39 | fieldDuplicates: '<', 40 | validation: '<', 41 | }; 42 | } 43 | -------------------------------------------------------------------------------- /src/dialog-editor/components/box/box.html: -------------------------------------------------------------------------------- 1 |
2 |
7 | 8 |
9 |
13 |
15 | {{ box.label }} 16 | 22 | 24 |
25 |
26 |
28 |
30 | 31 | {{ 'Drag items here to add to the dialog. At least one item is required before saving' | translate }} 32 |
33 |
34 |
35 | 39 |
40 |
41 |
42 |
43 |
44 |
45 |
48 |
49 | 50 |
51 |

Start with adding a section

52 |
53 | 58 |
 
59 |
60 |
61 | -------------------------------------------------------------------------------- /src/dialog-editor/components/box/boxComponent.ts: -------------------------------------------------------------------------------- 1 | import * as ng from 'angular'; 2 | import * as _ from 'lodash'; 3 | import {__} from '../../../common/translateFunction'; 4 | 5 | /** 6 | * Controller for the Dialog Editor box component 7 | * @memberof miqStaticAssets 8 | * @ngdoc controller 9 | * @name BoxController 10 | */ 11 | class BoxController { 12 | public sortableOptionsBox: any; 13 | public sortableOptionsFields: any; 14 | public service: any; 15 | public dialogTabs: any; 16 | public setupModalOptions: any; 17 | 18 | /*@ngInject*/ 19 | constructor(private DialogEditor: any) { 20 | } 21 | 22 | public onFieldEdit(type, tab, box, field) { 23 | this.setupModalOptions({type, tab, box, field}); 24 | } 25 | 26 | /** 27 | * Load service to be able to access it form the template. 28 | * Load status of tabs. 29 | * @memberof BoxController 30 | * @function $onInit 31 | */ 32 | public $onInit() { 33 | this.service = this.DialogEditor; 34 | this.dialogTabs = this.DialogEditor.getDialogTabs(); 35 | // Rules for Drag&Drop sorting of boxes 36 | this.sortableOptionsBox = { 37 | axis: 'y', 38 | cancel: '.nosort', 39 | cursor: 'move', 40 | opacity: 0.5, 41 | revert: 50, 42 | stop: (e: any, ui: any) => { 43 | let sortedBox = ui.item.scope().$parent.tab.dialog_groups; 44 | // update indexes of other boxes after changing their order 45 | this.DialogEditor.updatePositions(sortedBox); 46 | }, 47 | }; 48 | // Rules for Drag&Drop sorting of elements inside of boxes 49 | this.sortableOptionsFields = { 50 | axis: 'y', 51 | cancel: '.nosort', 52 | cursor: 'move', 53 | revert: 50, 54 | stop: (e: any, ui: any) => { 55 | let sortedField = ui.item.scope().$parent.box.dialog_fields; 56 | // update indexes of other fields after changing their order 57 | this.DialogEditor.updatePositions(sortedField); 58 | }, 59 | }; 60 | } 61 | 62 | /** 63 | * Add a new box to the list. 64 | * The new box is automatically appended to the last position of the list 65 | * @memberof BoxController 66 | * @function addBox 67 | */ 68 | public addBox() { 69 | this.dialogTabs[this.DialogEditor.activeTab].dialog_groups 70 | .push( 71 | { 72 | description: __('Description'), 73 | label: __('New Section'), 74 | display: 'edit', 75 | position: 0, 76 | dialog_fields: [], 77 | } 78 | ); 79 | // update indexes of other boxes after adding a new one 80 | this.DialogEditor.updatePositions( 81 | this.dialogTabs[this.DialogEditor.activeTab].dialog_groups 82 | ); 83 | } 84 | 85 | /** 86 | * Remove box and all its content from the dialog. 87 | * @memberof BoxController 88 | * @function removeBox 89 | * @param {number} id as index of removed box 90 | */ 91 | public removeBox(id: number) { 92 | _.remove( 93 | this.dialogTabs[this.DialogEditor.activeTab].dialog_groups, 94 | (box: any) => box.position === id 95 | ); 96 | // update indexes of other boxes after removing 97 | this.DialogEditor.updatePositions( 98 | this.dialogTabs[this.DialogEditor.activeTab].dialog_groups 99 | ); 100 | } 101 | 102 | /** 103 | * Handle Drag&Drop event. 104 | * @memberof BoxController 105 | * @function droppableOptions 106 | * @param {number} event jQuery object 107 | * @param {number} ui jQuery object 108 | */ 109 | public droppableOptions(e: any, ui: any) { 110 | const elementScope: any = ng.element(e.target).scope(); 111 | let droppedItem: any = elementScope.dndDragItem; 112 | let droppedPlace: any = elementScope.box; 113 | // update name for the dropped field 114 | if (!_.isEmpty(droppedItem)) { 115 | this.updateFieldName(droppedItem); 116 | } 117 | // update indexes of other boxes after changing their order 118 | this.DialogEditor.updatePositions( 119 | droppedPlace.dialog_fields 120 | ); 121 | } 122 | 123 | private updateFieldName(field) { 124 | let nameWithIndex: string = this.DialogEditor.newFieldName( 125 | field.name); 126 | field.name = nameWithIndex; 127 | } 128 | } 129 | 130 | /** 131 | * @memberof miqStaticAssets 132 | * @ngdoc component 133 | * @name dialogEditorBoxes 134 | * @description 135 | * Component implementing behaviour for the boxes inside of 136 | * the dialogs tabs. 137 | * @example 138 | * 139 | * 140 | */ 141 | export default class Box { 142 | public template = require('./box.html'); 143 | public controller: any = BoxController; 144 | public controllerAs: string = 'vm'; 145 | public bindings = { 146 | setupModalOptions: '&' 147 | }; 148 | } 149 | -------------------------------------------------------------------------------- /src/dialog-editor/components/box/index.ts: -------------------------------------------------------------------------------- 1 | import Box from './boxComponent'; 2 | 3 | export default (module: ng.IModule) => { 4 | module.component('dialogEditorBoxes', new Box); 5 | }; 6 | -------------------------------------------------------------------------------- /src/dialog-editor/components/dialog-editor/dialog-editor.html: -------------------------------------------------------------------------------- 1 |
2 | 7 |
8 |
9 |
10 | 11 |
12 |
13 |
14 | 15 | 16 |
17 |
18 |
19 | -------------------------------------------------------------------------------- /src/dialog-editor/components/dialog-editor/dialogEditorComponent.ts: -------------------------------------------------------------------------------- 1 | import * as ng from 'angular'; 2 | 3 | export class DialogEditorController { 4 | public modalOptions: any; 5 | public elementInfo: any; 6 | public treeOptions: any; 7 | 8 | public setupModalOptions(type, tab, box, field) { 9 | const components = { 10 | tab: 'dialog-editor-modal-tab', 11 | box: 'dialog-editor-modal-box', 12 | field: 'dialog-editor-modal-field' 13 | }; 14 | 15 | this.modalOptions = { 16 | component: components[type], 17 | size: 'lg', 18 | }; 19 | 20 | this.elementInfo = { 21 | type, 22 | tabId: tab, 23 | boxId: box, 24 | fieldId: field, 25 | }; 26 | } 27 | } 28 | 29 | /** 30 | * @memberof miqStaticAssets 31 | * @ngdoc component 32 | * @name dialogEditor 33 | * @description 34 | * Top-level dialog editor component. 35 | * @example 36 | * 37 | * 38 | */ 39 | 40 | export default class DialogEditor implements ng.IComponentOptions { 41 | public controller = DialogEditorController; 42 | public template = require('./dialog-editor.html'); 43 | public bindings = { 44 | treeOptions: '<', 45 | }; 46 | } 47 | -------------------------------------------------------------------------------- /src/dialog-editor/components/dialog-editor/index.ts: -------------------------------------------------------------------------------- 1 | import DialogEditor from './dialogEditorComponent'; 2 | 3 | export default (module: ng.IModule) => { 4 | module.component('dialogEditor', new DialogEditor); 5 | }; 6 | -------------------------------------------------------------------------------- /src/dialog-editor/components/field/field.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 | 6 | 11 | 12 | 13 | 19 | 20 | 21 | 25 | 26 | 27 |

28 | 34 | 35 | 40 | 41 |

42 | 43 |
44 |
45 |

46 | 52 | 53 | 57 | 58 |

59 |
60 |
61 | 62 |
63 |
64 | 65 | 66 |
67 |
68 | 74 |
75 |
76 | 82 |
83 |
84 | 85 | 86 | 87 | 95 | 96 | 97 | 98 | 106 | 107 |
108 |
109 | 119 | 130 |
131 |
132 | -------------------------------------------------------------------------------- /src/dialog-editor/components/field/fieldComponent.ts: -------------------------------------------------------------------------------- 1 | import * as _ from 'lodash'; 2 | import * as angular from 'angular'; 3 | 4 | /** 5 | * Controller for the Dialog Editor field component 6 | * @memberof miqStaticAssets 7 | * @ngdoc controller 8 | * @name FieldController 9 | */ 10 | class FieldController { 11 | public service: any; 12 | public sortedField: any; 13 | public fieldData: any; 14 | public boxPosition: any; 15 | 16 | /*@ngInject*/ 17 | constructor(private DialogEditor: any, private DialogData: any) { 18 | } 19 | 20 | /** 21 | * Load service to be able to access it form the template. 22 | * @memberof FieldController 23 | * @function $onInit 24 | */ 25 | public $onInit() { 26 | this.service = this.DialogEditor; 27 | this.sortedField = this.DialogData.setupSortableValues(this.fieldData); 28 | } 29 | 30 | /** 31 | * Update sortedField 32 | * @memberof FieldController 33 | * @function $onChanges 34 | */ 35 | public $onChanges(changesObj: any) { 36 | if (changesObj.fieldData) { 37 | this.sortedField = this.DialogData.setupSortableValues(this.fieldData); 38 | } 39 | } 40 | 41 | /** 42 | * Remove Field 43 | * @memberof FieldController 44 | * @function remmoveField 45 | * @param {number} tabId is an index of tab, where the box is placed 46 | * @param {number} boxId is an index of box, where the field is placed 47 | * @param {number} fieldId is an index of field 48 | */ 49 | public removeField(tabId: number, boxId: number, fieldId: number) { 50 | _.remove(this.getFields(tabId, boxId), (field: any) => field.position === fieldId); 51 | this.DialogEditor.updatePositions(this.getFields(tabId, boxId)); 52 | } 53 | 54 | /** 55 | * Find fields at tabId and boxId. 56 | * @memberof FieldController 57 | * @function getFields 58 | * @param {number} tabId is an index of tab, where the box is placed 59 | * @param {number} boxId is an index of box, where the field is placed 60 | * @returns {Array} of fields. 61 | */ 62 | private getFields(tabId: number, boxId: number) { 63 | const tabs = this.DialogEditor.getDialogTabs(); 64 | return tabs[tabId].dialog_groups[boxId].dialog_fields; 65 | } 66 | } 67 | 68 | /** 69 | * @memberof miqStaticAssets 70 | * @ngdoc component 71 | * @name dialogEditorField 72 | * @description 73 | * Component implementing behaviour for the fields inside of 74 | * the dialogs boxes. 75 | * @example 76 | * 79 | */ 80 | export default class Field { 81 | public template = require('./field.html'); 82 | public controller: any = FieldController; 83 | public controllerAs: string = 'vm'; 84 | public bindings: any = { 85 | fieldData: '<', 86 | boxPosition: '<', 87 | setupModalOptions: '&' 88 | }; 89 | } 90 | -------------------------------------------------------------------------------- /src/dialog-editor/components/field/index.ts: -------------------------------------------------------------------------------- 1 | import Field from './fieldComponent'; 2 | 3 | export default (module: ng.IModule) => { 4 | module.component('dialogEditorField', new Field); 5 | }; 6 | -------------------------------------------------------------------------------- /src/dialog-editor/components/index.ts: -------------------------------------------------------------------------------- 1 | import box from './box'; 2 | import dialogEditor from './dialog-editor'; 3 | import field from './field'; 4 | import modal from './modal'; 5 | import modalBox from './modal-box'; 6 | import modalField from './modal-field'; 7 | import modalFieldTemplate from './modal-field-template'; 8 | import modalTab from './modal-tab'; 9 | import tabList from './tab-list'; 10 | import toolbox from './toolbox'; 11 | import treeSelector from './tree-selector'; 12 | import validation from './validation'; 13 | 14 | export default (module: ng.IModule) => { 15 | box(module); 16 | dialogEditor(module); 17 | field(module); 18 | modal(module); 19 | modalBox(module); 20 | modalField(module); 21 | modalFieldTemplate(module); 22 | modalTab(module); 23 | tabList(module); 24 | toolbox(module); 25 | treeSelector(module); 26 | validation(module); 27 | }; 28 | -------------------------------------------------------------------------------- /src/dialog-editor/components/modal-box/box.html: -------------------------------------------------------------------------------- 1 | 7 | 8 | 29 | 30 | 39 | 40 | -------------------------------------------------------------------------------- /src/dialog-editor/components/modal-box/index.ts: -------------------------------------------------------------------------------- 1 | import ModalBox from './modalBoxComponent'; 2 | 3 | export default (module: ng.IModule) => { 4 | module.component('dialogEditorModalBox', new ModalBox); 5 | }; 6 | -------------------------------------------------------------------------------- /src/dialog-editor/components/modal-box/modalBoxComponent.ts: -------------------------------------------------------------------------------- 1 | import { AbstractModal, ModalController } from '../abstractModal'; 2 | 3 | /** 4 | * @memberof miqStaticAssets 5 | * @ngdoc component 6 | * @name dialogEditorModalBox 7 | * @description 8 | * Component contains templates for the modal for editing dialog editors 9 | * box (group) details 10 | * @example 11 | * 12 | */ 13 | export default class ModalBoxTemplate extends AbstractModal { 14 | public template = require('./box.html'); 15 | public controller = ModalBoxController; 16 | } 17 | 18 | class ModalBoxController extends ModalController { 19 | public modalData: any; 20 | public validation: any; 21 | 22 | public modalBoxIsValid() { 23 | return this.validation.validateGroup(this.modalData, true); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/dialog-editor/components/modal-field-template/check-box.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 10 |
11 |
12 | 17 |
18 |
19 | 24 |
25 |
26 | 31 |
32 | 34 | 35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | 43 | 44 | 45 | 46 |
47 |
48 | 50 | 51 |
52 | 57 |
58 | 60 | 61 |
62 | 63 | 65 | 66 |
67 |
68 |
69 |
70 |
71 | 76 |
77 |
78 | 83 |
84 |
85 |
86 | -------------------------------------------------------------------------------- /src/dialog-editor/components/modal-field-template/date-control.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 9 |
10 |
11 |
12 |

13 | 19 | 20 | 25 | 26 |

27 |
28 |
29 |
30 | 35 |
36 |
37 | 42 |
43 |
44 | 49 |
50 | 52 | 53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 | 61 | 62 | 63 | 64 |
65 |
66 |
67 | 72 |
73 |
74 | 79 |
80 | 82 | 83 |
84 | 85 |
86 | 91 |
92 | 93 | 95 | 96 |
97 |
98 |
99 |
100 |
101 | 106 |
107 |
108 | 113 |
114 |
115 |
116 | -------------------------------------------------------------------------------- /src/dialog-editor/components/modal-field-template/date-time-control.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 9 |
10 |
11 |
12 |

13 | 21 | 22 | 26 | 27 |

28 |
29 |
30 | 31 |
32 |
33 |
34 | 39 |
40 |
41 | 46 |
47 |
48 | 53 |
54 | 56 | 57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 | 65 | 66 | 67 | 68 |
69 |
70 |
71 | 76 |
77 |
78 | 83 |
84 | 86 | 87 |
88 | 89 |
90 | 95 |
96 | 97 | 99 | 100 |
101 |
102 |
103 |
104 |
105 | 110 |
111 |
112 | 117 |
118 |
119 |
120 | -------------------------------------------------------------------------------- /src/dialog-editor/components/modal-field-template/dynamic-values.html: -------------------------------------------------------------------------------- 1 |
2 | 7 |
8 |
9 | 14 |
15 | -------------------------------------------------------------------------------- /src/dialog-editor/components/modal-field-template/fields-to-refresh.html: -------------------------------------------------------------------------------- 1 |
2 | 9 |
10 | -------------------------------------------------------------------------------- /src/dialog-editor/components/modal-field-template/index.ts: -------------------------------------------------------------------------------- 1 | import ModalFieldTemplate from './modalFieldTemplateComponent'; 2 | 3 | export default (module: ng.IModule) => { 4 | module.component('dialogEditorModalFieldTemplate', new ModalFieldTemplate); 5 | }; 6 | -------------------------------------------------------------------------------- /src/dialog-editor/components/modal-field-template/modalFieldTemplateComponent.spec.ts: -------------------------------------------------------------------------------- 1 | import * as angular from 'angular'; 2 | 3 | describe('modalFieldTemplateSpec', () => { 4 | describe('component', () => { 5 | let scope, compile, template, compiledTemplate; 6 | 7 | beforeEach(() => { 8 | angular.mock.module('miqStaticAssets.dialogEditor', ($filterProvider) => { 9 | $filterProvider.register('translate', () => (value) => value); 10 | }); 11 | angular.mock.inject(($rootScope, $compile: ng.ICompileService) => { 12 | scope = $rootScope.$new(); 13 | compile = $compile; 14 | }); 15 | 16 | scope.modalTabIsSet = () => true; 17 | scope.modalTab = 'options'; 18 | scope.modalData = { 19 | type: 'DialogFieldDropDownList', 20 | options: { 21 | sort_by: 'none' 22 | }, 23 | values: [ 24 | ['aaa', 'AAA'], 25 | ['bbb', 'BBB'], 26 | ['ccc', 'CCC'], 27 | ['ddd', 'DDD'], 28 | ['eee', 'EEE'], 29 | ] 30 | }; 31 | 32 | template = angular.element( 33 | ` 37 | ` 38 | ); 39 | }); 40 | 41 | it('renders manually sortable fields', () => { 42 | compiledTemplate = compile(template)(scope); 43 | scope.$apply(); 44 | expect(compiledTemplate[0].querySelectorAll('.draggable-field').length).toBe(5); 45 | expect(compiledTemplate[0].querySelectorAll('.static-field').length).toBe(0); 46 | }); 47 | 48 | it('renders unsortable fields', () => { 49 | scope.modalData.options.sort_by = 'desc'; 50 | compiledTemplate = compile(template)(scope); 51 | scope.$apply(); 52 | expect(compiledTemplate[0].querySelectorAll('.draggable-field').length).toBe(0); 53 | expect(compiledTemplate[0].querySelectorAll('.static-field').length).toBe(5); 54 | }); 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /src/dialog-editor/components/modal-field-template/modalFieldTemplateComponent.ts: -------------------------------------------------------------------------------- 1 | import * as _ from 'lodash'; 2 | 3 | /** 4 | * Controller for the Dialog Editor Modal Field Template component 5 | * @ngdoc controller 6 | * @name ModalFieldController 7 | */ 8 | class ModalFieldController { 9 | public modalData: any; 10 | public sortableOptionsValues: any; 11 | public readonly DROPDOWN_ENTRY_VALUE: number = 0; 12 | public readonly DROPDOWN_ENTRY_DESCRIPTION: number = 1; 13 | 14 | /*@ngInject*/ 15 | constructor(private $scope, 16 | private $element: any) { 17 | // Rules for Drag&Drop sorting of values in a Dropdown element 18 | this.sortableOptionsValues = { 19 | axis: 'y', 20 | cancel: 'input', 21 | delay: 100, 22 | cursor: 'move', 23 | opacity: 0.5, 24 | revert: 50, 25 | stop: (e: any, ui: any) => { 26 | this.$element.find('select').selectpicker('refresh'); 27 | }, 28 | }; 29 | } 30 | 31 | public emptyDefaultValue(field) { 32 | // FIXME replace with DialogData shared impl? 33 | const byDataType = field.data_type === 'integer' ? 0 : ''; 34 | 35 | switch (field.type) { 36 | case 'DialogFieldTagControl': 37 | return field.options.force_single_value ? byDataType : []; 38 | case 'DialogFieldDropDownList': 39 | return field.options.force_multi_value ? [] : byDataType; 40 | default: 41 | return byDataType; 42 | } 43 | } 44 | 45 | public resetDefaultValue() { 46 | // TODO first use the real value if possible 47 | this.modalData.default_value = this.emptyDefaultValue(this.modalData); 48 | } 49 | 50 | // reset default_value on data_type change and single/multi change 51 | public $onInit() { 52 | const watch = (path, fn) => { 53 | this.$scope.$watch(path, (current, old) => { 54 | if (current !== old) { 55 | return fn(); 56 | } 57 | }); 58 | }; 59 | 60 | watch('vm.modalData.options.force_multi_value', () => this.resetDefaultValue()); 61 | watch('vm.modalData.options.force_single_value', () => this.resetDefaultValue()); 62 | watch('vm.modalData.data_type', () => this.resetDefaultValue()); 63 | // vm.modalData.values - handled by entriesChange 64 | } 65 | 66 | // reset default_value on entries list change 67 | public entriesChange() { 68 | setTimeout(() => this.$element.find('select').selectpicker('refresh')); 69 | this.resetDefaultValue(); 70 | } 71 | } 72 | 73 | /** 74 | * @memberof miqStaticAssets 75 | * @ngdoc component 76 | * @name dialogEditorModalFieldTemplate 77 | * @description 78 | * Component contains templates for the modal for each field type 79 | * @example 80 | * 83 | * 84 | */ 85 | export default class ModalFieldTemplate { 86 | /*@ngInject*/ 87 | public template = ($element: any, $attrs: any) => require(`./${$attrs.template}`); 88 | public scope: boolean = true; 89 | public controller = ModalFieldController; 90 | public controllerAs: string = 'vm'; 91 | public bindings: any = { 92 | modalData: '=', 93 | categories: '=?', 94 | addEntry: '=?', 95 | removeEntry: '=?', 96 | currentCategoryEntries: '=?', 97 | setupCategoryOptions: '=?', 98 | resolveCategories: '=?', 99 | modalTabIsSet: '<', 100 | modalTab: '=', 101 | showFullyQualifiedName: '<', 102 | treeOptions: '<', 103 | }; 104 | } 105 | -------------------------------------------------------------------------------- /src/dialog-editor/components/modal-field-template/tag-control.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 9 |
10 |
11 | 16 |
17 |
18 | 23 |
24 |
25 | 31 |
32 |
34 | 39 |
40 |
41 | 46 |
47 |
48 | 54 |
55 |
56 | 61 |
62 |
63 |
64 | 66 |
67 |
68 | 70 | 71 |
72 |
73 | -------------------------------------------------------------------------------- /src/dialog-editor/components/modal-field-template/text-area-box.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 8 |
9 |
10 | 15 |
16 |
17 | 22 |
23 |
24 | 29 |
30 | 31 | 33 | 34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | 42 | 43 | 44 | 45 |
46 |
47 | 49 | 50 |
51 | 56 |
57 | 58 | 60 | 61 |
62 | 63 | 65 | 66 |
67 |
68 |
69 |
70 |
71 | 76 |
77 |
78 | 83 |
84 |
85 | 89 |
90 |
91 |
92 | -------------------------------------------------------------------------------- /src/dialog-editor/components/modal-field/index.ts: -------------------------------------------------------------------------------- 1 | import ModalField from './modalFieldComponent'; 2 | 3 | export default (module: ng.IModule) => { 4 | module.component('dialogEditorModalField', new ModalField); 5 | }; 6 | -------------------------------------------------------------------------------- /src/dialog-editor/components/modal-tab/index.ts: -------------------------------------------------------------------------------- 1 | import ModalTab from './modalTabComponent'; 2 | 3 | export default (module: ng.IModule) => { 4 | module.component('dialogEditorModalTab', new ModalTab); 5 | }; 6 | -------------------------------------------------------------------------------- /src/dialog-editor/components/modal-tab/modalTabComponent.ts: -------------------------------------------------------------------------------- 1 | import { AbstractModal, ModalController } from '../abstractModal'; 2 | 3 | /** 4 | * @memberof miqStaticAssets 5 | * @ngdoc component 6 | * @name dialogEditorModalTab 7 | * @description 8 | * Component contains templates for the modal for editing dialog editors 9 | * tab (group) details 10 | * @example 11 | * 12 | */ 13 | export default class ModalTabTemplate extends AbstractModal { 14 | public template = require('./tab.html'); 15 | public controller = ModalTabController; 16 | } 17 | 18 | class ModalTabController extends ModalController { 19 | public modalData: any; 20 | public validation: any; 21 | 22 | public modalTabIsValid() { 23 | return this.validation.validateTab(this.modalData, true); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/dialog-editor/components/modal-tab/tab.html: -------------------------------------------------------------------------------- 1 | 7 | 8 | 29 | 30 | 39 | -------------------------------------------------------------------------------- /src/dialog-editor/components/modal/index.ts: -------------------------------------------------------------------------------- 1 | import Modal from './modalComponent'; 2 | 3 | export default (module: ng.IModule) => { 4 | module.component('dialogEditorModal', new Modal); 5 | }; 6 | -------------------------------------------------------------------------------- /src/dialog-editor/components/modal/modalComponent.spec.ts: -------------------------------------------------------------------------------- 1 | describe('modalComponentSpec', () => { 2 | let bindings; 3 | describe('controller', () => { 4 | let modalComponent; 5 | 6 | beforeEach(() => { 7 | bindings = { 8 | modalData: { options: { category_id: '10' } } 9 | }; 10 | angular.mock.module('miqStaticAssets.dialogEditor'); 11 | angular.mock.module('ui.bootstrap'); 12 | angular.mock.inject($componentController => { 13 | modalComponent = $componentController('dialogEditorModal', {DialogEditorHttp: {} }, bindings); 14 | }); 15 | }); 16 | 17 | describe('#setupCategoryOptions', () => { 18 | it('sets the id, name and description entries for the selected tag control', () => { 19 | modalComponent.categories = { resources : [{'id': '10', 'description': 'CategoryName', 'name': 'cc'}] }; 20 | modalComponent.setupCategoryOptions(); 21 | expect(modalComponent.modalData.options.category_id).toEqual('10'); 22 | expect(modalComponent.modalData.options.category_name).toEqual('cc'); 23 | expect(modalComponent.modalData.options.category_description).toEqual('CategoryName'); 24 | }); 25 | }); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /src/dialog-editor/components/tab-list/index.ts: -------------------------------------------------------------------------------- 1 | import TabList from './tabListComponent'; 2 | 3 | export default (module: ng.IModule) => { 4 | module.component('dialogEditorTabs', new TabList); 5 | }; 6 | -------------------------------------------------------------------------------- /src/dialog-editor/components/tab-list/tab-list.html: -------------------------------------------------------------------------------- 1 | 19 | -------------------------------------------------------------------------------- /src/dialog-editor/components/tab-list/tabListComponent.ts: -------------------------------------------------------------------------------- 1 | import * as ng from 'angular'; 2 | import * as _ from 'lodash'; 3 | import {__} from '../../../common/translateFunction'; 4 | 5 | /** 6 | * Controller for the Dialog Editor tab list component 7 | * @memberof miqStaticAssets 8 | * @ngdoc controller 9 | * @name TabListController 10 | */ 11 | class TabListController { 12 | public tabList: any; 13 | public sortableOptions: any; 14 | public setupModalOptions: any; 15 | 16 | /*@ngInject*/ 17 | constructor(private DialogEditor: any) { 18 | } 19 | 20 | /** 21 | * Activate the first tab in tab list, if there is any. 22 | * @memberof TabListController 23 | * @function onInit 24 | */ 25 | public $onInit() { 26 | // load tabs data from the service 27 | this.tabList = this.DialogEditor.getDialogTabs(); 28 | // set active tab 29 | if (this.tabList.length !== 0) { 30 | this.DialogEditor.activeTab = 0; 31 | this.tabList[this.DialogEditor.activeTab].active = true; 32 | } 33 | // set options for sorting tabs in list 34 | this.sortableOptions = { 35 | cancel: '.nosort', 36 | cursor: 'move', 37 | helper: 'clone', 38 | revert: 50, 39 | stop: (e: any, ui: any) => { 40 | let sortedTab: any = ng.element(ui.item).scope().$parent; 41 | let tabList = sortedTab.vm.tabList; 42 | this.DialogEditor.updatePositions(tabList); 43 | let activeTab: any = _.find(tabList, {active: true}); 44 | this.DialogEditor.activeTab = activeTab.position; 45 | }, 46 | }; 47 | } 48 | 49 | /** 50 | * Add a new tab to the list. 51 | * New tab is automatically appended to the last position of the list and 52 | * set as active. 53 | * @memberof TabListController 54 | * @function addTab 55 | */ 56 | public addTab() { 57 | // deactivate currently active tab 58 | this.tabList.forEach((tab) => tab.active = false); 59 | // create a new tab 60 | let nextIndex = this.tabList.length; 61 | this.tabList.push( 62 | { 63 | description: __('New tab ') + nextIndex, 64 | display: 'edit', 65 | label: __('New tab ') + nextIndex, 66 | position: nextIndex, 67 | active: true, 68 | dialog_groups: [{ 69 | 'label': __('New section'), 70 | 'position': 0, 71 | 'dialog_fields': [], 72 | }], 73 | } 74 | ); 75 | this.DialogEditor.activeTab = nextIndex; 76 | this.DialogEditor.updatePositions(this.tabList); 77 | } 78 | 79 | /** 80 | * Delete tab and all its content from the dialog. 81 | * After removing tab, position attributes needs to be updated. 82 | * If the tab to delete is active in the moment of the deletion, the 83 | * activity goes to the other tab. 84 | * @memberof TabListController 85 | * @function removeTab 86 | * @param {number} id is an index of remove tab 87 | */ 88 | public removeTab(id: number) { 89 | // pass the activity to other tab, if the deleted is active 90 | if (this.tabList[id].active) { 91 | if ((this.tabList.length - 1) === this.tabList[id].position && 92 | (this.tabList.length - 1) !== 0) { 93 | // active tab was at the end → new active tab is on previous index 94 | this.tabList[id - 1].active = true; 95 | } else if ((this.tabList.length - 1) > this.tabList[id].position) { 96 | // active tab was not at the end → new active tab is on following index 97 | this.tabList[id + 1].active = true; 98 | } 99 | } 100 | // remove tab with matching id 101 | _.remove(this.tabList, (tab: any) => tab.position === id); 102 | this.DialogEditor.backupSessionStorage( 103 | this.DialogEditor.getDialogId(), 104 | this.DialogEditor.data); 105 | // update indexes of other tabs after removing 106 | if (this.tabList.length !== 0) { 107 | this.DialogEditor.updatePositions(this.tabList); 108 | } else { 109 | return; 110 | } 111 | // set activity in the service 112 | let activeTabData: any = _.find( 113 | this.tabList, 114 | {active: true} 115 | ); 116 | if (ng.isDefined(activeTabData)) { 117 | this.DialogEditor.activeTab = activeTabData.position; 118 | } 119 | } 120 | 121 | /** 122 | * Assign activity to the selected tab. 123 | * @memberof TabListController 124 | * @function selectTab 125 | * @param {number} id is an index of remove tab 126 | */ 127 | public selectTab(id: number) { 128 | // deactivate currently active 129 | let deselectedTab = _.find( 130 | this.tabList, 131 | {active: true} 132 | ); 133 | deselectedTab.active = false; 134 | // activate selected 135 | let selectedTab = this.tabList[id]; 136 | selectedTab.active = true; 137 | this.DialogEditor.activeTab = id; 138 | } 139 | } 140 | 141 | /** 142 | * @memberof miqStaticAssets 143 | * @ngdoc component 144 | * @name dialogEditorTabs 145 | * @description 146 | * Component implementing behaviour for the tabs inside of 147 | * the dialogs. 148 | * @example 149 | * 150 | * 151 | */ 152 | export default class TabList { 153 | public template = require('./tab-list.html'); 154 | public controller: any = TabListController; 155 | public controllerAs: string = 'vm'; 156 | public bindings = { 157 | setupModalOptions: '&' 158 | }; 159 | } 160 | -------------------------------------------------------------------------------- /src/dialog-editor/components/toolbox/index.ts: -------------------------------------------------------------------------------- 1 | import Toolbox from './toolboxComponent'; 2 | 3 | export default (module: ng.IModule) => { 4 | module.component('dialogEditorFieldStatic', new Toolbox); 5 | }; 6 | -------------------------------------------------------------------------------- /src/dialog-editor/components/toolbox/toolbox.html: -------------------------------------------------------------------------------- 1 | 12 | -------------------------------------------------------------------------------- /src/dialog-editor/components/toolbox/toolboxComponent.ts: -------------------------------------------------------------------------------- 1 | import {__} from '../../../common/translateFunction'; 2 | class DialogField { 3 | public icon: string; 4 | public label: string; 5 | public placeholders: any; 6 | 7 | constructor(type: string, 8 | icon: string, 9 | label: string, 10 | name: string, 11 | options: any = {} 12 | ) { 13 | this.icon = icon; 14 | this.label = label; 15 | this.placeholders = Object.assign({ 16 | name: name, 17 | description: '', 18 | type: type, 19 | display: 'edit', 20 | display_method_options: {}, 21 | read_only: false, 22 | required: false, 23 | required_method_options: {}, 24 | default_value: '', 25 | values_method_options: {}, 26 | label: label, 27 | position: 0, 28 | dynamic: false, 29 | show_refresh_button: false, 30 | load_values_on_init: true, 31 | auto_refresh: false, 32 | trigger_auto_refresh: false, 33 | reconfigurable: false, 34 | visible: true, 35 | options: { 36 | protected: false, 37 | }, 38 | resource_action: {resource_type: 'DialogField', ae_attributes: {}}, 39 | }, options); 40 | } 41 | } 42 | 43 | /** 44 | * Controller for the Dialog Editor toolbox component 45 | * @memberof miqStaticAssets 46 | * @ngdoc controller 47 | * @name ToolboxController 48 | */ 49 | export class ToolboxController { 50 | public fields: any = { 51 | dialogFieldTextBox: 52 | new DialogField( 53 | 'DialogFieldTextBox', 54 | 'fa fa-font', 55 | __('Text Box'), 56 | 'text_box', 57 | { 58 | validator_type: false, 59 | } 60 | ), 61 | dialogFieldTextAreaBox: 62 | new DialogField( 63 | 'DialogFieldTextAreaBox', 64 | 'fa fa-file-text-o', 65 | __('Text Area'), 66 | 'textarea_box', 67 | { 68 | validator_type: false, 69 | } 70 | ), 71 | dialogFieldCheckBox: 72 | new DialogField( 73 | 'DialogFieldCheckBox', 74 | 'fa fa-check-square-o', 75 | __('Check Box'), 76 | 'check_box' 77 | ), 78 | dialogFieldDropDownList: 79 | new DialogField( 80 | 'DialogFieldDropDownList', 81 | 'fa fa-caret-square-o-down', 82 | __('Dropdown'), 83 | 'dropdown_list', 84 | { 85 | data_type: 'string', 86 | values: [['1', __('One')], ['2', __('Two')], ['3', __('Three')]], 87 | options: { 88 | sort_by: 'description', 89 | sort_order: 'ascending', 90 | force_multi_value: false, 91 | }, 92 | } 93 | ), 94 | dialogFieldRadioButton: 95 | new DialogField( 96 | 'DialogFieldRadioButton', 97 | 'fa fa-circle-o', 98 | __('Radio Button'), 99 | 'radio_button', 100 | { 101 | data_type: 'string', 102 | values: [['1', __('One')], ['2', __('Two')], ['3', __('Three')]], 103 | options: {sort_by: 'description', sort_order: 'ascending'}, 104 | } 105 | ), 106 | dialogFieldDateControl: 107 | new DialogField( 108 | 'DialogFieldDateControl', 109 | 'fa fa-calendar', 110 | __('Datepicker'), 111 | 'date_control', 112 | { 113 | options: { show_past_dates: false, } 114 | } 115 | ), 116 | dialogFieldDateTimeControl: 117 | new DialogField( 118 | 'DialogFieldDateTimeControl', 119 | 'fa fa-clock-o', 120 | __('Timepicker'), 121 | 'date_time_control', 122 | { 123 | options: { show_past_dates: false, } 124 | } 125 | ), 126 | dialogFieldTagControl: 127 | new DialogField( 128 | 'DialogFieldTagControl', 129 | 'fa fa-tags', 130 | __('Tag Control'), 131 | 'tag_control', 132 | { 133 | data_type: 'string', 134 | values: [], 135 | options: { 136 | category_id: '', 137 | force_single_value: false, 138 | sort_by: 'description', 139 | sort_order: 'ascending', 140 | }, 141 | } 142 | ), 143 | }; 144 | } 145 | 146 | /** 147 | * @memberof miqStaticAssets 148 | * @ngdoc component 149 | * @name dialogEditorFieldStatic 150 | * @description 151 | * Component is used as a toolbox for the Dialog Editor. 152 | * @example 153 | * 154 | * 155 | */ 156 | export default class Toolbox { 157 | public template = require('./toolbox.html'); 158 | public controller: any = ToolboxController; 159 | public controllerAs: string = 'vm'; 160 | } 161 | -------------------------------------------------------------------------------- /src/dialog-editor/components/tree-selector/index.ts: -------------------------------------------------------------------------------- 1 | import TreeSelector from './treeSelector'; 2 | 3 | export default (module: ng.IModule) => { 4 | module.component('dialogEditorTreeSelector', TreeSelector); 5 | }; 6 | -------------------------------------------------------------------------------- /src/dialog-editor/components/tree-selector/tree-selector.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 | Select Embedded Automate 6 | 7 | 8 | Select Embedded Workflow 9 | 10 |
11 |
12 |
13 | 16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | 31 |
32 |
33 |
34 | 35 | 40 |
41 |
42 |
43 |
44 |
45 | 57 |
58 |
59 |
60 |
61 | -------------------------------------------------------------------------------- /src/dialog-editor/components/tree-selector/treeSelector.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | template: require('./tree-selector.html'), 3 | bindings: { 4 | treeOptions: '<', 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /src/dialog-editor/components/validation/index.ts: -------------------------------------------------------------------------------- 1 | import Validation from './validation'; 2 | 3 | export default (module: ng.IModule) => { 4 | module.component('dialogEditorValidation', Validation); 5 | }; 6 | -------------------------------------------------------------------------------- /src/dialog-editor/components/validation/validation.html: -------------------------------------------------------------------------------- 1 |
2 | 9 |
10 |
12 | 16 | 17 | 19 |
20 |
22 | 24 |
25 | -------------------------------------------------------------------------------- /src/dialog-editor/components/validation/validation.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | template: require('./validation.html'), 3 | bindings: { 4 | modalData: '<', 5 | }, 6 | controller: function() { 7 | this.$onChanges = () => { 8 | if (this.modalData && ! this.modalData.validator_type) { 9 | // prevent null or undefined being interpreted wrong by the switch 10 | this.modalData.validator_type = false; 11 | } 12 | }; 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /src/dialog-editor/index.ts: -------------------------------------------------------------------------------- 1 | import services from './services'; 2 | import components from './components'; 3 | import * as angular from 'angular'; 4 | 5 | module dialogEditor { 6 | export const app = angular.module('miqStaticAssets.dialogEditor', [ 7 | 'ui.sortable', 8 | 'ngDragDrop', 9 | 'frapontillo.bootstrap-switch', 10 | 'miqStaticAssets.miqSelect', 11 | ]); 12 | 13 | services(app); 14 | components(app); 15 | } 16 | -------------------------------------------------------------------------------- /src/dialog-editor/services/index.ts: -------------------------------------------------------------------------------- 1 | import DialogEditorService from './dialogEditorService'; 2 | import DialogValidationService from './dialogValidationService'; 3 | 4 | export default (module: ng.IModule) => { 5 | module.service('DialogEditor', DialogEditorService); 6 | module.service('DialogValidation', DialogValidationService); 7 | }; 8 | -------------------------------------------------------------------------------- /src/dialog-user/components/dialog-user/dialog.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

No Provisioning Dialog Available.

4 |
5 |
6 | 7 | 8 |
9 |
10 |
11 |
12 | {{ ::buttonGroup.label }} 13 |
14 |
15 |
16 | 17 |
18 |
19 |
20 |
21 |
22 | 23 |
24 |
25 |
26 | -------------------------------------------------------------------------------- /src/dialog-user/components/dialog-user/dialogField.spec.ts: -------------------------------------------------------------------------------- 1 | const dialogField = { 2 | 'href': 'http://localhost:3001/api/service_templates/10000000000015/service_dialogs/10000000007060', 3 | 'id': 10000000007060, 4 | 'name': 'service_name', 5 | 'display': 'edit', 6 | 'display_method_options': {}, 7 | 'required': true, 8 | 'required_method_options': {}, 9 | 'default_value': '', 10 | 'values_method_options': {}, 11 | 'options': { 12 | 'protected': false 13 | }, 14 | 'created_at': '2017-06-16T19:29:28Z', 15 | 'updated_at': '2017-06-16T19:29:28Z', 16 | 'label': 'Service Name', 17 | 'dialog_group_id': 10000000002378, 18 | 'position': 0, 19 | 'validator_type': 'regex', 20 | 'validator_rule': '[0-9]', 21 | 'dynamic': false, 22 | 'read_only': false, 23 | 'visible': true, 24 | 'fieldBeingRefreshed': false, 25 | 'type': 'DialogFieldTextBox', 26 | 'resource_action': { 27 | 'resource_type': 'DialogField', 28 | 'ae_attributes': {} 29 | } 30 | }; 31 | 32 | describe('Dialog field test', () => { 33 | let bindings; 34 | 35 | describe('controller', () => { 36 | let dialogCtrl; 37 | 38 | beforeEach(() => { 39 | bindings = { 40 | field: {...dialogField}, 41 | onUpdate: () => true, 42 | inputDisabled: false 43 | }; 44 | angular.mock.module('miqStaticAssets.dialogUser'); 45 | angular.mock.inject($componentController => { 46 | dialogCtrl = $componentController('dialogField', null, bindings); 47 | dialogCtrl.$onInit(); 48 | }); 49 | }); 50 | 51 | it('should have some default properties set', () => { 52 | expect(dialogCtrl.validation.isValid).toBeDefined(); 53 | expect(dialogCtrl.dialogField.fieldBeingRefreshed).toBe(false); 54 | expect(dialogCtrl.validation.message).toBeDefined(); 55 | }); 56 | 57 | it('should allow a field to be validated', () => { 58 | dialogCtrl.dialogField.default_value = 'Test'; 59 | dialogCtrl.dialogField.validator_message = 'Numbers only'; 60 | dialogCtrl.validateField(); 61 | 62 | const fieldValid = dialogCtrl.validation; 63 | const expectedValue = { 64 | isValid: false, 65 | label: 'Service Name', 66 | message: 'Numbers only' 67 | }; 68 | 69 | expect(fieldValid).toEqual(jasmine.objectContaining(expectedValue)); 70 | }); 71 | 72 | it('should check and update a field when the parent component field has changed', () => { 73 | dialogCtrl.field.default_value = 'Testing'; 74 | dialogCtrl.$doCheck(); 75 | expect(dialogCtrl.clonedDialogField.default_value).toBe('Testing'); 76 | }); 77 | 78 | it('converts a string of default values to an array', () => { 79 | dialogCtrl.field = { 80 | ...dialogField, 81 | type: 'DialogFieldDropDownList', 82 | default_value: '["one", "two"]', 83 | values: [ 84 | ['val', 'label'], 85 | ], 86 | options: { 87 | ...dialogField.options, 88 | force_multi_value: true, 89 | }, 90 | data_type: 'string', 91 | }; 92 | dialogCtrl.$doCheck(); 93 | expect(dialogCtrl.dialogField.default_value).toEqual(['one', 'two']); 94 | }); 95 | }); 96 | 97 | describe('updates should be reported up to function that is passed into component', () => { 98 | let bindings; 99 | let dialogCtrl; 100 | 101 | it('should report back information when a field gets updated', () => { 102 | bindings = { 103 | field: dialogField, 104 | onUpdate: jasmine.createSpy('onUpdate', (dialogFieldName: any, value: any) => true), 105 | inputDisabled: false 106 | }; 107 | angular.mock.module('miqStaticAssets.dialogUser'); 108 | angular.mock.inject($componentController => { 109 | dialogCtrl = $componentController('dialogField', null, bindings); 110 | dialogCtrl.$onInit(); 111 | }); 112 | dialogCtrl.changesHappened(); 113 | expect(dialogCtrl.onUpdate).toHaveBeenCalledTimes(1); 114 | }); 115 | }); 116 | 117 | describe('#dateTimeFieldChanged', () => { 118 | let bindings; 119 | let dialogCtrl; 120 | let testDate = new Date(2018, 6, 11, 7, 30); 121 | const dateTimeDialogField = { 122 | 'name': 'dateTest', 123 | 'type': 'DialogFieldDateTimeControl', 124 | 'default_value': testDate, 125 | 'options': {}, 126 | }; 127 | 128 | it('calls onUpdate with the correct full date', () => { 129 | bindings = { 130 | field: dateTimeDialogField, 131 | onUpdate: jasmine.createSpy('onUpdate', (dialogFieldName: any, value: any) => true), 132 | inputDisabled: false 133 | }; 134 | angular.mock.module('miqStaticAssets.dialogUser'); 135 | angular.mock.inject($componentController => { 136 | dialogCtrl = $componentController('dialogField', null, bindings); 137 | dialogCtrl.$onInit(); 138 | }); 139 | 140 | dialogCtrl.dateTimeFieldChanged(); 141 | expect(dialogCtrl.onUpdate.calls.mostRecent().args[0].dialogFieldName).toEqual('dateTest'); 142 | expect(dialogCtrl.onUpdate.calls.mostRecent().args[0].value).toEqual(testDate); 143 | }); 144 | }); 145 | }); 146 | -------------------------------------------------------------------------------- /src/dialog-user/components/dialog-user/dialogField.ts: -------------------------------------------------------------------------------- 1 | import * as _ from 'lodash'; 2 | import * as angular from 'angular'; 3 | 4 | /** 5 | * This component deals with an individual dialog field 6 | * @memberof miqStaticAssets.dialogUser 7 | * @ngdoc controller 8 | * @name DialogFieldController 9 | */ 10 | export class DialogFieldController { 11 | public field: any; 12 | public onUpdate: any; 13 | public singleRefresh: any; 14 | public options: any; 15 | public inputDisabled: boolean; 16 | public reconfigureMode: boolean; 17 | 18 | public service: any; 19 | public dialogField: any; 20 | public validation: any; 21 | public minDate: any; 22 | public clonedDialogField: any; 23 | public areFieldsBeingRefreshed: boolean; 24 | 25 | /* @ngInject */ 26 | constructor(DialogData: any) { 27 | this.service = DialogData; 28 | } 29 | 30 | /** 31 | * Sets up the dialog field with defaults 32 | * @memberof DialogFieldController 33 | * @function $onInit 34 | */ 35 | public $onInit() { 36 | this.setup(); 37 | } 38 | 39 | /** 40 | * Checks to see if the dialog field has changed and re runs field setup if the field has changed 41 | * @memberof DialogFieldController 42 | * @function $doCheck 43 | */ 44 | public $doCheck() { 45 | if (!_.isEqual(this.field, this.clonedDialogField)) { 46 | this.setup(); 47 | } 48 | } 49 | 50 | // run field setup on field init or change 51 | public setup() { 52 | this.clonedDialogField = _.cloneDeep(this.field); 53 | this.dialogField = this.service.setupField(this.field); 54 | if (this.reconfigureMode && !this.dialogField.reconfigurable) { 55 | this.dialogField.disabled = true; 56 | } 57 | 58 | if ((this.dialogField.type === 'DialogFieldDateTimeControl') || 59 | (this.dialogField.type === 'DialogFieldDateControl')) { 60 | this.setMinDate(); 61 | } 62 | 63 | if (this.dialogField.type === 'DialogFieldDateTimeControl') { 64 | // dateTimeFieldChanged handles merging back to default_value 65 | this.dialogField.dateField = new Date(this.dialogField.default_value); 66 | this.dialogField.timeField = new Date(this.dialogField.default_value); 67 | 68 | if (! this.dialogField.default_value) { 69 | // clearing the date nulls the date field, so we're using that to represent null 70 | // timeField can't be null otherwise changing just date field to non-null would reset back to null 71 | this.dialogField.dateField = null; 72 | } 73 | } 74 | 75 | this.validateField(); 76 | } 77 | 78 | // validate field, set validation 79 | private validateField() { 80 | const field = this.dialogField; 81 | this.validation = this.service.validateField(field, field.default_value); 82 | } 83 | 84 | /** 85 | * This method is meant to be called whenever values change on a field. 86 | * It facilitates reporting updates to the parent component 87 | * @memberof DialogFieldController 88 | * @function changesHappened 89 | */ 90 | public changesHappened() { 91 | if (!this.areFieldsBeingRefreshed) { 92 | this.validateField(); 93 | 94 | const field = this.dialogField; 95 | this.onUpdate({ 96 | dialogFieldName: field.name, 97 | value: field.default_value, 98 | }); 99 | } 100 | } 101 | 102 | /** 103 | * This method is a 'changesHappened' method specific to dateTime fields. 104 | * It joins the two date and time models to then delegate to changesHappened. 105 | * @memberof DialogFieldController 106 | * @function dateTimeFieldChanged 107 | */ 108 | public dateTimeFieldChanged() { 109 | let dateField = this.dialogField.dateField; 110 | let timeField = this.dialogField.timeField; 111 | 112 | if (! dateField) { 113 | // cleared 114 | // timeField can't be null, see setup() 115 | this.dialogField.dateField = null; 116 | this.dialogField.default_value = null; 117 | return this.changesHappened(); 118 | } 119 | 120 | let fullYear = dateField.getFullYear(); 121 | let month = dateField.getMonth(); 122 | let date = dateField.getDate(); 123 | 124 | let hours = timeField.getHours(); 125 | let minutes = timeField.getMinutes(); 126 | 127 | this.dialogField.default_value = new Date(fullYear, month, date, hours, minutes); 128 | return this.changesHappened(); 129 | } 130 | 131 | /** 132 | * This method disables past days selection in the date control component 133 | * unless 'show_past_dates' is enabled 134 | * @memberof DialogFieldController 135 | * @function setMinDate 136 | */ 137 | public setMinDate() { 138 | this.minDate = this.dialogField.options.show_past_dates ? null : new Date(); 139 | } 140 | 141 | public refreshSingleField() { 142 | this.singleRefresh({ field: this.field.name }); 143 | } 144 | 145 | public isFieldDisabled() { 146 | return this.areFieldsBeingRefreshed || this.dialogField.read_only || this.inputDisabled || this.dialogField.disabled; 147 | } 148 | } 149 | 150 | export default class DialogField { 151 | public replace: boolean = true; 152 | public template = require('./dialogField.html'); 153 | public controller: any = DialogFieldController; 154 | public controllerAs: string = 'vm'; 155 | public bindings: any = { 156 | field: '<', 157 | onUpdate: '&', 158 | singleRefresh: '&', 159 | options: '=?', 160 | inputDisabled: '=?', 161 | reconfigureMode: '<', 162 | areFieldsBeingRefreshed: '<', 163 | }; 164 | } 165 | -------------------------------------------------------------------------------- /src/dialog-user/components/dialog-user/index.ts: -------------------------------------------------------------------------------- 1 | import DialogUser from './dialogUser'; 2 | import DialogField from './dialogField'; 3 | export default (module: ng.IModule) => { 4 | module.component('dialogUser', new DialogUser); 5 | module.component('dialogField',new DialogField); 6 | }; 7 | -------------------------------------------------------------------------------- /src/dialog-user/components/index.ts: -------------------------------------------------------------------------------- 1 | import dialogUser from './dialog-user'; 2 | import * as ng from 'angular'; 3 | 4 | export default (module: ng.IModule) => { 5 | dialogUser(module); 6 | }; 7 | -------------------------------------------------------------------------------- /src/dialog-user/index.ts: -------------------------------------------------------------------------------- 1 | import services from './services'; 2 | import components from './components'; 3 | import * as angular from 'angular'; 4 | 5 | module dialogUser { 6 | export const app = angular.module('miqStaticAssets.dialogUser',[]); 7 | services(app); 8 | components(app); 9 | } 10 | -------------------------------------------------------------------------------- /src/dialog-user/interfaces/abstractDialogClass.ts: -------------------------------------------------------------------------------- 1 | import {IDialogs} from './dialog'; 2 | /** 3 | * This is abstract controller for implementing fields and methods used by Dialog components 4 | * @memberof miqStaticAssets.dialogUser 5 | * @ngdoc controller 6 | * @name DialogClass 7 | */ 8 | export abstract class DialogClass implements IDialogs { 9 | public dialog: any; 10 | public refreshField: any; 11 | public onUpdate: any; 12 | public inputDisabled: boolean; 13 | 14 | /*@ngInject*/ 15 | constructor() { 16 | return; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/dialog-user/interfaces/dialog.ts: -------------------------------------------------------------------------------- 1 | export interface IDialogs { 2 | dialog: object; 3 | refreshField: (args: {field: object}) => void; 4 | onUpdate: (args: {data: object}) => void; 5 | inputDisabled?: boolean; 6 | } 7 | -------------------------------------------------------------------------------- /src/dialog-user/services/index.ts: -------------------------------------------------------------------------------- 1 | import DialogDataService from './dialogData'; 2 | 3 | export default (module: ng.IModule) => { 4 | module.service('DialogData', DialogDataService); 5 | }; 6 | -------------------------------------------------------------------------------- /src/fonticon-picker/components/fonticon-family/fonticonFamilyComponent.ts: -------------------------------------------------------------------------------- 1 | import { FonticonPickerController } from '../fonticon-picker/fonticonPickerComponent'; 2 | 3 | class FonticonFamilyController { 4 | public FonticonPickerCtrl : FonticonPickerController; 5 | private title : string; 6 | private selector : string; 7 | 8 | public $onInit() { 9 | this.FonticonPickerCtrl.addFamily({ 10 | title: this.title, 11 | selector: this.selector 12 | }); 13 | } 14 | } 15 | 16 | export default class FonticonPicker implements ng.IComponentOptions { 17 | public controller = FonticonFamilyController; 18 | public require = { 19 | FonticonPickerCtrl: '^miqFonticonPicker' 20 | }; 21 | public bindings = { 22 | title: '@', 23 | selector: '@' 24 | }; 25 | } 26 | -------------------------------------------------------------------------------- /src/fonticon-picker/components/fonticon-family/index.ts: -------------------------------------------------------------------------------- 1 | import FonticonFamily from './fonticonFamilyComponent'; 2 | 3 | export default (module: ng.IModule) => { 4 | module.component('miqFonticonFamily', new FonticonFamily); 5 | }; 6 | -------------------------------------------------------------------------------- /src/fonticon-picker/components/fonticon-picker/fonticon-modal.html: -------------------------------------------------------------------------------- 1 | 4 | 17 | 21 | -------------------------------------------------------------------------------- /src/fonticon-picker/components/fonticon-picker/fonticon-picker.html: -------------------------------------------------------------------------------- 1 |
2 | 6 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/fonticon-picker/components/fonticon-picker/fonticonPickerComponent.ts: -------------------------------------------------------------------------------- 1 | import FonticonService from '../../services/fonticonService'; 2 | 3 | export class FonticonPickerController { 4 | public families = []; 5 | public fonticons; 6 | public btnClass = 'btn-default'; 7 | public selected; 8 | public toSelect; 9 | public inputName; // TODO: this can be deleted after the form is angularized 10 | public iconChanged: (args: {selected: any}) => void; // TODO: this can be deleted after the form is angularized 11 | private modal; 12 | 13 | /*@ngInject*/ 14 | constructor(private MiQFonticonService: FonticonService, private $uibModal) {} 15 | 16 | public addFamily(font) { 17 | this.families.push(font); 18 | } 19 | 20 | public openModal() { 21 | if (this.fonticons === undefined) { 22 | this.fonticons = this.MiQFonticonService.fetch(this.families); 23 | } 24 | 25 | this.toSelect = this.selected; 26 | 27 | this.modal = this.$uibModal.open({ 28 | template: require('./fonticon-modal.html'), 29 | windowClass: 'fonticon-modal', 30 | keyboard: false, 31 | size: 'lg', 32 | controllerAs: '$ctrl', 33 | controller: ['parent', function(parent) { this.parent = parent; }], 34 | resolve: { 35 | parent: () => this 36 | }, 37 | }); 38 | } 39 | 40 | public closeModal(save) { 41 | if (save) { 42 | this.selected = this.toSelect; 43 | this.iconChanged({selected: this.selected}); // TODO: this can be deleted after the form is angularized 44 | } 45 | this.modal.close(); 46 | } 47 | 48 | public markToSelect(icon) { 49 | this.toSelect = icon; 50 | } 51 | 52 | public isDisabled(): boolean { 53 | return !this.toSelect || this.toSelect === this.selected; 54 | } 55 | } 56 | 57 | export default class FonticonPicker implements ng.IComponentOptions { 58 | public controller = FonticonPickerController; 59 | public template = require('./fonticon-picker.html'); 60 | public transclude = true; 61 | public bindings = { 62 | btnClass: '@?', 63 | selected: '@', 64 | inputName: '@', // TODO: this can be deleted after the form is angularized 65 | iconChanged: '&', // TODO: this can be deleted after the form is angularized 66 | }; 67 | } 68 | -------------------------------------------------------------------------------- /src/fonticon-picker/components/fonticon-picker/index.ts: -------------------------------------------------------------------------------- 1 | import * as ng from 'angular'; 2 | import FonticonPicker from './fonticonPickerComponent'; 3 | 4 | export default (module: ng.IModule) => { 5 | module.component('miqFonticonPicker', new FonticonPicker); 6 | }; 7 | -------------------------------------------------------------------------------- /src/fonticon-picker/components/index.ts: -------------------------------------------------------------------------------- 1 | import FonticonPicker from './fonticon-picker'; 2 | import FonticonFamily from './fonticon-family'; 3 | 4 | export default (module: ng.IModule) => { 5 | FonticonPicker(module); 6 | FonticonFamily(module); 7 | }; 8 | -------------------------------------------------------------------------------- /src/fonticon-picker/index.ts: -------------------------------------------------------------------------------- 1 | import components from './components'; 2 | import services from './services'; 3 | import * as angular from 'angular'; 4 | 5 | module fonticonPicker { 6 | export const app = angular.module('miqStaticAssets.fonticonPicker', ['ui.bootstrap']); 7 | services(app); 8 | components(app); 9 | } 10 | -------------------------------------------------------------------------------- /src/fonticon-picker/services/fonticonService.ts: -------------------------------------------------------------------------------- 1 | import * as _ from 'lodash'; 2 | 3 | export default class FonticonService { 4 | public fetch(families: any): any { 5 | return _.reduce(families, (result: any, value: any) => { 6 | result[value.selector] = FonticonService.filterRules(value.selector); 7 | return result; 8 | }, {}); 9 | } 10 | 11 | private static filterRules(family: string): any[] { 12 | return _.chain(document.styleSheets) 13 | .map((oneSheet: any) => oneSheet.cssRules) 14 | .map((rule: any) => _.filter(rule, value => FonticonService.isFontIcon(value, family))) 15 | .filter((rules: any) => rules.length !== 0) 16 | .map((rules: any[]) => _.map(rules, (value: any) => FonticonService.clearRule(value.selectorText, family))) 17 | .flatten() 18 | .reduce((result: any[], value: string) => [...result, FonticonService.makeRuleObject(family, value)], []) 19 | .value(); 20 | } 21 | 22 | private static isFontIcon(value, family): boolean { 23 | return value.selectorText && value.selectorText.indexOf(family) === 1 && value.cssText.indexOf('content:') !== -1; 24 | } 25 | 26 | private static clearRule(rule: string, family: string): string { 27 | let re = new RegExp(`.*(${family}\-[a-z0-9\-\_]+).*`); 28 | return rule.replace(re, '$1'); 29 | } 30 | 31 | private static makeRuleObject(family, value): any { 32 | return { 33 | 'class': `${family} ${value}`, 34 | 'selector': `.${family}.${value}` 35 | }; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/fonticon-picker/services/index.ts: -------------------------------------------------------------------------------- 1 | import FonticonService from './fonticonService'; 2 | 3 | export default (module: ng.IModule) => { 4 | module.service('MiQFonticonService', FonticonService); 5 | }; 6 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import * as angular from 'angular'; 2 | 3 | module miqStaticAssets { 4 | angular.module('miqStaticAssets', [ 5 | 'miqStaticAssets.common', 6 | 'miqStaticAssets.dialogEditor', 7 | 'miqStaticAssets.dialogUser', 8 | 'miqStaticAssets.fonticonPicker', 9 | 'miqStaticAssets.treeView', 10 | 'miqStaticAssets.treeSelector', 11 | 'miqStaticAssets.miqSelect' 12 | ]); 13 | } 14 | -------------------------------------------------------------------------------- /src/miq-select/index.ts: -------------------------------------------------------------------------------- 1 | import MiqSelect from './miqSelect'; 2 | import MiqOptions from './miqOptions'; 3 | import * as angular from 'angular'; 4 | 5 | module miqSelect { 6 | export const app = angular.module('miqStaticAssets.miqSelect', []); 7 | app.directive('miqSelect', MiqSelect); 8 | app.directive('miqOptions', MiqOptions); 9 | } 10 | -------------------------------------------------------------------------------- /src/miq-select/miqOptions.js: -------------------------------------------------------------------------------- 1 | // use together with ng-options to set extra attributes on the option elements 2 | // use miq-options="{ attr1: 'static', attr2: expression, attr3: vm.function }" 3 | // expression gets evaluated in each option's scope separately, 4 | // function gets run with (scope, element) 5 | 6 | /* @ngInject */ 7 | var miqOptions = function ($parse) { 8 | 'use strict'; 9 | 10 | return { 11 | priority: 0, 12 | restrict: 'A', 13 | link: function (scope, element, attrs) { 14 | var match = attrs.ngOptions.match(/for\s+(.+)\s+in\s+(.+)\s*/); 15 | if (!match) { 16 | console.warn('miqOptions: couldn\'t parse ngOptions', attrs.ngOptions); 17 | return; 18 | } 19 | var attrName = match[1]; 20 | var attrToWatch = match[2]; 21 | 22 | var parsedOptions = $parse(attrs.miqOptions); 23 | 24 | var updateOptions = function (newValue) { 25 | if (!newValue) { 26 | return; 27 | } 28 | 29 | var values = []; 30 | if (_.isArray(newValue)) { 31 | values = newValue; 32 | } else if (_.isObject(newValue)) { 33 | values = Object.values(newValue); 34 | } else { 35 | console.warn('miqOptions: Unknown ngOptions source', newValue); 36 | return; 37 | } 38 | 39 | var optionCount = angular.element('option', element).length; 40 | 41 | var ignoredOptions = 0; 42 | if (values.length === optionCount) { 43 | ignoredOptions = 0; 44 | } else if (values.length === optionCount - 1) { 45 | ignoredOptions = 1; 46 | } else { 47 | console.warn('miqOptions: mismatch between number of items and options', values.length, optionCount); 48 | return; 49 | } 50 | 51 | angular.element('option', element).each(function (i, optionElement) { 52 | var idx = i - ignoredOptions; 53 | if (idx < 0) { 54 | return; // skip empty option 55 | } 56 | 57 | var optionScope = scope.$new(); 58 | optionScope.$index = idx; 59 | optionScope[attrName] = values[idx]; 60 | 61 | var miqOptions = parsedOptions(optionScope); 62 | Object.keys(miqOptions).forEach(function(key) { 63 | var value = miqOptions[key]; 64 | if (_.isFunction(value)) { 65 | value = value(optionScope, optionElement); 66 | } 67 | 68 | angular.element(optionElement).attr(key, value); 69 | }); 70 | }); 71 | }; 72 | 73 | scope.$watch(attrToWatch, updateOptions); 74 | scope.$watchCollection(attrToWatch, updateOptions); 75 | }, 76 | }; 77 | }; 78 | 79 | export default miqOptions; 80 | -------------------------------------------------------------------------------- /src/miq-select/miqSelect.js: -------------------------------------------------------------------------------- 1 | var miqSelect = function () { 2 | 'use strict'; 3 | 4 | return { 5 | restrict: 'A', 6 | require: '?ngModel', 7 | scope: { 8 | selectPickerOptions: '=miqSelect' 9 | }, 10 | link: function (scope, element, attrs, ngModel) { 11 | var optionCollectionList, optionCollectionExpr, optionCollection, $render = ngModel.$render; 12 | 13 | var selectpickerRefresh = function (argument) { 14 | scope.$applyAsync(function () { 15 | element.selectpicker('refresh'); 16 | }); 17 | }; 18 | 19 | var selectpickerDestroy = function () { 20 | element.selectpicker('destroy'); 21 | }; 22 | 23 | element.selectpicker(scope.selectPickerOptions || { 24 | liveSearchFocus: false, 25 | }); 26 | 27 | ngModel.$render = function () { 28 | $render.apply(this, arguments); 29 | selectpickerRefresh(); 30 | }; 31 | 32 | if (attrs.ngOptions) { 33 | optionCollectionList = attrs.ngOptions.split('in '); 34 | optionCollectionExpr = optionCollectionList[optionCollectionList.length - 1].split(/track by|\|/); 35 | optionCollection = optionCollectionExpr[0]; 36 | 37 | scope.$parent.$watchCollection(optionCollection, selectpickerRefresh); 38 | } 39 | 40 | if (attrs.ngModel) { 41 | scope.$parent.$watch(attrs.ngModel, selectpickerRefresh); 42 | } 43 | 44 | if (attrs.watchModel) { 45 | scope.$parent.$watch(attrs.watchModel, selectpickerRefresh); 46 | } 47 | 48 | attrs.$observe('disabled', selectpickerRefresh); 49 | 50 | scope.$on('$destroy', selectpickerDestroy); 51 | } 52 | }; 53 | }; 54 | 55 | export default miqSelect; 56 | -------------------------------------------------------------------------------- /src/styles/colors.scss: -------------------------------------------------------------------------------- 1 | // Styleguide: 2 | // - Prefix names with color- 3 | // - Name the variable as the color it represents, not its intended function 4 | // - If the color includes an alpha channel suffix with -xx 5 | // - If the color is an approximate match suffix with -approx 6 | // - Do not suffix '-solid' for exact matches 7 | // - If the color already exists with -approx modifier, append a count -x starting at 2 8 | // - Use only hyphens and lowercase characters 9 | // 10 | // Examples 11 | // $color-blue: #0000ff 12 | // $color-governor-bay-approx: #3B4CC4 13 | // $color-governor-bay-approx-2: #3B4AC5 14 | // $color-white-70: rgba(255, 255, 255, 0.7) 15 | // $color-cerulean-45-approx: rgba(0, 173, 239, 0.45) 16 | // 17 | // Use a tool such as : http://name-of-color.com/ to find the name of your color 18 | 19 | // Grays 20 | $color-white: #fff; 21 | $color-off-white: #ddd; 22 | $color-black-haze-approx: #f4f7f8; 23 | $color-white-smoke: #f5f5f5; 24 | $color-gallery-approx: #eee; 25 | $color-cararra-approx: #ebebeb; 26 | $color-light-gray-approx: #d1d1d1; 27 | $color-gray-nurse-approx: #e9e8e8; 28 | $color-gray: #808080; 29 | $color-gray-1: #b1b1b1; 30 | $color-gray-2: #8b8d8f; 31 | $color-bombay-approx: #b1b3b6; 32 | $color-concrete-solid: #f2f2f2; 33 | $color-mountain-mist-approx: #939598; 34 | $color-abbey-approx: #4c4f56; 35 | $color-medium-gray-3: #666; 36 | $color-white-94: rgba(255, 255, 255, 0.94); 37 | 38 | $color-cape-cod-approx: #414042; 39 | $color-mine-shaft-approx: #333; 40 | $color-black: #000; 41 | $color-black-1: rgba(3, 3, 3, 1); 42 | $color-black-20: rgba(0, 0, 0, 0.2); 43 | $color-black-30: rgba(0, 0, 0, 0.3); 44 | $color-dark-gray-75: rgba($color-cape-cod-approx, 0.75); 45 | 46 | // Reds 47 | $color-red: #f00; 48 | $color-radical-red-approx: #f13b54; 49 | $color-froly-approx: #f1757b; 50 | $color-coral-approx: #f88954; 51 | $color-misty-rose-approx: #fbe7e7; 52 | $color-lava-approx: #cc151d; 53 | $color-boston-university-red: #c00; 54 | $color-serenade-approx: #fdf4ea; 55 | $color-cadmium-orange-approx: #eb822b; 56 | $color-tahiti-gold-approx: #ec7a08; 57 | 58 | // Greens 59 | $color-green: #0f0; 60 | $color-green-1: #008000; 61 | $color-green-crayola-approx: #23a27e; 62 | $color-dark-green-1: #3c763d; 63 | $color-mountain-meadow-approx: #1dc58e; 64 | $color-frostee-approx: #e8f9e6; 65 | $color-apple-approx: #4da146; 66 | 67 | // Blues 68 | $color-blue: #00f; 69 | $color-ebony-clay-approx: #2b2e3e; 70 | $color-martinique-approx: #33394d; 71 | $color-gun-power-approx: #3d445c; 72 | $color-storm-gray-approx: #6e7385; 73 | $color-blue-de-france-approx: #3397db; 74 | $color-picton-blue-approx: #44bde6; 75 | $color-light-blue: #0099d3; 76 | $color-lochmara-approx: #0088ce; 77 | $color-medium-blue-3: #0d6f9a; 78 | $color-pale-cerulean-approx: #95c3e3; 79 | $color-polar-approx: #e2f5f6; 80 | $color-deep-ocean-approx: #1c3753; 81 | $color-tarawera-approx: #063451; 82 | $color-blue-50: #def3ff; 83 | $color-blue-300: #39a5dc; 84 | $color-pattens-blue-approx: #e3f4fd; 85 | $color-spindle-approx: #b3d3e7; 86 | $color-onahau-approx: #d4edfa; 87 | 88 | // Purples 89 | $color-prelude-approx: #cebee1; 90 | $color-lavender-purple-approx: #9f7fc3; 91 | $color-royal-purple-approx: #6d3ba4; 92 | $color-kingfisher-daisy-approx: #3b0083; 93 | $color-paua-approx: #2d0064; 94 | $color-tolopea-approx: #1e0042; 95 | $color-black-russian-approx: #0f0021; 96 | -------------------------------------------------------------------------------- /src/styles/dialog-editor-boxes.scss: -------------------------------------------------------------------------------- 1 | .dialog-editor-container { 2 | background-color: #fff; 3 | padding: 20px; 4 | margin-bottom: 10px; 5 | } 6 | 7 | .draggable-box { 8 | background-color: $color-white-94; 9 | border-radius: 1px; 10 | cursor: pointer; 11 | margin-right: 16px; 12 | margin-top: 10px; 13 | height: 100%; 14 | width: 100%; 15 | position: relative; 16 | 17 | &.selected { 18 | border: 3px solid $color-blue-300; 19 | } 20 | 21 | &:hover { 22 | border: 1px solid $color-gray-1; 23 | box-shadow: 0 2px 6px $color-black-20; 24 | 25 | .header-fa { 26 | float: right; 27 | font-size: 22px; 28 | } 29 | 30 | &.selected { 31 | border: 3px solid $color-blue-300; 32 | } 33 | 34 | .header-pf { 35 | float: right; 36 | font-size: 16px; 37 | padding-top: 3px; 38 | padding-right: 8px; 39 | } 40 | 41 | .show-on-hover { 42 | display: initial !important; 43 | } 44 | 45 | &::before { 46 | background-image: linear-gradient(to bottom, $color-lochmara-approx 60%, $color-white 0%); 47 | background-position: left; 48 | background-repeat: repeat-y; 49 | background-size: 2px 5px; 50 | border: 4px solid $color-lochmara-approx; 51 | bottom: 4px; 52 | content: ""; 53 | left: 4px; 54 | position: absolute; 55 | top: 3px; 56 | width: 10px; 57 | } 58 | } 59 | 60 | &:first-child { 61 | margin-top: 0px; 62 | } 63 | } 64 | 65 | .draggable-field { 66 | background-color: $color-white-94; 67 | border-radius: 1px; 68 | cursor: pointer; 69 | margin-right: 16px; 70 | margin-top: 10px; 71 | width: 100%; 72 | padding-top: 9px; 73 | padding-left: 20px; 74 | padding-right: 8px; 75 | padding-bottom: 2px; 76 | position: relative; 77 | z-index: 600; 78 | 79 | &.selected { 80 | border: 3px solid $color-blue-300; 81 | } 82 | 83 | &:hover { 84 | border: 1px solid $color-gray-1; 85 | box-shadow: 0 2px 6px $color-black-20; 86 | 87 | &.selected { 88 | border: 3px solid $color-blue-300; 89 | } 90 | 91 | button { 92 | display: inherit !important; 93 | position: relative; 94 | top: 4px; 95 | } 96 | 97 | &::before { 98 | background-image: linear-gradient(to bottom, $color-lochmara-approx 60%, $color-white 0%); 99 | background-position: left; 100 | background-repeat: repeat-y; 101 | background-size: 2px 5px; 102 | border: 4px solid $color-lochmara-approx; 103 | bottom: 4px; 104 | content: ""; 105 | left: 4px; 106 | position: absolute; 107 | top: 3px; 108 | width: 10px; 109 | } 110 | } 111 | 112 | &:first-child { 113 | margin-top: 0px; 114 | } 115 | 116 | img { 117 | height: 100%; 118 | margin-right: 8px; 119 | object-fit: contain; 120 | } 121 | } 122 | 123 | .add-section-box { 124 | cursor: pointer; 125 | height: 45px; 126 | width: 100%; 127 | padding-left: 10px; 128 | padding-top: 10px; 129 | border: 2px dashed $color-gray-1; 130 | margin-bottom: 26px; 131 | 132 | a { 133 | cursor: pointer; 134 | } 135 | } 136 | 137 | .draggable-field-dropdown { 138 | z-index: initial; 139 | } 140 | 141 | .entry_point_selector_types { 142 | 143 | .automation_type_selector { 144 | margin-bottom: 20px !important; 145 | } 146 | .entry_point { 147 | margin-top: 20px; 148 | } 149 | } 150 | 151 | 152 | l.nav.nav-list.workflows { 153 | background-color: #f5f5f5; 154 | border: 1px solid #e3e3e3; 155 | 156 | li { 157 | border-bottom: 1px solid lightgray; 158 | padding: 10px; 159 | } 160 | } 161 | 162 | .tree_selector_wrapper { 163 | display: flex; 164 | flex-direction: column; 165 | border: 1px solid lightgray; 166 | margin-top: 10px; 167 | 168 | .tree_selector_title_wrapper { 169 | display: flex; 170 | flex-direction: row; 171 | background: lightgray; 172 | padding: 5px; 173 | 174 | .tree_selector_dialog_title { 175 | display: flex; 176 | flex-grow: 1; 177 | font-weight: bold; 178 | padding: 5px 0; 179 | font-size: 14px; 180 | } 181 | .tree_selector_action { 182 | display: flex; 183 | justify-content: center; 184 | align-items: center; 185 | min-width: 30px; 186 | } 187 | } 188 | .tree_selector_content_wrapper { 189 | display: flex; 190 | flex-direction: column; 191 | padding: 10px; 192 | 193 | ul.workflows_list_wrapper { 194 | display: flex; 195 | flex-direction: column; 196 | border: 1px solid lightgray; 197 | 198 | li.workflows_list_header { 199 | background: lightgray; 200 | padding: 10px 0; 201 | font-weight: bold; 202 | } 203 | 204 | li.workflow_item { 205 | display: flex; 206 | flex-grow: 1; 207 | padding: 5px; 208 | border-bottom: 1px solid lightgray; 209 | 210 | &:last-child { 211 | border-bottom: 0 !important; 212 | } 213 | 214 | .workflow_item_row { 215 | display: flex; 216 | flex-grow: 1; 217 | margin-left: -5px; 218 | } 219 | 220 | &:hover { 221 | background: #e4e5e6; 222 | cursor: pointer; 223 | } 224 | 225 | } 226 | } 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /src/styles/dialog-editor-toolbox.scss: -------------------------------------------------------------------------------- 1 | .static-field-container { 2 | background-color: $color-white-94; 3 | border: 1.5px solid $color-light-gray-approx; 4 | box-shadow: 0 2px 6px $color-black-30; 5 | position: absolute; 6 | 7 | .static-field-icon { 8 | font-size: 28px; 9 | padding-top: 6px; 10 | vertical-align: middle; 11 | } 12 | 13 | .static-field-list { 14 | list-style: none; 15 | width: 115px; 16 | overflow-y: auto; 17 | margin-bottom: 0px; 18 | padding-left: 0; 19 | } 20 | 21 | .static-field-item { 22 | background-color: $color-white-94; 23 | border-bottom: 1px solid $color-gray-1; 24 | border-radius: 1px; 25 | cursor: pointer; 26 | min-height: 70px; 27 | width: 100%; 28 | position: relative; 29 | text-align: center; 30 | vertical-align: middle; 31 | z-index: 600; 32 | 33 | &:hover { 34 | border: 1px solid $color-gray-1; 35 | box-shadow: 0 2px 6px $color-black-20; 36 | 37 | &::before { 38 | background-image: linear-gradient(to bottom, $color-lochmara-approx 60%, $color-white 0%); 39 | background-position: left; 40 | background-repeat: repeat-y; 41 | background-size: 2px 5px; 42 | border: 4px solid $color-lochmara-approx; 43 | bottom: 4px; 44 | content: ""; 45 | left: 4px; 46 | position: absolute; 47 | top: 3px; 48 | width: 10px; 49 | } 50 | } 51 | 52 | .fa { 53 | font-size: 26px; 54 | margin-top: 6px; 55 | vertical-align: middle; 56 | } 57 | 58 | span { 59 | position: relative; 60 | top: 4px; 61 | } 62 | 63 | img { 64 | height: 100%; 65 | margin-right: 8px; 66 | object-fit: contain; 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/styles/dialog-editor.scss: -------------------------------------------------------------------------------- 1 | .dialog-designer-container { 2 | background-color: #ffffff; 3 | height: 100%; 4 | width: 100%; 5 | 6 | .toolbox-container { 7 | height: 100%; 8 | position: relative; 9 | width: 100%; 10 | 11 | .editable-fields-container { 12 | padding-top: 10px; 13 | padding-left: 128px; 14 | width: 80%; 15 | } 16 | 17 | a { 18 | color: #000; 19 | } 20 | } 21 | } 22 | 23 | .create-tab { 24 | cursor: pointer !important; 25 | } 26 | 27 | .select-tab { 28 | cursor: pointer !important; 29 | 30 | &:hover { 31 | i { 32 | display: initial !important; 33 | } 34 | } 35 | } 36 | 37 | .tab-icon { 38 | opacity: .6; 39 | 40 | &:hover { 41 | opacity: 1; 42 | } 43 | } 44 | 45 | .pficon-edit.tab-icon { 46 | padding-left: 6px; 47 | } 48 | 49 | .editor-field-actions { 50 | .fa { 51 | float: right; 52 | font-size: 22px; 53 | } 54 | 55 | .pf { 56 | float: right; 57 | font-size: 16px; 58 | padding-top: 3px; 59 | padding-right: 8px; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/styles/vendor.scss: -------------------------------------------------------------------------------- 1 | //$fa-font-path: '~font-awesome/fonts'; 2 | 3 | $ff-font-path: '~@manageiq/font-fabulous/assets/fonts/font-fabulous'; 4 | @import '~@manageiq/font-fabulous/assets/stylesheets/font-fabulous'; 5 | @import '~google-code-prettify/bin/prettify.min.css'; 6 | @import '~eonasdan-bootstrap-datetimepicker/build/css/bootstrap-datetimepicker'; 7 | @import '~angular-patternfly/dist/styles/angular-patternfly.css'; 8 | -------------------------------------------------------------------------------- /src/tree-selector/index.ts: -------------------------------------------------------------------------------- 1 | import TreeSelector from './treeSelectorComponent'; 2 | import * as angular from 'angular'; 3 | 4 | module treeSelector { 5 | export const app = angular.module('miqStaticAssets.treeSelector', []); 6 | app.component('miqTreeSelector', new TreeSelector); 7 | } 8 | -------------------------------------------------------------------------------- /src/tree-selector/treeSelector.html: -------------------------------------------------------------------------------- 1 | 10 | 11 | -------------------------------------------------------------------------------- /src/tree-selector/treeSelectorComponent.ts: -------------------------------------------------------------------------------- 1 | import * as ng from 'angular'; 2 | 3 | export class TreeSelectorController { 4 | public name: string; 5 | public data: any; 6 | public persist: string; 7 | public selected: any; 8 | public selectable: any; 9 | public onSelect: Function; 10 | public lazyLoad: Function; 11 | 12 | public parsedData: any; 13 | private rendered = false; 14 | 15 | public $onChanges(changes) { 16 | // Render the tree after the data has been sent for the first time 17 | if (changes.data && !this.rendered && changes.data.currentValue !== undefined) { 18 | this.parsedData = this.parseSelectable(this.data); 19 | this.rendered = true; 20 | } 21 | } 22 | 23 | public handleLazyLoad(node) { 24 | return this.lazyLoad(node).then(data => this.parseSelectable(data)); 25 | } 26 | 27 | private matchSelectable(node) { 28 | return Object.keys(this.selectable).every(key => !!node[key].match(this.selectable[key])); 29 | } 30 | 31 | private parseSelectable(data) { 32 | return data.map(node => { 33 | const parsedData = {...node}; 34 | if(parsedData.nodes) { 35 | parsedData.nodes = this.parseSelectable(parsedData.nodes); 36 | } 37 | parsedData.selectable = this.matchSelectable(parsedData); 38 | return parsedData; 39 | }); 40 | } 41 | } 42 | 43 | export default class TreeSelector implements ng.IComponentOptions { 44 | public controller = TreeSelectorController; 45 | public template = require('./treeSelector.html'); 46 | public bindings: any = { 47 | name: '@', 48 | data: '<', 49 | persist: '@?', 50 | selected: '<', 51 | selectable: '<', 52 | onSelect: '&', 53 | lazyLoad: '&' 54 | }; 55 | } 56 | -------------------------------------------------------------------------------- /src/tree-view/index.ts: -------------------------------------------------------------------------------- 1 | import TreeView from './treeViewComponent'; 2 | import * as angular from 'angular'; 3 | module treeView { 4 | export const app = angular.module('miqStaticAssets.treeView', []); 5 | app.component('miqTreeView', new TreeView); 6 | } 7 | -------------------------------------------------------------------------------- /src/tree-view/treeViewComponent.spec.ts: -------------------------------------------------------------------------------- 1 | import TreeViewComponent from './treeViewComponent'; 2 | import * as angular from 'angular'; 3 | 4 | describe('TreeeView test', () => { 5 | let bindings; 6 | 7 | describe('controller', () => { 8 | let treeViewCtrl; 9 | 10 | beforeEach(() => { 11 | bindings = { 12 | name: 'demo-tree', 13 | data: '{}', 14 | }; 15 | angular.mock.module('miqStaticAssets.treeView'); 16 | angular.mock.inject($componentController => { 17 | treeViewCtrl = $componentController('miqTreeView', {$element: {}}, bindings); 18 | }); 19 | }); 20 | 21 | describe('findNode', () => { 22 | let items = [{x: 3}, {y: 4}, {z: 5}]; 23 | 24 | beforeEach(() => { 25 | treeViewCtrl.tree = {getNodes: () => items}; 26 | }); 27 | 28 | it('returns with the requested node when found', () => { 29 | items.forEach(item => { 30 | expect(treeViewCtrl.findNode(item)).toEqual(item); 31 | }); 32 | }); 33 | 34 | it('retuns with undefined when no node was found', () => { 35 | expect(treeViewCtrl.findNode({a: 1})).toBeUndefined(); 36 | }); 37 | }); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /src/vendor.ts: -------------------------------------------------------------------------------- 1 | import 'es6-shim'; 2 | import 'es7-shim'; 3 | import 'jquery'; 4 | import 'jquery-ui-bundle'; 5 | import 'bootstrap'; 6 | import 'bootstrap-switch'; 7 | import 'angular'; 8 | import 'angular-animate'; 9 | import 'angular-sanitize'; 10 | import 'ui-router'; 11 | import 'angular-patternfly'; 12 | import 'angular-ui-sortable'; 13 | import 'angular-dragdrop'; 14 | import 'angular-bootstrap-switch'; 15 | import 'bootstrap-select'; 16 | import 'patternfly-bootstrap-treeview'; 17 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "experimentalDecorators": true, 6 | "removeComments": false, 7 | "noEmitOnError": true, 8 | "sourceMap": true, 9 | "typeRoots" : ["./node_modules/@types"], 10 | "lib": ["es2017", "DOM", "ScriptHost"], 11 | "allowJs": true 12 | }, 13 | "files": [ 14 | "src/index.ts" 15 | ], 16 | "exclude": [ 17 | "node_modules", 18 | "dist" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "class-name": true, 4 | "curly": true, 5 | "eofline": true, 6 | "forin": true, 7 | "indent": [true, "spaces"], 8 | "label-position": true, 9 | "max-line-length": false, 10 | "member-access": true, 11 | "no-arg": true, 12 | "no-bitwise": true, 13 | "no-consecutive-blank-lines": true, 14 | "no-console": [true, 15 | "trace" 16 | ], 17 | "no-construct": true, 18 | "no-debugger": false, 19 | "no-duplicate-variable": true, 20 | "no-empty": true, 21 | "no-eval": true, 22 | "no-string-literal": false, 23 | "no-switch-case-fall-through": true, 24 | "no-trailing-whitespace": true, 25 | "no-unused-expression": false, 26 | "no-use-before-declare": true, 27 | "no-var-keyword": true, 28 | "one-line": [true, 29 | "check-open-brace", 30 | "check-whitespace", 31 | "check-catch", 32 | "check-else" 33 | ], 34 | "quotemark": [true, "single"], 35 | "radix": true, 36 | "semicolon": true, 37 | "trailing-comma": [false, {"multiline": "always", "singleline": "never"}], 38 | "triple-equals": [true, "allow-null-check"], 39 | "typedef-whitespace": [true], 40 | "variable-name": [true, "ban-keywords", "allow-leading-underscore"], 41 | "whitespace": [true, 42 | "check-decl", 43 | "check-operator", 44 | "check-type" 45 | ] 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const settings = require('./application-settings.js'); 3 | const webpack = require('webpack'), 4 | path = require('path'), 5 | NgAnnotatePlugin = require('ng-annotate-webpack-plugin'), 6 | BrowserSyncPlugin = require('browser-sync-webpack-plugin'), 7 | CopyWebpackPlugin = require('copy-webpack-plugin'), 8 | ExtractTextPlugin = require('extract-text-webpack-plugin'), 9 | HtmlWebpackPlugin = require('html-webpack-plugin'), 10 | spa = require('browser-sync-spa'); 11 | 12 | module.exports = (env) => { 13 | let production = env && env.NODE_ENV === 'production'; 14 | let test = env && env.test; 15 | 16 | const appEntry = { 17 | [settings.appName]: [ 18 | settings.sassEntryPoint, 19 | settings.tsEntryPoint, 20 | ].concat(settings.tsModules), 21 | 22 | 'demo-app': [ 23 | './demo/index.ts', 24 | './demo/styles/demo-app.scss', 25 | ], 26 | }; 27 | 28 | const plugins = [ 29 | new CopyWebpackPlugin([ 30 | {from: __dirname + '/demo/data', to: 'data'}, 31 | {from: __dirname + '/demo/assets', to: 'assets'}, 32 | ]), 33 | new HtmlWebpackPlugin({ 34 | title: 'ManageIQ Common Components', 35 | template: 'demo/template-index.ejs', // Load a custom template 36 | inject: 'body', 37 | }), 38 | production ? new webpack.optimize.CommonsChunkPlugin({ 39 | name: settings.appName, 40 | filename: settings.javascriptFolder + '/' + settings.appName + settings.isMinified(production), 41 | }) : undefined, 42 | new ExtractTextPlugin(settings.stylesheetPath), 43 | new NgAnnotatePlugin({add: true}), 44 | production ? new webpack.optimize.UglifyJsPlugin({ 45 | warnings: false, 46 | minimize: true, 47 | drop_console: true, 48 | }) : undefined, 49 | ].filter(p => !!p); 50 | 51 | return { 52 | context: __dirname, 53 | entry: appEntry, 54 | output: { 55 | path: settings.outputFolder, 56 | publicPath: '.', 57 | filename: settings.javascriptFolder + "/[name]" + settings.isMinified(production), 58 | }, 59 | resolve: { 60 | extensions: ['.ts', '.js'], 61 | }, 62 | stats: { 63 | colors: true, 64 | reasons: true, 65 | }, 66 | devtool: !production && 'source-map', 67 | module: { 68 | rules: [ 69 | { 70 | test: /\.ts?$/, 71 | exclude: /(node_modules|libs)/, 72 | loader: 'tslint-loader', 73 | enforce: 'pre', 74 | options: {emitErrors: true}, 75 | }, 76 | { 77 | test: /\.ts$/, 78 | exclude: /(node_modules|libs)/, 79 | loader: 'awesome-typescript-loader', 80 | }, 81 | { 82 | test: /\.html$/, 83 | exclude: /(node_modules|libs|dist|tsd)/, 84 | loader: 'raw-loader', 85 | }, 86 | { 87 | test: /\.scss/, 88 | exclude: /(node_modules|lib)/, 89 | loader: ExtractTextPlugin.extract({ 90 | fallbackLoader: 'style-loader', 91 | loader: 'css-loader!sass-loader', 92 | }), 93 | }, 94 | { 95 | test: /\.css$/, 96 | loader: ExtractTextPlugin.extract({ 97 | fallbackLoader: 'style-loader', 98 | loader: 'css-loader!sass-loader', 99 | }), 100 | }, 101 | { 102 | test: /\.(png|jpg|gif|svg|woff|ttf|eot)/, 103 | loader: 'url-loader?limit=20480', 104 | }, 105 | { 106 | test: /\.json$/, 107 | loader: 'json-loader', 108 | }, 109 | ], 110 | }, 111 | plugins: [ 112 | ...plugins, 113 | new BrowserSyncPlugin({ 114 | host: 'localhost', 115 | port: 4000, 116 | server: {baseDir: [__dirname + settings.distFolder]}, 117 | open: !test, 118 | middleware: [ 119 | { 120 | route: "/data", 121 | handle: function (req, res, next) { 122 | req.method = 'GET'; 123 | return next(); 124 | }, 125 | }, 126 | ], 127 | }, { 128 | use: spa({ 129 | selector: '[ng-app]', 130 | }), 131 | }), 132 | ], 133 | externals: { 134 | 'angular': 'angular', 135 | 'lodash': '_', 136 | '__': '__', 137 | }, 138 | watchOptions: { 139 | ignored: ['**/.*.sw[po]'], 140 | }, 141 | }; 142 | }; 143 | -------------------------------------------------------------------------------- /webpack.vendor.config.js: -------------------------------------------------------------------------------- 1 | const settings = require('./application-settings.js'); 2 | const production = process.argv.indexOf('--production') !== -1; 3 | 4 | var webpack = require('webpack'), 5 | ExtractTextPlugin = require('extract-text-webpack-plugin'), 6 | path = require('path'), 7 | plugins = [ 8 | !production ? undefined : new webpack.ProvidePlugin({ 9 | $: "jquery", 10 | jQuery: "jquery", 11 | "window.jQuery": "jquery", 12 | "angular": "angular", 13 | "_": "lodash" 14 | }), 15 | new ExtractTextPlugin(settings.stylesheetPath) 16 | ].filter(p => !!p); 17 | 18 | module.exports = { 19 | context: __dirname, 20 | entry: { 21 | vendor: ['./src/vendor.ts', settings.sassRootFolder + '/vendor.scss'] 22 | }, 23 | plugins: plugins, 24 | module: { 25 | rules: [ 26 | {enforce: 'pre', test: /\.ts?$/, loader: 'tslint-loader', exclude: /(node_modules|libs)/}, 27 | { test: require.resolve('jquery'), loader: 'expose-loader?jQuery!expose-loader?$' }, 28 | {test: /\.ts$/, loaders: ['awesome-typescript-loader'], exclude: /(node_modules|libs)/}, 29 | {test: /\.html$/, loader: 'raw-loader', exclude: /(node_modules|libs|dist|tsd)/}, 30 | {test: /\.(png|jpg|gif|svg|woff|ttf|eot)/, loader: 'url-loader?limit=20480'}, 31 | // stylesheets 32 | {test: /\.scss/, exclude: /(node_modules|lib)/, loader: ExtractTextPlugin.extract( 33 | { 34 | fallbackLoader: 'style-loader', 35 | loader: 'css-loader!sass-loader' 36 | } 37 | )}, 38 | {test: /\.css$/, loader: ExtractTextPlugin.extract( 39 | { 40 | fallbackLoader: 'style-loader', 41 | loader: 'css-loader!sass-loader' 42 | } 43 | )}, 44 | { test: /\.(woff|ttf|eot|svg)(\?v=[a-z0-9]\.[a-z0-9]\.[a-z0-9])?$/, loader: 'url-loader?limit=100000' }, 45 | {test: /\.json$/, loader: 'json-loader'} 46 | ] 47 | }, 48 | output: { 49 | path: settings.outputFolder, 50 | publicPath: '.', 51 | filename: 'js/[name].js' 52 | } 53 | }; 54 | --------------------------------------------------------------------------------