├── .editorconfig ├── .eslintrc ├── .gitattributes ├── .github ├── actions │ └── bump-version │ │ ├── action.yml │ │ └── index.js └── workflows │ ├── ci.yml │ └── release.yml ├── .gitignore ├── .husky ├── commit-msg ├── common.sh └── pre-commit ├── .jshintrc ├── .releaserc.json ├── .reuse └── dep5 ├── CONTRIBUTING.md ├── Gruntfile.js ├── LICENSE.txt ├── LICENSES └── Apache-2.0.txt ├── README.md ├── app ├── html │ ├── devtools │ │ └── index.html │ ├── panel │ │ └── ui5 │ │ │ └── index.html │ └── popup │ │ └── index.html ├── images │ ├── arrows │ │ ├── arrow-circle-hover.png │ │ ├── arrow-circle.png │ │ ├── arrow-down-white.png │ │ ├── arrow-down.png │ │ ├── arrow-right-white.png │ │ └── arrow-right.png │ ├── close.png │ ├── icon-128.png │ ├── icon-16.png │ ├── icon-19.png │ ├── icon-38.png │ ├── icon-48.png │ ├── largeIcons.svg │ ├── mediumIcons.svg │ ├── smallIcons.svg │ └── treeoutlineTriangles.svg ├── manifest.json ├── scripts │ ├── background │ │ └── main.js │ ├── content │ │ ├── detectUI5.js │ │ └── main.js │ ├── devtools │ │ └── panel │ │ │ ├── initialize.js │ │ │ └── ui5 │ │ │ └── main.js │ ├── injected │ │ ├── detectUI5.js │ │ └── main.js │ ├── modules │ │ ├── background │ │ │ ├── ContextMenu.js │ │ │ └── pageAction.js │ │ ├── content │ │ │ └── highLighter.ts │ │ ├── injected │ │ │ ├── applicationUtils.js │ │ │ ├── controlUtils.js │ │ │ ├── message.js │ │ │ ├── rightClickHandler.ts │ │ │ └── ui5inspector.js │ │ ├── ui │ │ │ ├── ControlTree.js │ │ │ ├── ControllerDetailView.js │ │ │ ├── DataView.js │ │ │ ├── FrameSelect.js │ │ │ ├── JSONFormatter.js │ │ │ ├── ODataDetailView.js │ │ │ ├── ODataMasterView.js │ │ │ ├── OElementsRegistryMasterView.js │ │ │ ├── SplitContainer.js │ │ │ ├── TabBar.js │ │ │ ├── XMLDetailView.js │ │ │ ├── datagrid │ │ │ │ ├── DOMExtension.js │ │ │ │ ├── DataGrid.js │ │ │ │ └── UIUtils.js │ │ │ └── helpers │ │ │ │ └── DataViewHelper.js │ │ └── utils │ │ │ ├── EntriesLog.js │ │ │ ├── ODataNode.js │ │ │ ├── multipartmixed2har.js │ │ │ └── utils.js │ └── popup │ │ └── popup.ts ├── styles │ └── less │ │ ├── modules │ │ ├── ControlTree.less │ │ ├── ControllerDetailView.less │ │ ├── DataGrid.less │ │ ├── DataView.less │ │ ├── ElementsRegistry.less │ │ ├── FrameSelect.less │ │ ├── JSONView.less │ │ ├── ODataView.less │ │ ├── SplitContainer.less │ │ ├── TabBar.less │ │ └── XMLView.less │ │ ├── popup.less │ │ └── themes │ │ ├── base │ │ ├── base.less │ │ ├── colors.less │ │ ├── devtools.less │ │ ├── globals.less │ │ ├── reset.less │ │ └── variables.less │ │ ├── dark │ │ └── dark.less │ │ └── light │ │ └── light.less └── vendor │ ├── ToolsAPI.js │ ├── ace.js │ ├── ext-searchbox.js │ ├── mode-json.js │ ├── mode-xml.js │ ├── theme-chrome.js │ ├── theme-vibrant_ink.js │ └── vkbeautify.js ├── commitlint.config.js ├── docs ├── bugreport_template.txt └── guidelines.md ├── global.d.ts ├── grunt ├── aliases.js ├── browserify.js ├── clean.js ├── compress.js ├── connect.js ├── copy.js ├── coveralls.js ├── eslint.js ├── jscs.js ├── jshint.js ├── karma.js ├── less.js ├── replace.js ├── usebanner.js └── watch.js ├── karma.bootstrap.js ├── karma.conf.js ├── package-lock.json ├── package.json ├── pom.xml ├── scripts └── update-version.js ├── tests ├── modules │ ├── background │ │ ├── ContextMenu.spec.js │ │ └── pageAction.spec.js │ ├── content │ │ └── highLighter.spec.js │ ├── injected │ │ ├── applicationUtils.spec.js │ │ ├── message.spec.js │ │ ├── rightClickHandler.spec.js │ │ └── ui5inspector.spec.js │ ├── ui │ │ ├── ControlTree.spec.js │ │ ├── DataView.spec.js │ │ ├── JSONFormatter.spec.js │ │ ├── Splitter.spec.js │ │ ├── TabBar.spec.js │ │ └── helpers │ │ │ └── DataViewHelper.spec.js │ └── utils │ │ └── utils.spec.js └── styles │ └── themes │ ├── dark │ └── dark.css │ └── light │ └── light.css └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | indent_style = space 10 | indent_size = 4 11 | end_of_line = lf 12 | charset = utf-8 13 | trim_trailing_whitespace = true 14 | insert_final_newline = true 15 | 16 | [*.md] 17 | trim_trailing_whitespace = false 18 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parserOptions": { 3 | "ecmaVersion": 2017 4 | }, 5 | "env": { 6 | "es6": true 7 | }, 8 | "rules": { 9 | "no-empty": [ 10 | 1, 11 | { 12 | "allowEmptyCatch": true 13 | } 14 | ], 15 | "no-implicit-coercion": [ 16 | 2, 17 | { 18 | "boolean": false, 19 | "string": true, 20 | "number": false 21 | } 22 | ], 23 | "no-with": 2, 24 | "brace-style": [ 25 | 1, 26 | "1tbs", 27 | { 28 | "allowSingleLine": true 29 | } 30 | ], 31 | "no-mixed-spaces-and-tabs": 2, 32 | "no-multiple-empty-lines": 1, 33 | "no-multi-str": 2, 34 | "one-var": [2, "never"], 35 | "key-spacing": [ 36 | 1, 37 | { 38 | "beforeColon": false, 39 | "afterColon": true 40 | } 41 | ], 42 | "space-unary-ops": [ 43 | 2, 44 | { 45 | "words": false, 46 | "nonwords": false 47 | } 48 | ], 49 | "no-trailing-spaces": 2, 50 | "yoda": [2, "never"], 51 | "camelcase": [ 52 | 2, 53 | { 54 | "properties": "never" 55 | } 56 | ], 57 | "comma-style": [2, "last"], 58 | "curly": [2, "all"], 59 | "dot-notation": 2, 60 | "eol-last": 2, 61 | "operator-linebreak": [2, "after"], 62 | "wrap-iife": 2, 63 | "space-infix-ops": 2, 64 | "keyword-spacing": [2, {}], 65 | "spaced-comment": [1, "always"], 66 | "space-before-blocks": [2, "always"], 67 | "consistent-this": [1, "that"], 68 | "linebreak-style": [2, "unix"], 69 | "quotes": [2, "single"] 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.* -lf 2 | text eol=lf 3 | -------------------------------------------------------------------------------- /.github/actions/bump-version/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Bump version' 2 | description: 'Bump version in files' 3 | runs: 4 | using: 'node16' 5 | main: 'index.js' 6 | -------------------------------------------------------------------------------- /.github/actions/bump-version/index.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const core = require('@actions/core'); 3 | const github = require('@actions/github'); 4 | const exec = require('@actions/exec'); 5 | 6 | const run = async () => { 7 | core.info('Setup octokit'); 8 | const octokit = github.getOctokit(process.env.GITHUB_TOKEN); 9 | const { owner, repo } = github.context.repo; 10 | 11 | core.info('Fetch latest release info'); 12 | const latestRelease = await octokit.rest.repos.getLatestRelease({ 13 | owner, 14 | repo, 15 | }); 16 | 17 | core.info('Set variables'); 18 | const version = latestRelease.data.tag_name.slice(1); 19 | 20 | core.info('Update files version field'); 21 | await exec.exec('node', [path.join(process.env.GITHUB_WORKSPACE, './scripts/update-version.js'), version]); 22 | 23 | await exec.exec('git', ['config', '--global', 'user.name', 'github-actions']); 24 | await exec.exec('git', ['config', '--global', 'user.email', 'actions@users.noreply.github.com']); 25 | await exec.exec('git', ['checkout', '-b', `bump-version-${version}`]); 26 | await exec.exec('git', ['commit', '-am', `chore: release ${version}`]); 27 | await exec.exec('git', ['push', '-u', 'origin', `bump-version-${version}`]); 28 | await exec.exec('gh', ['pr', 'create', '--fill']); 29 | }; 30 | 31 | run() 32 | .then(() => core.info('Updated files version successfully')) 33 | .catch(error => core.setFailed(error.message)); 34 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to automate the creation of the build artifact 2 | name: CI 3 | # Controls when the workflow will run 4 | on: 5 | pull_request: 6 | branches: [ master ] 7 | # Allows you to run this workflow manually from the Actions tab 8 | workflow_dispatch: 9 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 10 | jobs: 11 | test: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | - name: Install NPM dependencies 16 | run: npm ci 17 | - name: Run tests 18 | run: grunt test 19 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to automate the creation of the build artifact 2 | name: Release 3 | # Controls when the workflow will run 4 | on: 5 | schedule: 6 | - cron: "0 9 15 * *" 7 | # Allows you to run this workflow manually from the Actions tab 8 | workflow_dispatch: 9 | jobs: 10 | release: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | - name: Install NPM dependencies 15 | run: npm ci 16 | - name: Run tests 17 | run: grunt test 18 | # Release using semantic-relaease if there are new smenatic commits 19 | - name: Release 20 | env: 21 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 22 | run: npx semantic-release 23 | - name: Bump Version 24 | env: 25 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 26 | uses: ./.github/actions/bump-version 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # IDE 2 | .idea 3 | 4 | # NodeJS 5 | node_modules 6 | npm-debug.log 7 | 8 | # Dependencies 9 | 10 | 11 | # Temporary folders 12 | temp 13 | .tmp 14 | tests/reports 15 | tempDist 16 | 17 | # App files 18 | **/styles/*.css 19 | FRONTEND.md 20 | 21 | # Distribution folders 22 | dist 23 | package 24 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | . "$(dirname "$0")/common.sh" 4 | 5 | npm run husky:commit-msg -------------------------------------------------------------------------------- /.husky/common.sh: -------------------------------------------------------------------------------- 1 | command_exists () { 2 | command -v "$1" >/dev/null 2>&1 3 | } 4 | 5 | # Windows 10, Git Bash and Yarn workaround 6 | if command_exists winpty && test -t 1; then 7 | exec < /dev/tty 8 | fi -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | . "$(dirname "$0")/common.sh" 4 | 5 | npm run husky:pre-commit -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | // {int} Maximum error before stopping 3 | "maxerr": 100, 4 | 5 | // ================================================================================ 6 | // Enforcing 7 | // ================================================================================ 8 | 9 | // true: Prohibit bitwise operators (&, |, ^, etc.) 10 | "bitwise": false, 11 | 12 | // true: Identifiers must be in camelCase 13 | "camelcase": true, 14 | 15 | // true: Require {} for every new block or scope 16 | "curly": true, 17 | 18 | // true: Require triple equals (===) for comparison 19 | "eqeqeq": true, 20 | 21 | // true: Require immediate invocations to be wrapped in parens e.g. `(function () { } ());` 22 | "immed": true, 23 | 24 | // {int} Number of spaces to use for indentation. 25 | "indent": 4, 26 | 27 | // true: Require variables/functions to be defined before being used 28 | "latedef": true, 29 | 30 | // true: Require capitalization of all constructor functions e.g. `new F()` 31 | "newcap": true, 32 | 33 | // true: Prohibit use of `arguments.caller` and `arguments.callee` 34 | "noarg": true, 35 | 36 | // true: Prohibit use of empty blocks 37 | "noempty": true, 38 | 39 | // true: Prohibit use of constructors for side-effects (without assignment) 40 | "nonew": false, 41 | 42 | // true: Prohibit use of `++` & `--` 43 | "plusplus": false, 44 | 45 | // Quotation mark consistency: 46 | "quotmark": "single", 47 | 48 | // true: Require all non-global variables to be declared (prevents global leaks) 49 | "undef": true, 50 | 51 | // true: Requires all functions run in ES5 Strict Mode 52 | "strict": false, 53 | 54 | // {int} Max number of formal params allowed per function 55 | "maxparams": 4, 56 | 57 | // {int} Max depth of nested blocks (within functions) 58 | "maxdepth": 3, 59 | 60 | // {int} Max number statements per function 61 | "maxstatements": 30, 62 | 63 | // {int} Max cyclomatic complexity per function. The cyclomatic complexity of a section of source code is the count 64 | // of the number of linearly independent paths through the source code. 65 | "maxcomplexity": 13, 66 | 67 | // ================================================================================ 68 | // Relaxing 69 | // ================================================================================ 70 | 71 | // true: Tolerate Automatic Semicolon Insertion (no semicolons) 72 | "asi": false, 73 | 74 | // true: Tolerate assignments where comparisons would be expected 75 | "boss": false, 76 | 77 | // true: Allow debugger statements e.g. browser breakpoints. 78 | "debug": true, 79 | 80 | // true: Tolerate use of `== null` 81 | "eqnull": false, 82 | 83 | // true: Allow ES5 syntax (ex: getters and setters) 84 | "es5": false, 85 | 86 | // true: Allow ES.next (ES6) syntax (ex: `const`) 87 | "esnext": true, 88 | 89 | // true: Allow Mozilla specific syntax (extends and overrides esnext features)(ex: `for each`, multiple try/catch, function expression…) 90 | "moz": false, 91 | 92 | // true: Tolerate use of `eval` and `new Function()` 93 | "evil": false, 94 | 95 | // true: Tolerate `ExpressionStatement` as Programs 96 | "expr": true, 97 | 98 | // true: Tolerate defining variables inside control statements" 99 | "funcscope": false, 100 | 101 | // true: Allow global "use strict" (also enables 'strict') 102 | "globalstrict": false, 103 | 104 | // true: Tolerate using the `__iterator__` property 105 | "iterator": false, 106 | 107 | // true: Tolerate omitting a semicolon for the last statement of a 1-line block 108 | "lastsemic": false, 109 | 110 | // true: Tolerate possibly unsafe line breakings 111 | "laxbreak": false, 112 | 113 | // true: Tolerate comma-first style coding 114 | "laxcomma": false, 115 | 116 | // true: Tolerate functions being defined in loops 117 | "loopfunc": false, 118 | 119 | // true: Tolerate multi-line strings 120 | "multistr": false, 121 | 122 | // true: Tolerate using the `__proto__` property 123 | "proto": false, 124 | 125 | // true: Tolerate script-targeted URLs 126 | "scripturl": false, 127 | 128 | // true: Allows re-define variables later in code e.g. `var x=1; x=2;` 129 | "shadow": false, 130 | 131 | // true: Tolerate using `[]` notation when it can still be expressed in dot notation 132 | "sub": false, 133 | 134 | // true: Tolerate `new function () { ... };` and `new Object;` 135 | "supernew": false, 136 | 137 | // true: Tolerate using this in a non-constructor function 138 | "validthis": false, 139 | 140 | // ================================================================================ 141 | // Environments 142 | // ================================================================================ 143 | 144 | // Web Browser (window, document, etc) 145 | "browser": true, 146 | 147 | // Development/debugging (alert, confirm, etc) 148 | "devel": true, 149 | 150 | // jQuery 151 | "jquery": true, 152 | 153 | // Node.js 154 | "node": true, 155 | 156 | // Mocha.js 157 | "mocha": true, 158 | 159 | // Additional predefined global variables 160 | "globals": { 161 | "chrome": true, 162 | "sap": true, 163 | "expect": true, 164 | "sinon": true, 165 | "should": true, 166 | "Event": true, 167 | "ace": true 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /.releaserc.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "@semantic-release/commit-analyzer", 4 | "@semantic-release/release-notes-generator", 5 | "@semantic-release/changelog", 6 | [ 7 | "@semantic-release/exec", 8 | { 9 | "prepareCmd": "node ./scripts/update-version.js ${nextRelease.version} && npm i" 10 | } 11 | ], 12 | [ 13 | "@semantic-release/github", 14 | { 15 | "assets": [ 16 | { 17 | "path": "package/ui5inspector.zip", 18 | "name": "ui5inspector-v${nextRelease.version}.zip" 19 | } 20 | ] 21 | } 22 | ] 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /.reuse/dep5: -------------------------------------------------------------------------------- 1 | Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: ui5-inspector 3 | Upstream-Contact: SAP OpenUI5 4 | Source: https://github.com/SAP/ui5-inspector 5 | Disclaimer: The code in this project may include calls to APIs (“API Calls”) of 6 | SAP or third-party products or services developed outside of this project 7 | (“External Products”). 8 | “APIs” means application programming interfaces, as well as their respective 9 | specifications and implementing code that allows software to communicate with 10 | other software. 11 | API Calls to External Products are not licensed under the open source license 12 | that governs this project. The use of such API Calls and related External 13 | Products are subject to applicable additional agreements with the relevant 14 | provider of the External Products. In no event shall the open source license 15 | that governs this project grant any rights in or to any External Products,or 16 | alter, expand or supersede any terms of the applicable additional agreements. 17 | If you have a valid license agreement with SAP for the use of a particular SAP 18 | External Product, then you may make use of any API Calls included in this 19 | project’s code for that SAP External Product, subject to the terms of such 20 | license agreement. If you do not have a valid license agreement for the use of 21 | a particular SAP External Product, then you may only make use of any API Calls 22 | in this project for that SAP External Product for your internal, non-productive 23 | and non-commercial test and evaluation of such API Calls. Nothing herein grants 24 | you any rights to use or access any SAP External Product, or provide any third 25 | parties the right to use of access any SAP External Product, through API Calls. 26 | 27 | Files: * 28 | Copyright: 2015 SAP SE or an SAP affiliate company and ui5-inspector contributors 29 | License: Apache-2.0 30 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * SAP 3 | * (c) Copyright 2015 SAP SE or an SAP affiliate company. 4 | * Licensed under the Apache License, Version 2.0 - see LICENSE.txt. 5 | */ 6 | 7 | 'use strict'; 8 | 9 | /** 10 | * Grunt configuration file. 11 | * @param {Object} grunt 12 | */ 13 | module.exports = function (grunt) { 14 | 15 | require('time-grunt')(grunt); 16 | 17 | require('load-grunt-config')(grunt, { 18 | // Data passed into config. Can use with <%= *** %> 19 | data: { 20 | app: 'app', 21 | dist: 'dist', 22 | tests: 'tests', 23 | bower: 'bower_components', 24 | grunt: 'grunt', 25 | tempDist: 'tempDist' 26 | } 27 | }); 28 | 29 | }; 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![OpenUI5](http://openui5.org/images/OpenUI5_new_big_side.png) 2 | 3 | [![Built with Grunt](https://cdn.gruntjs.com/builtwith.png)](http://gruntjs.com/) 4 | [![Build Status](https://travis-ci.org/SAP/ui5-inspector.svg?branch=master)](https://travis-ci.org/SAP/ui5-inspector) 5 | [![Coverage Status](https://coveralls.io/repos/SAP/ui5-inspector/badge.svg?branch=master&service=github)](https://coveralls.io/github/SAP/ui5-inspector?branch=master) 6 | [![REUSE status](https://api.reuse.software/badge/github.com/SAP/ui5-inspector)](https://api.reuse.software/info/github.com/SAP/ui5-inspector) 7 | 8 | # About UI5 Inspector 9 | 10 | UI5 Inspector is a standard Chrome or Edge extension for debugging and getting to know UI5 applications. 11 | 12 | It's free and open source: UI5 Inspector is licensed under the Apache License, Version 2.0. 13 | See [LICENSE.txt](https://github.com/SAP/ui5-inspector/blob/master/LICENSE.txt) for more information. 14 | 15 | ## Direct download and use 16 | 17 | The latest released version can be downloaded and installed as follows: 18 | 19 | 1. Download zip file from [Releases](https://github.com/SAP/ui5-inspector/releases) 20 | 2. Unpack to a directory 21 | 3. In Chrome open as url: `chrome://extensions/`. Alternatively, you can access `edge://extensions/` when in Edge. The extensions page is also reachable via the browser's menu. 22 | 4. Check “Developer mode” setting and then choose "Load unpacked extension..." 23 | 5. From the newly opened window select the folder to which the zip file was unpacked 24 | 6. Restart Chrome or Edge 25 | 7. Open a OpenUI5/SAPUI5 based web application like: [https://openui5.hana.ondemand.com/explored.html](https://openui5.hana.ondemand.com/explored.html) 26 | 27 | ## Local development and use 28 | 29 | You can get the source code locally and contribute to the project. 30 | 31 | 1. Clone the project locally: `git clone git@github.com:SAP/ui5-inspector.git` 32 | 2. Install dependencies with the following commands: `npm install` 33 | 3. In Chrome open as url: `chrome://extensions/` 34 | 4. Check “Developer mode” and then click "Load unpacked extension..." 35 | 5. From the newly opened window select the **dist** folder from the locally cloned project 36 | 6. Restart Chrome 37 | 7. Open a OpenUI5/SAPUI5 based web application like: [https://openui5.hana.ondemand.com/explored.html](https://openui5.hana.ondemand.com/explored.html) 38 | 39 | ## License 40 | 41 | [![Apache 2](https://img.shields.io/badge/license-Apache%202-blue.svg)](./LICENSE.txt) 42 | -------------------------------------------------------------------------------- /app/html/devtools/index.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/html/popup/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |   11 | 16 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /app/images/arrows/arrow-circle-hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SAP/ui5-inspector/d2287991be858b382eaaeecb47a764b3b3898923/app/images/arrows/arrow-circle-hover.png -------------------------------------------------------------------------------- /app/images/arrows/arrow-circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SAP/ui5-inspector/d2287991be858b382eaaeecb47a764b3b3898923/app/images/arrows/arrow-circle.png -------------------------------------------------------------------------------- /app/images/arrows/arrow-down-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SAP/ui5-inspector/d2287991be858b382eaaeecb47a764b3b3898923/app/images/arrows/arrow-down-white.png -------------------------------------------------------------------------------- /app/images/arrows/arrow-down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SAP/ui5-inspector/d2287991be858b382eaaeecb47a764b3b3898923/app/images/arrows/arrow-down.png -------------------------------------------------------------------------------- /app/images/arrows/arrow-right-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SAP/ui5-inspector/d2287991be858b382eaaeecb47a764b3b3898923/app/images/arrows/arrow-right-white.png -------------------------------------------------------------------------------- /app/images/arrows/arrow-right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SAP/ui5-inspector/d2287991be858b382eaaeecb47a764b3b3898923/app/images/arrows/arrow-right.png -------------------------------------------------------------------------------- /app/images/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SAP/ui5-inspector/d2287991be858b382eaaeecb47a764b3b3898923/app/images/close.png -------------------------------------------------------------------------------- /app/images/icon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SAP/ui5-inspector/d2287991be858b382eaaeecb47a764b3b3898923/app/images/icon-128.png -------------------------------------------------------------------------------- /app/images/icon-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SAP/ui5-inspector/d2287991be858b382eaaeecb47a764b3b3898923/app/images/icon-16.png -------------------------------------------------------------------------------- /app/images/icon-19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SAP/ui5-inspector/d2287991be858b382eaaeecb47a764b3b3898923/app/images/icon-19.png -------------------------------------------------------------------------------- /app/images/icon-38.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SAP/ui5-inspector/d2287991be858b382eaaeecb47a764b3b3898923/app/images/icon-38.png -------------------------------------------------------------------------------- /app/images/icon-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SAP/ui5-inspector/d2287991be858b382eaaeecb47a764b3b3898923/app/images/icon-48.png -------------------------------------------------------------------------------- /app/images/treeoutlineTriangles.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 3, 3 | "name": "UI5 Inspector", 4 | "version": "1.7.0", 5 | "background": { 6 | "service_worker": "/scripts/background/main.js" 7 | }, 8 | "content_scripts": [ 9 | { 10 | "js": [ 11 | "/scripts/content/detectUI5.js" 12 | ], 13 | "all_frames": true, 14 | "matches": [ 15 | "http://*/*", 16 | "https://*/*" 17 | ] 18 | } 19 | ], 20 | "content_security_policy": { 21 | "extension_pages": "default-src 'self'; img-src 'self' data:; style-src 'unsafe-inline';" 22 | }, 23 | "description": "With the UI5 Inspector, you can easily debug and support your OpenUI5 or SAPUI5-based apps.", 24 | "devtools_page": "/html/devtools/index.html", 25 | "icons": { 26 | "16": "/images/icon-16.png", 27 | "128": "/images/icon-128.png" 28 | }, 29 | "action": { 30 | "default_icon": { 31 | "19": "/images/icon-19.png", 32 | "38": "/images/icon-38.png" 33 | }, 34 | "default_popup": "/html/popup/index.html" 35 | }, 36 | "permissions": [ 37 | "scripting", 38 | "contextMenus", 39 | "activeTab" 40 | ], 41 | "host_permissions": [ 42 | "http://*/*", 43 | "https://*/*" 44 | ], 45 | "web_accessible_resources": [ 46 | { 47 | "matches": [ 48 | "http://*/*", 49 | "https://*/*" 50 | ], 51 | "resources": [ 52 | "/scripts/injected/*.js", 53 | "/vendor/ToolsAPI.js", 54 | "/vendor/ace.js", 55 | "/vendor/ext-searchbox.js", 56 | "/vendor/mode-json.js", 57 | "/vendor/mode-xml.js", 58 | "/vendor/theme-chrome.js", 59 | "/vendor/theme-vibrant_ink.js", 60 | "/vendor/vkbeautify.js", 61 | "/modules/utils/multipartmixed2har.js" 62 | ] 63 | } 64 | ] 65 | } -------------------------------------------------------------------------------- /app/scripts/background/main.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | var utils = require('../modules/utils/utils.js'); 5 | var ContextMenu = require('../modules/background/ContextMenu.js'); 6 | var pageAction = require('../modules/background/pageAction.js'); 7 | 8 | var contextMenu = new ContextMenu({ 9 | title: 'Inspect UI5 control', 10 | id: 'context-menu', 11 | contexts: ['all'] 12 | }); 13 | 14 | /** 15 | * This method will be fired when an instance is clicked. The idea is to be overwritten from the instance. 16 | * @param {Object} info - Information sent when a context menu item is clicked. Check chrome.contextMenus.onClicked. 17 | * @param {Object} tab - The details of the tab where the click took place. 18 | */ 19 | contextMenu.onClicked = function (info, tab) { 20 | utils.sendToAll({ 21 | action: 'do-context-menu-control-select', 22 | target: contextMenu._rightClickTarget, 23 | // specify the frame in which the user clicked 24 | frameId: info.frameId 25 | }); 26 | }; 27 | 28 | // Name space for message handler functions. 29 | var messageHandler = { 30 | 31 | /** 32 | * Create an icons with hover information inside the address bar. 33 | * @param {Object} message 34 | * @param {Object} messageSender 35 | */ 36 | 'on-ui5-detected': function (message, messageSender) { 37 | var framework = message.framework; 38 | 39 | if (message.isVersionSupported === true) { 40 | pageAction.create({ 41 | version: framework.version, 42 | framework: framework.name, 43 | tabId: messageSender.tab.id 44 | }); 45 | 46 | pageAction.enable(); 47 | } 48 | }, 49 | 50 | /** 51 | * Handler for UI5 none detection on the current inspected page. 52 | * @param {Object} message 53 | */ 54 | 'on-ui5-not-detected': function (message) { 55 | pageAction.disable(); 56 | }, 57 | 58 | /** 59 | * Inject script into the inspected page. 60 | * @param {Object} message 61 | */ 62 | 'do-script-injection': function (message) { 63 | const frameId = message.frameId; 64 | chrome.windows.getCurrent().then(w => { 65 | chrome.tabs.query({ active: true, windowId: w.id }).then(tabs => { 66 | const target = { 67 | tabId: tabs[0].id 68 | }; 69 | // inject the script only into the frame 70 | // specified in the request from the devTools UI5 panel script; 71 | // If no frameId specified, the script will be injected into the main frame 72 | if (frameId !== undefined) { 73 | target.frameIds = [message.frameId]; 74 | } 75 | chrome.scripting.executeScript({ 76 | target, 77 | files: [message.file] 78 | }); 79 | }); 80 | }); 81 | }, 82 | 83 | /** 84 | * Set the element that was clicked with the right button of the mouse. 85 | * @param {Object} message 86 | */ 87 | 'on-right-click': function (message) { 88 | contextMenu.setRightClickTarget(message.target); 89 | }, 90 | 91 | /** 92 | * Create the button for the context menu, when the user switches to the "UI5" panel. 93 | * @param {Object} message 94 | */ 95 | 'on-ui5-devtool-show': function (message) { 96 | contextMenu.create(); 97 | }, 98 | 99 | /** 100 | * Delete the button for the context menu, when the user switches away to the "UI5" panel. 101 | * @param {Object} message 102 | */ 103 | 'on-ui5-devtool-hide': function (message) { 104 | contextMenu.removeAll(); 105 | }, 106 | 107 | 'do-ping-frames': function (message, messageSender) { 108 | var frameIds = message.frameIds; 109 | var liveFrameIds = []; 110 | var pingFrame = function (i) { 111 | if (i >= frameIds.length) { 112 | // no more frameId to ping 113 | // => done with pinging each frame 114 | // => send a message [to the devTools UI5 panel] 115 | // with the updated list of 'live' frame ids 116 | chrome.runtime.sendMessage(messageSender.id, { 117 | action: 'on-ping-frames', 118 | frameIds: liveFrameIds 119 | }); 120 | return; 121 | } 122 | 123 | var frameId = frameIds[i]; 124 | // ping the next frame 125 | // from the frameIds list 126 | utils.sendToAll({ 127 | action: 'do-ping', 128 | frameId: frameId 129 | }, function (isAlive) { 130 | if (isAlive) { 131 | liveFrameIds.push(frameId); 132 | } 133 | pingFrame(i + 1); 134 | }); 135 | }; 136 | 137 | pingFrame(0); 138 | } 139 | }; 140 | 141 | chrome.runtime.onMessage.addListener(function (request, messageSender, sendResponse) { 142 | // Resolve incoming messages 143 | utils.resolveMessage({ 144 | message: request, 145 | messageSender: messageSender, 146 | sendResponse: sendResponse, 147 | actions: messageHandler 148 | }); 149 | 150 | utils.sendToAll(request); 151 | }); 152 | 153 | chrome.runtime.onInstalled.addListener(() => { 154 | // Page actions are disabled by default and enabled on select tabs 155 | chrome.action.disable(); 156 | }); 157 | }()); 158 | -------------------------------------------------------------------------------- /app/scripts/content/detectUI5.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | var utils = require('../modules/utils/utils.js'); 5 | 6 | // Create a port with background page for continuous message communication 7 | var port = utils.getPort(); 8 | 9 | // Inject a script file in the current page 10 | var script = document.createElement('script'); 11 | script.src = chrome.runtime.getURL('/scripts/injected/detectUI5.js'); 12 | document.head.appendChild(script); 13 | 14 | /** 15 | * Delete the injected file, when it is loaded. 16 | */ 17 | script.onload = function () { 18 | script.parentNode.removeChild(script); 19 | }; 20 | 21 | // Listen for messages from the background page 22 | port.onMessage(function (message) { 23 | // Resolve incoming messages 24 | if (message.action === 'do-ui5-detection') { 25 | document.dispatchEvent(new Event('do-ui5-detection-injected')); 26 | } 27 | }); 28 | 29 | /** 30 | * Listens for messages from the injected script. 31 | */ 32 | document.addEventListener('detect-ui5-content', function sendEvent(detectEvent) { 33 | // Send the received event detail object to background page 34 | port.postMessage(detectEvent.detail); 35 | 36 | }, false); 37 | }()); 38 | -------------------------------------------------------------------------------- /app/scripts/content/main.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | var utils = require('../modules/utils/utils.js'); 4 | var highLighter = require('../modules/content/highLighter.js'); 5 | var port = utils.getPort(); 6 | 7 | function confirmScriptInjectionDone() { 8 | // Add this action to the Q 9 | // This is needed when the devtools are undocked from the current inspected window 10 | setTimeout(function () { 11 | port.postMessage({ 12 | action: 'on-main-script-injection' 13 | }); 14 | }, 0); 15 | } 16 | 17 | var DONE_FLAG = 'MAIN_SCRIPT_INJECTION_DONE'; 18 | if (window[DONE_FLAG] === true) { 19 | confirmScriptInjectionDone(); 20 | return; 21 | } 22 | 23 | // Inject needed scripts into the inspected page 24 | // ================================================================================ 25 | 26 | /** 27 | * Inject javascript file into the page. 28 | * @param {string} source - file path 29 | * @callback 30 | */ 31 | var injectScript = function (source, callback) { 32 | var script = document.createElement('script'); 33 | script.src = chrome.runtime.getURL(source); 34 | document.head.appendChild(script); 35 | 36 | /** 37 | * Delete the injected file, when it is loaded. 38 | */ 39 | script.onload = function () { 40 | script.parentNode.removeChild(script); 41 | 42 | if (callback) { 43 | callback(); 44 | } 45 | }; 46 | }; 47 | 48 | injectScript('vendor/ToolsAPI.js', function () { 49 | injectScript('scripts/injected/main.js', function () { 50 | window[DONE_FLAG] = true; 51 | confirmScriptInjectionDone(); 52 | }); 53 | }); 54 | 55 | // ================================================================================ 56 | // Communication 57 | // ================================================================================ 58 | 59 | /** 60 | * Send message to injected script. 61 | * @param {Object} message 62 | */ 63 | var sendCustomMessageToInjectedScript = function (message) { 64 | document.dispatchEvent(new CustomEvent('ui5-communication-with-injected-script', { 65 | detail: message 66 | })); 67 | }; 68 | 69 | // Name space for message handler functions. 70 | var messageHandler = { 71 | 72 | /** 73 | * Changes the highlighter position and size, 74 | * when an element from the ControlTree is hovered. 75 | * @param {Object} message 76 | */ 77 | 'on-control-tree-hover': function (message) { 78 | highLighter.setDimensions(message.target); 79 | }, 80 | 81 | 'on-hide-highlight': function () { 82 | highLighter.hide(); 83 | }, 84 | 85 | 'do-ping': function (message, messageSender, sendResponse) { 86 | // respond to ping 87 | sendResponse(true /* alive status */); 88 | } 89 | }; 90 | 91 | // Listen for messages from the background page 92 | port.onMessage(function (message, messageSender, sendResponse) { 93 | // Resolve incoming messages 94 | utils.resolveMessage({ 95 | message: message, 96 | messageSender: messageSender, 97 | sendResponse: sendResponse, 98 | actions: messageHandler 99 | }); 100 | 101 | // Send events to injected script 102 | sendCustomMessageToInjectedScript(message); 103 | }); 104 | 105 | /** 106 | * Listener for messages from the injected script. 107 | */ 108 | document.addEventListener('ui5-communication-with-content-script', function sendEvent(detectEvent) { 109 | // Send the received event detail object to background page 110 | port.postMessage(detectEvent.detail); 111 | }, false); 112 | }()); 113 | -------------------------------------------------------------------------------- /app/scripts/devtools/panel/initialize.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var utils = require('../../modules/utils/utils.js'); 4 | 5 | // Create a port with background page for continuous message communication 6 | var port = utils.getPort(); 7 | 8 | /** 9 | * Find the ID of the nearest UI5 control from the current selected element in Chrome elements panel. 10 | * @param {string} selectedElement - The ID of the selected element in Chrome elements panel 11 | * @returns {string} The ID of the nearest UI5 Control from the selectedElement 12 | * @private 13 | */ 14 | function _getNearestUI5ControlID(selectedElement) { 15 | var element = selectedElement; 16 | while (!element.getAttribute('data-sap-ui')) { 17 | if (element.nodeName === 'BODY') { 18 | return undefined; 19 | } 20 | element = element.parentNode; 21 | } 22 | return element.id; 23 | } 24 | 25 | chrome.devtools.panels.create('UI5', '/images/icon-128.png', '/html/panel/ui5/index.html', function (panel) { 26 | panel.onHidden.addListener(function () { 27 | port.postMessage({ 28 | action: 'on-ui5-devtool-hide' 29 | }); 30 | }); 31 | panel.onShown.addListener(function () { 32 | port.postMessage({ 33 | action: 'on-ui5-devtool-show' 34 | }); 35 | }); 36 | }); 37 | 38 | chrome.devtools.panels.elements.onSelectionChanged.addListener(function () { 39 | // To get the selected element in Chrome elements panel, is needed to use eval and call the function with $0 parameter 40 | var getNearestUI5Control = _getNearestUI5ControlID.toString() + '_getNearestUI5ControlID($0);'; 41 | 42 | /* JSHINT evil: true */ 43 | chrome.devtools.inspectedWindow.eval(getNearestUI5Control, {useContentScriptContext: true}, function (elementId) { 44 | if (elementId === undefined) { 45 | return; 46 | } 47 | 48 | port.postMessage({ 49 | action: 'on-select-ui5-control-from-element-tab', 50 | nearestUI5Control: elementId 51 | }); 52 | }); 53 | /* JSHINT evil: false */ 54 | }); 55 | -------------------------------------------------------------------------------- /app/scripts/injected/detectUI5.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | /** 5 | * Create an object witch the initial needed information from the UI5 availability check. 6 | * @returns {Object} 7 | */ 8 | function createResponseToContentScript() { 9 | var responseToContentScript = Object.create(null); 10 | var responseToContentScriptBody; 11 | var frameworkInfo; 12 | var versionInfo; 13 | 14 | responseToContentScript.detail = Object.create(null); 15 | responseToContentScriptBody = responseToContentScript.detail; 16 | 17 | if (window.sap && window.sap.ui) { 18 | responseToContentScriptBody.action = 'on-ui5-detected'; 19 | responseToContentScriptBody.framework = Object.create(null); 20 | 21 | // Get framework version 22 | try { 23 | responseToContentScriptBody.framework.version = sap.ui.getCore().getConfiguration().getVersion().toString(); 24 | } catch (e) { 25 | responseToContentScriptBody.framework.version = ''; 26 | } 27 | 28 | // Get framework name 29 | try { 30 | versionInfo = sap.ui.getVersionInfo(); 31 | 32 | // Use group artifact version for maven builds or name for other builds (like SAPUI5-on-ABAP) 33 | frameworkInfo = versionInfo.gav ? versionInfo.gav : versionInfo.name; 34 | 35 | responseToContentScriptBody.framework.name = frameworkInfo.indexOf('openui5') !== -1 ? 'OpenUI5' : 'SAPUI5'; 36 | } catch (e) { 37 | responseToContentScriptBody.framework.name = 'UI5'; 38 | } 39 | 40 | // Check if the version is supported 41 | responseToContentScriptBody.isVersionSupported = !!sap.ui.require; 42 | 43 | } else { 44 | responseToContentScriptBody.action = 'on-ui5-not-detected'; 45 | } 46 | 47 | return responseToContentScript; 48 | } 49 | 50 | // Send information to content script 51 | document.dispatchEvent(new CustomEvent('detect-ui5-content', createResponseToContentScript())); 52 | 53 | // Listens for event from injected script 54 | document.addEventListener('do-ui5-detection-injected', function () { 55 | document.dispatchEvent(new CustomEvent('detect-ui5-content', createResponseToContentScript())); 56 | }, false); 57 | }()); 58 | -------------------------------------------------------------------------------- /app/scripts/modules/background/ContextMenu.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var oContextMenusCreated = {}; 3 | /** 4 | * Context menu. 5 | * @param {Object} options 6 | * @constructor 7 | */ 8 | function ContextMenu(options) { 9 | this._title = options.title; 10 | this._id = options.id; 11 | this._contexts = options.contexts; 12 | 13 | /** 14 | * This method will be fired when an instanced is clicked. The idea is to be overwritten from the instance. 15 | * @param {Object} info - Information sent when a context menu item is clicked. 16 | * @param {Object} tab - The details of the tab where the click took place. If the click did not take place in a tab, 17 | * this parameter will be missing. 18 | */ 19 | this.onClicked = function (info, tab) { 20 | }; 21 | } 22 | 23 | /** 24 | * Create context menu item. 25 | */ 26 | ContextMenu.prototype.create = function () { 27 | var that = this; 28 | 29 | if (!oContextMenusCreated[that._id]) { 30 | chrome.contextMenus.create({ 31 | title: that._title, 32 | id: that._id, 33 | contexts: that._contexts 34 | }); 35 | 36 | chrome.contextMenus.onClicked.addListener(that._onClickHandler.bind(that)); 37 | oContextMenusCreated[that._id] = true; 38 | } 39 | }; 40 | 41 | /** 42 | * Delete all context menu items. 43 | */ 44 | ContextMenu.prototype.removeAll = function () { 45 | chrome.contextMenus.removeAll(); 46 | oContextMenusCreated = {}; 47 | }; 48 | 49 | /** 50 | * Set right clicked element. 51 | * @param {string} target 52 | */ 53 | ContextMenu.prototype.setRightClickTarget = function (target) { 54 | this._rightClickTarget = target; 55 | }; 56 | 57 | /** 58 | * Click handler. 59 | * @param {Object} info - Information sent when a context menu item is clicked. 60 | * @param {Object} tabs - The details of the tab where the click took place. If the click did not take place in a tab, 61 | * this parameter will be missing. 62 | */ 63 | ContextMenu.prototype._onClickHandler = function (info, tabs) { 64 | if (info.menuItemId === this._id) { 65 | this.onClicked(info, tabs); 66 | } 67 | }; 68 | 69 | module.exports = ContextMenu; 70 | -------------------------------------------------------------------------------- /app/scripts/modules/background/pageAction.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Page action. 5 | * @type {{create: Function}} 6 | */ 7 | var pageAction = { 8 | 9 | /** 10 | * Create page action. 11 | * @param {Object} options 12 | */ 13 | create: function (options) { 14 | var framework = options.framework; 15 | var version = options.version; 16 | var tabId = options.tabId; 17 | 18 | chrome.action.setTitle({ 19 | tabId: tabId, 20 | title: 'This page is using ' + framework + ' v' + version 21 | }); 22 | }, 23 | 24 | /** 25 | * Disable page action. 26 | * 27 | */ 28 | disable: function () { 29 | chrome.action.disable(); 30 | }, 31 | 32 | /** 33 | * Enable page action. 34 | * 35 | */ 36 | enable: function () { 37 | chrome.action.enable(); 38 | } 39 | }; 40 | 41 | module.exports = pageAction; 42 | -------------------------------------------------------------------------------- /app/scripts/modules/content/highLighter.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Singleton helper to highlight controls DOM elements 5 | */ 6 | var Highlighter = { 7 | // Reference for the highlighter DOM element 8 | _highLighterDomEl: null, 9 | 10 | /** 11 | * Hide the highlighter. 12 | * @public 13 | */ 14 | hide: function () { 15 | this._highLighterDomEl && (this._highLighterDomEl.style.display = 'none'); 16 | }, 17 | 18 | /** 19 | * Show the highlighter. 20 | * @private 21 | */ 22 | _show: function () { 23 | this._highLighterDomEl && (this._highLighterDomEl.style.display = 'block'); 24 | }, 25 | 26 | /** 27 | * Create DOM element for visual highlighting. 28 | * @private 29 | */ 30 | _create: function () { 31 | var highLighter = document.createElement('div'); 32 | highLighter.style.cssText = 'box-sizing: border-box;border:1px solid blue;background: rgba(20, 20, 200, 0.4);position: absolute'; 33 | 34 | var highLighterWrapper = document.createElement('div'); 35 | highLighterWrapper.id = 'ui5-highlighter'; 36 | highLighterWrapper.style.cssText = 'position: fixed;top:0;right:0;bottom:0;left:0;z-index: 1000;overflow: hidden;'; 37 | highLighterWrapper.appendChild(highLighter); 38 | 39 | document.body.appendChild(highLighterWrapper); 40 | 41 | // Save reference for later usage 42 | this._highLighterDomEl = document.getElementById('ui5-highlighter'); 43 | 44 | // Add event handler 45 | this._highLighterDomEl.onmouseover = this.hide.bind(this); 46 | }, 47 | 48 | /** 49 | * Set the position of the visual highlighter. 50 | * @param {string} elementId - The id of the DOM element that need to be highlighted 51 | * @returns {exports} 52 | */ 53 | setDimensions: function (elementId) { 54 | var highlighter; 55 | var targetDomElement; 56 | var targetRect; 57 | 58 | // the hightlighter DOM element may already have been created in a previous DevTools session 59 | // (followed by closing and reopening the DevTools) 60 | this._highLighterDomEl || (this._highLighterDomEl = document.getElementById('ui5-highlighter')); 61 | 62 | if (!this._highLighterDomEl) { 63 | this._create(); 64 | } else { 65 | this._show(); 66 | } 67 | 68 | highlighter = this._highLighterDomEl.firstElementChild; 69 | targetDomElement = document.getElementById(elementId); 70 | 71 | if (targetDomElement) { 72 | targetRect = targetDomElement.getBoundingClientRect(); 73 | 74 | highlighter.style.top = targetRect.top + 'px'; 75 | highlighter.style.left = targetRect.left + 'px'; 76 | highlighter.style.height = targetRect.height + 'px'; 77 | highlighter.style.width = targetRect.width + 'px'; 78 | } 79 | 80 | return this; 81 | } 82 | }; 83 | 84 | module.exports = Highlighter; 85 | -------------------------------------------------------------------------------- /app/scripts/modules/injected/applicationUtils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var utils = require('../utils/utils.js'); 4 | 5 | /** 6 | * Get common application information. 7 | * @returns {Object} commonInformation - commonInformation property from ToolsAPI.getFrameworkInformation() 8 | * @private 9 | */ 10 | function _getCommonInformation(commonInformation) { 11 | var frameworkName = commonInformation.frameworkName; 12 | var buildTime = utils.formatter.convertUI5TimeStampToHumanReadableFormat(commonInformation.buildTime); 13 | var result = {}; 14 | 15 | result[frameworkName] = commonInformation.version + ' (built at ' + buildTime + ')'; 16 | 17 | result['User Agent'] = commonInformation.userAgent; 18 | result.Application = commonInformation.applicationHREF; 19 | 20 | return result; 21 | } 22 | 23 | /** 24 | * Get bootstrap configuration information. 25 | * @returns {Object} configurationBootstrap - configurationBootstrap property from ToolsAPI.getFrameworkInformation() 26 | * @private 27 | */ 28 | function _getConfigurationBootstrap(configurationBootstrap) { 29 | var bootConfigurationResult = {}; 30 | 31 | for (var key in configurationBootstrap) { 32 | if (configurationBootstrap[key] instanceof Object === true) { 33 | bootConfigurationResult[key] = JSON.stringify(configurationBootstrap[key]); 34 | } else { 35 | bootConfigurationResult[key] = configurationBootstrap[key]; 36 | } 37 | } 38 | 39 | return bootConfigurationResult; 40 | } 41 | 42 | /** 43 | * Get loaded modules application information. 44 | * @returns {Object} loadedModules - loadedModules property from ToolsAPI.getFrameworkInformation() 45 | * @private 46 | */ 47 | function _getLoadedModules(loadedModules) { 48 | var loadedModulesResult = {}; 49 | 50 | for (var i = 0; i < loadedModules.length; i++) { 51 | loadedModulesResult[i + 1] = loadedModules[i]; 52 | } 53 | 54 | return loadedModulesResult; 55 | } 56 | 57 | /** 58 | * Get application URL parameters. 59 | * @returns {Object} URLParameters - URLParameters property from ToolsAPI.getFrameworkInformation() 60 | * @private 61 | */ 62 | function _getURLParameters(URLParameters) { 63 | var urlParametersResult = {}; 64 | 65 | for (var key in URLParameters) { 66 | urlParametersResult[key] = URLParameters[key].join(', '); 67 | } 68 | 69 | return urlParametersResult; 70 | } 71 | 72 | // Public API 73 | module.exports = { 74 | 75 | /** 76 | * Get UI5 information for the current inspected page. 77 | * @param {Object} frameworkInformation - frameworkInformation property from ToolsAPI.getFrameworkInformation() 78 | * @returns {Object} 79 | */ 80 | getApplicationInfo: function (frameworkInformation) { 81 | return { 82 | common: { 83 | options: { 84 | title: 'General', 85 | expandable: true, 86 | expanded: true 87 | }, 88 | data: _getCommonInformation(frameworkInformation.commonInformation) 89 | }, 90 | 91 | configurationBootstrap: { 92 | options: { 93 | title: 'Configuration (bootstrap)', 94 | expandable: true, 95 | expanded: true 96 | }, 97 | data: _getConfigurationBootstrap(frameworkInformation.configurationBootstrap) 98 | }, 99 | 100 | configurationComputed: { 101 | options: { 102 | title: 'Configuration (computed)', 103 | expandable: true, 104 | expanded: true 105 | }, 106 | data: frameworkInformation.configurationComputed 107 | }, 108 | 109 | urlParameters: { 110 | options: { 111 | title: 'URL Parameters', 112 | expandable: true, 113 | expanded: true 114 | }, 115 | data: _getURLParameters(frameworkInformation.URLParameters) 116 | }, 117 | 118 | loadedLibraries: { 119 | options: { 120 | title: 'Libraries (loaded)', 121 | expandable: true, 122 | expanded: true 123 | }, 124 | data: frameworkInformation.loadedLibraries 125 | }, 126 | 127 | libraries: { 128 | options: { 129 | title: 'Libraries (all)', 130 | expandable: true 131 | }, 132 | data: frameworkInformation.libraries 133 | }, 134 | 135 | loadedModules: { 136 | options: { 137 | title: 'Modules (loaded)', 138 | expandable: true 139 | }, 140 | data: _getLoadedModules(frameworkInformation.loadedModules) 141 | } 142 | }; 143 | }, 144 | 145 | /** 146 | * Get the needed information for the popup. 147 | * @param {Object} frameworkInformation - frameworkInformation property from ToolsAPI.getFrameworkInformation(); 148 | * @returns {Object} 149 | */ 150 | getInformationForPopUp: function (frameworkInformation) { 151 | return _getCommonInformation(frameworkInformation.commonInformation); 152 | } 153 | }; 154 | -------------------------------------------------------------------------------- /app/scripts/modules/injected/message.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Creates a parser that simplifies complex objects by removing non-serializable functions and complex instances. 5 | * @constructor 6 | */ 7 | function ObjectParser() { 8 | } 9 | 10 | /** 11 | * Checks whether a given object is a simple/plain object. 12 | * @param {Object} object - input object, must not be null 13 | * @returns {boolean} true if simple object, false else 14 | * @private 15 | */ 16 | ObjectParser.prototype._isSimpleObject = function (object) { 17 | // Check if toString output indicates object 18 | if (typeof object.toString === 'function' && object.toString() !== '[object Object]') { 19 | return false; 20 | } 21 | var proto = object.prototype; 22 | // Check if prototype is missing 23 | if (!proto) { 24 | return true; 25 | } 26 | // Check if constructed by a global object function 27 | var Ctor = proto.hasOwnProperty('constructor') && proto.constructor; 28 | return typeof Ctor === 'function' && Ctor.toString() === 'function() {}'; 29 | }; 30 | 31 | /** 32 | * Deep copies an object. 33 | * @param {Object} object - the object, must not be null 34 | * @param {Array} predecessors - list of predecessors to detect circular references 35 | * @returns {Array|Object} the deep copied object 36 | * @private 37 | */ 38 | ObjectParser.prototype._deepCopy = function (object, predecessors) { 39 | this.visitedObjects.push(object); 40 | var targetObject = Array.isArray(object) ? [] : {}; 41 | this.createdObjects.push(targetObject); 42 | var currentPredecessors = predecessors.slice(0); 43 | currentPredecessors.push(object); 44 | for (var sKey in object) { 45 | // Ignore undefined and functions (similar to JSON.stringify) 46 | if (object[sKey] !== undefined && typeof object[sKey] !== 'function') { 47 | // Recursive call 48 | targetObject[sKey] = this._parseObject(object[sKey], currentPredecessors); 49 | } 50 | } 51 | return targetObject; 52 | }; 53 | 54 | /** 55 | * Parses an object recursively. 56 | * @param {*} object - the object to parse, can be a simple type 57 | * @param {Array} predecessors - list of predecessors to detect circular references 58 | * @returns {*} returns the parsed object 59 | * @private 60 | */ 61 | ObjectParser.prototype._parseObject = function (object, predecessors) { 62 | // Resolve simple type 63 | if (object === null || typeof object === 'number' || typeof object === 'boolean' || typeof object === 'string') { 64 | return object; 65 | } 66 | // Ignore complex types 67 | if (!Array.isArray(object) && !this._isSimpleObject(object)) { 68 | return ''; 69 | } 70 | // Ignore & mark circular reference 71 | if (predecessors.indexOf(object) !== -1) { 72 | return ''; 73 | } 74 | // Resolve simple reference 75 | var referenceIndex = this.visitedObjects.indexOf(object); 76 | if (referenceIndex !== -1) { 77 | return this.createdObjects[referenceIndex]; 78 | } 79 | // Handle object by deep copy 80 | return this._deepCopy(object, predecessors); 81 | }; 82 | 83 | /** 84 | * Parses given object into a JSON object removing all functions and remove circular references. 85 | * @param {Object} object - input object 86 | * @returns {Object} JSON object 87 | */ 88 | ObjectParser.prototype.parse = function (object) { 89 | this.visitedObjects = []; 90 | this.createdObjects = []; 91 | return this._parseObject(object, []); 92 | }; 93 | 94 | var messageParser = new ObjectParser(); 95 | 96 | module.exports = { 97 | /** 98 | * Send message to content script. 99 | * @param {Object} object 100 | */ 101 | send: function (object) { 102 | var message = { 103 | detail: messageParser.parse(object) 104 | }; 105 | 106 | document.dispatchEvent(new CustomEvent('ui5-communication-with-content-script', message)); 107 | } 108 | }; 109 | -------------------------------------------------------------------------------- /app/scripts/modules/injected/rightClickHandler.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | 5 | // Reference for the ID of the last click UI5 control. 6 | _clickedElementId: null, 7 | 8 | /** 9 | * Return the ID of the UI5 control that was clicked. 10 | * @returns {string} 11 | */ 12 | getClickedElementId: function () { 13 | return this._clickedElementId; 14 | }, 15 | 16 | /** 17 | * Set the ID of the UI5 control that was clicked. 18 | * @param {Element} target 19 | * @returns {string} 20 | */ 21 | setClickedElementId: function (target) { 22 | while (target && !target.getAttribute('data-sap-ui')) { 23 | if (target.nodeName === 'BODY') { 24 | break; 25 | } 26 | target = target.parentNode; 27 | } 28 | 29 | this._clickedElementId = target.id; 30 | return this; 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /app/scripts/modules/injected/ui5inspector.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Create global reference for the extension. 5 | * @private 6 | */ 7 | function _createReferences() { 8 | if (window.ui5inspector === undefined) { 9 | window.ui5inspector = { 10 | events: Object.create(null) 11 | }; 12 | } 13 | } 14 | 15 | /** 16 | * Register event listener if is not already registered. 17 | * @param {string} eventName - the name of the event that will be register 18 | * @callback 19 | * @private 20 | */ 21 | function _registerEventListener(eventName, callback) { 22 | if (window.ui5inspector.events[eventName] === undefined) { 23 | // Register reference 24 | window.ui5inspector.events[eventName] = { 25 | callback: callback.name, 26 | state: 'registered' 27 | }; 28 | 29 | document.addEventListener(eventName, callback, false); 30 | } 31 | } 32 | 33 | module.exports = { 34 | createReferences: _createReferences, 35 | registerEventListener: _registerEventListener 36 | }; 37 | -------------------------------------------------------------------------------- /app/scripts/modules/ui/ControllerDetailView.js: -------------------------------------------------------------------------------- 1 | 2 | 'use strict'; 3 | const NOCONTROLLERMESSAGE = 'Select a \'sap.ui.core.mvc.XMLView\' to see its Controller content. Click to filter on XMLViews'; 4 | const CONTROLLERNAME = 'Name:'; 5 | const CONTROLLERPATH = 'Relative Path:'; 6 | /** 7 | * @param {string} containerId - id of the DOM container 8 | * @constructor 9 | */ 10 | function ControllerDetailView(containerId) { 11 | this.oContainer = document.getElementById(containerId); 12 | this.oEditorDOM = document.createElement('div'); 13 | this.oEditorDOM.id = 'controllerEditor'; 14 | this.oEditorDOM.classList.toggle('hidden', true); 15 | this.oContainer.appendChild(this.oEditorDOM); 16 | 17 | this.oNamePlaceholderDOM = document.createElement('div'); 18 | this.oNamePlaceholderDOM.classList.add('longTextReduce'); 19 | this.oNamePlaceholderDOM.onclick = this._selectAllText; 20 | this.oPathPlaceholderDOM = document.createElement('div'); 21 | this.oPathPlaceholderDOM.classList.add('longTextReduce'); 22 | this.oPathPlaceholderDOM.onclick = this._selectAllText; 23 | this.oNameDOM = document.createElement('div'); 24 | this.oNameDOM.classList.add('firstColAlignment'); 25 | this.oNameDOM.innerText = CONTROLLERNAME; 26 | this.oPathDOM = document.createElement('div'); 27 | this.oPathDOM.classList.add('firstColAlignment'); 28 | this.oPathDOM.innerText = CONTROLLERPATH; 29 | this.oEditorDOM.appendChild(this.oNameDOM); 30 | this.oEditorDOM.appendChild(this.oNamePlaceholderDOM); 31 | this.oEditorDOM.appendChild(this.oPathDOM); 32 | this.oEditorDOM.appendChild(this.oPathPlaceholderDOM); 33 | 34 | this.oEditorAltDOM = document.createElement('div'); 35 | this.oEditorAltDOM.classList.add('editorAlt'); 36 | this.oEditorAltDOM.classList.toggle('hidden', false); 37 | this.oEditorAltMessageDOM = document.createElement('div'); 38 | this.oEditorAltMessageDOM.innerText = NOCONTROLLERMESSAGE; 39 | 40 | this.oEditorAltMessageDOM.addEventListener('click', function() { 41 | var searchField = document.getElementById('elementsRegistrySearch'); 42 | var filterCheckbox = document.getElementById('elementsRegistryCheckbox'); 43 | searchField.value = 'sap.ui.core.mvc.XMLView'; 44 | if (!filterCheckbox.checked) { 45 | filterCheckbox.click(); 46 | } 47 | return false; 48 | }); 49 | this.oContainer.appendChild(this.oEditorAltDOM); 50 | this.oEditorAltDOM.appendChild(this.oEditorAltMessageDOM); 51 | } 52 | 53 | /** 54 | * Updates data. 55 | * @param {Object} data - object structure as JSON 56 | */ 57 | ControllerDetailView.prototype.update = function (controllerInfo) { 58 | 59 | var bIsDataValid = !!(controllerInfo.sControllerName && controllerInfo.sControllerRelPath); 60 | 61 | this.oEditorDOM.classList.toggle('hidden', !bIsDataValid); 62 | this.oEditorAltDOM.classList.toggle('hidden', bIsDataValid); 63 | 64 | if (bIsDataValid) { 65 | this.oNamePlaceholderDOM.innerText = controllerInfo.sControllerName; 66 | this.oPathPlaceholderDOM.innerText = controllerInfo.sControllerRelPath; 67 | } 68 | }; 69 | 70 | ControllerDetailView.prototype._selectAllText = function (oEvent) { 71 | var range = document.createRange(); 72 | range.selectNode(oEvent.target); 73 | window.getSelection().removeAllRanges(); 74 | window.getSelection().addRange(range); 75 | }; 76 | 77 | 78 | module.exports = ControllerDetailView; 79 | -------------------------------------------------------------------------------- /app/scripts/modules/ui/FrameSelect.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * FrameSelect constructor 5 | * @param {string} id - The id of the DOM container 6 | * @param {object} options 7 | * @constructor 8 | */ 9 | function FrameSelect(id, options) { 10 | this._sltDomRef = document.getElementById(id).querySelector('select'); 11 | // Chrome treats the main window as the default frame with id 0 12 | this._selectedId = 0; 13 | this.onSelectionChange = options.onSelectionChange ? options.onSelectionChange : function () {}; 14 | 15 | this._sltDomRef.addEventListener('change', function(event) { 16 | var selectedId = parseInt(event.target.value); 17 | var oldSelectedId = this._selectedId; 18 | this._selectedId = selectedId; 19 | this.onSelectionChange({selectedId, oldSelectedId}); 20 | }.bind(this)); 21 | } 22 | 23 | FrameSelect.prototype.getSelectedId = function () { 24 | return this._selectedId; 25 | }; 26 | 27 | FrameSelect.prototype.setSelectedId = function (frameId) { 28 | this._sltDomRef.value = frameId; 29 | this._selectedId = frameId; 30 | }; 31 | 32 | FrameSelect.prototype.setData = function (data) { 33 | var frameIds = Object.keys(data).map( x => parseInt(x)); 34 | var selectedId; 35 | var oldSelectedId; 36 | 37 | this._sltDomRef.innerHTML = ''; 38 | frameIds.forEach(function(frameId) { 39 | this._addOption(frameId, data[frameId]); 40 | }, this); 41 | if (frameIds.indexOf(this._selectedId) < 0) { 42 | // the previously selected id is no loger found 43 | // (e.g. frame deleted) 44 | // => reset selection to the top frame 45 | selectedId = 0; 46 | oldSelectedId = this._selectedId; 47 | this.setSelectedId(selectedId); 48 | this.onSelectionChange({selectedId, oldSelectedId}); 49 | } 50 | this._sltDomRef.hidden = false; 51 | }; 52 | 53 | FrameSelect.prototype._addOption = function (frameId, data) { 54 | var option = document.createElement('option'); 55 | option.value = frameId; 56 | option.innerText = this._getFrameLabel(frameId, data.url); 57 | this._sltDomRef.appendChild(option); 58 | }; 59 | 60 | FrameSelect.prototype._getFrameLabel = function (frameId, frameUrl) { 61 | if (frameId === 0) { 62 | return 'top'; 63 | } 64 | var aUrl = frameUrl.split('/'); 65 | return aUrl[aUrl.length - 1]; 66 | }; 67 | 68 | module.exports = FrameSelect; 69 | -------------------------------------------------------------------------------- /app/scripts/modules/ui/JSONFormatter.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Sample usage 5 | * JSONView = require('../../../modules/ui/JSONFormatter.js'); 6 | * JSONViewFormater.formatJSONtoHTML(sampleJSONData); 7 | */ 8 | 9 | /** 10 | * 11 | * @param {Object} json 12 | * @returns {string|HTML} 13 | * @private 14 | */ 15 | function _syntaxHighlight(json) { 16 | json = JSON.stringify(json, undefined, 2); 17 | json = json.replace(/&/g, '&').replace(//g, '>'); 18 | return json.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function (match) { 19 | var tagName = 'number'; 20 | if (/^"/.test(match)) { 21 | if (/:$/.test(match)) { 22 | tagName = 'key'; 23 | } else { 24 | tagName = 'string'; 25 | } 26 | } else if (/true|false/.test(match)) { 27 | tagName = 'boolean'; 28 | } else if (/null/.test(match)) { 29 | tagName = 'null'; 30 | } 31 | return '<' + tagName + '>' + match + ''; 32 | }); 33 | } 34 | 35 | module.exports = { 36 | 37 | /** 38 | * Create HTML from a json object. 39 | * @param {Object} json 40 | * @returns {string} 41 | */ 42 | formatJSONtoHTML: function (json) { 43 | return '
' + _syntaxHighlight(json) + '
'; 44 | } 45 | }; 46 | -------------------------------------------------------------------------------- /app/scripts/modules/ui/ODataDetailView.js: -------------------------------------------------------------------------------- 1 | /* globals ResizeObserver */ 2 | 3 | 'use strict'; 4 | 5 | /** 6 | * @param {string} containerId - id of the DOM container 7 | * @constructor 8 | */ 9 | function ODataDetailView(containerId) { 10 | this.oContainer = document.getElementById(containerId); 11 | this.oEditorDOM = document.createElement('div'); 12 | this.oEditorDOM.id = 'editor'; 13 | this.oContainer.appendChild(this.oEditorDOM); 14 | 15 | this.oEditorAltDOM = document.createElement('div'); 16 | this.oEditorAltDOM.classList.add('editorAlt'); 17 | this.oEditorAltMessageDOM = document.createElement('div'); 18 | this.oContainer.appendChild(this.oEditorAltDOM); 19 | this.oEditorAltDOM.appendChild(this.oEditorAltMessageDOM); 20 | 21 | this.oEditor = ace.edit('editor'); 22 | this.oEditor.getSession().setUseWrapMode(true); 23 | 24 | this._setTheme(); 25 | 26 | const oResizeObserver = new ResizeObserver(function () { 27 | this.oEditor.resize(); 28 | }.bind(this)); 29 | oResizeObserver.observe(this.oEditorDOM); 30 | } 31 | 32 | /** 33 | * Updates data. 34 | * @param {Object} data - object structure as JSON 35 | */ 36 | ODataDetailView.prototype.update = function (data) { 37 | const sResponseBody = data.responseBody; 38 | let sAltMessage; 39 | 40 | this.oEditorDOM.classList.toggle('hidden', !sResponseBody); 41 | this.oEditorAltDOM.classList.toggle('hidden', !!sResponseBody); 42 | 43 | if (sResponseBody) { 44 | this.oEditor.session.setMode('ace/mode/' + sResponseBody.type); 45 | this.oEditor.setValue(sResponseBody.content, 0); 46 | } else { 47 | sAltMessage = data.altMessage || 'No response body'; 48 | this.oEditorAltMessageDOM.innerText = sAltMessage; 49 | } 50 | 51 | this.oEditor.clearSelection(); 52 | }; 53 | 54 | /** 55 | * Clears editor. 56 | */ 57 | ODataDetailView.prototype.clear = function () { 58 | this.oEditor.setValue('', -1); 59 | }; 60 | 61 | /** 62 | * Sets theme. 63 | */ 64 | ODataDetailView.prototype._setTheme = function () { 65 | var bDarkMode = chrome.devtools.panels.themeName === 'dark'; 66 | 67 | this.oEditor.setTheme(bDarkMode ? 'ace/theme/vibrant_ink' : 'ace/theme/chrome'); 68 | }; 69 | 70 | module.exports = ODataDetailView; 71 | -------------------------------------------------------------------------------- /app/scripts/modules/ui/ODataMasterView.js: -------------------------------------------------------------------------------- 1 | /* globals ResizeObserver */ 2 | 3 | 'use strict'; 4 | 5 | const DataGrid = require('./datagrid/DataGrid.js'); 6 | const UIUtils = require('./datagrid/UIUtils.js'); 7 | const EntriesLog = require('../utils/EntriesLog.js'); 8 | 9 | const COLUMNS = [{ 10 | id: 'name', 11 | title: 'Name', 12 | sortable: true, 13 | align: undefined, 14 | nonSelectable: false, 15 | weight: 40, 16 | visible: true, 17 | allowInSortByEvenWhenHidden: false, 18 | disclosure: true, 19 | /** 20 | * Sorts Items. 21 | * @param {Object} a 22 | * @param {Object} b 23 | */ 24 | sortingFunction: function (a, b) { 25 | return DataGrid.SortableDataGrid.StringComparator('name', a, b); 26 | } 27 | }, 28 | { 29 | id: 'method', 30 | title: 'Method', 31 | sortable: true, 32 | align: undefined, 33 | nonSelectable: false, 34 | weight: 10, 35 | visible: true, 36 | allowInSortByEvenWhenHidden: false, 37 | /** 38 | * Sorts Items. 39 | * @param {Object} a 40 | * @param {Object} b 41 | */ 42 | sortingFunction: function (a, b) { 43 | return DataGrid.SortableDataGrid.StringComparator('method', a, b); 44 | } 45 | }, 46 | { 47 | id: 'status', 48 | title: 'Status', 49 | sortable: true, 50 | align: undefined, 51 | nonSelectable: false, 52 | weight: 10, 53 | visible: true, 54 | allowInSortByEvenWhenHidden: false, 55 | /** 56 | * Sorts Items. 57 | * @param {Object} a 58 | * @param {Object} b 59 | */ 60 | sortingFunction: function (a, b) { 61 | return DataGrid.SortableDataGrid.NumericComparator('status', a, b); 62 | } 63 | }, 64 | { 65 | id: 'note', 66 | title: 'Detail', 67 | sortable: true, 68 | align: undefined, 69 | nonSelectable: false, 70 | weight: 20, 71 | visible: true, 72 | allowInSortByEvenWhenHidden: false, 73 | /** 74 | * Sorts Items. 75 | * @param {Object} a 76 | * @param {Object} b 77 | */ 78 | sortingFunction: function (a, b) { 79 | return DataGrid.SortableDataGrid.StringComparator('note', a, b); 80 | } 81 | }]; 82 | 83 | /** 84 | * @param {string} domId - id of the DOM container 85 | * @param {Object} options - initial configuration 86 | * @constructor 87 | */ 88 | function ODataMasterView(domId, options) { 89 | 90 | this.oContainerDOM = document.getElementById(domId); 91 | this.oEntriesLog = new EntriesLog(); 92 | 93 | /** 94 | * Selects an OData Entry log item. 95 | * @param {Object} oSelectedData 96 | */ 97 | this.onSelectItem = function (oSelectedData) {}; 98 | /** 99 | * Clears all OData Entry log items. 100 | * @param {Object} oSelectedData 101 | */ 102 | this.onClearItems = function (oSelectedData) {}; 103 | if (options) { 104 | this.onSelectItem = options.onSelectItem || this.onSelectItem; 105 | this.onClearItems = options.onClearItems || this.onClearItems; 106 | } 107 | 108 | const oClearButton = this._createClearButton(); 109 | this.oContainerDOM.appendChild(oClearButton); 110 | 111 | this.oDataGrid = this._createDataGrid(); 112 | this.oContainerDOM.appendChild(this.oDataGrid.element); 113 | this._getHAR(); 114 | } 115 | 116 | /** 117 | * Logs OData entry. 118 | * @param {Object} oEntry - Log entry 119 | * @private 120 | */ 121 | ODataMasterView.prototype._logEntry = function (oEntry) { 122 | const oNode = this.oEntriesLog.getEntryNode(oEntry); 123 | 124 | if (oNode) { 125 | this.oDataGrid.insertChild(oNode); 126 | } 127 | }; 128 | 129 | /* jshint ignore:start */ 130 | /** 131 | * Gets HTTP Archive request. 132 | * @private 133 | */ 134 | ODataMasterView.prototype._getHAR = function () { 135 | /** 136 | * Processes the HTTP Archive Requests. 137 | * @param {Object} result 138 | */ 139 | chrome.devtools.network.getHAR(result => { 140 | const entries = result.entries; 141 | if (!entries.length) { 142 | console.warn('No requests found by now'); 143 | } 144 | entries.forEach(this._logEntry, this); 145 | chrome.devtools.network.onRequestFinished.addListener(this._logEntry.bind(this)); 146 | }); 147 | }; 148 | /* jshint ignore:end */ 149 | 150 | /** 151 | * Creates Clear button. 152 | * @returns {Object} - Clear button Icon 153 | * @private 154 | */ 155 | ODataMasterView.prototype._createClearButton = function () { 156 | const oIcon = UIUtils.Icon.create('', 'toolbar-glyph hidden'); 157 | oIcon.setIconType('largeicon-clear'); 158 | 159 | /** 160 | * Clear Icon click handler. 161 | */ 162 | oIcon.onclick = function () { 163 | this.oDataGrid.rootNode().removeChildren(); 164 | this.onClearItems(); 165 | }.bind(this); 166 | 167 | return oIcon; 168 | }; 169 | 170 | /** 171 | * Creates DataGrid. 172 | * @returns {Object} - DataGrid 173 | * @private 174 | */ 175 | ODataMasterView.prototype._createDataGrid = function () { 176 | const oDataGrid = new DataGrid.SortableDataGrid({ 177 | displayName: 'test', 178 | columns: COLUMNS 179 | }); 180 | 181 | oDataGrid.addEventListener(DataGrid.Events.SortingChanged, this.sortHandler, this); 182 | oDataGrid.addEventListener(DataGrid.Events.SelectedNode, this.selectHandler, this); 183 | 184 | /** 185 | * Resize Handler for DataGrid. 186 | */ 187 | const oResizeObserver = new ResizeObserver(function () { 188 | oDataGrid.onResize(); 189 | }); 190 | oResizeObserver.observe(oDataGrid.element); 191 | 192 | return oDataGrid; 193 | }; 194 | 195 | /** 196 | * Sorts Columns of the DataGrid. 197 | */ 198 | ODataMasterView.prototype.sortHandler = function () { 199 | const columnId = this.oDataGrid.sortColumnId(); 200 | /** 201 | * Finds Column config by Id. 202 | * @param {Object} columnConfig 203 | */ 204 | const columnConfig = COLUMNS.find(columnConfig => columnConfig.id === columnId); 205 | if (!columnConfig || !columnConfig.sortingFunction) { 206 | return; 207 | } 208 | this.oDataGrid.sortNodes(columnConfig.sortingFunction, !this.oDataGrid.isSortOrderAscending()); 209 | }; 210 | 211 | /** 212 | * Selects clicked log entry. 213 | * @param {Object} oEvent 214 | */ 215 | ODataMasterView.prototype.selectHandler = function (oEvent) { 216 | const oSelectedNode = oEvent.data; 217 | const iSelectedId = oSelectedNode && oSelectedNode.data.id; 218 | 219 | this.onSelectItem({ 220 | responseBody: this.oEntriesLog.getEditorContent(iSelectedId), 221 | altMessage: this.oEntriesLog.getNoResponseMessage(iSelectedId) 222 | }); 223 | 224 | }; 225 | 226 | module.exports = ODataMasterView; 227 | -------------------------------------------------------------------------------- /app/scripts/modules/ui/TabBar.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * TabBar. 5 | * @param {string} containerId 6 | * @constructor 7 | */ 8 | function TabBar(containerId) { 9 | this._container = document.getElementById(containerId); 10 | this._contentsContainer = this._container.querySelector('contents'); 11 | this._tabsContainer = this._container.querySelector('tabs'); 12 | this.init(); 13 | } 14 | 15 | /** 16 | * Initialize TabBar. 17 | */ 18 | TabBar.prototype.init = function () { 19 | this.setActiveTab(this.getActiveTab()); 20 | 21 | // Add event handler on the tab container 22 | this._tabsContainer.onclick = this._onTabsClick.bind(this); 23 | }; 24 | 25 | /** 26 | * Get current active tab ID. 27 | * @returns {string} 28 | */ 29 | TabBar.prototype.getActiveTab = function () { 30 | return this._activeTabId ? this._activeTabId : this._tabsContainer.querySelector('[selected]').id; 31 | }; 32 | 33 | /** 34 | * Set active tab ID. 35 | * @param {string} newActiveTabId 36 | * @returns {TabBar} 37 | */ 38 | TabBar.prototype.setActiveTab = function (newActiveTabId) { 39 | if (!newActiveTabId) { 40 | return; 41 | } 42 | 43 | if (typeof newActiveTabId !== 'string') { 44 | console.warn('parameter error: The parameter must be a string'); 45 | return; 46 | } 47 | 48 | if (!this._tabsContainer.querySelector('#' + newActiveTabId)) { 49 | console.warn('parameter error: The parameter must be a valid ID of a child tab element'); 50 | return; 51 | } 52 | 53 | // Check for double clicking on active tab 54 | if (newActiveTabId === this.getActiveTab()) { 55 | var activeContent = this._contentsContainer.querySelector('[for="' + this.getActiveTab() + '"]'); 56 | 57 | if (activeContent.getAttribute('selected')) { 58 | return; 59 | } 60 | } 61 | 62 | this._changeActiveTab(newActiveTabId); 63 | this._activeTabId = newActiveTabId; 64 | 65 | return this; 66 | }; 67 | 68 | /** 69 | * Event handler for mouse click on a tabs. 70 | * @param {Object} event - click event 71 | * @private 72 | */ 73 | TabBar.prototype._onTabsClick = function (event) { 74 | var targetID = event.target.id; 75 | this.setActiveTab(targetID); 76 | }; 77 | 78 | /** 79 | * Change visible tab and content. 80 | * @param {string} tabId - The Id of the desired tab 81 | */ 82 | TabBar.prototype._changeActiveTab = function (tabId) { 83 | var currentActiveTab = this._tabsContainer.querySelector('[selected]'); 84 | var currentActiveContent = this._contentsContainer.querySelector('[for="' + this.getActiveTab() + '"]'); 85 | var newActiveTab = this._tabsContainer.querySelector('#' + tabId); 86 | var newActiveContent = this._contentsContainer.querySelector('[for="' + tabId + '"]'); 87 | 88 | currentActiveTab.removeAttribute('selected'); 89 | currentActiveContent.removeAttribute('selected'); 90 | 91 | newActiveTab.setAttribute('selected', 'true'); 92 | newActiveContent.setAttribute('selected', 'true'); 93 | }; 94 | 95 | module.exports = TabBar; 96 | -------------------------------------------------------------------------------- /app/scripts/modules/ui/XMLDetailView.js: -------------------------------------------------------------------------------- 1 | /* globals ResizeObserver */ 2 | 3 | 'use strict'; 4 | const formatXML = require('prettify-xml'); 5 | const NOXMLVIEWMESSAGE = 'Select a \'sap.ui.core.mvc.XMLView\' to see its XML content. Click to filter on XMLViews'; 6 | 7 | /** 8 | * @param {string} containerId - id of the DOM container 9 | * @constructor 10 | */ 11 | function XMLDetailView(containerId) { 12 | this.oContainer = document.getElementById(containerId); 13 | this.oEditorDOM = document.createElement('div'); 14 | this.oEditorDOM.id = 'xmlEditor'; 15 | this.oEditorDOM.classList.toggle('hidden', true); 16 | this.oContainer.appendChild(this.oEditorDOM); 17 | 18 | this.oEditorAltDOM = document.createElement('div'); 19 | this.oEditorAltDOM.classList.add('editorAlt'); 20 | this.oEditorAltDOM.classList.toggle('hidden', false); 21 | this.oEditorAltMessageDOM = document.createElement('div'); 22 | this.oEditorAltMessageDOM.innerText = NOXMLVIEWMESSAGE; 23 | 24 | this.oEditorAltMessageDOM.addEventListener('click', function() { 25 | var searchField = document.getElementById('elementsRegistrySearch'); 26 | var filterCheckbox = document.getElementById('elementsRegistryCheckbox'); 27 | searchField.value = 'sap.ui.core.mvc.XMLView'; 28 | if (!filterCheckbox.checked) { 29 | filterCheckbox.click(); 30 | } 31 | return false; 32 | }); 33 | this.oContainer.appendChild(this.oEditorAltDOM); 34 | this.oEditorAltDOM.appendChild(this.oEditorAltMessageDOM); 35 | 36 | this.oEditor = ace.edit(this.oEditorDOM.id); 37 | this.oEditor.getSession().setUseWrapMode(true); 38 | 39 | this._setTheme(); 40 | 41 | const oResizeObserver = new ResizeObserver(function () { 42 | this.oEditor.resize(); 43 | }.bind(this)); 44 | oResizeObserver.observe(this.oEditorDOM); 45 | } 46 | 47 | /** 48 | * Updates data. 49 | * @param {Object} data - object structure as JSON 50 | */ 51 | XMLDetailView.prototype.update = function (data) { 52 | const xml = data.xml && formatXML(data.xml); 53 | let sAltMessage; 54 | 55 | this.oEditorDOM.classList.toggle('hidden', !xml); 56 | this.oEditorAltDOM.classList.toggle('hidden', !!xml); 57 | 58 | if (xml) { 59 | this.oEditor.session.setMode('ace/mode/xml'); 60 | this.oEditor.setValue(xml, 0); 61 | } else { 62 | sAltMessage = data.altMessage || NOXMLVIEWMESSAGE; 63 | this.oEditorAltMessageDOM.innerText = sAltMessage; 64 | } 65 | 66 | this.oEditor.clearSelection(); 67 | }; 68 | 69 | /** 70 | * Clears editor. 71 | */ 72 | XMLDetailView.prototype.clear = function () { 73 | this.oEditor.setValue('', -1); 74 | }; 75 | 76 | /** 77 | * Sets theme. 78 | */ 79 | XMLDetailView.prototype._setTheme = function () { 80 | var bDarkMode = chrome.devtools.panels.themeName === 'dark'; 81 | 82 | this.oEditor.setTheme(bDarkMode ? 'ace/theme/vibrant_ink' : 'ace/theme/chrome'); 83 | }; 84 | 85 | module.exports = XMLDetailView; 86 | -------------------------------------------------------------------------------- /app/scripts/modules/ui/datagrid/DOMExtension.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2007 Apple Inc. All rights reserved. 3 | * Copyright (C) 2012 Google Inc. All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 9 | * 1. Redistributions of source code must retain the above copyright 10 | * notice, this list of conditions and the following disclaimer. 11 | * 2. Redistributions in binary form must reproduce the above copyright 12 | * notice, this list of conditions and the following disclaimer in the 13 | * documentation and/or other materials provided with the distribution. 14 | * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of 15 | * its contributors may be used to endorse or promote products derived 16 | * from this software without specific prior written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY 19 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY 22 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 25 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 27 | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | * 29 | * Contains diff method based on Javascript Diff Algorithm By John Resig 30 | * http://ejohn.org/files/jsdiff.js (released under the MIT license). 31 | */ 32 | 33 | /** 34 | * @param {string} tagName 35 | * @param {string=} customElementType 36 | * @return {!Element} 37 | * @suppress {checkTypes} 38 | * @suppressGlobalPropertiesCheck 39 | */ 40 | window.createElement = function(tagName, customElementType) { 41 | return document.createElement(tagName, {is: customElementType}); 42 | }; 43 | 44 | /** 45 | * @param {string} elementName 46 | * @param {string=} className 47 | * @param {string=} customElementType 48 | * @suppress {checkTypes} 49 | * @return {!Element} 50 | */ 51 | Document.prototype.createElementWithClass = function(elementName, className, customElementType) { 52 | const element = this.createElement(elementName, {is: customElementType}); 53 | if (className) { 54 | element.className = className; 55 | } 56 | return element; 57 | }; 58 | 59 | /** 60 | * @param {string} elementName 61 | * @param {string=} className 62 | * @param {string=} customElementType 63 | * @return {!Element} 64 | * @suppressGlobalPropertiesCheck 65 | */ 66 | window.createElementWithClass = function(elementName, className, customElementType) { 67 | return document.createElementWithClass(elementName, className, customElementType); 68 | }; 69 | 70 | 71 | /** 72 | * @param {string} elementName 73 | * @param {string=} className 74 | * @param {string=} customElementType 75 | * @return {!Element} 76 | */ 77 | Element.prototype.createChild = function(elementName, className, customElementType) { 78 | const element = this.ownerDocument.createElementWithClass(elementName, className, customElementType); 79 | this.appendChild(element); 80 | return element; 81 | }; 82 | 83 | 84 | Element.prototype.removeChildren = function() { 85 | if (this.firstChild) { 86 | this.textContent = ''; 87 | } 88 | }; 89 | 90 | 91 | /** 92 | * @return {!Window} 93 | */ 94 | Node.prototype.window = function() { 95 | return /** @type {!Window} */ (this.ownerDocument.defaultView); 96 | }; 97 | 98 | 99 | /** 100 | * @param {!Array.} nameArray 101 | * @return {?Node} 102 | */ 103 | Node.prototype.enclosingNodeOrSelfWithNodeNameInArray = function(nameArray) { 104 | // eslint-disable-next-line consistent-this 105 | for (let node = this; node && node !== this.ownerDocument; node = node.parentNodeOrShadowHost()) { 106 | for (let i = 0; i < nameArray.length; ++i) { 107 | if (node.nodeName.toLowerCase() === nameArray[i].toLowerCase()) { 108 | return node; 109 | } 110 | } 111 | } 112 | return null; 113 | }; 114 | 115 | /** 116 | * @param {string} nodeName 117 | * @return {?Node} 118 | */ 119 | Node.prototype.enclosingNodeOrSelfWithNodeName = function(nodeName) { 120 | return this.enclosingNodeOrSelfWithNodeNameInArray([nodeName]); 121 | }; 122 | 123 | 124 | /** 125 | * @return {?Node} 126 | */ 127 | Node.prototype.parentNodeOrShadowHost = function() { 128 | if (this.parentNode) { 129 | return this.parentNode; 130 | } 131 | if (this.nodeType === Node.DOCUMENT_FRAGMENT_NODE && this.host) { 132 | return this.host; 133 | } 134 | return null; 135 | }; 136 | 137 | /** 138 | * @return {number} 139 | */ 140 | Element.prototype.totalOffsetLeft = function() { 141 | return this.totalOffset().left; 142 | }; 143 | 144 | /** 145 | * @return {!{left: number, top: number}} 146 | */ 147 | Element.prototype.totalOffset = function() { 148 | const rect = this.getBoundingClientRect(); 149 | return {left: rect.left, top: rect.top}; 150 | }; 151 | 152 | 153 | /** 154 | * @param {boolean=} preventDefault 155 | */ 156 | Event.prototype.consume = function(preventDefault) { 157 | this.stopImmediatePropagation(); 158 | if (preventDefault) { 159 | this.preventDefault(); 160 | } 161 | this.handled = true; 162 | }; 163 | 164 | /** 165 | * @param {!Event} event 166 | * @return {boolean} 167 | */ 168 | window.isEnterKey = function(event) { 169 | // Check if in IME. 170 | return event.keyCode !== 229 && event.key === 'Enter'; 171 | }; 172 | 173 | /** 174 | * @return {boolean} 175 | */ 176 | Element.prototype.isScrolledToBottom = function() { 177 | // This code works only for 0-width border. 178 | // The scrollTop, clientHeight and scrollHeight are computed in double values internally. 179 | // However, they are exposed to javascript differently, each being either rounded (via 180 | // round, ceil or floor functions) or left intouch. 181 | // This adds up a total error up to 2. 182 | return Math.abs(this.scrollTop + this.clientHeight - this.scrollHeight) <= 2; 183 | }; 184 | 185 | 186 | Object.defineProperty(Array.prototype, 'upperBound', { 187 | /** 188 | * Return index of the leftmost element that is greater 189 | * than the specimen object. If there's no such element (i.e. all 190 | * elements are smaller or equal to the specimen) returns right bound. 191 | * The function works for sorted array. 192 | * When specified, |left| (inclusive) and |right| (exclusive) indices 193 | * define the search window. 194 | * 195 | * @param {!T} object 196 | * @param {function(!T,!S):number=} comparator 197 | * @param {number=} left 198 | * @param {number=} right 199 | * @return {number} 200 | * @this {Array.} 201 | * @template T,S 202 | */ 203 | value: function(object, comparator, left, right) { 204 | function defaultComparator(a, b) { 205 | return a < b ? -1 : (a > b ? 1 : 0); 206 | } 207 | comparator = comparator || defaultComparator; 208 | let l = left || 0; 209 | let r = right !== undefined ? right : this.length; 210 | while (l < r) { 211 | const m = (l + r) >> 1; 212 | if (comparator(object, this[m]) >= 0) { 213 | l = m + 1; 214 | } else { 215 | r = m; 216 | } 217 | } 218 | return r; 219 | }, 220 | configurable: true 221 | }); 222 | 223 | /** 224 | * @param {number} num 225 | * @param {number} min 226 | * @param {number} max 227 | * @return {number} 228 | */ 229 | Number.constrain = function(num, min, max) { 230 | if (num < min) { 231 | num = min; 232 | } else if (num > max) { 233 | num = max; 234 | } 235 | return num; 236 | }; 237 | -------------------------------------------------------------------------------- /app/scripts/modules/utils/EntriesLog.js: -------------------------------------------------------------------------------- 1 | const ODataNode = require('./ODataNode.js'); 2 | const multipartmixed2har = require('./multipartmixed2har.js'); 3 | const formatXML = require('prettify-xml'); 4 | 5 | /** 6 | * @constructor 7 | */ 8 | function EntriesLog () { 9 | this.oEditorContent = {}; 10 | this.oNoResponseMessage = {}; 11 | this._index = 0; 12 | } 13 | 14 | /** 15 | * Creates Log entry node. 16 | * @param {Object} entry - OData entry 17 | * @returns {Object} HTML Node 18 | */ 19 | EntriesLog.prototype.getEntryNode = function (entry) { 20 | let oNode; 21 | let aNodes = []; 22 | 23 | /** 24 | * Finds current OData version. 25 | * @param {Object} el - headers element 26 | */ 27 | const odataVersion = entry.response.headers.find(el => el.name.toLowerCase() === 'odata-version' || el.name.toLowerCase() === 'dataserviceversion'); 28 | 29 | if (odataVersion && ( 30 | odataVersion.value === '4.0' || 31 | odataVersion.value === '3.0' || 32 | odataVersion.value === '2.0' 33 | )) { 34 | const contentIndex = this._nextIndex(); 35 | const bIsBatch = entry.response.content.mimeType.includes('multipart/mixed'); 36 | const classes = !( 37 | entry.request.method === 'HEAD' || 38 | bIsBatch 39 | ) && 'clickable' || ''; 40 | const options = { 41 | id: contentIndex, 42 | classes: classes, 43 | url: entry.request.url, 44 | status: entry.response.status, 45 | method: entry.request.method, 46 | note: `${this._formatDateTime(entry.startedDateTime)} : ${this._formatDuration(entry.time)} ms`, 47 | isBatch: bIsBatch 48 | }; 49 | bIsBatch && (options.classes += ' batch'); 50 | oNode = this._createNode(options); 51 | 52 | if (entry.response.content.mimeType.includes('application/xml')) { 53 | /** 54 | * @param {Object} content 55 | */ 56 | multipartmixed2har.getContent(entry).then(content => { 57 | this.oEditorContent[contentIndex] = {type: 'xml', content: formatXML(content)}; 58 | }); 59 | } else if (bIsBatch) { 60 | const serviceUrl = entry.request.url.split('$batch')[0]; 61 | /** 62 | * @param {Array} childEntries 63 | */ 64 | multipartmixed2har.extractMultipartEntry(entry).then(childEntries => { 65 | aNodes = this._showEmbeddedRequests(childEntries, serviceUrl); 66 | this.oNoResponseMessage[contentIndex] = 'See the split responses of this batch request'; 67 | aNodes.forEach(function (oChildNode) { 68 | Array.isArray(oChildNode) ? oNode.appendChild(oChildNode[0]) : oNode.appendChild(oChildNode); 69 | }); 70 | }); 71 | 72 | } else if (entry.response.content.mimeType.includes('application/json')) { 73 | delete entry._initiator; 74 | /** 75 | * @param {Object} content 76 | */ 77 | multipartmixed2har.getContent(entry).then(content => { 78 | entry.response._content = JSON.parse(content || '{}'); 79 | this.oEditorContent[contentIndex] = {type: 'json', content: JSON.stringify(entry, null, 2)}; 80 | }); 81 | } else if (entry.response.content.mimeType.includes('text/plain')) { 82 | /** 83 | * @param {Object} content 84 | */ 85 | multipartmixed2har.getContent(entry).then(content => { 86 | this.oEditorContent[contentIndex] = {type: 'text', content: content}; 87 | }); 88 | } 89 | } else if (entry.response.status > 299 && entry.response.content.mimeType.includes('application/xml')) { 90 | const contentIndex = this._nextIndex(); 91 | const options = { 92 | id: contentIndex, 93 | classes: 'clickable error', 94 | url: entry.request.url, 95 | status: entry.response.status, 96 | method: entry.request.method, 97 | note: `${entry.startedDateTime}: ${entry.time} ms` 98 | }; 99 | oNode = this._createNode(options); 100 | 101 | /** 102 | * @param {Object} content 103 | */ 104 | multipartmixed2har.getContent(entry).then(content => { 105 | this.oEditorContent[contentIndex] = {type: 'xml', content: formatXML(content)}; 106 | }); 107 | } else if (entry._error === 'net::ERR_CONNECTION_REFUSED') { 108 | const contentIndex = this._nextIndex(); 109 | const options = { 110 | classes: 'error', 111 | url: entry.request.url, 112 | status: entry.response.status, 113 | method: entry.request.method 114 | }; 115 | oNode = this._createNode(options); 116 | this.oNoResponseMessage[contentIndex] = 'Check if the server went down or the network was interrupted'; 117 | } 118 | 119 | return oNode; 120 | }; 121 | 122 | /** 123 | * Shows embedded requests. 124 | * @param {Array} entries 125 | * @param {string} serviceUrl 126 | * @param {string} prefix 127 | * @returns {Array} mapped entries 128 | * @private 129 | */ 130 | EntriesLog.prototype._showEmbeddedRequests = function (entries, serviceUrl, prefix) { 131 | /** 132 | * Maps entry. 133 | * @param {Object} entry 134 | */ 135 | return entries.map(entry => { 136 | if (entry.children) { 137 | return this._showEmbeddedRequests(entry.children, serviceUrl, entry.changeset); 138 | } else { 139 | const contentIndex = this._nextIndex(); 140 | const classes = 'clickable secondLevel' + (!entry.response || entry.response.status === 499) && 'warning' || 141 | (entry.response && entry.response.status > 299 && ' error' || '' ); 142 | 143 | this.oEditorContent[contentIndex] = {type: 'json', content: JSON.stringify(entry, null, 2)}; 144 | const options = { 145 | id: contentIndex, 146 | classes: classes, 147 | url: `${prefix ? prefix + '-> ' : ''} ${entry.request.url}`, 148 | status: entry.response.status, 149 | method: entry.request.method, 150 | note: `${entry.response.headers.location ? '
   -> ' + entry.response.headers.location : ''}` 151 | }; 152 | 153 | return this._createNode(options); 154 | } 155 | }); 156 | }; 157 | 158 | /** 159 | * Returns editor content. 160 | * @param {number} iSelectedId 161 | * @returns {Object} editor content 162 | */ 163 | EntriesLog.prototype.getEditorContent = function (iSelectedId) { 164 | return this.oEditorContent[iSelectedId]; 165 | }; 166 | 167 | /** 168 | * Returns editor content. 169 | * @param {number} iSelectedId 170 | * @returns {string} No response message 171 | */ 172 | EntriesLog.prototype.getNoResponseMessage = function (iSelectedId) { 173 | return this.oNoResponseMessage[iSelectedId]; 174 | }; 175 | 176 | /** 177 | * Formats Datetime. 178 | * @param {Object} x - Datetime 179 | * @returns {Object} Datetime 180 | * @private 181 | */ 182 | EntriesLog.prototype._formatDateTime = function (x) { 183 | return x.match(/.+T(.+)Z/).pop(); 184 | }; 185 | 186 | /** 187 | * Formats Duration. 188 | * @param {Object} x - Datetime 189 | * @returns {number} Duration 190 | * @private 191 | */ 192 | EntriesLog.prototype._formatDuration = function (x) { 193 | return x.toPrecision(7); 194 | }; 195 | 196 | /** 197 | * Return next Entry log index. 198 | * @returns {number} Index 199 | * @private 200 | */ 201 | EntriesLog.prototype._nextIndex = function () { 202 | return this._index++; 203 | }; 204 | 205 | /** 206 | * Creates ODataNode. 207 | * @param {Object} options - settings 208 | * @returns {Object} ODataNode 209 | * @private 210 | */ 211 | EntriesLog.prototype._createNode = function (options) { 212 | options.name = options.url.split('/').pop(); 213 | 214 | return new ODataNode(options); 215 | }; 216 | 217 | module.exports = EntriesLog; 218 | -------------------------------------------------------------------------------- /app/scripts/modules/utils/ODataNode.js: -------------------------------------------------------------------------------- 1 | /* globals createElementWithClass */ 2 | 3 | const DataGrid = require('../ui/datagrid/DataGrid.js'); 4 | 5 | const BATCH_ICON = 128194; 6 | const REQUEST_ICON = 128463; 7 | 8 | class ODataNode extends DataGrid.SortableDataGridNode { 9 | 10 | createCell(columnId) { 11 | const cell = super.createCell(columnId); 12 | if (columnId === 'name') { 13 | this._renderPrimaryCell(cell, columnId); 14 | } 15 | 16 | return cell; 17 | } 18 | 19 | _renderPrimaryCell(cell) { 20 | const iconElement = createElementWithClass('span', 'icon'); 21 | const iSymbol = (this.data.isBatch) ? BATCH_ICON : REQUEST_ICON; 22 | const sClass = (this.data.isBatch) ? 'batchIcon' : 'requestIcon'; 23 | 24 | iconElement.classList.add(sClass); 25 | iconElement.innerHTML = `&#${iSymbol}`; 26 | cell.prepend(iconElement); 27 | 28 | cell.title = this.data.url; 29 | } 30 | } 31 | 32 | module.exports = ODataNode; 33 | -------------------------------------------------------------------------------- /app/scripts/modules/utils/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @typedef {Object} resolveMessageOptions 5 | * @property {Object} message - port.onMessage.addListener parameter 6 | * @property {Object} messageSender - port.onMessage.addListener parameter 7 | * @property {Object} sendResponse - port.onMessage.addListener parameter 8 | * @property {Object} actions - Object with all the needed actions as methods 9 | */ 10 | 11 | /** 12 | * Calls the needed message action. 13 | * @param {resolveMessageOptions} options 14 | * @private 15 | */ 16 | function _resolveMessage(options) { 17 | if (!options) { 18 | return; 19 | } 20 | 21 | var message = options.message; 22 | var messageSender = options.messageSender; 23 | var sendResponse = options.sendResponse; 24 | var actions = options.actions; 25 | var messageHandlerFunction = actions[message.action]; 26 | 27 | if (messageHandlerFunction) { 28 | messageHandlerFunction(message, messageSender, sendResponse); 29 | } 30 | } 31 | 32 | /** 33 | * Convert UI5 timestamp to readable date. 34 | * @param {string} timeStamp - timestamp in UI5 format ("20150427-1201") 35 | * @returns {string|undefined} 36 | * @private 37 | */ 38 | function _convertUI5TimeStampToHumanReadableFormat(timeStamp) { 39 | var formattedTime = ''; 40 | 41 | if (!timeStamp) { 42 | return; 43 | } 44 | 45 | timeStamp = timeStamp.replace(/-/g, ''); 46 | 47 | // Year 48 | formattedTime += timeStamp.substr(0, 4) + '/'; 49 | // Month 50 | formattedTime += timeStamp.substr(4, 2) + '/'; 51 | // Date 52 | formattedTime += timeStamp.substr(6, 2); 53 | 54 | formattedTime += ' '; 55 | 56 | // Hour 57 | formattedTime += timeStamp.substr(8, 2) + ':'; 58 | // Minutes 59 | formattedTime += timeStamp.substr(10, 2) + 'h'; 60 | 61 | return formattedTime; 62 | } 63 | 64 | /** 65 | * Set specific class for each OS. 66 | * @private 67 | */ 68 | function _setOSClassNameToBody() { 69 | // Set a body attribute for detecting and styling according the OS 70 | var osName = ''; 71 | if (navigator.appVersion.indexOf('Win') !== -1) { 72 | osName = 'windows'; 73 | } 74 | if (navigator.appVersion.indexOf('Mac') !== -1) { 75 | osName = 'mac'; 76 | } 77 | if (navigator.appVersion.indexOf('Linux') !== -1) { 78 | osName = 'linux'; 79 | } 80 | 81 | document.querySelector('body').setAttribute('os', osName); 82 | } 83 | 84 | /** 85 | * Applies the theme. Default is light. 86 | * @private 87 | */ 88 | function _applyTheme(theme) { 89 | var oldLink = document.getElementById('ui5inspector-theme'); 90 | var head = document.getElementsByTagName('head')[0]; 91 | var link = document.createElement('link'); 92 | var url = '/styles/themes/light/light.css'; 93 | 94 | if (oldLink) { 95 | oldLink.remove(); 96 | } 97 | 98 | if (theme === 'dark') { 99 | url = '/styles/themes/dark/dark.css'; 100 | } 101 | 102 | link.id = 'ui5inspector-theme'; 103 | link.rel = 'stylesheet'; 104 | link.href = url; 105 | 106 | head.appendChild(link); 107 | } 108 | 109 | function _isObjectEmpty(obj) { 110 | return Object.keys(obj).length === 0 && obj.constructor === Object; 111 | } 112 | 113 | function _getPort () { 114 | return { 115 | postMessage: function (message, callback) { 116 | chrome.runtime.sendMessage(message, callback); 117 | }, 118 | onMessage: function (callback) { 119 | chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) { 120 | callback(request, sender, sendResponse); 121 | }); 122 | } 123 | }; 124 | } 125 | 126 | /** 127 | * Send message to all ports listening. 128 | * @param {Object} message 129 | */ 130 | function _sendToAll(message, callback) { 131 | var frameId = message.frameId; 132 | var options; 133 | chrome.windows.getCurrent().then(w => { 134 | chrome.tabs.query({ active: true, windowId: w.id }).then(tabs => { 135 | // options.frameId allows to send the message to 136 | // a specific frame instead of all frames in the tab 137 | if (frameId !== undefined) { 138 | options = { frameId }; 139 | } 140 | chrome.tabs.sendMessage(tabs[0].id, message, options, callback); 141 | }); 142 | }); 143 | } 144 | 145 | module.exports = { 146 | formatter: { 147 | convertUI5TimeStampToHumanReadableFormat: _convertUI5TimeStampToHumanReadableFormat 148 | }, 149 | resolveMessage: _resolveMessage, 150 | setOSClassName: _setOSClassNameToBody, 151 | applyTheme: _applyTheme, 152 | isObjectEmpty: _isObjectEmpty, 153 | getPort: _getPort, 154 | sendToAll: _sendToAll 155 | }; 156 | -------------------------------------------------------------------------------- /app/scripts/popup/popup.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var utils = require('../modules/utils/utils.js'); 4 | 5 | window.chrome.tabs.query({active: true, currentWindow: true}).then(function (tabs) { 6 | // Create a port with background page for continuous message communication 7 | var port = utils.getPort(); 8 | 9 | // Name space for message handler functions. 10 | var messageHandler = { 11 | 12 | /** 13 | * Ask for the framework information, as soon as the main script is injected. 14 | * @param {Object} message 15 | */ 16 | 'on-main-script-injection': function (message) { 17 | port.postMessage({action: 'get-framework-information'}); 18 | }, 19 | 20 | /** 21 | * Visualize the framework information. 22 | * @param {Object} message 23 | */ 24 | 'on-framework-information': function (message) { 25 | var linksDom; 26 | var library: HTMLElement = document.querySelector('library'); 27 | var buildtime: HTMLElement = document.querySelector('buildtime'); 28 | 29 | if (message.frameworkInformation.OpenUI5) { 30 | linksDom = document.querySelector('links[openui5]'); 31 | library.innerText = 'OpenUI5'; 32 | buildtime.innerText = message.frameworkInformation.OpenUI5; 33 | } else { 34 | linksDom = document.querySelector('links[sapui5]'); 35 | library.innerText = 'SAPUI5'; 36 | buildtime.innerText = message.frameworkInformation.SAPUI5; 37 | } 38 | 39 | linksDom.removeAttribute('hidden'); 40 | } 41 | }; 42 | 43 | // Listen for messages from the background page 44 | port.onMessage(function (message, messageSender, sendResponse) { 45 | // Resolve incoming messages 46 | utils.resolveMessage({ 47 | message: message, 48 | messageSender: messageSender, 49 | sendResponse: sendResponse, 50 | actions: messageHandler 51 | }); 52 | }); 53 | 54 | port.postMessage({ 55 | action: 'do-script-injection', 56 | tabId: tabs[0].id, 57 | file: '/scripts/content/main.js' 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /app/styles/less/modules/ControlTree.less: -------------------------------------------------------------------------------- 1 | /* ========================================================================== 2 | ControlTree 3 | ========================================================================== */ 4 | 5 | @filter-height: 30px; 6 | 7 | control-tree { 8 | display: flex; 9 | flex-direction: column; 10 | overflow: hidden; 11 | padding-top: @filter-height; 12 | position: relative; 13 | flex: 1 1 0; 14 | flex-wrap: nowrap; 15 | 16 | filter { 17 | border-bottom: solid 1px @border-grey-light; 18 | display: flex; 19 | left: 0; 20 | line-height: 1; 21 | min-width: 550px; 22 | padding: 5px; 23 | position: absolute; 24 | right: 0; 25 | top: 0; 26 | align-items: center; 27 | } 28 | 29 | filter > start, 30 | filter > end { 31 | display: flex; 32 | } 33 | 34 | filter > start { 35 | flex: 1 1 0; 36 | } 37 | 38 | filter [type="search"] { 39 | border: solid 1px @border-gray-dark; 40 | border-radius: 2px; 41 | background-color: @bg-search-field; 42 | color: @text-color; 43 | padding-left: 5px; 44 | padding-right: 5px; 45 | } 46 | 47 | filter [type="checkbox"] { 48 | margin-right: 2px; 49 | } 50 | 51 | filter label { 52 | display: flex; 53 | line-height: 20px; 54 | padding: 0 6px; 55 | white-space: nowrap; 56 | align-items: center; 57 | } 58 | 59 | tree { 60 | margin: 0; 61 | overflow-x: hidden; 62 | padding-top: 5px; 63 | transform: translateZ(0); 64 | flex: 1 1 0; 65 | } 66 | 67 | tree > :last-child { 68 | margin-bottom: 10px; 69 | } 70 | 71 | tree:not([show-filtered-elements]) { 72 | 73 | [matching] { 74 | background: @bg-highlight-color; 75 | } 76 | 77 | [selected] { 78 | background: @lighter-blue; 79 | ::selection { 80 | background: @gray-dark; 81 | } 82 | } 83 | 84 | } 85 | 86 | [show-namespaces] { 87 | namespace { 88 | display: inline 89 | } 90 | } 91 | 92 | [show-attributes] { 93 | attribute { 94 | display: inline 95 | } 96 | } 97 | 98 | [show-filtered-elements] { 99 | li { 100 | display: none; 101 | } 102 | 103 | [visible], 104 | [matching] { 105 | display: flex; 106 | } 107 | 108 | } 109 | 110 | ul { 111 | display: flex; 112 | flex-direction: column; 113 | 114 | & > ul { 115 | display: none; 116 | } 117 | } 118 | 119 | ul[expanded] > ul { 120 | display: flex; 121 | } 122 | 123 | li { 124 | border-radius: 5px; 125 | display: flex; 126 | line-height: @line-height; 127 | padding-top: 1px; 128 | position: relative; 129 | word-break: break-word; 130 | 131 | &:hover { 132 | background-color: @hover-highlight-color; 133 | cursor: default; 134 | } 135 | 136 | offset { 137 | width: 0; 138 | padding-right: 12px; 139 | } 140 | 141 | &[selected] { 142 | background: @lighter-blue; 143 | border-radius: 0; 144 | 145 | tag, 146 | attribute, 147 | attribute-value, 148 | version { 149 | color: @bg-selected-element-color; 150 | } 151 | 152 | arrow { 153 | &[right] { 154 | background-image: url("/images/arrows/arrow-right-white.png"); 155 | } 156 | 157 | &[down] { 158 | background-image: url("/images/arrows/arrow-down-white.png"); 159 | } 160 | } 161 | } 162 | } 163 | 164 | version { 165 | color: @gray; 166 | padding-left: 10px; 167 | } 168 | 169 | namespace, 170 | attribute { 171 | display: none; 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /app/styles/less/modules/ControllerDetailView.less: -------------------------------------------------------------------------------- 1 | xml-view { 2 | height: 100%; 3 | } 4 | 5 | #elements-registry-control-controller { 6 | .editorAlt { 7 | display: flex; 8 | div { 9 | cursor: pointer; 10 | } 11 | } 12 | 13 | div.hidden { 14 | display: none !important; 15 | } 16 | 17 | .editorAlt { 18 | position: absolute; 19 | top: .5rem; 20 | left: .5rem; 21 | right: .5rem; 22 | bottom: .5rem; 23 | height: auto; 24 | } 25 | 26 | .firstColAlignment { 27 | text-align: end; 28 | } 29 | 30 | .longTextReduce { 31 | text-overflow: ellipsis; 32 | white-space: nowrap; 33 | overflow: hidden; 34 | } 35 | 36 | #controllerEditor{ 37 | display: grid; 38 | grid-template-columns: 25% auto; 39 | gap: .5rem; 40 | padding: .5rem; 41 | } 42 | } -------------------------------------------------------------------------------- /app/styles/less/modules/DataGrid.less: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2008 Apple Inc. All Rights Reserved. 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions 6 | * are met: 7 | * 1. Redistributions of source code must retain the above copyright 8 | * notice, this list of conditions and the following disclaimer. 9 | * 2. Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * 13 | * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY 14 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR 17 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 20 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 21 | * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | */ 25 | 26 | .data-grid { position: relative; line-height: 120%; border: 1px solid rgb(204, 204, 204) !important; } 27 | .data-grid table { table-layout: fixed; border-spacing: 0px; border-collapse: separate; height: 100%; width: 100%; } 28 | .data-grid .header-container, .data-grid .data-container { position: absolute; left: 0px; right: 0px; overflow-x: hidden; } 29 | .data-grid .header-container { top: 0px; height: 21px; } 30 | .data-grid .data-container { top: 21px; bottom: 0px; overflow-y: overlay; transform: translateZ(0px); } 31 | .data-grid .aria-live-label { width: 1px; height: 1px; overflow: hidden; } 32 | .data-grid.inline .header-container, .data-grid.inline .data-container { position: static; } 33 | .data-grid.inline .corner { display: none; } 34 | .platform-mac .data-grid .corner, .data-grid.data-grid-fits-viewport .corner { display: none; } 35 | .data-grid .corner { width: 14px; padding-right: 0px; padding-left: 0px; border-left: 0px none transparent !important; } 36 | .data-grid .top-filler-td, .data-grid .bottom-filler-td { height: auto !important; padding: 0px !important; } 37 | .data-grid table.data { position: absolute; left: 0px; top: 0px; right: 0px; bottom: 0px; border-top: 0px none transparent; table-layout: fixed; } 38 | .data-grid.inline table.data { position: static; } 39 | .data-grid table.data tr { display: none; height: 20px; } 40 | .data-grid table.data tr.revealed { display: table-row; background-color: @bg-datagrid; &.data-grid-data-grid-node:hover { background-color: @bg-datagrid-tr-hover; cursor: pointer; }} 41 | .data-grid table.data tr.revealed.odd { background-color: @bg-datagrid-row; } 42 | .striped-data-grid .revealed.data-grid-data-grid-node:nth-child(2n+1), .striped-data-grid-starts-with-odd .revealed.data-grid-data-grid-node:nth-child(2n) { background-color: rgb(242, 247, 253); } 43 | .data-grid td, .data-grid th { white-space: nowrap; text-overflow: ellipsis; overflow: hidden; line-height: 18px; height: 18px; border-left: 1px solid rgb(170, 170, 170); padding: 1px 4px; } 44 | .data-grid th:first-child, .data-grid td:first-child { border-left: none !important; } 45 | .data-grid td { vertical-align: top; user-select: text; } 46 | .data-grid th { text-align: left; background-color: @bg-datagrid-row; border-bottom: 1px solid rgb(170, 170, 170); font-weight: normal; vertical-align: middle; color: @text-color } 47 | .data-grid td > div, .data-grid th > div { white-space: nowrap; text-overflow: ellipsis; overflow: hidden; } 48 | .data-grid td.editing > div { text-overflow: clip; } 49 | .data-grid .center { text-align: center; } 50 | .data-grid .right { text-align: right; } 51 | .data-grid th.sortable { position: relative; } 52 | .data-grid th.sortable:active::after { content: ""; position: absolute; left: 0px; right: 0px; top: 0px; bottom: 0px; background-color: rgba(0, 0, 0, 0.15); } 53 | .data-grid th .sort-order-icon-container { position: absolute; top: 1px; right: 0px; bottom: 1px; display: flex; align-items: center; } 54 | .data-grid th .sort-order-icon { margin-right: 4px; margin-bottom: -2px; display: none; } 55 | .data-grid th.sort-ascending .sort-order-icon, .data-grid th.sort-descending .sort-order-icon { display: block; } 56 | .data-grid th.sortable:hover { background-color: @bg-datagrid-th-sortable; } 57 | .data-grid .top-filler-td { border-bottom: 0px none transparent; line-height: 0; } 58 | .data-grid button { line-height: 18px; color: inherit; } 59 | .data-grid td.disclosure::before { user-select: none; -webkit-mask-image: url("/images/treeoutlineTriangles.svg"); -webkit-mask-position: 0px 0px; -webkit-mask-size: 32px 24px; float: left; width: 8px; height: 12px; margin-right: 2px; content: ""; position: relative; top: 3px; background-color: rgb(110, 110, 110); } 60 | .data-grid tr:not(.parent) td.disclosure::before { background-color: transparent; } 61 | .data-grid tr.expanded td.disclosure::before { -webkit-mask-position: -16px 0px; } 62 | .data-grid table.data tr.revealed.selected.data-grid-data-grid-node { background-color: @lighter-blue; color: var(--selection-fg-color); } 63 | .data-grid:focus table.data tr.selected { background-color: @lighter-blue; color: var(--selection-fg-color); } 64 | .data-grid:focus tr.selected .devtools-link { color: var(--selection-fg-color); } 65 | .data-grid:focus tr.parent.selected td.disclosure::before { background-color: var(--selection-fg-color); -webkit-mask-position: 0px 0px; } 66 | .data-grid:focus tr.expanded.selected td.disclosure::before { background-color: var(--selection-fg-color); -webkit-mask-position: -16px 0px; } 67 | .data-grid tr.inactive { color: rgb(128, 128, 128); font-style: italic; } 68 | .data-grid tr.dirty { background-color: rgb(255, 214, 214); color: red; font-style: normal; } 69 | .data-grid:focus tr.selected.dirty { background-color: rgb(255, 102, 102); } 70 | .data-grid td.show-more { white-space: normal; } 71 | .data-grid td.show-more::before { display: none; } 72 | .data-grid-resizer { position: absolute; top: 0px; bottom: 0px; width: 5px; z-index: 500; } 73 | 74 | 75 | :root { height: 100%; overflow: hidden; } 76 | :root { --accent-color: #1a73e8; --accent-fg-color: #1a73e8; --accent-color-hover: #3b86e8; --active-control-bg-color: #5a5a5a; --focus-bg-color: hsl(214, 40%, 92%); --input-validation-error: #db1600; --toolbar-bg-color: #f3f3f3; --toolbar-hover-bg-color: #eaeaea; --selection-fg-color: white; --selection-inactive-fg-color: #5a5a5a; --selection-inactive-bg-color: #dadada; --tab-selected-fg-color: #333; --tab-selected-bg-color: var(--toolbar-bg-color); --drop-shadow: 0 0 0 1px rgba(0, 0, 0, 0.05), 0 2px 4px rgba(0, 0, 0, 0.2), 0 2px 6px rgba(0, 0, 0, 0.1); --divider-color: #d0d0d0; --focus-ring-inactive-shadow: 0 0 0 1px #e0e0e0; --item-selection-bg-color: #cfe8fc; --item-selection-inactive-bg-color: #e0e0e0; } 77 | :root { --focus-ring-active-shadow: 0 0 0 1px var(--accent-color); --selection-bg-color: var(--accent-color); --divider-border: 1px solid var(--divider-color); --item-hover-color: rgba(56, 121, 217, 0.1); } 78 | -------------------------------------------------------------------------------- /app/styles/less/modules/DataView.less: -------------------------------------------------------------------------------- 1 | data-view { 2 | flex-grow: 1; 3 | flex-wrap: nowrap; 4 | overflow: auto; 5 | width: 100%; 6 | -webkit-transform: translateZ(0); 7 | word-break: break-all; 8 | 9 | clickable-value, 10 | .controlId { 11 | color: @light-blue; 12 | } 13 | 14 | anchor, 15 | clickable-value, 16 | .controlId { 17 | cursor: pointer; 18 | text-decoration: underline; 19 | } 20 | 21 | anchor:hover, 22 | clickable-value:hover, 23 | .controlId:hover { 24 | text-decoration: none; 25 | } 26 | 27 | arrow { 28 | display: inline-block; 29 | vertical-align: top; 30 | } 31 | 32 | arrow~section-title{ 33 | cursor: default; 34 | } 35 | 36 | & ul no-data { 37 | font-style: normal; 38 | padding: 0; 39 | text-align: left; 40 | } 41 | 42 | & ul li padding-base-left { 43 | padding-left: @padding-base-horizontal; 44 | } 45 | 46 | & > ul { 47 | border-bottom: 1px solid @gray-lighter; 48 | padding: 2px 2px 4px 4px; 49 | } 50 | 51 | key { 52 | white-space: nowrap; 53 | } 54 | 55 | li { 56 | & > ul { 57 | display: none; 58 | padding-left: 19px; 59 | padding-top: 2px; 60 | 61 | & section-title { 62 | color: @red-saturated 63 | } 64 | } 65 | 66 | & > ul[expanded] { 67 | display: flex; 68 | flex-direction: column; 69 | } 70 | 71 | & > ul[expanded] ~ collapsed-typeinfo { 72 | display: none; 73 | } 74 | } 75 | 76 | section-title{ 77 | display: inline-block; 78 | } 79 | 80 | value[contentEditable=true]:focus { 81 | display: inline-block; 82 | outline: 1px solid @gray; 83 | outline-offset: 2px; 84 | box-shadow: 0 0 0 2px #fff, 0 1px 2px 3px rgba(0,0,0,0.6); 85 | } 86 | 87 | [gray] { 88 | color: @gray; 89 | } 90 | button.tools-button { 91 | display: block; 92 | } 93 | 94 | .disclaimer { 95 | font-size: 10px; 96 | padding-left: 5px; 97 | color: lightcoral; 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /app/styles/less/modules/ElementsRegistry.less: -------------------------------------------------------------------------------- 1 | 2 | elements-registry-master-view { 3 | display: flex; 4 | overflow: hidden; 5 | position: relative; 6 | width: 100%; 7 | 8 | #elementsRegistryContent { 9 | display: flex; 10 | flex-direction: column; 11 | flex: 1 1 0; 12 | flex-wrap: nowrap; 13 | } 14 | 15 | span[is="dt-icon-label"] { flex: 0 0 auto; } 16 | [is="ui-icon"] { display: inline-block; flex-shrink: 0; } 17 | [is="ui-icon"].icon-mask { background-color: rgb(110, 110, 110); -webkit-mask-position: var(--spritesheet-position); } 18 | .spritesheet-smallicons:not(.icon-mask) { background-image: url("/images/smallIcons.svg"); } 19 | .spritesheet-smallicons.icon-mask { -webkit-mask-image: url("/images/smallIcons.svg"); } 20 | .spritesheet-largeicons.icon-mask { -webkit-mask-image: url("/images/largeIcons.svg"); } 21 | .spritesheet-mediumicons.icon-mask { -webkit-mask-image: url("/images/mediumIcons.svg"); } 22 | .spritesheet-mediumicons:not(.icon-mask) { background-image: url(/images/mediumIcons.svg); } 23 | :host-context(.force-white-icons) [is="ui-icon"].spritesheet-smallicons, .force-white-icons [is="ui-icon"].spritesheet-smallicons, [is="ui-icon"].force-white-icons.spritesheet-smallicons, -theme-preserve { -webkit-mask-image: url("/images/smallIcons.svg"); -webkit-mask-position: var(--spritesheet-position); background: rgb(250, 250, 250) !important; } 24 | ::selection { background-color: @bg-datagrid; } 25 | [data-aria-utils-animation-hack] { animation: 0s ease 0s 1 normal none running ANIMATION-HACK; } 26 | cursor: default; font-family: ".SFNSDisplay-Regular", "Helvetica Neue", "Lucida Grande", sans-serif; font-size: 12px; tab-size: 4; user-select: none; 27 | 28 | .largeicon-clear:hover:not(:active) { background-color: #333; } 29 | 30 | [is=ui-icon]:not(.icon-mask) { 31 | background-position: var(--spritesheet-position); 32 | } 33 | 34 | filter { 35 | border-bottom: solid 1px @border-grey-light; 36 | display: flex; 37 | left: 0; 38 | line-height: 1; 39 | padding: 5px; 40 | align-items: center; 41 | } 42 | 43 | filter > start, 44 | filter > end { 45 | display: flex; 46 | } 47 | 48 | filter > start { 49 | flex: 1 1 0; 50 | } 51 | 52 | filter [type="search"] { 53 | border: solid 1px @border-gray-dark; 54 | border-radius: 2px; 55 | background-color: @bg-search-field; 56 | color: @text-color; 57 | padding-left: 5px; 58 | padding-right: 5px; 59 | } 60 | 61 | filter [type="checkbox"] { 62 | margin-right: 2px; 63 | } 64 | 65 | filter label { 66 | display: flex; 67 | line-height: 20px; 68 | padding: 0 6px; 69 | white-space: nowrap; 70 | align-items: center; 71 | } 72 | 73 | } 74 | 75 | elements-registry-master-view:not([show-filtered-elements]) { 76 | .matching { 77 | background: @bg-highlight-color !important; 78 | } 79 | 80 | .selected { 81 | background: @lighter-blue !important; 82 | ::selection { 83 | background: @gray-dark !important; 84 | } 85 | } 86 | } 87 | 88 | .elementsRegistry splitter { 89 | background-color: @bg-datagrid-header; 90 | } 91 | -------------------------------------------------------------------------------- /app/styles/less/modules/FrameSelect.less: -------------------------------------------------------------------------------- 1 | /* ========================================================================== 2 | FrameSelect 3 | ========================================================================== */ 4 | 5 | frame-select { 6 | padding-top: 1px; 7 | padding-bottom: 1px; 8 | padding-left: 10px; 9 | } 10 | 11 | frame-select select { 12 | outline: 2px solid @lighter-blue; 13 | } 14 | -------------------------------------------------------------------------------- /app/styles/less/modules/JSONView.less: -------------------------------------------------------------------------------- 1 | [json] { 2 | key { 3 | color: @purple; 4 | } 5 | 6 | string { 7 | color: @gray-darkest; 8 | } 9 | 10 | number { 11 | color: @gray-darkest; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /app/styles/less/modules/ODataView.less: -------------------------------------------------------------------------------- 1 | .data-grid { 2 | height: 100%; 3 | } 4 | 5 | span.batchIcon, 6 | span.requestIcon { 7 | margin-left: 1px; 8 | margin-right: 4px; 9 | } 10 | 11 | span.requestIcon { 12 | font-size: 1rem; 13 | vertical-align: middle; 14 | } 15 | 16 | odata-master-view, 17 | odata-detail-view { 18 | height: 100%; 19 | } 20 | 21 | .odataTab { 22 | flex-direction: column; 23 | background-color: @bg-datagrid; 24 | color: @text-color; 25 | 26 | .data-grid:focus { 27 | outline: none; 28 | } 29 | 30 | h3, 31 | h4, 32 | p { 33 | margin-top: 2px; 34 | margin-bottom: 2px; 35 | } 36 | 37 | .error { 38 | color: red; 39 | } 40 | 41 | span.url { 42 | margin-right: 0.15rem; 43 | margin-left: 0.15rem; 44 | font-size: medium; 45 | vertical-align: middle; 46 | } 47 | 48 | tr.batch span.url { 49 | font-size: small; 50 | vertical-align: baseline; 51 | } 52 | 53 | td.url { 54 | padding-bottom: 0.3rem; 55 | padding-top: 0.3rem; 56 | } 57 | 58 | .secondLevel td.url { 59 | padding-left: 1rem; 60 | } 61 | 62 | tr:nth-child(even) { 63 | background-color: @bg-datagrid; 64 | } 65 | 66 | tr { 67 | padding: 0.05rem; 68 | } 69 | 70 | td.status, 71 | td.method { 72 | min-width: 30px; 73 | } 74 | 75 | td.note { 76 | min-width: 100px; 77 | } 78 | 79 | splitter.endVisible { 80 | td.note { 81 | display: none; 82 | } 83 | } 84 | 85 | tr.selected { 86 | background-color: Lightblue 87 | } 88 | 89 | div.hidden { 90 | visibility: hidden; 91 | } 92 | 93 | .editorAlt { 94 | display: flex; 95 | div { 96 | margin: auto; 97 | } 98 | } 99 | 100 | splitter { 101 | background-color: @bg-datagrid-header; 102 | } 103 | 104 | splitter start { 105 | flex-direction:column; 106 | } 107 | 108 | splitter > start clear-button { 109 | height: 1.3rem; 110 | width: 1.3rem; 111 | padding-top: 0.15rem; 112 | padding-left: 0.15rem; 113 | font-size: 0.9rem; 114 | cursor: pointer; 115 | opacity: 0.7; 116 | transition: opacity 0.2s; 117 | color: #838181; 118 | font-weight: bold; 119 | display: block; 120 | } 121 | 122 | splitter > start clear-button:hover { 123 | opacity: 1; 124 | } 125 | 126 | .data-grid, #editor, .editorAlt { 127 | position: absolute; 128 | top: 25px; 129 | left: 0; 130 | right: 0; 131 | bottom: 0; 132 | height: auto; 133 | } 134 | 135 | table { 136 | width: 100%; 137 | } 138 | } 139 | 140 | /* 141 | * Copyright (C) 2008 Apple Inc. All Rights Reserved. 142 | * 143 | * Redistribution and use in source and binary forms, with or without 144 | * modification, are permitted provided that the following conditions 145 | * are met: 146 | * 1. Redistributions of source code must retain the above copyright 147 | * notice, this list of conditions and the following disclaimer. 148 | * 2. Redistributions in binary form must reproduce the above copyright 149 | * notice, this list of conditions and the following disclaimer in the 150 | * documentation and/or other materials provided with the distribution. 151 | * 152 | * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY 153 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 154 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 155 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR 156 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 157 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 158 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 159 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 160 | * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 161 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 162 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 163 | */ 164 | .odataTab { 165 | * { min-width: 0px; min-height: 0px; } 166 | * { box-sizing: border-box; } 167 | :focus { outline-width: 0px; } 168 | input[type="radio"]:focus { outline: -webkit-focus-ring-color auto 5px; } 169 | img { -webkit-user-drag: none; } 170 | iframe, a img { border: none; } 171 | button, input, select { font-family: inherit; font-size: inherit; } 172 | input { background-color: white; color: inherit; } 173 | input::placeholder { color: rgba(0, 0, 0, 0.54); } 174 | span[is="dt-icon-label"] { flex: 0 0 auto; } 175 | [is="ui-icon"] { display: inline-block; flex-shrink: 0; } 176 | [is="ui-icon"].icon-mask { background-color: rgb(110, 110, 110); -webkit-mask-position: var(--spritesheet-position); } 177 | .spritesheet-smallicons:not(.icon-mask) { background-image: url("/images/smallIcons.svg"); } 178 | .spritesheet-smallicons.icon-mask { -webkit-mask-image: url("/images/smallIcons.svg"); } 179 | .spritesheet-largeicons.icon-mask { -webkit-mask-image: url("/images/largeIcons.svg"); } 180 | .spritesheet-mediumicons.icon-mask { -webkit-mask-image: url("/images/mediumIcons.svg"); } 181 | .spritesheet-mediumicons:not(.icon-mask) { background-image: url(/images/mediumIcons.svg); } 182 | :host-context(.force-white-icons) [is="ui-icon"].spritesheet-smallicons, .force-white-icons [is="ui-icon"].spritesheet-smallicons, [is="ui-icon"].force-white-icons.spritesheet-smallicons, -theme-preserve { -webkit-mask-image: url("/images/smallIcons.svg"); -webkit-mask-position: var(--spritesheet-position); background: rgb(250, 250, 250) !important; } 183 | ::selection { background-color: @bg-datagrid; } 184 | [data-aria-utils-animation-hack] { animation: 0s ease 0s 1 normal none running ANIMATION-HACK; } 185 | cursor: default; font-family: ".SFNSDisplay-Regular", "Helvetica Neue", "Lucida Grande", sans-serif; font-size: 12px; tab-size: 4; user-select: none; 186 | 187 | .largeicon-clear:hover:not(:active) { background-color: #333; } 188 | 189 | [is=ui-icon]:not(.icon-mask) { 190 | background-position: var(--spritesheet-position); 191 | } 192 | 193 | splitter > end > .toolbar-item { 194 | background: none; 195 | top: 0.15rem; 196 | left: 0.2rem; 197 | right: auto; 198 | opacity: 0.7; 199 | } 200 | 201 | .close-button { 202 | width: 14px; 203 | height: 14px; 204 | cursor: default; 205 | display: flex; 206 | align-items: center; 207 | justify-content: center; 208 | } 209 | 210 | .hover-icon, .active-icon { 211 | display: none; 212 | } 213 | 214 | .close-button:hover .default-icon, .close-button:active .default-icon { 215 | display: none; 216 | } 217 | 218 | .close-button:hover .hover-icon { 219 | display: block; 220 | } 221 | 222 | .close-button[data-keyboard-focus="true"]:focus .default-icon, .close-button:active .default-icon { 223 | display: none; 224 | } 225 | 226 | .close-button[data-keyboard-focus="true"]:focus .hover-icon { 227 | display: block; 228 | } 229 | 230 | .close-button:active .hover-icon { 231 | display: none !important; 232 | } 233 | 234 | .close-button:active .active-icon { 235 | display: block; 236 | } 237 | 238 | .toolbar-item { 239 | position: relative; 240 | display: flex; 241 | background-color: transparent; 242 | flex: none; 243 | align-items: center; 244 | justify-content: center; 245 | padding: 0; 246 | height: 26px; 247 | width: 26px; 248 | border: none; 249 | white-space: pre; 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /app/styles/less/modules/SplitContainer.less: -------------------------------------------------------------------------------- 1 | /* ========================================================================== 2 | SplitContainer 3 | ========================================================================== */ 4 | 5 | .user-is-resizing-vertically, 6 | .user-is-resizing-horizontally { 7 | cursor: ns-resize; 8 | -webkit-user-select: none; 9 | } 10 | 11 | .user-is-resizing-horizontally { 12 | cursor: ew-resize; 13 | } 14 | 15 | splitter { 16 | display: flex; 17 | flex-direction: row; 18 | overflow: hidden; 19 | position: relative; 20 | width: 100%; 21 | flex: 1 1 0; 22 | 23 | & > start { 24 | display: flex; 25 | overflow: hidden; 26 | position: relative; 27 | transform: translateZ(0); 28 | flex: 1 100px; 29 | } 30 | 31 | & > end { 32 | display: flex; 33 | overflow: hidden; 34 | position: relative; 35 | transform: translateZ(0); 36 | } 37 | 38 | /* right */ 39 | 40 | & > end[withHeader] { 41 | padding-top: 20px; 42 | } 43 | 44 | & > end > header { 45 | background-color: @gray-lightest; 46 | border-bottom: solid 1px @gray; 47 | left: 0; 48 | line-height: 20px; 49 | padding-left: 5px; 50 | position: fixed; 51 | right: 0; 52 | top: 0; 53 | } 54 | 55 | //close button 56 | & > end > .toolbar-item { 57 | background: url("/images/close.png") no-repeat center; 58 | content: ''; 59 | cursor: pointer; 60 | display: none; 61 | height: 15px; 62 | opacity: 0.5; 63 | position: absolute; 64 | right: 2px; 65 | top: 2px; 66 | transition: opacity 0.2s; 67 | width: 15px; 68 | 69 | &:hover { 70 | opacity: 1; 71 | } 72 | } 73 | 74 | & > end > content { 75 | display: flex; 76 | flex: 1; 77 | } 78 | 79 | & > end > content section { 80 | border-bottom: solid 1px #a3a3a3; 81 | padding: 4px 5px; 82 | white-space: nowrap; 83 | } 84 | 85 | & > end > content section:last-child { 86 | border-bottom: none; 87 | } 88 | 89 | & > end > content section[selected] { 90 | flex: 1; 91 | } 92 | 93 | /* end right */ 94 | 95 | & > divider { 96 | background: #a3a3a3; 97 | display: flex; 98 | position: relative; 99 | width: 1px; 100 | } 101 | 102 | & > divider handler { 103 | bottom: 0; 104 | cursor: ew-resize; 105 | left: -3px; 106 | position: absolute; 107 | right: -3px; 108 | top: 0; 109 | } 110 | 111 | &.verticalOrientation { 112 | flex-direction: column; 113 | 114 | & > start { 115 | flex: 1 50px; 116 | } 117 | 118 | & > divider { 119 | height: 1px; 120 | width: auto; 121 | } 122 | 123 | & > end { 124 | min-height: 50px; 125 | height: 50%; 126 | min-width: 0; 127 | width: auto; 128 | } 129 | 130 | & > divider handler { 131 | bottom: -3px; 132 | left: 0; 133 | right: 0; 134 | top: -3px; 135 | cursor: ns-resize; 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /app/styles/less/modules/TabBar.less: -------------------------------------------------------------------------------- 1 | /* ========================================================================== 2 | Tabs 3 | ========================================================================== */ 4 | 5 | tabbar { 6 | display: flex; 7 | flex-direction: column; 8 | flex-grow: 1; 9 | width: 100%; 10 | } 11 | 12 | /* Tab Links 13 | ========================================================================== */ 14 | 15 | tabbar > tabs { 16 | background: @bg-gray-lightest; 17 | border-bottom: 1px solid @border-gray; 18 | display: flex; 19 | height: 23px; 20 | padding: 0 5px; 21 | align-items: stretch; 22 | -webkit-user-select: none; 23 | } 24 | 25 | tabbar > tabs > tab { 26 | border: 1px solid transparent; 27 | border-bottom: none; 28 | cursor: pointer; 29 | display: flex; 30 | font-family: @tabs-font-family-windows; 31 | margin-top: 2px; 32 | overflow: hidden; 33 | padding: 0 4px 1px; 34 | white-space: nowrap; 35 | align-items: center; 36 | } 37 | 38 | body[os='linux'] tabbar > tabs > tab { 39 | font-family: @tabs-font-family-linux; 40 | } 41 | 42 | body[os='mac'] tabbar > tabs > tab { 43 | font-family: @tabs-font-family-mac; 44 | } 45 | 46 | tabbar > tabs > tab[selected] { 47 | background: @bg-white; 48 | border-color: @border-gray; 49 | } 50 | 51 | /* Content for Tabs 52 | ========================================================================== */ 53 | 54 | tabbar > contents { 55 | display: flex; 56 | overflow-y: auto; 57 | -webkit-transform: translateZ(0); 58 | flex: 1 1 0; 59 | } 60 | 61 | tabbar > contents > content { 62 | display: none; 63 | width: 100%; 64 | } 65 | 66 | tabbar > contents > [selected] { 67 | display: flex; 68 | flex: 1 1 0; 69 | } 70 | -------------------------------------------------------------------------------- /app/styles/less/modules/XMLView.less: -------------------------------------------------------------------------------- 1 | xml-view { 2 | height: 100%; 3 | } 4 | 5 | #elements-registry-control-xmlview { 6 | .editorAlt { 7 | display: flex; 8 | div { 9 | margin: auto; 10 | cursor: pointer; 11 | } 12 | } 13 | 14 | div.hidden { 15 | visibility: hidden; 16 | } 17 | 18 | .editorAlt, #xmlEditor { 19 | position: absolute; 20 | top: 25px; 21 | left: 0; 22 | right: 0; 23 | bottom: 0; 24 | height: auto; 25 | } 26 | } -------------------------------------------------------------------------------- /app/styles/less/popup.less: -------------------------------------------------------------------------------- 1 | @import "themes/base/variables.less"; 2 | 3 | @icon-height: 16px; 4 | @icon-width: 16px; 5 | @icon-offset: 6px; 6 | 7 | @gray-darker: #333; 8 | @popup-link-color: #0000ee; 9 | 10 | [hidden] { 11 | display: none; 12 | } 13 | 14 | popup { 15 | color: @gray-darker; 16 | display: inline-block; 17 | padding: 5px 10px 5px 6px; 18 | 19 | product { 20 | display: block; 21 | height: 16px; 22 | line-height: 16px; 23 | margin-top: -1px; 24 | white-space: nowrap; 25 | } 26 | 27 | library { 28 | display: inline-block; 29 | font-weight: bold; 30 | } 31 | 32 | buildtime { 33 | display: inline-block; 34 | } 35 | 36 | logo { 37 | background-image: url("/images/icon-38.png"); 38 | background-repeat: no-repeat; 39 | background-position: left center; 40 | background-size: 16px; 41 | display: inline-block; 42 | height: 16px; 43 | width: @icon-width + @icon-offset; 44 | vertical-align: bottom; 45 | } 46 | 47 | links { 48 | display: block; 49 | padding-left: @icon-width + @icon-offset; 50 | margin-top: 4px; 51 | margin-bottom: 5px; 52 | 53 | a, 54 | a:visited { 55 | color: @popup-link-color; 56 | display: block; 57 | line-height: 1.3; 58 | white-space: nowrap; 59 | } 60 | 61 | a:hover { 62 | text-decoration: none; 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /app/styles/less/themes/base/base.less: -------------------------------------------------------------------------------- 1 | @import "colors.less"; 2 | @import "variables.less"; 3 | @import "globals.less"; 4 | @import "reset.less"; 5 | 6 | @import "devtools.less"; 7 | 8 | // Components 9 | @import "../../modules/TabBar.less"; 10 | @import "../../modules/ControlTree.less"; 11 | @import "../../modules/DataView.less"; 12 | @import "../../modules/FrameSelect.less"; 13 | @import "../../modules/SplitContainer.less"; 14 | @import "../../modules/JSONView.less"; 15 | @import "../../modules/ODataView.less"; 16 | @import "../../modules/ControllerDetailView.less"; 17 | @import "../../modules/XMLView.less"; 18 | @import "../../modules/DataGrid.less"; 19 | @import "../../modules/ElementsRegistry.less"; 20 | 21 | -------------------------------------------------------------------------------- /app/styles/less/themes/base/colors.less: -------------------------------------------------------------------------------- 1 | // Background colors 2 | @bg-black: @black; 3 | 4 | @bg-gray-darkest: @gray-darkest; 5 | @bg-gray-darker: @gray-darker; 6 | @bg-gray-dark: @gray-dark; 7 | @bg-gray: @gray; 8 | @bg-grey-light: @grey-light; 9 | @bg-gray-lighter: @gray-lighter; 10 | @bg-gray-lightest: @gray-lightest; 11 | 12 | @bg-white: @tab-background-color; 13 | 14 | @bg-purple: @purple; 15 | @bg-blue: @blue; 16 | @bg-light-blue: @light-blue; 17 | @bg-lighter-blue: @lighter-blue; 18 | @bg-red: @red; 19 | 20 | @bg-search-field: @search-background-color; 21 | @bg-highlight-color: @highlight-color; 22 | @bg-selected-element-color: @selected-element-color; 23 | @bg-datagrid: @odata-tab-background-color; 24 | @bg-datagrid-row: @bg-datagrid-row-background-color; 25 | @bg-datagrid-header: @bg-datagrid-header-background-color; 26 | @bg-datagrid-th-sortable: @bg-datagrid-th-sortable-background-color; 27 | @bg-datagrid-tr-hover: @bg-datagrid-tr-hover-background-color; 28 | 29 | // Border colors 30 | @border-gray-dark: @gray-dark; 31 | @border-gray: @gray; 32 | @border-grey-light: @grey-light; 33 | @border-gray-lighter: @gray-lighter; 34 | @border-gray-lightest: @gray-lightest; 35 | 36 | // Text color 37 | @text-color: @gray-darkest; 38 | 39 | // Hover 40 | @hover-highlight-color: @hover-color; 41 | 42 | // Section title 43 | @title-color: @section-title-color; 44 | 45 | // Links 46 | @link: @light-blue; 47 | -------------------------------------------------------------------------------- /app/styles/less/themes/base/devtools.less: -------------------------------------------------------------------------------- 1 | overlay { 2 | background: @tab-background-color; 3 | height: 100%; 4 | position: fixed; 5 | left: 0; 6 | top: 18px; 7 | width: 100%; 8 | z-index: 9999999; 9 | 10 | & > section { 11 | display: none; 12 | } 13 | 14 | h1 { 15 | background: @bg-gray-lightest; 16 | padding: 2rem 1rem; 17 | line-height: 1.25; 18 | } 19 | 20 | p { 21 | padding: 0 1rem; 22 | margin: 1rem 0; 23 | line-height: 1.25; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/styles/less/themes/base/globals.less: -------------------------------------------------------------------------------- 1 | // 2 | // Global styles 3 | // -------------------------------------------------- 4 | 5 | arrow, 6 | place-holder { 7 | display: block; 8 | height: @line-height; 9 | width: @font-size; 10 | } 11 | 12 | arrow { 13 | background-repeat: no-repeat; 14 | background-position: center center; 15 | 16 | &[right] { 17 | background-image: url("/images/arrows/arrow-right.png"); 18 | background-size: 7px 8px; 19 | } 20 | &[down] { 21 | background-image: url("/images/arrows/arrow-down.png"); 22 | background-size: 8px 7px; 23 | } 24 | } 25 | 26 | arrow { 27 | cursor: pointer; 28 | } 29 | 30 | attribute, 31 | string { 32 | color: @red; 33 | } 34 | 35 | attribute-value, 36 | boolean { 37 | color: @blue; 38 | } 39 | 40 | key { 41 | color: @red-saturated; 42 | } 43 | 44 | no-data { 45 | color: @grey-light; 46 | display: block; 47 | padding-top: @padding-base-horizontal; 48 | text-align: center; 49 | } 50 | 51 | pre { 52 | display: inline; 53 | word-wrap: break-word; 54 | white-space: pre-wrap; 55 | } 56 | 57 | [verical-aligment] { 58 | display: inline-block; 59 | vertical-align: middle; 60 | } 61 | 62 | section-title, value { 63 | color: @title-color; 64 | } 65 | 66 | tag { 67 | color: @purple; 68 | } 69 | 70 | [hidden] { 71 | display: none; 72 | } 73 | 74 | opaque { 75 | color: @grey-light; 76 | } 77 | 78 | [padding] { 79 | padding: 10px; 80 | } 81 | -------------------------------------------------------------------------------- /app/styles/less/themes/base/reset.less: -------------------------------------------------------------------------------- 1 | // 2 | // CSS reset 3 | // -------------------------------------------------- 4 | 5 | * { 6 | box-sizing: border-box; 7 | margin: 0; 8 | padding: 0; 9 | } 10 | 11 | html { 12 | display: flex; 13 | height: 100%; 14 | overflow: hidden; 15 | } 16 | 17 | body { 18 | color: @text-color; 19 | background-color: @tab-background-color; 20 | display: flex; 21 | flex-direction: column; 22 | font-family: @font-family-windows; 23 | font-size: @main-font-size-windows; 24 | height: 100%; 25 | width: 100%; 26 | line-height: @line-height; 27 | flex-grow: 1; 28 | } 29 | 30 | body[os='linux'] { 31 | font-family: @font-family-linux; 32 | font-size: @main-font-size-linux; 33 | } 34 | 35 | body[os='mac'] { 36 | font-family: @font-family-mac; 37 | font-size: @main-font-size-mac; 38 | } 39 | 40 | ol, ul { 41 | list-style: none; 42 | } 43 | 44 | label { 45 | cursor: pointer; 46 | -webkit-user-select: none; 47 | } 48 | -------------------------------------------------------------------------------- /app/styles/less/themes/base/variables.less: -------------------------------------------------------------------------------- 1 | // Font 2 | @font-family-windows: Consolas, Lucida Console, monospace; 3 | @font-family-mac: Menlo, monospace; 4 | @font-family-linux: dejavu sans mono, monospace; 5 | 6 | @tabs-font-family-windows: 'Segoe UI', Tahoma, sans-serif; 7 | @tabs-font-family-linux: Ubuntu, Arial, sans-serif; 8 | @tabs-font-family-mac: 'Lucida Grande', sans-serif; 9 | 10 | @main-font-size-windows: 12px; 11 | @main-font-size-mac: 11px; 12 | @main-font-size-linux: 11px; 13 | 14 | @font-size-large: ceil((@font-size * 1.25)); // 15px 15 | @font-size: 12px; 16 | @font-size-small: ceil((@font-size * 0.85)); // ~10px 17 | 18 | // Line height 19 | @line-height-large: 1.3333333; 20 | @line-height: 14px; 21 | @line-height-small: 1.5; 22 | 23 | // Padding 24 | @padding-base-vertical: 6px; 25 | @padding-base-horizontal: 12px; 26 | @padding-large-vertical: 10px; 27 | @padding-large-horizontal: 16px; 28 | @padding-small-vertical: 5px; 29 | @padding-small-horizontal: 10px; 30 | @padding-xs-vertical: 1px; 31 | @padding-xs-horizontal: 5px; 32 | 33 | -------------------------------------------------------------------------------- /app/styles/less/themes/dark/dark.less: -------------------------------------------------------------------------------- 1 | @import "../base/base.less"; 2 | 3 | // Colors 4 | @black: #000; 5 | 6 | @gray-darkest: #949494; 7 | @gray-darker: #333; 8 | @gray-dark: #a3a3a3; 9 | @gray: #626262; 10 | @grey-light: #c0c0c0; 11 | @gray-lighter: #d4d4d4; 12 | @gray-lightest: @black; 13 | 14 | @white: #fff; 15 | 16 | @purple: #59AFD6; 17 | @blue: #ED9868; 18 | @light-blue: #ED9868; 19 | @lighter-blue: #C98730; 20 | @red: #9ABADA; 21 | @red-saturated: #22D4C2; 22 | 23 | @highlight-color: rgba(98, 98, 98, 0.9); 24 | 25 | @hover-color: rgba(201, 135, 48, 0.1); 26 | @selected-element-color: @black; 27 | @section-title-color: #CBCBCB; 28 | @tab-background-color: #252525; 29 | @search-background-color: #1B1B1B; 30 | @odata-tab-background-color: #252525; 31 | @bg-datagrid-row-background-color: @black; 32 | @bg-datagrid-header-background-color: @black; 33 | @bg-datagrid-th-sortable-background-color: #252525; 34 | @bg-datagrid-tr-hover-background-color: rgba(201, 135, 48, 0.1); 35 | -------------------------------------------------------------------------------- /app/styles/less/themes/light/light.less: -------------------------------------------------------------------------------- 1 | @import "../base/base.less"; 2 | 3 | // Colors 4 | @black: #000; 5 | 6 | @gray-darkest: #222; 7 | @gray-darker: #333; 8 | @gray-dark: #a3a3a3; 9 | @gray: #bbb; 10 | @grey-light: #c0c0c0; 11 | @gray-lighter: #d4d4d4; 12 | @gray-lightest: #eee; 13 | 14 | @white: #fff; 15 | 16 | @purple: #881280; 17 | @blue: #1a1aa6; 18 | @light-blue: #0000ee; 19 | @lighter-blue: #3879D9; 20 | @red: #994500; 21 | @red-saturated: #C80000; 22 | 23 | @highlight-color: #ffff70; 24 | 25 | @hover-color: rgba(56, 121, 217, 0.1); 26 | @selected-element-color: @white; 27 | @section-title-color: @black; 28 | @tab-background-color: @white; 29 | @search-background-color: @white; 30 | @odata-tab-background-color: @white; 31 | @bg-datagrid-row-background-color: #f3f3f3; 32 | @bg-datagrid-header-background-color: #f2f2f2; 33 | @bg-datagrid-th-sortable-background-color: #e6e6e6; 34 | @bg-datagrid-tr-hover-background-color: AliceBlue; 35 | -------------------------------------------------------------------------------- /app/vendor/mode-json.js: -------------------------------------------------------------------------------- 1 | ace.define("ace/mode/json_highlight_rules",["require","exports","module","ace/lib/oop","ace/mode/text_highlight_rules"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text_highlight_rules").TextHighlightRules,s=function(){this.$rules={start:[{token:"variable",regex:'["](?:(?:\\\\.)|(?:[^"\\\\]))*?["]\\s*(?=:)'},{token:"string",regex:'"',next:"string"},{token:"constant.numeric",regex:"0[xX][0-9a-fA-F]+\\b"},{token:"constant.numeric",regex:"[+-]?\\d+(?:(?:\\.\\d*)?(?:[eE][+-]?\\d+)?)?\\b"},{token:"constant.language.boolean",regex:"(?:true|false)\\b"},{token:"text",regex:"['](?:(?:\\\\.)|(?:[^'\\\\]))*?[']"},{token:"comment",regex:"\\/\\/.*$"},{token:"comment.start",regex:"\\/\\*",next:"comment"},{token:"paren.lparen",regex:"[[({]"},{token:"paren.rparen",regex:"[\\])}]"},{token:"text",regex:"\\s+"}],string:[{token:"constant.language.escape",regex:/\\(?:x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4}|["\\\/bfnrt])/},{token:"string",regex:'"|$',next:"start"},{defaultToken:"string"}],comment:[{token:"comment.end",regex:"\\*\\/",next:"start"},{defaultToken:"comment"}]}};r.inherits(s,i),t.JsonHighlightRules=s}),ace.define("ace/mode/matching_brace_outdent",["require","exports","module","ace/range"],function(e,t,n){"use strict";var r=e("../range").Range,i=function(){};(function(){this.checkOutdent=function(e,t){return/^\s+$/.test(e)?/^\s*\}/.test(t):!1},this.autoOutdent=function(e,t){var n=e.getLine(t),i=n.match(/^(\s*\})/);if(!i)return 0;var s=i[1].length,o=e.findMatchingBracket({row:t,column:s});if(!o||o.row==t)return 0;var u=this.$getIndent(e.getLine(o.row));e.replace(new r(t,0,t,s-1),u)},this.$getIndent=function(e){return e.match(/^\s*/)[0]}}).call(i.prototype),t.MatchingBraceOutdent=i}),ace.define("ace/mode/folding/cstyle",["require","exports","module","ace/lib/oop","ace/range","ace/mode/folding/fold_mode"],function(e,t,n){"use strict";var r=e("../../lib/oop"),i=e("../../range").Range,s=e("./fold_mode").FoldMode,o=t.FoldMode=function(e){e&&(this.foldingStartMarker=new RegExp(this.foldingStartMarker.source.replace(/\|[^|]*?$/,"|"+e.start)),this.foldingStopMarker=new RegExp(this.foldingStopMarker.source.replace(/\|[^|]*?$/,"|"+e.end)))};r.inherits(o,s),function(){this.foldingStartMarker=/([\{\[\(])[^\}\]\)]*$|^\s*(\/\*)/,this.foldingStopMarker=/^[^\[\{\(]*([\}\]\)])|^[\s\*]*(\*\/)/,this.singleLineBlockCommentRe=/^\s*(\/\*).*\*\/\s*$/,this.tripleStarBlockCommentRe=/^\s*(\/\*\*\*).*\*\/\s*$/,this.startRegionRe=/^\s*(\/\*|\/\/)#?region\b/,this._getFoldWidgetBase=this.getFoldWidget,this.getFoldWidget=function(e,t,n){var r=e.getLine(n);if(this.singleLineBlockCommentRe.test(r)&&!this.startRegionRe.test(r)&&!this.tripleStarBlockCommentRe.test(r))return"";var i=this._getFoldWidgetBase(e,t,n);return!i&&this.startRegionRe.test(r)?"start":i},this.getFoldWidgetRange=function(e,t,n,r){var i=e.getLine(n);if(this.startRegionRe.test(i))return this.getCommentRegionBlock(e,i,n);var s=i.match(this.foldingStartMarker);if(s){var o=s.index;if(s[1])return this.openingBracketBlock(e,s[1],n,o);var u=e.getCommentFoldRange(n,o+s[0].length,1);return u&&!u.isMultiLine()&&(r?u=this.getSectionRange(e,n):t!="all"&&(u=null)),u}if(t==="markbegin")return;var s=i.match(this.foldingStopMarker);if(s){var o=s.index+s[0].length;return s[1]?this.closingBracketBlock(e,s[1],n,o):e.getCommentFoldRange(n,o,-1)}},this.getSectionRange=function(e,t){var n=e.getLine(t),r=n.search(/\S/),s=t,o=n.length;t+=1;var u=t,a=e.getLength();while(++tf)break;var l=this.getFoldWidgetRange(e,"all",t);if(l){if(l.start.row<=s)break;if(l.isMultiLine())t=l.end.row;else if(r==f)break}u=t}return new i(s,o,u,e.getLine(u).length)},this.getCommentRegionBlock=function(e,t,n){var r=t.search(/\s*$/),s=e.getLength(),o=n,u=/^\s*(?:\/\*|\/\/|--)#?(end)?region\b/,a=1;while(++no)return new i(o,r,l,t.length)}}.call(o.prototype)}),ace.define("ace/mode/json",["require","exports","module","ace/lib/oop","ace/mode/text","ace/mode/json_highlight_rules","ace/mode/matching_brace_outdent","ace/mode/behaviour/cstyle","ace/mode/folding/cstyle","ace/worker/worker_client"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text").Mode,s=e("./json_highlight_rules").JsonHighlightRules,o=e("./matching_brace_outdent").MatchingBraceOutdent,u=e("./behaviour/cstyle").CstyleBehaviour,a=e("./folding/cstyle").FoldMode,f=e("../worker/worker_client").WorkerClient,l=function(){this.HighlightRules=s,this.$outdent=new o,this.$behaviour=new u,this.foldingRules=new a};r.inherits(l,i),function(){this.getNextLineIndent=function(e,t,n){var r=this.$getIndent(t);if(e=="start"){var i=t.match(/^.*[\{\(\[]\s*$/);i&&(r+=n)}return r},this.checkOutdent=function(e,t,n){return this.$outdent.checkOutdent(t,n)},this.autoOutdent=function(e,t,n){this.$outdent.autoOutdent(t,n)},this.createWorker=function(e){var t=new f(["ace"],"ace/mode/json_worker","JsonWorker");return t.attachToDocument(e.getDocument()),t.on("annotate",function(t){e.setAnnotations(t.data)}),t.on("terminate",function(){e.clearAnnotations()}),t},this.$id="ace/mode/json"}.call(l.prototype),t.Mode=l}); (function() { 2 | ace.require(["ace/mode/json"], function(m) { 3 | if (typeof module == "object" && typeof exports == "object" && module) { 4 | module.exports = m; 5 | } 6 | }); 7 | })(); 8 | -------------------------------------------------------------------------------- /app/vendor/theme-chrome.js: -------------------------------------------------------------------------------- 1 | ace.define("ace/theme/chrome",["require","exports","module","ace/lib/dom"],function(e,t,n){t.isDark=!1,t.cssClass="ace-chrome",t.cssText='.ace-chrome .ace_gutter {background: #ebebeb;color: #333;overflow : hidden;}.ace-chrome .ace_print-margin {width: 1px;background: #e8e8e8;}.ace-chrome {background-color: #FFFFFF;color: black;}.ace-chrome .ace_cursor {color: black;}.ace-chrome .ace_invisible {color: rgb(191, 191, 191);}.ace-chrome .ace_constant.ace_buildin {color: rgb(88, 72, 246);}.ace-chrome .ace_constant.ace_language {color: rgb(88, 92, 246);}.ace-chrome .ace_constant.ace_library {color: rgb(6, 150, 14);}.ace-chrome .ace_invalid {background-color: rgb(153, 0, 0);color: white;}.ace-chrome .ace_fold {}.ace-chrome .ace_support.ace_function {color: rgb(60, 76, 114);}.ace-chrome .ace_support.ace_constant {color: rgb(6, 150, 14);}.ace-chrome .ace_support.ace_type,.ace-chrome .ace_support.ace_class.ace-chrome .ace_support.ace_other {color: rgb(109, 121, 222);}.ace-chrome .ace_variable.ace_parameter {font-style:italic;color:#FD971F;}.ace-chrome .ace_keyword.ace_operator {color: rgb(104, 118, 135);}.ace-chrome .ace_comment {color: #236e24;}.ace-chrome .ace_comment.ace_doc {color: #236e24;}.ace-chrome .ace_comment.ace_doc.ace_tag {color: #236e24;}.ace-chrome .ace_constant.ace_numeric {color: rgb(0, 0, 205);}.ace-chrome .ace_variable {color: rgb(49, 132, 149);}.ace-chrome .ace_xml-pe {color: rgb(104, 104, 91);}.ace-chrome .ace_entity.ace_name.ace_function {color: #0000A2;}.ace-chrome .ace_heading {color: rgb(12, 7, 255);}.ace-chrome .ace_list {color:rgb(185, 6, 144);}.ace-chrome .ace_marker-layer .ace_selection {background: rgb(181, 213, 255);}.ace-chrome .ace_marker-layer .ace_step {background: rgb(252, 255, 0);}.ace-chrome .ace_marker-layer .ace_stack {background: rgb(164, 229, 101);}.ace-chrome .ace_marker-layer .ace_bracket {margin: -1px 0 0 -1px;border: 1px solid rgb(192, 192, 192);}.ace-chrome .ace_marker-layer .ace_active-line {background: rgba(0, 0, 0, 0.07);}.ace-chrome .ace_gutter-active-line {background-color : #dcdcdc;}.ace-chrome .ace_marker-layer .ace_selected-word {background: rgb(250, 250, 255);border: 1px solid rgb(200, 200, 250);}.ace-chrome .ace_storage,.ace-chrome .ace_keyword,.ace-chrome .ace_meta.ace_tag {color: rgb(147, 15, 128);}.ace-chrome .ace_string.ace_regex {color: rgb(255, 0, 0)}.ace-chrome .ace_string {color: #1A1AA6;}.ace-chrome .ace_entity.ace_other.ace_attribute-name {color: #994409;}.ace-chrome .ace_indent-guide {background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAE0lEQVQImWP4////f4bLly//BwAmVgd1/w11/gAAAABJRU5ErkJggg==") right repeat-y;}';var r=e("../lib/dom");r.importCssString(t.cssText,t.cssClass)}); (function() { 2 | ace.require(["ace/theme/chrome"], function(m) { 3 | if (typeof module == "object" && typeof exports == "object" && module) { 4 | module.exports = m; 5 | } 6 | }); 7 | })(); 8 | -------------------------------------------------------------------------------- /app/vendor/theme-vibrant_ink.js: -------------------------------------------------------------------------------- 1 | ace.define("ace/theme/vibrant_ink",["require","exports","module","ace/lib/dom"],function(r,e,m){e.isDark=true;e.cssClass="ace-vibrant-ink";e.cssText=".ace-vibrant-ink .ace_gutter {background: #1a1a1a;color: #BEBEBE}.ace-vibrant-ink .ace_print-margin {width: 1px;background: #1a1a1a}.ace-vibrant-ink {background-color: #0F0F0F;color: #FFFFFF}.ace-vibrant-ink .ace_cursor {color: #FFFFFF}.ace-vibrant-ink .ace_marker-layer .ace_selection {background: #6699CC}.ace-vibrant-ink.ace_multiselect .ace_selection.ace_start {box-shadow: 0 0 3px 0px #0F0F0F;}.ace-vibrant-ink .ace_marker-layer .ace_step {background: rgb(102, 82, 0)}.ace-vibrant-ink .ace_marker-layer .ace_bracket {margin: -1px 0 0 -1px;border: 1px solid #404040}.ace-vibrant-ink .ace_marker-layer .ace_active-line {background: #333333}.ace-vibrant-ink .ace_gutter-active-line {background-color: #333333}.ace-vibrant-ink .ace_marker-layer .ace_selected-word {border: 1px solid #6699CC}.ace-vibrant-ink .ace_invisible {color: #404040}.ace-vibrant-ink .ace_keyword,.ace-vibrant-ink .ace_meta {color: #FF6600}.ace-vibrant-ink .ace_constant,.ace-vibrant-ink .ace_constant.ace_character,.ace-vibrant-ink .ace_constant.ace_character.ace_escape,.ace-vibrant-ink .ace_constant.ace_other {color: #339999}.ace-vibrant-ink .ace_constant.ace_numeric {color: #99CC99}.ace-vibrant-ink .ace_invalid,.ace-vibrant-ink .ace_invalid.ace_deprecated {color: #CCFF33;background-color: #000000}.ace-vibrant-ink .ace_fold {background-color: #FFCC00;border-color: #FFFFFF}.ace-vibrant-ink .ace_entity.ace_name.ace_function,.ace-vibrant-ink .ace_support.ace_function,.ace-vibrant-ink .ace_variable {color: #FFCC00}.ace-vibrant-ink .ace_variable.ace_parameter {font-style: italic}.ace-vibrant-ink .ace_string {color: #66FF00}.ace-vibrant-ink .ace_string.ace_regexp {color: #44B4CC}.ace-vibrant-ink .ace_comment {color: #9933CC}.ace-vibrant-ink .ace_entity.ace_other.ace_attribute-name {font-style: italic;color: #99CC99}.ace-vibrant-ink .ace_indent-guide {background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAEklEQVQImWNgYGBgYNDTc/oPAALPAZ7hxlbYAAAAAElFTkSuQmCC) right repeat-y}";var d=r("../lib/dom");d.importCssString(e.cssText,e.cssClass);});(function(){ace.require(["ace/theme/vibrant_ink"],function(m){if(typeof module=="object"&&typeof exports=="object"&&module){module.exports=m;}});})(); 2 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { extends: ['@commitlint/config-conventional'] }; 2 | -------------------------------------------------------------------------------- /docs/bugreport_template.txt: -------------------------------------------------------------------------------- 1 | OpenUI5/SAPUI5 version: 2 | 3 | Browser/version: 4 | 5 | URL (minimal example if possible): 6 | 7 | User/password (if required and possible - do not post any confidential information here): 8 | 9 | Steps to reproduce the problem: 10 | 1. 11 | 2. 12 | 3. 13 | 14 | What is the expected result? 15 | 16 | What happens instead? 17 | 18 | Any other information? (attach screenshots if possible) 19 | 20 | -------------------------------------------------------------------------------- /docs/guidelines.md: -------------------------------------------------------------------------------- 1 | Development Conventions and Guidelines 2 | ====================================== 3 | 4 | To keep the UI5 code readable and maintainable, please follow these rules, even if you find them violated somewhere. Note that this list is not complete. 5 | When a file is consistently not following these rules and adhering to the rules would make the code worse, follow the local style. 6 | 7 | ### Table of Contents 8 | 9 | - [Development Conventions and Guidelines](#development-conventions-and-guidelines) 10 | - [Table of Contents](#table-of-contents) 11 | - [General](#general) 12 | - [JavaScript Coding Guidelines](#javascript-coding-guidelines) 13 | - [Code Formatting](#code-formatting) 14 | - [Naming Conventions](#naming-conventions) 15 | - [Documentation (JSDoc)](#documentation-jsdoc) 16 | - [Product Standards / Acceptance Criteria](#product-standards--acceptance-criteria) 17 | - [File Names and Encoding](#file-names-and-encoding) 18 | - [Git Guidelines](#git-guidelines) 19 | - [Tools](#tools) 20 | - [ESLint](#eslint) 21 | - [JSHint](#jshint) 22 | 23 | 24 | 25 | General 26 | ------- 27 | 28 | - Always consider the developers who USE your code! Do not surprise them, but give them what they expect. And make it simple. 29 | - Adhere to any local standard in the file 30 | - Use Unix line endings (LF-only) 31 | - There is *no* 80-character line length guideline, but that doesn't mean to abuse it. 32 | - Use comments. Don't rephrase the code, but tell the reader what is NOT in the code. Describe why your code does what it does. Prefer line comments. 33 | 34 | JavaScript Coding Guidelines 35 | ---------------------------- 36 | 37 | - No global JavaScript variables; 38 | - This also means: no undeclared variables 39 | - Do not access internal (private) members of other objects 40 | - Do not use console.log() 41 | 42 | ### Code Formatting 43 | 44 | - Our ESLint and JSHint checks needs to run successfully; Check the ".eslintrc" and ".jshintrc" files for more information: 45 | - The WebStorm default settings for the JavaScript editor are pretty fine, but make sure space are used for indentation 46 | 47 | ### Naming Conventions 48 | 49 | - Class names should use CamelCase, starting with an uppercase letter 50 | 51 | ### Documentation (JSDoc) 52 | 53 | For documenting JavaScript, UI5 Inspector uses the JSDoc3. See the [JSDoc3 Toolkit Homepage](http://usejsdoc.org/) for an explanation of the available tags. The JSDoc3 is checked from the ESLint and it is a mandatory. 54 | 55 | 56 | Product Standards / Acceptance Criteria 57 | --------------------------------------- 58 | 59 | UI5 Inspector needs to fulfill certain "product standards". While these are not directly related to code conventions, the most important ones are mentioned here, because new code needs to fulfill these requirements: 60 | 61 | General: 62 | 63 | - Security (e.g. output encoding against XSS attacks) 64 | - Performance (not a yes/no question, but needs to be in focus) 65 | - Automated tests 66 | - Make sure other Open Source libraries (or parts of them) are officially approved before adding them to UI5 Inspector. Do not add code you "found" somewhere. 67 | 68 | File Names and Encoding 69 | ----------------------- 70 | 71 | Some of the target platforms of UI5 Inspector impose technical restrictions on the naming or structure of resources (files). Hence, these restrictions apply: 72 | 73 | - Folder names must not contain spaces 74 | - Single folder names must not be longer than 40 characters 75 | - Two resource names must not only differ in case 76 | - Avoid non-ASCII characters in resource names 77 | 78 | 79 | 80 | Git Guidelines 81 | -------------- 82 | 83 | Set the Git `core.autocrlf` configuration property to "false" (and make sure to use Unix-style linebreaks (LF-only)) 84 | 85 | The commit message consists of two or three parts, separated by empty lines: 86 | 87 | 1. The commit summary (the first line) 88 | 2. An optional commit description text (may contain additional empty lines) 89 | 3. A data section 90 | 91 | - The summary line must be prefixed by `[FIX]` or `[FEATURE]` and should start with the control/component which was the main subject of the change 92 | - Instead of `[FIX]`/`[FEATURE]` and at any other location in the commit message `[INTERNAL]` can be used for commits/explanations which are not supposed to be part of the release notes because they are not relevant for users of UI5 Inspector 93 | - The data section consists of name-value pairs 94 | - `Fixes https://github.com/SAP/openui5/issues/(issueNumber)` when the change fixes a GitHub-reported bug; it is important that there is NO colon between "Fixes" and the URL! 95 | - `Closes https://github.com/SAP/openui5/pull/(pullRequestNumber)` when the change comes from a pull request; it is important that there is NO colon between "Fixes" and the URL! As the pull request number is not known before it is created, this is usually added by the UI5 Inspector committer handling the pull request 96 | - Further internal information - like `CSS` (for old SAP-internally reported bugs), `BCP` (for customer messages reported at SAP and new internal bug reports), a mandatory `Change-Id`, and the `CR-Id` ("Change Request ID", mandatory for maintenance codelines) - is added by SAP developers when required 97 | - A commit message can thus look like this: 98 | 99 | ``` wiki 100 | fix: prevent rerendering if the same model is set. 101 | 102 | Fixes #123 103 | ``` 104 | 105 | 106 | Tools 107 | ----- 108 | 109 | - It is helpful to configure your JavaScript editor to display whitespace and linebreak characters; this makes issues with mixed tabs/spaces and windows-style linebreaks immediately obvious 110 | - It also helps to configure the code formatter of your code editor accordingly. The default formatter for JavaScript in WebStorm is already pretty ok. 111 | 112 | ### ESLint 113 | 114 | UI5 Inspector comes with a ruleset for [ESLint](https://eslint.org/). Adhering to these rules is mandatory. 115 | 116 | ### JSHint 117 | 118 | UI5 Inspector comes with a ruleset for [JSHint](http://jshint.com/docs/). Adhering to these rules is mandatory. 119 | -------------------------------------------------------------------------------- /global.d.ts: -------------------------------------------------------------------------------- 1 | export {}; 2 | 3 | declare global { 4 | 5 | interface Window { 6 | chrome: any; 7 | } 8 | 9 | } 10 | -------------------------------------------------------------------------------- /grunt/aliases.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Grunt aliases 5 | * @type {Object} 6 | */ 7 | module.exports = { 8 | 9 | // Make sure code styles are up to par and there are no obvious mistakes. 10 | lint: [ 11 | 'jshint', 12 | 'eslint' 13 | ], 14 | 15 | // Preprocess .js and .less files. 16 | preprocess: [ 17 | 'browserify', 18 | 'less' 19 | ], 20 | 21 | // Builds the project without running any test. 22 | dist: [ 23 | 'clean', 24 | 'preprocess', 25 | 'copy', 26 | 'replace', 27 | 'usebanner', 28 | 'compress' 29 | ], 30 | 31 | // Runs all tests for continues integration. 32 | test: [ 33 | 'lint', 34 | 'less', 35 | 'karma:CI' 36 | ], 37 | 38 | // Builds the project and monitor changes in files 39 | default: [ 40 | 'dist', 41 | 'watch' 42 | ] 43 | }; 44 | 45 | -------------------------------------------------------------------------------- /grunt/browserify.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Bundl the needed modules. 5 | * @param {Object} grunt 6 | * @param {Object} config 7 | */ 8 | module.exports = function (grunt, config) { 9 | return { 10 | options: { 11 | debug: true 12 | }, 13 | dev: { 14 | files: [{ 15 | expand: true, 16 | cwd: '<%= tempDist %>/scripts', 17 | src: ['**/*.js', '!modules/**/*.js'], 18 | dest: '<%= dist %>/scripts' 19 | }] 20 | } 21 | }; 22 | }; 23 | -------------------------------------------------------------------------------- /grunt/clean.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Empties folders to start fresh. 5 | * @param {Object} grunt 6 | * @param {Object} config 7 | */ 8 | module.exports = function (grunt, config) { 9 | return { 10 | all: { 11 | files: [{ 12 | dot: true, 13 | src: [ 14 | '<%= dist %>/*' 15 | ] 16 | }] 17 | } 18 | }; 19 | }; 20 | -------------------------------------------------------------------------------- /grunt/compress.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Compress dist files to zip package. 5 | * @param {Object} grunt 6 | * @param {Object} config 7 | */ 8 | module.exports = function (grunt, config) { 9 | return { 10 | dist: { 11 | options: { 12 | /** 13 | * Describe archive location. 14 | * @returns {string} 15 | */ 16 | archive: function () { 17 | // If we need to add the version to the zip name 18 | // var manifest = grunt.file.readJSON('app/manifest.json'); 19 | return 'package/ui5inspector.zip'; 20 | } 21 | }, 22 | files: [{ 23 | expand: true, 24 | cwd: 'dist/', 25 | src: ['**'], 26 | dest: '' 27 | }] 28 | } 29 | }; 30 | }; 31 | -------------------------------------------------------------------------------- /grunt/connect.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Create Grunt server. 5 | * @param {Object} grunt 6 | * @param {Object} config 7 | */ 8 | module.exports = function (grunt, config) { 9 | return { 10 | options: { 11 | hostname: 'localhost' 12 | }, 13 | dist: { 14 | options: { 15 | open: true, 16 | port: 9001, 17 | base: ['<%= dist %>'] 18 | } 19 | } 20 | }; 21 | }; 22 | -------------------------------------------------------------------------------- /grunt/copy.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Copies remaining files. 5 | * @param {Object} grunt 6 | * @param {Object} config 7 | */ 8 | module.exports = function (grunt, config) { 9 | return { 10 | dist: { 11 | files: [{ 12 | expand: true, 13 | dot: true, 14 | cwd: '<%= app %>', 15 | dest: '<%= dist %>', 16 | src: [ 17 | '*.txt', 18 | 'html/**/*.html', 19 | 'images/**/*.*', 20 | 'vendor/*.js', 21 | 'manifest.json' 22 | ] 23 | }] 24 | }, 25 | 26 | license: { 27 | files: [{ 28 | expand: true, 29 | dot: true, 30 | cwd: './', 31 | dest: '<%= dist %>', 32 | src: [ 33 | '*.txt' 34 | ] 35 | }] 36 | } 37 | }; 38 | }; 39 | -------------------------------------------------------------------------------- /grunt/coveralls.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Upload lcov file to coveralls. 5 | * @param {Object} grunt 6 | * @param {Object} config 7 | */ 8 | module.exports = function (grunt, config) { 9 | return { 10 | target: { 11 | src: '<%= tests %>/reports/coverage/lcov.info' 12 | } 13 | }; 14 | }; 15 | -------------------------------------------------------------------------------- /grunt/eslint.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Make sure code styles are up to par and there are no obvious mistakes. 5 | * @param {Object} grunt 6 | * @param {Object} config 7 | */ 8 | module.exports = function (grunt, config) { 9 | return { 10 | options: { 11 | configFile: '.eslintrc', 12 | }, 13 | target: ['<%= app %>/scripts/**/*.js'], 14 | }; 15 | }; 16 | -------------------------------------------------------------------------------- /grunt/jscs.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Make sure code styles are up to par and there are no obvious mistakes. 5 | * @param {Object} grunt 6 | * @param {Object} config 7 | */ 8 | module.exports = function (grunt, config) { 9 | return { 10 | options: { 11 | config: '.jscsrc', 12 | requireCurlyBraces: ['if'] 13 | }, 14 | scripts: { 15 | files: { 16 | src: ['<%= app %>/scripts/**/*.js'] 17 | } 18 | }, 19 | karma: { 20 | files: { 21 | src: ['<%= tests %>/**/*.js', '!<%= tests %>/reports/**/*.*'] 22 | } 23 | }, 24 | gruntfiles: { 25 | files: { 26 | src: ['Gruntfile.js', '<%= grunt %>/*.js'] 27 | } 28 | } 29 | }; 30 | }; 31 | -------------------------------------------------------------------------------- /grunt/jshint.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Make sure code styles are up to par and there are no obvious mistakes. 5 | * @param {Object} grunt 6 | * @param {Object} config 7 | */ 8 | module.exports = function (grunt, config) { 9 | return { 10 | options: { 11 | node: true, 12 | jshintrc: '.jshintrc', 13 | reporter: require('jshint-stylish') 14 | }, 15 | scripts: { 16 | files: { 17 | src: ['<%= app %>/scripts/**/*.js'] 18 | } 19 | }, 20 | karma: { 21 | files: { 22 | src: ['<%= tests %>/**/*.js', '!<%= tests %>/reports/**/*.*'] 23 | } 24 | }, 25 | gruntfiles: { 26 | files: { 27 | src: ['Gruntfile.js', '<%= grunt %>/*.js'] 28 | } 29 | } 30 | }; 31 | }; 32 | -------------------------------------------------------------------------------- /grunt/karma.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Unit test runner. 5 | * @param {Object} grunt 6 | * @param {Object} config 7 | */ 8 | module.exports = function (grunt, config) { 9 | return { 10 | options: { 11 | configFile: 'karma.conf.js', 12 | client: { 13 | mocha: { 14 | reporter: 'html', 15 | ui: 'bdd' 16 | } 17 | } 18 | }, 19 | dev: {}, 20 | CI: { 21 | singleRun: true 22 | } 23 | }; 24 | }; 25 | -------------------------------------------------------------------------------- /grunt/less.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Compiles less files to css files. 5 | * @param {Object} grunt 6 | * @param {Object} config 7 | */ 8 | module.exports = function (grunt, config) { 9 | return { 10 | dist: { 11 | options: { 12 | optimization: 2 13 | }, 14 | files: [{ 15 | expand: true, 16 | cwd: '<%= app %>/styles/less', 17 | dest: '<%= dist %>/styles', 18 | src: [ 19 | 'themes/light/light.less', 20 | 'themes/dark/dark.less', 21 | 'popup.less' 22 | ], 23 | ext: '.css' 24 | }] 25 | }, 26 | test: { 27 | files: [{ 28 | expand: true, 29 | cwd: '<%= app %>/styles/less', 30 | dest: '<%= tests %>/styles', 31 | src: [ 32 | 'themes/light/light.less', 33 | 'themes/dark/dark.less' 34 | ], 35 | ext: '.css' 36 | }] 37 | } 38 | }; 39 | }; 40 | -------------------------------------------------------------------------------- /grunt/replace.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Replace text in files. 5 | * @param {Object} grunt 6 | * @param {Object} config 7 | */ 8 | module.exports = function (grunt, config) { 9 | return { 10 | toolsAPI: { 11 | src: [ 12 | '<%= app %>/vendor/ToolsAPI.js' 13 | ], 14 | dest: '<%= dist %>/vendor/ToolsAPI.js', 15 | replacements: [{ 16 | from: 'sap.ui.define([', 17 | to: 'sap.ui.define(\'ToolsAPI\', [' 18 | }] 19 | } 20 | }; 21 | }; 22 | -------------------------------------------------------------------------------- /grunt/usebanner.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Add copy right comments inside the distributed files. 5 | * @param {Object} grunt 6 | * @param {Object} config 7 | */ 8 | module.exports = function (grunt, config) { 9 | return { 10 | HTML: { 11 | options: { 12 | position: 'top', 13 | banner: '', 18 | linebreak: true 19 | }, 20 | files: { 21 | src: ['<%= dist %>/**/*.html'] 22 | } 23 | } 24 | }; 25 | }; 26 | -------------------------------------------------------------------------------- /grunt/watch.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Watches files for changes and runs tasks based on the changed files. 5 | * @param {Object} grunt 6 | * @param {Object} config 7 | */ 8 | module.exports = function (grunt, config) { 9 | return { 10 | options: { 11 | spawn: false, 12 | interval: 5000 13 | }, 14 | html: { 15 | files: ['<%= app %>/**/*.html'], 16 | tasks: ['copy'] 17 | }, 18 | js: { 19 | files: ['<%= app %>/scripts/**/*.js'], 20 | tasks: ['newer:jshint:scripts', 'newer:eslint:scripts', 'browserify'] 21 | }, 22 | tests: { 23 | files: ['<%= tests %>/**/*.js'], 24 | tasks: ['newer:jshint:karma', 'newer:eslint:karma'] 25 | }, 26 | gruntfiles: { 27 | files: ['Gruntfile.js', '<%= grunt %>/*.js'], 28 | tasks: ['newer:jshint:gruntfiles', 'newer:eslint:gruntfiles'] 29 | }, 30 | styles: { 31 | files: ['<%= app %>/styles/less/**/*.less'], 32 | tasks: ['less'] 33 | } 34 | }; 35 | }; 36 | -------------------------------------------------------------------------------- /karma.bootstrap.js: -------------------------------------------------------------------------------- 1 | /*global document, window*/ 2 | (function (window) { 3 | 'use strict'; 4 | 5 | document.body.innerHTML += '
'; 6 | 7 | window.assert = chai.assert; 8 | window.expect = chai.expect; 9 | window.should = chai.should(); 10 | })(window); 11 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | 3 | module.exports = function (config) { 4 | var configuration = { 5 | 6 | // Base path that will be used to resolve all patterns (eg. files, exclude) 7 | basePath: './', 8 | 9 | // Frameworks to use 10 | frameworks: [ 11 | 'browserify', 12 | 'mocha', 13 | 'chai', 14 | 'sinon' 15 | ], 16 | 17 | plugins: [ 18 | 'browserify', 19 | 'browserify-istanbul', 20 | 'karma-browserify', 21 | 'karma-coverage-istanbul-reporter', 22 | 'karma-mocha', 23 | 'karma-chai', 24 | 'karma-sinon', 25 | 'karma-chrome-launcher' 26 | ], 27 | 28 | browserify: { 29 | watch: true, 30 | debug: true, 31 | transform: ['browserify-istanbul'] 32 | }, 33 | 34 | coverageIstanbulReporter: { 35 | // Specify a common output directory 36 | dir: 'tests/reports/coverage', 37 | reporters: [ 38 | {type: 'lcov', subdir: 'report-lcov'}, 39 | {type : 'html', subdir : './'} 40 | ], 41 | check: { 42 | each: { 43 | statements: 90, 44 | branches: 90, 45 | functions: 90, 46 | lines: 90 47 | } 48 | } 49 | }, 50 | 51 | // Add preprocessor to the files that should be processed 52 | preprocessors: { 53 | 'tests/**/*spec.js': ['browserify'] 54 | }, 55 | 56 | // List of files / patterns to load in the browser 57 | files: [ 58 | {pattern: 'karma.bootstrap.js', watched: true, included: true, served: true}, 59 | {pattern: 'tests/styles/themes/light/light.css', watched: true, included: true, served: true}, 60 | {pattern: 'tests/styles/themes/dark/dark.css', watched: true, included: true, served: true}, 61 | {pattern: 'tests/**/*spec.js', watched: true, included: true, served: true} 62 | ], 63 | 64 | client: { 65 | mocha: { 66 | reporter: 'html', // Change Karma's debug.html to the mocha web reporter 67 | ui: 'bdd' 68 | } 69 | }, 70 | 71 | // List of files to exclude 72 | exclude: [], 73 | 74 | // Test results reporter to use 75 | // possible values: 'dots', 'progress' 76 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 77 | reporters: ['progress', 'coverage-istanbul'], 78 | 79 | // Web server port 80 | port: 9876, 81 | 82 | // Enable / disable colors in the output (reporters and logs) 83 | colors: true, 84 | 85 | // Level of logging 86 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 87 | logLevel: config.LOG_DEBUG, 88 | 89 | // Enable / disable watching file and executing tests whenever any file changes 90 | autoWatch: true, 91 | 92 | // Start these browsers 93 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 94 | browsers: ['ChromeHeadless'], 95 | 96 | customLaunchers: { 97 | Chrome_travis_ci: { 98 | base: 'ChromeHeadless', 99 | flags: ['--no-sandbox'] 100 | } 101 | }, 102 | 103 | // Continuous Integration mode 104 | // if true, Karma captures browsers, runs the tests and exits 105 | singleRun: false, 106 | 107 | concurrency: 2 108 | }; 109 | 110 | if (process.env.TRAVIS) { 111 | configuration.browsers = ['Chrome_travis_ci']; 112 | } 113 | 114 | config.set(configuration); 115 | }; 116 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ui5inspector", 3 | "version": "1.7.0", 4 | "author": "SAP SE", 5 | "license": "Apache-2.0", 6 | "contributors": [ 7 | "SAP SE <*@sap.com>" 8 | ], 9 | "dependencies": { 10 | "lodash": "^4.17.21" 11 | }, 12 | "devDependencies": { 13 | "@actions/core": "^1.9.1", 14 | "@actions/exec": "^1.1.1", 15 | "@actions/github": "^5.0.3", 16 | "@commitlint/cli": "^16.2.3", 17 | "@commitlint/config-conventional": "^16.2.1", 18 | "@semantic-release/changelog": "^6.0.1", 19 | "@semantic-release/commit-analyzer": "^9.0.2", 20 | "@semantic-release/exec": "^6.0.3", 21 | "@semantic-release/git": "^10.0.1", 22 | "@semantic-release/github": "^8.0.5", 23 | "@semantic-release/release-notes-generator": "^10.0.3", 24 | "browserify": "^16.5.1", 25 | "browserify-istanbul": "^3.0.1", 26 | "chai": "~4.2.0", 27 | "chrome-stub": "^1.0.2", 28 | "coveralls": "^3.1.1", 29 | "grunt": "^1.4.1", 30 | "grunt-banner": "~0.6.0", 31 | "grunt-browserify": "^6.0.0", 32 | "grunt-contrib-clean": "~2.0.0", 33 | "grunt-contrib-compress": "~1.6.0", 34 | "grunt-contrib-connect": "~2.1.0", 35 | "grunt-contrib-copy": "~1.0.0", 36 | "grunt-contrib-jshint": "~2.1.0", 37 | "grunt-contrib-less": "~2.0.0", 38 | "grunt-contrib-watch": "~1.1.0", 39 | "grunt-coveralls": "^2.0.0", 40 | "grunt-eslint": "^23.0.0", 41 | "grunt-karma": "^4.0.2", 42 | "grunt-newer": "~1.3.0", 43 | "grunt-text-replace": "~0.4.0", 44 | "husky": "^7.0.4", 45 | "jshint-stylish": "~2.2.1", 46 | "karma": "^6.4.0", 47 | "karma-browserify": "^8.1.0", 48 | "karma-chai": "^0.1.0", 49 | "karma-chrome-launcher": "^3.1.1", 50 | "karma-coverage": "^2.2.0", 51 | "karma-coverage-istanbul-reporter": "^3.0.3", 52 | "karma-mocha": "^2.0.1", 53 | "karma-sinon": "^1.0.5", 54 | "load-grunt-config": "~3.0.1", 55 | "load-grunt-tasks": "~5.1.0", 56 | "mocha": "^11.1.0", 57 | "prettify-xml": "^1.2.0", 58 | "semantic-release": "^19.0.3", 59 | "sinon": "~9.0.2", 60 | "time-grunt": "~2.0.0", 61 | "typescript": "^4.9.5" 62 | }, 63 | "engines": { 64 | "node": ">=10.0.0" 65 | }, 66 | "repository": { 67 | "type": "git", 68 | "url": "https://github.com/SAP/ui5-inspector" 69 | }, 70 | "scripts": { 71 | "generate:typescript": "tsc", 72 | "postinstall": "npm run generate:typescript && node -e \"require('grunt').tasks(['dist']);\"", 73 | "prepare": "husky install", 74 | "husky:pre-commit": "grunt test", 75 | "husky:commit-msg": "commitlint -e" 76 | } 77 | } -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | com.sap.ui5.inspector 5 | package 6 | 0.9.10 7 | pom 8 | 9 | UI5 Inspector 10 | With the UI5 Inspector, you can easily debug and support your OpenUI5 or SAPUI5-based apps. 11 | https://chrome.google.com/webstore/detail/ui5-inspector/bebecogbafbighhaildooiibipcnbngo 12 | 13 | 14 | 15 | Apache License, Version 2.0 16 | http://www.apache.org/licenses/LICENSE-2.0.txt 17 | repo 18 | 19 | 20 | 21 | 22 | SAP 23 | http://www.sap.com 24 | 25 | 26 | 27 | 28 | UI5 29 | SAP SE 30 | http://www.sap.com 31 | 32 | 33 | 34 | 35 | 36 | scm:git:https://github.com/SAP/ui5-inspector.git 37 | 38 | 39 | https://github.com/SAP/ui5-inspector 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /scripts/update-version.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | 4 | const args = process.argv.slice(2); 5 | console.log('Updating version - ' + args[0]); 6 | 7 | const packagePath = path.join(path.dirname(__dirname), 'package.json'); 8 | const manifestPath = path.join(path.dirname(__dirname), 'app/manifest.json'); 9 | 10 | const version = args[0]; 11 | const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf8')); 12 | const manifestJson = JSON.parse(fs.readFileSync(manifestPath, 'utf8')); 13 | 14 | packageJson.version = version; 15 | manifestJson.version = version; 16 | 17 | try { 18 | fs.writeFileSync(packagePath, JSON.stringify(packageJson, null, 2)); 19 | fs.writeFileSync(manifestPath, JSON.stringify(manifestJson, null, 4)); 20 | //file written successfully 21 | console.log('Update successful'); 22 | } catch (err) { 23 | console.error(err); 24 | } 25 | -------------------------------------------------------------------------------- /tests/modules/background/ContextMenu.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | window.chrome = require('chrome-stub'); 4 | var ContextMenu = require('../../../app/scripts/modules/background/ContextMenu.js'); 5 | 6 | describe('ContextMenu', function () { 7 | 8 | it('should exists', function () { 9 | ContextMenu.should.be.a('function'); 10 | }); 11 | 12 | describe('Constructor', function () { 13 | var contextMenu; 14 | 15 | beforeEach(function () { 16 | contextMenu = new ContextMenu({ 17 | title: 'some title', 18 | id: 'some ID', 19 | contexts: 'all' 20 | }); 21 | }); 22 | 23 | afterEach(function () { 24 | contextMenu = null; 25 | }); 26 | 27 | it('should set all given options', function () { 28 | contextMenu._title.should.equal('some title'); 29 | contextMenu._id.should.equal('some ID'); 30 | contextMenu._contexts.should.equal('all'); 31 | }); 32 | 33 | it('should create #onClicked()', function () { 34 | contextMenu.onClicked.should.be.a('function'); 35 | }); 36 | }); 37 | 38 | describe('#create()', function () { 39 | it('should call "chrome.contextMenus.create" only once', function () { 40 | // System under Test 41 | var contextMenu = new ContextMenu({ 42 | title: 'some title', 43 | id: 'some ID', 44 | contexts: 'all' 45 | }); 46 | 47 | // Act 48 | contextMenu.create(); 49 | 50 | // Assertion 51 | chrome.contextMenus.create.callCount.should.be.equals(1); 52 | }); 53 | 54 | it('should call "chrome.contextMenus.create" with the same parameter as the constructor was called', function () { 55 | // System under Test 56 | var contextMenu = new ContextMenu({ 57 | title: 'some title', 58 | id: 'some ID', 59 | contexts: 'all' 60 | }); 61 | 62 | // Act 63 | contextMenu.create(); 64 | 65 | // Assertion 66 | chrome.contextMenus.create.calledWith({ 67 | title: 'some title', 68 | id: 'some ID', 69 | contexts: 'all' 70 | }); 71 | }); 72 | }); 73 | 74 | describe('#removeAll()', function () { 75 | var contextMenu; 76 | 77 | beforeEach(function () { 78 | contextMenu = new ContextMenu({ 79 | title: 'some title', 80 | id: 'some ID', 81 | contexts: 'all' 82 | }); 83 | }); 84 | 85 | afterEach(function () { 86 | contextMenu = null; 87 | }); 88 | 89 | it('should remove all given options from chrome.contextMenu', function () { 90 | contextMenu.removeAll(); 91 | chrome.contextMenus.removeAll.callCount.should.be.equals(1); 92 | }); 93 | }); 94 | 95 | describe('#setRightClickTarget()', function () { 96 | var contextMenu; 97 | 98 | beforeEach(function () { 99 | contextMenu = new ContextMenu({ 100 | title: 'some title', 101 | id: 'some ID', 102 | contexts: 'all' 103 | }); 104 | }); 105 | 106 | afterEach(function () { 107 | contextMenu = null; 108 | }); 109 | 110 | it('should set the given target as property', function () { 111 | contextMenu.setRightClickTarget('target'); 112 | contextMenu._rightClickTarget.should.equal('target'); 113 | }); 114 | }); 115 | 116 | describe('#_onClickHandler()', function () { 117 | var contextMenu; 118 | var contextMenuOnClicked; 119 | 120 | beforeEach(function () { 121 | contextMenu = new ContextMenu({ 122 | title: 'some title', 123 | id: '1', 124 | contexts: 'all' 125 | }); 126 | 127 | contextMenuOnClicked = sinon.spy(contextMenu, 'onClicked'); 128 | }); 129 | 130 | afterEach(function () { 131 | contextMenu.onClicked.restore(); 132 | contextMenu = null; 133 | }); 134 | 135 | it('should call #onClicked with given properties', function () { 136 | contextMenu._onClickHandler({menuItemId:'1'},{}); 137 | 138 | contextMenu.onClicked.callCount.should.be.equals(1); 139 | }); 140 | 141 | it('should not call #onClicked when given ID is different from the one used for instantiation', function () { 142 | contextMenu._onClickHandler({menuItemId:'2'},{}); 143 | 144 | contextMenu.onClicked.callCount.should.be.equals(0); 145 | }); 146 | }); 147 | 148 | }); 149 | -------------------------------------------------------------------------------- /tests/modules/background/pageAction.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var pageAction = require('../../../app/scripts/modules/background/pageAction.js'); 4 | 5 | window.chrome = require('chrome-stub'); 6 | window.chrome.action = { 7 | /** 8 | * Mock function of chrome api. 9 | */ 10 | setTitle: function () { 11 | }, 12 | disable: function () { 13 | }, 14 | enable: function () { 15 | } 16 | }; 17 | 18 | describe('pageAction', function () { 19 | it('should return a object', function () { 20 | pageAction.should.be.a('object'); 21 | }); 22 | 23 | describe('#create()', function () { 24 | var pageActionSetTitle; 25 | 26 | beforeEach(function () { 27 | pageActionSetTitle = sinon.spy(window.chrome.action, 'setTitle'); 28 | }); 29 | 30 | afterEach(function () { 31 | pageActionSetTitle.restore(); 32 | }); 33 | 34 | it('should call chrome.action.setTitle', function () { 35 | pageAction.create({ 36 | framework: 'mock-framework', 37 | version: 'mock-version', 38 | tabId: 'mock-tabId' 39 | }); 40 | 41 | pageActionSetTitle.callCount.should.equal(1); 42 | }); 43 | }); 44 | 45 | describe('#disable() & #enable()', function () { 46 | var disableStub; 47 | var enableStub; 48 | 49 | beforeEach(function () { 50 | disableStub = sinon.spy(window.chrome.action, 'disable'); 51 | enableStub = sinon.spy(window.chrome.action, 'enable'); 52 | }); 53 | 54 | afterEach(function () { 55 | disableStub.restore(); 56 | enableStub.restore(); 57 | }); 58 | 59 | it('should call chrome.action.disable', function () { 60 | pageAction.disable(); 61 | 62 | disableStub.callCount.should.equal(1); 63 | }); 64 | 65 | it('should call chrome.action.enable', function () { 66 | pageAction.enable(); 67 | 68 | enableStub.callCount.should.equal(1); 69 | }); 70 | }); 71 | }); 72 | -------------------------------------------------------------------------------- /tests/modules/content/highLighter.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // TODO the highLighter module needs refactoring 4 | var highLighter = require('../../../app/scripts/modules/content/highLighter.ts'); 5 | 6 | describe('highLighter', function () { 7 | it('should return a object', function () { 8 | highLighter.should.be.a('object'); 9 | }); 10 | 11 | describe('#setDimensions()', function () { 12 | var fixtures = document.getElementById('fixtures'); 13 | 14 | before(function () { 15 | fixtures.innerHTML = '
'; 16 | }); 17 | 18 | after(function () { 19 | fixtures.innerHTML = ''; 20 | document.getElementById('ui5-highlighter').parentNode.removeChild(document.getElementById('ui5-highlighter')); 21 | }); 22 | 23 | it('should create a DOM elements', function () { 24 | highLighter.setDimensions('shell'); 25 | 26 | document.body.querySelector('#ui5-highlighter').should.exist; 27 | document.body.querySelector('#ui5-highlighter > div').should.exist; 28 | }); 29 | 30 | it('should set proper sizes to the "#ui5-highlighter > div" element', function () { 31 | highLighter.setDimensions('shell'); 32 | 33 | document.body.querySelector('#ui5-highlighter > div').style.width.should.equal('50px'); 34 | document.body.querySelector('#ui5-highlighter > div').style.height.should.equal('50px'); 35 | }); 36 | 37 | it('should position the inner div according to target', function () { 38 | var mock = document.getElementById('shell').getBoundingClientRect(); 39 | highLighter.setDimensions('shell'); 40 | 41 | document.body.querySelector('#ui5-highlighter > div').style.top.should.equal(mock.top + 'px'); 42 | document.body.querySelector('#ui5-highlighter > div').style.left.should.equal(mock.left + 'px'); 43 | }); 44 | 45 | it('should not add CSS styles if the target can not be found', function () { 46 | document.body.querySelector('#ui5-highlighter > div').removeAttribute('style'); 47 | highLighter.setDimensions('shell-mock'); 48 | 49 | document.body.querySelector('#ui5-highlighter > div').style.width.should.equal(''); 50 | document.body.querySelector('#ui5-highlighter > div').style.height.should.equal(''); 51 | document.body.querySelector('#ui5-highlighter > div').style.top.should.equal(''); 52 | document.body.querySelector('#ui5-highlighter > div').style.left.should.equal(''); 53 | }); 54 | 55 | it('should hide the highlighter on hover', function () { 56 | highLighter.setDimensions('shell'); 57 | document.body.querySelector('#ui5-highlighter').onmouseover(); 58 | 59 | document.body.querySelector('#ui5-highlighter').style.display.should.equal('none'); 60 | }); 61 | 62 | it('should create only one highlighter', function () { 63 | highLighter.setDimensions('shell'); 64 | highLighter.setDimensions('shell'); 65 | 66 | document.body.querySelectorAll('#ui5-highlighter').length.should.equal(1); 67 | }); 68 | 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /tests/modules/injected/message.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var message = require('../../../app/scripts/modules/injected/message.js'); 4 | var mock = { 5 | mock: 'mock' 6 | }; 7 | 8 | describe('message.js', function () { 9 | it('should return a object', function () { 10 | message.should.be.a('object'); 11 | }); 12 | 13 | it('should has send method', function () { 14 | message.send.should.be.a('function'); 15 | }); 16 | 17 | describe('#send()', function () { 18 | it('should fire custom event', function () { 19 | var customEvent = sinon.spy(); 20 | document.addEventListener('ui5-communication-with-content-script', customEvent); 21 | 22 | message.send(mock); 23 | 24 | customEvent.callCount.should.be.equal(1); 25 | customEvent.args[0][0].detail.mock.should.be.equal('mock'); 26 | }); 27 | it('should parse input but not alter it', function () { 28 | var customEvent = sinon.spy(); 29 | var myObject = { 30 | /** 31 | * Dummy function. 32 | */ 33 | function: function () { 34 | console.log('remove me'); 35 | }, 36 | array: [], 37 | undefined: undefined 38 | }; 39 | document.addEventListener('ui5-communication-with-content-script', customEvent); 40 | 41 | message.send(myObject); 42 | 43 | customEvent.args[0][0].detail.should.not.have.property('function'); 44 | customEvent.args[0][0].detail.should.not.have.property('undefined'); 45 | myObject.should.have.property('function'); 46 | myObject.should.have.property('undefined'); 47 | myObject.should.not.be.equal(customEvent.args[0][0].detail); 48 | }); 49 | it('should send a valid json message equivalent to JSON.parse(JSON.stringify(myObject))', function () { 50 | var customEvent = sinon.spy(); 51 | var myObject = { 52 | number: 5, 53 | boolean: true, 54 | string: 'Hello World', 55 | object: { 56 | another: 'object', 57 | emptyArray: [] 58 | }, 59 | array: [ 60 | { 61 | hello: 'world' 62 | }, 63 | 5, 64 | false, 65 | 'Hello World', 66 | [ 67 | null, 68 | 1 69 | ] 70 | ], 71 | null: null, 72 | undefined: undefined, 73 | /** 74 | * Dummy function. 75 | */ 76 | function: function () { 77 | console.log('remove me'); 78 | } 79 | }; 80 | document.addEventListener('ui5-communication-with-content-script', customEvent); 81 | 82 | message.send(myObject); 83 | 84 | customEvent.args[0][0].detail.should.be.deep.equal(JSON.parse(JSON.stringify(myObject))); 85 | }); 86 | it('should handle circular dependencies in input object', function () { 87 | var customEvent = sinon.spy(); 88 | var myObject = { 89 | object: { 90 | /** 91 | * Dummy function. 92 | */ 93 | function: function () { 94 | console.log('remove me'); 95 | }, 96 | another: 'object', 97 | array: [ 98 | 5, 99 | 'this becomes a circular dependency' 100 | ] 101 | }, 102 | array: [ 103 | { 104 | reference: 'this becomes a circular dependency' 105 | } 106 | ], 107 | anotherRef: 'this becomes a circular dependency' 108 | }; 109 | myObject.object.array[1] = myObject.object; 110 | myObject.array[0].reference = myObject.array; 111 | myObject.anotherRef = myObject; 112 | 113 | document.addEventListener('ui5-communication-with-content-script', customEvent); 114 | message.send(myObject); 115 | 116 | customEvent.args[0][0].detail.object.array[1].should.be.equal(''); 117 | customEvent.args[0][0].detail.array[0].reference.should.be.equal(''); 118 | customEvent.args[0][0].detail.anotherRef.should.be.equal(''); 119 | customEvent.args[0][0].detail.object.should.not.have.property('function'); 120 | }); 121 | }); 122 | }); 123 | -------------------------------------------------------------------------------- /tests/modules/injected/rightClickHandler.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var rightClickHandler = require('../../../app/scripts/modules/injected/rightClickHandler.ts'); 4 | 5 | describe('rightClickHandler', function () { 6 | it('should return a object', function () { 7 | rightClickHandler.should.be.a('object'); 8 | }); 9 | 10 | describe('#setClickedElementId()', function () { 11 | var fixtures = document.getElementById('fixtures'); 12 | 13 | beforeEach(function () { 14 | fixtures.innerHTML = '
mock
'; 15 | }); 16 | 17 | afterEach(function () { 18 | fixtures.innerHTML = ''; 19 | }); 20 | 21 | it('should be a function', function () { 22 | rightClickHandler.setClickedElementId.should.be.a('function'); 23 | }); 24 | 25 | it('should set the ID of the UI5 control', function () { 26 | var element = fixtures.querySelector('#shell'); 27 | rightClickHandler.setClickedElementId(element); 28 | 29 | rightClickHandler._clickedElementId.should.equal('shell'); 30 | }); 31 | 32 | it('should set the ID of the first UI5 parent control of the given target', function () { 33 | var element = fixtures.querySelector('#shell span'); 34 | rightClickHandler.setClickedElementId(element); 35 | 36 | rightClickHandler._clickedElementId.should.equal('shell'); 37 | }); 38 | 39 | it('should set empty string if there are no UI5 controls', function () { 40 | rightClickHandler.setClickedElementId(fixtures.firstElementChild); 41 | 42 | rightClickHandler._clickedElementId.should.equal(''); 43 | }); 44 | }); 45 | 46 | describe('#getClickedElementId()', function () { 47 | it('should be a function', function () { 48 | rightClickHandler.getClickedElementId.should.be.a('function'); 49 | }); 50 | 51 | it('should return the last clicked element ID', function () { 52 | rightClickHandler._clickedElementId = 'mock'; 53 | rightClickHandler.getClickedElementId().should.equal('mock'); 54 | }); 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /tests/modules/injected/ui5inspector.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var ui5inspector = require('../../../app/scripts/modules/injected/ui5inspector.js'); 4 | 5 | /** 6 | * Mock function of an eventListener callback. 7 | * @returns {string} 8 | */ 9 | var callBackMock = function mockFunctionName() { 10 | return 'mock'; 11 | }; 12 | 13 | /** 14 | * Mock function of an eventListener callback. 15 | * @returns {string} 16 | */ 17 | var callBackSecondMock = function secondMockFunctionName() { 18 | return 'mock'; 19 | }; 20 | 21 | describe('ui5inspector', function () { 22 | it('should return a object', function () { 23 | ui5inspector.should.be.a('object'); 24 | }); 25 | 26 | describe('#createReferences()', function () { 27 | beforeEach(function () { 28 | ui5inspector.createReferences(); 29 | }); 30 | 31 | afterEach(function () { 32 | window.ui5inspector = undefined; 33 | }); 34 | 35 | it('should be a function', function () { 36 | ui5inspector.createReferences.should.be.a('function'); 37 | }); 38 | 39 | it('should create ui5inspector global object', function () { 40 | window.ui5inspector.should.be.a('object'); 41 | }); 42 | 43 | it('should not overwrite window.ui5inspector if it is already set', function () { 44 | window.ui5inspector.mock = 'mock'; 45 | ui5inspector.createReferences(); 46 | 47 | window.ui5inspector.mock.should.equal('mock'); 48 | }); 49 | }); 50 | 51 | describe('#registerEventListener()', function () { 52 | 53 | beforeEach(function () { 54 | ui5inspector.createReferences(); 55 | }); 56 | 57 | afterEach(function () { 58 | window.ui5inspector = undefined; 59 | }); 60 | 61 | it('should be a function', function () { 62 | ui5inspector.registerEventListener.should.be.a('function'); 63 | }); 64 | 65 | it('should create a references for every event that is registered', function () { 66 | ui5inspector.registerEventListener('mock', callBackMock); 67 | 68 | window.ui5inspector.events.mock.callback.should.equal('mockFunctionName'); 69 | window.ui5inspector.events.mock.state.should.equal('registered'); 70 | }); 71 | 72 | it('should not register two events with the same name', function () { 73 | ui5inspector.registerEventListener('mock', callBackMock); 74 | ui5inspector.registerEventListener('mock', callBackSecondMock); 75 | 76 | window.ui5inspector.events.mock.callback.should.not.equal('secondMockFunctionName'); 77 | }); 78 | }); 79 | }); 80 | -------------------------------------------------------------------------------- /tests/modules/ui/JSONFormatter.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var JSONFormatter = require('../../../app/scripts/modules/ui/JSONFormatter.js'); 4 | 5 | var mockData = { 6 | title: 'Example Schema', 7 | type: 'object', 8 | properties: { 9 | firstName: { 10 | type: 'string' 11 | }, 12 | lastName: { 13 | type: 'string' 14 | }, 15 | age: { 16 | description: 'Age in years', 17 | type: 'integer', 18 | minimum: 0 19 | } 20 | }, 21 | required: ['firstName', 'lastName'], 22 | boolean: true, 23 | null: null 24 | }; 25 | 26 | describe('JSONFormatter', function () { 27 | var fixtures = document.getElementById('fixtures'); 28 | 29 | beforeEach(function () { 30 | // Create HTML elements from mockData object 31 | fixtures.innerHTML = JSONFormatter.formatJSONtoHTML(mockData); 32 | }); 33 | afterEach(function () { 34 | fixtures.innerHTML = ''; 35 | }); 36 | 37 | it('should create HTML elements from JSON object', function () { 38 | // Get all created elements from JSONFormater 39 | var result = JSON.parse(fixtures.firstChild.innerText); 40 | 41 | // Stringify the result, so that it can be compared to the mockData 42 | result = JSON.stringify(result); 43 | 44 | // Compaction 45 | result.should.equal(JSON.stringify(mockData)); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /tests/modules/ui/TabBar.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var TabBar = require('../../../app/scripts/modules/ui/TabBar.js'); 4 | 5 | describe('TabBar', function () { 6 | var fixtures = document.getElementById('fixtures'); 7 | var tabbar; 8 | 9 | beforeEach(function () { 10 | fixtures.innerHTML = '' + 11 | '' + 12 | ' ' + 13 | ' first' + 14 | ' second' + 15 | ' third' + 16 | ' ' + 17 | ' ' + 18 | ' lorem' + 19 | ' Lorem ipsum.' + 20 | ' Lorem ipsum dolor.' + 21 | ' ' + 22 | ''; 23 | 24 | tabbar = new TabBar('tabbar'); 25 | }); 26 | 27 | afterEach(function () { 28 | document.getElementById('tabbar').parentNode.removeChild(document.getElementById('tabbar')); 29 | }); 30 | 31 | describe('#init()', function () { 32 | describe('should call "#setActiveTab()" ', function () { 33 | var setActiveTab; 34 | var eventMock; 35 | 36 | beforeEach(function () { 37 | setActiveTab = sinon.spy(tabbar, 'setActiveTab'); 38 | 39 | tabbar.init(); 40 | }); 41 | 42 | afterEach(function () { 43 | tabbar.setActiveTab.restore(); 44 | }); 45 | 46 | it('only once', function () { 47 | setActiveTab.callCount.should.equal(1); 48 | }); 49 | 50 | it('with the ID of the initial active tab', function () { 51 | setActiveTab.calledWith(tabbar.getActiveTab()).should.equal(true); 52 | }); 53 | }); 54 | }); 55 | 56 | describe('#getActiveTab()', function () { 57 | it('should return "third"', function () { 58 | tabbar.getActiveTab().should.equal('third'); 59 | }); 60 | }); 61 | 62 | describe('#setActiveTab()', function () { 63 | 64 | it('should not change the active tab if a falsy parameter is used', function () { 65 | tabbar.setActiveTab(''); 66 | tabbar.getActiveTab().should.equal('third'); 67 | }); 68 | 69 | describe('should log warning message because', function () { 70 | var spyConsole; 71 | 72 | beforeEach(function () { 73 | spyConsole = sinon.spy(console, 'warn'); 74 | }); 75 | 76 | afterEach(function () { 77 | console.warn.restore(); 78 | }); 79 | 80 | it('an unknown ID is used', function () { 81 | tabbar.setActiveTab('unknownId'); 82 | spyConsole.callCount.should.equal(1); 83 | }); 84 | 85 | it('an non string parameter is used', function () { 86 | tabbar.setActiveTab(123); 87 | spyConsole.callCount.should.equal(1); 88 | }); 89 | }); 90 | 91 | it('should change active tab', function () { 92 | tabbar.setActiveTab('first'); 93 | tabbar.getActiveTab().should.equal('first'); 94 | }); 95 | 96 | }); 97 | 98 | describe('#_onTabsClick()', function () { 99 | describe('should call "#setActiveTab()" ', function () { 100 | var setActiveTab; 101 | var eventMock; 102 | 103 | beforeEach(function () { 104 | setActiveTab = sinon.spy(tabbar, 'setActiveTab'); 105 | eventMock = {target: {id: 'first'}}; 106 | 107 | tabbar._onTabsClick(eventMock); 108 | }); 109 | 110 | afterEach(function () { 111 | tabbar.setActiveTab.restore(); 112 | }); 113 | 114 | it('only once', function () { 115 | setActiveTab.callCount.should.equal(1); 116 | }); 117 | 118 | it('with mouse event argument', function () { 119 | setActiveTab.calledWith(eventMock.target.id).should.equal(true); 120 | }); 121 | }); 122 | }); 123 | 124 | describe('#_changeActiveTab()', function () { 125 | var successfulClick; 126 | 127 | beforeEach(function () { 128 | successfulClick = sinon.spy(tabbar, '_changeActiveTab'); 129 | }); 130 | 131 | afterEach(function () { 132 | tabbar._changeActiveTab.restore(); 133 | }); 134 | 135 | it('should add "selected" attribute to the active tab', function () { 136 | tabbar._changeActiveTab('first'); 137 | var selectedTab = tabbar._tabsContainer.querySelector('#first').getAttribute('selected'); 138 | 139 | selectedTab.should.equal('true'); 140 | }); 141 | it('should add "selected" attribute to the active content', function () { 142 | tabbar._changeActiveTab('first'); 143 | var selectedTab = tabbar._contentsContainer.querySelector('[for="first"]').getAttribute('selected'); 144 | 145 | selectedTab.should.equal('true'); 146 | }); 147 | it('should set only one "selected" attribute on the active tag and active content', function () { 148 | tabbar._changeActiveTab('first'); 149 | 150 | tabbar._tabsContainer.querySelectorAll('[selected]').length.should.equal(1); 151 | tabbar._contentsContainer.querySelectorAll('[selected]').length.should.equal(1); 152 | }); 153 | 154 | describe('should be called from mouse click', function () { 155 | beforeEach(function () { 156 | tabbar._tabsContainer.querySelector('#first').click(); 157 | }); 158 | 159 | it('only once', function () { 160 | tabbar._tabsContainer.querySelector('#first').click(); 161 | tabbar._tabsContainer.querySelector('#first').click(); 162 | 163 | successfulClick.callCount.should.equal(1); 164 | }); 165 | it('with parameter equal to "first"', function () { 166 | tabbar._tabsContainer.querySelector('#first').click(); 167 | 168 | var calledWithArgument = successfulClick.getCall(0).args[0]; 169 | calledWithArgument.should.equal('first'); 170 | }); 171 | it('and set "selected" attribute on the clicked tab ', function () { 172 | tabbar._tabsContainer.querySelector('[selected]').id.should.equal('first'); 173 | }); 174 | }); 175 | }); 176 | 177 | describe('Clicking with mouse', function () { 178 | var successfulClick; 179 | 180 | beforeEach(function () { 181 | successfulClick = sinon.spy(tabbar, '_changeActiveTab'); 182 | 183 | tabbar._tabsContainer.querySelector('#first').click(); 184 | tabbar._tabsContainer.querySelector('#second').click(); 185 | tabbar._tabsContainer.querySelector('#third').click(); 186 | }); 187 | 188 | afterEach(function () { 189 | tabbar._changeActiveTab.restore(); 190 | }); 191 | describe('multiple times', function () { 192 | it('should set only one "selected" attribute on the active tab"', function () { 193 | tabbar._tabsContainer.querySelectorAll('[selected]').length.should.equal(1); 194 | }); 195 | it('should set only one "selected" attribute on the active content"', function () { 196 | tabbar._contentsContainer.querySelectorAll('[selected]').length.should.equal(1); 197 | }); 198 | }); 199 | }); 200 | }); 201 | -------------------------------------------------------------------------------- /tests/modules/utils/utils.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var utils = require('../../../app/scripts/modules/utils/utils.js'); 4 | 5 | var messageAction = { 6 | /** 7 | * Mock function for the unit test purpose. 8 | * @param {Object} message 9 | */ 10 | 'mock-action': function (message) { 11 | return console.log('Message resolver is working'); 12 | } 13 | }; 14 | 15 | describe('utils', function () { 16 | it('should exists', function () { 17 | utils.should.be.a('object'); 18 | }); 19 | 20 | describe('#formatter', function () { 21 | it('should be an object', function () { 22 | utils.formatter.should.be.a('object'); 23 | }); 24 | }); 25 | 26 | describe('#formatter.convertUI5TimeStampToHumanReadableFormat()', function () { 27 | it('should re-format UI5 timestamp to human readable format', function () { 28 | utils.formatter.convertUI5TimeStampToHumanReadableFormat('201508171459').should.be.equal('2015/08/17 14:59h'); 29 | }); 30 | }); 31 | 32 | describe('#resolveMessage()', function () { 33 | var spyConsole; 34 | 35 | beforeEach(function () { 36 | spyConsole = sinon.spy(console, 'log'); 37 | }); 38 | 39 | afterEach(function () { 40 | console.log.restore(); 41 | }); 42 | 43 | it('should resolve the action that is needed from a message', function () { 44 | utils.resolveMessage({ 45 | message: { 46 | action: 'mock-action' 47 | }, 48 | messageSender: 'messageSender', 49 | sendResponse: 'sendResponse', 50 | actions: messageAction 51 | }); 52 | 53 | spyConsole.callCount.should.equal(1); 54 | }); 55 | 56 | it('should do nothing if no parameters are provided', function () { 57 | utils.resolveMessage(); 58 | 59 | spyConsole.callCount.should.equal(0); 60 | }); 61 | 62 | it('should do nothing if the parameter contains unknown action', function () { 63 | utils.resolveMessage({ 64 | message: { 65 | action: 'other-mock-action' 66 | }, 67 | messageSender: 'messageSender', 68 | sendResponse: 'sendResponse', 69 | actions: messageAction 70 | }); 71 | 72 | spyConsole.callCount.should.equal(0); 73 | }); 74 | 75 | }); 76 | 77 | describe('#setOSClassName()', function () { 78 | var navigatorAppVersionInitialValue = navigator.appVersion; 79 | 80 | beforeEach(function () { 81 | }); 82 | 83 | afterEach(function () { 84 | navigator.__defineGetter__('appVersion', function () { 85 | return navigatorAppVersionInitialValue; 86 | }); 87 | }); 88 | 89 | it('should add attribute os="windows" on Windows', function () { 90 | navigator.__defineGetter__('appVersion', function () { 91 | return 'Win'; 92 | }); 93 | 94 | utils.setOSClassName(); 95 | 96 | document.querySelector('body').getAttribute('os').should.equal('windows'); 97 | }); 98 | 99 | it('should add attribute os="mac" on OSx', function () { 100 | navigator.__defineGetter__('appVersion', function () { 101 | return 'Mac'; 102 | }); 103 | 104 | utils.setOSClassName(); 105 | 106 | document.querySelector('body').getAttribute('os').should.equal('mac'); 107 | }); 108 | 109 | it('should add attribute os="linux" on Linux', function () { 110 | navigator.__defineGetter__('appVersion', function () { 111 | return 'Linux'; 112 | }); 113 | 114 | utils.setOSClassName(); 115 | 116 | document.querySelector('body').getAttribute('os').should.equal('linux'); 117 | }); 118 | }); 119 | 120 | describe('#applyTheme()', function () { 121 | 122 | it('should change theme', function () { 123 | 124 | utils.applyTheme('dark'); 125 | 126 | expect(document.getElementById('ui5inspector-theme').href).to.have.string('/styles/themes/dark/dark.css'); 127 | 128 | utils.applyTheme('light'); 129 | 130 | expect(document.getElementById('ui5inspector-theme').href).to.have.string('/styles/themes/light/light.css'); 131 | }); 132 | }); 133 | 134 | describe('#getPort()', function () { 135 | 136 | it('should return port object', function () { 137 | 138 | var port = utils.getPort('dark'); 139 | 140 | port.onMessage.should.be.a('function'); 141 | port.postMessage.should.be.a('function'); 142 | }); 143 | }); 144 | }); 145 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": [ 3 | "app/**/*", 4 | "global.d.ts" 5 | ], 6 | "compilerOptions": { 7 | "declaration": true, 8 | "outDir": "tempDist", 9 | "target": "ES2021", 10 | "skipLibCheck": true, 11 | "sourceMap": true, 12 | "moduleResolution": "node", 13 | "allowJs": true, 14 | "checkJs": false 15 | } 16 | } --------------------------------------------------------------------------------