Compare commits
218 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1e0a7c1204 | ||
|
|
17b5df296c | ||
|
|
64b778b1af | ||
|
|
be6718ccf4 | ||
|
|
28e6237a43 | ||
|
|
4fab3b5933 | ||
|
|
79339a8963 | ||
|
|
a6faeeb656 | ||
|
|
c22b7f5c6b | ||
|
|
80f97602e4 | ||
|
|
f745656b4c | ||
|
|
f7e780b92e | ||
|
|
d7a76c7db9 | ||
|
|
16c380e1c6 | ||
|
|
072a3b80c4 | ||
|
|
5572b7ce67 | ||
|
|
71cf92c217 | ||
|
|
e26d5fded8 | ||
|
|
20282b0b99 | ||
|
|
5f18321ccf | ||
|
|
d14f5951ac | ||
|
|
5872ee7b58 | ||
|
|
1436463f59 | ||
|
|
729a31920d | ||
|
|
6d305b21b2 | ||
|
|
0b210e0d35 | ||
|
|
c8afb83130 | ||
|
|
49981f6ae3 | ||
|
|
b3b17e8d0c | ||
|
|
5d45f57e00 | ||
|
|
2f59d97cf3 | ||
|
|
b8d6b0a53e | ||
|
|
6cc82e7b38 | ||
|
|
cc7e3e62ed | ||
|
|
26de3b63ed | ||
|
|
8eafeffcec | ||
|
|
ef7926dd13 | ||
|
|
af307fd24b | ||
|
|
98401db922 | ||
|
|
75771e4815 | ||
|
|
a12205c840 | ||
|
|
ebfee69c7b | ||
|
|
05c25c72cb | ||
|
|
d2a24dd256 | ||
|
|
a6a6ef24b5 | ||
|
|
8374ecce85 | ||
|
|
0135d4fc64 | ||
|
|
47d7f33cb9 | ||
|
|
4c2e483592 | ||
|
|
801c7c862e | ||
|
|
f3bda74202 | ||
|
|
3ec5d13ae6 | ||
|
|
2133bb44ef | ||
|
|
e447666982 | ||
|
|
7038140088 | ||
|
|
b5d9633496 | ||
|
|
a518432db4 | ||
|
|
f1875702be | ||
|
|
b1a9130663 | ||
|
|
dfb30285ab | ||
|
|
c8cd78ae5f | ||
|
|
a5bc3ecd1b | ||
|
|
bec68e7cd4 | ||
|
|
02bba4ad5a | ||
|
|
57c4b12b24 | ||
|
|
ca33540fd6 | ||
|
|
cac3fabb2f | ||
|
|
c57b0edea8 | ||
|
|
3862bd294c | ||
|
|
0c18523891 | ||
|
|
629821bd6b | ||
|
|
08c5cf8104 | ||
|
|
d3c39bea75 | ||
|
|
141be3f962 | ||
|
|
433a725f33 | ||
|
|
769d5cfa3f | ||
|
|
9d925b6205 | ||
|
|
34527c3d6d | ||
|
|
35d489c2e2 | ||
|
|
9ef9981305 | ||
|
|
398192e057 | ||
|
|
4421217cb0 | ||
|
|
c92be00ca7 | ||
|
|
c66dfdc06c | ||
|
|
a882b1d6aa | ||
|
|
554b029569 | ||
|
|
4a15628325 | ||
|
|
fdb2aaaef4 | ||
|
|
a400e9c233 | ||
|
|
091c564a75 | ||
|
|
99eba8afcb | ||
|
|
cb78a2f2f9 | ||
|
|
f255efbf5d | ||
|
|
2f2d08fedd | ||
|
|
ac8b70bc84 | ||
|
|
0f2a4f9805 | ||
|
|
85228412cc | ||
|
|
03dfc0ce93 | ||
|
|
48427df559 | ||
|
|
7fda7aa2d9 | ||
|
|
4ba12b8b2d | ||
|
|
07af7b509e | ||
|
|
b7ff83e1da | ||
|
|
621359d6f9 | ||
|
|
a71f80f8fa | ||
|
|
a6cbfcda70 | ||
|
|
de2e9c16ac | ||
|
|
ad0c0dacba | ||
|
|
97564cf8b1 | ||
|
|
7fa44c06b0 | ||
|
|
dae192b7af | ||
|
|
5abfd5b7e5 | ||
|
|
65f5d70654 | ||
|
|
da7ba5c3a8 | ||
|
|
6e5df323d8 | ||
|
|
aad92678db | ||
|
|
15226d8598 | ||
|
|
a5fc586859 | ||
|
|
980c0387bf | ||
|
|
710d4a8842 | ||
|
|
ebb5a7cf16 | ||
|
|
7107f485be | ||
|
|
633fd39b80 | ||
|
|
9eeaecb994 | ||
|
|
7305175049 | ||
|
|
f99281ff47 | ||
|
|
557cd1aeb6 | ||
|
|
ca5f442fe0 | ||
|
|
585243bb73 | ||
|
|
a25b2c40c0 | ||
|
|
46d6520273 | ||
|
|
19286f6c56 | ||
|
|
8b760646ab | ||
|
|
b5f25e5e18 | ||
|
|
b335b4883e | ||
|
|
94e4203a0b | ||
|
|
221e2d365c | ||
|
|
6797f7d561 | ||
|
|
f843779b64 | ||
|
|
951d534343 | ||
|
|
c5acb43a7a | ||
|
|
979c036b49 | ||
|
|
63c877face | ||
|
|
9809587c68 | ||
|
|
808f4df3e3 | ||
|
|
bbadc711dc | ||
|
|
47b4d1aa5b | ||
|
|
18e2b2024e | ||
|
|
614d768eea | ||
|
|
807a609a90 | ||
|
|
82257be3a8 | ||
|
|
734fe33537 | ||
|
|
347e6b7408 | ||
|
|
bdf231f88d | ||
|
|
88ad73bd41 | ||
|
|
530f0919f1 | ||
|
|
79b89a3b25 | ||
|
|
4520d6a48a | ||
|
|
98678d4a13 | ||
|
|
da57204c41 | ||
|
|
906a11c6b4 | ||
|
|
c66ad78e48 | ||
|
|
6994bf51a3 | ||
|
|
521d508604 | ||
|
|
3946f6c894 | ||
|
|
aeccbf5d17 | ||
|
|
95e330a50d | ||
|
|
555000be54 | ||
|
|
c367f24dd9 | ||
|
|
0b3b7b3f57 | ||
|
|
44b33b4c92 | ||
|
|
61b339b146 | ||
|
|
27b84c5623 | ||
|
|
24edf1c6f4 | ||
|
|
fc363d7739 | ||
|
|
35d4dde118 | ||
|
|
e70e76a401 | ||
|
|
a804c31658 | ||
|
|
9efe580d18 | ||
|
|
bcb9d30237 | ||
|
|
fa60dd5248 | ||
|
|
fd653de0e1 | ||
|
|
ec140efc56 | ||
|
|
34477d7ef1 | ||
|
|
b65d1c51fc | ||
|
|
9111ec762b | ||
|
|
48970cb4f4 | ||
|
|
fe4b5a72ec | ||
|
|
8b41022ddc | ||
|
|
9c66c09899 | ||
|
|
09d9055643 | ||
|
|
b1204e0929 | ||
|
|
d130ca266d | ||
|
|
f730dc2827 | ||
|
|
b5f74395f7 | ||
|
|
b86806d4cd | ||
|
|
dfac9ad5e9 | ||
|
|
db67b864ee | ||
|
|
377d642fe2 | ||
|
|
c3651a37da | ||
|
|
8b843e028a | ||
|
|
2bdd98aaca | ||
|
|
d723451656 | ||
|
|
c47b401a1d | ||
|
|
3c330465dd | ||
|
|
f2f201c6e2 | ||
|
|
8984affe87 | ||
|
|
ce1e196b78 | ||
|
|
e3b7a7640e | ||
|
|
68dccf8575 | ||
|
|
8e100f286b | ||
|
|
f7ceb5c2bf | ||
|
|
8e1394e898 | ||
|
|
c69cfec533 | ||
|
|
c4a717bd77 | ||
|
|
b979a579f9 | ||
|
|
6233b9dbae | ||
|
|
9b682e62f8 |
@@ -19,8 +19,13 @@ jobs:
|
||||
name: Install dependencies
|
||||
command: npm install
|
||||
- run:
|
||||
name: Run tests
|
||||
command: npm run test-once
|
||||
name: BlackDuck scan
|
||||
command: curl -s https://blackducksoftware.github.io/hub-detect/hub-detect.sh | bash -s -- \
|
||||
--blackduck.url="https://qliktech.blackducksoftware.com" \
|
||||
--blackduck.trust.cert=true \
|
||||
--blackduck.username="svc-blackduck" \
|
||||
--blackduck.password=${svc_blackduck} \
|
||||
--detect.project.name="viz-bundle-qlik-smart-pivot"
|
||||
|
||||
bump-version:
|
||||
<<: *defaults
|
||||
@@ -48,16 +53,18 @@ jobs:
|
||||
command: |
|
||||
export VERSION=$(scripts/get-bumped-version.sh)
|
||||
echo "Version: ${VERSION}"
|
||||
npm run build
|
||||
npm run build:zip
|
||||
sudo chmod +x scripts/verify-files.sh
|
||||
scripts/verify-files.sh
|
||||
environment:
|
||||
NODE_ENV: production
|
||||
- persist_to_workspace:
|
||||
root: ~/qlik-smart-pivot
|
||||
paths:
|
||||
- build
|
||||
- dist
|
||||
- store_artifacts:
|
||||
path: build
|
||||
destination: build
|
||||
path: dist
|
||||
destination: dist
|
||||
deploy:
|
||||
<<: *defaults
|
||||
steps:
|
||||
|
||||
20
.eslintrc.js
20
.eslintrc.js
@@ -15,6 +15,7 @@ module.exports = {
|
||||
},
|
||||
globals: {
|
||||
angular: false,
|
||||
beforeEach: false,
|
||||
define: false,
|
||||
describe: false,
|
||||
document: false,
|
||||
@@ -41,12 +42,12 @@ module.exports = {
|
||||
"no-cond-assign": ["warn"],
|
||||
"no-fallthrough": ["warn"],
|
||||
"no-undef": ["error"],
|
||||
"no-unused-vars": ["warn"],
|
||||
"no-use-before-define": ["warn", { "functions": false, "classes": false, "variables": false }],
|
||||
"no-unused-vars": ["error"],
|
||||
"no-use-before-define": ["error", { "functions": false, "classes": false, "variables": false }],
|
||||
"no-useless-escape": ["warn"],
|
||||
"no-useless-return": ["warn"],
|
||||
"no-underscore-dangle": ["warn", { "allow": ["_id"] }],
|
||||
"no-redeclare": ["warn"],
|
||||
"no-redeclare": ["error"],
|
||||
"no-restricted-syntax": ["warn"],
|
||||
"operator-linebreak": ["warn", "before"],
|
||||
"prefer-promise-reject-errors": ["warn"],
|
||||
@@ -63,11 +64,11 @@ module.exports = {
|
||||
"complexity": ["warn"],
|
||||
"camelcase": ["warn"],
|
||||
"max-statements": ["off"], // marks the entire functions, a bit too noisy
|
||||
"sort-vars": ["warn"],
|
||||
"sort-vars": ["off"], // not much value for the work
|
||||
"init-declarations": ["off"],
|
||||
"capitalized-comments": ["off"],
|
||||
"one-var": ["off"],
|
||||
"no-var": ["warn"],
|
||||
"no-var": ["error"],
|
||||
"no-plusplus": ["warn"],
|
||||
"vars-on-top": ["off"],
|
||||
"no-magic-numbers": ["off"], // useful, but also complains for reasonable checks with actual numbers
|
||||
@@ -80,7 +81,7 @@ module.exports = {
|
||||
"quote-props": ["off"],
|
||||
"prefer-template": ["warn"],
|
||||
"no-lonely-if": ["warn"],
|
||||
"sort-keys": ["warn"],
|
||||
"sort-keys": ["off"], // not much value for the work
|
||||
"no-implicit-coercion": ["warn"],
|
||||
"no-inline-comments": ["off"],
|
||||
"spaced-comment": ["warn"],
|
||||
@@ -107,7 +108,7 @@ module.exports = {
|
||||
"strict": ["warn"],
|
||||
"no-ternary": ["off"],
|
||||
"multiline-ternary": ["off"],
|
||||
"no-param-reassign": ["warn"],
|
||||
"no-param-reassign": ["error"],
|
||||
"prefer-destructuring": ["warn"],
|
||||
"arrow-parens": ["off"],
|
||||
"no-array-constructor": ["warn"],
|
||||
@@ -116,7 +117,7 @@ module.exports = {
|
||||
"max-params": ["warn"],
|
||||
"brace-style": ["warn", "1tbs", { "allowSingleLine": true }],
|
||||
"prefer-const": ["warn"],
|
||||
|
||||
"class-methods-use-this":["warn"],
|
||||
// plugin:react
|
||||
"react/jsx-indent": ["warn", 2],
|
||||
"react/jsx-indent-props": ["warn", 2],
|
||||
@@ -129,7 +130,8 @@ module.exports = {
|
||||
"react/jsx-no-literals": ["off"],
|
||||
"react/jsx-max-depth": ["off"], // rule throws exception in single-dimension-measure
|
||||
"react/jsx-filename-extension": ["warn"],
|
||||
"react/prefer-stateless-function": ["warn"]
|
||||
"react/prefer-stateless-function": ["warn"],
|
||||
"react/no-set-state": ["warn"]
|
||||
},
|
||||
extends: [
|
||||
"eslint:all",
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -19,7 +19,7 @@ $RECYCLE.BIN/
|
||||
|
||||
# Temporary build files
|
||||
node_modules/
|
||||
build/
|
||||
dist/
|
||||
BUMPED_VERSION
|
||||
|
||||
# =========================
|
||||
|
||||
49
README.md
49
README.md
@@ -1,57 +1,22 @@
|
||||
# P&L Smart Pivot, a Qlik Sense Extension for Financial reporting
|
||||
[](https://circleci.com/gh/qlik-oss/PLSmartPivot)
|
||||
This extension is part of the extension bundles for Qlik Sense. The repository is maintained and moderated by Qlik RD.
|
||||
|
||||
This extension is useful to create reports where the look&feel is rellevantand and pivot a second dimension is needed. Based on P&L Smart.
|
||||
|
||||
It's specifically focused on financial reports, trying to solve some common needs of this area:
|
||||
- smart export to excel
|
||||
- easy creation of reports
|
||||
- custom corporate reporting (bold, italic, background color, letter size, headers,...)
|
||||
- selections inside the reports
|
||||
- custom external templates
|
||||
- analytical reports
|
||||
|
||||
|
||||
# Manual
|
||||
You'll find a manual [Qlik Sense P&LSmart Pivot Extension Manual.pdf](resources/Qlik Sense P&LSmart Pivot Extension Manual.pdf) and one app example [P&LSmartPivot_demo.qvf](resources/P&LSmartPivot_demo.qvf).
|
||||
|
||||
|
||||
# If the import does not work at first time
|
||||
- remove [Accounts.csv](resources/Accounts.csv), [Accounts2.csv](resources/Accounts2.csv) and [Excel.png](resources/Excel.png), zip it again and import.
|
||||
- Then reintroduce [Accounts.csv](resources/Accounts.csv), [Accounts2.csv](resources/Accounts2.csv) and [Excel.png](resources/Excel.png), zip it again and import.
|
||||
|
||||
|
||||
# Installation
|
||||
|
||||
1. Download the extension zip, `qlik-smart-pivot_<version>.zip`, from the latest release(https://github.com/qlik-oss/PLSmartPivot/releases/latest)
|
||||
2. Install the extension:
|
||||
|
||||
a. **Qlik Sense Desktop**: unzip to a directory under [My Documents]/Qlik/Sense/Extensions.
|
||||
|
||||
b. **Qlik Sense Server**: import the zip file in the QMC.
|
||||
Feel free to fork and suggest pull requests for improvements and bug fixes. Changes will be moderated and reviewed before inclusion in future bundle versions. Please note that emphasis is on backward compatibility, i.e. breaking changes will most likely not be approved.
|
||||
|
||||
Usage documentation for the extension is available at https://help.qlik.com.
|
||||
|
||||
# Developing the extension
|
||||
|
||||
If you want to do code changes to the extension follow these simple steps to get going.
|
||||
|
||||
1. Get Qlik Sense Desktop
|
||||
1. Create a new app and add the extension to a sheet.
|
||||
1. Create a new app and add P&L pivot to a sheet.
|
||||
2. Clone the repository
|
||||
3. Run `npm install`
|
||||
4. Set the environment variable `BUILD_PATH` to your extensions directory. It will be something like `C:/Users/<user>/Documents/Qlik/Sense/Extensions/<extension_name>`.
|
||||
5. You now have two options. Either run the watch task or the build task. They are explained below. Both of them default to development mode but can be run in production by setting `NODE_ENV=production` before running the npm task.
|
||||
|
||||
a. **Watch**: `npm run watch`. This will start a watcher which will rebuild the extension and output all needed files to the `buildFolder` for each code change you make. See your changes directly in your Qlik Sense app.
|
||||
|
||||
b. **Build**: `npm run build`. If you want to build the extension package. The output zip-file can be found in the `buildFolder`.
|
||||
|
||||
4. Run `npm run build` - to build a dev-version to the /dist folder.
|
||||
5. Move the content of the /dist folder to the extension directory. Usually in `C:/Users/<user>/Documents/Qlik/Sense/Extensions/qlik-smart-pivot`.
|
||||
|
||||
# Original authors
|
||||
|
||||
[github.com/iviasensio](https://github.com/iviasensio)
|
||||
|
||||
|
||||
# License
|
||||
|
||||
Released under the [MIT License](LICENSE).
|
||||
Released under the [MIT License](LICENSE).
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 123 B After Width: | Height: | Size: 7.5 KiB |
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"name": "Smart pivot",
|
||||
"description": "Formatted table for P&L reports.",
|
||||
"type": "visualization",
|
||||
"version": "X.Y.Z",
|
||||
"icon": "table",
|
||||
"preview": "",
|
||||
"author": "Ivan Felipe Asensio <ivan.felipe@qlik.com>",
|
||||
"homepage": "",
|
||||
"keywords": "qlik-sense, visualization",
|
||||
"license": "MIT",
|
||||
"repository": "https://github.com/qlik-oss/PLSmartPivot",
|
||||
"dependencies": {
|
||||
"qlik-sense": ">=5.5.x"
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
qlik-smart-pivot.js;
|
||||
qlik-smart-pivot.css;
|
||||
qlik-smart-pivot.qext
|
||||
94
gulpfile.js
94
gulpfile.js
@@ -1,23 +1,61 @@
|
||||
var gulp = require('gulp');
|
||||
var gutil = require('gulp-util');
|
||||
var zip = require('gulp-zip');
|
||||
var del = require('del');
|
||||
var path = require('path');
|
||||
var settings = require('./settings');
|
||||
var webpackConfig = require('./webpack.config');
|
||||
var webpack = require('webpack');
|
||||
var WebpackDevServer = require('webpack-dev-server');
|
||||
var jeditor = require("gulp-json-editor");
|
||||
var pkg = require('./package.json');
|
||||
|
||||
var srcFiles = path.resolve('./src/**/*.*');
|
||||
var DIST = './dist';
|
||||
var VERSION = process.env.VERSION || 'local-dev';
|
||||
|
||||
gulp.task('remove-build-folder', function(){
|
||||
return del([settings.buildDestination], { force: true });
|
||||
gulp.task('qext', function () {
|
||||
var qext = {
|
||||
name: 'P&L pivot',
|
||||
type: 'visualization',
|
||||
description: pkg.description + '\nVersion: ' + VERSION,
|
||||
version: VERSION,
|
||||
icon: 'pivot-table',
|
||||
preview: 'qlik-smart-pivot.png',
|
||||
keywords: 'qlik-sense, visualization',
|
||||
author: pkg.author,
|
||||
homepage: pkg.homepage,
|
||||
license: pkg.license,
|
||||
repository: pkg.repository,
|
||||
dependencies: {
|
||||
'qlik-sense': '>=5.5.x'
|
||||
}
|
||||
};
|
||||
if (pkg.contributors) {
|
||||
qext.contributors = pkg.contributors;
|
||||
}
|
||||
var src = require('stream').Readable({
|
||||
objectMode: true
|
||||
});
|
||||
src._read = function () {
|
||||
this.push(new gutil.File({
|
||||
cwd: '',
|
||||
base: '',
|
||||
path: pkg.name + '.qext',
|
||||
contents: Buffer.from(JSON.stringify(qext, null, 4))
|
||||
}));
|
||||
this.push(null);
|
||||
};
|
||||
return src.pipe(gulp.dest(DIST));
|
||||
});
|
||||
|
||||
gulp.task('clean', function(){
|
||||
return del([DIST], { force: true });
|
||||
});
|
||||
|
||||
gulp.task('zip-build', function(){
|
||||
return gulp.src(settings.buildDestination + '/**/*')
|
||||
.pipe(zip(`${settings.name}_${settings.version}.zip`))
|
||||
.pipe(gulp.dest(settings.buildDestination));
|
||||
return gulp.src(DIST + '/**/*')
|
||||
.pipe(zip(`${pkg.name}_${VERSION}.zip`))
|
||||
.pipe(gulp.dest(DIST));
|
||||
});
|
||||
|
||||
gulp.task('add-assets', function(){
|
||||
return gulp.src('./assets/**/*').pipe(gulp.dest(DIST));
|
||||
});
|
||||
|
||||
gulp.task('webpack-build', done => {
|
||||
@@ -36,40 +74,14 @@ gulp.task('webpack-build', done => {
|
||||
});
|
||||
});
|
||||
|
||||
gulp.task('update-qext-version', function () {
|
||||
return gulp.src(`${settings.buildDestination}/${settings.name}.qext`)
|
||||
.pipe(jeditor({
|
||||
'version': settings.version
|
||||
}))
|
||||
.pipe(gulp.dest(settings.buildDestination));
|
||||
});
|
||||
|
||||
gulp.task('build',
|
||||
gulp.series('remove-build-folder', 'webpack-build', 'update-qext-version', 'zip-build')
|
||||
gulp.series('clean', 'webpack-build', 'qext', 'add-assets')
|
||||
);
|
||||
|
||||
gulp.task('zip',
|
||||
gulp.series('build', 'zip-build')
|
||||
);
|
||||
|
||||
gulp.task('default',
|
||||
gulp.series('build')
|
||||
);
|
||||
|
||||
gulp.task('watch', () => new Promise((resolve, reject) => {
|
||||
webpackConfig.entry.unshift('webpack-dev-server/client?http://localhost:' + settings.port);
|
||||
const compiler = webpack(webpackConfig);
|
||||
const originalOutputFileSystem = compiler.outputFileSystem;
|
||||
const devServer = new WebpackDevServer(compiler, {
|
||||
headers: {
|
||||
"Access-Control-Allow-Origin": "*"
|
||||
},
|
||||
}).listen(settings.port, 'localhost', error => {
|
||||
compiler.outputFileSystem = originalOutputFileSystem;
|
||||
if (error) {
|
||||
console.error(error); // eslint-disable-line no-console
|
||||
return reject(error);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('Listening at localhost:' + settings.port);
|
||||
|
||||
resolve(null, devServer);
|
||||
});
|
||||
}));
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
const settings = require('./settings');
|
||||
|
||||
module.exports = (config) => {
|
||||
config.set({
|
||||
browsers: ['SlimChromeHeadless'],
|
||||
customLaunchers: {
|
||||
SlimChromeHeadless: {
|
||||
base: 'ChromeHeadless',
|
||||
flags: ['--headless', '--disable-gpu', '--disable-translate', '--disable-extensions']
|
||||
}
|
||||
},
|
||||
files: [
|
||||
{ pattern: 'src/*.spec.js', watched: false }
|
||||
],
|
||||
frameworks: ['jasmine'],
|
||||
preprocessors: {
|
||||
'src/*.spec.js': ['webpack', 'sourcemap']
|
||||
},
|
||||
webpack: {
|
||||
devtool: 'source-map',
|
||||
mode: settings.mode,
|
||||
externals: {
|
||||
jquery: {
|
||||
amd: 'jquery',
|
||||
commonjs: 'jquery',
|
||||
commonjs2: 'jquery',
|
||||
root: '_'
|
||||
},
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
exclude: [/node_modules/],
|
||||
loaders: ['babel-loader']
|
||||
},
|
||||
{ test: /\.less$/, loader: 'ignore-loader' },
|
||||
{ test: /\.json$/, loader: 'ignore-loader' }
|
||||
]
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
2979
package-lock.json
generated
2979
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
27
package.json
27
package.json
@@ -1,51 +1,44 @@
|
||||
{
|
||||
"name": "qlik-smart-pivot",
|
||||
"version": "0.0.1",
|
||||
"description": "Formatted table for P&L reports.",
|
||||
"keywords": "smart pivot qliksense extension",
|
||||
"description": "Profit & Loss reporting with color and font customizations.",
|
||||
"homepage": "",
|
||||
"repository": "https://github.com/iviasensio/PLSmartPivot",
|
||||
"author": "Ivan Felipe Asensio <ivan.felipe@qlik.com>",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"build": "gulp build",
|
||||
"build:zip": "gulp zip",
|
||||
"eslint": "eslint src",
|
||||
"eslint:fix": "eslint --fix src",
|
||||
"test": "karma start karma.conf.js",
|
||||
"test-once": "karma start karma.conf.js --single-run",
|
||||
"watch": "gulp watch"
|
||||
"stylelint": "stylelint src/main.less"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.1.2",
|
||||
"@babel/plugin-proposal-class-properties": "7.3.3",
|
||||
"@babel/plugin-transform-async-to-generator": "7.1.0",
|
||||
"@babel/polyfill": "7.0.0",
|
||||
"@babel/preset-env": "7.1.0",
|
||||
"@babel/preset-react": "7.0.0",
|
||||
"babel-eslint": "10.0.1",
|
||||
"babel-loader": "8.0.4",
|
||||
"copy-webpack-plugin": "4.5.3",
|
||||
"css-loader": "1.0.0",
|
||||
"del": "2.0.2",
|
||||
"enzyme": "3.8.0",
|
||||
"enzyme-adapter-react-16": "1.9.1",
|
||||
"eslint": "5.7.0",
|
||||
"eslint-loader": "2.1.1",
|
||||
"eslint-plugin-react": "7.11.1",
|
||||
"gulp": "4.0.0",
|
||||
"gulp-json-editor": "2.4.3",
|
||||
"gulp-util": "^3.0.7",
|
||||
"gulp-zip": "3.0.2",
|
||||
"jasmine-core": "3.2.1",
|
||||
"karma": "3.0.0",
|
||||
"karma-chrome-launcher": "2.2.0",
|
||||
"karma-jasmine": "1.1.2",
|
||||
"karma-sourcemap-loader": "0.3.7",
|
||||
"karma-webpack": "3.0.5",
|
||||
"less": "3.8.1",
|
||||
"less-loader": "4.1.0",
|
||||
"lodash.merge": "4.6.1",
|
||||
"style-loader": "0.23.1",
|
||||
"stylelint": "8.4.0",
|
||||
"stylelint-webpack-plugin": "0.10.5",
|
||||
"text-loader": "0.0.1",
|
||||
"webpack": "4.20.2",
|
||||
"webpack-cli": "3.1.2",
|
||||
"webpack-dev-server": "3.1.10"
|
||||
"webpack": "4.20.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"prop-types": "15.6.2",
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
Accounts;Bold;Background;FontStyle;LetterColor;Align;Size;Comment
|
||||
Revenues;;rgb(183, 219, 255);<italic>;;<center>;;<comment>
|
||||
Gross sales revenues;;;;;;;
|
||||
Less return & allowances;;;;;;;
|
||||
Net sales revenues;<bold>;<soft>;;;;;
|
||||
Cost of goods sold;<bold>;;;;;;
|
||||
Direct materials;;;;;;;
|
||||
Direct labor;;;;;;;
|
||||
Manufacturing overhead;;#b7dbff;<italic>;;<center>;;<comment>
|
||||
Indirect labor;;;;;;;
|
||||
Depreciation, manufacturing equip;;;;;;;
|
||||
Other mfr overhead;;;;;;;
|
||||
Net mfr overhead;<bold>;<violete>;;;;;
|
||||
Net costs of goods sold;<bold>;<violete>;;;;;
|
||||
Gross profit;<bold>;<dark>;;;<center>;<large>;
|
||||
Operating expenses;<bold>;;<italic>;;;;
|
||||
Selling expenses;;;;;;;
|
||||
Sales salaries;;;;;;;
|
||||
Warranty expenses;;;;;;;
|
||||
Depreciation, store equipment;;;;;;;
|
||||
Other selling expenses3;;;;;;;
|
||||
Total selling expenses;<bold>;<violete>;;;;;
|
||||
General & administrative expenses;;#b7dbff;<italic>;;<center>;;<comment>
|
||||
Administration salaries;;;;;;;
|
||||
Rent expenses;;;;;;;
|
||||
Depreciation, computers;;;;;;;
|
||||
Other general & admin expenses;;;;;;;
|
||||
total general & admin expenses;<bold>;<soft>;;;;;
|
||||
total operating expenses;<bold>;<violete>;;;;;
|
||||
Operating income before taxes;<bold>;<dark>;;<white>;;<large>;
|
||||
Financial revenue & expenses;;#b7dbff;<italic>;;<center>;;<comment>
|
||||
Revenue from investments;;;;;;;
|
||||
Less interest expenses;;;;;;;
|
||||
Net financial gain (expense);;;;;;;
|
||||
Income before tax & extraordinary items;<bold>;<dark>;;<white>;;<large>;
|
||||
Less income tax on operations;;;;;;;
|
||||
Income before extraordinary items;;;;;;;
|
||||
Extraordinary items;;#b7dbff;<italic>;;<center>;;<comment>
|
||||
Sale of land;;;;;;;
|
||||
Less initial cost;;;;;;;
|
||||
Net gain on sale of land;<bold>;<soft>;;;;;
|
||||
Less income tax on gain;;;;;;;
|
||||
Extraordinary items after tax;<bold>;<soft>;;;;;
|
||||
Net Income (Profit);<bold>;<dark>;;<white>;<center>;<large>;
|
||||
|
@@ -1,21 +0,0 @@
|
||||
Accounts;Bold;Background;FontStyle;LetterColor;Align;Size;Comment
|
||||
Revenues;<bold>;;<italic>;;<center>;;<comment>
|
||||
Net sales revenues;<bold>;rgb(128, 191, 255);;;;;
|
||||
Cost of goods sold;<bold>;rgb(128, 191, 255);;;;;
|
||||
Manufacturing overhead;<bold>;;<italic>;;<center>;;<comment>
|
||||
Net mfr overhead;<bold>;rgb(128, 191, 255);;;;;
|
||||
Net costs of goods sold;<bold>;rgb(128, 191, 255);;;;;
|
||||
Gross profit;<bold>;rgb(0, 102, 204);<white>;;<center>;<large>;
|
||||
Operating expenses;<bold>;;<italic>;;;;
|
||||
Total selling expenses;<bold>;rgb(128, 191, 255);;;;;
|
||||
General & administrative expenses;<bold>;;<italic>;;<center>;;<comment>
|
||||
Other general & admin expenses;<bold>;rgb(128, 191, 255);<white>;;<center>;<large>;
|
||||
total general & admin expenses;<bold>;<soft>;;;;;
|
||||
total operating expenses;<bold>;rgb(128, 191, 255);;;;;
|
||||
Operating income before taxes;<bold>;rgb(0, 102, 204);;<white>;;<large>;
|
||||
Financial revenue & expenses;<bold>;;<italic>;;<center>;;<comment>
|
||||
Income before tax & extraordinary items;<bold>;rgb(0, 102, 204);;<white>;;<large>;
|
||||
Extraordinary items;<bold>;;<italic>;;<center>;;<comment>
|
||||
Net gain on sale of land;<bold>;rgb(0, 102, 204);<white>;;<center>;<large>;
|
||||
Extraordinary items after tax;<bold>;rgb(0, 102, 204);<white>;;<center>;<large>;
|
||||
Net Income (Profit);<bold>;<night>;;<white>;<center>;<large>;
|
||||
|
Binary file not shown.
|
Before Width: | Height: | Size: 2.2 KiB |
Binary file not shown.
@@ -1,21 +0,0 @@
|
||||
Accounts;Bold;Background;FontStyle;LetterColor;Align;Size;Comment
|
||||
Revenues;<bold>;;<italic>;;<center>;;<comment>
|
||||
Net sales revenues;<bold>;RGB(225,226,226);;;;;
|
||||
Cost of goods sold;<bold>;RGB(225,226,226);;;;;
|
||||
Manufacturing overhead;<bold>;;<italic>;;<center>;;<comment>
|
||||
Net mfr overhead;<bold>;RGB(225,226,226);;;;;
|
||||
Net costs of goods sold;<bold>;RGB(225,226,226);;;;;
|
||||
Gross profit;<bold>;RGB(193,216,47);;;<center>;<large>;
|
||||
Operating expenses;<bold>;;<italic>;;;;
|
||||
Total selling expenses;<bold>;RGB(225,226,226);;;;;
|
||||
General & administrative expenses;<bold>;;<italic>;;<center>;;<comment>
|
||||
Other general & admin expenses;<bold>;rgb(128, 191, 255);<white>;;<center>;<large>;
|
||||
total general & admin expenses;<bold>;<soft>;;;;;
|
||||
total operating expenses;<bold>;rgb(128, 191, 255);<white>;;;;
|
||||
Operating income before taxes;<bold>;RGB(193,216,47);;;;<large>;
|
||||
Financial revenue & expenses;<bold>;;<italic>;;<center>;;<comment>
|
||||
Income before tax & extraordinary items;<bold>;RGB(193,216,47);;;;<large>;
|
||||
Extraordinary items;<bold>;;<italic>;;<center>;;<comment>
|
||||
Net gain on sale of land;<bold>;RGB(193,216,47);;;<center>;<large>;
|
||||
Extraordinary items after tax;<bold>;RGB(193,216,47);;;<center>;<large>;
|
||||
Net Income (Profit);<bold>;<night>;;<white>;<center>;<large>;
|
||||
|
@@ -2,8 +2,8 @@
|
||||
set -o errexit
|
||||
|
||||
echo "Creating release for version: $VERSION"
|
||||
echo "Artifact name: ./build/${3}_${VERSION}.zip"
|
||||
$HOME/bin/ghr -t ${ghoauth} -u ${CIRCLE_PROJECT_USERNAME} -r ${CIRCLE_PROJECT_REPONAME} -c ${CIRCLE_SHA1} ${VERSION} "./build/${3}_${4}.zip"
|
||||
echo "Artifact name: ./dist/${3}_${VERSION}.zip"
|
||||
$HOME/bin/ghr -t ${ghoauth} -u ${CIRCLE_PROJECT_USERNAME} -r ${CIRCLE_PROJECT_REPONAME} -c ${CIRCLE_SHA1} ${VERSION} "./dist/${3}_${4}.zip"
|
||||
|
||||
|
||||
# Usage
|
||||
|
||||
25
scripts/verify-files.sh
Normal file
25
scripts/verify-files.sh
Normal file
@@ -0,0 +1,25 @@
|
||||
#!/bin/bash
|
||||
# The build script has a known race-condition that sometimes causes it to not include all files
|
||||
# in the built zip. This script verifies the that the zip contains the correct number of files.
|
||||
|
||||
set -o errexit
|
||||
|
||||
echo "Verifying built file count"
|
||||
|
||||
while read line; do
|
||||
if [[ $line =~ ^\"name\": ]]; then
|
||||
name=${line#*: \"}
|
||||
name=${name%\"*}
|
||||
fi
|
||||
done < package.json
|
||||
|
||||
expected_file_count=$(($(find dist -type f | wc -l)-1))
|
||||
zip_file_count=$(zipinfo dist/${name}_${VERSION}.zip | grep ^- | wc -l)
|
||||
|
||||
if [ "${expected_file_count}" -ne "${zip_file_count}" ]; then
|
||||
# File count is incorrect
|
||||
echo "Expected file count ${expected_file_count}, but was ${zip_file_count}"
|
||||
exit 1
|
||||
fi
|
||||
echo "File count OK"
|
||||
exit 0
|
||||
13
settings.js
13
settings.js
@@ -1,13 +0,0 @@
|
||||
const path = require('path');
|
||||
const packageJSON = require('./package.json');
|
||||
|
||||
const defaultBuildDestination = path.resolve("./build");
|
||||
|
||||
module.exports = {
|
||||
buildDestination: process.env.BUILD_PATH || defaultBuildDestination,
|
||||
mode: process.env.NODE_ENV || 'development',
|
||||
name: packageJSON.name,
|
||||
version: process.env.VERSION || 'local-dev',
|
||||
url: process.env.BUILD_URL || defaultBuildDestination,
|
||||
port: process.env.PORT || 8085
|
||||
};
|
||||
149
src/data-table/data-cell.jsx
Normal file
149
src/data-table/data-cell.jsx
Normal file
@@ -0,0 +1,149 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Tooltip from '../tooltip/index.jsx';
|
||||
|
||||
class DataCell extends React.PureComponent {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
this.handleSelect = this.handleSelect.bind(this);
|
||||
}
|
||||
|
||||
handleSelect () {
|
||||
const {
|
||||
data: {
|
||||
meta: {
|
||||
dimensionCount
|
||||
}
|
||||
},
|
||||
general: {
|
||||
allowFilteringByClick
|
||||
},
|
||||
measurement,
|
||||
component
|
||||
} = this.props;
|
||||
|
||||
const hasSecondDimension = dimensionCount > 1;
|
||||
if (!allowFilteringByClick) {
|
||||
return;
|
||||
}
|
||||
|
||||
component.backendApi.selectValues(0, [measurement.parents.dimension1.elementNumber], false);
|
||||
if (hasSecondDimension) {
|
||||
component.backendApi.selectValues(1, [measurement.parents.dimension2.elementNumber], false);
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const {
|
||||
cellWidth,
|
||||
measurement,
|
||||
styleBuilder,
|
||||
styling
|
||||
} = this.props;
|
||||
|
||||
let textAlignment = styling.options.textAlignment || 'Right';
|
||||
|
||||
let cellStyle = {
|
||||
fontFamily: styling.options.fontFamily,
|
||||
...styleBuilder.getStyle(),
|
||||
paddingLeft: '5px',
|
||||
textAlign: textAlignment,
|
||||
minWidth: cellWidth,
|
||||
maxWidth: cellWidth
|
||||
};
|
||||
|
||||
const isEmptyCell = measurement.displayValue === '';
|
||||
let formattedMeasurementValue;
|
||||
if (isEmptyCell || styleBuilder.hasComments()) {
|
||||
formattedMeasurementValue = '';
|
||||
cellStyle.cursor = 'default';
|
||||
} else {
|
||||
formattedMeasurementValue = formatMeasurementValue(measurement, styling);
|
||||
}
|
||||
|
||||
const { conditionalColoring } = styling;
|
||||
if (conditionalColoring.enabled) {
|
||||
const isValidConditionalColoringValue = !styleBuilder.hasComments() && !isNaN(measurement.value);
|
||||
const isSpecifiedRow =
|
||||
conditionalColoring.rows.indexOf(measurement.parents.dimension1.header) !== -1;
|
||||
const isSpecifiedMeasure =
|
||||
conditionalColoring.measures.indexOf(measurement.parents.measurement.index) !== -1;
|
||||
const shouldHaveConditionalColoring = (conditionalColoring.colorAllRows || isSpecifiedRow)
|
||||
&& (conditionalColoring.colorAllMeasures || isSpecifiedMeasure);
|
||||
if (isValidConditionalColoringValue && shouldHaveConditionalColoring) {
|
||||
const { color, textColor } = getConditionalColor(measurement, conditionalColoring);
|
||||
cellStyle.backgroundColor = color.color;
|
||||
cellStyle.color = textColor.color;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<td
|
||||
className="grid-cells"
|
||||
onClick={isEmptyCell ? null : this.handleSelect}
|
||||
style={cellStyle}
|
||||
>
|
||||
<Tooltip
|
||||
styling={styling}
|
||||
tooltipText={formattedMeasurementValue}
|
||||
>
|
||||
{formattedMeasurementValue}
|
||||
</Tooltip>
|
||||
</td>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
DataCell.propTypes = {
|
||||
cellWidth: PropTypes.string.isRequired,
|
||||
data: PropTypes.shape({
|
||||
headers: PropTypes.shape({
|
||||
measurements: PropTypes.array.isRequired
|
||||
}).isRequired,
|
||||
meta: PropTypes.shape({
|
||||
dimensionCount: PropTypes.number.isRequired
|
||||
}).isRequired
|
||||
}).isRequired,
|
||||
general: PropTypes.shape({}).isRequired,
|
||||
measurement: PropTypes.shape({
|
||||
format: PropTypes.string,
|
||||
name: PropTypes.string,
|
||||
value: PropTypes.any
|
||||
}).isRequired,
|
||||
component: PropTypes.shape({
|
||||
backendApi: PropTypes.shape({
|
||||
selectValues: function (props, propName) {
|
||||
if (props.isSnapshot || typeof props[propName] === 'function') {
|
||||
return null;
|
||||
}
|
||||
return new Error('Missing implementation of qlik.backendApi.selectValues.');
|
||||
}
|
||||
}).isRequired
|
||||
}).isRequired,
|
||||
styleBuilder: PropTypes.shape({
|
||||
hasComments: PropTypes.func.isRequired
|
||||
}).isRequired,
|
||||
styling: PropTypes.shape({
|
||||
symbolForNulls: PropTypes.any.isRequired
|
||||
}).isRequired
|
||||
};
|
||||
|
||||
export default DataCell;
|
||||
|
||||
function formatMeasurementValue (measurement, styling) {
|
||||
if (isNaN(measurement.value)) {
|
||||
return styling.symbolForNulls;
|
||||
}
|
||||
|
||||
return measurement.displayValue;
|
||||
}
|
||||
|
||||
function getConditionalColor (measurement, conditionalColoring) {
|
||||
if (measurement.value < conditionalColoring.threshold.poor) {
|
||||
return conditionalColoring.colors.poor;
|
||||
}
|
||||
if (measurement.value < conditionalColoring.threshold.fair) {
|
||||
return conditionalColoring.colors.fair;
|
||||
}
|
||||
return conditionalColoring.colors.good;
|
||||
}
|
||||
28
src/data-table/header-padding.jsx
Normal file
28
src/data-table/header-padding.jsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const HeaderPadding = ({ styleBuilder, styling }) => {
|
||||
if (styling.usePadding && !styleBuilder.hasCustomFileStyle()) {
|
||||
const paddingStyle = {
|
||||
fontFamily: styling.options.fontFamily,
|
||||
marginLeft: '15px'
|
||||
};
|
||||
return (
|
||||
<span style={paddingStyle} />
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
HeaderPadding.propTypes = {
|
||||
styleBuilder: PropTypes.shape({
|
||||
hasCustomFileStyle: PropTypes.func.isRequired
|
||||
}).isRequired,
|
||||
styling: PropTypes.shape({
|
||||
options: PropTypes.shape({
|
||||
fontFamily: PropTypes.string.isRequired
|
||||
}).isRequired
|
||||
}).isRequired
|
||||
};
|
||||
|
||||
export default HeaderPadding;
|
||||
129
src/data-table/index.jsx
Normal file
129
src/data-table/index.jsx
Normal file
@@ -0,0 +1,129 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import StyleBuilder from '../style-builder';
|
||||
import DataCell from './data-cell.jsx';
|
||||
import RowHeader from './row-header.jsx';
|
||||
import { injectSeparators } from '../utilities';
|
||||
|
||||
class DataTable extends React.PureComponent {
|
||||
render () {
|
||||
const {
|
||||
cellWidth,
|
||||
columnSeparatorWidth,
|
||||
component,
|
||||
data,
|
||||
general,
|
||||
renderData,
|
||||
styling
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
headers: {
|
||||
dimension1,
|
||||
measurements
|
||||
},
|
||||
matrix
|
||||
} = data;
|
||||
|
||||
const separatorStyle = {
|
||||
minWidth: columnSeparatorWidth,
|
||||
maxWidth: columnSeparatorWidth
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="row-wrapper">
|
||||
<table>
|
||||
<tbody>
|
||||
{dimension1.map((dimensionEntry, dimensionIndex) => {
|
||||
const rowHeaderText = dimensionEntry.displayValue || '';
|
||||
if (rowHeaderText === '-') {
|
||||
return null;
|
||||
}
|
||||
const styleBuilder = new StyleBuilder(styling);
|
||||
if (styling.hasCustomFileStyle) {
|
||||
styleBuilder.parseCustomFileStyle(rowHeaderText);
|
||||
} else {
|
||||
styleBuilder.applyStandardAttributes(dimensionIndex);
|
||||
styleBuilder.applyCustomStyle({
|
||||
fontSize: `${14 + styling.options.fontSizeAdjustment}px`
|
||||
});
|
||||
}
|
||||
const rowStyle = {
|
||||
fontFamily: styling.options.fontFamily,
|
||||
width: '230px',
|
||||
...styleBuilder.getStyle()
|
||||
};
|
||||
|
||||
return (
|
||||
<tr key={dimensionEntry.displayValue}>
|
||||
{!renderData ?
|
||||
<RowHeader
|
||||
component={component}
|
||||
entry={dimensionEntry}
|
||||
rowStyle={rowStyle}
|
||||
styleBuilder={styleBuilder}
|
||||
styling={styling}
|
||||
/> : null
|
||||
}
|
||||
{renderData && injectSeparators(
|
||||
matrix[dimensionIndex],
|
||||
columnSeparatorWidth,
|
||||
{ atEvery: measurements.length }
|
||||
).map((measurementData, index) => {
|
||||
if (measurementData.isSeparator) {
|
||||
return (
|
||||
<td
|
||||
className="empty"
|
||||
key={`${dimensionEntry.displayValue}-${index}-separator`}
|
||||
style={separatorStyle}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const { dimension1: dimension1Info, dimension2, measurement } = measurementData.parents;
|
||||
const id = `${dimension1Info.elementNumber}-${dimension2 && dimension2.elementNumber}-${measurement.header}-${measurement.index}`;
|
||||
return (
|
||||
<DataCell
|
||||
cellWidth={cellWidth}
|
||||
component={component}
|
||||
data={data}
|
||||
general={general}
|
||||
key={`${dimensionEntry.displayValue}-${id}`}
|
||||
measurement={measurementData}
|
||||
styleBuilder={styleBuilder}
|
||||
styling={styling}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
DataTable.defaultProps = {
|
||||
renderData: true
|
||||
};
|
||||
|
||||
DataTable.propTypes = {
|
||||
cellWidth: PropTypes.string.isRequired,
|
||||
columnSeparatorWidth: PropTypes.string.isRequired,
|
||||
data: PropTypes.shape({
|
||||
headers: PropTypes.shape({
|
||||
dimension1: PropTypes.array.isRequired
|
||||
}).isRequired,
|
||||
matrix: PropTypes.arrayOf(PropTypes.array.isRequired).isRequired
|
||||
}).isRequired,
|
||||
general: PropTypes.shape({}).isRequired,
|
||||
component: PropTypes.shape({}).isRequired,
|
||||
renderData: PropTypes.bool,
|
||||
styling: PropTypes.shape({
|
||||
hasCustomFileStyle: PropTypes.bool.isRequired
|
||||
}).isRequired
|
||||
};
|
||||
|
||||
export default DataTable;
|
||||
64
src/data-table/row-header.jsx
Normal file
64
src/data-table/row-header.jsx
Normal file
@@ -0,0 +1,64 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import HeaderPadding from './header-padding.jsx';
|
||||
import Tooltip from '../tooltip/index.jsx';
|
||||
|
||||
class RowHeader extends React.PureComponent {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
|
||||
this.handleSelect = this.handleSelect.bind(this);
|
||||
}
|
||||
|
||||
handleSelect () {
|
||||
const { component, entry } = this.props;
|
||||
component.backendApi.selectValues(0, [entry.elementNumber], false);
|
||||
}
|
||||
|
||||
render () {
|
||||
const { entry, rowStyle, styleBuilder, styling, component } = this.props;
|
||||
const inEditState = component.inEditState();
|
||||
|
||||
return (
|
||||
<td
|
||||
className="fdim-cells"
|
||||
onClick={this.handleSelect}
|
||||
style={rowStyle}
|
||||
>
|
||||
<Tooltip
|
||||
isTooltipActive={!inEditState}
|
||||
styling={styling}
|
||||
tooltipText={entry.displayValue}
|
||||
>
|
||||
<HeaderPadding
|
||||
styleBuilder={styleBuilder}
|
||||
styling={styling}
|
||||
/>
|
||||
{entry.displayValue}
|
||||
</Tooltip>
|
||||
</td>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
RowHeader.propTypes = {
|
||||
entry: PropTypes.shape({
|
||||
displayValue: PropTypes.string.isRequired,
|
||||
elementNumber: PropTypes.number.isRequired
|
||||
}).isRequired,
|
||||
component: PropTypes.shape({
|
||||
backendApi: PropTypes.shape({
|
||||
selectValues: function (props, propName) {
|
||||
if (props.isSnapshot || typeof props[propName] === 'function') {
|
||||
return null;
|
||||
}
|
||||
return new Error('Missing implementation of qlik.backendApi.selectValues.');
|
||||
}
|
||||
}).isRequired
|
||||
}).isRequired,
|
||||
rowStyle: PropTypes.shape({}).isRequired,
|
||||
styleBuilder: PropTypes.shape({}).isRequired,
|
||||
styling: PropTypes.shape({}).isRequired
|
||||
};
|
||||
|
||||
export default RowHeader;
|
||||
68
src/dataset.js
Normal file
68
src/dataset.js
Normal file
@@ -0,0 +1,68 @@
|
||||
import qlik from 'qlik';
|
||||
|
||||
function createCube (definition, app) {
|
||||
return new Promise(resolve => {
|
||||
app.createCube(definition, resolve);
|
||||
});
|
||||
}
|
||||
|
||||
async function buildDataCube (originCubeDefinition, originCube, app) {
|
||||
const cubeDefinition = {
|
||||
...originCubeDefinition,
|
||||
qInitialDataFetch: [
|
||||
{
|
||||
qHeight: originCube.qSize.qcy,
|
||||
qWidth: originCube.qSize.qcx
|
||||
}
|
||||
],
|
||||
qDimensions: [originCubeDefinition.qDimensions[0]],
|
||||
qMeasures: originCubeDefinition.qMeasures
|
||||
};
|
||||
if (originCube.qDimensionInfo.length === 2) {
|
||||
cubeDefinition.qDimensions.push(originCubeDefinition.qDimensions[1]);
|
||||
}
|
||||
const cube = await createCube(cubeDefinition, app);
|
||||
const cubeMatrix = cube.qHyperCube.qDataPages[0].qMatrix;
|
||||
app.destroySessionObject(cube.qInfo.qId);
|
||||
return cubeMatrix;
|
||||
}
|
||||
|
||||
export async function initializeDataCube (component, layout) {
|
||||
if (component.backendApi.isSnapshot) {
|
||||
return layout.snapshotData.dataCube;
|
||||
}
|
||||
|
||||
const app = qlik.currApp(component);
|
||||
const properties = (await component.backendApi.getProperties());
|
||||
|
||||
// If this is a master object, fetch the hyperCubeDef of the original object
|
||||
let hyperCubeDef = properties.qExtendsId
|
||||
? (await app.getObjectProperties(properties.qExtendsId)).properties.qHyperCubeDef
|
||||
: properties.qHyperCubeDef;
|
||||
hyperCubeDef = JSON.parse(JSON.stringify(hyperCubeDef));
|
||||
hyperCubeDef.qStateName = layout.qStateName;
|
||||
|
||||
return buildDataCube(hyperCubeDef, layout.qHyperCube, app);
|
||||
}
|
||||
|
||||
export function initializeDesignList (component, layout) {
|
||||
if (component.backendApi.isSnapshot) {
|
||||
return layout.snapshotData.designList;
|
||||
}
|
||||
|
||||
if (!layout.stylingfield) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Promise(resolve => {
|
||||
const app = qlik.currApp(component);
|
||||
const stylingField = app.field(layout.stylingfield);
|
||||
const listener = function () {
|
||||
const data = stylingField.rows.map(row => row.qText);
|
||||
stylingField.OnData.unbind(listener);
|
||||
resolve(data);
|
||||
};
|
||||
stylingField.OnData.bind(listener);
|
||||
stylingField.getData();
|
||||
});
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
const colorLibrary = {
|
||||
type: 'items',
|
||||
label: 'Primary Colors Library',
|
||||
items: {
|
||||
ColLibClean: {
|
||||
ref: 'collibclean',
|
||||
translation: 'Clean',
|
||||
type: 'string',
|
||||
defaultValue: '#ffffff'
|
||||
},
|
||||
ColLibSoft: {
|
||||
ref: 'collibsoft',
|
||||
translation: 'Soft',
|
||||
type: 'string',
|
||||
defaultValue: '#efefef'
|
||||
},
|
||||
ColLibDark: {
|
||||
ref: 'collibdark',
|
||||
translation: 'Dark',
|
||||
type: 'string',
|
||||
defaultValue: '#c4c4c4'
|
||||
},
|
||||
ColLibNight: {
|
||||
ref: 'collibnight',
|
||||
translation: 'Night',
|
||||
type: 'string',
|
||||
defaultValue: '#808080'
|
||||
},
|
||||
ColLibRed: {
|
||||
ref: 'collibred',
|
||||
translation: 'Red',
|
||||
type: 'string',
|
||||
defaultValue: '#d58b94'
|
||||
},
|
||||
ColLibOrange: {
|
||||
ref: 'colliborange',
|
||||
translation: 'Orange',
|
||||
type: 'string',
|
||||
defaultValue: '#fd6600'
|
||||
},
|
||||
ColLibViolete: {
|
||||
ref: 'collibviolete',
|
||||
translation: 'Violete',
|
||||
type: 'string',
|
||||
defaultValue: '#ccc0ff'
|
||||
},
|
||||
ColLibBlue: {
|
||||
ref: 'collibblue',
|
||||
translation: 'Blue',
|
||||
type: 'string',
|
||||
defaultValue: '#4575b4'
|
||||
},
|
||||
ColLibGreen: {
|
||||
ref: 'collibgreen',
|
||||
translation: 'Green',
|
||||
type: 'string',
|
||||
defaultValue: '#7bb51c'
|
||||
},
|
||||
ColLibCustom: {
|
||||
ref: 'collibcustom',
|
||||
label: 'Custom',
|
||||
type: 'string',
|
||||
defaultValue: '#ffcccc'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default colorLibrary;
|
||||
@@ -1,96 +0,0 @@
|
||||
const conceptSemaphores = {
|
||||
items: {
|
||||
AllConcepts: {
|
||||
component: 'switch',
|
||||
defaultValue: true,
|
||||
label: 'All concepts affected',
|
||||
options: [
|
||||
{
|
||||
label: 'On',
|
||||
value: true
|
||||
},
|
||||
{
|
||||
label: 'Off',
|
||||
value: false
|
||||
}
|
||||
],
|
||||
ref: 'allsemaphores',
|
||||
type: 'boolean'
|
||||
},
|
||||
ConceptsAffected1: {
|
||||
defaultValue: '',
|
||||
ref: 'conceptsemaphore1',
|
||||
show: data => !data.allsemaphores,
|
||||
translation: 'Concept 1',
|
||||
type: 'string'
|
||||
},
|
||||
ConceptsAffected2: {
|
||||
defaultValue: '',
|
||||
ref: 'conceptsemaphore2',
|
||||
show: data => !data.allsemaphores,
|
||||
translation: 'Concept 2',
|
||||
type: 'string'
|
||||
},
|
||||
ConceptsAffected3: {
|
||||
defaultValue: '',
|
||||
ref: 'conceptsemaphore3',
|
||||
show: data => !data.allsemaphores,
|
||||
translation: 'Concept 3',
|
||||
type: 'string'
|
||||
},
|
||||
ConceptsAffected4: {
|
||||
defaultValue: '',
|
||||
ref: 'conceptsemaphore4',
|
||||
show: data => !data.allsemaphores,
|
||||
translation: 'Concept 4',
|
||||
type: 'string'
|
||||
},
|
||||
ConceptsAffected5: {
|
||||
defaultValue: '',
|
||||
ref: 'conceptsemaphore5',
|
||||
show: data => !data.allsemaphores,
|
||||
translation: 'Concept 5',
|
||||
type: 'string'
|
||||
},
|
||||
ConceptsAffected6: {
|
||||
defaultValue: '',
|
||||
ref: 'conceptsemaphore6',
|
||||
show: data => !data.allsemaphores,
|
||||
translation: 'Concept 6',
|
||||
type: 'string'
|
||||
},
|
||||
ConceptsAffected7: {
|
||||
defaultValue: '',
|
||||
ref: 'conceptsemaphore7',
|
||||
show: data => !data.allsemaphores,
|
||||
translation: 'Concept 7',
|
||||
type: 'string'
|
||||
},
|
||||
ConceptsAffected8: {
|
||||
defaultValue: '',
|
||||
ref: 'conceptsemaphore8',
|
||||
show: data => !data.allsemaphores,
|
||||
translation: 'Concept 8',
|
||||
type: 'string'
|
||||
},
|
||||
ConceptsAffected9: {
|
||||
defaultValue: '',
|
||||
ref: 'conceptsemaphore9',
|
||||
show: data => !data.allsemaphores,
|
||||
translation: 'Concept 9',
|
||||
type: 'string'
|
||||
},
|
||||
// eslint-disable-next-line sort-keys
|
||||
ConceptsAffected10: {
|
||||
defaultValue: '',
|
||||
ref: 'conceptsemaphore10',
|
||||
show: data => !data.allsemaphores,
|
||||
translation: 'Concept 10',
|
||||
type: 'string'
|
||||
}
|
||||
},
|
||||
label: 'Concept Semaphores',
|
||||
type: 'items'
|
||||
};
|
||||
|
||||
export default conceptSemaphores;
|
||||
199
src/definition/conditional-coloring.js
Normal file
199
src/definition/conditional-coloring.js
Normal file
@@ -0,0 +1,199 @@
|
||||
const conditionalColoring = {
|
||||
type: 'items',
|
||||
label: 'Color by performance',
|
||||
items: {
|
||||
Enabled: {
|
||||
ref: 'conditionalcoloring.enabled',
|
||||
type: 'boolean',
|
||||
label: 'Enabled',
|
||||
component: 'switch',
|
||||
defaultValue: false,
|
||||
options: [
|
||||
{
|
||||
value: true,
|
||||
label: 'On'
|
||||
},
|
||||
{
|
||||
value: false,
|
||||
label: 'Off'
|
||||
}
|
||||
]
|
||||
},
|
||||
ColorAllRows: {
|
||||
ref: 'conditionalcoloring.colorall',
|
||||
type: 'boolean',
|
||||
label: 'Color all rows',
|
||||
component: 'switch',
|
||||
defaultValue: true,
|
||||
options: [
|
||||
{
|
||||
value: true,
|
||||
label: 'All rows'
|
||||
},
|
||||
{
|
||||
value: false,
|
||||
label: 'Specified rows'
|
||||
}
|
||||
],
|
||||
show (data) {
|
||||
return data.conditionalcoloring.enabled;
|
||||
}
|
||||
},
|
||||
Rows: {
|
||||
type: 'array',
|
||||
ref: 'conditionalcoloring.rows',
|
||||
label: 'Rows to color',
|
||||
itemTitleRef: function (data) {
|
||||
return data.rowname;
|
||||
},
|
||||
allowAdd: true,
|
||||
allowRemove: true,
|
||||
addTranslation: 'Add row to color',
|
||||
items: {
|
||||
Row: {
|
||||
ref: 'rowname',
|
||||
label: 'Name of row',
|
||||
type: 'string',
|
||||
defaultValue: ''
|
||||
}
|
||||
},
|
||||
show (data) {
|
||||
return data.conditionalcoloring.enabled && !data.conditionalcoloring.colorall;
|
||||
}
|
||||
},
|
||||
ColorAllMeasures: {
|
||||
ref: 'conditionalcoloring.colorallmeasures',
|
||||
type: 'boolean',
|
||||
label: 'Color all measures',
|
||||
component: 'switch',
|
||||
defaultValue: true,
|
||||
options: [
|
||||
{
|
||||
value: true,
|
||||
label: 'All measures'
|
||||
},
|
||||
{
|
||||
value: false,
|
||||
label: 'Specified measures'
|
||||
}
|
||||
],
|
||||
show (data) {
|
||||
return data.conditionalcoloring.enabled;
|
||||
}
|
||||
},
|
||||
Measures: {
|
||||
ref: 'conditionalcoloring.measures',
|
||||
translation: 'Measures by index (ex: 0,3)',
|
||||
type: 'string',
|
||||
defaultValue: '',
|
||||
show (data) {
|
||||
return data.conditionalcoloring.enabled
|
||||
&& data.conditionalcoloring.colorallmeasures === false;
|
||||
}
|
||||
},
|
||||
ThresholdPoor: {
|
||||
ref: 'conditionalcoloring.threshold_poor',
|
||||
translation: 'Poor range limit',
|
||||
type: 'number',
|
||||
defaultValue: -0.1,
|
||||
show (data) {
|
||||
return data.conditionalcoloring.enabled;
|
||||
}
|
||||
},
|
||||
ColorPoor: {
|
||||
ref: 'conditionalcoloring.color_poor',
|
||||
label: 'Poor background color',
|
||||
type: 'object',
|
||||
component: 'color-picker',
|
||||
dualOutput: true,
|
||||
defaultValue: {
|
||||
index: 10,
|
||||
color: '#f93f17'
|
||||
},
|
||||
show (data) {
|
||||
return data.conditionalcoloring.enabled;
|
||||
}
|
||||
},
|
||||
TextColorPoor: {
|
||||
ref: 'conditionalcoloring.textcolor_poor',
|
||||
label: 'Poor text color',
|
||||
type: 'object',
|
||||
component: 'color-picker',
|
||||
dualOutput: true,
|
||||
defaultValue: {
|
||||
index: 1,
|
||||
color: '#ffffff'
|
||||
},
|
||||
show (data) {
|
||||
return data.conditionalcoloring.enabled;
|
||||
}
|
||||
},
|
||||
ThresholdFair: {
|
||||
ref: 'conditionalcoloring.threshold_fair',
|
||||
translation: 'Fair range limit',
|
||||
type: 'number',
|
||||
defaultValue: 0,
|
||||
show (data) {
|
||||
return data.conditionalcoloring.enabled;
|
||||
}
|
||||
},
|
||||
ColorFair: {
|
||||
ref: 'conditionalcoloring.color_fair',
|
||||
label: 'Fair background color',
|
||||
type: 'object',
|
||||
component: 'color-picker',
|
||||
dualOutput: true,
|
||||
defaultValue: {
|
||||
index: 8,
|
||||
color: '#ffcf02'
|
||||
},
|
||||
show (data) {
|
||||
return data.conditionalcoloring.enabled;
|
||||
}
|
||||
},
|
||||
TextColorFair: {
|
||||
ref: 'conditionalcoloring.textcolor_fair',
|
||||
label: 'Fair text color',
|
||||
type: 'object',
|
||||
component: 'color-picker',
|
||||
dualOutput: true,
|
||||
defaultValue: {
|
||||
index: 15,
|
||||
color: '#000000'
|
||||
},
|
||||
show (data) {
|
||||
return data.conditionalcoloring.enabled;
|
||||
}
|
||||
},
|
||||
ColorGood: {
|
||||
ref: 'conditionalcoloring.color_good',
|
||||
label: 'Good background color',
|
||||
type: 'object',
|
||||
component: 'color-picker',
|
||||
dualOutput: true,
|
||||
defaultValue: {
|
||||
index: 3,
|
||||
color: '#276e27'
|
||||
},
|
||||
show (data) {
|
||||
return data.conditionalcoloring.enabled;
|
||||
}
|
||||
},
|
||||
TextColorGood: {
|
||||
ref: 'conditionalcoloring.textcolor_good',
|
||||
label: 'Good text color',
|
||||
type: 'object',
|
||||
component: 'color-picker',
|
||||
dualOutput: true,
|
||||
defaultValue: {
|
||||
index: 1,
|
||||
color: '#ffffff'
|
||||
},
|
||||
show (data) {
|
||||
return data.conditionalcoloring.enabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default conditionalColoring;
|
||||
@@ -1,239 +0,0 @@
|
||||
const formatted = {
|
||||
type: 'items',
|
||||
label: 'Table Format',
|
||||
items: {
|
||||
IndentBool: {
|
||||
ref: 'indentbool',
|
||||
type: 'boolean',
|
||||
label: 'Indent',
|
||||
defaultValue: true
|
||||
},
|
||||
SeparatorColumns: {
|
||||
ref: 'separatorcols',
|
||||
type: 'boolean',
|
||||
label: 'Separator Columns',
|
||||
defaultValue: false
|
||||
},
|
||||
CustomFileBool: {
|
||||
ref: 'customfilebool',
|
||||
type: 'boolean',
|
||||
label: 'Include External File',
|
||||
defaultValue: false
|
||||
},
|
||||
CustomFile: {
|
||||
ref: 'customfile',
|
||||
label: 'Name of CSV file (; separated)',
|
||||
type: 'string',
|
||||
defaultValue: '',
|
||||
show (data) {
|
||||
return data.customfilebool;
|
||||
}
|
||||
},
|
||||
colors: {
|
||||
ref: 'ColorSchema',
|
||||
type: 'string',
|
||||
component: 'dropdown',
|
||||
label: 'BackGround Style',
|
||||
options: [
|
||||
{
|
||||
value: 'Clean',
|
||||
label: 'Clean'
|
||||
},
|
||||
{
|
||||
value: 'Soft',
|
||||
label: 'Soft'
|
||||
},
|
||||
{
|
||||
value: 'Dark',
|
||||
label: 'Dark'
|
||||
},
|
||||
{
|
||||
value: 'Night',
|
||||
label: 'Night'
|
||||
},
|
||||
{
|
||||
value: 'Blue',
|
||||
label: 'Blue'
|
||||
},
|
||||
{
|
||||
value: 'Orange',
|
||||
label: 'Orange'
|
||||
},
|
||||
{
|
||||
value: 'Red',
|
||||
label: 'Red'
|
||||
},
|
||||
{
|
||||
value: 'Green',
|
||||
label: 'Green'
|
||||
},
|
||||
{
|
||||
value: 'Violete',
|
||||
label: 'Violete'
|
||||
},
|
||||
{
|
||||
value: 'Custom',
|
||||
label: 'Custom'
|
||||
}
|
||||
],
|
||||
defaultValue: 'Clean',
|
||||
show (data) {
|
||||
return data.customfilebool == false;
|
||||
}
|
||||
},
|
||||
BodyTextColor: {
|
||||
ref: 'BodyTextColorSchema',
|
||||
type: 'string',
|
||||
component: 'dropdown',
|
||||
label: 'Text Body Color',
|
||||
options: [
|
||||
{
|
||||
value: 'Black',
|
||||
label: 'Black'
|
||||
},
|
||||
{
|
||||
value: 'DimGray',
|
||||
label: 'DimGray'
|
||||
},
|
||||
{
|
||||
value: 'ForestGreen',
|
||||
label: 'ForestGreen'
|
||||
},
|
||||
{
|
||||
value: 'Gainsboro',
|
||||
label: 'Gainsboro'
|
||||
},
|
||||
{
|
||||
value: 'Indigo',
|
||||
label: 'Indigo'
|
||||
},
|
||||
{
|
||||
value: 'Navy',
|
||||
label: 'Navy'
|
||||
},
|
||||
{
|
||||
value: 'Purple',
|
||||
label: 'Purple'
|
||||
},
|
||||
{
|
||||
value: 'WhiteSmoke',
|
||||
label: 'WhiteSmoke'
|
||||
},
|
||||
{
|
||||
value: 'White',
|
||||
label: 'White'
|
||||
},
|
||||
{
|
||||
value: 'YellowGreen',
|
||||
label: 'YellowGreen'
|
||||
}
|
||||
],
|
||||
defaultValue: 'Black',
|
||||
show (data) {
|
||||
return data.customfilebool == false;
|
||||
}
|
||||
},
|
||||
FontFamily: {
|
||||
ref: 'FontFamily',
|
||||
type: 'string',
|
||||
component: 'dropdown',
|
||||
label: 'FontFamily',
|
||||
options: [
|
||||
{
|
||||
value: 'Arial',
|
||||
label: 'Arial'
|
||||
},
|
||||
{
|
||||
value: 'Calibri',
|
||||
label: 'Calibri'
|
||||
},
|
||||
{
|
||||
value: 'Comic Sans MS',
|
||||
label: 'Comic Sans MS'
|
||||
},
|
||||
{
|
||||
value: 'MS Sans Serif',
|
||||
label: 'MS Sans Serif'
|
||||
},
|
||||
{
|
||||
value: 'Tahoma',
|
||||
label: 'Tahoma'
|
||||
},
|
||||
{
|
||||
value: 'Verdana',
|
||||
label: 'Verdana'
|
||||
}
|
||||
],
|
||||
defaultValue: 'Calibri'
|
||||
},
|
||||
DataFontSize: {
|
||||
ref: 'lettersize',
|
||||
translation: 'Font Size',
|
||||
type: 'number',
|
||||
component: 'buttongroup',
|
||||
options: [
|
||||
{
|
||||
value: 1,
|
||||
label: 'Small'
|
||||
},
|
||||
{
|
||||
value: 2,
|
||||
label: 'Medium'
|
||||
}
|
||||
],
|
||||
defaultValue: 2
|
||||
},
|
||||
ColumnWidthSlider: {
|
||||
type: 'number',
|
||||
component: 'slider',
|
||||
label: 'Column Width',
|
||||
ref: 'columnwidthslider',
|
||||
min: 1,
|
||||
max: 3,
|
||||
step: 1,
|
||||
defaultValue: 2
|
||||
},
|
||||
SymbolForNulls: {
|
||||
ref: 'symbolfornulls',
|
||||
label: 'Symbol for Nulls',
|
||||
type: 'string',
|
||||
defaultValue: ' '
|
||||
},
|
||||
AllowExportXLS: {
|
||||
ref: 'allowexportxls',
|
||||
type: 'boolean',
|
||||
component: 'switch',
|
||||
label: 'Allow export to Excel',
|
||||
options: [
|
||||
{
|
||||
value: true,
|
||||
label: 'On'
|
||||
},
|
||||
{
|
||||
value: false,
|
||||
label: 'Off'
|
||||
}
|
||||
],
|
||||
defaultValue: true
|
||||
},
|
||||
FilterOnCellClick: {
|
||||
ref: 'filteroncellclick',
|
||||
type: 'boolean',
|
||||
component: 'switch',
|
||||
label: 'Filter data when cell clicked',
|
||||
options: [
|
||||
{
|
||||
value: true,
|
||||
label: 'On'
|
||||
},
|
||||
{
|
||||
value: false,
|
||||
label: 'Off'
|
||||
}
|
||||
],
|
||||
defaultValue: true
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default formatted;
|
||||
@@ -1,10 +1,10 @@
|
||||
const header = {
|
||||
type: 'items',
|
||||
label: 'Header Format',
|
||||
label: 'Header format',
|
||||
items: {
|
||||
Align: {
|
||||
ref: 'HeaderAlign',
|
||||
translation: 'Header Alignment',
|
||||
translation: 'Header alignment',
|
||||
type: 'number',
|
||||
component: 'buttongroup',
|
||||
options: [
|
||||
@@ -24,106 +24,30 @@ const header = {
|
||||
defaultValue: 2
|
||||
},
|
||||
headercolors: {
|
||||
component: 'color-picker',
|
||||
defaultValue: {
|
||||
index: 6,
|
||||
color: '#4477aa'
|
||||
},
|
||||
label: 'Background color',
|
||||
ref: 'HeaderColorSchema',
|
||||
type: 'string',
|
||||
component: 'dropdown',
|
||||
label: 'BackGround Header Color',
|
||||
options: [
|
||||
{
|
||||
value: 'Clean',
|
||||
label: 'Clean'
|
||||
},
|
||||
{
|
||||
value: 'Soft',
|
||||
label: 'Soft'
|
||||
},
|
||||
{
|
||||
value: 'Dark',
|
||||
label: 'Dark'
|
||||
},
|
||||
{
|
||||
value: 'Night',
|
||||
label: 'Night'
|
||||
},
|
||||
{
|
||||
value: 'Blue',
|
||||
label: 'Blue'
|
||||
},
|
||||
{
|
||||
value: 'Orange',
|
||||
label: 'Orange'
|
||||
},
|
||||
{
|
||||
value: 'Red',
|
||||
label: 'Red'
|
||||
},
|
||||
{
|
||||
value: 'Green',
|
||||
label: 'Green'
|
||||
},
|
||||
{
|
||||
value: 'Violete',
|
||||
label: 'Violete'
|
||||
},
|
||||
{
|
||||
value: 'Custom',
|
||||
label: 'Custom'
|
||||
}
|
||||
],
|
||||
defaultValue: 'Night'
|
||||
type: 'object',
|
||||
dualOutput: true
|
||||
},
|
||||
HeaderTextColor: {
|
||||
ref: 'HeaderTextColorSchema',
|
||||
type: 'string',
|
||||
component: 'dropdown',
|
||||
label: 'Text Header Color',
|
||||
options: [
|
||||
{
|
||||
value: 'Black',
|
||||
label: 'Black'
|
||||
},
|
||||
{
|
||||
value: 'DimGray',
|
||||
label: 'DimGray'
|
||||
},
|
||||
{
|
||||
value: 'ForestGreen',
|
||||
label: 'ForestGreen'
|
||||
},
|
||||
{
|
||||
value: 'Gainsboro',
|
||||
label: 'Gainsboro'
|
||||
},
|
||||
{
|
||||
value: 'Indigo',
|
||||
label: 'Indigo'
|
||||
},
|
||||
{
|
||||
value: 'Navy',
|
||||
label: 'Navy'
|
||||
},
|
||||
{
|
||||
value: 'Purple',
|
||||
label: 'Purple'
|
||||
},
|
||||
{
|
||||
value: 'WhiteSmoke',
|
||||
label: 'WhiteSmoke'
|
||||
},
|
||||
{
|
||||
value: 'White',
|
||||
label: 'White'
|
||||
},
|
||||
{
|
||||
value: 'YellowGreen',
|
||||
label: 'YellowGreen'
|
||||
}
|
||||
],
|
||||
defaultValue: 'WhiteSmoke'
|
||||
label: 'Text color',
|
||||
component: 'color-picker',
|
||||
defaultValue: {
|
||||
index: 1,
|
||||
color: '#ffffff'
|
||||
},
|
||||
type: 'object',
|
||||
dualOutput: true
|
||||
},
|
||||
HeaderFontSize: {
|
||||
ref: 'lettersizeheader',
|
||||
translation: 'Font Size',
|
||||
translation: 'Font size',
|
||||
type: 'number',
|
||||
component: 'buttongroup',
|
||||
options: [
|
||||
@@ -136,7 +60,7 @@ const header = {
|
||||
label: 'Medium'
|
||||
}
|
||||
],
|
||||
defaultValue: 2
|
||||
defaultValue: 1
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,38 +1,53 @@
|
||||
import pagination from './pagination';
|
||||
import header from './header';
|
||||
import formatted from './formatted';
|
||||
import conceptSemaphores from './concept-semaphores';
|
||||
import metricSemaphores from './metric-semaphores';
|
||||
import colorLibrary from './color-library';
|
||||
import pijamaColorLibrary from './pijama-color-library';
|
||||
import tableFormat from './table-format';
|
||||
import conditionalColoring from './conditional-coloring';
|
||||
|
||||
const definition = {
|
||||
component: 'accordion',
|
||||
items: {
|
||||
dimensions: {
|
||||
max: 2,
|
||||
min: 1,
|
||||
uses: 'dimensions'
|
||||
},
|
||||
measures: {
|
||||
max: 9,
|
||||
min: 1,
|
||||
uses: 'measures'
|
||||
},
|
||||
settings: {
|
||||
data: {
|
||||
items: {
|
||||
ColorLibrary: colorLibrary,
|
||||
ConceptSemaphores: conceptSemaphores,
|
||||
Formatted: formatted,
|
||||
Header: header,
|
||||
MetricSemaphores: metricSemaphores,
|
||||
Pagination: pagination,
|
||||
PijamaColorLibrary: pijamaColorLibrary
|
||||
dimensions: {
|
||||
disabledRef: ''
|
||||
},
|
||||
measures: {
|
||||
disabledRef: ''
|
||||
}
|
||||
},
|
||||
uses: 'settings'
|
||||
uses: 'data'
|
||||
},
|
||||
sorting: {
|
||||
uses: 'sorting'
|
||||
},
|
||||
settings: {
|
||||
items: {
|
||||
Formatted: tableFormat,
|
||||
Header: header,
|
||||
ConditionalColoring: conditionalColoring,
|
||||
Pagination: pagination
|
||||
},
|
||||
uses: 'settings'
|
||||
},
|
||||
about: {
|
||||
component: 'items',
|
||||
label: 'About',
|
||||
items: {
|
||||
header: {
|
||||
label: 'P&L pivot',
|
||||
style: 'header',
|
||||
component: 'text'
|
||||
},
|
||||
paragraph1: {
|
||||
label: `P&L pivot is a Qlik Sense extension which allows you to display Profit & Loss
|
||||
reporting with color and font customizations.`,
|
||||
component: 'text'
|
||||
},
|
||||
paragraph2: {
|
||||
label: 'P&L pivot is based upon an extension created by Ivan Felipe Asensio.',
|
||||
component: 'text'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
type: 'items'
|
||||
|
||||
@@ -1,106 +0,0 @@
|
||||
const metricSemaphores = {
|
||||
type: 'items',
|
||||
label: 'Metric Semaphores',
|
||||
items: {
|
||||
AllMetrics: {
|
||||
ref: 'allmetrics',
|
||||
type: 'boolean',
|
||||
component: 'switch',
|
||||
label: 'All metrics affected',
|
||||
options: [
|
||||
{
|
||||
value: true,
|
||||
label: 'On'
|
||||
},
|
||||
{
|
||||
value: false,
|
||||
label: 'Off'
|
||||
}
|
||||
],
|
||||
defaultValue: false
|
||||
},
|
||||
MetricsAffected: {
|
||||
ref: 'metricssemaphore',
|
||||
translation: 'Metrics affected (1,2,4,...)',
|
||||
type: 'string',
|
||||
defaultValue: '0',
|
||||
show (data) {
|
||||
return data.allmetrics == false;
|
||||
}
|
||||
},
|
||||
MetricStatus1: {
|
||||
ref: 'metricsstatus1',
|
||||
translation: 'Critic is less than',
|
||||
type: 'number',
|
||||
defaultValue: -0.1
|
||||
},
|
||||
ColorStatus1: {
|
||||
ref: 'colorstatus1',
|
||||
label: 'Critic Color Fill',
|
||||
type: 'object',
|
||||
component: 'color-picker',
|
||||
defaultValue: {
|
||||
index: 7,
|
||||
color: '#f93f17'
|
||||
}
|
||||
},
|
||||
ColorStatus1Text: {
|
||||
ref: 'colorstatus1text',
|
||||
label: 'Critic Color Text',
|
||||
type: 'object',
|
||||
component: 'color-picker',
|
||||
defaultValue: {
|
||||
index: 10,
|
||||
color: '#ffffff'
|
||||
}
|
||||
},
|
||||
MetricStatus2: {
|
||||
ref: 'metricsstatus2',
|
||||
translation: 'Medium is less than',
|
||||
type: 'number',
|
||||
defaultValue: 0
|
||||
},
|
||||
ColorStatus2: {
|
||||
ref: 'colorstatus2',
|
||||
label: 'Medium Color Fill',
|
||||
type: 'object',
|
||||
component: 'color-picker',
|
||||
defaultValue: {
|
||||
index: 8,
|
||||
color: '#ffcf02'
|
||||
}
|
||||
},
|
||||
ColorStatus2Text: {
|
||||
ref: 'colorstatus2text',
|
||||
label: 'Medium Color Text',
|
||||
type: 'object',
|
||||
component: 'color-picker',
|
||||
defaultValue: {
|
||||
index: 11,
|
||||
color: '#000000'
|
||||
}
|
||||
},
|
||||
ColorStatus3: {
|
||||
ref: 'colorstatus3',
|
||||
label: 'Success Color Fill',
|
||||
type: 'object',
|
||||
component: 'color-picker',
|
||||
defaultValue: {
|
||||
index: 9,
|
||||
color: '#276e27'
|
||||
}
|
||||
},
|
||||
ColorStatus3Text: {
|
||||
ref: 'colorstatus3text',
|
||||
label: 'Success Color Text',
|
||||
type: 'object',
|
||||
component: 'color-picker',
|
||||
defaultValue: {
|
||||
index: 10,
|
||||
color: '#ffffff'
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default metricSemaphores;
|
||||
@@ -6,7 +6,7 @@ const pagination = {
|
||||
ref: 'maxloops',
|
||||
type: 'number',
|
||||
component: 'dropdown',
|
||||
label: 'Max Pagination Loops',
|
||||
label: 'Max pagination loops',
|
||||
options: [
|
||||
{
|
||||
value: 1,
|
||||
@@ -55,7 +55,7 @@ const pagination = {
|
||||
ref: 'errormessage',
|
||||
label: 'Default error message',
|
||||
type: 'string',
|
||||
defaultValue: 'Ups! It seems you asked for too many data. Please filter more to see the whole picture.'
|
||||
defaultValue: 'Unable to display all the data. Apply more filters to limit the amount of displayed data.'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
const pijamaColorLibrary = {
|
||||
type: 'items',
|
||||
label: 'Pijama Colors Library',
|
||||
items: {
|
||||
ColLibCleanP: {
|
||||
ref: 'collibcleanp',
|
||||
translation: 'Clean',
|
||||
type: 'string',
|
||||
defaultValue: '#ffffff'
|
||||
},
|
||||
ColLibSoftP: {
|
||||
ref: 'collibsoftp',
|
||||
translation: 'Soft',
|
||||
type: 'string',
|
||||
defaultValue: '#ffffff'
|
||||
},
|
||||
ColLibDarkP: {
|
||||
ref: 'collibdarkp',
|
||||
translation: 'Dark',
|
||||
type: 'string',
|
||||
defaultValue: '#efefef'
|
||||
},
|
||||
ColLibNightP: {
|
||||
ref: 'collibnightp',
|
||||
translation: 'Night',
|
||||
type: 'string',
|
||||
defaultValue: '#c4c4c4'
|
||||
},
|
||||
ColLibRedP: {
|
||||
ref: 'collibredp',
|
||||
translation: 'Red',
|
||||
type: 'string',
|
||||
defaultValue: '#ffcccc'
|
||||
},
|
||||
ColLibOrangeP: {
|
||||
ref: 'colliborangep',
|
||||
translation: 'Orange',
|
||||
type: 'string',
|
||||
defaultValue: '#ffcc66'
|
||||
},
|
||||
ColLibVioleteP: {
|
||||
ref: 'collibvioletep',
|
||||
translation: 'Violete',
|
||||
type: 'string',
|
||||
defaultValue: '#e6e6ff'
|
||||
},
|
||||
ColLibBlueP: {
|
||||
ref: 'collibbluep',
|
||||
translation: 'Blue',
|
||||
type: 'string',
|
||||
defaultValue: '#b3d9ff'
|
||||
},
|
||||
ColLibGreenP: {
|
||||
ref: 'collibgreenp',
|
||||
translation: 'Green',
|
||||
type: 'string',
|
||||
defaultValue: '#98fb98'
|
||||
},
|
||||
ColLibCustomP: {
|
||||
ref: 'collibcustomp',
|
||||
label: 'Custom',
|
||||
type: 'string',
|
||||
defaultValue: '#ffffff'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default pijamaColorLibrary;
|
||||
283
src/definition/table-format.js
Normal file
283
src/definition/table-format.js
Normal file
@@ -0,0 +1,283 @@
|
||||
const qlik = window.require('qlik');
|
||||
|
||||
// fixes case for when there are 3 dimensions, missies the case with 1 design dimension and 1 data dimension
|
||||
function hasDesignDimension (data) {
|
||||
return data.qHyperCubeDef.qDimensions.length > 2;
|
||||
}
|
||||
|
||||
function getFieldList () {
|
||||
return new Promise(function (resolve) {
|
||||
const app = qlik.currApp();
|
||||
app.getList('FieldList').then(function (model) {
|
||||
// Close the model to prevent any updates.
|
||||
app.destroySessionObject(model.layout.qInfo.qId);
|
||||
|
||||
// This is a bit iffy, might be smarter to reject and handle empty lists on the props instead.
|
||||
if (!model.layout.qFieldList.qItems) {
|
||||
return resolve([]);
|
||||
}
|
||||
// Resolve an array with master objects.
|
||||
return resolve(model.layout.qFieldList.qItems.map(function (item) {
|
||||
return {
|
||||
value: item.qName,
|
||||
label: item.qName
|
||||
};
|
||||
}));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const tableFormat = {
|
||||
type: 'items',
|
||||
label: 'Table format',
|
||||
items: {
|
||||
StylingField: {
|
||||
ref: 'stylingfield',
|
||||
disabledRef: '',
|
||||
type: 'string',
|
||||
component: 'dropdown',
|
||||
label: 'Style template field',
|
||||
options: function () {
|
||||
return getFieldList().then(function (items) {
|
||||
items.unshift(
|
||||
{
|
||||
value: '',
|
||||
label: 'None'
|
||||
});
|
||||
return items;
|
||||
});
|
||||
}
|
||||
},
|
||||
IndentBool: {
|
||||
ref: 'indentbool',
|
||||
type: 'boolean',
|
||||
label: 'Indent',
|
||||
defaultValue: false,
|
||||
show: data => !hasDesignDimension(data)
|
||||
},
|
||||
SeparatorColumns: {
|
||||
ref: 'separatorcols',
|
||||
type: 'boolean',
|
||||
label: 'Column separators',
|
||||
defaultValue: false
|
||||
},
|
||||
rowEvenBGColor: {
|
||||
component: 'color-picker',
|
||||
defaultValue: {
|
||||
color: '#fff',
|
||||
index: 1
|
||||
},
|
||||
label: 'Even row background color',
|
||||
ref: 'rowEvenBGColor',
|
||||
type: 'object',
|
||||
dualOutput: true,
|
||||
show: data => !hasDesignDimension(data)
|
||||
},
|
||||
rowOddBGColor: {
|
||||
component: 'color-picker',
|
||||
defaultValue: {
|
||||
color: '#b6d7ea',
|
||||
index: 4
|
||||
},
|
||||
label: 'Odd row background color',
|
||||
ref: 'rowOddBGColor',
|
||||
type: 'object',
|
||||
dualOutput: true,
|
||||
show: data => !hasDesignDimension(data)
|
||||
},
|
||||
BodyTextColor: {
|
||||
ref: 'BodyTextColorSchema',
|
||||
type: 'string',
|
||||
component: 'dropdown',
|
||||
label: 'Text body color',
|
||||
options: [
|
||||
{
|
||||
value: 'Black',
|
||||
label: 'Black'
|
||||
},
|
||||
{
|
||||
value: 'DimGray',
|
||||
label: 'DimGray'
|
||||
},
|
||||
{
|
||||
value: 'ForestGreen',
|
||||
label: 'ForestGreen'
|
||||
},
|
||||
{
|
||||
value: 'Gainsboro',
|
||||
label: 'Gainsboro'
|
||||
},
|
||||
{
|
||||
value: 'Indigo',
|
||||
label: 'Indigo'
|
||||
},
|
||||
{
|
||||
value: 'Navy',
|
||||
label: 'Navy'
|
||||
},
|
||||
{
|
||||
value: 'Purple',
|
||||
label: 'Purple'
|
||||
},
|
||||
{
|
||||
value: 'WhiteSmoke',
|
||||
label: 'WhiteSmoke'
|
||||
},
|
||||
{
|
||||
value: 'White',
|
||||
label: 'White'
|
||||
},
|
||||
{
|
||||
value: 'YellowGreen',
|
||||
label: 'YellowGreen'
|
||||
}
|
||||
],
|
||||
defaultValue: 'Black',
|
||||
show: data => !hasDesignDimension(data)
|
||||
},
|
||||
FontFamily: {
|
||||
ref: 'FontFamily',
|
||||
type: 'string',
|
||||
component: 'dropdown',
|
||||
label: 'Font family',
|
||||
options: [
|
||||
{
|
||||
value: 'QlikView Sans, -apple-system, sans-serif',
|
||||
label: 'QlikView Sans'
|
||||
},
|
||||
{
|
||||
value: 'Arial, -apple-system, sans-serif',
|
||||
label: 'Arial'
|
||||
},
|
||||
{
|
||||
value: 'Calibri, -apple-system, sans-serif',
|
||||
label: 'Calibri'
|
||||
},
|
||||
{
|
||||
value: 'Comic Sans MS, -apple-system, sans-serif',
|
||||
label: 'Comic Sans MS'
|
||||
},
|
||||
{
|
||||
value: 'MS Sans Serif, -apple-system, sans-serif',
|
||||
label: 'MS Sans Serif'
|
||||
},
|
||||
{
|
||||
value: 'Tahoma, -apple-system, sans-serif',
|
||||
label: 'Tahoma'
|
||||
},
|
||||
{
|
||||
value: 'Verdana, -apple-system, sans-serif',
|
||||
label: 'Verdana'
|
||||
}
|
||||
],
|
||||
defaultValue: 'QlikView Sans, -apple-system, sans-serif'
|
||||
},
|
||||
DataFontSize: {
|
||||
ref: 'lettersize',
|
||||
translation: 'Font size',
|
||||
type: 'number',
|
||||
component: 'buttongroup',
|
||||
options: [
|
||||
{
|
||||
value: 1,
|
||||
label: 'Small'
|
||||
},
|
||||
{
|
||||
value: 2,
|
||||
label: 'Medium'
|
||||
}
|
||||
],
|
||||
defaultValue: 1
|
||||
},
|
||||
textAlignment: {
|
||||
ref: 'cellTextAlignment',
|
||||
label: 'Cell text alignment',
|
||||
component: 'buttongroup',
|
||||
options: [
|
||||
{
|
||||
value: 'left',
|
||||
label: 'Left'
|
||||
},
|
||||
{
|
||||
value: 'center',
|
||||
label: 'Center'
|
||||
},
|
||||
{
|
||||
value: 'right',
|
||||
label: 'Right'
|
||||
}
|
||||
],
|
||||
defaultValue: 'right'
|
||||
},
|
||||
FitChartWidth: {
|
||||
ref: 'fitchartwidth',
|
||||
type: 'boolean',
|
||||
component: 'switch',
|
||||
label: 'Fill chart width',
|
||||
options: [
|
||||
{
|
||||
value: true,
|
||||
label: 'On'
|
||||
},
|
||||
{
|
||||
value: false,
|
||||
label: 'Off'
|
||||
}
|
||||
],
|
||||
defaultValue: false
|
||||
},
|
||||
ColumnWidthSlider: {
|
||||
type: 'number',
|
||||
component: 'slider',
|
||||
label: 'Column width',
|
||||
ref: 'columnwidthslider',
|
||||
min: 20,
|
||||
max: 250,
|
||||
step: 10,
|
||||
defaultValue: 50,
|
||||
show: data => !data.fitchartwidth
|
||||
},
|
||||
SymbolForNulls: {
|
||||
ref: 'symbolfornulls',
|
||||
label: 'Symbol for Nulls',
|
||||
type: 'string',
|
||||
defaultValue: ' '
|
||||
},
|
||||
AllowExportXLS: {
|
||||
ref: 'allowexportxls',
|
||||
type: 'boolean',
|
||||
component: 'switch',
|
||||
label: 'Allow export to Excel',
|
||||
options: [
|
||||
{
|
||||
value: true,
|
||||
label: 'On'
|
||||
},
|
||||
{
|
||||
value: false,
|
||||
label: 'Off'
|
||||
}
|
||||
],
|
||||
defaultValue: true
|
||||
},
|
||||
FilterOnCellClick: {
|
||||
ref: 'filteroncellclick',
|
||||
type: 'boolean',
|
||||
component: 'switch',
|
||||
label: 'Allow selection in cells',
|
||||
options: [
|
||||
{
|
||||
value: true,
|
||||
label: 'On'
|
||||
},
|
||||
{
|
||||
value: false,
|
||||
label: 'Off'
|
||||
}
|
||||
],
|
||||
defaultValue: true
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default tableFormat;
|
||||
@@ -1,222 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { ApplyPreMask } from './masking';
|
||||
import { addSeparators } from './utilities';
|
||||
|
||||
class ElseDimensionMeasures extends React.PureComponent {
|
||||
render () {
|
||||
const {
|
||||
vFontFamily,
|
||||
vSeparatorCols,
|
||||
measure_count,
|
||||
sufixCells,
|
||||
vSymbolForNulls,
|
||||
vLetterSize,
|
||||
vColorMetric1,
|
||||
vColorMetric1Text,
|
||||
vColorMetric2,
|
||||
vColorMetric2Text,
|
||||
vColorMetric3,
|
||||
vColorMetric3Text,
|
||||
vAllSemaphores,
|
||||
ConceptMatrixPivot,
|
||||
ConceptsAffectedMatrix,
|
||||
vAllMetrics,
|
||||
MetricsAffectedMatrix,
|
||||
vCritic,
|
||||
vMMedium,
|
||||
vNumMeasures,
|
||||
vNumMeasures2,
|
||||
MeasuresFormat,
|
||||
rowNumber,
|
||||
columnText,
|
||||
styleBuilder
|
||||
} = this.props;
|
||||
|
||||
// modified in here
|
||||
let columnNumber,
|
||||
vMaskNum,
|
||||
vColorSemaphore,
|
||||
vColorSemaphoreText,
|
||||
vDivide;
|
||||
|
||||
const measurementCells = [];
|
||||
|
||||
var nMeasure7 = 0;
|
||||
var nMeasure72 = -1;
|
||||
var nMeasure72Semaphore = 0;
|
||||
|
||||
for (var nMeasures22 = 1; nMeasures22 <= vNumMeasures2; nMeasures22++) {
|
||||
nMeasure7++;
|
||||
nMeasure72++;
|
||||
if (columnText.substring(0, 1) === '%') {
|
||||
columnNumber = ApplyPreMask('0,00%', ConceptMatrixPivot[rowNumber][nMeasures22]);
|
||||
var vSpecialF = '0,00%';
|
||||
} else {
|
||||
switch (MeasuresFormat[nMeasure72].substr(MeasuresFormat[nMeasure72].length - 1)) {
|
||||
case 'k':
|
||||
vDivide = 1000;
|
||||
break;
|
||||
|
||||
case 'K':
|
||||
vDivide = 1000;
|
||||
break;
|
||||
|
||||
case 'm':
|
||||
vDivide = 1000000;
|
||||
break;
|
||||
|
||||
case 'M':
|
||||
vDivide = 1000000;
|
||||
break;
|
||||
|
||||
default:
|
||||
vDivide = 1;
|
||||
break;
|
||||
}
|
||||
var vSpecialF = MeasuresFormat[nMeasure72].replace(/k|K|m|M/gi, '');
|
||||
if (!isNaN(ConceptMatrixPivot[rowNumber][nMeasures22])) {
|
||||
vMaskNum = ConceptMatrixPivot[rowNumber][nMeasures22];
|
||||
if (vSpecialF.substring(vSpecialF.length - 1) === '%') {
|
||||
vMaskNum = vMaskNum * 100;
|
||||
}
|
||||
|
||||
switch (vSpecialF) {
|
||||
case '#.##0':
|
||||
columnNumber = addSeparators((vMaskNum / vDivide), '.', ',', 0);
|
||||
break;
|
||||
case '#,##0':
|
||||
columnNumber = addSeparators((vMaskNum / vDivide), ',', '.', 0);
|
||||
break;
|
||||
default:
|
||||
columnNumber = ApplyPreMask(vSpecialF, (vMaskNum / vDivide));
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
columnNumber = vSymbolForNulls;
|
||||
}
|
||||
}
|
||||
|
||||
if (vSeparatorCols && nMeasure7 === (measure_count + 1)) {
|
||||
const seperatorStyle = {
|
||||
color: 'white',
|
||||
fontFamily: vFontFamily,
|
||||
fontSize: (12 + vLetterSize) + 'px'
|
||||
};
|
||||
const seperatorElement = (
|
||||
<th key={`${nMeasures22}-header`} className="empty" style={seperatorStyle}>
|
||||
*
|
||||
</th>
|
||||
);
|
||||
measurementCells.push(seperatorElement);
|
||||
nMeasure7 = 1;
|
||||
}
|
||||
if (nMeasure72 === (measure_count - 1)) {
|
||||
nMeasure72 = -1;
|
||||
nMeasure72Semaphore = measure_count;
|
||||
} else {
|
||||
nMeasure72Semaphore = nMeasure72 + 1;
|
||||
}
|
||||
|
||||
// apply the semaphores where needed
|
||||
if (styleBuilder.hasComments()) {
|
||||
columnNumber = '.';
|
||||
}
|
||||
|
||||
let cellElement;
|
||||
if ((vAllSemaphores || ConceptsAffectedMatrix.indexOf(columnText) >= 0) && (vAllMetrics || MetricsAffectedMatrix.indexOf(nMeasure72Semaphore) >= 0) && !isNaN(ConceptMatrixPivot[rowNumber][nMeasures22]) && !styleBuilder.hasComments()) {
|
||||
if (ConceptMatrixPivot[rowNumber][nMeasures22] < vCritic) {
|
||||
vColorSemaphore = vColorMetric1;
|
||||
vColorSemaphoreText = vColorMetric1Text;
|
||||
} else {
|
||||
if (ConceptMatrixPivot[rowNumber][nMeasures22] < vMMedium) {
|
||||
vColorSemaphore = vColorMetric2;
|
||||
vColorSemaphoreText = vColorMetric2Text;
|
||||
} else {
|
||||
vColorSemaphore = vColorMetric3;
|
||||
vColorSemaphoreText = vColorMetric3Text;
|
||||
}
|
||||
}
|
||||
|
||||
const cellStyle = {
|
||||
fontFamily: vFontFamily,
|
||||
fontSize: styleBuilder.getStyle().fontSize,
|
||||
color: vColorSemaphoreText,
|
||||
backgroundColor: vColorSemaphore,
|
||||
textAlign: 'right',
|
||||
paddingLeft: '4px'
|
||||
};
|
||||
if (vSpecialF.substring(vSpecialF.length - 1) === '%' && vNumMeasures > 1) {
|
||||
cellElement = (
|
||||
<td key={nMeasures22} className={'grid-cells-small' + sufixCells} style={cellStyle}>
|
||||
{columnNumber}
|
||||
</td>
|
||||
);
|
||||
} else {
|
||||
cellElement = (
|
||||
<td key={nMeasures22} className={'grid-cells' + sufixCells} style={cellStyle}>
|
||||
{columnNumber}
|
||||
</td>
|
||||
);
|
||||
}
|
||||
} else {
|
||||
const cellStyle = {
|
||||
fontFamily: vFontFamily,
|
||||
...styleBuilder.getStyle(),
|
||||
textAlign: 'right',
|
||||
paddingRight: '4px'
|
||||
};
|
||||
if (vSpecialF.substring(vSpecialF.length - 1) === '%' && vNumMeasures > 1) {
|
||||
cellElement = (
|
||||
<td key={nMeasures22} className={'grid-cells-small' + sufixCells} style={cellStyle}>
|
||||
{columnNumber}
|
||||
</td>
|
||||
);
|
||||
} else {
|
||||
cellElement = (
|
||||
<td key={nMeasures22} className={'grid-cells' + sufixCells} style={cellStyle}>
|
||||
{columnNumber}
|
||||
</td>
|
||||
);
|
||||
}
|
||||
}
|
||||
measurementCells.push(cellElement);
|
||||
}
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{measurementCells}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ElseDimensionMeasures.propTypes = {
|
||||
vFontFamily: PropTypes.any,
|
||||
vSeparatorCols: PropTypes.any,
|
||||
measure_count: PropTypes.any,
|
||||
sufixCells: PropTypes.any,
|
||||
vSymbolForNulls: PropTypes.any,
|
||||
vLetterSize: PropTypes.any,
|
||||
vColorMetric1: PropTypes.any,
|
||||
vColorMetric1Text: PropTypes.any,
|
||||
vColorMetric2: PropTypes.any,
|
||||
vColorMetric2Text: PropTypes.any,
|
||||
vColorMetric3: PropTypes.any,
|
||||
vColorMetric3Text: PropTypes.any,
|
||||
vAllSemaphores: PropTypes.any,
|
||||
ConceptMatrixPivot: PropTypes.any,
|
||||
ConceptsAffectedMatrix: PropTypes.any,
|
||||
vAllMetrics: PropTypes.any,
|
||||
MetricsAffectedMatrix: PropTypes.any,
|
||||
vCritic: PropTypes.any,
|
||||
vMMedium: PropTypes.any,
|
||||
vNumMeasures: PropTypes.any,
|
||||
vNumMeasures2: PropTypes.any,
|
||||
MeasuresFormat: PropTypes.any,
|
||||
rowNumber: PropTypes.any,
|
||||
columnText: PropTypes.any,
|
||||
styleBuilder: PropTypes.any
|
||||
};
|
||||
|
||||
export default ElseDimensionMeasures;
|
||||
@@ -1,73 +1,106 @@
|
||||
import $ from 'jquery';
|
||||
|
||||
const isIE = /* @cc_on!@*/false || Boolean(document.documentMode);
|
||||
const isChrome = Boolean(window.chrome) && Boolean(window.chrome.webstore);
|
||||
const isSafari = Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0;
|
||||
const isFirefox = typeof InstallTrigger !== 'undefined';
|
||||
|
||||
export function enableExcelExport (layout, f) {
|
||||
let myTitle = '';
|
||||
let mySubTitle = '';
|
||||
let myFootNote = '';
|
||||
if (layout.title.length > 0) {
|
||||
myTitle += '<p style="font-size:15pt"><b>';
|
||||
myTitle += layout.title;
|
||||
myTitle += '</b></p>';
|
||||
}
|
||||
if (layout.subtitle.length > 0) {
|
||||
mySubTitle += '<p style="font-size:11pt">';
|
||||
mySubTitle += layout.subtitle;
|
||||
mySubTitle += '</p>';
|
||||
}
|
||||
if (layout.footnote.length > 0) {
|
||||
myFootNote += '<p style="font-size:11pt"><i>Note:</i>';
|
||||
myFootNote += layout.footnote;
|
||||
myFootNote += '</p>';
|
||||
}
|
||||
|
||||
$('.icon-xls').on('click', () => {
|
||||
$('.header-wrapper th').children('.tooltip')
|
||||
.remove(); // remove some popup effects when exporting
|
||||
$('.header-wrapper th').children('.icon-xls')
|
||||
.remove(); // remove the xls icon when exporting
|
||||
if (isChrome || isSafari) {
|
||||
const $clonedDiv = $('.data-table').clone(true); // .kpi-table a secas exporta la 1ªcol
|
||||
let vEncodeHead = '<html><head><meta charset="UTF-8"></head>';
|
||||
vEncodeHead += myTitle + mySubTitle + myFootNote;
|
||||
const vEncode = encodeURIComponent($clonedDiv.html());
|
||||
let vDecode = `${vEncodeHead + vEncode}</html>`;
|
||||
|
||||
$clonedDiv.find('tr.header');
|
||||
vDecode = vDecode.split('%3E.%3C').join('%3E%3C');
|
||||
window.open(`data:application/vnd.ms-excel,${vDecode}`);
|
||||
$.preventDefault();
|
||||
}
|
||||
if (isIE) {
|
||||
let a = '<html><head><meta charset="UTF-8"></head>';
|
||||
a += myTitle + mySubTitle + myFootNote;
|
||||
a += f;
|
||||
a = a.split('>.<').join('><');
|
||||
a += '</html>';
|
||||
|
||||
const w = window.open();
|
||||
w.document.open();
|
||||
w.document.write(a);
|
||||
w.document.close();
|
||||
w.document.execCommand('SaveAs', true, 'Analysis.xls' || 'c:\TMP');
|
||||
w.close();
|
||||
}
|
||||
|
||||
if (isFirefox) {
|
||||
const $clonedDiv = $('.data-table').clone(true);// .kpi-table a secas exporta la 1ªcol
|
||||
let vEncodeHead = '<html><head><meta charset="UTF-8"></head>';
|
||||
vEncodeHead += myTitle + mySubTitle + myFootNote;
|
||||
const vEncode = encodeURIComponent($clonedDiv.html());
|
||||
let vDecode = `${vEncodeHead + vEncode}</html>`;
|
||||
|
||||
$clonedDiv.find('tr.header');
|
||||
vDecode = vDecode.split('>.<').join('><');
|
||||
window.open(`data:application/vnd.ms-excel,${vDecode}`);
|
||||
$.preventDefault();
|
||||
function cleanupNodes (node) {
|
||||
const removables = node.querySelectorAll('.tooltip,input');
|
||||
[].forEach.call(removables, removeable => {
|
||||
if (removeable.parentNode) {
|
||||
removeable.parentNode.removeChild(removeable);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function buildTableHTML (containerElement, title, subtitle, footnote) {
|
||||
const titleHTML = `<p style="font-size:15pt"><b>${title}</b></p>`;
|
||||
const subtitleHTML = `<p style="font-size:11pt">${subtitle}</p>`;
|
||||
const footnoteHTML = `<p style="font-size:11pt">${footnote}</p>`;
|
||||
const kpiTableClone = containerElement[0].querySelector('.kpi-table').cloneNode(true);
|
||||
const dataTableClone = containerElement[0].querySelector('.data-table').cloneNode(true);
|
||||
cleanupNodes(kpiTableClone);
|
||||
cleanupNodes(kpiTableClone);
|
||||
|
||||
const kpiTableBodies = kpiTableClone.querySelectorAll('tbody');
|
||||
const dataTableBodies = dataTableClone.querySelectorAll('tbody');
|
||||
const kpiHeader = kpiTableBodies[0].querySelector('tr');
|
||||
const dataTableHeaders = dataTableBodies[0].querySelectorAll('tr');
|
||||
const kpiRows = kpiTableBodies[1].querySelectorAll('tr');
|
||||
const dataRows = dataTableBodies[1].querySelectorAll('tr');
|
||||
let combinedRows = '';
|
||||
for (let i = 0; i < kpiRows.length; i++) {
|
||||
combinedRows += `<tr>${kpiRows[i].innerHTML}${dataRows[i].innerHTML}</tr>`;
|
||||
}
|
||||
|
||||
const tableHTML = `
|
||||
<html
|
||||
xmlns:o="urn:schemas-microsoft-com:office:office"
|
||||
xmlns:x="urn:schemas-microsoft-com:office:excel"
|
||||
xmlns="http://www.w3.org/TR/REC-html40"
|
||||
>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<!--[if gte mso 9]>
|
||||
<xml>
|
||||
<x:ExcelWorkbook>
|
||||
<x:ExcelWorksheets>
|
||||
<x:ExcelWorksheet>
|
||||
<x:Name>${title || 'Analyze'}</x:Name>
|
||||
<x:WorksheetOptions>
|
||||
<x:DisplayGridlines/>
|
||||
</x:WorksheetOptions>
|
||||
</x:ExcelWorksheet>
|
||||
</x:ExcelWorksheets>
|
||||
</x:ExcelWorkbook>
|
||||
</xml>
|
||||
<![endif]-->
|
||||
</head>
|
||||
<body>
|
||||
${titleHTML.length > 0 ? titleHTML : ''}
|
||||
${subtitleHTML.length > 0 ? subtitleHTML : ''}
|
||||
<div>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
${kpiHeader.innerHTML}
|
||||
${dataTableHeaders[0].innerHTML}
|
||||
</tr>
|
||||
${dataTableHeaders.length > 1 ? dataTableHeaders[1].outerHTML : ''}
|
||||
</tbody>
|
||||
</table>
|
||||
<table>
|
||||
<tbody>
|
||||
${combinedRows}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
${footnoteHTML.length > 0 ? footnoteHTML : ''}
|
||||
</body>
|
||||
</html>
|
||||
`.split('>.<')
|
||||
.join('><')
|
||||
.split('>*<')
|
||||
.join('><');
|
||||
|
||||
return tableHTML;
|
||||
}
|
||||
|
||||
function downloadXLS (html) {
|
||||
const filename = 'analysis.xls';
|
||||
const blobObject = new Blob([html], { type: 'application/vnd.ms-excel' });
|
||||
|
||||
// IE/Edge
|
||||
if (window.navigator.msSaveOrOpenBlob) {
|
||||
return window.navigator.msSaveOrOpenBlob(blobObject, filename);
|
||||
}
|
||||
|
||||
const link = window.document.createElement('a');
|
||||
link.href = URL.createObjectURL(blobObject);
|
||||
link.download = filename;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export function exportXLS (containerElement, title, subtitle, footnote) {
|
||||
// original was removing icon when starting export, disable and some spinner instead, shouldn't take enough time to warrant either..?
|
||||
const table = buildTableHTML(containerElement, title, subtitle, footnote);
|
||||
downloadXLS(table);
|
||||
}
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
// TODO: move interaction logic in here from excel-export.js
|
||||
class ExportButton extends React.PureComponent {
|
||||
render () {
|
||||
const { excelExport } = this.props;
|
||||
return excelExport === true && (
|
||||
<input
|
||||
className="icon-xls"
|
||||
src="/Extensions/qlik-smart-pivot/Excel.png"
|
||||
type="image"
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ExportButton.defaultProps = {
|
||||
excelExport: false
|
||||
};
|
||||
|
||||
ExportButton.propTypes = {
|
||||
excelExport: PropTypes.bool
|
||||
};
|
||||
|
||||
export default ExportButton;
|
||||
@@ -1,9 +0,0 @@
|
||||
/* https://randomhaiku.com */
|
||||
|
||||
describe('behind the money', () => {
|
||||
describe('Canada and Panda work.', () => {
|
||||
it('Tiger starts blowing.', () => {
|
||||
expect(true).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,360 +0,0 @@
|
||||
import React, { PureComponent, Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ExportButton from './export-button.jsx';
|
||||
|
||||
/* TODO: the different render methods are very similar, split it into a few components
|
||||
and try to get rid of some duplication */
|
||||
class HeaderWrapper extends PureComponent {
|
||||
getBaseCSS () {
|
||||
const {
|
||||
vHeaderColorText,
|
||||
vFontFamily,
|
||||
vHeaderColorSchema,
|
||||
vHeaderAlignText
|
||||
} = this.props;
|
||||
|
||||
const baseCSS = {
|
||||
backgroundColor: vHeaderColorSchema,
|
||||
color: vHeaderColorText,
|
||||
fontFamily: vFontFamily,
|
||||
textAlign: vHeaderAlignText
|
||||
};
|
||||
return baseCSS;
|
||||
}
|
||||
|
||||
renderSecondDimensionTitles () {
|
||||
const {
|
||||
vFontFamily,
|
||||
vLetterSizeHeader,
|
||||
vExportToExcel,
|
||||
sufixCells,
|
||||
vNumDims,
|
||||
measure_count,
|
||||
LabelsArray,
|
||||
SecondHeader,
|
||||
vSeparatorCols,
|
||||
nSecond,
|
||||
vLetterSize,
|
||||
ExtraLabelsArray
|
||||
} = this.props;
|
||||
|
||||
const baseCSS = this.getBaseCSS();
|
||||
|
||||
if (vNumDims === 2) {
|
||||
if (measure_count > 1) {
|
||||
const thStyle = {
|
||||
...baseCSS,
|
||||
cursor: 'default',
|
||||
fontSize: `${16 + vLetterSizeHeader} px`,
|
||||
height: '80px',
|
||||
verticalAlign: 'middle',
|
||||
width: '230px'
|
||||
};
|
||||
return (
|
||||
<Fragment key="second-dimension-titles">
|
||||
<th
|
||||
className="fdim-cells"
|
||||
padding-left="20px"
|
||||
rowSpan="2"
|
||||
style={thStyle}
|
||||
>
|
||||
<ExportButton excelExport={vExportToExcel} />
|
||||
{LabelsArray[0]}
|
||||
</th>
|
||||
{SecondHeader.map((header, index) => {
|
||||
const emptyStyle = {
|
||||
color: 'white',
|
||||
fontFamily: vFontFamily,
|
||||
fontSize: `${13 + vLetterSizeHeader} px`
|
||||
};
|
||||
const style = {
|
||||
...baseCSS,
|
||||
fontSize: `${14 + vLetterSizeHeader} px`,
|
||||
height: '45px',
|
||||
verticalAlign: 'middle'
|
||||
};
|
||||
return (
|
||||
<Fragment key={index}>
|
||||
{vSeparatorCols && index > 0 && (
|
||||
<th
|
||||
className="empty"
|
||||
style={emptyStyle}
|
||||
>
|
||||
*
|
||||
</th>
|
||||
)}
|
||||
<th
|
||||
className={`grid-cells2${sufixCells}`}
|
||||
colSpan={measure_count}
|
||||
style={style}
|
||||
>
|
||||
{header}
|
||||
</th>
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
const fDimCellsStyle = {
|
||||
...baseCSS,
|
||||
cursor: 'default',
|
||||
fontSize: `${16 + vLetterSizeHeader} px`,
|
||||
height: '70px',
|
||||
verticalAlign: 'middle',
|
||||
width: '230px'
|
||||
};
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<th
|
||||
className="fdim-cells"
|
||||
style={fDimCellsStyle}
|
||||
>
|
||||
<ExportButton excelExport={vExportToExcel} />
|
||||
{LabelsArray[0] + ExtraLabelsArray[0]}
|
||||
</th>
|
||||
{SecondHeader.map((entry, entryIndex) => {
|
||||
// TODO: seperator element is reused a bunch, only difference being font-size
|
||||
const hasSeperator = vSeparatorCols && nSecond > 0;
|
||||
const seperatorStyle = {
|
||||
color: 'white',
|
||||
fontFamily: vFontFamily,
|
||||
fontSize: `${12 + vLetterSize} px`
|
||||
};
|
||||
const seperatorElement = (
|
||||
<th
|
||||
className="empty"
|
||||
style={seperatorStyle}
|
||||
>
|
||||
*
|
||||
</th>
|
||||
);
|
||||
let sufixWrap = '';
|
||||
if ((entry.length > 11 && vLetterSizeHeader === 0) || (entry.length > 12 && vLetterSizeHeader === -2)) {
|
||||
sufixWrap = '70';
|
||||
} else {
|
||||
sufixWrap = 'Empty';
|
||||
}
|
||||
const gridCells2Style = {
|
||||
...baseCSS,
|
||||
fontSize: `${14 + vLetterSizeHeader} px`,
|
||||
height: '70px',
|
||||
verticalAlign: 'middle'
|
||||
};
|
||||
const wrapStyle = {
|
||||
fontFamily: vFontFamily
|
||||
};
|
||||
return (
|
||||
<Fragment key={entryIndex}>
|
||||
{hasSeperator && seperatorElement}
|
||||
<th
|
||||
className={`grid-cells2${sufixCells}`}
|
||||
style={gridCells2Style}
|
||||
>
|
||||
<span
|
||||
className={`wrapclass${sufixWrap}`}
|
||||
style={wrapStyle}
|
||||
>
|
||||
{entry}
|
||||
</span>
|
||||
</th>
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
renderSecondDimensionSubTitles () {
|
||||
const {
|
||||
vFontFamily,
|
||||
vLetterSizeHeader,
|
||||
sufixCells,
|
||||
LabelsArray,
|
||||
SecondHeader,
|
||||
vSeparatorCols,
|
||||
vLetterSize,
|
||||
MeasuresFormat,
|
||||
ExtraLabelsArray
|
||||
} = this.props;
|
||||
|
||||
const baseCSS = this.getBaseCSS();
|
||||
|
||||
return SecondHeader.map((header, index) => {
|
||||
const emptyStyle = {
|
||||
color: 'white',
|
||||
fontFamily: vFontFamily,
|
||||
fontSize: `${12 + vLetterSizeHeader} px`
|
||||
};
|
||||
return (
|
||||
<Fragment key={index}>
|
||||
{vSeparatorCols && index > 0 && (
|
||||
<th
|
||||
className="empty"
|
||||
style={emptyStyle}
|
||||
>
|
||||
*
|
||||
</th>
|
||||
)}
|
||||
{MeasuresFormat.map((measureFormat, measureFormatIndex) => {
|
||||
if (measureFormat.substring(measureFormat.length - 1) === '%') {
|
||||
const cells2SmallStyle = {
|
||||
...baseCSS,
|
||||
cursor: 'default',
|
||||
fontSize: `${13 + vLetterSizeHeader} px`,
|
||||
height: '25px',
|
||||
verticalAlign: 'middle'
|
||||
};
|
||||
return (
|
||||
<th key={measureFormatIndex} className={'grid-cells2-small' + sufixCells} style={cells2SmallStyle}>
|
||||
<span className="wrapclass25">
|
||||
{LabelsArray[measureFormatIndex + 1]}
|
||||
{ExtraLabelsArray[measureFormatIndex]}
|
||||
</span>
|
||||
</th>
|
||||
);
|
||||
}
|
||||
const cells2Style = {
|
||||
...baseCSS,
|
||||
cursor: 'default',
|
||||
fontSize: `${14 + vLetterSizeHeader} px`,
|
||||
height: '25px',
|
||||
verticalAlign: 'middle'
|
||||
};
|
||||
return (
|
||||
<th key={measureFormatIndex} className={'grid-cells2' + sufixCells} style={cells2Style}>
|
||||
<span className="wrapclass25">
|
||||
{LabelsArray[measureFormatIndex + 1]}
|
||||
{ExtraLabelsArray[measureFormatIndex]}
|
||||
</span>
|
||||
</th>
|
||||
);
|
||||
})}
|
||||
</Fragment>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
renderMeasureInfos () {
|
||||
const {
|
||||
vFontFamily,
|
||||
vLetterSizeHeader,
|
||||
dim_count,
|
||||
vExtraLabel,
|
||||
sufixCells,
|
||||
measureInfos
|
||||
} = this.props;
|
||||
|
||||
const baseCSS = this.getBaseCSS();
|
||||
|
||||
if (dim_count === 1) {
|
||||
return measureInfos.map((measureInfo, measureInfoIndex) => {
|
||||
let sufixWrap = '';
|
||||
if (((measureInfo.qFallbackTitle + vExtraLabel).length > 11 && vLetterSizeHeader === 0)
|
||||
|| ((measureInfo.qFallbackTitle + vExtraLabel).length > 12 && vLetterSizeHeader === -2)) {
|
||||
sufixWrap = '70';
|
||||
} else {
|
||||
sufixWrap = 'Empty';
|
||||
}
|
||||
const thStyle = {
|
||||
...baseCSS,
|
||||
cursor: 'default',
|
||||
fontSize: `${15 + vLetterSizeHeader} px`,
|
||||
height: '70px',
|
||||
verticalAlign: 'middle'
|
||||
};
|
||||
return (
|
||||
<th key={measureInfoIndex} className={'grid-cells2' + sufixCells} style={thStyle}>
|
||||
<span className={'wrapclass' + sufixWrap} style={{ fontFamily: vFontFamily }}>
|
||||
{measureInfo.qFallbackTitle + vExtraLabel}
|
||||
</span>
|
||||
</th>
|
||||
);
|
||||
});
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
renderDimensionInfos () {
|
||||
const {
|
||||
dimensionInfos,
|
||||
vLetterSizeHeader,
|
||||
vExportToExcel
|
||||
} = this.props;
|
||||
|
||||
const baseCSS = this.getBaseCSS();
|
||||
|
||||
return dimensionInfos.map((dimensionInfo, dimensionInfoIndex) => {
|
||||
// TODO: move static properties to css file
|
||||
const style = {
|
||||
...baseCSS,
|
||||
cursor: 'default',
|
||||
fontSize: `${17 + vLetterSizeHeader} px`,
|
||||
height: '70px',
|
||||
verticalAlign: 'middle',
|
||||
width: '230px'
|
||||
};
|
||||
|
||||
return (
|
||||
<th
|
||||
className="fdim-cells"
|
||||
key={dimensionInfoIndex}
|
||||
style={style}
|
||||
>
|
||||
<ExportButton excelExport={vExportToExcel} />
|
||||
{dimensionInfo.qFallbackTitle}
|
||||
</th>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
render () {
|
||||
const { vNumDims, measure_count } = this.props;
|
||||
return (
|
||||
<div className="header-wrapper">
|
||||
<table className="header">
|
||||
<tbody>
|
||||
<tr>
|
||||
{this.renderDimensionInfos()}
|
||||
{this.renderMeasureInfos()}
|
||||
{this.renderSecondDimensionTitles()}
|
||||
</tr>
|
||||
{ vNumDims === 2 && measure_count > 1 && (
|
||||
<tr>
|
||||
{this.renderSecondDimensionSubTitles()}
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: make any, object and array forbidden
|
||||
HeaderWrapper.propTypes = {
|
||||
vHeaderColorText: PropTypes.any,
|
||||
vFontFamily: PropTypes.any,
|
||||
vHeaderColorSchema: PropTypes.any,
|
||||
vExportToExcel: PropTypes.any,
|
||||
vNumDims: PropTypes.any,
|
||||
dimensionInfos: PropTypes.any,
|
||||
vLetterSizeHeader: PropTypes.any,
|
||||
vHeaderAlignText: PropTypes.any,
|
||||
MeasuresFormat: PropTypes.any,
|
||||
measure_count: PropTypes.any,
|
||||
sufixCells: PropTypes.any,
|
||||
LabelsArray: PropTypes.any,
|
||||
SecondHeader: PropTypes.any,
|
||||
vSeparatorCols: PropTypes.any,
|
||||
nSecond: PropTypes.any,
|
||||
vLetterSize: PropTypes.any,
|
||||
ExtraLabelsArray: PropTypes.any,
|
||||
dim_count: PropTypes.any,
|
||||
vExtraLabel: PropTypes.any,
|
||||
measureInfos: PropTypes.any
|
||||
};
|
||||
|
||||
export default HeaderWrapper;
|
||||
79
src/headers-table/column-header.jsx
Normal file
79
src/headers-table/column-header.jsx
Normal file
@@ -0,0 +1,79 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { HEADER_FONT_SIZE } from '../initialize-transformed';
|
||||
import Tooltip from '../tooltip/index.jsx';
|
||||
|
||||
class ColumnHeader extends React.PureComponent {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
this.handleSelect = this.handleSelect.bind(this);
|
||||
}
|
||||
|
||||
handleSelect () {
|
||||
const { component, entry } = this.props;
|
||||
component.backendApi.selectValues(1, [entry.elementNumber], false);
|
||||
}
|
||||
|
||||
render () {
|
||||
const { baseCSS, cellWidth, colSpan, component, entry, styling } = this.props;
|
||||
const inEditState = component.inEditState();
|
||||
const isMediumFontSize = styling.headerOptions.fontSizeAdjustment === HEADER_FONT_SIZE.MEDIUM;
|
||||
|
||||
const style = {
|
||||
...baseCSS,
|
||||
fontSize: `${14 + styling.headerOptions.fontSizeAdjustment}px`,
|
||||
height: isMediumFontSize ? '43px' : '33px',
|
||||
verticalAlign: 'middle',
|
||||
minWidth: cellWidth,
|
||||
maxWidth: cellWidth
|
||||
};
|
||||
|
||||
return (
|
||||
<th
|
||||
className="grid-cells"
|
||||
colSpan={colSpan}
|
||||
onClick={this.handleSelect}
|
||||
style={style}
|
||||
>
|
||||
<Tooltip
|
||||
isTooltipActive={!inEditState}
|
||||
styling={styling}
|
||||
tooltipText={entry.displayValue}
|
||||
>
|
||||
{entry.displayValue}
|
||||
</Tooltip>
|
||||
</th>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ColumnHeader.defaultProps = {
|
||||
colSpan: 1
|
||||
};
|
||||
|
||||
ColumnHeader.propTypes = {
|
||||
baseCSS: PropTypes.shape({}).isRequired,
|
||||
cellWidth: PropTypes.string.isRequired,
|
||||
colSpan: PropTypes.number,
|
||||
component: PropTypes.shape({
|
||||
backendApi: PropTypes.shape({
|
||||
selectValues: function (props, propName) {
|
||||
if (props.isSnapshot || typeof props[propName] === 'function') {
|
||||
return null;
|
||||
}
|
||||
return new Error('Missing implementation of qlik.backendApi.selectValues.');
|
||||
}
|
||||
}).isRequired
|
||||
}).isRequired,
|
||||
entry: PropTypes.shape({
|
||||
displayValue: PropTypes.string.isRequired,
|
||||
elementNumber: PropTypes.number.isRequired
|
||||
}).isRequired,
|
||||
styling: PropTypes.shape({
|
||||
headerOptions: PropTypes.shape({
|
||||
fontSizeAdjustment: PropTypes.number.isRequired
|
||||
}).isRequired
|
||||
}).isRequired
|
||||
};
|
||||
|
||||
export default ColumnHeader;
|
||||
48
src/headers-table/dim1-header.jsx
Normal file
48
src/headers-table/dim1-header.jsx
Normal file
@@ -0,0 +1,48 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { HEADER_FONT_SIZE } from '../initialize-transformed';
|
||||
import Tooltip from '../tooltip/index.jsx';
|
||||
|
||||
const Dim1Header = ({ component, baseCSS, title, hasSecondDimension, styling }) => {
|
||||
const inEditState = component.inEditState();
|
||||
const rowSpan = hasSecondDimension ? 2 : 1;
|
||||
const isMediumFontSize = styling.headerOptions.fontSizeAdjustment === HEADER_FONT_SIZE.MEDIUM;
|
||||
const style = {
|
||||
...baseCSS,
|
||||
cursor: 'default',
|
||||
fontSize: `${16 + styling.headerOptions.fontSizeAdjustment}px`,
|
||||
height: isMediumFontSize ? '90px' : '70px',
|
||||
verticalAlign: 'middle',
|
||||
width: '230px'
|
||||
};
|
||||
|
||||
return (
|
||||
<th
|
||||
className="fdim-cells"
|
||||
rowSpan={rowSpan}
|
||||
style={style}
|
||||
>
|
||||
<Tooltip
|
||||
isTooltipActive={!inEditState}
|
||||
styling={styling}
|
||||
tooltipText={title}
|
||||
>
|
||||
{title}
|
||||
</Tooltip>
|
||||
</th>
|
||||
);
|
||||
};
|
||||
|
||||
Dim1Header.propTypes = {
|
||||
baseCSS: PropTypes.shape({}).isRequired,
|
||||
component: PropTypes.shape({}).isRequired,
|
||||
hasSecondDimension: PropTypes.bool.isRequired,
|
||||
styling: PropTypes.shape({
|
||||
headerOptions: PropTypes.shape({
|
||||
fontSizeAdjustment: PropTypes.number.isRequired
|
||||
}).isRequired
|
||||
}).isRequired,
|
||||
title: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
export default Dim1Header;
|
||||
5
src/headers-table/index.componentModel.js
Normal file
5
src/headers-table/index.componentModel.js
Normal file
@@ -0,0 +1,5 @@
|
||||
function Model (component) {
|
||||
this.component = component;
|
||||
}
|
||||
|
||||
export default Model;
|
||||
137
src/headers-table/index.jsx
Normal file
137
src/headers-table/index.jsx
Normal file
@@ -0,0 +1,137 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Dim1Header from './dim1-header.jsx';
|
||||
import ColumnHeader from './column-header.jsx';
|
||||
import MeasurementColumnHeader from './measurement-column-header.jsx';
|
||||
import { injectSeparators } from '../utilities';
|
||||
|
||||
class HeadersTable extends React.PureComponent {
|
||||
render () {
|
||||
const {
|
||||
cellWidth,
|
||||
columnSeparatorWidth,
|
||||
component,
|
||||
data,
|
||||
isKpi,
|
||||
styling
|
||||
} = this.props;
|
||||
|
||||
const baseCSS = {
|
||||
backgroundColor: styling.headerOptions.colorSchema,
|
||||
color: styling.headerOptions.textColor,
|
||||
fontFamily: styling.options.fontFamily,
|
||||
textAlign: styling.headerOptions.alignment
|
||||
};
|
||||
|
||||
const {
|
||||
dimension1,
|
||||
dimension2,
|
||||
measurements
|
||||
} = data.headers;
|
||||
|
||||
const hasSecondDimension = dimension2.length > 0;
|
||||
|
||||
const separatorStyle = {
|
||||
minWidth: columnSeparatorWidth,
|
||||
maxWidth: columnSeparatorWidth
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="header-wrapper">
|
||||
<table className="header">
|
||||
<tbody>
|
||||
<tr>
|
||||
{isKpi ?
|
||||
<Dim1Header
|
||||
baseCSS={baseCSS}
|
||||
component={component}
|
||||
hasSecondDimension={hasSecondDimension}
|
||||
styling={styling}
|
||||
title={dimension1[0].name}
|
||||
/> : null
|
||||
}
|
||||
{!isKpi && !hasSecondDimension && measurements.map(measurementEntry => (
|
||||
<MeasurementColumnHeader
|
||||
baseCSS={baseCSS}
|
||||
cellWidth={cellWidth}
|
||||
hasSecondDimension={hasSecondDimension}
|
||||
key={`${measurementEntry.displayValue}-${measurementEntry.name}-${measurementEntry.index}`}
|
||||
measurement={measurementEntry}
|
||||
styling={styling}
|
||||
/>
|
||||
))}
|
||||
{!isKpi && hasSecondDimension && injectSeparators(dimension2, columnSeparatorWidth).map((entry, index) => {
|
||||
if (entry.isSeparator) {
|
||||
return (
|
||||
<th
|
||||
className="empty"
|
||||
key={index}
|
||||
style={separatorStyle}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<ColumnHeader
|
||||
baseCSS={baseCSS}
|
||||
cellWidth={cellWidth}
|
||||
colSpan={measurements.length}
|
||||
component={component}
|
||||
entry={entry}
|
||||
key={entry.displayValue}
|
||||
styling={styling}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</tr>
|
||||
{!isKpi && hasSecondDimension && (
|
||||
<tr>
|
||||
{injectSeparators(dimension2, columnSeparatorWidth).map((dimensionEntry, index) => {
|
||||
if (dimensionEntry.isSeparator) {
|
||||
return (
|
||||
<th
|
||||
className="empty"
|
||||
key={index}
|
||||
style={separatorStyle}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return measurements.map(measurementEntry => (
|
||||
<MeasurementColumnHeader
|
||||
baseCSS={baseCSS}
|
||||
cellWidth={cellWidth}
|
||||
dimensionEntry={dimensionEntry}
|
||||
hasSecondDimension={hasSecondDimension}
|
||||
key={`${measurementEntry.displayValue}-${measurementEntry.name}-${measurementEntry.index}-${dimensionEntry.name}`}
|
||||
measurement={measurementEntry}
|
||||
styling={styling}
|
||||
/>
|
||||
));
|
||||
})}
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
HeadersTable.propTypes = {
|
||||
cellWidth: PropTypes.string.isRequired,
|
||||
columnSeparatorWidth: PropTypes.string.isRequired,
|
||||
data: PropTypes.shape({
|
||||
headers: PropTypes.shape({
|
||||
dimension1: PropTypes.array,
|
||||
dimension2: PropTypes.array,
|
||||
measurements: PropTypes.array
|
||||
})
|
||||
}).isRequired,
|
||||
component: PropTypes.shape({}).isRequired,
|
||||
styling: PropTypes.shape({
|
||||
headerOptions: PropTypes.shape({}),
|
||||
options: PropTypes.shape({})
|
||||
}).isRequired,
|
||||
isKpi: PropTypes.bool.isRequired
|
||||
};
|
||||
|
||||
export default HeadersTable;
|
||||
43
src/headers-table/index.spec.js
Normal file
43
src/headers-table/index.spec.js
Normal file
@@ -0,0 +1,43 @@
|
||||
import merge from 'lodash.merge';
|
||||
import Model from './index.componentModel';
|
||||
import Component from './index.jsx';
|
||||
import { mountedComponent } from 'test-utilities';
|
||||
import sampleState from 'test-utilities/capex-sample-state';
|
||||
|
||||
|
||||
describe('<HeadersTable />', () => {
|
||||
const { data, general, styling } = sampleState;
|
||||
const defaultProps = {
|
||||
data,
|
||||
general,
|
||||
qlik: {
|
||||
backendApi: {
|
||||
selectValues: () => {}
|
||||
},
|
||||
inEditState: () => {}
|
||||
},
|
||||
styling
|
||||
};
|
||||
|
||||
function setup (otherProps = {}) {
|
||||
const props = merge(defaultProps, otherProps);
|
||||
|
||||
return mountedComponent(Model, Component, props);
|
||||
}
|
||||
|
||||
it('should render without exploding when 2 dimensions', () => {
|
||||
const model = setup();
|
||||
expect(model.component).toBeDefined();
|
||||
});
|
||||
|
||||
it('should render without exploding when 1 dimension', () => {
|
||||
const noSecondDimensionProps = {
|
||||
data: {
|
||||
...defaultProps.data.headers,
|
||||
dimension2: []
|
||||
}
|
||||
};
|
||||
const model = setup(noSecondDimensionProps);
|
||||
expect(model.component).toBeDefined();
|
||||
});
|
||||
});
|
||||
77
src/headers-table/measurement-column-header.jsx
Normal file
77
src/headers-table/measurement-column-header.jsx
Normal file
@@ -0,0 +1,77 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { HEADER_FONT_SIZE } from '../initialize-transformed';
|
||||
import Tooltip from '../tooltip/index.jsx';
|
||||
|
||||
const MeasurementColumnHeader = ({ baseCSS, cellWidth, hasSecondDimension, measurement, styling }) => {
|
||||
const title = `${measurement.name}`;
|
||||
const { fontSizeAdjustment } = styling.headerOptions;
|
||||
const isMediumFontSize = fontSizeAdjustment === HEADER_FONT_SIZE.MEDIUM;
|
||||
|
||||
const cellStyle = {
|
||||
...baseCSS,
|
||||
verticalAlign: 'middle',
|
||||
minWidth: cellWidth,
|
||||
maxWidth: cellWidth
|
||||
};
|
||||
|
||||
if (hasSecondDimension) {
|
||||
const isPercentageFormat = measurement.format.substring(measurement.format.length - 1) === '%';
|
||||
let baseFontSize = 14;
|
||||
if (isPercentageFormat) {
|
||||
baseFontSize = 13;
|
||||
}
|
||||
cellStyle.fontSize = `${baseFontSize + fontSizeAdjustment}px`;
|
||||
cellStyle.height = isMediumFontSize ? '45px' : '35px';
|
||||
|
||||
return (
|
||||
<th
|
||||
className="grid-cells"
|
||||
style={cellStyle}
|
||||
>
|
||||
<Tooltip
|
||||
tooltipText={title}
|
||||
styling={styling}
|
||||
>
|
||||
{title}
|
||||
</Tooltip>
|
||||
</th>
|
||||
);
|
||||
}
|
||||
|
||||
cellStyle.fontSize = `${15 + fontSizeAdjustment}px`;
|
||||
cellStyle.height = isMediumFontSize ? '90px' : '70px';
|
||||
return (
|
||||
<th
|
||||
className="grid-cells"
|
||||
style={cellStyle}
|
||||
>
|
||||
<Tooltip
|
||||
tooltipText={title}
|
||||
styling={styling}
|
||||
>
|
||||
{title}
|
||||
</Tooltip>
|
||||
</th>
|
||||
);
|
||||
};
|
||||
|
||||
MeasurementColumnHeader.defaultProps = {
|
||||
hasSecondDimension: false
|
||||
};
|
||||
|
||||
MeasurementColumnHeader.propTypes = {
|
||||
baseCSS: PropTypes.shape({}).isRequired,
|
||||
cellWidth: PropTypes.string.isRequired,
|
||||
hasSecondDimension: PropTypes.bool,
|
||||
measurement: PropTypes.shape({
|
||||
name: PropTypes.string.isRequired
|
||||
}).isRequired,
|
||||
styling: PropTypes.shape({
|
||||
headerOptions: PropTypes.shape({
|
||||
fontSizeAdjustment: PropTypes.number.isRequired
|
||||
}).isRequired
|
||||
}).isRequired
|
||||
};
|
||||
|
||||
export default MeasurementColumnHeader;
|
||||
100
src/index.js
100
src/index.js
@@ -1,5 +1,11 @@
|
||||
import paint from './paint';
|
||||
import definition from './definition';
|
||||
import { exportXLS } from './excel-export';
|
||||
import { initializeDataCube, initializeDesignList } from './dataset';
|
||||
import initializeStore from './store';
|
||||
import qlik from 'qlik';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import Root from './root.jsx';
|
||||
import './main.less';
|
||||
|
||||
if (!window._babelPolyfill) { // eslint-disable-line no-underscore-dangle
|
||||
@@ -7,34 +13,98 @@ if (!window._babelPolyfill) { // eslint-disable-line no-underscore-dangle
|
||||
}
|
||||
|
||||
export default {
|
||||
controller: [
|
||||
'$scope',
|
||||
'$timeout',
|
||||
function () { }
|
||||
],
|
||||
design: {
|
||||
dimensions: {
|
||||
max: 1,
|
||||
min: 0
|
||||
}
|
||||
},
|
||||
data: {
|
||||
dimensions: {
|
||||
max: function (nMeasures) {
|
||||
return nMeasures < 9 ? 2 : 1;
|
||||
},
|
||||
min: 1,
|
||||
uses: 'dimensions'
|
||||
},
|
||||
measures: {
|
||||
max: function (nDims) {
|
||||
return nDims < 2 ? 9 : 8;
|
||||
},
|
||||
min: 1,
|
||||
uses: 'measures'
|
||||
}
|
||||
},
|
||||
definition,
|
||||
initialProperties: {
|
||||
version: 1.0,
|
||||
qHyperCubeDef: {
|
||||
qDimensions: [],
|
||||
qInitialDataFetch: [
|
||||
{
|
||||
qHeight: 1000,
|
||||
qHeight: 1,
|
||||
qWidth: 10
|
||||
}
|
||||
],
|
||||
qMeasures: []
|
||||
qMeasures: [],
|
||||
qSuppressMissing: true,
|
||||
qSuppressZero: false
|
||||
}
|
||||
},
|
||||
paint ($element, layout) {
|
||||
try {
|
||||
paint($element, layout, this);
|
||||
} catch (exception) {
|
||||
console.error(exception); // eslint-disable-line no-console
|
||||
throw exception;
|
||||
}
|
||||
support: {
|
||||
export: true,
|
||||
exportData: true,
|
||||
snapshot: true
|
||||
},
|
||||
paint: async function ($element, layout) {
|
||||
const dataCube = await initializeDataCube(this, layout);
|
||||
const designList = await initializeDesignList(this, layout);
|
||||
const state = await initializeStore({
|
||||
$element,
|
||||
component: this,
|
||||
dataCube,
|
||||
designList,
|
||||
layout
|
||||
});
|
||||
const editmodeClass = this.inAnalysisState() ? '' : 'edit-mode';
|
||||
const jsx = (
|
||||
<Root
|
||||
editmodeClass={editmodeClass}
|
||||
component={this}
|
||||
state={state}
|
||||
/>
|
||||
);
|
||||
|
||||
ReactDOM.render(jsx, $element[0]);
|
||||
},
|
||||
snapshot: {
|
||||
canTakeSnapshot: true
|
||||
},
|
||||
setSnapshotData: async function (snapshotLayout) {
|
||||
snapshotLayout.snapshotData.dataCube = await initializeDataCube(this, snapshotLayout);
|
||||
snapshotLayout.snapshotData.designList = await initializeDesignList(this, snapshotLayout);
|
||||
return snapshotLayout;
|
||||
},
|
||||
getContextMenu: async function (obj, menu) {
|
||||
const app = qlik.currApp(this);
|
||||
const isPersonalResult = await app.global.isPersonalMode();
|
||||
if (!this.$scope.layout.allowexportxls || (isPersonalResult && isPersonalResult.qReturn)) {
|
||||
return menu;
|
||||
}
|
||||
|
||||
menu.addItem({
|
||||
translation: "Export as XLS",
|
||||
tid: "export-excel",
|
||||
icon: "export",
|
||||
select: () => {
|
||||
exportXLS(
|
||||
this.$element,
|
||||
this.$scope.layout.title,
|
||||
this.$scope.layout.subtitle,
|
||||
this.$scope.layout.footnote);
|
||||
}
|
||||
});
|
||||
return menu;
|
||||
},
|
||||
version: 1.0
|
||||
};
|
||||
|
||||
340
src/initialize-transformed.js
Normal file
340
src/initialize-transformed.js
Normal file
@@ -0,0 +1,340 @@
|
||||
import { distinctArray } from './utilities';
|
||||
|
||||
export const HEADER_FONT_SIZE = {
|
||||
SMALL: -1,
|
||||
MEDIUM: 1
|
||||
};
|
||||
|
||||
function getAlignment (option) {
|
||||
const alignmentOptions = {
|
||||
1: 'left',
|
||||
2: 'center',
|
||||
3: 'right'
|
||||
};
|
||||
|
||||
return alignmentOptions[option] || 'left';
|
||||
}
|
||||
|
||||
function getFontSizeAdjustment (option) {
|
||||
const fontSizeAdjustmentOptions = {
|
||||
1: HEADER_FONT_SIZE.SMALL,
|
||||
2: HEADER_FONT_SIZE.MEDIUM
|
||||
};
|
||||
|
||||
return fontSizeAdjustmentOptions[option] || 0;
|
||||
}
|
||||
|
||||
function generateMeasurements (information) {
|
||||
return information.map(measurement => {
|
||||
const transformedMeasurement = {
|
||||
format: measurement.qNumFormat.qFmt || '#.##0',
|
||||
name: measurement.qFallbackTitle
|
||||
};
|
||||
|
||||
return transformedMeasurement;
|
||||
});
|
||||
}
|
||||
|
||||
function generateDimensionEntry (information, data) {
|
||||
return {
|
||||
displayValue: data.qText || data.qNum,
|
||||
elementNumber: data.qElemNumber,
|
||||
name: information.qFallbackTitle,
|
||||
value: data.qNum
|
||||
};
|
||||
}
|
||||
|
||||
function generateMatrixCell ({ cell, dimension1Information, dimension2Information, measurementInformation }) {
|
||||
const matrixCell = {
|
||||
displayValue: cell.qText,
|
||||
format: measurementInformation.format,
|
||||
name: measurementInformation.name,
|
||||
parents: {
|
||||
dimension1: {
|
||||
elementNumber: dimension1Information.qElemNumber,
|
||||
header: dimension1Information.qText
|
||||
},
|
||||
measurement: {
|
||||
header: measurementInformation.name,
|
||||
index: measurementInformation.index
|
||||
}
|
||||
},
|
||||
value: cell.qNum
|
||||
};
|
||||
|
||||
if (dimension2Information) {
|
||||
matrixCell.parents.dimension2 = {
|
||||
elementNumber: dimension2Information.qElemNumber
|
||||
};
|
||||
}
|
||||
|
||||
return matrixCell;
|
||||
}
|
||||
|
||||
let lastRow = 0;
|
||||
function generateDataSet (
|
||||
component, dimensionsInformation, measurementsInformation, dataCube) {
|
||||
|
||||
const measurements = generateMeasurements(measurementsInformation);
|
||||
let dimension1 = [];
|
||||
let dimension2 = [];
|
||||
let matrix = [];
|
||||
|
||||
const hasSecondDimension = dimensionsInformation.length > 1;
|
||||
dataCube.forEach(row => {
|
||||
lastRow += 1;
|
||||
const dimension1Entry = generateDimensionEntry(dimensionsInformation[0], row[0]);
|
||||
dimension1.push(dimension1Entry);
|
||||
let dimension2Entry;
|
||||
let firstDataCell = 1;
|
||||
if (hasSecondDimension) {
|
||||
dimension2Entry = generateDimensionEntry(dimensionsInformation[1], row[1]);
|
||||
dimension2.push(dimension2Entry);
|
||||
firstDataCell = 2;
|
||||
}
|
||||
let matrixRow = row
|
||||
.slice(firstDataCell, row.length)
|
||||
.map((cell, cellIndex) => {
|
||||
const measurementInformation = measurements[cellIndex];
|
||||
measurementInformation.index = cellIndex;
|
||||
const dimension1Information = row[0]; // eslint-disable-line prefer-destructuring
|
||||
const dimension2Information = hasSecondDimension ? row[1] : null;
|
||||
const generatedCell = generateMatrixCell({
|
||||
cell,
|
||||
dimension1Information,
|
||||
dimension2Information,
|
||||
measurementInformation
|
||||
});
|
||||
|
||||
return generatedCell;
|
||||
});
|
||||
|
||||
let appendToRowIndex = matrix.length;
|
||||
if (hasSecondDimension) {
|
||||
// See if there already is a row for the current dim1
|
||||
for (let i = 0; i < matrix.length; i++) {
|
||||
if (matrix[i][0].parents.dimension1.header === matrixRow[0].parents.dimension1.header) {
|
||||
appendToRowIndex = i;
|
||||
matrixRow = matrix[i].concat(matrixRow);
|
||||
}
|
||||
}
|
||||
}
|
||||
matrix[appendToRowIndex] = matrixRow;
|
||||
});
|
||||
|
||||
// filter header dimensions to only have distinct values
|
||||
dimension1 = distinctArray(dimension1);
|
||||
dimension2 = distinctArray(dimension2);
|
||||
|
||||
// Make sure all rows are saturated, otherwise data risks being displayed in the wrong column
|
||||
matrix = matrix.map((row, rowIndex) => {
|
||||
if ((hasSecondDimension && row.length == (dimension2.length * measurements.length))
|
||||
|| (!hasSecondDimension && row.length == measurements.length)) {
|
||||
// Row is saturated
|
||||
return row;
|
||||
}
|
||||
|
||||
// Row is not saturated, so must add empty cells to fill the gaps
|
||||
let newRow = [];
|
||||
if (hasSecondDimension) {
|
||||
// Got a second dimension, so need to add measurements for all values of the second dimension
|
||||
let rowDataIndex = 0;
|
||||
dimension2.forEach(dim => {
|
||||
rowDataIndex = appendMissingCells(
|
||||
row, newRow, rowDataIndex, measurements, rowIndex, dim.elementNumber);
|
||||
});
|
||||
} else {
|
||||
appendMissingCells(row, newRow, 0, measurements, rowIndex);
|
||||
}
|
||||
|
||||
return newRow;
|
||||
});
|
||||
|
||||
return {
|
||||
dimension1: dimension1,
|
||||
dimension2: dimension2,
|
||||
matrix,
|
||||
measurements
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
* Appends the cells of the source row, as well as those missing, to the destination row, starting
|
||||
* from the given source index. Returns the source index of the next source cell after this has
|
||||
* completed. If there is a second dimension the dim2ElementNumber should be set to the current
|
||||
* index of the dimension2 value being processed.
|
||||
*/
|
||||
function appendMissingCells (
|
||||
sourceRow, destRow, sourceIndex, measurements, dim1ElementNumber, dim2ElementNumber = -1) {
|
||||
|
||||
let index = sourceIndex;
|
||||
measurements.forEach((measurement, measureIndex) => {
|
||||
if (index < sourceRow.length
|
||||
&& (dim2ElementNumber === -1
|
||||
|| sourceRow[index].parents.dimension2.elementNumber === dim2ElementNumber)
|
||||
&& sourceRow[index].parents.measurement.header === measurement.name) {
|
||||
// Source contains the expected cell
|
||||
destRow.push(sourceRow[index]);
|
||||
index++;
|
||||
} else {
|
||||
// Source doesn't contain the expected cell, so add empty
|
||||
destRow.push({
|
||||
displayValue: '',
|
||||
parents: {
|
||||
dimension1: { elementNumber: dim1ElementNumber },
|
||||
dimension2: { elementNumber: dim2ElementNumber },
|
||||
measurement: {
|
||||
header: measurement.name,
|
||||
index: measureIndex
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
return index;
|
||||
}
|
||||
|
||||
function initializeTransformed ({ $element, component, dataCube, designList, layout }) {
|
||||
const dimensionsInformation = component.backendApi.getDimensionInfos();
|
||||
const measurementsInformation = component.backendApi.getMeasureInfos();
|
||||
const dimensionCount = layout.qHyperCube.qDimensionInfo.length;
|
||||
const rowCount = component.backendApi.getRowCount();
|
||||
const maxLoops = layout.maxloops;
|
||||
const {
|
||||
dimension1,
|
||||
dimension2,
|
||||
measurements,
|
||||
matrix
|
||||
} = generateDataSet(component, dimensionsInformation, measurementsInformation, dataCube);
|
||||
|
||||
const customSchemaBasic = [];
|
||||
const customSchemaFull = [];
|
||||
let customHeadersCount = 0;
|
||||
|
||||
if (designList && designList.length > 0) {
|
||||
const headers = designList[0].split(';');
|
||||
customHeadersCount = headers.length;
|
||||
for (let lineNumber = 0; lineNumber < designList.length; lineNumber += 1) {
|
||||
customSchemaFull[lineNumber] = new Array(headers.length);
|
||||
const data = designList[lineNumber].split(';');
|
||||
|
||||
if (data.length === headers.length) {
|
||||
for (let headerIndex = 0; headerIndex < headers.length; headerIndex += 1) {
|
||||
[customSchemaBasic[lineNumber]] = data;
|
||||
customSchemaFull[lineNumber][headerIndex] = data[headerIndex];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let cellWidth;
|
||||
if (layout.fitchartwidth) {
|
||||
// The widths are calculated based on the current element width. Note: this could use % to set
|
||||
// the widths as percentages of the available width. However, this often results in random
|
||||
// columns getting 1px wider than the others because of rounding necessary to fill the width.
|
||||
// This 1px causes missalignment between the data- and header tables.
|
||||
cellWidth = '';
|
||||
} else {
|
||||
// If using the previous solution just set 60px
|
||||
cellWidth = `${layout.columnwidthslider > 10 ? layout.columnwidthslider : 60}px`;
|
||||
}
|
||||
|
||||
// top level properties could be reducers and then components connect to grab what they want,
|
||||
// possibly with reselect for some presentational transforms (moving some of the presentational logic like formatting and such)
|
||||
const transformedProperties = {
|
||||
data: {
|
||||
headers: {
|
||||
dimension1, // column headers
|
||||
dimension2, // parent row headers if exists
|
||||
measurements // row headers, looped for each dimension2 if exists
|
||||
},
|
||||
matrix, // 2d array of all rows/cells to render in body of datatable
|
||||
meta: {
|
||||
dimensionCount: dimensionsInformation.length
|
||||
}
|
||||
},
|
||||
general: {
|
||||
allowExcelExport: layout.allowexportxls,
|
||||
allowFilteringByClick: layout.filteroncellclick,
|
||||
cellWidth: cellWidth,
|
||||
errorMessage: layout.errormessage,
|
||||
footnote: layout.footnote,
|
||||
maxLoops,
|
||||
subtitle: layout.subtitle,
|
||||
title: layout.title,
|
||||
useColumnSeparator: layout.separatorcols && dimensionCount > 1
|
||||
},
|
||||
selection: {
|
||||
dimensionSelectionCounts: dimensionsInformation.map(dimensionInfo => dimensionInfo.qStateCounts.qSelected)
|
||||
},
|
||||
styling: {
|
||||
customCSV: {
|
||||
basic: customSchemaBasic,
|
||||
count: customHeadersCount,
|
||||
full: customSchemaFull
|
||||
},
|
||||
hasCustomFileStyle: Boolean(designList),
|
||||
headerOptions: {
|
||||
alignment: getAlignment(layout.HeaderAlign),
|
||||
colorSchema: layout.HeaderColorSchema.color,
|
||||
fontSizeAdjustment: getFontSizeAdjustment(layout.lettersizeheader),
|
||||
textColor: layout.HeaderTextColorSchema.color
|
||||
},
|
||||
options: {
|
||||
backgroundColor: layout.rowEvenBGColor,
|
||||
backgroundColorOdd: layout.rowOddBGColor,
|
||||
color: layout.BodyTextColorSchema,
|
||||
fontFamily: layout.FontFamily,
|
||||
fontSizeAdjustment: getFontSizeAdjustment(layout.lettersize),
|
||||
textAlignment: layout.cellTextAlignment
|
||||
},
|
||||
conditionalColoring: {
|
||||
enabled: layout.conditionalcoloring.enabled,
|
||||
colorAllRows: layout.conditionalcoloring.colorall,
|
||||
rows: layout.conditionalcoloring.rows.map(row => row.rowname),
|
||||
colorAllMeasures: typeof layout.conditionalcoloring.colorallmeasures === 'undefined'
|
||||
|| layout.conditionalcoloring.colorallmeasures,
|
||||
measures: !layout.conditionalcoloring.measures
|
||||
? [] : layout.conditionalcoloring.measures.split(',').map(index => Number(index)),
|
||||
threshold: {
|
||||
poor: layout.conditionalcoloring.threshold_poor,
|
||||
fair: layout.conditionalcoloring.threshold_fair
|
||||
},
|
||||
colors: {
|
||||
poor: {
|
||||
color: layout.conditionalcoloring.color_poor,
|
||||
textColor: layout.conditionalcoloring.textcolor_poor
|
||||
},
|
||||
fair: {
|
||||
color: layout.conditionalcoloring.color_fair,
|
||||
textColor: layout.conditionalcoloring.textcolor_fair
|
||||
},
|
||||
good: {
|
||||
color: layout.conditionalcoloring.color_good,
|
||||
textColor: layout.conditionalcoloring.textcolor_good
|
||||
}
|
||||
}
|
||||
},
|
||||
symbolForNulls: layout.symbolfornulls,
|
||||
usePadding: layout.indentbool
|
||||
}
|
||||
};
|
||||
|
||||
if (rowCount > lastRow && rowCount <= (maxLoops * 1000)) {
|
||||
const requestPage = [
|
||||
{
|
||||
qHeight: Math.min(1000, rowCount - lastRow),
|
||||
qLeft: 0,
|
||||
qTop: matrix.length,
|
||||
qWidth: 10 // should be # of columns
|
||||
}
|
||||
];
|
||||
component.backendApi.getData(requestPage).then(() => {
|
||||
component.paint($element, layout);
|
||||
});
|
||||
}
|
||||
|
||||
return transformedProperties;
|
||||
}
|
||||
|
||||
export default initializeTransformed;
|
||||
2
src/linked-scroll/index.js
Normal file
2
src/linked-scroll/index.js
Normal file
@@ -0,0 +1,2 @@
|
||||
export { default as LinkedScrollWrapper } from './linked-scroll-wrapper.jsx';
|
||||
export { default as LinkedScrollSection } from './linked-scroll-section.jsx';
|
||||
29
src/linked-scroll/linked-scroll-section.jsx
Normal file
29
src/linked-scroll/linked-scroll-section.jsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { LinkedScrollContext } from './linked-scroll-wrapper.jsx';
|
||||
|
||||
class LinkedScrollSection extends React.PureComponent {
|
||||
static contextType = LinkedScrollContext;
|
||||
|
||||
componentDidMount () {
|
||||
const { link } = this.context;
|
||||
link(this);
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
const { unlink } = this.context;
|
||||
unlink(this);
|
||||
}
|
||||
|
||||
render () {
|
||||
const { children } = this.props;
|
||||
|
||||
return children;
|
||||
}
|
||||
}
|
||||
|
||||
LinkedScrollSection.propTypes = {
|
||||
children: PropTypes.any
|
||||
};
|
||||
|
||||
export default LinkedScrollSection;
|
||||
82
src/linked-scroll/linked-scroll-wrapper.jsx
Normal file
82
src/linked-scroll/linked-scroll-wrapper.jsx
Normal file
@@ -0,0 +1,82 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
export const LinkedScrollContext = React.createContext();
|
||||
|
||||
class LinkedScrollWrapper extends React.PureComponent {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
|
||||
this.linkComponent = this.linkComponent.bind(this);
|
||||
this.unlinkComponent = this.unlinkComponent.bind(this);
|
||||
this.handleScroll = this.handleScroll.bind(this);
|
||||
this.scrollElements = [];
|
||||
|
||||
this.linkActions = {
|
||||
link: this.linkComponent,
|
||||
unlink: this.unlinkComponent
|
||||
};
|
||||
}
|
||||
|
||||
linkComponent (component) {
|
||||
// eslint-disable-next-line react/no-find-dom-node
|
||||
const node = ReactDOM.findDOMNode(component);
|
||||
const element = {
|
||||
component,
|
||||
node
|
||||
};
|
||||
this.scrollElements.push(element);
|
||||
node.onscroll = this.handleScroll.bind(this, element);
|
||||
}
|
||||
|
||||
unlinkComponent (component) {
|
||||
const componentIndex = this.scrollElements.map(element => element.component).indexOf(component);
|
||||
if (componentIndex !== -1) {
|
||||
this.scrollElements.removeAt(componentIndex);
|
||||
// eslint-disable-next-line react/no-find-dom-node
|
||||
const node = ReactDOM.findDOMNode(component);
|
||||
node.onscroll = null;
|
||||
}
|
||||
}
|
||||
|
||||
handleScroll (element) {
|
||||
window.requestAnimationFrame(() => {
|
||||
this.sync(element);
|
||||
});
|
||||
}
|
||||
|
||||
sync (scrollElement) {
|
||||
this.scrollElements.forEach(element => {
|
||||
if (scrollElement === element) {
|
||||
return;
|
||||
}
|
||||
element.node.onscroll = null;
|
||||
if (element.component.props.linkHorizontal) {
|
||||
element.node.scrollLeft = scrollElement.node.scrollLeft;
|
||||
}
|
||||
|
||||
if (element.component.props.linkVertical) {
|
||||
element.node.scrollTop = scrollElement.node.scrollTop;
|
||||
}
|
||||
window.requestAnimationFrame(() => {
|
||||
element.node.onscroll = this.handleScroll.bind(this, element);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
render () {
|
||||
const { children } = this.props;
|
||||
return (
|
||||
<LinkedScrollContext.Provider value={this.linkActions}>
|
||||
{children}
|
||||
</LinkedScrollContext.Provider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
LinkedScrollWrapper.propTypes = {
|
||||
children: PropTypes.any
|
||||
};
|
||||
|
||||
export default LinkedScrollWrapper;
|
||||
383
src/main.less
383
src/main.less
@@ -1,215 +1,240 @@
|
||||
/* eslint-disable */
|
||||
.qv-object-qlik-smart-pivot {
|
||||
@TableBorder: 1px solid #d3d3d3;
|
||||
@KpiTableWidth: 230px;
|
||||
|
||||
._cell(@Width: 50px) {
|
||||
min-width: @Width!important;
|
||||
max-width: @Width!important;
|
||||
cursor: pointer;
|
||||
line-height: 1em!important;
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
div.qv-object-content-container {
|
||||
overflow-x: scroll;
|
||||
overflow-y: hidden;
|
||||
z-index: 110;
|
||||
}
|
||||
.edit-mode {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.icon-xls {
|
||||
text-align: left;
|
||||
}
|
||||
.grid-cells {
|
||||
cursor: pointer;
|
||||
line-height: 1em !important;
|
||||
}
|
||||
|
||||
button {
|
||||
width: 100%;
|
||||
}
|
||||
div.qv-object-content-container {
|
||||
z-index: 110;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
width: auto;
|
||||
border-left: @TableBorder;
|
||||
border-right: @TableBorder;
|
||||
border-top: @TableBorder;
|
||||
}
|
||||
.icon-xls {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
td, th {
|
||||
border: 1px solid #ffffff;
|
||||
padding: 5px;
|
||||
border-collapse: collapse;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
vertical-align: middle;
|
||||
cursor: default;
|
||||
}
|
||||
button {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.empty {
|
||||
width: 3%;
|
||||
background: #ffffff;
|
||||
min-width: 4px !important;
|
||||
max-width: 4px !important;
|
||||
}
|
||||
table {
|
||||
border-collapse: separate;
|
||||
border-spacing: 1px;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
th.main-kpi {
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
border-bottom: @TableBorder;
|
||||
}
|
||||
tr {
|
||||
height: 25px;
|
||||
}
|
||||
|
||||
td,
|
||||
th {
|
||||
padding: 5px !important; // prevent overwriting from single object
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
vertical-align: middle;
|
||||
cursor: default;
|
||||
|
||||
.numeric {
|
||||
text-align: right;
|
||||
}
|
||||
/*This is for wrap text in headers*/
|
||||
.wrapclass25 {
|
||||
width: 100%;
|
||||
height: 25px;
|
||||
white-space: pre-line;
|
||||
overflow: hidden;
|
||||
display: block;
|
||||
}
|
||||
> div {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
font-family: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
.wrapclass45 {
|
||||
width: 100%;
|
||||
height: 45px;
|
||||
white-space: pre-line;
|
||||
overflow: hidden;
|
||||
display: block;
|
||||
}
|
||||
.empty {
|
||||
background: #fff;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.wrapclass70 {
|
||||
width: 100%;
|
||||
height: 70px;
|
||||
white-space: pre-line;
|
||||
overflow: hidden;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
line-height: 20px;
|
||||
}
|
||||
th.main-kpi {
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.wrapclassEmpty {
|
||||
width: 100%;
|
||||
}
|
||||
/*******************/
|
||||
/* Medium column size*/
|
||||
/*******************/
|
||||
.grid-cells { ._cell(70px); }
|
||||
.grid-cells2 { ._cell(70px); }
|
||||
.grid-cells-small { ._cell(52px); }
|
||||
.grid-cells2-small { ._cell(52px); }
|
||||
/*******************/
|
||||
/* Small column size*/
|
||||
/*******************/
|
||||
.grid-cells-s { ._cell(67px); }
|
||||
.grid-cells2-s { ._cell(67px); }
|
||||
.grid-cells-small-s { ._cell(52px); }
|
||||
.grid-cells2-small-s { ._cell(52px); }
|
||||
/*******************/
|
||||
/* large column size*/
|
||||
/*******************/
|
||||
.grid-cells-l { ._cell(82px); }
|
||||
.grid-cells2-l { ._cell(82px); }
|
||||
.grid-cells-small-l { ._cell(66px); }
|
||||
.grid-cells2-small-l { ._cell(66px); }
|
||||
.numeric {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/*END OF GRID CELLS*/
|
||||
/*First Column*/
|
||||
.fdim-cells {
|
||||
min-width: 230px !Important;
|
||||
max-width: 230px !Important;
|
||||
cursor: pointer;
|
||||
background-color: white;
|
||||
}
|
||||
// First Column
|
||||
.fdim-cells {
|
||||
min-width: 230px !Important;
|
||||
max-width: 230px !Important;
|
||||
cursor: pointer;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.fdim-cells:hover {
|
||||
/*cursor: default;*/
|
||||
background-color: #808080 !important;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
tbody tr:hover {
|
||||
cursor: default;
|
||||
background-color: #808080 !important;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.grid-cells-header {
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.grid-cells-title {
|
||||
min-width: 522px;
|
||||
}
|
||||
|
||||
.grid-cells:before {
|
||||
content: "\00a0";
|
||||
}
|
||||
tbody tr:hover td {
|
||||
cursor: default;
|
||||
background-color: #808080 !important;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.grid {
|
||||
height: 50px;
|
||||
width: 350px;
|
||||
height: 50px;
|
||||
width: 350px;
|
||||
}
|
||||
|
||||
.header-wrapper {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/*popups for headers*/
|
||||
.tooltip {
|
||||
position: fixed !important;
|
||||
color: RGB(70,70,70);
|
||||
background-color: RGB(245,239,207);
|
||||
text-align: center;
|
||||
border: groove;
|
||||
position: fixed !important;
|
||||
color: rgb(70, 70, 70);
|
||||
background-color: rgb(245, 239, 207);
|
||||
text-align: center;
|
||||
border: groove;
|
||||
}
|
||||
|
||||
/*end popups*/
|
||||
.row-wrapper {
|
||||
position: absolute;
|
||||
top: 97px;
|
||||
height: calc(~"100% - 97px");
|
||||
overflow-x: hidden;
|
||||
overflow-y: scroll;
|
||||
padding: 0;
|
||||
margin-top: 0;
|
||||
.root {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.kpi-table .fdim-cells, .data-table td {
|
||||
line-height: 1em!important;
|
||||
}
|
||||
|
||||
.data-table .fdim-cells {
|
||||
display: none;
|
||||
.kpi-table .fdim-cells,
|
||||
.data-table td {
|
||||
line-height: 1em !important;
|
||||
}
|
||||
|
||||
.kpi-table {
|
||||
width: @KpiTableWidth !important;
|
||||
overflow: hidden !important;
|
||||
display: table;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
z-index: 100;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
border-right: 1px solid white;
|
||||
box-shadow: 4px 2px 8px #e1e1e1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-wrap: nowrap;
|
||||
flex: none;
|
||||
width: @KpiTableWidth !important;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
.header-wrapper {
|
||||
flex: none;
|
||||
box-shadow: 4px 2px 8px #e1e1e1;
|
||||
}
|
||||
|
||||
.row-wrapper {
|
||||
overflow: scroll;
|
||||
margin: 0;
|
||||
margin-bottom: 8px;
|
||||
padding: 0;
|
||||
box-shadow: 4px 2px 8px #e1e1e1;
|
||||
min-height: 0; /* This is to make flex size-filling work */
|
||||
|
||||
/* Adapt for Edge */
|
||||
@supports (-ms-ime-align: auto) {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
/* Adapt for IE11 */
|
||||
@media screen and (-ms-high-contrast: none) {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.kpi-table .row-wrapper {
|
||||
overflow: hidden;
|
||||
.row-wrapper .fdim-cells {
|
||||
padding-left: 12px;
|
||||
}
|
||||
|
||||
.data-table {
|
||||
width: 272px !important;
|
||||
float: left;
|
||||
display: table;
|
||||
height: 100%;
|
||||
z-index: 90;
|
||||
position: absolute;
|
||||
margin-left: @KpiTableWidth + 13px;
|
||||
-ms-overflow-style: none;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-wrap: nowrap;
|
||||
margin-left: 13px;
|
||||
min-width: 0; /* This is to make flex size-filling work */
|
||||
|
||||
.header-wrapper {
|
||||
flex: none;
|
||||
overflow: scroll;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.row-wrapper {
|
||||
overflow: scroll;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
min-height: 0; /* This is to make flex size-filling work */
|
||||
|
||||
/* Style scrollbar for FF */
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: #d3d3d3 transparent;
|
||||
}
|
||||
|
||||
/* Adapt for Edge */
|
||||
@supports (-ms-ime-align: auto) {
|
||||
.header-wrapper {
|
||||
margin-right: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Adapt for IE11 */
|
||||
@media screen and (-ms-high-contrast: none) {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
.header-wrapper {
|
||||
margin-right: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// hide scrollbars
|
||||
.kpi-table .header-wrapper,
|
||||
.kpi-table .row-wrapper,
|
||||
.data-table .header-wrapper {
|
||||
// stylelint-disable-next-line property-no-unknown
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none; // IE 10+
|
||||
-moz-overflow: -moz-scrollbars-none; // Firefox
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none; // Safari and Chrome
|
||||
}
|
||||
}
|
||||
|
||||
.tooltip-wrapper {
|
||||
min-width: 25px;
|
||||
position: fixed;
|
||||
padding: 5px;
|
||||
padding-top: 8px;
|
||||
background-color: #404040;
|
||||
z-index: 100;
|
||||
pointer-events: none;
|
||||
border-radius: 5px;
|
||||
height: 30px;
|
||||
width: auto;
|
||||
opacity: 0.9;
|
||||
text-align: center;
|
||||
transform: translate(-50%, -110%);
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: -10px;
|
||||
left: 50%;
|
||||
border-width: 10px 10px 0;
|
||||
border-style: solid;
|
||||
border-color: #404040 transparent;
|
||||
margin-left: -10px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
> p {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* eslint-enable */
|
||||
|
||||
131
src/masking.js
131
src/masking.js
@@ -1,131 +0,0 @@
|
||||
import { addSeparators } from './utilities';
|
||||
|
||||
export function ApplyPreMask (mask, value) { // aqui
|
||||
if (mask.indexOf(';') >= 0) {
|
||||
if (value >= 0) {
|
||||
switch (mask.substring(0, mask.indexOf(';'))) {
|
||||
case '#.##0':
|
||||
return (addSeparators(value, '.', ',', 0));
|
||||
case '#,##0':
|
||||
return (addSeparators(value, ',', '.', 0));
|
||||
case '+#.##0':
|
||||
return (addSeparators(value, '.', ',', 0));
|
||||
case '+#,##0':
|
||||
return (addSeparators(value, ',', '.', 0));
|
||||
default:
|
||||
return (ApplyMask(mask.substring(0, mask.indexOf(';')), value));
|
||||
}
|
||||
} else {
|
||||
const vMyValue = value * -1;
|
||||
let vMyMask = mask.substring(mask.indexOf(';') + 1, mask.length);
|
||||
vMyMask = vMyMask.replace('(', '');
|
||||
vMyMask = vMyMask.replace(')', '');
|
||||
switch (vMyMask) {
|
||||
case '#.##0':
|
||||
return (`(${addSeparators(vMyValue, '.', ',', 0)})`);
|
||||
case '#,##0':
|
||||
return (`(${addSeparators(vMyValue, ',', '.', 0)})`);
|
||||
case '-#.##0':
|
||||
return (`(${addSeparators(vMyValue, '.', ',', 0)})`);
|
||||
case '-#,##0':
|
||||
return (`(${addSeparators(vMyValue, ',', '.', 0)})`);
|
||||
default:
|
||||
return (`(${ApplyMask(vMyMask, vMyValue)})`);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return (ApplyMask(mask, value));
|
||||
}
|
||||
}
|
||||
|
||||
function ApplyMask (mask, value) {
|
||||
if (!mask || isNaN(Number(value))) {
|
||||
return value; // return as it is.
|
||||
}
|
||||
|
||||
let isNegative, result, decimal, group, posLeadZero, posTrailZero, posSeparator,
|
||||
part, szSep, integer,
|
||||
|
||||
// find prefix/suffix
|
||||
len = mask.length,
|
||||
start = mask.search(/[0-9\-\+#]/),
|
||||
prefix = start > 0 ? mask.substring(0, start) : '',
|
||||
// reverse string: not an ideal method if there are surrogate pairs
|
||||
str = mask.split('').reverse()
|
||||
.join(''),
|
||||
end = str.search(/[0-9\-\+#]/),
|
||||
offset = len - end,
|
||||
substr = mask.substring(offset, offset + 1),
|
||||
indx = offset + ((substr === '.' || (substr === ',')) ? 1 : 0),
|
||||
suffix = end > 0 ? mask.substring(indx, len) : '';
|
||||
|
||||
// mask with prefix & suffix removed
|
||||
mask = mask.substring(start, indx);
|
||||
|
||||
// convert any string to number according to formation sign.
|
||||
value = mask.charAt(0) === '-' ? -value : Number(value);
|
||||
isNegative = value < 0 ? value = -value : 0; // process only abs(), and turn on flag.
|
||||
|
||||
// search for separator for grp & decimal, anything not digit, not +/- sign, not #.
|
||||
result = mask.match(/[^\d\-\+#]/g);
|
||||
decimal = (result && result[result.length - 1]) || '.'; // treat the right most symbol as decimal
|
||||
group = (result && result[1] && result[0]) || ','; // treat the left most symbol as group separator
|
||||
|
||||
// split the decimal for the format string if any.
|
||||
mask = mask.split(decimal);
|
||||
// Fix the decimal first, toFixed will auto fill trailing zero.
|
||||
value = value.toFixed(mask[1] && mask[1].length);
|
||||
value = String(Number(value)); // convert number to string to trim off *all* trailing decimal zero(es)
|
||||
|
||||
// fill back any trailing zero according to format
|
||||
posTrailZero = mask[1] && mask[1].lastIndexOf('0'); // look for last zero in format
|
||||
part = value.split('.');
|
||||
// integer will get !part[1]
|
||||
if (!part[1] || (part[1] && part[1].length <= posTrailZero)) {
|
||||
value = (Number(value)).toFixed(posTrailZero + 1);
|
||||
}
|
||||
szSep = mask[0].split(group); // look for separator
|
||||
mask[0] = szSep.join(''); // join back without separator for counting the pos of any leading 0.
|
||||
|
||||
posLeadZero = mask[0] && mask[0].indexOf('0');
|
||||
if (posLeadZero > -1) {
|
||||
while (part[0].length < (mask[0].length - posLeadZero)) {
|
||||
part[0] = `0${part[0]}`;
|
||||
}
|
||||
} else if (Number(part[0]) === 0) {
|
||||
part[0] = '';
|
||||
}
|
||||
|
||||
value = value.split('.');
|
||||
value[0] = part[0];
|
||||
|
||||
// process the first group separator from decimal (.) only, the rest ignore.
|
||||
// get the length of the last slice of split result.
|
||||
posSeparator = (szSep[1] && szSep[szSep.length - 1].length);
|
||||
if (posSeparator) {
|
||||
integer = value[0];
|
||||
str = '';
|
||||
offset = integer.length % posSeparator;
|
||||
len = integer.length;
|
||||
for (indx = 0; indx < len; indx++) {
|
||||
str += integer.charAt(indx); // ie6 only support charAt for sz.
|
||||
// -posSeparator so that won't trail separator on full length
|
||||
// jshint -W018
|
||||
if (!((indx - offset + 1) % posSeparator) && indx < len - posSeparator) {
|
||||
str += group;
|
||||
}
|
||||
}
|
||||
value[0] = str;
|
||||
}
|
||||
value[1] = (mask[1] && value[1]) ? decimal + value[1] : '';
|
||||
|
||||
// remove negative sign if result is zero
|
||||
result = value.join('');
|
||||
if (result === '0' || result === '') {
|
||||
// remove negative sign if result is zero
|
||||
isNegative = false;
|
||||
}
|
||||
|
||||
// put back any negation, combine integer and fraction, and add back prefix & suffix
|
||||
return prefix + ((isNegative ? '-' : '') + result) + suffix;
|
||||
}
|
||||
210
src/paint.js
210
src/paint.js
@@ -1,210 +0,0 @@
|
||||
import $ from 'jquery';
|
||||
import { enableExcelExport } from './excel-export';
|
||||
import HeaderWrapper from './header-wrapper.jsx';
|
||||
import RowWrapper, { prepareProps } from './row-wrapper.jsx';
|
||||
import initializeStore from './store';
|
||||
import React from 'react';
|
||||
// import ReactDOM from 'react-dom';
|
||||
import { renderToStaticMarkup } from 'react-dom/server';
|
||||
|
||||
export default async function paint ($element, layout, component) {
|
||||
const state = initializeStore({
|
||||
$element,
|
||||
layout,
|
||||
component
|
||||
});
|
||||
const {
|
||||
ArrayGetSelectedCount,
|
||||
vNumDims,
|
||||
ConceptMatrixColElem,
|
||||
ConceptMatrixColElemTable,
|
||||
ConceptMatrixRowElem,
|
||||
vSeparatorCols
|
||||
} = state.properties;
|
||||
|
||||
const rowWrapperProps = await prepareProps({
|
||||
state: {
|
||||
layout,
|
||||
colors: state.colors,
|
||||
...state.properties
|
||||
}
|
||||
});
|
||||
|
||||
const jsx = (
|
||||
<React.Fragment>
|
||||
<div className="kpi-table">
|
||||
<HeaderWrapper
|
||||
dimensionInfos={state.dimensionInfos}
|
||||
measureInfos={state.measureInfos}
|
||||
{...state.properties}
|
||||
/>
|
||||
<RowWrapper
|
||||
colors={state.colors}
|
||||
layout={layout}
|
||||
{...state.properties}
|
||||
{...rowWrapperProps}
|
||||
/>
|
||||
</div>
|
||||
<div className="data-table">
|
||||
<HeaderWrapper
|
||||
dimensionInfos={state.dimensionInfos}
|
||||
measureInfos={state.measureInfos}
|
||||
{...state.properties}
|
||||
/>
|
||||
<RowWrapper
|
||||
colors={state.colors}
|
||||
layout={layout}
|
||||
{...state.properties}
|
||||
{...rowWrapperProps}
|
||||
/>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
|
||||
// TODO: switch to render when jquery interaction stuff in renderData is gone
|
||||
// ReactDOM.render(jsx, $element[0]);
|
||||
|
||||
const html = renderToStaticMarkup(jsx);
|
||||
|
||||
$element.html(html);
|
||||
|
||||
RenderData();
|
||||
|
||||
// TODO: move jquery interactions into their respective components
|
||||
// Hook up interactions and some html mangling
|
||||
function RenderData () {
|
||||
$('.data-table .row-wrapper').on('scroll', function () {
|
||||
$(`[tid="${layout.qInfo.qId}"] .kpi-table .row-wrapper`).scrollTop($(this).scrollTop());
|
||||
});
|
||||
|
||||
// on hover popup with cell value, only in headers
|
||||
$('.header-wrapper th').hover(function () {
|
||||
$('.tooltip').delay(500)
|
||||
.show(0);
|
||||
$('.header-wrapper th').children('.tooltip')
|
||||
.remove();
|
||||
|
||||
const element = $(this);
|
||||
const offset = element.offset();
|
||||
const toolTip = $("<div class='tooltip'></div>");
|
||||
|
||||
toolTip.css({
|
||||
top: offset.top,
|
||||
left: offset.left
|
||||
});
|
||||
|
||||
toolTip.text(element.text());
|
||||
$('.header-wrapper th').append(toolTip);
|
||||
}, () => {
|
||||
$('.tooltip').delay(0)
|
||||
.hide(0);
|
||||
});
|
||||
|
||||
// allow making selections inside the table
|
||||
$('.data-table td').on('click', function () {
|
||||
if (layout.filteroncellclick == false) {
|
||||
return;
|
||||
}
|
||||
const indextr = $(this).parent()
|
||||
.parent()
|
||||
.children()
|
||||
.index($(this).parent()); // identifica la row
|
||||
const indextd = $(this).parent()
|
||||
.children()
|
||||
.index($(this)); // identifica la col
|
||||
|
||||
let SelectRow = 0;
|
||||
let SelectCol = 0;
|
||||
|
||||
SelectRow = ConceptMatrixRowElem[(indextr)];
|
||||
|
||||
// este if verifica primero si hay selecciones hechas en la dimensión, si las hay
|
||||
// las reselecciona para poder borrar antes de poder seleccionar lo que quiero
|
||||
// no es viable pedirle que seleccione a la vez elementos de 2 selecciones, se queda
|
||||
// colgado el menú de confirm, por eso uso este sistema, que sí funciona.
|
||||
// it can cause issues like error messages and wrong selections if there are null values
|
||||
// and the check allow null values is active
|
||||
if (vNumDims > 1 && indextd > 0) {
|
||||
if (ArrayGetSelectedCount[1] > 0) {
|
||||
const SelectB = JSON.parse(JSON.stringify(ConceptMatrixColElemTable));
|
||||
component.backendApi.selectValues(1, SelectB, true);
|
||||
$(this).toggleClass('selected');
|
||||
}
|
||||
SelectCol = ConceptMatrixColElemTable[(indextd)];
|
||||
|
||||
component.backendApi.selectValues(1, [SelectCol], true);
|
||||
$(this).toggleClass('selected');
|
||||
}
|
||||
|
||||
if (indextd > 0 && ArrayGetSelectedCount[0] > 0) {
|
||||
const SelectA = JSON.parse(JSON.stringify(ConceptMatrixRowElem));
|
||||
component.backendApi.selectValues(0, SelectA, true);
|
||||
$(this).toggleClass('selected');
|
||||
}
|
||||
|
||||
if (indextd > 0) {
|
||||
component.backendApi.selectValues(0, [SelectRow], true);
|
||||
$(this).toggleClass('selected');
|
||||
}
|
||||
});
|
||||
// allow selections through the header of the second dimension
|
||||
$('.header-wrapper th').on('click', function () {
|
||||
const indextd = $(this).parent()
|
||||
.children()
|
||||
.index($(this)); // identifica la col
|
||||
|
||||
let SelectCol = 0;
|
||||
|
||||
if (vNumDims > 1 && indextd > 0) {
|
||||
if (ArrayGetSelectedCount[1] > 0) {
|
||||
const SelectB = JSON.parse(JSON.stringify(ConceptMatrixColElem));
|
||||
component.backendApi.selectValues(1, SelectB, true);
|
||||
$(this).toggleClass('selected');
|
||||
}
|
||||
if (vSeparatorCols) {
|
||||
SelectCol = ConceptMatrixColElem[(Math.round(indextd / 2) - 1)];
|
||||
} else {
|
||||
SelectCol = ConceptMatrixColElem[(Math.round(indextd) - 1)];
|
||||
}
|
||||
|
||||
component.backendApi.selectValues(1, [SelectCol], true);
|
||||
$(this).toggleClass('selected');
|
||||
}
|
||||
});
|
||||
// allow selections in desc dimension cells
|
||||
$('.kpi-table td').on('click', function () {
|
||||
const indextr = $(this).parent()
|
||||
.parent()
|
||||
.children()
|
||||
.index($(this).parent()); // identifica la row
|
||||
let SelectRow = 0;
|
||||
SelectRow = ConceptMatrixRowElem[(indextr)];
|
||||
|
||||
if (ArrayGetSelectedCount[0] > 0) {
|
||||
const SelectA = JSON.parse(JSON.stringify(ConceptMatrixRowElem));
|
||||
component.backendApi.selectValues(0, SelectA, true);
|
||||
$(this).toggleClass('selected');
|
||||
}
|
||||
|
||||
component.backendApi.selectValues(0, [SelectRow], true);
|
||||
$(this).toggleClass('selected');
|
||||
});
|
||||
|
||||
enableExcelExport(layout, html);
|
||||
|
||||
// freeze first column
|
||||
$('.qv-object-content-container').on('scroll', (t) => {
|
||||
$('.kpi-table').css('left', `${Math.round(t.target.scrollLeft)}px`);
|
||||
});
|
||||
$('.kpi-table .row-wrapper tr').each(function () {
|
||||
$(this).find('th:not(.fdim-cells)')
|
||||
.remove();
|
||||
$(this).find('td:not(.fdim-cells)')
|
||||
.remove();
|
||||
});
|
||||
$('.kpi-table .header-wrapper tr').each(function () {
|
||||
$(this).find('th:not(.fdim-cells)')
|
||||
.remove();
|
||||
});
|
||||
}
|
||||
}
|
||||
5
src/root.componentModel.js
Normal file
5
src/root.componentModel.js
Normal file
@@ -0,0 +1,5 @@
|
||||
function Model (component) {
|
||||
this.component = component;
|
||||
}
|
||||
|
||||
export default Model;
|
||||
132
src/root.jsx
Normal file
132
src/root.jsx
Normal file
@@ -0,0 +1,132 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import HeadersTable from './headers-table/index.jsx';
|
||||
import DataTable from './data-table/index.jsx';
|
||||
import { LinkedScrollWrapper, LinkedScrollSection } from './linked-scroll';
|
||||
|
||||
class Root extends React.PureComponent {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
this.onDataTableRefSet = this.onDataTableRefSet.bind(this);
|
||||
this.renderedTableWidth = 0;
|
||||
}
|
||||
|
||||
componentDidUpdate () {
|
||||
const tableWidth = this.dataTableRef.getBoundingClientRect().width;
|
||||
if (this.renderedTableWidth !== tableWidth) {
|
||||
this.forceUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
onDataTableRefSet (element) {
|
||||
this.dataTableRef = element;
|
||||
this.forceUpdate();
|
||||
}
|
||||
|
||||
render () {
|
||||
const { editmodeClass, component, state } = this.props;
|
||||
const { data, general, styling } = state;
|
||||
|
||||
// Determine cell- and column separator width
|
||||
let cellWidth = '0px';
|
||||
let columnSeparatorWidth = '';
|
||||
if (this.dataTableRef) {
|
||||
const tableWidth = this.dataTableRef.getBoundingClientRect().width;
|
||||
this.renderedTableWidth = tableWidth;
|
||||
|
||||
if (general.cellWidth) {
|
||||
cellWidth = general.cellWidth;
|
||||
if (general.useColumnSeparator) {
|
||||
columnSeparatorWidth = '8px';
|
||||
}
|
||||
} else {
|
||||
const headerMarginRight = 8;
|
||||
const borderWidth = 1;
|
||||
const rowCellCount = data.matrix[0].length;
|
||||
|
||||
let separatorCount = 0;
|
||||
let separatorWidth = 0;
|
||||
if (general.useColumnSeparator) {
|
||||
separatorCount = data.headers.dimension2.length - 1;
|
||||
separatorWidth = Math.min(Math.floor(tableWidth * 0.2 / separatorCount), 8);
|
||||
columnSeparatorWidth = `${separatorWidth}px`;
|
||||
}
|
||||
|
||||
const separatorWidthSum = (separatorWidth + borderWidth) * separatorCount;
|
||||
cellWidth = `${Math.floor((tableWidth - separatorWidthSum - headerMarginRight - borderWidth)
|
||||
/ rowCellCount) - borderWidth}px`;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="root">
|
||||
<LinkedScrollWrapper>
|
||||
<div className={`kpi-table ${editmodeClass}`}>
|
||||
<HeadersTable
|
||||
cellWidth={cellWidth}
|
||||
columnSeparatorWidth={columnSeparatorWidth}
|
||||
component={component}
|
||||
data={data}
|
||||
general={general}
|
||||
isKpi
|
||||
styling={styling}
|
||||
/>
|
||||
<LinkedScrollSection linkVertical>
|
||||
<DataTable
|
||||
cellWidth={cellWidth}
|
||||
columnSeparatorWidth={columnSeparatorWidth}
|
||||
component={component}
|
||||
data={data}
|
||||
general={general}
|
||||
renderData={false}
|
||||
styling={styling}
|
||||
/>
|
||||
</LinkedScrollSection>
|
||||
</div>
|
||||
<div
|
||||
className={`data-table ${editmodeClass}`}
|
||||
style={{ width: general.cellWidth ? 'auto' : '100%' }}
|
||||
ref={this.onDataTableRefSet}
|
||||
>
|
||||
<LinkedScrollSection linkHorizontal>
|
||||
<HeadersTable
|
||||
cellWidth={cellWidth}
|
||||
columnSeparatorWidth={columnSeparatorWidth}
|
||||
component={component}
|
||||
data={data}
|
||||
general={general}
|
||||
isKpi={false}
|
||||
styling={styling}
|
||||
/>
|
||||
</LinkedScrollSection>
|
||||
<LinkedScrollSection
|
||||
linkHorizontal
|
||||
linkVertical
|
||||
>
|
||||
<DataTable
|
||||
cellWidth={cellWidth}
|
||||
columnSeparatorWidth={columnSeparatorWidth}
|
||||
component={component}
|
||||
data={data}
|
||||
general={general}
|
||||
styling={styling}
|
||||
/>
|
||||
</LinkedScrollSection>
|
||||
</div>
|
||||
</LinkedScrollWrapper>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Root.propTypes = {
|
||||
component: PropTypes.shape({}).isRequired,
|
||||
editmodeClass: PropTypes.string.isRequired,
|
||||
state: PropTypes.shape({
|
||||
data: PropTypes.object.isRequired,
|
||||
general: PropTypes.object.isRequired,
|
||||
styling: PropTypes.object.isRequired
|
||||
}).isRequired
|
||||
};
|
||||
|
||||
export default Root;
|
||||
29
src/root.spec.js
Normal file
29
src/root.spec.js
Normal file
@@ -0,0 +1,29 @@
|
||||
import merge from 'lodash.merge';
|
||||
import Model from './root.componentModel';
|
||||
import Component from './root.jsx';
|
||||
import { mountedComponent } from 'test-utilities';
|
||||
import sampleState from 'test-utilities/capex-sample-state';
|
||||
|
||||
describe('<Root />', () => {
|
||||
const state = sampleState;
|
||||
const defaultProps = {
|
||||
qlik: {
|
||||
backendApi: {
|
||||
selectValues: () => {}
|
||||
},
|
||||
inEditState: () => {}
|
||||
},
|
||||
state
|
||||
};
|
||||
|
||||
function setup (otherProps = {}) {
|
||||
const props = merge(defaultProps, otherProps);
|
||||
|
||||
return mountedComponent(Model, Component, props);
|
||||
}
|
||||
|
||||
it('should render without exploding', () => {
|
||||
const model = setup();
|
||||
expect(model.component).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -1,81 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import StyleBuilder from './style-builder';
|
||||
|
||||
class RowList extends React.PureComponent {
|
||||
generatePaddingTextElement (hasCustomFileStyle) {
|
||||
const { vPadding, vFontFamily } = this.props;
|
||||
if (vPadding && !hasCustomFileStyle) {
|
||||
const paddingStyle = {
|
||||
fontFamily: vFontFamily,
|
||||
marginLeft: '15px'
|
||||
};
|
||||
return (
|
||||
<span style={paddingStyle} />
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
render () {
|
||||
const {
|
||||
vLetterSize,
|
||||
vCustomFileBool,
|
||||
vFontFamily,
|
||||
tableData,
|
||||
MeasurementsComponent
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<tbody>
|
||||
{tableData.map((row, rowNumber) => {
|
||||
const rowHeaderText = row[0] || '';
|
||||
if (rowHeaderText === '-') {
|
||||
return null;
|
||||
}
|
||||
const styleBuilder = new StyleBuilder(this.props);
|
||||
if (vCustomFileBool) {
|
||||
styleBuilder.parseCustomFileStyle(rowHeaderText);
|
||||
} else {
|
||||
styleBuilder.applyStandardAttributes(rowNumber);
|
||||
styleBuilder.applyCustomStyle({ fontSize: (14 + vLetterSize) + 'px' });
|
||||
}
|
||||
|
||||
const rowStyle = {
|
||||
fontFamily: vFontFamily,
|
||||
width: '230px',
|
||||
...styleBuilder.getStyle()
|
||||
};
|
||||
const paddingTextElement = this.generatePaddingTextElement(styleBuilder.hasCustomFileStyle());
|
||||
const measurementsProps = {
|
||||
rowHeaderText,
|
||||
rowNumber,
|
||||
styleBuilder
|
||||
};
|
||||
return (
|
||||
<tr key={rowNumber}>
|
||||
<td
|
||||
className="fdim-cells"
|
||||
style={rowStyle}
|
||||
>
|
||||
{paddingTextElement}
|
||||
{rowHeaderText}
|
||||
</td>
|
||||
<MeasurementsComponent
|
||||
columnText={rowHeaderText}
|
||||
{...this.props}
|
||||
{...measurementsProps}
|
||||
/>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
RowList.propTypes = {
|
||||
tableData: PropTypes.array.isRequired
|
||||
};
|
||||
|
||||
export default RowList;
|
||||
@@ -1,129 +0,0 @@
|
||||
import $ from 'jquery';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
|
||||
import ElseDimensionMeasures from './else-dimension-measures.jsx';
|
||||
import RowList from './row-list.jsx';
|
||||
import SingleDimensionMeasures from './single-dimension-measures.jsx';
|
||||
|
||||
const RowWrapper = props => {
|
||||
const {
|
||||
ConceptMatrix,
|
||||
ConceptMatrixPivot,
|
||||
vNumDims
|
||||
} = props;
|
||||
let MeasurementsComponent,
|
||||
tableData;
|
||||
if (vNumDims === 1) {
|
||||
tableData = ConceptMatrix;
|
||||
MeasurementsComponent = SingleDimensionMeasures;
|
||||
} else {
|
||||
tableData = ConceptMatrixPivot.filter(array => array.length);
|
||||
MeasurementsComponent = ElseDimensionMeasures;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="row-wrapper">
|
||||
<table>
|
||||
<RowList
|
||||
MeasurementsComponent={MeasurementsComponent}
|
||||
tableData={tableData}
|
||||
{...props}
|
||||
/>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
RowWrapper.propTypes = {
|
||||
ConceptMatrix: PropTypes.array.isRequired,
|
||||
ConceptMatrixPivot: PropTypes.array.isRequired
|
||||
};
|
||||
|
||||
export default RowWrapper;
|
||||
|
||||
export async function prepareProps ({ state }) {
|
||||
const { colors, layout, vAllSemaphores, vDynamicColorBody, vDynamicColorBodyP } = state;
|
||||
const props = {
|
||||
colors,
|
||||
vCustomFileBool: layout.customfilebool,
|
||||
vCustomFile: layout.customfile,
|
||||
vPadding: layout.indentbool,
|
||||
vPaddingText: '',
|
||||
vGlobalComas: 0,
|
||||
vGlobalComas2: 0,
|
||||
vGlobalComment: 0,
|
||||
vGlobalCommentColor: '',
|
||||
vGlobalFontSize: 0,
|
||||
vComas: 0,
|
||||
vMedium: false,
|
||||
vFontSize: '',
|
||||
vColorText: layout.BodyTextColorSchema,
|
||||
vDivide: 1,
|
||||
vSymbolForNulls: layout.symbolfornulls,
|
||||
vDynamicColorBody: 'vColLib' + layout.ColorSchema,
|
||||
vDynamicColorBodyP: 'vColLib' + layout.ColorSchema + 'P',
|
||||
vAllMetrics: layout.allmetrics,
|
||||
MetricsAffectedMatrix: JSON.parse('[' + layout.metricssemaphore + ']'),
|
||||
vColorMetric1: layout.colorstatus1.color,
|
||||
vColorMetric2: layout.colorstatus2.color,
|
||||
vColorMetric3: layout.colorstatus3.color,
|
||||
vColorMetric1Text: layout.colorstatus1text.color,
|
||||
vColorMetric2Text: layout.colorstatus2text.color,
|
||||
vColorMetric3Text: layout.colorstatus3text.color,
|
||||
vColorSemaphore: '',
|
||||
vColorSemaphoreText: '',
|
||||
vCritic: layout.metricsstatus1,
|
||||
vMMedium: layout.metricsstatus2,
|
||||
CustomArray: new Array(),
|
||||
CustomArrayBasic: new Array(),
|
||||
vNumCustomHeaders: 0,
|
||||
vColumnText: '',
|
||||
vColumnNum: '',
|
||||
vMaskNum: 0,
|
||||
StyleTags: '',
|
||||
vColorSchema: colors[vDynamicColorBody],
|
||||
vColorSchemaP: colors[vDynamicColorBodyP],
|
||||
vAllSemaphores: layout.allsemaphores,
|
||||
ConceptsAffectedMatrix: new Array(10)
|
||||
};
|
||||
if (vAllSemaphores == false) {
|
||||
props.ConceptsAffectedMatrix[0] = layout.conceptsemaphore1;
|
||||
props.ConceptsAffectedMatrix[1] = layout.conceptsemaphore2;
|
||||
props.ConceptsAffectedMatrix[2] = layout.conceptsemaphore3;
|
||||
props.ConceptsAffectedMatrix[3] = layout.conceptsemaphore4;
|
||||
props.ConceptsAffectedMatrix[4] = layout.conceptsemaphore5;
|
||||
props.ConceptsAffectedMatrix[5] = layout.conceptsemaphore6;
|
||||
props.ConceptsAffectedMatrix[6] = layout.conceptsemaphore7;
|
||||
props.ConceptsAffectedMatrix[7] = layout.conceptsemaphore8;
|
||||
props.ConceptsAffectedMatrix[8] = layout.conceptsemaphore9;
|
||||
props.ConceptsAffectedMatrix[9] = layout.conceptsemaphore10;
|
||||
}
|
||||
|
||||
function ReadCustomSchema () {
|
||||
var Url = '/Extensions/qlik-smart-pivot/' + props.vCustomFile;
|
||||
return $.get(Url).then(function (response) {
|
||||
var allTextLines = response.split(/\r\n|\n/);
|
||||
var headers = allTextLines[0].split(';');
|
||||
props.vNumCustomHeaders = headers.length;
|
||||
|
||||
for (var i = 0; i < allTextLines.length; i++) {
|
||||
props.CustomArray[i] = new Array(headers.length);
|
||||
var data = allTextLines[i].split(';');
|
||||
|
||||
if (data.length == headers.length) {
|
||||
for (var j = 0; j < headers.length; j++) {
|
||||
props.CustomArrayBasic[i] = data[0];
|
||||
props.CustomArray[i][j] = data[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const hasCustomSchema = (props.vCustomFileBool && props.vCustomFile.length > 4);
|
||||
const schemaPromise = hasCustomSchema ? ReadCustomSchema() : Promise.resolve();
|
||||
await schemaPromise;
|
||||
|
||||
return props;
|
||||
}
|
||||
@@ -1,159 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { ApplyPreMask } from './masking';
|
||||
import { addSeparators } from './utilities';
|
||||
|
||||
class SingleDimensionMeasures extends React.PureComponent {
|
||||
render () {
|
||||
const {
|
||||
vFontFamily,
|
||||
vSymbolForNulls,
|
||||
vColorMetric1,
|
||||
vColorMetric1Text,
|
||||
vColorMetric2,
|
||||
vColorMetric2Text,
|
||||
vColorMetric3,
|
||||
vColorMetric3Text,
|
||||
ConceptMatrix,
|
||||
vAllSemaphores,
|
||||
ConceptsAffectedMatrix,
|
||||
vAllMetrics,
|
||||
MetricsAffectedMatrix,
|
||||
vCritic,
|
||||
vMMedium,
|
||||
vNumMeasures,
|
||||
MeasuresFormat,
|
||||
rowNumber,
|
||||
columnText,
|
||||
styleBuilder
|
||||
} = this.props;
|
||||
|
||||
// modified in here
|
||||
let vColumnNum,
|
||||
vMaskNum,
|
||||
vColorSemaphore,
|
||||
vColorSemaphoreText,
|
||||
vDivide;
|
||||
|
||||
const measurementCells = [];
|
||||
|
||||
// TODO: map ConceptMatrix[rowNumber] into cells
|
||||
for (var nMeasures2 = 1; nMeasures2 <= vNumMeasures; nMeasures2++) {
|
||||
var vSpecialF = MeasuresFormat[nMeasures2 - 1].replace(/k|K|m|M/gi, '');
|
||||
if (columnText.substring(0, 1) == '%') {
|
||||
vColumnNum = ApplyPreMask('0,00%', ConceptMatrix[rowNumber][nMeasures2]);
|
||||
vSpecialF = '0,00%';
|
||||
} else {
|
||||
const magnitude = MeasuresFormat[nMeasures2 - 1].substr(MeasuresFormat[nMeasures2 - 1].length - 1);
|
||||
switch (magnitude.toLowerCase()) {
|
||||
case 'k':
|
||||
vDivide = 1000;
|
||||
break;
|
||||
|
||||
case 'm':
|
||||
vDivide = 1000000;
|
||||
break;
|
||||
|
||||
default:
|
||||
vDivide = 1;
|
||||
break;
|
||||
}
|
||||
if (!isNaN(ConceptMatrix[rowNumber][nMeasures2])) {
|
||||
vMaskNum = ConceptMatrix[rowNumber][nMeasures2];
|
||||
if (vSpecialF.substring(vSpecialF.length - 1) == '%') {
|
||||
vMaskNum = vMaskNum * 100;
|
||||
}
|
||||
switch (vSpecialF) {
|
||||
case '#.##0':
|
||||
vColumnNum = addSeparators((vMaskNum / vDivide), '.', ',', 0);
|
||||
break;
|
||||
|
||||
case '#,##0':
|
||||
vColumnNum = addSeparators((vMaskNum / vDivide), ',', '.', 0);
|
||||
break;
|
||||
|
||||
default:
|
||||
vColumnNum = ApplyPreMask(vSpecialF, (vMaskNum / vDivide));
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
vColumnNum = vSymbolForNulls;
|
||||
}
|
||||
}
|
||||
if (styleBuilder.hasComments()) {
|
||||
vColumnNum = '.';
|
||||
}
|
||||
// apply the semaphore styles where needed
|
||||
let cellStyle;
|
||||
if ((vAllSemaphores || ConceptsAffectedMatrix.indexOf(columnText) >= 0) && (vAllMetrics || MetricsAffectedMatrix.indexOf(nMeasures2) >= 0) && !isNaN(ConceptMatrix[rowNumber][nMeasures2]) && !styleBuilder.hasComments()) {
|
||||
if (ConceptMatrix[rowNumber][nMeasures2] < vCritic) {
|
||||
vColorSemaphore = vColorMetric1;
|
||||
vColorSemaphoreText = vColorMetric1Text;
|
||||
} else {
|
||||
if (ConceptMatrix[rowNumber][nMeasures2] < vMMedium) {
|
||||
vColorSemaphore = vColorMetric2;
|
||||
vColorSemaphoreText = vColorMetric2Text;
|
||||
} else {
|
||||
vColorSemaphore = vColorMetric3;
|
||||
vColorSemaphoreText = vColorMetric3Text;
|
||||
}
|
||||
}
|
||||
|
||||
cellStyle = {
|
||||
fontFamily: vFontFamily,
|
||||
fontSize: styleBuilder.getStyle().fontSize,
|
||||
color: vColorSemaphoreText,
|
||||
backgroundColor: vColorSemaphore,
|
||||
textAlign: 'right',
|
||||
paddingLeft: '4px'
|
||||
};
|
||||
} else {
|
||||
cellStyle = {
|
||||
fontFamily: vFontFamily,
|
||||
textAlign: 'right',
|
||||
paddingLeft: '4px',
|
||||
...styleBuilder.getStyle()
|
||||
};
|
||||
}
|
||||
|
||||
const measurementCell = (
|
||||
<td key={nMeasures2} className="grid-cells' + sufixCells + '" style={cellStyle}>
|
||||
{vColumnNum}
|
||||
</td>
|
||||
);
|
||||
|
||||
measurementCells.push(measurementCell);
|
||||
}
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{measurementCells}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SingleDimensionMeasures.propTypes = {
|
||||
vFontFamily: PropTypes.any,
|
||||
vSymbolForNulls: PropTypes.any,
|
||||
vColorMetric1: PropTypes.any,
|
||||
vColorMetric1Text: PropTypes.any,
|
||||
vColorMetric2: PropTypes.any,
|
||||
vColorMetric2Text: PropTypes.any,
|
||||
vColorMetric3: PropTypes.any,
|
||||
vColorMetric3Text: PropTypes.any,
|
||||
ConceptMatrix: PropTypes.any,
|
||||
vAllSemaphores: PropTypes.any,
|
||||
ConceptsAffectedMatrix: PropTypes.any,
|
||||
vAllMetrics: PropTypes.any,
|
||||
MetricsAffectedMatrix: PropTypes.any,
|
||||
vCritic: PropTypes.any,
|
||||
vMMedium: PropTypes.any,
|
||||
vNumMeasures: PropTypes.any,
|
||||
MeasuresFormat: PropTypes.any,
|
||||
rowNumber: PropTypes.any,
|
||||
columnText: PropTypes.any,
|
||||
styleBuilder: PropTypes.any
|
||||
};
|
||||
|
||||
export default SingleDimensionMeasures;
|
||||
302
src/store.js
302
src/store.js
@@ -1,299 +1,15 @@
|
||||
import { onlyUnique } from './utilities';
|
||||
import initializeTransformed from './initialize-transformed';
|
||||
|
||||
function initialize ({ $element, layout, component }) {
|
||||
const colors = {
|
||||
vColLibClean: layout.collibclean,
|
||||
vColLibSoft: layout.collibsoft,
|
||||
vColLibDark: layout.collibdark,
|
||||
vColLibNight: layout.collibnight,
|
||||
vColLibRed: layout.collibred,
|
||||
vColLibOrange: layout.colliborange,
|
||||
vColLibBlue: layout.collibblue,
|
||||
vColLibGreen: layout.collibgreen,
|
||||
vColLibViolete: layout.collibviolete,
|
||||
vColLibCustom: layout.collibcustom,
|
||||
vColLibCleanP: layout.collibcleanp,
|
||||
vColLibSoftP: layout.collibsoftp,
|
||||
vColLibDarkP: layout.collibdarkp,
|
||||
vColLibNightP: layout.collibnightp,
|
||||
vColLibRedP: layout.collibredp,
|
||||
vColLibOrangeP: layout.colliborangep,
|
||||
vColLibBlueP: layout.collibbluep,
|
||||
vColLibGreenP: layout.collibgreenp,
|
||||
vColLibVioleteP: layout.collibvioletep,
|
||||
vColLibCustomP: layout.collibcustomp
|
||||
};
|
||||
|
||||
const nMeasAux = 0;
|
||||
const vMaxLoops = layout.maxloops;
|
||||
const vErrorMessage = layout.errormessage;
|
||||
const vDynamicColorHeader = `vColLib${layout.HeaderColorSchema}`;
|
||||
const vHeaderColorSchema = colors[vDynamicColorHeader];
|
||||
const vExportToExcel = layout.allowexportxls;
|
||||
const vHeaderColorText = layout.HeaderTextColorSchema;
|
||||
const vHeaderAlign = layout.HeaderAlign;
|
||||
let vHeaderAlignText = '';
|
||||
switch (vHeaderAlign) {
|
||||
case 1:
|
||||
vHeaderAlignText = 'left';
|
||||
break;
|
||||
case 2:
|
||||
vHeaderAlignText = 'center';
|
||||
break;
|
||||
case 3:
|
||||
vHeaderAlignText = 'right';
|
||||
break;
|
||||
}
|
||||
let vLetterSizeHeader = 0;
|
||||
switch (layout.lettersizeheader) {
|
||||
case 1:
|
||||
vLetterSizeHeader = -2;
|
||||
break;
|
||||
case 2:
|
||||
vLetterSizeHeader = 0;
|
||||
break;
|
||||
case 3:
|
||||
vLetterSizeHeader = 2;
|
||||
break;
|
||||
}
|
||||
let vDimName = '';
|
||||
const ConceptMatrixFirst = new Array();
|
||||
const ConceptMatrixSecond = new Array();
|
||||
let SecondHeaderLength = 0;
|
||||
const LabelsArray = new Array();
|
||||
const ExtraLabelsArray = new Array();
|
||||
let vExtraLabel = '';
|
||||
const vExcelButtonCode = '';
|
||||
const ArrayGetSelectedCount = new Array();
|
||||
let vNumDims = 0;
|
||||
let vNumMeasures = 0;
|
||||
let vNumMeasures2 = 0;
|
||||
const MeasuresFormat = new Array();
|
||||
let sufixCells = '';
|
||||
switch (layout.columnwidthslider) {
|
||||
case 1:
|
||||
sufixCells += '-s';
|
||||
break;
|
||||
case 2:
|
||||
sufixCells = String(sufixCells);
|
||||
break;
|
||||
case 3:
|
||||
sufixCells += '-l';
|
||||
break;
|
||||
default:
|
||||
sufixCells = String(sufixCells);
|
||||
break;
|
||||
}
|
||||
const dim_count = layout.qHyperCube.qDimensionInfo.length;
|
||||
const measure_count = layout.qHyperCube.qMeasureInfo.length;
|
||||
let vSeparatorCols = layout.separatorcols;
|
||||
if (dim_count == 1) {
|
||||
vSeparatorCols = false;
|
||||
}
|
||||
const vFontFamily = layout.FontFamily;
|
||||
let lastrow = 0;
|
||||
const ConceptMatrix = new Array();
|
||||
let ConceptMatrixRowElem = new Array();
|
||||
let ConceptMatrixColElem = new Array();
|
||||
const ConceptMatrixColElemTable = new Array();
|
||||
const ConceptMatrixPivot = new Array();
|
||||
let ConceptMatrixFirstClean = new Array();
|
||||
let vLetterSize = 0;
|
||||
switch (layout.lettersize) {
|
||||
case 1:
|
||||
vLetterSize = -2;
|
||||
break;
|
||||
case 2:
|
||||
vLetterSize = -1;
|
||||
break;
|
||||
case 3:
|
||||
vLetterSize = 2;
|
||||
break;
|
||||
}
|
||||
const nRows = component.backendApi.getRowCount();
|
||||
|
||||
const dimensionInfos = component.backendApi.getDimensionInfos();
|
||||
LabelsArray.push(dimensionInfos[0].qFallbackTitle);
|
||||
ArrayGetSelectedCount.concat(dimensionInfos.map(dimensionInfo => dimensionInfo.qStateCounts.qSelected));
|
||||
vNumDims += dimensionInfos.length;
|
||||
|
||||
const measureInfos = component.backendApi.getMeasureInfos();
|
||||
measureInfos.forEach(measureInfo => {
|
||||
vDimName = measureInfo.qFallbackTitle;
|
||||
LabelsArray.push(vDimName);
|
||||
let mfor = '';
|
||||
|
||||
if (measureInfo.qNumFormat.qType == 'U' || measureInfo.qNumFormat.qFmt == '##############') {
|
||||
mfor = '#.##0'; // in case of undefined
|
||||
} else if (measureInfo.qNumFormat.qType == 'R') {
|
||||
mfor = measureInfo.qNumFormat.qFmt;
|
||||
mfor = mfor.replace(/(|)/gi, '');
|
||||
} else {
|
||||
mfor = measureInfo.qNumFormat.qFmt;
|
||||
}
|
||||
|
||||
MeasuresFormat.push(mfor);
|
||||
|
||||
switch (mfor.substr(mfor.length - 1).toLowerCase()) {
|
||||
case 'm':
|
||||
vExtraLabel = ' (M)';
|
||||
ExtraLabelsArray.push(' (M)');
|
||||
break;
|
||||
case 'k':
|
||||
vExtraLabel = ' (k)';
|
||||
ExtraLabelsArray.push(' (k)');
|
||||
break;
|
||||
default:
|
||||
vExtraLabel = '';
|
||||
ExtraLabelsArray.push('');
|
||||
break;
|
||||
}
|
||||
vNumMeasures++;
|
||||
async function initialize ({ $element, layout, component, dataCube, designList }) {
|
||||
const transformedProperties = await initializeTransformed({
|
||||
$element,
|
||||
component,
|
||||
dataCube,
|
||||
designList,
|
||||
layout
|
||||
});
|
||||
|
||||
component.backendApi.eachDataRow((t, a) => {
|
||||
lastrow = t;
|
||||
|
||||
const vNumMeasuresPlus = vNumMeasures + 1;
|
||||
|
||||
ConceptMatrix[t] = new Array();
|
||||
ConceptMatrix[t][0] = a[0].qText;
|
||||
|
||||
ConceptMatrixFirst[t] = a[0].qText;
|
||||
ConceptMatrixRowElem[t] = a[0].qElemNumber;
|
||||
let nMeasures = 0;
|
||||
if (vNumDims == 1) {
|
||||
for (nMeasures = 1; nMeasures <= vNumMeasures; nMeasures++) {
|
||||
ConceptMatrix[t][nMeasures] = a[nMeasures].qNum;
|
||||
}
|
||||
} else {
|
||||
ConceptMatrix[t][1] = a[1].qText;
|
||||
ConceptMatrixColElem[t] = a[1].qElemNumber;
|
||||
ConceptMatrixSecond[t] = a[1].qText;
|
||||
// set the hipercube in a plain array without pivoting
|
||||
for (nMeasures = 2; nMeasures <= vNumMeasuresPlus; nMeasures++) {
|
||||
ConceptMatrix[t][nMeasures] = a[nMeasures].qNum;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ConceptMatrixFirstClean = ConceptMatrixFirst.filter(onlyUnique);
|
||||
|
||||
if (nRows >= (vMaxLoops * 1000)) {
|
||||
alert(vErrorMessage);
|
||||
}
|
||||
|
||||
if (vNumDims == 2) {
|
||||
// new array with unique values for 2nd dim
|
||||
var SecondHeader = ConceptMatrixSecond.filter(onlyUnique);// second dimension concepts
|
||||
ConceptMatrixRowElem = ConceptMatrixRowElem.filter(onlyUnique);// first dimension concepts
|
||||
ConceptMatrixColElem = ConceptMatrixColElem.filter(onlyUnique);// dimension code for further selections
|
||||
const eo = ConceptMatrixColElem.length;
|
||||
let vLoopColsMeasures = 1;
|
||||
ConceptMatrixColElemTable[0] = ConceptMatrixColElem[0];
|
||||
for (let xx = 0; xx < eo; xx++) {
|
||||
if (vSeparatorCols && xx > 0) {
|
||||
ConceptMatrixColElemTable[vLoopColsMeasures] = ConceptMatrixColElem[xx];
|
||||
vLoopColsMeasures++;
|
||||
}
|
||||
|
||||
for (let xxx = 0; xxx < vNumMeasures; xxx++) {
|
||||
ConceptMatrixColElemTable[vLoopColsMeasures] = ConceptMatrixColElem[xx];
|
||||
vLoopColsMeasures++;
|
||||
}
|
||||
}
|
||||
|
||||
SecondHeaderLength = SecondHeader.length;
|
||||
vNumMeasures2 = vNumMeasures * SecondHeaderLength;
|
||||
|
||||
let ConceptPos = 0;
|
||||
let nMeas3 = 0;
|
||||
let vHeaderIndex = 0;
|
||||
let MeasurePos = 0;
|
||||
for (let nPivotElems = 0; nPivotElems <= lastrow; nPivotElems++) {
|
||||
ConceptMatrixPivot[nPivotElems] = new Array();
|
||||
ConceptPos = ConceptMatrixFirstClean.indexOf(ConceptMatrix[nPivotElems][0]);
|
||||
ConceptMatrixPivot[ConceptPos][0] = ConceptMatrix[nPivotElems][0];
|
||||
|
||||
for (let nMeas2 = 1; nMeas2 <= measure_count; nMeas2++) {
|
||||
nMeas3 = nMeas2 + 1;
|
||||
vHeaderIndex = (SecondHeader.indexOf(ConceptMatrix[nPivotElems][1]) + 1);
|
||||
MeasurePos = (vHeaderIndex * measure_count) + (nMeas2 - measure_count);
|
||||
ConceptMatrixPivot[ConceptPos][MeasurePos] = ConceptMatrix[nPivotElems][nMeas3];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (nRows > (lastrow + 1) && nRows <= (vMaxLoops * 1000)) {
|
||||
const requestPage = [
|
||||
{
|
||||
qTop: lastrow + 1,
|
||||
qLeft: 0,
|
||||
qWidth: 10, // should be # of columns
|
||||
qHeight: Math.min(1000, nRows - lastrow)
|
||||
}
|
||||
];
|
||||
component.backendApi.getData(requestPage).then(() => {
|
||||
component.paint($element);
|
||||
});
|
||||
}
|
||||
|
||||
const properties = {
|
||||
ArrayGetSelectedCount,
|
||||
ConceptMatrix,
|
||||
ConceptMatrixColElem,
|
||||
ConceptMatrixColElemTable,
|
||||
ConceptMatrixRowElem,
|
||||
ConceptMatrixFirstClean,
|
||||
ConceptMatrixPivot,
|
||||
vHeaderColorText,
|
||||
vFontFamily,
|
||||
vHeaderColorSchema,
|
||||
vExportToExcel,
|
||||
vNumDims,
|
||||
nMeasAux,
|
||||
dimensionInfos,
|
||||
vLetterSizeHeader,
|
||||
vHeaderAlignText,
|
||||
MeasuresFormat,
|
||||
measure_count,
|
||||
vExcelButtonCode,
|
||||
sufixCells,
|
||||
LabelsArray,
|
||||
SecondHeader,
|
||||
vSeparatorCols,
|
||||
nSecond: SecondHeaderLength - 1,
|
||||
nSecond2: SecondHeaderLength - 1,
|
||||
vLetterSize,
|
||||
ExtraLabelsArray,
|
||||
dim_count,
|
||||
vExtraLabel,
|
||||
vNumMeasures,
|
||||
vNumMeasures2,
|
||||
lastrow,
|
||||
measureInfos
|
||||
};
|
||||
|
||||
// TODO: figure out a reasonable datastructure and use these for component
|
||||
const transformedProperties = {
|
||||
dimensions: [],
|
||||
headers: LabelsArray,
|
||||
headerOptions: {
|
||||
colorSchema: vHeaderColorSchema,
|
||||
textColor: vHeaderColorText
|
||||
},
|
||||
options: {
|
||||
fontFamily: vFontFamily
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
colors,
|
||||
dimensionInfos,
|
||||
measureInfos,
|
||||
properties,
|
||||
transformedProperties
|
||||
};
|
||||
return transformedProperties;
|
||||
}
|
||||
|
||||
export default initialize;
|
||||
|
||||
@@ -1,26 +1,24 @@
|
||||
function StyleBuilder (state) {
|
||||
function StyleBuilder (styling) {
|
||||
const {
|
||||
CustomArray,
|
||||
CustomArrayBasic,
|
||||
vNumCustomHeaders,
|
||||
vColorSchema,
|
||||
vColorText,
|
||||
vColorSchemaP,
|
||||
vLetterSize,
|
||||
colors
|
||||
} = state;
|
||||
customCSV,
|
||||
options
|
||||
} = styling;
|
||||
let style = {
|
||||
fontSize: `${14 + vLetterSize}px`
|
||||
fontSize: `${14 + options.fontSizeAdjustment}px`
|
||||
};
|
||||
let hasComments = false;
|
||||
let commentColor;
|
||||
let hasCustomFileStyle = false;
|
||||
|
||||
function applyStandardAttributes (rowNumber) {
|
||||
const isEven = rowNumber % 2 === 0;
|
||||
style.backgroundColor = isEven ? vColorSchema : vColorSchemaP;
|
||||
style.color = vColorText;
|
||||
style.fontSize = `${14 + vLetterSize}px`;
|
||||
const hasBackgroundColor = options.backgroundColor && options.backgroundColor.color;
|
||||
const hasOddBackgroundColor = options.backgroundColorOdd && options.backgroundColorOdd.color;
|
||||
if (hasBackgroundColor && hasOddBackgroundColor) {
|
||||
const isEven = rowNumber % 2 === 0;
|
||||
style.backgroundColor = isEven ? options.backgroundColor.color : options.backgroundColorOdd.color;
|
||||
style.color = options.color;
|
||||
}
|
||||
style.fontSize = `${13 + options.fontSizeAdjustment}px`;
|
||||
}
|
||||
|
||||
function applyColor (color) {
|
||||
@@ -28,35 +26,28 @@ function StyleBuilder (state) {
|
||||
commentColor = color;
|
||||
}
|
||||
|
||||
/* eslint-disable sort-keys*/
|
||||
const properties = {
|
||||
'<comment>': () => { hasComments = true; },
|
||||
// text
|
||||
'<bold>': () => { style.fontWeight = 'bold'; },
|
||||
'<italic>': () => { style.fontStyle = 'italic'; },
|
||||
'<oblique>': () => { style.fontStyle = 'oblique'; },
|
||||
// background and comment color
|
||||
'<dark>': () => applyColor(colors.vColLibDark),
|
||||
'<night>': () => applyColor(colors.vColLibNight),
|
||||
'<soft>': () => applyColor(colors.vColLibSoft),
|
||||
'<red>': () => applyColor(colors.vColLibRed),
|
||||
'<orange>': () => applyColor(colors.vColLibOrange),
|
||||
'<violete>': () => applyColor(colors.vColLibViolete),
|
||||
'<blue>': () => applyColor(colors.vColLibBlue),
|
||||
'<green>': () => applyColor(colors.vColLibGreen),
|
||||
// font color TODO: this is a color just like the others, but it applies to text instead.. any way to make it less weird?
|
||||
// font color
|
||||
'<white>': () => { style.color = 'white'; },
|
||||
// font size
|
||||
'<large>': () => { style.fontSize = `${15 + vLetterSize}px`; },
|
||||
'<medium>': () => { style.fontSize = `${14 + vLetterSize}px`; },
|
||||
'<small>': () => { style.fontSize = `${13 + vLetterSize}px`; },
|
||||
'<large>': () => { style.fontSize = `${15 + options.fontSizeAdjustment}px`; },
|
||||
'<medium>': () => { style.fontSize = `${14 + options.fontSizeAdjustment}px`; },
|
||||
'<small>': () => { style.fontSize = `${13 + options.fontSizeAdjustment}px`; },
|
||||
// text alignment
|
||||
'<center>': () => { style.textAlign = 'center'; }
|
||||
};
|
||||
/* eslint-enable sort-keys */
|
||||
|
||||
// TODO: need to improve this, it has way too many false positives
|
||||
function isCSSColor (property) {
|
||||
const isHexColor = property.substring(0, 1) == '#';
|
||||
const isRGBColor = property.substring(0, 3).toUpperCase() == 'RGB';
|
||||
const isHexColor = property.substring(0, 1) === '#';
|
||||
const isRGBColor = property.substring(0, 3).toUpperCase() === 'RGB';
|
||||
|
||||
return isHexColor || isRGBColor;
|
||||
}
|
||||
@@ -84,27 +75,27 @@ function StyleBuilder (state) {
|
||||
}
|
||||
|
||||
function parseCustomFileStyle (columnText) {
|
||||
hasCustomFileStyle = true;
|
||||
for (let csvAttribute = 1; csvAttribute < vNumCustomHeaders; csvAttribute += 1) {
|
||||
for (let csvAttribute = 1; csvAttribute < customCSV.count; csvAttribute += 1) {
|
||||
let customAttribute = '';
|
||||
if (CustomArrayBasic.indexOf(columnText) < 0) {
|
||||
if (customCSV.basic.indexOf(columnText) < 0) {
|
||||
customAttribute = 'none';
|
||||
} else {
|
||||
customAttribute = CustomArray[CustomArrayBasic.indexOf(columnText)][csvAttribute];
|
||||
hasCustomFileStyle = true;
|
||||
customAttribute = customCSV.full[customCSV.basic.indexOf(columnText)][csvAttribute];
|
||||
}
|
||||
applyProperty(customAttribute);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
applyCustomStyle,
|
||||
applyProperty,
|
||||
applyStandardAttributes,
|
||||
getCommentColor: () => commentColor,
|
||||
getStyle: () => style,
|
||||
hasComments: () => hasComments,
|
||||
hasCustomFileStyle: () => hasCustomFileStyle,
|
||||
hasFontSize: () => Boolean(style.fontSize),
|
||||
hasComments: () => hasComments,
|
||||
applyStandardAttributes,
|
||||
applyProperty,
|
||||
applyCustomStyle,
|
||||
parseCustomFileStyle
|
||||
};
|
||||
}
|
||||
|
||||
65
src/tooltip/index.jsx
Normal file
65
src/tooltip/index.jsx
Normal file
@@ -0,0 +1,65 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const handleCalculateTooltipPosition = (event) => {
|
||||
const tooltip = document.querySelector('.tooltip-wrapper');
|
||||
if (!tooltip) {
|
||||
return;
|
||||
}
|
||||
tooltip.style.left = `${event.clientX}px`;
|
||||
tooltip.style.top = `${event.clientY}px`;
|
||||
};
|
||||
|
||||
class Tooltip extends React.PureComponent {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
showTooltip: false
|
||||
};
|
||||
this.handleRenderTooltip = this.handleRenderTooltip.bind(this);
|
||||
}
|
||||
|
||||
handleRenderTooltip () {
|
||||
const { showTooltip } = this.state;
|
||||
this.setState({ showTooltip: !showTooltip });
|
||||
}
|
||||
|
||||
render () {
|
||||
const { children, styling, tooltipText } = this.props;
|
||||
const { showTooltip } = this.state;
|
||||
return (
|
||||
<div
|
||||
onMouseMove={handleCalculateTooltipPosition}
|
||||
onMouseOut={this.handleRenderTooltip}
|
||||
onMouseOver={this.handleRenderTooltip}
|
||||
style={{ fontFamily: styling.options.fontFamily }}
|
||||
>
|
||||
{children}
|
||||
|
||||
{showTooltip
|
||||
? (
|
||||
<div className="tooltip-wrapper">
|
||||
<p style={{ fontFamily: styling.options.fontFamily }}>
|
||||
{tooltipText}
|
||||
</p>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Tooltip.propTypes = {
|
||||
children: PropTypes.oneOfType([
|
||||
PropTypes.arrayOf(PropTypes.node),
|
||||
PropTypes.node
|
||||
]).isRequired,
|
||||
styling: PropTypes.shape({
|
||||
options: PropTypes.shape({
|
||||
fontFamily: PropTypes.string.isRequired
|
||||
}).isRequired
|
||||
}).isRequired,
|
||||
tooltipText: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
export default Tooltip;
|
||||
@@ -2,17 +2,11 @@ export function onlyUnique (value, index, self) {
|
||||
return self.indexOf(value) === index;
|
||||
}
|
||||
|
||||
export function addSeparators (nStr, thousandsSep, decimalSep, numDecimals) {
|
||||
let x1;
|
||||
nStr = nStr.toFixed(numDecimals);
|
||||
const x = nStr.split('.');
|
||||
x1 = x[0];
|
||||
const x2 = x.length > 1 ? decimalSep + x[1] : '';
|
||||
const rgx = /(\d+)(\d{3})/;
|
||||
while (rgx.test(x1)) {
|
||||
x1 = x1.replace(rgx, `$1${thousandsSep}$2`);
|
||||
}
|
||||
return x1 + x2;
|
||||
export function distinctArray (array) {
|
||||
return array
|
||||
.map(entry => JSON.stringify(entry))
|
||||
.filter(onlyUnique)
|
||||
.map(entry => JSON.parse(entry));
|
||||
}
|
||||
|
||||
export function Deferred () {
|
||||
@@ -21,3 +15,25 @@ export function Deferred () {
|
||||
this.reject = reject;
|
||||
});
|
||||
}
|
||||
|
||||
export function injectSeparators (array, columnSeparatorWidth, suppliedOptions) {
|
||||
const defaultOptions = {
|
||||
atEvery: 1,
|
||||
separator: { isSeparator: true }
|
||||
};
|
||||
const options = {
|
||||
...defaultOptions,
|
||||
...suppliedOptions
|
||||
};
|
||||
|
||||
if (!columnSeparatorWidth) {
|
||||
return array;
|
||||
}
|
||||
return array.reduce((result, entry, index) => {
|
||||
result.push(entry);
|
||||
if (index < array.length - 1 && (index + 1) % options.atEvery === 0) {
|
||||
result.push(options.separator);
|
||||
}
|
||||
return result;
|
||||
}, []);
|
||||
}
|
||||
|
||||
@@ -1,35 +1,129 @@
|
||||
"use strict";
|
||||
|
||||
module.exports = {
|
||||
rules: {
|
||||
"at-rule-no-unknown": true,
|
||||
"block-no-empty": true,
|
||||
"color-no-invalid-hex": true,
|
||||
"comment-no-empty": true,
|
||||
"declaration-block-no-duplicate-properties": [
|
||||
true,
|
||||
'rules': {
|
||||
'at-rule-empty-line-before': [
|
||||
'always',
|
||||
{
|
||||
ignore: ["consecutive-duplicates-with-different-values"]
|
||||
except: [
|
||||
'blockless-after-same-name-blockless',
|
||||
'first-nested'
|
||||
],
|
||||
ignore: ['after-comment']
|
||||
}
|
||||
],
|
||||
"declaration-block-no-shorthand-property-overrides": true,
|
||||
"font-family-no-duplicate-names": true,
|
||||
"font-family-no-missing-generic-family-keyword": true,
|
||||
"function-calc-no-unspaced-operator": true,
|
||||
"function-linear-gradient-no-nonstandard-direction": true,
|
||||
"keyframe-declaration-no-important": true,
|
||||
"media-feature-name-no-unknown": true,
|
||||
"no-descending-specificity": true,
|
||||
"no-duplicate-at-import-rules": true,
|
||||
"no-duplicate-selectors": true,
|
||||
"no-empty-source": true,
|
||||
"no-extra-semicolons": true,
|
||||
"no-invalid-double-slash-comments": true,
|
||||
"property-no-unknown": true,
|
||||
"selector-pseudo-class-no-unknown": true,
|
||||
"selector-pseudo-element-no-unknown": true,
|
||||
"selector-type-no-unknown": true,
|
||||
"string-no-newline": true,
|
||||
"unit-no-unknown": true
|
||||
'at-rule-name-case': 'lower',
|
||||
'at-rule-name-space-after': 'always-single-line',
|
||||
'at-rule-semicolon-newline-after': 'always',
|
||||
'block-closing-brace-empty-line-before': 'never',
|
||||
'block-closing-brace-newline-after': 'always',
|
||||
'block-closing-brace-newline-before': 'always-multi-line',
|
||||
'block-closing-brace-space-before': 'always-single-line',
|
||||
'block-opening-brace-newline-after': 'always-multi-line',
|
||||
'block-opening-brace-space-after': 'always-single-line',
|
||||
'block-opening-brace-space-before': 'always',
|
||||
'color-hex-case': 'lower',
|
||||
'color-hex-length': 'short',
|
||||
'color-named': 'never',
|
||||
'comment-empty-line-before': [
|
||||
'always',
|
||||
{
|
||||
except: ['first-nested'],
|
||||
ignore: ['stylelint-commands']
|
||||
}
|
||||
],
|
||||
'comment-whitespace-inside': 'always',
|
||||
'custom-property-empty-line-before': [
|
||||
'always',
|
||||
{
|
||||
except: [
|
||||
'after-custom-property',
|
||||
'first-nested'
|
||||
],
|
||||
ignore: [
|
||||
'after-comment',
|
||||
'inside-single-line-block'
|
||||
]
|
||||
}
|
||||
],
|
||||
'declaration-bang-space-after': 'never',
|
||||
'declaration-bang-space-before': 'always',
|
||||
'declaration-block-semicolon-newline-after': 'always-multi-line',
|
||||
'declaration-block-semicolon-space-after': 'always-single-line',
|
||||
'declaration-block-semicolon-space-before': 'never',
|
||||
'declaration-block-single-line-max-declarations': 1,
|
||||
'declaration-block-trailing-semicolon': 'always',
|
||||
'declaration-colon-newline-after': 'always-multi-line',
|
||||
'declaration-colon-space-after': 'always-single-line',
|
||||
'declaration-colon-space-before': 'never',
|
||||
'declaration-empty-line-before': [
|
||||
'always',
|
||||
{
|
||||
except: [
|
||||
'after-declaration',
|
||||
'first-nested'
|
||||
],
|
||||
ignore: [
|
||||
'after-comment',
|
||||
'inside-single-line-block'
|
||||
]
|
||||
}
|
||||
],
|
||||
'declaration-no-important': [
|
||||
true,
|
||||
{
|
||||
severity: 'warning'
|
||||
}
|
||||
],
|
||||
'function-comma-newline-after': 'always-multi-line',
|
||||
'function-comma-space-after': 'always-single-line',
|
||||
'function-comma-space-before': 'never',
|
||||
'function-max-empty-lines': 0,
|
||||
'function-name-case': 'lower',
|
||||
'function-parentheses-newline-inside': 'always-multi-line',
|
||||
'function-parentheses-space-inside': 'never-single-line',
|
||||
'function-whitespace-after': 'always',
|
||||
'indentation': 2,
|
||||
'length-zero-no-unit': true,
|
||||
'max-empty-lines': 1,
|
||||
'max-nesting-depth': 5,
|
||||
'media-feature-colon-space-after': 'always',
|
||||
'media-feature-colon-space-before': 'never',
|
||||
'media-feature-name-case': 'lower',
|
||||
'media-feature-parentheses-space-inside': 'never',
|
||||
'media-feature-range-operator-space-after': 'always',
|
||||
'media-feature-range-operator-space-before': 'always',
|
||||
'media-query-list-comma-newline-after': 'always-multi-line',
|
||||
'media-query-list-comma-space-after': 'always-single-line',
|
||||
'media-query-list-comma-space-before': 'never',
|
||||
'no-extra-semicolons': true,
|
||||
'no-missing-end-of-source-newline': true,
|
||||
'number-leading-zero': 'always',
|
||||
'number-no-trailing-zeros': true,
|
||||
'property-case': 'lower',
|
||||
'rule-empty-line-before': [
|
||||
'always-multi-line',
|
||||
{
|
||||
except: ['first-nested'],
|
||||
ignore: ['after-comment']
|
||||
}
|
||||
],
|
||||
'selector-attribute-brackets-space-inside': 'never',
|
||||
'selector-attribute-operator-space-after': 'never',
|
||||
'selector-attribute-operator-space-before': 'never',
|
||||
'selector-combinator-space-after': 'always',
|
||||
'selector-combinator-space-before': 'always',
|
||||
'selector-descendant-combinator-no-non-space': true,
|
||||
'selector-list-comma-newline-after': 'always',
|
||||
'selector-list-comma-space-before': 'never',
|
||||
'selector-max-empty-lines': 0,
|
||||
'selector-pseudo-class-case': 'lower',
|
||||
'selector-pseudo-class-parentheses-space-inside': 'never',
|
||||
'selector-pseudo-element-case': 'lower',
|
||||
'selector-pseudo-element-colon-notation': 'double',
|
||||
'selector-type-case': 'lower',
|
||||
'unit-case': 'lower',
|
||||
'value-list-comma-newline-after': 'always-multi-line',
|
||||
'value-list-comma-space-after': 'always-single-line',
|
||||
'value-list-comma-space-before': 'never',
|
||||
'value-list-max-empty-lines': 0
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,20 +1,15 @@
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||
const StyleLintPlugin = require('stylelint-webpack-plugin');
|
||||
const settings = require('./settings');
|
||||
const packageJSON = require('./package.json');
|
||||
const path = require('path');
|
||||
|
||||
console.log('Webpack mode:', settings.mode); // eslint-disable-line no-console
|
||||
const DIST = path.resolve("./dist");
|
||||
const MODE = process.env.NODE_ENV || 'development';
|
||||
|
||||
console.log('Webpack mode:', MODE); // eslint-disable-line no-console
|
||||
|
||||
const config = {
|
||||
devtool: 'source-map',
|
||||
entry: [
|
||||
'./src/index.js'
|
||||
],
|
||||
mode: settings.mode,
|
||||
output: {
|
||||
path: settings.buildDestination,
|
||||
filename: settings.name + '.js',
|
||||
libraryTarget: 'amd'
|
||||
},
|
||||
entry: ['./src/index.js'],
|
||||
externals: {
|
||||
jquery: {
|
||||
amd: 'jquery',
|
||||
@@ -22,29 +17,35 @@ const config = {
|
||||
commonjs2: 'jquery',
|
||||
root: '_'
|
||||
},
|
||||
qlik: {
|
||||
amd: 'qlik',
|
||||
commonjs: 'qlik',
|
||||
commonjs2: 'qlik',
|
||||
root: '_'
|
||||
}
|
||||
},
|
||||
// TODO: breaks core-js for some reason
|
||||
// resolve: {
|
||||
// extensions: ['js', 'jsx']
|
||||
// },
|
||||
mode: MODE,
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
enforce: 'pre',
|
||||
test: /\.(js|jsx)$/,
|
||||
exclude: /(node_modules|Library)/,
|
||||
loader: 'eslint-loader',
|
||||
options: {
|
||||
failOnError: true
|
||||
}
|
||||
},
|
||||
test: /\.(js|jsx)$/
|
||||
},
|
||||
{
|
||||
test: /\.(js|jsx)$/,
|
||||
exclude: /node_modules/,
|
||||
test: /\.(js|jsx)$/,
|
||||
use: {
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
plugins: ['@babel/plugin-transform-async-to-generator'],
|
||||
plugins: [
|
||||
'@babel/plugin-transform-async-to-generator',
|
||||
'@babel/plugin-proposal-class-properties'
|
||||
],
|
||||
presets: [
|
||||
'@babel/preset-env',
|
||||
'@babel/preset-react'
|
||||
@@ -54,23 +55,23 @@ const config = {
|
||||
},
|
||||
{
|
||||
test: /.less$/,
|
||||
use: ['style-loader', 'css-loader', 'less-loader']
|
||||
use: [
|
||||
'style-loader',
|
||||
'css-loader',
|
||||
'less-loader'
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
output: {
|
||||
filename: `${packageJSON.name}.js`,
|
||||
libraryTarget: 'amd',
|
||||
path: DIST
|
||||
},
|
||||
plugins: [
|
||||
new CopyWebpackPlugin([
|
||||
'assets/' + settings.name + '.qext',
|
||||
'assets/' + settings.name + '.png',
|
||||
'assets/wbfolder.wbl',
|
||||
|
||||
// TODO: remove entries below this line
|
||||
'resources/Accounts.csv',
|
||||
'resources/Accounts2.csv',
|
||||
'resources/QlikLook.csv',
|
||||
'resources/Excel.png',
|
||||
], {}),
|
||||
new StyleLintPlugin()
|
||||
new StyleLintPlugin({
|
||||
files: '**/*.less'
|
||||
})
|
||||
]
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user