diff --git a/package-lock.json b/package-lock.json index 565fea0..dacfa21 100644 --- a/package-lock.json +++ b/package-lock.json @@ -101,6 +101,104 @@ "@babel/types": "^7.0.0" } }, + "@babel/helper-create-class-features-plugin": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.3.2.tgz", + "integrity": "sha512-tdW8+V8ceh2US4GsYdNVNoohq5uVwOf9k6krjwW4E1lINcHgttnWcNqgdoessn12dAy8QkbezlbQh2nXISNY+A==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-member-expression-to-functions": "^7.0.0", + "@babel/helper-optimise-call-expression": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-replace-supers": "^7.2.3" + }, + "dependencies": { + "@babel/generator": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.3.3.tgz", + "integrity": "sha512-aEADYwRRZjJyMnKN7llGIlircxTCofm3dtV5pmY6ob18MSIuipHpA2yZWkPlycwu5HJcx/pADS3zssd8eY7/6A==", + "dev": true, + "requires": { + "@babel/types": "^7.3.3", + "jsesc": "^2.5.1", + "lodash": "^4.17.11", + "source-map": "^0.5.0", + "trim-right": "^1.0.1" + }, + "dependencies": { + "@babel/types": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.3.3.tgz", + "integrity": "sha512-2tACZ80Wg09UnPg5uGAOUvvInaqLk3l/IAhQzlxLQOIXacr6bMsra5SH6AWw/hIDRCSbCdHP2KzSOD+cT7TzMQ==", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.11", + "to-fast-properties": "^2.0.0" + } + } + } + }, + "@babel/helper-replace-supers": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.2.3.tgz", + "integrity": "sha512-GyieIznGUfPXPWu0yLS6U55Mz67AZD9cUk0BfirOWlPrXlBcan9Gz+vHGz+cPfuoweZSnPzPIm67VtQM0OWZbA==", + "dev": true, + "requires": { + "@babel/helper-member-expression-to-functions": "^7.0.0", + "@babel/helper-optimise-call-expression": "^7.0.0", + "@babel/traverse": "^7.2.3", + "@babel/types": "^7.0.0" + } + }, + "@babel/parser": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.3.3.tgz", + "integrity": "sha512-xsH1CJoln2r74hR+y7cg2B5JCPaTh+Hd+EbBRk9nWGSNspuo6krjhX0Om6RnRQuIvFq8wVXCLKH3kwKDYhanSg==", + "dev": true + }, + "@babel/traverse": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.2.3.tgz", + "integrity": "sha512-Z31oUD/fJvEWVR0lNZtfgvVt512ForCTNKYcJBGbPb1QZfve4WGH8Wsy7+Mev33/45fhP/hwQtvgusNdcCMgSw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/generator": "^7.2.2", + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-split-export-declaration": "^7.0.0", + "@babel/parser": "^7.2.3", + "@babel/types": "^7.2.2", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.10" + }, + "dependencies": { + "@babel/types": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.3.3.tgz", + "integrity": "sha512-2tACZ80Wg09UnPg5uGAOUvvInaqLk3l/IAhQzlxLQOIXacr6bMsra5SH6AWw/hIDRCSbCdHP2KzSOD+cT7TzMQ==", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.11", + "to-fast-properties": "^2.0.0" + } + } + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, "@babel/helper-define-map": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.1.0.tgz", @@ -302,6 +400,16 @@ "@babel/plugin-syntax-async-generators": "^7.2.0" } }, + "@babel/plugin-proposal-class-properties": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.3.3.tgz", + "integrity": "sha512-XO9eeU1/UwGPM8L+TjnQCykuVcXqaO5J1bkRPIygqZ/A2L1xVMJ9aZXrY31c0U4H2/LHKL4lbFQLsxktSrc/Ng==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.3.0", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, "@babel/plugin-proposal-json-strings": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.2.0.tgz", diff --git a/package.json b/package.json index 4584c2b..999ea52 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ }, "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", diff --git a/src/linked-scroll/index.js b/src/linked-scroll/index.js new file mode 100644 index 0000000..5e5ded1 --- /dev/null +++ b/src/linked-scroll/index.js @@ -0,0 +1,2 @@ +export { default as LinkedScrollWrapper } from './linked-scroll-wrapper.jsx'; +export { default as LinkedScrollSection } from './linked-scroll-section.jsx'; diff --git a/src/linked-scroll/linked-scroll-section.jsx b/src/linked-scroll/linked-scroll-section.jsx new file mode 100644 index 0000000..e77aaeb --- /dev/null +++ b/src/linked-scroll/linked-scroll-section.jsx @@ -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; diff --git a/src/linked-scroll/linked-scroll-wrapper.jsx b/src/linked-scroll/linked-scroll-wrapper.jsx new file mode 100644 index 0000000..be95b55 --- /dev/null +++ b/src/linked-scroll/linked-scroll-wrapper.jsx @@ -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 ( + + {children} + + ); + } +} + +LinkedScrollWrapper.propTypes = { + children: PropTypes.any +}; + +export default LinkedScrollWrapper; diff --git a/src/main.less b/src/main.less index e836cbb..b43d649 100644 --- a/src/main.less +++ b/src/main.less @@ -12,8 +12,6 @@ } div.qv-object-content-container { - overflow-x: scroll; - overflow-y: hidden; z-index: 110; } @@ -151,12 +149,6 @@ width: 350px; } - .header-wrapper { - position: absolute; - top: 0; - z-index: 1; - } - /*popups for headers*/ .tooltip { position: fixed !important; @@ -168,11 +160,7 @@ /*end popups*/ .row-wrapper { - position: absolute; - top: 97px; height: calc(~"100% - 97px"); - overflow-x: hidden; - overflow-y: scroll; padding: 0; margin-top: 0; } @@ -188,30 +176,54 @@ .kpi-table { width: @KpiTableWidth !important; overflow: hidden !important; - display: table; - height: 100%; + 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; - } - .kpi-table .row-wrapper { - overflow: hidden; + .row-wrapper { + height: calc(~"100% - 97px"); + overflow: scroll; + position: absolute; + padding: 0; + margin-top: 0; + } } .data-table { - width: 272px !important; - float: left; - display: table; height: 100%; - z-index: 90; + width: calc(100% - 243px); position: absolute; margin-left: @KpiTableWidth + 13px; - -ms-overflow-style: none; + + .header-wrapper { + overflow: scroll; + width: 100%; + } + + .row-wrapper { + height: calc(~"100% - 97px"); + width: 100%; + overflow: scroll; + padding: 0; + margin-top: 0; + } + } + + // hide scrollbars + .kpi-table .header-wrapper, + .kpi-table .row-wrapper, + .data-table .header-wrapper, + .data-table .row-wrapper { + -ms-overflow-style: none; // IE 10+ + -moz-overflow: -moz-scrollbars-none; // Firefox + + &::-webkit-scrollbar { + display: none; // Safari and Chrome + } } } diff --git a/src/paint.jsx b/src/paint.jsx index 2255a89..29ec860 100644 --- a/src/paint.jsx +++ b/src/paint.jsx @@ -4,6 +4,7 @@ import React from 'react'; import ReactDOM from 'react-dom'; import HeadersTable from './headers-table/index.jsx'; import DataTable from './data-table/index.jsx'; +import { LinkedScrollWrapper, LinkedScrollSection } from './linked-scroll'; export default async function paint ($element, layout, component) { const state = await initializeStore({ @@ -13,7 +14,7 @@ export default async function paint ($element, layout, component) { }); const editmodeClass = component.inAnalysisState() ? '' : 'edit-mode'; const jsx = ( - +
- + + +
- - + + + + + +
-
+ ); ReactDOM.render(jsx, $element[0]); - // TODO: skipped the following as they weren't blockers for letting react handle rendering, - // they are however the only reason we still depend on jQuery and should be removed as part of unnecessary dependencies issue - $(`[tid="${layout.qInfo.qId}"] .data-table .row-wrapper`).on('scroll', function () { - $(`[tid="${layout.qInfo.qId}"] .kpi-table .row-wrapper`).scrollTop($(this).scrollTop()); - }); - - // freeze first column - $(`[tid="${layout.qInfo.qId}"] .qv-object-content-container`).on('scroll', (t) => { - $(`[tid="${layout.qInfo.qId}"] .kpi-table`).css('left', `${Math.round(t.target.scrollLeft)}px`); - }); - // TODO: fixing tooltips has a seperate issue, make sure to remove this as part of that issue $(`[tid="${layout.qInfo.qId}"] .header-wrapper th`).hover(function () { $(`[tid="${layout.qInfo.qId}"] .tooltip`).delay(500) diff --git a/webpack.config.js b/webpack.config.js index a70a9b1..83f5ec2 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -6,23 +6,16 @@ console.log('Webpack mode:', settings.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', commonjs: 'jquery', commonjs2: 'jquery', root: '_' - }, + } }, + mode: settings.mode, // TODO: breaks core-js for some reason // resolve: { // extensions: ['js', 'jsx'] @@ -31,20 +24,23 @@ const config = { 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,21 +50,30 @@ const config = { }, { test: /.less$/, - use: ['style-loader', 'css-loader', 'less-loader'] + use: [ + 'style-loader', + 'css-loader', + 'less-loader' + ] } ] }, + output: { + filename: `${settings.name}.js`, + libraryTarget: 'amd', + path: settings.buildDestination + }, plugins: [ new CopyWebpackPlugin([ - 'assets/' + settings.name + '.qext', - 'assets/' + settings.name + '.png', + `assets/${settings.name}.qext`, + `assets/${settings.name}.png`, 'assets/wbfolder.wbl', + 'resources/Excel.png', // TODO: remove entries below this line 'resources/Accounts.csv', 'resources/Accounts2.csv', - 'resources/QlikLook.csv', - 'resources/Excel.png', + 'resources/QlikLook.csv' ], {}), new StyleLintPlugin() ]