diff --git a/.babelrc b/.babelrc deleted file mode 100644 index a3451f77d..000000000 --- a/.babelrc +++ /dev/null @@ -1,20 +0,0 @@ -{ - "presets": [ - ["env", { "modules": false }], - "react", - "stage-0" - ], - "env": { - "test": { - "presets": ["env", "react", "stage-0"], - "only": [ - "src/*.js", - "src/**/*.js", - "config/*.js" - ], - "plugins": [ - "babel-plugin-dynamic-import-node" - ] - } - } -} diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 75e274d0a..000000000 --- a/.eslintignore +++ /dev/null @@ -1,5 +0,0 @@ -node_modules/ -build/ -target/* -webpack.config.js -src/dev_jquery.js diff --git a/.eslintrc b/.eslintrc deleted file mode 100644 index 9972a1705..000000000 --- a/.eslintrc +++ /dev/null @@ -1,31 +0,0 @@ -{ - "extends": [ - "react-app", - "prettier" - ], - "settings": { - "react": { - "version": "^15.3.1" - } - }, - "env": { - "browser": true, - "jest": true - }, - "rules": { - "no-param-reassign": [2, { "props": false }], - "no-fallthrough": "off", - "array-callback-return": "off", - "import/no-extraneous-dependencies": 0, - "no-unused-expressions": ["error", { - "allowTernary": true, - "allowShortCircuit": true - }] - }, - "parser": "babel-eslint", - "parserOptions": { - "ecmaFeatures": { - "experimentalObjectRestSpread": true - } - } -} diff --git a/.github/workflows/dhis2-verify-app.yml b/.github/workflows/dhis2-verify-app.yml index 4aeac2e24..6b1b58735 100644 --- a/.github/workflows/dhis2-verify-app.yml +++ b/.github/workflows/dhis2-verify-app.yml @@ -21,7 +21,7 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-node@v1 with: - node-version: 12.x + node-version: 14.x - uses: actions/cache@v2 id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`) @@ -41,7 +41,7 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-node@v1 with: - node-version: 12.x + node-version: 14.x - uses: actions/cache@v2 id: yarn-cache @@ -68,7 +68,7 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-node@v1 with: - node-version: 12.x + node-version: 14.x - uses: actions/cache@v2 id: yarn-cache @@ -87,7 +87,7 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-node@v1 with: - node-version: 12.x + node-version: 14.x - uses: actions/cache@v2 id: yarn-cache @@ -109,7 +109,7 @@ jobs: - uses: actions/setup-node@v1 with: - node-version: 12.x + node-version: 14.x - uses: actions/download-artifact@v2 with: @@ -135,7 +135,7 @@ jobs: - uses: actions/setup-node@v1 with: - node-version: 12.x + node-version: 14.x - name: Publish release to GitHub run: npx @dhis2/cli-utils release diff --git a/.gitignore b/.gitignore index a44edb5ef..f394999d4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,12 @@ +.d2 + *.iml .idea/* .vscode/* node_modules* .sass-cache -src/css/* +src/css/**/*.scss coverage/* .nyc_output/ diff --git a/.jshintrc.bak b/.jshintrc.bak new file mode 100644 index 000000000..997b3f7d4 --- /dev/null +++ b/.jshintrc.bak @@ -0,0 +1,10 @@ +{ + "node": true, + + "curly": true, + "latedef": true, + "quotmark": true, + "undef": true, + "unused": true, + "trailing": true +} diff --git a/.prettierrc b/.prettierrc.bak similarity index 100% rename from .prettierrc rename to .prettierrc.bak diff --git a/.prettierrc.js b/.prettierrc.js.bak similarity index 100% rename from .prettierrc.js rename to .prettierrc.js.bak diff --git a/d2.config.js b/d2.config.js new file mode 100644 index 000000000..5cdc78511 --- /dev/null +++ b/d2.config.js @@ -0,0 +1,14 @@ +const config = { + type: 'app', + name: 'maintenance', + title: 'DHIS2 Maintenance app', + description: '@TODO', + coreApp: true, + pwa: { enabled: false }, + + entryPoints: { + app: './src/app.js', + }, +} + +module.exports = config diff --git a/src/i18n/en.pot b/i18n/en.pot similarity index 100% rename from src/i18n/en.pot rename to i18n/en.pot diff --git a/src/i18n/fr.po b/i18n/fr.po similarity index 100% rename from src/i18n/fr.po rename to i18n/fr.po diff --git a/src/i18n/i18n_module_ar.properties b/i18n/i18n_module_ar.properties similarity index 100% rename from src/i18n/i18n_module_ar.properties rename to i18n/i18n_module_ar.properties diff --git a/src/i18n/i18n_module_ar_IQ.properties b/i18n/i18n_module_ar_IQ.properties similarity index 100% rename from src/i18n/i18n_module_ar_IQ.properties rename to i18n/i18n_module_ar_IQ.properties diff --git a/src/i18n/i18n_module_ckb.properties b/i18n/i18n_module_ckb.properties similarity index 100% rename from src/i18n/i18n_module_ckb.properties rename to i18n/i18n_module_ckb.properties diff --git a/src/i18n/i18n_module_cs.properties b/i18n/i18n_module_cs.properties similarity index 100% rename from src/i18n/i18n_module_cs.properties rename to i18n/i18n_module_cs.properties diff --git a/src/i18n/i18n_module_da.properties b/i18n/i18n_module_da.properties similarity index 100% rename from src/i18n/i18n_module_da.properties rename to i18n/i18n_module_da.properties diff --git a/src/i18n/i18n_module_en.properties b/i18n/i18n_module_en.properties similarity index 100% rename from src/i18n/i18n_module_en.properties rename to i18n/i18n_module_en.properties diff --git a/src/i18n/i18n_module_es.properties b/i18n/i18n_module_es.properties similarity index 100% rename from src/i18n/i18n_module_es.properties rename to i18n/i18n_module_es.properties diff --git a/src/i18n/i18n_module_fr.properties b/i18n/i18n_module_fr.properties similarity index 100% rename from src/i18n/i18n_module_fr.properties rename to i18n/i18n_module_fr.properties diff --git a/src/i18n/i18n_module_id.properties b/i18n/i18n_module_id.properties similarity index 100% rename from src/i18n/i18n_module_id.properties rename to i18n/i18n_module_id.properties diff --git a/src/i18n/i18n_module_km.properties b/i18n/i18n_module_km.properties similarity index 100% rename from src/i18n/i18n_module_km.properties rename to i18n/i18n_module_km.properties diff --git a/src/i18n/i18n_module_lo.properties b/i18n/i18n_module_lo.properties similarity index 100% rename from src/i18n/i18n_module_lo.properties rename to i18n/i18n_module_lo.properties diff --git a/src/i18n/i18n_module_my.properties b/i18n/i18n_module_my.properties similarity index 100% rename from src/i18n/i18n_module_my.properties rename to i18n/i18n_module_my.properties diff --git a/src/i18n/i18n_module_nb.properties b/i18n/i18n_module_nb.properties similarity index 100% rename from src/i18n/i18n_module_nb.properties rename to i18n/i18n_module_nb.properties diff --git a/src/i18n/i18n_module_nl.properties b/i18n/i18n_module_nl.properties similarity index 100% rename from src/i18n/i18n_module_nl.properties rename to i18n/i18n_module_nl.properties diff --git a/src/i18n/i18n_module_prs.properties b/i18n/i18n_module_prs.properties similarity index 100% rename from src/i18n/i18n_module_prs.properties rename to i18n/i18n_module_prs.properties diff --git a/src/i18n/i18n_module_ps.properties b/i18n/i18n_module_ps.properties similarity index 100% rename from src/i18n/i18n_module_ps.properties rename to i18n/i18n_module_ps.properties diff --git a/src/i18n/i18n_module_pt.properties b/i18n/i18n_module_pt.properties similarity index 100% rename from src/i18n/i18n_module_pt.properties rename to i18n/i18n_module_pt.properties diff --git a/src/i18n/i18n_module_pt_BR.properties b/i18n/i18n_module_pt_BR.properties similarity index 100% rename from src/i18n/i18n_module_pt_BR.properties rename to i18n/i18n_module_pt_BR.properties diff --git a/src/i18n/i18n_module_ru.properties b/i18n/i18n_module_ru.properties similarity index 100% rename from src/i18n/i18n_module_ru.properties rename to i18n/i18n_module_ru.properties diff --git a/src/i18n/i18n_module_sv.properties b/i18n/i18n_module_sv.properties similarity index 100% rename from src/i18n/i18n_module_sv.properties rename to i18n/i18n_module_sv.properties diff --git a/src/i18n/i18n_module_tet.properties b/i18n/i18n_module_tet.properties similarity index 100% rename from src/i18n/i18n_module_tet.properties rename to i18n/i18n_module_tet.properties diff --git a/src/i18n/i18n_module_tg.properties b/i18n/i18n_module_tg.properties similarity index 100% rename from src/i18n/i18n_module_tg.properties rename to i18n/i18n_module_tg.properties diff --git a/src/i18n/i18n_module_uk.properties b/i18n/i18n_module_uk.properties similarity index 100% rename from src/i18n/i18n_module_uk.properties rename to i18n/i18n_module_uk.properties diff --git a/src/i18n/i18n_module_ur.properties b/i18n/i18n_module_ur.properties similarity index 100% rename from src/i18n/i18n_module_ur.properties rename to i18n/i18n_module_ur.properties diff --git a/src/i18n/i18n_module_uz.properties b/i18n/i18n_module_uz.properties similarity index 100% rename from src/i18n/i18n_module_uz.properties rename to i18n/i18n_module_uz.properties diff --git a/src/i18n/i18n_module_uz_Cyrl.properties b/i18n/i18n_module_uz_Cyrl.properties similarity index 100% rename from src/i18n/i18n_module_uz_Cyrl.properties rename to i18n/i18n_module_uz_Cyrl.properties diff --git a/src/i18n/i18n_module_uz_Latn.properties b/i18n/i18n_module_uz_Latn.properties similarity index 100% rename from src/i18n/i18n_module_uz_Latn.properties rename to i18n/i18n_module_uz_Latn.properties diff --git a/src/i18n/i18n_module_vi.properties b/i18n/i18n_module_vi.properties similarity index 100% rename from src/i18n/i18n_module_vi.properties rename to i18n/i18n_module_vi.properties diff --git a/src/i18n/i18n_module_zh.properties b/i18n/i18n_module_zh.properties similarity index 100% rename from src/i18n/i18n_module_zh.properties rename to i18n/i18n_module_zh.properties diff --git a/src/i18n/i18n_module_zh_CN.properties b/i18n/i18n_module_zh_CN.properties similarity index 100% rename from src/i18n/i18n_module_zh_CN.properties rename to i18n/i18n_module_zh_CN.properties diff --git a/index.ejs b/index.ejs deleted file mode 100644 index 17d2f3e3f..000000000 --- a/index.ejs +++ /dev/null @@ -1,29 +0,0 @@ - - - - - DHIS 2 Maintenance - - - - - - -
- - <% if (process.env.NODE_ENV === 'production') { %> - - <% } %> <% if (process.env.NODE_ENV !== 'production') { %> - - <% } %> - <%= htmlWebpackPlugin.options.serviceWorkerScript %> - - diff --git a/jest.config.js b/jest.config.js index 410e08927..58cdaa170 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,5 +1,5 @@ module.exports = { - setupTestFrameworkScriptFile: '/config/setup.js', + setupFilesAfterEnv: ['/src/setup-tests.js'], collectCoverageFrom: ['src/**/*.js'], testPathIgnorePatterns: [ '/node_modules/', diff --git a/package.json b/package.json index 74502aff4..41d96b113 100644 --- a/package.json +++ b/package.json @@ -8,56 +8,60 @@ }, "license": "BSD-3-Clause", "scripts": { - "prestart": "d2-manifest package.json manifest.webapp", - "start": "webpack-dev-server", + "_prestart": "d2-manifest package.json manifest.webapp", + "_start": "webpack-dev-server", + "start": "concurrently 'yarn start:app' 'yarn wait-for-app && yarn copy-legacy-i18n'", + "copy-legacy-i18n": "rm -rf ./.d2/shell/public/i18n && cp -r ./i18n ./.d2/shell/public/", + "wait-for-app": "wait-on http-get://localhost:3000", + "prestart:app": "rm -rf .d2", + "start:app": "d2-app-scripts start --force", "coverage": "npm test -- --coverage", - "test": "jest", + "test": "d2-app-scripts test", "test:watch": "npm test -- --watch", - "prebuild": "rm -rf build", - "build": "npm test && NODE_ENV=production webpack --progress && npm run manifest", - "postbuild": "cp -r src/i18n icon.png package.json ./build/ && yarn copy-ckeditor", + "_prebuild": "rm -rf build", + "_build": "npm test && NODE_ENV=production webpack --progress && npm run manifest", + "build": "d2-app-scripts build", "validate": "npm ls --depth 0", "manifest": "d2-manifest package.json build/manifest.webapp", "lint": "eslint ./src", - "profile": "npm run start -- --profile", - "copy-ckeditor": "mkdir ./build/vendor && cp -r ./node_modules/ckeditor ./build/vendor" + "profile": "npm run start -- --profile" }, "devDependencies": { - "babel-cli": "^6.26.0", - "babel-core": "^6.26.3", + "@babel/cli": "^7.15.7", + "@babel/core": "^7.15.8", + "@babel/plugin-proposal-class-properties": "^7.14.5", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.14.5", + "@babel/plugin-proposal-optional-chaining": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-transform-modules-commonjs": "^7.15.4", + "@babel/plugin-transform-runtime": "^7.15.8", + "@babel/preset-env": "^7.15.8", + "@babel/preset-react": "^7.14.5", "babel-eslint": "^8.2.3", "babel-jest": "^23.0.1", - "babel-loader": "latest", + "babel-loader": "^8.2.2", "babel-plugin-dynamic-import-node": "^1.2.0", + "babel-plugin-react-require": "^3.1.3", "babel-plugin-transform-runtime": "^6.12.0", - "babel-polyfill": "^6.26.0", - "babel-preset-env": "^1.7.0", - "babel-preset-es2015": "^6.13.2", - "babel-preset-react": "^6.24.1", - "babel-preset-stage-0": "^6.5.0", "classnames": "^2.2.3", + "concurrently": "^7.0.0", "css-loader": "^0.28.1", "d2-manifest": "^1.0.0-2", "d2-utilizr": "^0.2.9", "d3-color": "^1.0.2", "d3-format": "^1.0.2", "d3-scale": "^1.0.3", - "enzyme": "^3.0.0", + "enzyme": "^3.11.0", "enzyme-adapter-react-15": "^1.0.0", - "eslint": "^5.0.1", - "eslint-config-prettier": "^2.9.0", - "eslint-config-react-app": "^2.1.0", - "eslint-plugin-flowtype": "^2.49.3", - "eslint-plugin-import": "^2.13.0", - "eslint-plugin-jsx-a11y": "^5.0.1", - "eslint-plugin-react": "^7.10.0", + "enzyme-adapter-react-16": "^1.15.6", + "eslint-config-react-app": "^7.0.0", + "eslint-plugin-react": "^7.29.4", "fbjs": "^0.8.8", "file-loader": "2", "glob": "^7.1.1", "html-webpack-plugin": "^2.26.0", "ignore-styles": "^5.0.1", - "jest": "^23.0.1", - "jest-enzyme": "^4.0.0", + "jest-enzyme": "^7.1.2", "json-loader": "^0.5.4", "lodash.isfinite": "^3.3.1", "lodash.isnumber": "^3.0.3", @@ -65,30 +69,47 @@ "moment": "^2.16.0", "node-fetch": "^1.6.3", "node-pre-gyp": "^0.6.30", - "node-sass": "^4.13.1", + "node-sass": "^6.0.1", "precommit-hook": "^3.0.0", "prettier": "^1.13.7", "recompose": "^0.23.1", "redux-logger": "^3.0.6", "sass-loader": "^6.0.5", "style-loader": "^0.16.1", + "wait-on": "^6.0.1", "webpack": "^2.5.1", "webpack-bundle-analyzer": "^2.7.0", "webpack-dev-server": "^2.4.5", "webpack-visualizer-plugin": "^0.1.5" }, "dependencies": { - "@dhis2/d2-ui-header-bar": "^1.1.4", - "@dhis2/d2-ui-sharing-dialog": "^1.0.12", + "@babel/runtime": "^7.15.4", + "@dhis2/app-runtime": "^3.4.0", + "@dhis2/app-runtime-adapter-d2": "^1.1.0", + "@dhis2/cli-app-scripts": "^9.0.0", + "@dhis2/d2-i18n": "^1.1.0", + "@dhis2/d2-ui-core": "^7.3.4", + "@dhis2/d2-ui-expression-manager": "^7.3.4", + "@dhis2/d2-ui-forms": "^7.3.4", + "@dhis2/d2-ui-group-editor": "^7.3.4", + "@dhis2/d2-ui-icon-picker": "^7.3.4", + "@dhis2/d2-ui-legend": "^7.3.4", + "@dhis2/d2-ui-org-unit-select": "^7.3.4", + "@dhis2/d2-ui-org-unit-tree": "^7.3.4", + "@dhis2/d2-ui-sharing-dialog": "^7.3.4", + "@dhis2/d2-ui-translation-dialog": "^7.3.4", + "@dhis2/prop-types": "^3.0.0", + "@dhis2/ui": "^8.1.6", "ckeditor": "4.6.1", - "d2": "30.2.2", - "d2-ui": "29.0.34", + "ckeditor4-react": "^3.0.0", + "create-react-class": "^15.7.0", + "d2": "^31.10.2", "lodash": "^4.17.11", "material-design-icons-iconfont": "^4.0.5", - "material-ui": "^0.17.0", + "material-ui": "0.20.0", "nyc": "10.1.2", "prop-types": "^15.6.0", - "react": "~15.5.0", + "react": "16.14.0", "react-addons-create-fragment": "^15.5.4", "react-addons-css-transition-group": "^15.3.1", "react-addons-linked-state-mixin": "^15.3.1", @@ -98,18 +119,25 @@ "react-color": "^2.11.7", "react-dnd": "^2.4.0", "react-dnd-html5-backend": "^2.4.1", - "react-dom": "~15.5.0", + "react-dom": "16.14.0", "react-loadable": "5.3", "react-redux": "^5.0.3", "react-router": "^3.0.0", "react-sortable-hoc": "^0.6.1", "react-speed-dial": "^0.4.7", - "react-tap-event-plugin": "2.0.1", "react-test-renderer": "15", "redux": "^3.6.0", "redux-observable": "0.18.0", "rxjs": "^5.2.0", - "typeface-roboto": "^0.0.54" + "typeface-roboto": "^0.0.54", + "use-debounce": "^7.0.1" + }, + "resolutions": { + "@dhis2/app-runtime": "3.4.0", + "material-ui": "0.20.0", + "react": "^16.14.0", + "react-dom": "^16.14.0", + "react-error-overlay": "6.0.9" }, "pre-commit": [ "test" diff --git a/scss/maintenance.scss b/scss/maintenance.scss index 779bf9bbb..6be59ac9d 100644 --- a/scss/maintenance.scss +++ b/scss/maintenance.scss @@ -1,7 +1,3 @@ -// Component stylesheets from dependencies -@import '../node_modules/d2-ui/lib/css/DataTable'; -@import '../node_modules/d2-ui/lib/css/Pagination'; - @import '../scss/List/DetailsBox'; @import '../scss/List/List'; @import '../scss/SqlView/SqlView'; diff --git a/src/App/AccessDenied.component.js b/src/App/AccessDenied.component.js index c064a57c9..84a2da37a 100644 --- a/src/App/AccessDenied.component.js +++ b/src/App/AccessDenied.component.js @@ -1,5 +1,4 @@ -import React from 'react'; -import Heading from 'd2-ui/lib/headings/Heading.component'; +import { Heading } from '@dhis2/d2-ui-core'; export default function AccessDenied() { return ( diff --git a/src/App/App.component.js b/src/App/App.component.js index 165728a59..0d957ac7d 100644 --- a/src/App/App.component.js +++ b/src/App/App.component.js @@ -1,20 +1,17 @@ -import React from 'react'; -import MainContent from 'd2-ui/lib/layout/main-content/MainContent.component'; +import React, { Component } from 'react' +import PropTypes from 'prop-types'; +import { MainContent, withStateFrom, SinglePanel, TwoPanel } from '@dhis2/d2-ui-core'; import SideBar from '../SideBar/SideBarContainer.component'; import SnackbarContainer from '../Snackbar/SnackbarContainer.component'; -import { getInstance } from 'd2/lib/d2'; -import AppWithD2 from 'd2-ui/lib/app/AppWithD2.component'; +import { getInstance } from 'd2'; import LoadingMask from '../loading-mask/LoadingMask.component'; import SectionTabs from '../TopBar/SectionTabs.component'; -import withStateFrom from 'd2-ui/lib/component-helpers/withStateFrom'; import { Observable } from 'rxjs'; -import SinglePanelLayout from 'd2-ui/lib/layout/SinglePanel.component'; -import TwoPanelLayout from 'd2-ui/lib/layout/TwoPanel.component'; import { goToRoute } from '../router-utils'; import appState, { setAppState } from './appStateStore'; import { Provider } from 'react-redux'; import store from '../store'; -import HeaderBar from "@dhis2/d2-ui-header-bar"; +import { HeaderBar } from "@dhis2/ui"; import DialogRouter from '../Dialog/DialogRouter'; import 'typeface-roboto'; @@ -36,6 +33,51 @@ const sections$ = appState const SectionTabsWrap = withStateFrom(sections$, SectionTabs); +// From https://github.com/dhis2/d2-ui/blob/v29.0.35/src/app/AppWithD2.component.js +class AppWithD2 extends Component { + state = {}; + + componentDidMount() { + if (!this.props.d2) { + log.error('D2 is a required prop to '); + } else { + this.props.d2 + .then(d2 => this.setState({ d2 })) + .catch(error => log.error(error)); + } + } + + getChildContext = () => { + return { + d2: this.state.d2, + }; + }; + + render() { + const getChildren = () => { + if (!this.props.children) { return null; } + return React.Children.map(this.props.children, child => React.cloneElement(child)); + }; + + return ( +
+ {getChildren()} +
+ ); + } +} + +AppWithD2.propTypes = { + children: PropTypes.element, + d2: PropTypes.shape({ + then: PropTypes.func.isRequired, + }), +}; + +AppWithD2.childContextTypes = { + d2: PropTypes.object, +}; + class App extends AppWithD2 { componentDidMount() { super.componentDidMount(); @@ -81,20 +123,19 @@ class App extends AppWithD2 { return (
- {this.state.hasSection && !this.props.children.props.route.hideSidebar ? ( - + {this.props.children} - + ) : ( - + {this.props.children} - + )}
diff --git a/src/App/appStateStore.js b/src/App/appStateStore.js index ca13959d8..dedc4dd2f 100644 --- a/src/App/appStateStore.js +++ b/src/App/appStateStore.js @@ -1,5 +1,5 @@ -import Store from 'd2-ui/lib/store/Store'; -import { getInstance } from 'd2/lib/d2'; +import { Store } from '@dhis2/d2-ui-core'; +import { getInstance } from 'd2'; import camelCaseToUnderscores from 'd2-utilizr/lib/camelCaseToUnderscores'; import isObject from 'd2-utilizr/lib/isObject'; import snackActions from '../Snackbar/snack.actions'; diff --git a/src/App/periodTypeStore.js b/src/App/periodTypeStore.js index f21c18020..6e2801ebc 100644 --- a/src/App/periodTypeStore.js +++ b/src/App/periodTypeStore.js @@ -1,3 +1,3 @@ -import Store from 'd2-ui/lib/store/Store'; +import { Store } from '@dhis2/d2-ui-core'; export default Store.create(); diff --git a/src/App/systemSettingsStore.js b/src/App/systemSettingsStore.js index f21c18020..6e2801ebc 100644 --- a/src/App/systemSettingsStore.js +++ b/src/App/systemSettingsStore.js @@ -1,3 +1,3 @@ -import Store from 'd2-ui/lib/store/Store'; +import { Store } from '@dhis2/d2-ui-core'; export default Store.create(); diff --git a/src/Dialog/DialogRouter.js b/src/Dialog/DialogRouter.js index 66e6c6bd1..8ff466fb5 100644 --- a/src/Dialog/DialogRouter.js +++ b/src/Dialog/DialogRouter.js @@ -1,4 +1,4 @@ -import React, { Component } from 'react'; +import { Component } from 'react'; import { connect } from 'react-redux'; import { getDialogType, getDialogProps, getDialogIsOpen } from './selectors'; import { closeDialog } from './actions'; diff --git a/src/EditModel/BackButton.component.js b/src/EditModel/BackButton.component.js index bd044c36d..9e79c97ca 100644 --- a/src/EditModel/BackButton.component.js +++ b/src/EditModel/BackButton.component.js @@ -1,6 +1,6 @@ -import React from 'react'; +import PropTypes from 'prop-types' import IconButton from 'material-ui/IconButton/IconButton'; -import addD2Context from 'd2-ui/lib/component-helpers/addD2Context'; +import { addD2Context } from '@dhis2/d2-ui-core'; import modelToEditStore from '../EditModel/modelToEditStore'; function BackButton(props, context) { @@ -31,7 +31,7 @@ function BackButton(props, context) { ); } BackButton.propTypes = { - tooltip: React.PropTypes.string, + tooltip: PropTypes.string, }; export default addD2Context(BackButton); diff --git a/src/EditModel/CancelButton.component.js b/src/EditModel/CancelButton.component.js index 0562d8f29..d7e96045f 100644 --- a/src/EditModel/CancelButton.component.js +++ b/src/EditModel/CancelButton.component.js @@ -1,6 +1,6 @@ -import React from 'react'; +import PropTypes from 'prop-types' import Button from 'material-ui/FlatButton'; -import addD2Context from 'd2-ui/lib/component-helpers/addD2Context'; +import { addD2Context } from '@dhis2/d2-ui-core'; import modelToEditStore from '../EditModel/modelToEditStore'; function CancelButton( @@ -37,11 +37,11 @@ function CancelButton( } CancelButton.propTypes = { - onClick: React.PropTypes.func.isRequired, + onClick: PropTypes.func.isRequired, /* A handler that should return an object with "dirty"-key, describing if the current edited model is dirty */ - isDirtyHandler: React.PropTypes.func, + isDirtyHandler: PropTypes.func, }; export default addD2Context(CancelButton); diff --git a/src/EditModel/DataIndicatorGroupsAssignment.component.js b/src/EditModel/DataIndicatorGroupsAssignment.component.js index 60a14f54c..73c1c13d0 100644 --- a/src/EditModel/DataIndicatorGroupsAssignment.component.js +++ b/src/EditModel/DataIndicatorGroupsAssignment.component.js @@ -1,6 +1,7 @@ -import React from 'react'; -import { getInstance } from 'd2/lib/d2'; -import CircularProgress from 'd2-ui/lib/circular-progress/CircularProgress'; +import ReactCreateClass from 'create-react-class' +import PropTypes from 'prop-types' +import { getInstance } from 'd2'; +import { CircularProgress } from '@dhis2/d2-ui-core'; import DropDown from '../forms/form-fields/drop-down'; import store from './indicatorGroupsStore'; @@ -19,9 +20,9 @@ function findValue(optionList, model) { .find(option => Array.from(model.indicatorGroups.values()).map(indicatorGroup => indicatorGroup.id).indexOf(option) !== -1); } -export default React.createClass({ +export default ReactCreateClass({ propTypes: { - source: React.PropTypes.object.isRequired, + source: PropTypes.object.isRequired, }, getInitialState() { diff --git a/src/EditModel/EditDataEntryForm.component.js b/src/EditModel/EditDataEntryForm.component.js deleted file mode 100644 index f2973a4d0..000000000 --- a/src/EditModel/EditDataEntryForm.component.js +++ /dev/null @@ -1,502 +0,0 @@ -import React from 'react'; -import Rx from 'rxjs'; -import log from 'loglevel'; - -import RaisedButton from 'material-ui/RaisedButton/RaisedButton'; -import FlatButton from 'material-ui/FlatButton/FlatButton'; -import SelectField from 'material-ui/SelectField/SelectField'; -import MenuItem from 'material-ui/MenuItem/MenuItem'; -import Paper from 'material-ui/Paper/Paper'; -import TextField from 'material-ui/TextField/TextField'; -import CheckBox from 'material-ui/Checkbox/Checkbox'; - -import LoadingMask from 'd2-ui/lib/loading-mask/LoadingMask.component'; -import Heading from 'd2-ui/lib/headings/Heading.component'; -import Action from 'd2-ui/lib/action/Action'; - -import snackActions from '../Snackbar/snack.actions'; -import modelToEditStore from './modelToEditStore'; -import { goToRoute } from '../router-utils'; - -import '../../scss/EditModel/EditDataEntryForm.scss'; - - -const inputPattern = //gi; -const dataElementCategoryOptionIdPattern = /id="(\w*?)-(\w*?)-val"/; -const dataElementPattern = /dataelementid="(\w{11})"/; -const indicatorPattern = /indicatorid="(\w{11})"/; - -function clampPaletteWidth(width) { - return Math.min(750, Math.max(width, 250)); -} - - -// TODO?: Automatic labels / - -const styles = { - heading: { - paddingBottom: 18, - }, - formContainer: {}, - formPaper: { - width: '100%', - margin: '0 auto 2rem', - padding: '1px 4rem 4rem', - position: 'relative', - }, - formSection: { - marginTop: 28, - }, - cancelButton: { - marginLeft: '2rem', - }, - deleteButton: { - marginLeft: '2rem', - }, - paletteHeader: {}, - paletteFilter: { - position: 'absolute', - top: -16, - width: '100%', - padding: '8px 8px 16px', - }, - paletteFilterField: { - width: '100%', - }, - greySwitch: { - position: 'absolute', - bottom: 8, - left: 8, - right: 8, - }, -}; - -class EditDataEntryForm extends React.Component { - constructor(props, context) { - super(props, context); - - this.state = { - usedIds: [], - filter: '', - paletteWidth: clampPaletteWidth(window.innerWidth / 3), - expand: 'data_elements', - insertGrey: false, - }; - - // Load form data, operands, indicators and flags - Promise.all([ - context.d2.models.dataSets.get(props.params.modelId, { - fields: 'id,displayName,dataEntryForm[id,style,htmlCode],indicators[id,displayName]', - }), - // TODO: Use d2.models when dataElementOperands are properly supported - context.d2.Api.getApi().get('dataElementOperands', { - paging: false, - totals: true, - fields: 'id,dimensionItem,displayName', - dataSet: props.params.modelId, - }), - context.d2.Api.getApi().get('system/flags'), - ]).then(([ - dataSet, - ops, - flags, - ]) => { - // Operands with ID's that contain a dot ('.') are combined dataElementId's and categoryOptionId's - // The API returns "dataElementId.categoryOptionId", which are transformed to the format expected by - // custom forms: "dataElementId-categoryOptionId-val" - this.operands = ops.dataElementOperands - .filter(op => op.dimensionItem.indexOf('.') !== -1) - .reduce((out, op) => { - const id = `${op.dimensionItem.split('.').join('-')}-val`; - out[id] = op.displayName; // eslint-disable-line - return out; - }, {}); - - // Data element totals have only a single ID and thus no dot ('.') - this.totals = ops.dataElementOperands - .filter(op => op.dimensionItem.indexOf('.') === -1) - .reduce((out, op) => { - out[op.id] = op.displayName; // eslint-disable-line - return out; - }, {}); - - this.indicators = dataSet.indicators.toArray() - .sort((a, b) => a.displayName.localeCompare(b.displayName)) - .reduce((out, i) => { - out[i.id] = i.displayName; // eslint-disable-line - return out; - }, {}); - - this.flags = flags.reduce((out, flag) => { - out[flag.path] = flag.name; // eslint-disable-line - return out; - }, {}); - - // Create inserter functions for all insertable elements - // This avoids having to bind the functions during rendering - this.insertFn = {}; - Object.keys(this.operands).forEach((x) => { - this.insertFn[x] = this.insertElement.bind(this, x); - }); - Object.keys(this.totals).forEach((x) => { - this.insertFn[x] = this.insertElement.bind(this, x); - }); - Object.keys(this.indicators).forEach((x) => { - this.insertFn[x] = this.insertElement.bind(this, x); - }); - Object.keys(this.flags).forEach((flag) => { - this.insertFn[flag] = this.insertFlag.bind(this, flag); - }); - - // Create element filtering action - this.filterAction = Action.create('filter'); - this.filterAction - .map(({ data, complete, error }) => ({ data: data[1], complete, error })) - .debounceTime(75) - .subscribe((args) => { - const filter = args.data - .split(' ') - .filter(x => x.length); - this.setState({ filter }); - }); - - const formHtml = dataSet.dataEntryForm ? this.processFormData(dataSet.dataEntryForm) : ''; - - this.setState({ - formTitle: dataSet.displayName, - formHtml, - formStyle: dataSet.dataEntryForm && dataSet.dataEntryForm.style || 'NORMAL', - }, () => { - this._editor = window.CKEDITOR.replace('designTextarea', { - plugins: [ - 'a11yhelp', 'basicstyles', 'bidi', 'blockquote', - 'clipboard', 'colorbutton', 'colordialog', 'contextmenu', - 'dialogadvtab', 'div', 'elementspath', 'enterkey', - 'entities', 'filebrowser', 'find', 'floatingspace', - 'font', 'format', 'horizontalrule', 'htmlwriter', - 'image', 'indentlist', 'indentblock', 'justify', - 'link', 'list', 'liststyle', 'magicline', - 'maximize', 'forms', 'pastefromword', 'pastetext', - 'preview', 'removeformat', 'resize', 'selectall', - 'showblocks', 'showborders', 'sourcearea', 'specialchar', - 'stylescombo', 'tab', 'table', 'tabletools', - 'toolbar', 'undo', 'wsc', 'wysiwygarea', - ].join(','), - removePlugins: 'scayt,wsc,about', - allowedContent: true, - extraPlugins: 'div', - height: 500, - }); - this._editor.setData(this.state.formHtml); - - Rx.Observable.fromEventPattern((x) => { - this._editor.on('change', x); - }) - .debounceTime(250) - .subscribe(() => { - this.processFormData.call(this, this._editor.getData()); - }); - }); - }); - - this.getTranslation = this.context.d2.i18n.getTranslation.bind(this.context.d2.i18n); - this.handleSaveClick = this.handleSaveClick.bind(this); - this.handleCancelClick = this.handleCancelClick.bind(this); - this.handleDeleteClick = this.handleDeleteClick.bind(this); - this.handleStyleChange = this.handleStyleChange.bind(this); - - this.startResize = this.startResize.bind(this); - this.doResize = this.doResize.bind(this); - this.endResize = this.endResize.bind(this); - } - - componentWillUnmount() { - if (this._editor) { - this._editor.destroy(); - } - } - - handleSaveClick() { - const payload = { - style: this.state.formStyle, - htmlCode: this._editor.getData(), - }; - this.context.d2.Api.getApi().post(['dataSets', this.props.params.modelId, 'form'].join('/'), payload) - .then(() => { - log.info('Form saved successfully'); - snackActions.show({ message: this.getTranslation('form_saved') }); - goToRoute('list/dataSetSection/dataSet'); - }) - .catch((e) => { - log.warn('Failed to save form:', e); - snackActions.show({ - message: `${this.getTranslation('failed_to_save_form')}${e.message ? `: ${e.message}` : ''}`, - action: this.context.d2.i18n.getTranslation('ok'), - }); - }); - } - - handleCancelClick() { - goToRoute('list/dataSetSection/dataSet'); - } - - handleDeleteClick() { - snackActions.show({ - message: this.getTranslation('dataentryform_confirm_delete'), - action: 'confirm', - onActionTouchTap: () => { - this.context.d2.Api.getApi() - .delete(['dataEntryForms', modelToEditStore.state.dataEntryForm.id].join('/')) - .then(() => { - snackActions.show({ message: this.getTranslation('form_deleted') }); - goToRoute('list/dataSetSection/dataSet'); - }) - .catch((err) => { - log.error('Failed to delete form:', err); - snackActions.show({ message: this.getTranslation('failed_to_delete_form'), action: 'ok' }); - }); - }, - }); - } - - handleStyleChange(e, i, value) { - this.setState({ - formStyle: value, - }); - } - - startResize(e) { - this._startPos = e.clientX; - this._startWidth = this.state.paletteWidth; - window.addEventListener('mousemove', this.doResize); - window.addEventListener('mouseup', this.endResize); - } - - doResize(e) { - if (!e.buttons) { - // If no buttons are pressed it probably simply means we missed a mouseUp event - so stop resizing - this.endResize(); - } - e.preventDefault(); - e.stopPropagation(); - const width = clampPaletteWidth(this._startWidth + (this._startPos - e.clientX)); - window.requestAnimationFrame(() => { - this.setState({ - paletteWidth: width, - }); - }); - } - - endResize() { - window.removeEventListener('mousemove', this.doResize); - window.removeEventListener('mouseup', this.endResize); - } - - generateHtml(id, styleAttr, disabledAttr) { - const style = styleAttr ? ` style=${styleAttr}` : ''; - const disabled = disabledAttr || this.state.insertGrey ? ' disabled="disabled"' : ''; - - if (id.indexOf('-') !== -1) { - const label = this.operands && this.operands[id]; - const attr = `name="entryfield" title="${label}" value="[ ${label} ]"${style}${disabled}`.trim(); - return ``; - } else if (this.totals.hasOwnProperty(id)) { - const label = this.totals[id]; - const attr = `name="total" readonly title="${label}" value="[ ${label} ]"${style}${disabled}`.trim(); - return ``; - } else if (this.indicators.hasOwnProperty(id)) { - const label = this.indicators[id]; - const attr = `name="indicator" readonly title="${label}" value="[ ${label} ]"${style}${disabled}`.trim(); - return ``; - } - - log.warn('Failed to generate HTML for ID:', id); - return ''; - } - - processFormData(formData) { - const inHtml = formData.hasOwnProperty('htmlCode') ? formData.htmlCode : formData || ''; - let outHtml = ''; - - const usedIds = []; - - let inputElement = inputPattern.exec(inHtml); - let inPos = 0; - while (inputElement !== null) { - outHtml += inHtml.substr(inPos, inputElement.index - inPos); - inPos = inputPattern.lastIndex; - - const inputHtml = inputElement[0]; - const inputStyle = (/style="(.*?)"/.exec(inputHtml) || ['', ''])[1]; - const inputDisabled = /disabled/.exec(inputHtml) !== null; - - const idMatch = dataElementCategoryOptionIdPattern.exec(inputHtml); - const dataElementTotalMatch = dataElementPattern.exec(inputHtml); - const indicatorMatch = indicatorPattern.exec(inputHtml); - if (idMatch) { - const id = `${idMatch[1]}-${idMatch[2]}-val`; - usedIds.push(id); - outHtml += this.generateHtml(id, inputStyle, inputDisabled); - } else if (dataElementTotalMatch) { - const id = dataElementTotalMatch[1]; - usedIds.push(id); - outHtml += this.generateHtml(id, inputStyle, inputDisabled); - } else if (indicatorMatch) { - const id = indicatorMatch[1]; - usedIds.push(id); - outHtml += this.generateHtml(id, inputStyle, inputDisabled); - } else { - outHtml += inputHtml; - } - - inputElement = inputPattern.exec(inHtml); - } - outHtml += inHtml.substr(inPos); - - this.setState({ usedIds }); - - return outHtml; - } - - insertElement(id) { - if (this.state.usedIds.indexOf(id) !== -1) { - return; - } - - this._editor.insertHtml(this.generateHtml(id), 'unfiltered_html'); - this.setState(state => ({ usedIds: state.usedIds.concat(id) })); - // Move the current selection to just after the newly inserted element - const range = this._editor.getSelection().getRanges()[0]; - range.moveToElementEditablePosition(range.endContainer, true); - } - - insertFlag(img) { - this._editor.insertHtml(``, 'unfiltered_html'); - const range = this._editor.getSelection().getRanges()[0]; - range.moveToElementEditablePosition(range.endContainer, true); - } - - renderPaletteSection(keySet, label) { - const filteredItems = Object.keys(keySet) - .filter(key => !this.state.filter.length || this.state.filter.every( - filter => keySet[key].toLowerCase().indexOf(filter.toLowerCase()) !== -1 - )); - - const cellClass = label === this.state.expand ? 'cell expanded' : 'cell'; - - const expandClick = () => { - this.setState({ expand: label }); - }; - - return ( -
-
-
- {this.getTranslation(label)}: -
{filteredItems.length}
-
-
- { - filteredItems - .sort((a, b) => keySet[a] ? keySet[a].localeCompare(keySet[b]) : a.localeCompare(b)) - .map((key) => { - // Active items are items that are not already added to the form - const isActive = this.state.usedIds.indexOf(key) === -1; - const className = isActive ? 'item active' : 'item inactive'; - const name = keySet[key].name || keySet[key]; - return ( -
- {name} -
- ); - }) - } -
-
- ); - } - - renderPalette() { - const toggleGrey = (e, value) => { - this.setState({ insertGrey: value }); - }; - - return ( -
-
-
-
- -
-
- {this.renderPaletteSection(this.operands, 'data_elements')} - {this.renderPaletteSection(this.totals, 'totals')} - {this.renderPaletteSection(this.indicators, 'indicators')} - {this.renderPaletteSection(this.flags, 'flags')} -
- -
-
- ); - } - - render() { - return this.state.formHtml === undefined ? : ( -
- - {this.state.formTitle} {this.getTranslation('data_entry_form')} - - {this.renderPalette()} -