diff --git a/.eslintrc.json b/.eslintrc.json index 805abf68f8..71b3bec230 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,23 +1,39 @@ { "root": true, + "extends": "eslint:recommended", "env": { "es6": true, "node": true, "browser": true }, "parser": "@babel/eslint-parser", - "extends": "eslint:recommended", + "plugins": [ + "react" + ], "parserOptions": { - "sourceType": "module" + "ecmaVersion": 6, + "sourceType": "module", + "requireConfigFile": false }, - "plugins": ["react"], "rules": { + "indent": ["error", 2, { "SwitchCase": 1 }], + "linebreak-style": ["error", "unix"], + "no-trailing-spaces": 2, + "eol-last": 2, + "space-in-parens": ["error", "never"], + "no-multiple-empty-lines": 1, + "prefer-const": "error", + "space-infix-ops": "error", + "no-useless-escape": "off", + "require-atomic-updates": "off", "react/jsx-uses-vars": 1, "react/jsx-uses-react": 1, "react/react-in-jsx-scope": 1, "no-console": 0, "no-case-declarations": 0, "quotes": ["error", "single"], - "eol-last": ["error", "always"] + "no-var": "error", + "no-prototype-builtins": "off", + "curly": ["error", "all"] } } diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000000..783e7ca2e7 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,5 @@ +semi: true +trailingComma: "es5" +singleQuote: true +arrowParens: "avoid" +printWidth: 100 diff --git a/Parse-Dashboard/Authentication.js b/Parse-Dashboard/Authentication.js index 53f2b05b17..2ba4318f05 100644 --- a/Parse-Dashboard/Authentication.js +++ b/Parse-Dashboard/Authentication.js @@ -1,8 +1,8 @@ 'use strict'; -var bcrypt = require('bcryptjs'); -var csrf = require('csurf'); -var passport = require('passport'); -var LocalStrategy = require('passport-local').Strategy; +const bcrypt = require('bcryptjs'); +const csrf = require('csurf'); +const passport = require('passport'); +const LocalStrategy = require('passport-local').Strategy; const OTPAuth = require('otpauth') /** @@ -20,11 +20,11 @@ function Authentication(validUsers, useEncryptedPasswords, mountPath) { function initialize(app, options) { options = options || {}; - var self = this; + const self = this; passport.use('local', new LocalStrategy( {passReqToCallback:true}, function(req, username, password, cb) { - var match = self.authenticate({ + const match = self.authenticate({ name: username, pass: password, otpCode: req.body.otpCode @@ -47,13 +47,13 @@ function initialize(app, options) { }); passport.deserializeUser(function(username, cb) { - var user = self.authenticate({ + const user = self.authenticate({ name: username }, true); cb(null, user); }); - var cookieSessionSecret = options.cookieSessionSecret || require('crypto').randomBytes(64).toString('hex'); + const cookieSessionSecret = options.cookieSessionSecret || require('crypto').randomBytes(64).toString('hex'); const cookieSessionMaxAge = options.cookieSessionMaxAge; app.use(require('connect-flash')()); app.use(require('body-parser').urlencoded({ extended: true })); @@ -67,16 +67,16 @@ function initialize(app, options) { app.post('/login', csrf(), - (req,res,next) => { - let redirect = 'apps'; - if (req.body.redirect) { - redirect = req.body.redirect.charAt(0) === '/' ? req.body.redirect.substring(1) : req.body.redirect - } - return passport.authenticate('local', { - successRedirect: `${self.mountPath}${redirect}`, - failureRedirect: `${self.mountPath}login${req.body.redirect ? `?redirect=${req.body.redirect}` : ''}`, - failureFlash : true - })(req, res, next) + (req,res,next) => { + let redirect = 'apps'; + if (req.body.redirect) { + redirect = req.body.redirect.charAt(0) === '/' ? req.body.redirect.substring(1) : req.body.redirect + } + return passport.authenticate('local', { + successRedirect: `${self.mountPath}${redirect}`, + failureRedirect: `${self.mountPath}login${req.body.redirect ? `?redirect=${req.body.redirect}` : ''}`, + failureFlash : true + })(req, res, next) }, ); @@ -100,13 +100,13 @@ function authenticate(userToTest, usernameOnly) { let otpValid = true; //they provided auth - let isAuthenticated = userToTest && + const isAuthenticated = userToTest && //there are configured users this.validUsers && //the provided auth matches one of the users this.validUsers.find(user => { let isAuthenticated = false; - let usernameMatches = userToTest.name == user.user; + const usernameMatches = userToTest.name == user.user; if (usernameMatches && user.mfa && !usernameOnly) { if (!userToTest.otpCode) { otpMissingLength = user.mfaDigits || 6; @@ -126,7 +126,7 @@ function authenticate(userToTest, usernameOnly) { } } } - let passwordMatches = this.useEncryptedPasswords && !usernameOnly ? bcrypt.compareSync(userToTest.pass, user.pass) : userToTest.pass == user.pass; + const passwordMatches = this.useEncryptedPasswords && !usernameOnly ? bcrypt.compareSync(userToTest.pass, user.pass) : userToTest.pass == user.pass; if (usernameMatches && (usernameOnly || passwordMatches)) { isAuthenticated = true; matchingUsername = user.user; diff --git a/Parse-Dashboard/CLI/mfa.js b/Parse-Dashboard/CLI/mfa.js index 3f26a73a98..feab6433a8 100644 --- a/Parse-Dashboard/CLI/mfa.js +++ b/Parse-Dashboard/CLI/mfa.js @@ -129,7 +129,7 @@ const showInstructions = ({ app, username, passwordCopied, encrypt, config }) => `\n${getOrder()}. Make sure that "useEncryptedPasswords" is set to "true" in your dashboard configuration.` + '\n You chose to generate an encrypted password for this user.' + '\n Any existing users with non-encrypted passwords will require newly created, encrypted passwords.' - ); + ); } console.log( '\n------------------------------------------------------------------------------\n' @@ -198,7 +198,7 @@ module.exports = { } ]); const { algorithm, digits, period } = await getAlgorithm(); - const secret =generateSecret({ app, username, algorithm, digits, period }); + const secret = generateSecret({ app, username, algorithm, digits, period }); Object.assign(config, secret.config); showQR(secret.config.url); } diff --git a/Parse-Dashboard/app.js b/Parse-Dashboard/app.js index 2b4c73fca0..5ec6cc26b9 100644 --- a/Parse-Dashboard/app.js +++ b/Parse-Dashboard/app.js @@ -4,11 +4,11 @@ const path = require('path'); const packageJson = require('package-json'); const csrf = require('csurf'); const Authentication = require('./Authentication.js'); -var fs = require('fs'); +const fs = require('fs'); const currentVersionFeatures = require('../package.json').parseDashboardFeatures; -var newFeaturesInLatestVersion = []; +let newFeaturesInLatestVersion = []; packageJson('parse-dashboard', { version: 'latest', fullMetadata: true }) .then(latestPackage => { if (latestPackage.parseDashboardFeatures instanceof Array) { @@ -31,29 +31,29 @@ function getMount(mountPath) { } function checkIfIconsExistForApps(apps, iconsFolder) { - for (var i in apps) { - var currentApp = apps[i]; - var iconName = currentApp.iconName; - var path = iconsFolder + '/' + iconName; + for (const i in apps) { + const currentApp = apps[i]; + const iconName = currentApp.iconName; + const path = iconsFolder + '/' + iconName; fs.stat(path, function(err) { if (err) { - if ('ENOENT' == err.code) {// file does not exist - console.warn('Icon with file name: ' + iconName +' couldn\'t be found in icons folder!'); - } else { - console.log( - 'An error occurd while checking for icons, please check permission!'); - } + if ('ENOENT' == err.code) {// file does not exist + console.warn('Icon with file name: ' + iconName + ' couldn\'t be found in icons folder!'); + } else { + console.log( + 'An error occurd while checking for icons, please check permission!'); + } } else { - //every thing was ok so for example you can read it and send it to client + //every thing was ok so for example you can read it and send it to client } - } ); + }); } } module.exports = function(config, options) { options = options || {}; - var app = express(); + const app = express(); // Serve public files. app.use(express.static(path.join(__dirname,'public'))); @@ -72,7 +72,7 @@ module.exports = function(config, options) { // CSRF error handler app.use(function (err, req, res, next) { - if (err.code !== 'EBADCSRFTOKEN') return next(err) + if (err.code !== 'EBADCSRFTOKEN') {return next(err)} // handle CSRF token errors here res.status(403) @@ -81,8 +81,8 @@ module.exports = function(config, options) { // Serve the configuration. app.get('/parse-dashboard-config.json', function(req, res) { - let apps = config.apps.map((app) => Object.assign({}, app)); // make a copy - let response = { + const apps = config.apps.map((app) => Object.assign({}, app)); // make a copy + const response = { apps: apps, newFeaturesInLatestVersion: newFeaturesInLatestVersion, }; @@ -159,7 +159,7 @@ module.exports = function(config, options) { // running parse-dashboard from globally installed npm. if (config.iconsFolder) { try { - var stat = fs.statSync(config.iconsFolder); + const stat = fs.statSync(config.iconsFolder); if (stat.isDirectory()) { app.use('/appicons', express.static(config.iconsFolder)); //Check also if the icons really exist @@ -213,7 +213,7 @@ module.exports = function(config, options) { } return res.redirect(`${mountPath}login`); } - if (users && req.user && req.user.matchingUsername ) { + if (users && req.user && req.user.matchingUsername) { res.append('username', req.user.matchingUsername); } res.send(` diff --git a/Parse-Dashboard/server.js b/Parse-Dashboard/server.js index 76ac4bc398..2f47489e74 100644 --- a/Parse-Dashboard/server.js +++ b/Parse-Dashboard/server.js @@ -27,18 +27,18 @@ module.exports = (options) => { process.exit(-1); } - let explicitConfigFileProvided = !!options.config; + const explicitConfigFileProvided = !!options.config; let configFile = null; let configFromCLI = null; - let configServerURL = options.serverURL || process.env.PARSE_DASHBOARD_SERVER_URL; - let configGraphQLServerURL = options.graphQLServerURL || process.env.PARSE_DASHBOARD_GRAPHQL_SERVER_URL; - let configMasterKey = options.masterKey || process.env.PARSE_DASHBOARD_MASTER_KEY; - let configAppId = options.appId || process.env.PARSE_DASHBOARD_APP_ID; - let configAppName = options.appName || process.env.PARSE_DASHBOARD_APP_NAME; - let configUserId = options.userId || process.env.PARSE_DASHBOARD_USER_ID; - let configUserPassword = options.userPassword || process.env.PARSE_DASHBOARD_USER_PASSWORD; - let configSSLKey = options.sslKey || process.env.PARSE_DASHBOARD_SSL_KEY; - let configSSLCert = options.sslCert || process.env.PARSE_DASHBOARD_SSL_CERT; + const configServerURL = options.serverURL || process.env.PARSE_DASHBOARD_SERVER_URL; + const configGraphQLServerURL = options.graphQLServerURL || process.env.PARSE_DASHBOARD_GRAPHQL_SERVER_URL; + const configMasterKey = options.masterKey || process.env.PARSE_DASHBOARD_MASTER_KEY; + const configAppId = options.appId || process.env.PARSE_DASHBOARD_APP_ID; + const configAppName = options.appName || process.env.PARSE_DASHBOARD_APP_NAME; + const configUserId = options.userId || process.env.PARSE_DASHBOARD_USER_ID; + const configUserPassword = options.userPassword || process.env.PARSE_DASHBOARD_USER_PASSWORD; + const configSSLKey = options.sslKey || process.env.PARSE_DASHBOARD_SSL_KEY; + const configSSLCert = options.sslCert || process.env.PARSE_DASHBOARD_SSL_CERT; function handleSIGs(server) { const signals = { @@ -143,10 +143,10 @@ module.exports = (options) => { const app = express(); - if (allowInsecureHTTP || trustProxy || dev) app.enable('trust proxy'); + if (allowInsecureHTTP || trustProxy || dev) {app.enable('trust proxy');} config.data.trustProxy = trustProxy; - let dashboardOptions = { allowInsecureHTTP, cookieSessionSecret, dev, cookieSessionMaxAge }; + const dashboardOptions = { allowInsecureHTTP, cookieSessionSecret, dev, cookieSessionMaxAge }; app.use(mountPath, parseDashboard(config.data, dashboardOptions)); let server; if(!configSSLKey || !configSSLCert){ @@ -156,8 +156,8 @@ module.exports = (options) => { }); } else { // Start the server using SSL. - var privateKey = fs.readFileSync(configSSLKey); - var certificate = fs.readFileSync(configSSLCert); + const privateKey = fs.readFileSync(configSSLKey); + const certificate = fs.readFileSync(configSSLCert); server = require('https').createServer({ key: privateKey, diff --git a/README.md b/README.md index 0a12d09e8c..5c439fbf62 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,7 @@ Parse Dashboard is a standalone dashboard for managing your [Parse Server](https - [Parse Server](#parse-server) - [Node.js](#nodejs) - [Configuring Parse Dashboard](#configuring-parse-dashboard) + - [Options](#options) - [File](#file) - [Environment variables](#environment-variables) - [Multiple apps](#multiple-apps) @@ -42,6 +43,8 @@ Parse Dashboard is a standalone dashboard for managing your [Parse Server](https - [Other Configuration Options](#other-configuration-options) - [Prevent columns sorting](#prevent-columns-sorting) - [Custom order in the filter popup](#custom-order-in-the-filter-popup) + - [Persistent Filters](#persistent-filters) + - [Scripts](#scripts) - [Running as Express Middleware](#running-as-express-middleware) - [Deploying Parse Dashboard](#deploying-parse-dashboard) - [Preparing for Deployment](#preparing-for-deployment) @@ -102,14 +105,26 @@ Parse Dashboard is compatible with the following Parse Server versions. ### Node.js Parse Dashboard is continuously tested with the most recent releases of Node.js to ensure compatibility. We follow the [Node.js Long Term Support plan](https://github.com/nodejs/Release) and only test against versions that are officially supported and have not reached their end-of-life date. -| Version | Latest Version | End-of-Life | Compatible | -|------------|----------------|-------------|--------------| -| Node.js 14 | 14.20.1 | April 2023 | ✅ Yes | -| Node.js 16 | 16.17.0 | April 2024 | ✅ Yes | -| Node.js 18 | 18.9.0 | May 2025 | ✅ Yes | +| Version | Latest Version | End-of-Life | Compatible | +|------------|----------------|-------------|------------| +| Node.js 14 | 14.20.1 | April 2023 | ✅ Yes | +| Node.js 16 | 16.17.0 | April 2024 | ✅ Yes | +| Node.js 18 | 18.9.0 | May 2025 | ✅ Yes | ## Configuring Parse Dashboard +### Options + +| Parameter | Type | Optional | Default | Example | Description | +|----------------------------------------|---------------------|----------|---------|----------------------|---------------------------------------------------------------------------------------------------------------------------------------------| +| `apps` | Array<Object> | no | - | `[{ ... }, { ... }]` | The apps that are configured for the dashboard. | +| `apps.scripts` | Array<Object> | yes | `[]` | `[{ ... }, { ... }]` | The scripts that can be executed for that app. | +| `apps.scripts.title` | String | no | - | `'Delete User'` | The title that will be displayed in the data browser context menu and the script run confirmation dialog. | +| `apps.scripts.classes` | Array<String> | no | - | `['_User']` | The classes of Parse Objects for which the scripts can be executed. | +| `apps.scripts.cloudCodeFunction` | String | no | - | `'deleteUser'` | The name of the Parse Cloud Function to execute. | +| `apps.scripts.showConfirmationDialog` | Bool | yes | `false` | `true` | Is `true` if a confirmation dialog should be displayed before the script is executed, `false` if the script should be executed immediately. | +| `apps.scripts.confirmationDialogStyle` | String | yes | `info` | `critical` | The style of the confirmation dialog. Valid values: `info` (blue style), `critical` (red style). | + ### File You can also start the dashboard from the command line with a config file. To do this, create a new file called `parse-dashboard-config.json` inside your local Parse Dashboard directory hierarchy. The file should match the following format: @@ -362,6 +377,86 @@ For example: You can conveniently create a filter definition without having to write it by hand by first saving a filter in the data browser, then exporting the filter definition under *App Settings > Export Class Preferences*. +### Scripts + +You can specify scripts to execute Cloud Functions with the `scripts` option: + +```json +"apps": [ + { + "scripts": [ + { + "title": "Delete Account", + "classes": ["_User"], + "cloudCodeFunction": "deleteAccount", + "showConfirmationDialog": true, + "confirmationDialogStyle": "critical" + } + ] + } +] +``` + +Next, define the Cloud Function in Parse Server that will be called. The object that has been selected in the data browser will be made available as a request parameter: + +```js +Parse.Cloud.define('deleteAccount', async (req) => { + req.params.object.set('deleted', true); + await req.params.object.save(null, {useMasterKey: true}); +}, { + requireMaster: true +}); +``` + +The field which the script was invoked on can be accessed by `selectedField`: + +```js +Parse.Cloud.define('deleteAccount', async (req) => { + if (req.params.selectedField !== 'objectId') { + throw new Parse.Error(Parse.Error.SCRIPT_FAILED, 'Deleting accounts is only available on the objectId field.'); + } + req.params.object.set('deleted', true); + await req.params.object.save(null, {useMasterKey: true}); +}, { + requireMaster: true +}); +``` + +⚠️ Depending on your Parse Server version you may need to set the Parse Server option `encodeParseObjectInCloudFunction` to `true` so that the selected object in the data browser is made available in the Cloud Function as an instance of `Parse.Object`. If the option is not set, is set to `false`, or you are using an older version of Parse Server, the object is made available as a plain JavaScript object and needs to be converted from a JSON object to a `Parse.Object` instance with `req.params.object = Parse.Object.fromJSON(req.params.object);`, before you can call any `Parse.Object` properties and methods on it. + +For older versions of Parse Server: + +
+Parse Server >=4.4.0 <6.2.0 + +```js +Parse.Cloud.define('deleteAccount', async (req) => { + req.params.object = Parse.Object.fromJSON(req.params.object); + req.params.object.set('deleted', true); + await req.params.object.save(null, {useMasterKey: true}); +}, { + requireMaster: true +}); +``` + +
+ +
+Parse Server >=2.1.4 <4.4.0 + +```js +Parse.Cloud.define('deleteAccount', async (req) => { + if (!req.master || !req.params.object) { + throw 'Unauthorized'; + } + req.params.object = Parse.Object.fromJSON(req.params.object); + req.params.object.set('deleted', true); + await req.params.object.save(null, {useMasterKey: true}); +}); +``` + +
+ # Running as Express Middleware Instead of starting Parse Dashboard with the CLI, you can also run it as an [express](https://github.com/expressjs/express) middleware. @@ -445,6 +540,24 @@ var dashboard = new ParseDashboard({ }); ``` +## Security Checks + +You can view the security status of your Parse Server by enabling the dashboard option `enableSecurityChecks`, and visiting App Settings > Security. + +```javascript +const dashboard = new ParseDashboard({ + "apps": [ + { + "serverURL": "http://localhost:1337/parse", + "appId": "myAppId", + "masterKey": "myMasterKey", + "appName": "MyApp" + "enableSecurityChecks": true + } + ], +}); +``` + ### Configuring Basic Authentication diff --git a/changelogs/CHANGELOG_alpha.md b/changelogs/CHANGELOG_alpha.md index 31fa633b72..d7222e0967 100644 --- a/changelogs/CHANGELOG_alpha.md +++ b/changelogs/CHANGELOG_alpha.md @@ -1,3 +1,101 @@ +# [5.2.0-alpha.28](https://github.com/ParsePlatform/parse-dashboard/compare/5.2.0-alpha.27...5.2.0-alpha.28) (2023-08-27) + + +### Features + +* Add security checks page ([#2491](https://github.com/ParsePlatform/parse-dashboard/issues/2491)) ([103b9c6](https://github.com/ParsePlatform/parse-dashboard/commit/103b9c61d152487898062485b40f11ecdac3d2e7)) + +# [5.2.0-alpha.27](https://github.com/ParsePlatform/parse-dashboard/compare/5.2.0-alpha.26...5.2.0-alpha.27) (2023-06-30) + + +### Bug Fixes + +* Adding a file when adding a new row in the data browser doesn't show filename ([#2471](https://github.com/ParsePlatform/parse-dashboard/issues/2471)) ([5bbb94e](https://github.com/ParsePlatform/parse-dashboard/commit/5bbb94e5b5266af5ed770d0241605eb859699831)) + +# [5.2.0-alpha.26](https://github.com/ParsePlatform/parse-dashboard/compare/5.2.0-alpha.25...5.2.0-alpha.26) (2023-06-30) + + +### Bug Fixes + +* File extension is hidden in file field when editing object in modal dialog in data browser ([#2472](https://github.com/ParsePlatform/parse-dashboard/issues/2472)) ([8df4e4d](https://github.com/ParsePlatform/parse-dashboard/commit/8df4e4d9abf2ef9e487a48b209f33bedc03b55a3)) + +# [5.2.0-alpha.25](https://github.com/ParsePlatform/parse-dashboard/compare/5.2.0-alpha.24...5.2.0-alpha.25) (2023-06-29) + + +### Bug Fixes + +* Incorrect highlight maker position in class list in data browser ([#2490](https://github.com/ParsePlatform/parse-dashboard/issues/2490)) ([8c28d24](https://github.com/ParsePlatform/parse-dashboard/commit/8c28d245cfe5d9558ffd276b9660f73449c4f35a)) + +# [5.2.0-alpha.24](https://github.com/ParsePlatform/parse-dashboard/compare/5.2.0-alpha.23...5.2.0-alpha.24) (2023-06-28) + + +### Features + +* Add support for confirmation dialog before script execution in data browser ([#2481](https://github.com/ParsePlatform/parse-dashboard/issues/2481)) ([64d3913](https://github.com/ParsePlatform/parse-dashboard/commit/64d391320bbdb519af8ff93fe8579315ef48e36e)) + +# [5.2.0-alpha.23](https://github.com/ParsePlatform/parse-dashboard/compare/5.2.0-alpha.22...5.2.0-alpha.23) (2023-06-28) + + +### Features + +* Add parameter `selectedField` to script payload to determine which object field was selected when script was invoked ([#2483](https://github.com/ParsePlatform/parse-dashboard/issues/2483)) ([e98d653](https://github.com/ParsePlatform/parse-dashboard/commit/e98d653b96787720dad5310c5af98869e2ac2923)) + +# [5.2.0-alpha.22](https://github.com/ParsePlatform/parse-dashboard/compare/5.2.0-alpha.21...5.2.0-alpha.22) (2023-06-27) + + +### Features + +* Add refresh button to Cloud Config page ([#2480](https://github.com/ParsePlatform/parse-dashboard/issues/2480)) ([be212b0](https://github.com/ParsePlatform/parse-dashboard/commit/be212b0ad6c777f7c5ee9a74cac0affa63faa1c1)) + +# [5.2.0-alpha.21](https://github.com/ParsePlatform/parse-dashboard/compare/5.2.0-alpha.20...5.2.0-alpha.21) (2023-06-24) + + +### Reverts + +* fix: Vertical scrollbar in data browser is outside visible area when scrolling horizontally ([#2457](https://github.com/ParsePlatform/parse-dashboard/issues/2457)) ([#2477](https://github.com/ParsePlatform/parse-dashboard/issues/2477)) ([2f1d84e](https://github.com/ParsePlatform/parse-dashboard/commit/2f1d84e41c24507b516b933037807f1061182991)) + +# [5.2.0-alpha.20](https://github.com/ParsePlatform/parse-dashboard/compare/5.2.0-alpha.19...5.2.0-alpha.20) (2023-06-23) + + +### Bug Fixes + +* Selecting a saved filter in data browser also highlights other filters with equal names ([#2466](https://github.com/ParsePlatform/parse-dashboard/issues/2466)) ([35360fe](https://github.com/ParsePlatform/parse-dashboard/commit/35360fec68edbca619075227960062859bb9db2e)) + +# [5.2.0-alpha.19](https://github.com/ParsePlatform/parse-dashboard/compare/5.2.0-alpha.18...5.2.0-alpha.19) (2023-06-23) + + +### Features + +* Add Cloud Function execution on Parse Object in data browser ([#2409](https://github.com/ParsePlatform/parse-dashboard/issues/2409)) ([996ce91](https://github.com/ParsePlatform/parse-dashboard/commit/996ce916bfedb92c36deede4c234dde8c0554cbb)) + +# [5.2.0-alpha.18](https://github.com/ParsePlatform/parse-dashboard/compare/5.2.0-alpha.17...5.2.0-alpha.18) (2023-06-20) + + +### Bug Fixes + +* Pasting location coordinates into field of type `GeoPoint` does not work in data browser ([#2464](https://github.com/ParsePlatform/parse-dashboard/issues/2464)) ([a8ce343](https://github.com/ParsePlatform/parse-dashboard/commit/a8ce3436a4ffe76ccf892965fa21dc2a467e2d14)) + +# [5.2.0-alpha.17](https://github.com/ParsePlatform/parse-dashboard/compare/5.2.0-alpha.16...5.2.0-alpha.17) (2023-06-19) + + +### Features + +* Add typing with auto-complete to select a filter field in the data browser ([#2463](https://github.com/ParsePlatform/parse-dashboard/issues/2463)) ([257f76b](https://github.com/ParsePlatform/parse-dashboard/commit/257f76bbf8d1e880e3b7b704edee2eebf76451c8)) + +# [5.2.0-alpha.16](https://github.com/ParsePlatform/parse-dashboard/compare/5.2.0-alpha.15...5.2.0-alpha.16) (2023-06-19) + + +### Features + +* Reopen last opened class when navigating to data browser ([#2468](https://github.com/ParsePlatform/parse-dashboard/issues/2468)) ([3d7148e](https://github.com/ParsePlatform/parse-dashboard/commit/3d7148e75a6e9eaeeb7cbb546885b5916f6025bb)) + +# [5.2.0-alpha.15](https://github.com/ParsePlatform/parse-dashboard/compare/5.2.0-alpha.14...5.2.0-alpha.15) (2023-06-11) + + +### Bug Fixes + +* Vertical scrollbar in data browser is outside visible area when scrolling horizontally ([#2457](https://github.com/ParsePlatform/parse-dashboard/issues/2457)) ([5acac3f](https://github.com/ParsePlatform/parse-dashboard/commit/5acac3fb5c74cbb24ec96b721d874fbc36096c39)) + # [5.2.0-alpha.14](https://github.com/ParsePlatform/parse-dashboard/compare/5.2.0-alpha.13...5.2.0-alpha.14) (2023-06-10) diff --git a/changelogs/CHANGELOG_beta.md b/changelogs/CHANGELOG_beta.md index 52dd2fb8ba..269484967d 100644 --- a/changelogs/CHANGELOG_beta.md +++ b/changelogs/CHANGELOG_beta.md @@ -1,3 +1,29 @@ +# [5.3.0-beta.1](https://github.com/ParsePlatform/parse-dashboard/compare/5.2.0...5.3.0-beta.1) (2023-09-15) + + +### Bug Fixes + +* Adding a file when adding a new row in the data browser doesn't show filename ([#2471](https://github.com/ParsePlatform/parse-dashboard/issues/2471)) ([5bbb94e](https://github.com/ParsePlatform/parse-dashboard/commit/5bbb94e5b5266af5ed770d0241605eb859699831)) +* File extension is hidden in file field when editing object in modal dialog in data browser ([#2472](https://github.com/ParsePlatform/parse-dashboard/issues/2472)) ([8df4e4d](https://github.com/ParsePlatform/parse-dashboard/commit/8df4e4d9abf2ef9e487a48b209f33bedc03b55a3)) +* Incorrect highlight maker position in class list in data browser ([#2490](https://github.com/ParsePlatform/parse-dashboard/issues/2490)) ([8c28d24](https://github.com/ParsePlatform/parse-dashboard/commit/8c28d245cfe5d9558ffd276b9660f73449c4f35a)) +* Pasting location coordinates into field of type `GeoPoint` does not work in data browser ([#2464](https://github.com/ParsePlatform/parse-dashboard/issues/2464)) ([a8ce343](https://github.com/ParsePlatform/parse-dashboard/commit/a8ce3436a4ffe76ccf892965fa21dc2a467e2d14)) +* Selecting a saved filter in data browser also highlights other filters with equal names ([#2466](https://github.com/ParsePlatform/parse-dashboard/issues/2466)) ([35360fe](https://github.com/ParsePlatform/parse-dashboard/commit/35360fec68edbca619075227960062859bb9db2e)) +* Vertical scrollbar in data browser is outside visible area when scrolling horizontally ([#2457](https://github.com/ParsePlatform/parse-dashboard/issues/2457)) ([5acac3f](https://github.com/ParsePlatform/parse-dashboard/commit/5acac3fb5c74cbb24ec96b721d874fbc36096c39)) + +### Features + +* Add Cloud Function execution on Parse Object in data browser ([#2409](https://github.com/ParsePlatform/parse-dashboard/issues/2409)) ([996ce91](https://github.com/ParsePlatform/parse-dashboard/commit/996ce916bfedb92c36deede4c234dde8c0554cbb)) +* Add parameter `selectedField` to script payload to determine which object field was selected when script was invoked ([#2483](https://github.com/ParsePlatform/parse-dashboard/issues/2483)) ([e98d653](https://github.com/ParsePlatform/parse-dashboard/commit/e98d653b96787720dad5310c5af98869e2ac2923)) +* Add refresh button to Cloud Config page ([#2480](https://github.com/ParsePlatform/parse-dashboard/issues/2480)) ([be212b0](https://github.com/ParsePlatform/parse-dashboard/commit/be212b0ad6c777f7c5ee9a74cac0affa63faa1c1)) +* Add security checks page ([#2491](https://github.com/ParsePlatform/parse-dashboard/issues/2491)) ([103b9c6](https://github.com/ParsePlatform/parse-dashboard/commit/103b9c61d152487898062485b40f11ecdac3d2e7)) +* Add support for confirmation dialog before script execution in data browser ([#2481](https://github.com/ParsePlatform/parse-dashboard/issues/2481)) ([64d3913](https://github.com/ParsePlatform/parse-dashboard/commit/64d391320bbdb519af8ff93fe8579315ef48e36e)) +* Add typing with auto-complete to select a filter field in the data browser ([#2463](https://github.com/ParsePlatform/parse-dashboard/issues/2463)) ([257f76b](https://github.com/ParsePlatform/parse-dashboard/commit/257f76bbf8d1e880e3b7b704edee2eebf76451c8)) +* Reopen last opened class when navigating to data browser ([#2468](https://github.com/ParsePlatform/parse-dashboard/issues/2468)) ([3d7148e](https://github.com/ParsePlatform/parse-dashboard/commit/3d7148e75a6e9eaeeb7cbb546885b5916f6025bb)) + +### Reverts + +* fix: Vertical scrollbar in data browser is outside visible area when scrolling horizontally ([#2457](https://github.com/ParsePlatform/parse-dashboard/issues/2457)) ([#2477](https://github.com/ParsePlatform/parse-dashboard/issues/2477)) ([2f1d84e](https://github.com/ParsePlatform/parse-dashboard/commit/2f1d84e41c24507b516b933037807f1061182991)) + # [5.2.0-beta.2](https://github.com/ParsePlatform/parse-dashboard/compare/5.2.0-beta.1...5.2.0-beta.2) (2023-06-10) diff --git a/ci/nodeEngineCheck.js b/ci/nodeEngineCheck.js index 2b71a02d47..9af1544237 100644 --- a/ci/nodeEngineCheck.js +++ b/ci/nodeEngineCheck.js @@ -46,7 +46,7 @@ class NodeEngineCheck { const dirents = await fs.readdir(basePath, { withFileTypes: true }); const validFiles = dirents.filter(d => d.name.toLowerCase() == 'package.json').map(d => path.join(basePath, d.name)); files.push(...validFiles); - + // For each directory entry for (const dirent of dirents) { if (dirent.isDirectory()) { diff --git a/package-lock.json b/package-lock.json index 6c5cdc29ce..d8375073fc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "parse-dashboard", - "version": "5.2.0", + "version": "5.3.0-beta.1", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -9032,6 +9032,12 @@ "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", "dev": true }, + "husky": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/husky/-/husky-8.0.3.tgz", + "integrity": "sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==", + "dev": true + }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -15452,6 +15458,12 @@ "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", "dev": true }, + "prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "dev": true + }, "pretty-format": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", @@ -16825,9 +16837,9 @@ } }, "semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "version": "7.5.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.2.tgz", + "integrity": "sha512-SoftuTROv/cRjCze/scjGyiDtcUyxw1rgYQSZY7XTmtR5hX+dm76iDbTH8TkLPHCQmlbQVSSbNZCPM2hb0knnQ==", "requires": { "lru-cache": "^6.0.0" } diff --git a/package.json b/package.json index 4499961981..56765b75c6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "parse-dashboard", - "version": "5.2.0", + "version": "5.3.0-beta.1", "repository": { "type": "git", "url": "https://github.com/ParsePlatform/parse-dashboard" @@ -70,7 +70,7 @@ "react-popper-tooltip": "4.4.2", "react-router-dom": "6.4.1", "regenerator-runtime": "0.13.11", - "semver": "7.3.7", + "semver": "7.5.2", "typescript": "4.8.3" }, "devDependencies": { @@ -96,11 +96,13 @@ "eslint-plugin-jest": "27.0.4", "eslint-plugin-react": "7.31.8", "http-server": "14.0.0", + "husky": "8.0.3", "jest": "29.1.2", "jest-environment-jsdom": "29.1.2", "madge": "5.0.1", "marked": "4.0.10", "null-loader": "4.0.1", + "prettier": "2.8.8", "puppeteer": "18.0.5", "react-test-renderer": "16.13.1", "request": "2.88.2", @@ -122,7 +124,9 @@ "pig": "http-server ./PIG -p 4041 -s & webpack --config webpack/PIG.config.js --progress --watch", "build": "webpack --node-env=production --config webpack/production.config.js && webpack --config webpack/PIG.config.js", "test": "jest", - "lint": "eslint . --ignore-path .gitignore --cache", + "lint": "eslint --ignore-path .gitignore --cache ./", + "lint:fix": "DEBUG=eslint:cli-engine eslint --ignore-path .gitignore --fix --cache ./", + "prettier": "prettier --write '{src,webpack}/**/*.js'", "generate": "node scripts/generate.js", "prepare": "webpack --config webpack/publish.config.js --progress", "start": "node ./Parse-Dashboard/index.js", @@ -152,5 +156,17 @@ "react-addons-test-utils", "fbjs" ] + }, + "husky": { + "hooks": { + "pre-commit": "lint-staged" + } + }, + "lint-staged": { + "{src,webpack}/{**/*,*}.js": [ + "prettier --write", + "eslint --fix --cache", + "git add" + ] } } diff --git a/release.config.js b/release.config.js index 9858e3ce9f..082f461781 100644 --- a/release.config.js +++ b/release.config.js @@ -116,7 +116,7 @@ async function readFile(filePath) { function getReleaseComment() { const url = repositoryUrl + '/releases/tag/${nextRelease.gitTag}'; - let comment = '🎉 This change has been released in version [${nextRelease.version}](' + url + ')'; + const comment = '🎉 This change has been released in version [${nextRelease.version}](' + url + ')'; return comment; } diff --git a/scripts/generate.js b/scripts/generate.js index 36070e8728..3fbc000d95 100644 --- a/scripts/generate.js +++ b/scripts/generate.js @@ -17,7 +17,7 @@ const pigDir = path.join(__dirname, '..', 'src','parse-interface-guide'); const testDir = path.join(__dirname, '..', 'src','lib', 'tests'); function padding(length) { - let space = []; + const space = []; for (let i = 0; i < length; i++) { space[i] = ' '; } @@ -26,7 +26,7 @@ function padding(length) { function generateReact(name) { return ( -`/* + `/* * Copyright (c) 2016-present, Parse, LLC * All rights reserved. * @@ -49,7 +49,7 @@ ${name}.propTypes = { function generateExample(name) { return ( -`/* + `/* * Copyright (c) 2016-present, Parse, LLC * All rights reserved. * @@ -57,7 +57,7 @@ function generateExample(name) { * the root directory of this source tree. */ import React${padding(name.length - 5)} from 'react'; -import ${name}${padding(5 - name.length)} f`+ 'rom' +` 'components/${name}/${name}.react'; +import ${name}${padding(5 - name.length)} f` + 'rom' + ` 'components/${name}/${name}.react'; export const component = ${name}; @@ -74,7 +74,7 @@ export const demos = [ function generateTest(name) { return ( -`/* + `/* * Copyright (c) 2016-present, Parse, LLC * All rights reserved. * @@ -109,18 +109,18 @@ function updateComponentMap(name) { } let spaces = ''; - for (let i = 0; i Promise.resolve([]) + find: () => Promise.resolve([]), }; - let user = text.substring(5); + const user = text.substring(5); entry = user; userQuery = new Parse.Query.or( new Parse.Query(Parse.User).equalTo('username', user), @@ -37,9 +36,9 @@ function validateEntry(text, returnInvalid = true) { type = 'role'; // no need to query users userQuery = { - find: () => Promise.resolve([]) + find: () => Promise.resolve([]), }; - let role = text.substring(5); + const role = text.substring(5); entry = role; roleQuery = new Parse.Query.or( new Parse.Query(Parse.Role).equalTo('name', role), @@ -60,15 +59,15 @@ function validateEntry(text, returnInvalid = true) { return Promise.all([ userQuery.find({ useMasterKey: true }), - roleQuery.find({ useMasterKey: true }) + roleQuery.find({ useMasterKey: true }), ]).then(([user, role]) => { if (user.length > 0) { return { entry: user[0], type: 'user' }; } else if (role.length > 0) { return { entry: role[0], type: 'role' }; } else { - if(returnInvalid) { - return Promise.resolve({entry, type}) + if (returnInvalid) { + return Promise.resolve({ entry, type }); } return Promise.reject(); } @@ -79,9 +78,9 @@ function toPerms(acl) { if (!acl) { return { read: { '*': true }, write: { '*': true } }; } - let json = acl.toJSON(); - let perms = { read: {}, write: {} }; - for (let key in json) { + const json = acl.toJSON(); + const perms = { read: {}, write: {} }; + for (const key in json) { if (json[key].read) { perms.read[key] = true; } @@ -93,13 +92,13 @@ function toPerms(acl) { } function toACL(perms) { - let acl = {}; - for (let key in perms.read) { + const acl = {}; + for (const key in perms.read) { if (perms.read[key]) { acl[key] = { read: true }; } } - for (let key in perms.write) { + for (const key in perms.write) { if (perms.write[key]) { if (acl[key]) { acl[key].write = true; @@ -111,20 +110,25 @@ function toACL(perms) { return new Parse.ACL(acl); } -let ACLEditor = ({ value, onCommit }) => ( +const ACLEditor = ({ value, onCommit }) => ( Learn more about ACLs and app security} + confirmText="Save ACL" + details={ + + Learn more about ACLs and app security + + } permissions={toPerms(value)} validateEntry={validateEntry} onCancel={() => { onCommit(value); }} - onConfirm={(perms) => { + onConfirm={perms => { onCommit(toACL(perms)); - }} /> + }} + /> ); export default ACLEditor; diff --git a/src/components/AppBadge/AppBadge.react.js b/src/components/AppBadge/AppBadge.react.js index b0cf290407..c426819579 100644 --- a/src/components/AppBadge/AppBadge.react.js +++ b/src/components/AppBadge/AppBadge.react.js @@ -6,10 +6,10 @@ * the root directory of this source tree. */ import PropTypes from 'lib/PropTypes'; -import React from 'react'; -import styles from 'components/AppBadge/AppBadge.scss'; +import React from 'react'; +import styles from 'components/AppBadge/AppBadge.scss'; -let AppBadge = ({ production }) => ( +const AppBadge = ({ production }) => ( {production ? 'PROD' : 'DEV'} @@ -18,7 +18,5 @@ let AppBadge = ({ production }) => ( export default AppBadge; AppBadge.propTypes = { - production: PropTypes.bool.describe( - 'Indicates whether the app is in production mode or not.' - ) + production: PropTypes.bool.describe('Indicates whether the app is in production mode or not.'), }; diff --git a/src/components/Autocomplete/Autocomplete.example.js b/src/components/Autocomplete/Autocomplete.example.js index 7ecec5ab61..06df47cac6 100644 --- a/src/components/Autocomplete/Autocomplete.example.js +++ b/src/components/Autocomplete/Autocomplete.example.js @@ -5,7 +5,7 @@ * This source code is licensed under the license found in the LICENSE file in * the root directory of this source tree. */ -import React from 'react'; +import React from 'react'; import Autocomplete from 'components/Autocomplete/Autocomplete.react'; export const component = Autocomplete; @@ -15,7 +15,7 @@ class AutocompleteDemo extends React.Component { super(); this.state = { - suggestions: ['aaa', 'abc', 'xxx', 'xyz'] + suggestions: ['aaa', 'abc', 'xxx', 'xyz'], }; this.onSubmit = input => console.log('onSubmit: ' + input); @@ -23,11 +23,8 @@ class AutocompleteDemo extends React.Component { console.log(`input: ${input}`); }; this.buildLabel = input => - input.length > 0 - ? `You've typed ${input.length} characters` - : 'Start typing'; - this.buildSuggestions = input => - this.state.suggestions.filter(s => s.startsWith(input)); + input.length > 0 ? `You've typed ${input.length} characters` : 'Start typing'; + this.buildSuggestions = input => this.state.suggestions.filter(s => s.startsWith(input)); } render() { @@ -36,11 +33,11 @@ class AutocompleteDemo extends React.Component { inputStyle={{ width: '400px', padding: '0 6px', - margin: '10px 20px' + margin: '10px 20px', }} suggestionsStyle={{ margin: '-6px 0px 0px 20px', - width: '400px' + width: '400px', }} locked={true} onChange={this.onUserInput} @@ -59,6 +56,6 @@ export const demos = [
- ) - } + ), + }, ]; diff --git a/src/components/Autocomplete/Autocomplete.react.js b/src/components/Autocomplete/Autocomplete.react.js index 785f61656b..4904fffc9f 100644 --- a/src/components/Autocomplete/Autocomplete.react.js +++ b/src/components/Autocomplete/Autocomplete.react.js @@ -5,11 +5,11 @@ * This source code is licensed under the license found in the LICENSE file in * the root directory of this source tree. */ -import Position from 'lib/Position'; -import PropTypes from 'lib/PropTypes' +import Position from 'lib/Position'; +import PropTypes from 'lib/PropTypes'; import React, { Component } from 'react'; -import styles from 'components/Autocomplete/Autocomplete.scss'; -import SuggestionsList from 'components/SuggestionsList/SuggestionsList.react'; +import styles from 'components/Autocomplete/Autocomplete.scss'; +import SuggestionsList from 'components/SuggestionsList/SuggestionsList.react'; export default class Autocomplete extends Component { constructor(props) { @@ -23,6 +23,7 @@ export default class Autocomplete extends Component { this.onBlur = this.onBlur.bind(this); this.onClick = this.onClick.bind(this); + this.onMouseDown = this.onMouseDown.bind(this); this.onChange = this.onChange.bind(this); this.onKeyDown = this.onKeyDown.bind(this); this.onInputClick = this.onInputClick.bind(this); @@ -46,12 +47,13 @@ export default class Autocomplete extends Component { }; this.state = { + valueFromSuggestion: props.strict ? props.value ?? props.suggestions[0] : '', activeSuggestion: 0, filteredSuggestions: [], showSuggestions: false, - userInput: '', + userInput: props.strict ? props.value ?? props.suggestions[0] : '', label: props.label, - position: null + position: null, }; } @@ -60,6 +62,7 @@ export default class Autocomplete extends Component { this.fieldRef.current.addEventListener('scroll', this.handleScroll); this.recalculatePosition(); this._ignoreBlur = false; + this._suggestionClicked = false; } componentWillUnmount() { @@ -70,9 +73,7 @@ export default class Autocomplete extends Component { getPosition() { const node = this.fieldRef.current; - let newPosition = this.props.fixed - ? Position.inWindow(node) - : Position.inDocument(node); + const newPosition = this.props.fixed ? Position.inWindow(node) : Position.inDocument(node); newPosition.y += node.offsetHeight; @@ -94,9 +95,8 @@ export default class Autocomplete extends Component { const filteredSuggestions = buildSuggestions ? buildSuggestions(userInput) : suggestions.filter( - suggestion => - suggestion.toLowerCase().indexOf(userInput.toLowerCase()) > -1 - ); + suggestion => suggestion.toLowerCase().indexOf(userInput.toLowerCase()) > -1 + ); return filteredSuggestions; } @@ -117,14 +117,19 @@ export default class Autocomplete extends Component { showSuggestions: true, userInput, label, - error: undefined + error: undefined, }); - this.props.onChange && this.props.onChange(userInput); + if (!this.props.strict) { + this.props.onChange && this.props.onChange(userInput); + } } onClick(e) { const userInput = e.currentTarget.innerText; + if (this.props.strict) { + this.props.onChange && this.props.onChange(userInput); + } const label = this.props.label || this.props.buildLabel(userInput); this.inputRef.current.focus(); @@ -136,7 +141,7 @@ export default class Autocomplete extends Component { filteredSuggestions: [], showSuggestions: false, userInput, - label + label, }, () => { this.props.onClick && this.props.onClick(e); @@ -144,15 +149,34 @@ export default class Autocomplete extends Component { ); } + onMouseDown(e) { + this._suggestionClicked = true; + this.props.onMouseDown && this.props.onMouseDown(e); + } + onFocus(e) { if (!this._ignoreBlur && !this.state.showSuggestions) { this._ignoreBlur = true; } - + if (this.props.strict) { + e.target.select(); + } this.activate(e); } onBlur(e) { + if (this.props.strict) { + if (!this._suggestionClicked) { + if (!this.props.suggestions.includes(this.state.userInput)) { + this.setState({ userInput: this.state.valueFromSuggestion }); + this.props.onChange && this.props.onChange(this.state.valueFromSuggestion); + } else { + this.setState({ valueFromSuggestion: this.state.userInput }); + this.props.onChange && this.props.onChange(this.state.userInput); + } + } + this._suggestionClicked = false; + } this.props.onBlur && this.props.onBlur(e); } @@ -186,7 +210,7 @@ export default class Autocomplete extends Component { filteredSuggestions, position, label, - showSuggestions: true + showSuggestions: true, }, () => { this.props.onFocus && this.props.onFocus(); @@ -199,7 +223,7 @@ export default class Autocomplete extends Component { { active: false, showSuggestions: false, - activeSuggestion: 0 + activeSuggestion: 0, }, () => { this.props.onBlur && this.props.onBlur(); @@ -213,7 +237,7 @@ export default class Autocomplete extends Component { active: false, activeSuggestion: 0, showSuggestions: false, - userInput: '' + userInput: '', }, () => { this.inputRef.current.blur(); @@ -227,8 +251,8 @@ export default class Autocomplete extends Component { // Enter const { userInput } = this.state; - if (e.keyCode === 13) { - if (userInput && userInput.length > 0) { + if (e.keyCode === 13) { + if (userInput && userInput.length > 0) { this.props.onSubmit(userInput); } } else if (e.keyCode === 9) { @@ -243,7 +267,7 @@ export default class Autocomplete extends Component { active: true, activeSuggestion: 0, showSuggestions: false, - userInput: filteredSuggestions[activeSuggestion] + userInput: filteredSuggestions[activeSuggestion], }); } else if (e.keyCode === 38) { // arrow up @@ -253,7 +277,7 @@ export default class Autocomplete extends Component { this.setState({ active: false, - activeSuggestion: activeSuggestion - 1 + activeSuggestion: activeSuggestion - 1, }); } else if (e.keyCode === 40) { // arrow down @@ -263,7 +287,7 @@ export default class Autocomplete extends Component { this.setState({ active: false, - activeSuggestion: activeSuggestion + 1 + activeSuggestion: activeSuggestion + 1, }); } } @@ -279,9 +303,17 @@ export default class Autocomplete extends Component { onChange, onClick, onBlur, + onMouseDown, onFocus, onKeyDown, - props: { suggestionsStyle, inputStyle, placeholder, error }, + props: { + suggestionsStyle, + suggestionsItemStyle, + inputStyle, + containerStyle, + placeholder, + error, + }, state: { activeSuggestion, filteredSuggestions, @@ -289,18 +321,15 @@ export default class Autocomplete extends Component { userInput, hidden, active, - label - } + label, + }, } = this; const fieldClassName = [ styles.field, active && styles.active, error ? styles.error : undefined, - showSuggestions && - !hidden && - filteredSuggestions.length && - styles.dropdown + showSuggestions && !hidden && filteredSuggestions.length && styles.dropdown, ].join(' '); const inputClasses = [error && styles.error].join(' '); @@ -314,19 +343,21 @@ export default class Autocomplete extends Component { onExternalClick={onExternalClick} suggestions={filteredSuggestions} suggestionsStyle={suggestionsStyle} + suggestionsItemStyle={suggestionsItemStyle} activeSuggestion={activeSuggestion} onClick={onClick} + onMouseDown={onMouseDown} /> ); } return ( -
+
this.setState({ value })} /> + onChange={value => this.setState({ value })} + />
); } diff --git a/src/components/BrowserCell/BrowserCell.react.js b/src/components/BrowserCell/BrowserCell.react.js index 94d5cfc2b8..52323ecaa5 100644 --- a/src/components/BrowserCell/BrowserCell.react.js +++ b/src/components/BrowserCell/BrowserCell.react.js @@ -5,16 +5,18 @@ * This source code is licensed under the license found in the LICENSE file in * the root directory of this source tree. */ -import * as Filters from 'lib/Filters'; -import { List, Map } from 'immutable'; -import { dateStringUTC } from 'lib/DateUtils'; -import getFileName from 'lib/getFileName'; -import Parse from 'parse'; -import Pill from 'components/Pill/Pill.react'; -import React, { Component } from 'react'; -import styles from 'components/BrowserCell/BrowserCell.scss'; -import baseStyles from 'stylesheets/base.scss'; -import * as ColumnPreferences from 'lib/ColumnPreferences'; +import * as Filters from 'lib/Filters'; +import { List, Map } from 'immutable'; +import { dateStringUTC } from 'lib/DateUtils'; +import getFileName from 'lib/getFileName'; +import Parse from 'parse'; +import Pill from 'components/Pill/Pill.react'; +import React, { Component } from 'react'; +import styles from 'components/BrowserCell/BrowserCell.scss'; +import baseStyles from 'stylesheets/base.scss'; +import * as ColumnPreferences from 'lib/ColumnPreferences'; +import labelStyles from 'components/Label/Label.scss'; +import Modal from 'components/Modal/Modal.react'; export default class BrowserCell extends Component { constructor() { @@ -22,20 +24,27 @@ export default class BrowserCell extends Component { this.cellRef = React.createRef(); this.copyableValue = undefined; + this.selectedScript = null; this.state = { showTooltip: false, content: null, - classes: [] + classes: [], + showConfirmationDialog: false, }; } renderCellContent() { let content = this.props.value; - let isNewRow = this.props.row < 0; + const isNewRow = this.props.row < 0; this.copyableValue = content; - let classes = [styles.cell, baseStyles.unselectable]; + const classes = [styles.cell, baseStyles.unselectable]; if (this.props.hidden) { - content = this.props.value !== undefined || !isNewRow ? '(hidden)' : this.props.isRequired ? '(required)' : '(undefined)'; + content = + this.props.value !== undefined || !isNewRow + ? '(hidden)' + : this.props.isRequired + ? '(required)' + : '(undefined)'; classes.push(styles.empty); } else if (this.props.value === undefined) { if (this.props.type === 'ACL') { @@ -44,7 +53,10 @@ export default class BrowserCell extends Component { this.copyableValue = content = '(undefined)'; classes.push(styles.empty); } - content = isNewRow && this.props.isRequired && this.props.value === undefined ? '(required)' : content; + content = + isNewRow && this.props.isRequired && this.props.value === undefined + ? '(required)' + : content; } else if (this.props.value === null) { this.copyableValue = content = '(null)'; classes.push(styles.empty); @@ -52,24 +64,26 @@ export default class BrowserCell extends Component { content =  ; classes.push(styles.empty); } else if (this.props.type === 'Pointer') { - const defaultPointerKey = ColumnPreferences.getPointerDefaultKey(this.props.appId, this.props.value.className); + const defaultPointerKey = ColumnPreferences.getPointerDefaultKey( + this.props.appId, + this.props.value.className + ); let dataValue = this.props.value.id; - if( defaultPointerKey !== 'objectId' ) { + if (defaultPointerKey !== 'objectId') { dataValue = this.props.value.get(defaultPointerKey); - if ( dataValue && typeof dataValue === 'object' ){ - if ( dataValue instanceof Date ) { + if (dataValue && typeof dataValue === 'object') { + if (dataValue instanceof Date) { dataValue = dataValue.toLocaleString(); - } - else { - if ( !this.props.value.id ) { + } else { + if (!this.props.value.id) { dataValue = this.props.value.id; } else { dataValue = '(undefined)'; } } } - if ( !dataValue ) { - if ( this.props.value.id ) { + if (!dataValue) { + if (this.props.value.id) { dataValue = this.props.value.id; } else { dataValue = '(undefined)'; @@ -84,38 +98,55 @@ export default class BrowserCell extends Component { } content = this.props.onPointerClick ? ( - + ) : ( dataValue ); this.copyableValue = this.props.value.id; - } - else if (this.props.type === 'Array') { - if ( this.props.value[0] && typeof this.props.value[0] === 'object' && this.props.value[0].__type === 'Pointer' && typeof this.props.onPointerClick === 'function' ) { + } else if (this.props.type === 'Array') { + if ( + this.props.value[0] && + typeof this.props.value[0] === 'object' && + this.props.value[0].__type === 'Pointer' && + typeof this.props.onPointerClick === 'function' + ) { const array = []; - this.props.value.map( (v, i) => { - if ( typeof v !== 'object' || v.__type !== 'Pointer' ) { + this.props.value.map((v, i) => { + if (typeof v !== 'object' || v.__type !== 'Pointer') { throw new Error('Invalid type found in pointer array'); } const object = new Parse.Object(v.className); object.id = v.objectId; array.push( - - ); + + ); }); - this.copyableValue = content =
    - { array.map( a =>
  • {a}
  • ) } -
- if ( array.length > 1 ) { + this.copyableValue = content = ( +
    + {array.map(a => ( +
  • {a}
  • + ))} +
+ ); + if (array.length > 1) { classes.push(styles.hasMore); } - } - else { + } else { this.copyableValue = content = JSON.stringify(this.props.value); } - } - else if (this.props.type === 'Date') { + } else if (this.props.type === 'Date') { if (typeof value === 'object' && this.props.value.__type) { this.props.value = new Date(this.props.value.iso); } else if (typeof value === 'string') { @@ -127,12 +158,16 @@ export default class BrowserCell extends Component { } else if (this.props.type === 'Object' || this.props.type === 'Bytes') { this.copyableValue = content = JSON.stringify(this.props.value); } else if (this.props.type === 'File') { - const fileName = this.props.value.url() ? getFileName(this.props.value) : 'Uploading\u2026'; + const fileName = this.props.value + ? this.props.value.url() + ? getFileName(this.props.value) + : this.props.value.name() + : 'Uploading\u2026'; content = ; this.copyableValue = fileName; } else if (this.props.type === 'ACL') { - let pieces = []; - let json = this.props.value.toJSON(); + const pieces = []; + const json = this.props.value.toJSON(); if (Object.prototype.hasOwnProperty.call(json, '*')) { if (json['*'].read && json['*'].write) { pieces.push('Public Read + Write'); @@ -142,7 +177,7 @@ export default class BrowserCell extends Component { pieces.push('Public Write'); } } - for (let role in json) { + for (const role in json) { if (role !== '*') { pieces.push(role); } @@ -152,17 +187,23 @@ export default class BrowserCell extends Component { } this.copyableValue = content = pieces.join(', '); } else if (this.props.type === 'GeoPoint') { - this.copyableValue = content = `(${this.props.value.latitude}, ${this.props.value.longitude})`; + this.copyableValue = + content = `(${this.props.value.latitude}, ${this.props.value.longitude})`; } else if (this.props.type === 'Polygon') { - this.copyableValue = content = this.props.value.coordinates.map(coord => `(${coord})`) + this.copyableValue = content = this.props.value.coordinates.map(coord => `(${coord})`); } else if (this.props.type === 'Relation') { content = this.props.setRelation ? (
- this.props.setRelation(this.props.value)} value='View relation' followClick={true} shrinkablePill /> + this.props.setRelation(this.props.value)} + value="View relation" + followClick={true} + shrinkablePill + />
) : ( - 'Relation' - ); + 'Relation' + ); this.copyableValue = undefined; } this.onContextMenu = this.onContextMenu.bind(this); @@ -171,15 +212,15 @@ export default class BrowserCell extends Component { classes.push(styles.required); } - this.setState({ ...this.state, content, classes }) + this.setState({ ...this.state, content, classes }); } componentDidUpdate(prevProps) { - if ( this.props.value !== prevProps.value ) { + if (this.props.value !== prevProps.value) { this.renderCellContent(); this.props.value?._previousSave ?.then(() => this.renderCellContent()) - ?.catch(err => console.log(err)) + ?.catch(err => console.log(err)); } if (this.props.current) { const node = this.cellRef.current; @@ -209,11 +250,16 @@ export default class BrowserCell extends Component { } shouldComponentUpdate(nextProps, nextState) { - if (nextState.showTooltip !== this.state.showTooltip || nextState.content !== this.state.content ) { + if ( + nextState.showTooltip !== this.state.showTooltip || + nextState.content !== this.state.content || + nextState.showConfirmationDialog !== this.state.showConfirmationDialog + ) { return true; } - const shallowVerifyProps = [...new Set(Object.keys(this.props).concat(Object.keys(nextProps)))] - .filter(propName => propName !== 'value'); + const shallowVerifyProps = [ + ...new Set(Object.keys(this.props).concat(Object.keys(nextProps))), + ].filter(propName => propName !== 'value'); if (shallowVerifyProps.some(propName => this.props[propName] !== nextProps[propName])) { return true; } @@ -232,7 +278,9 @@ export default class BrowserCell extends Component { //#region Cell Context Menu related methods onContextMenu(event) { - if (event.type !== 'contextmenu') { return; } + if (event.type !== 'contextmenu') { + return; + } event.preventDefault(); const { field, hidden, onSelect, setCopyableValue, setContextMenu, row, col } = this.props; @@ -240,7 +288,11 @@ export default class BrowserCell extends Component { onSelect({ row, col }); setCopyableValue(hidden ? undefined : this.copyableValue); - const available = Filters.availableFilters(this.props.simplifiedSchema, this.props.filters, Filters.BLACKLISTED_FILTERS); + const available = Filters.availableFilters( + this.props.simplifiedSchema, + this.props.filters, + Filters.BLACKLISTED_FILTERS + ); const constraints = available && available[field]; const { pageX, pageY } = event; @@ -249,7 +301,7 @@ export default class BrowserCell extends Component { } getContextMenuOptions(constraints) { - let { onEditSelectedRow, readonly } = this.props; + const { onEditSelectedRow, readonly } = this.props; const contextMenuOptions = []; const setFilterContextMenuOption = this.getSetFilterContextMenuOption(constraints); @@ -261,31 +313,86 @@ export default class BrowserCell extends Component { const relatedObjectsContextMenuOption = this.getRelatedObjectsContextMenuOption(); relatedObjectsContextMenuOption && contextMenuOptions.push(relatedObjectsContextMenuOption); - !readonly && onEditSelectedRow && contextMenuOptions.push({ - text: 'Edit row', - callback: () => { - let { objectId, onEditSelectedRow } = this.props; - onEditSelectedRow(true, objectId); - } - }); - - if (this.props.type === 'Pointer') { - onEditSelectedRow && contextMenuOptions.push({ - text: 'Open pointer in new tab', + !readonly && + onEditSelectedRow && + contextMenuOptions.push({ + text: 'Edit row', callback: () => { - let { value, onPointerCmdClick } = this.props; - onPointerCmdClick(value); - } + const { objectId, onEditSelectedRow } = this.props; + onEditSelectedRow(true, objectId); + }, }); + + if (this.props.type === 'Pointer') { + onEditSelectedRow && + contextMenuOptions.push({ + text: 'Open pointer in new tab', + callback: () => { + const { value, onPointerCmdClick } = this.props; + onPointerCmdClick(value); + }, + }); + } + + const { className, objectId } = this.props; + const validScripts = (this.props.scripts || []).filter(script => + script.classes?.includes(this.props.className) + ); + if (validScripts.length) { + onEditSelectedRow && + contextMenuOptions.push({ + text: 'Scripts', + items: validScripts.map(script => { + return { + text: script.title, + callback: () => { + this.selectedScript = { ...script, className, objectId }; + if (script.showConfirmationDialog) { + this.toggleConfirmationDialog(); + } else { + this.executeSript(script); + } + }, + }; + }), + }); } return contextMenuOptions; } + async executeSript(script) { + try { + const object = Parse.Object.extend(this.props.className).createWithoutData( + this.props.objectId + ); + const response = await Parse.Cloud.run( + script.cloudCodeFunction, + { object: object.toPointer() }, + { useMasterKey: true } + ); + this.props.showNote( + response || + `Ran script "${script.title}" on "${this.props.className}" object "${object.id}".` + ); + this.props.onRefresh(); + } catch (e) { + this.props.showNote(e.message, true); + console.log(`Could not run ${script.title}: ${e}`); + } + } + + toggleConfirmationDialog() { + this.setState(prevState => ({ + showConfirmationDialog: !prevState.showConfirmationDialog, + })); + } + getSetFilterContextMenuOption(constraints) { if (constraints) { return { - text: 'Set filter...', items: constraints.map(constraint => { + text: 'Set filter...', + items: constraints.map(constraint => { const definition = Filters.Constraints[constraint]; const copyableValue = String(this.copyableValue); // Smart ellipsis for value - if it's long trim it in the middle: Lorem ipsum dolor si... aliqua @@ -293,16 +400,16 @@ export default class BrowserCell extends Component { copyableValue.length < 30 ? copyableValue : `${copyableValue.substr(0, 20)}...${copyableValue.substr( - copyableValue.length - 7 - )}`; + copyableValue.length - 7 + )}`; const text = `${this.props.field} ${definition.name}${ definition.comparable ? ' ' + value : '' }`; return { text, - callback: this.pickFilter.bind(this, constraint) + callback: this.pickFilter.bind(this, constraint), }; - }) + }), }; } } @@ -310,14 +417,17 @@ export default class BrowserCell extends Component { getAddFilterContextMenuOption(constraints) { if (constraints && this.props.filters && this.props.filters.size > 0) { return { - text: 'Add filter...', items: constraints.map(constraint => { + text: 'Add filter...', + items: constraints.map(constraint => { const definition = Filters.Constraints[constraint]; - const text = `${this.props.field} ${definition.name}${definition.comparable ? (' ' + this.copyableValue) : ''}`; + const text = `${this.props.field} ${definition.name}${ + definition.comparable ? ' ' + this.copyableValue : '' + }`; return { text, - callback: this.pickFilter.bind(this, constraint, true) + callback: this.pickFilter.bind(this, constraint, true), }; - }) + }), }; } } @@ -329,25 +439,39 @@ export default class BrowserCell extends Component { getRelatedObjectsContextMenuOption() { const { value, schema, onPointerClick } = this.props; - const pointerClassName = (value && value.className) - || (this.props.field === 'objectId' && this.props.className); + const pointerClassName = + (value && value.className) || (this.props.field === 'objectId' && this.props.className); if (pointerClassName) { - const relatedRecordsMenuItem = { text: 'Get related records from...', items: [] }; - schema.data.get('classes').sortBy((v, k) => k).forEach((cl, className) => { - cl.forEach((column, field) => { - if (column.targetClass !== pointerClassName) { return; } - relatedRecordsMenuItem.items.push({ - text: `${className}`, subtext: `${field}`, callback: () => { - let relatedObject = value; - if (this.props.field === 'objectId') { - relatedObject = new Parse.Object(pointerClassName); - relatedObject.id = value; - } - onPointerClick({ className, id: relatedObject.toPointer(), field }) + const relatedRecordsMenuItem = { + text: 'Get related records from...', + items: [], + }; + schema.data + .get('classes') + .sortBy((v, k) => k) + .forEach((cl, className) => { + cl.forEach((column, field) => { + if (column.targetClass !== pointerClassName) { + return; } - }) + relatedRecordsMenuItem.items.push({ + text: `${className}`, + subtext: `${field}`, + callback: () => { + let relatedObject = value; + if (this.props.field === 'objectId') { + relatedObject = new Parse.Object(pointerClassName); + relatedObject.id = value; + } + onPointerClick({ + className, + id: relatedObject.toPointer(), + field, + }); + }, + }); + }); }); - }); return relatedRecordsMenuItem.items.length ? relatedRecordsMenuItem : undefined; } @@ -361,13 +485,15 @@ export default class BrowserCell extends Component { if (definition.comparable) { switch (type) { case 'Pointer': - compareTo = value.toPointer() + compareTo = value.toPointer(); break; case 'Date': - compareTo = value.__type ? value : { - __type: 'Date', - iso: value - }; + compareTo = value.__type + ? value + : { + __type: 'Date', + iso: value, + }; break; default: @@ -376,62 +502,111 @@ export default class BrowserCell extends Component { } } - this.props.onFilterChange(newFilters.push(new Map({ - field, - constraint, - compareTo - }))); + this.props.onFilterChange( + newFilters.push( + new Map({ + field, + constraint, + compareTo, + }) + ) + ); } - componentDidMount(){ + componentDidMount() { this.renderCellContent(); } //#endregion render() { - let { type, value, hidden, width, current, onSelect, onEditChange, setCopyableValue, onPointerCmdClick, row, col, field, onEditSelectedRow, isRequired, markRequiredFieldRow } = this.props; + const { + type, + value, + hidden, + width, + current, + onSelect, + onEditChange, + setCopyableValue, + onPointerCmdClick, + row, + col, + field, + onEditSelectedRow, + isRequired, + markRequiredFieldRow, + } = this.props; - let classes = [...this.state.classes]; + const classes = [...this.state.classes]; - if ( current ) { + if (current) { classes.push(styles.current); } if (markRequiredFieldRow === row && isRequired && value == null) { classes.push(styles.required); } - return { - if (e.metaKey === true && type === 'Pointer') { - onPointerCmdClick(value); - } - else { - onSelect({ row, col }); - setCopyableValue(hidden ? undefined : this.copyableValue); - } - }} - onDoubleClick={() => { - // Since objectId can't be edited, double click event opens edit row dialog - if (field === 'objectId' && onEditSelectedRow) { - onEditSelectedRow(true, value); - } else if (type !== 'Relation') { - onEditChange(true) - } - }} - onTouchEnd={e => { - if (current && type !== 'Relation') { - // The touch event may trigger an unwanted change in the column value - if (['ACL', 'Boolean', 'File'].includes(type)) { - e.preventDefault(); + let extras = null; + if (this.state.showConfirmationDialog) { + extras = ( + this.toggleConfirmationDialog()} + onConfirm={() => { + this.executeSript(this.selectedScript); + this.toggleConfirmationDialog(); + }} + > +
+ {`Do you want to run script "${this.selectedScript.title}" on "${this.selectedScript.className}" object "${this.selectedScript.objectId}"?`} +
+
+ ); + } + + return ( + { + if (e.metaKey === true && type === 'Pointer') { + onPointerCmdClick(value); + } else { + onSelect({ row, col }); + setCopyableValue(hidden ? undefined : this.copyableValue); + } + }} + onDoubleClick={() => { + // Since objectId can't be edited, double click event opens edit row dialog + if (field === 'objectId' && onEditSelectedRow) { + onEditSelectedRow(true, value); + } else if (type !== 'Relation') { + onEditChange(true); + } + }} + onTouchEnd={e => { + if (current && type !== 'Relation') { + // The touch event may trigger an unwanted change in the column value + if (['ACL', 'Boolean', 'File'].includes(type)) { + e.preventDefault(); + } } - }}} - onContextMenu={this.onContextMenu.bind(this)} + }} + onContextMenu={this.onContextMenu.bind(this)} > {this.state.content} - + {extras} +
+ ); } } diff --git a/src/components/BrowserCell/BrowserCell.scss b/src/components/BrowserCell/BrowserCell.scss index 8fc90b8eef..f2244498d8 100644 --- a/src/components/BrowserCell/BrowserCell.scss +++ b/src/components/BrowserCell/BrowserCell.scss @@ -77,4 +77,9 @@ .readonly { color: #04263bd1; -} \ No newline at end of file +} + +.action { + padding: 28px; + border-style: solid; +} diff --git a/src/components/BrowserFilter/BrowserFilter.react.js b/src/components/BrowserFilter/BrowserFilter.react.js index 30e8f7f2b5..4f04d2561f 100644 --- a/src/components/BrowserFilter/BrowserFilter.react.js +++ b/src/components/BrowserFilter/BrowserFilter.react.js @@ -46,28 +46,34 @@ export default class BrowserFilter extends React.Component { toggle() { let filters = this.props.filters; if (this.props.filters.size === 0) { - let available = Filters.availableFilters(this.props.schema, null, this.state.blacklistedFilters); - let field = Object.keys(available)[0]; + const available = Filters.availableFilters( + this.props.schema, + null, + this.state.blacklistedFilters + ); + const field = Object.keys(available)[0]; filters = new List([new Map({ field: field, constraint: available[field][0] })]); } - this.setState((prevState) => ({ + this.setState(prevState => ({ open: !prevState.open, filters: filters, name: '', confirmName: false, - editMode: this.props.filters.size === 0 + editMode: this.props.filters.size === 0, })); this.props.setCurrent(null); } addRow() { - let available = Filters.availableFilters(this.props.schema, this.state.filters, this.state.blacklistedFilters); - let field = Object.keys(available)[0]; + const available = Filters.availableFilters( + this.props.schema, + this.state.filters, + this.state.blacklistedFilters + ); + const field = Object.keys(available)[0]; this.setState(({ filters }) => ({ - filters: filters.push( - new Map({ field: field, constraint: available[field][0] }) - ), - editMode: true + filters: filters.push(new Map({ field: field, constraint: available[field][0] })), + editMode: true, })); } @@ -76,7 +82,7 @@ export default class BrowserFilter extends React.Component { } apply() { - let formatted = this.state.filters.map((filter) => { + const formatted = this.state.filters.map(filter => { // TODO: type is unused? /*let type = this.props.schema[filter.get('field')].type; if (Filters.Constraints[filter.get('constraint')].hasOwnProperty('field')) { @@ -85,7 +91,7 @@ export default class BrowserFilter extends React.Component { // since we are preserving previous compareTo value // remove compareTo for constraints which are not comparable - let isComparable = Filters.Constraints[filter.get('constraint')].comparable; + const isComparable = Filters.Constraints[filter.get('constraint')].comparable; if (!isComparable) { return filter.delete('compareTo'); } @@ -95,8 +101,8 @@ export default class BrowserFilter extends React.Component { } save() { - let formatted = this.state.filters.map((filter) => { - let isComparable = Filters.Constraints[filter.get('constraint')].comparable; + const formatted = this.state.filters.map(filter => { + const isComparable = Filters.Constraints[filter.get('constraint')].comparable; if (!isComparable) { return filter.delete('compareTo'); } @@ -108,21 +114,30 @@ export default class BrowserFilter extends React.Component { render() { let popover = null; - let buttonStyle = [styles.entry]; + const buttonStyle = [styles.entry]; const node = this.wrapRef.current; if (this.state.open) { - let position = Position.inDocument(node); - let popoverStyle = [styles.popover]; + const position = Position.inDocument(node); + const popoverStyle = [styles.popover]; buttonStyle.push(styles.title); if (this.props.filters.size) { popoverStyle.push(styles.active); } - let available = Filters.availableFilters(this.props.schema, this.state.filters); + const available = Filters.availableFilters(this.props.schema, this.state.filters); popover = ( - -
this.props.setCurrent(null)} id={POPOVER_CONTENT_ID}> + +
this.props.setCurrent(null)} + id={POPOVER_CONTENT_ID} + >
this.setState({ filters: filters })} + onChange={filters => this.setState({ filters: filters })} onSearch={this.apply.bind(this)} renderRow={props => ( - 0} editMode={this.state.editMode} parentContentId={POPOVER_CONTENT_ID} /> + 0} + editMode={this.state.editMode} + parentContentId={POPOVER_CONTENT_ID} + /> )} /> - {this.state.confirmName && } input={ this.setState({ name })} />} />} + {this.state.confirmName && ( + } + input={ + this.setState({ name })} + /> + } + /> + )} {this.state.confirmName && (
-
)} {!this.state.confirmName && (
-
)}
diff --git a/src/components/BrowserFilter/FilterRow.react.js b/src/components/BrowserFilter/FilterRow.react.js index cf8cc5a90d..961901ef35 100644 --- a/src/components/BrowserFilter/FilterRow.react.js +++ b/src/components/BrowserFilter/FilterRow.react.js @@ -5,48 +5,73 @@ * This source code is licensed under the license found in the LICENSE file in * the root directory of this source tree. */ -import ChromeDropdown from 'components/ChromeDropdown/ChromeDropdown.react'; +import ChromeDropdown from 'components/ChromeDropdown/ChromeDropdown.react'; +import Autocomplete from 'components/Autocomplete/Autocomplete.react'; import { Constraints } from 'lib/Filters'; -import DateTimeEntry from 'components/DateTimeEntry/DateTimeEntry.react'; -import Icon from 'components/Icon/Icon.react'; -import Parse from 'parse'; -import PropTypes from 'lib/PropTypes'; -import React, { useCallback } from 'react'; -import styles from 'components/BrowserFilter/BrowserFilter.scss'; +import DateTimeEntry from 'components/DateTimeEntry/DateTimeEntry.react'; +import Icon from 'components/Icon/Icon.react'; +import Parse from 'parse'; +import PropTypes from 'lib/PropTypes'; +import React, { useCallback } from 'react'; +import styles from 'components/BrowserFilter/BrowserFilter.scss'; import validateNumeric from 'lib/validateNumeric'; -let constraintLookup = {}; -for (let c in Constraints) { +const constraintLookup = {}; +for (const c in Constraints) { constraintLookup[Constraints[c].name] = c; } -function compareValue(info, value, onChangeCompareTo, onKeyDown, active, parentContentId, setFocus) { +function compareValue( + info, + value, + onChangeCompareTo, + onKeyDown, + active, + parentContentId, + setFocus +) { switch (info.type) { case null: return null; case 'Object': case 'String': - return onChangeCompareTo(e.target.value)} onKeyDown={onKeyDown} ref={setFocus}/>; + return ( + onChangeCompareTo(e.target.value)} + onKeyDown={onKeyDown} + ref={setFocus} + /> + ); case 'Pointer': return ( { - let obj = new Parse.Object(info.targetClass); + onChange={e => { + const obj = new Parse.Object(info.targetClass); obj.id = e.target.value; onChangeCompareTo(obj.toPointer()); }} - ref={setFocus} /> + ref={setFocus} + /> ); case 'Boolean': - return onChangeCompareTo(val === 'True')} />; + return ( + onChangeCompareTo(val === 'True')} + /> + ); case 'Number': return ( { + onChange={e => { let val = value; if (!e.target.value.length || e.target.value === '-') { val = e.target.value; @@ -56,7 +81,7 @@ function compareValue(info, value, onChangeCompareTo, onKeyDown, active, parentC onChangeCompareTo(val); }} onKeyDown={onKeyDown} - /> + /> ); case 'Date': return ( @@ -64,54 +89,100 @@ function compareValue(info, value, onChangeCompareTo, onKeyDown, active, parentC fixed={true} className={styles.date} value={Parse._decode('date', value)} - onChange={(value) => onChangeCompareTo(Parse._encode(value))} + onChange={value => onChangeCompareTo(Parse._encode(value))} ref={setFocus} - parentContentId={parentContentId} /> + parentContentId={parentContentId} + /> ); } } -let FilterRow = ({ - fields, - constraints, - compareInfo, - currentField, - currentConstraint, - compareTo, - onChangeField, - onChangeConstraint, - onChangeCompareTo, - onKeyDown, - onDeleteRow, - active, - parentContentId, - editMode - }) => { +const FilterRow = ({ + fields, + constraints, + compareInfo, + currentField, + currentConstraint, + compareTo, + onChangeField, + onChangeConstraint, + onChangeCompareTo, + onKeyDown, + onDeleteRow, + active, + parentContentId, + editMode, +}) => { + const setFocus = useCallback(input => { + if (input !== null && editMode) { + input.focus(); + } + }, []); - let setFocus = useCallback((input) => { - if (input !== null && editMode) { - input.focus(); - } - }, []) + const buildSuggestions = input => { + const regex = new RegExp(input.split('').join('.*?'), 'i'); + return fields.filter(f => regex.test(f)); + }; - return ( -
- - Constraints[c].name)} - onChange={(c) => onChangeConstraint(constraintLookup[c], compareTo)} /> - {compareValue(compareInfo, compareTo, onChangeCompareTo, onKeyDown, active, parentContentId, setFocus)} - -
- ); -} + return ( +
+ ''} + /> + Constraints[c].name)} + onChange={c => onChangeConstraint(constraintLookup[c], compareTo)} + /> + {compareValue( + compareInfo, + compareTo, + onChangeCompareTo, + onKeyDown, + active, + parentContentId, + setFocus + )} + +
+ ); +}; export default React.memo(FilterRow); @@ -121,5 +192,5 @@ FilterRow.propTypes = { constraints: PropTypes.arrayOf(PropTypes.string).isRequired, currentConstraint: PropTypes.string.isRequired, compareTo: PropTypes.any, - compareInfo: PropTypes.object + compareInfo: PropTypes.object, }; diff --git a/src/components/BrowserMenu/BrowserMenu.react.js b/src/components/BrowserMenu/BrowserMenu.react.js index 5939920667..2eb60e50ec 100644 --- a/src/components/BrowserMenu/BrowserMenu.react.js +++ b/src/components/BrowserMenu/BrowserMenu.react.js @@ -5,12 +5,12 @@ * This source code is licensed under the license found in the LICENSE file in * the root directory of this source tree. */ -import Popover from 'components/Popover/Popover.react'; -import Icon from 'components/Icon/Icon.react'; -import Position from 'lib/Position'; -import PropTypes from 'lib/PropTypes'; -import React from 'react'; -import styles from 'components/BrowserMenu/BrowserMenu.scss'; +import Popover from 'components/Popover/Popover.react'; +import Icon from 'components/Icon/Icon.react'; +import Position from 'lib/Position'; +import PropTypes from 'lib/PropTypes'; +import React from 'react'; +import styles from 'components/BrowserMenu/BrowserMenu.scss'; export default class BrowserMenu extends React.Component { constructor() { @@ -23,25 +23,32 @@ export default class BrowserMenu extends React.Component { render() { let menu = null; if (this.state.open) { - let position = Position.inDocument(this.wrapRef.current); - let titleStyle = [styles.title]; + const position = Position.inDocument(this.wrapRef.current); + const titleStyle = [styles.title]; if (this.props.active) { titleStyle.push(styles.active); } menu = ( - this.setState({ open: false })}> + this.setState({ open: false })} + >
this.setState({ open: false })}> {this.props.title}
- {React.Children.map(this.props.children, (child) => ( - React.cloneElement(child, { ...child.props, onClick: () => { - this.setState({ open: false }); - child.props.onClick(); - }}) - ))} + {React.Children.map(this.props.children, child => + React.cloneElement(child, { + ...child.props, + onClick: () => { + this.setState({ open: false }); + child.props.onClick(); + }, + }) + )}
@@ -74,12 +81,8 @@ export default class BrowserMenu extends React.Component { } BrowserMenu.propTypes = { - icon: PropTypes.string.isRequired.describe( - 'The name of the icon to place in the menu.' - ), - title: PropTypes.string.isRequired.describe( - 'The title text of the menu.' - ), + icon: PropTypes.string.isRequired.describe('The name of the icon to place in the menu.'), + title: PropTypes.string.isRequired.describe('The title text of the menu.'), children: PropTypes.arrayOf(PropTypes.node).describe( 'The contents of the menu when open. It should be a set of MenuItem and Separator components.' ), diff --git a/src/components/BrowserMenu/MenuItem.react.js b/src/components/BrowserMenu/MenuItem.react.js index 27b1df6f9f..5b8e72c51a 100644 --- a/src/components/BrowserMenu/MenuItem.react.js +++ b/src/components/BrowserMenu/MenuItem.react.js @@ -5,11 +5,11 @@ * This source code is licensed under the license found in the LICENSE file in * the root directory of this source tree. */ -import React from 'react'; +import React from 'react'; import styles from 'components/BrowserMenu/BrowserMenu.scss'; -let MenuItem = ({ text, disabled, active, greenActive, onClick }) => { - let classes = [styles.item]; +const MenuItem = ({ text, disabled, active, greenActive, onClick }) => { + const classes = [styles.item]; if (disabled) { classes.push(styles.disabled); } @@ -19,7 +19,11 @@ let MenuItem = ({ text, disabled, active, greenActive, onClick }) => { if (greenActive) { classes.push(styles.greenActive); } - return
{text}
; + return ( +
+ {text} +
+ ); }; export default MenuItem; diff --git a/src/components/BrowserMenu/Separator.react.js b/src/components/BrowserMenu/Separator.react.js index 655e8daf85..11f5fd45ee 100644 --- a/src/components/BrowserMenu/Separator.react.js +++ b/src/components/BrowserMenu/Separator.react.js @@ -5,9 +5,9 @@ * This source code is licensed under the license found in the LICENSE file in * the root directory of this source tree. */ -import React from 'react'; +import React from 'react'; import styles from 'components/BrowserMenu/BrowserMenu.scss'; -let Separator = () =>
; +const Separator = () =>
; export default Separator; diff --git a/src/components/BrowserRow/BrowserRow.react.js b/src/components/BrowserRow/BrowserRow.react.js index 5ae4e78ac3..dce01aeb45 100644 --- a/src/components/BrowserRow/BrowserRow.react.js +++ b/src/components/BrowserRow/BrowserRow.react.js @@ -7,8 +7,9 @@ import styles from 'dashboard/Data/Browser/Browser.scss'; export default class BrowserRow extends Component { shouldComponentUpdate(nextProps) { - const shallowVerifyProps = [...new Set(Object.keys(this.props).concat(Object.keys(nextProps)))] - .filter(propName => propName !== 'obj'); + const shallowVerifyProps = [ + ...new Set(Object.keys(this.props).concat(Object.keys(nextProps))), + ].filter(propName => propName !== 'obj'); if (shallowVerifyProps.some(propName => this.props[propName] !== nextProps[propName])) { return true; } @@ -19,8 +20,30 @@ export default class BrowserRow extends Component { } render() { - const { className, columns, currentCol, isUnique, obj, onPointerClick, onPointerCmdClick, order, readOnlyFields, row, rowWidth, selection, selectRow, setCopyableValue, setCurrent, setEditing, setRelation, onEditSelectedRow, setContextMenu, onFilterChange, markRequiredFieldRow } = this.props; - let attributes = obj.attributes; + const { + className, + columns, + currentCol, + isUnique, + obj, + onPointerClick, + onPointerCmdClick, + order, + readOnlyFields, + row, + rowWidth, + selection, + selectRow, + setCopyableValue, + setCurrent, + setEditing, + setRelation, + onEditSelectedRow, + setContextMenu, + onFilterChange, + markRequiredFieldRow, + } = this.props; + const attributes = obj.attributes; let requiredCols = []; Object.entries(columns).reduce((acc, cur) => { if (cur[1].required) { @@ -29,7 +52,10 @@ export default class BrowserRow extends Component { return acc; }, requiredCols); // for dynamically changing required field on _User class - if (obj.className === '_User' && (obj.get('username') !== undefined || obj.get('password') !== undefined)) { + if ( + obj.className === '_User' && + (obj.get('username') !== undefined || obj.get('password') !== undefined) + ) { requiredCols = ['username', 'password']; } else if (obj.className === '_User' && obj.get('authData') !== undefined) { requiredCols = ['authData']; @@ -38,20 +64,26 @@ export default class BrowserRow extends Component {
selectRow(obj.id, e.target.checked)} /> + onChange={e => selectRow(obj.id, e.target.checked)} + /> {order.map(({ name, width, visible }, j) => { - if (!visible) return null; - let type = columns[name].type; + if (!visible) { + return null; + } + const type = columns[name].type; let attr = obj; if (!isUnique) { - attr = attributes[name]; + attr = attributes[name]; if (name === 'objectId') { attr = obj.id; } else if (name === 'ACL' && className === '_User' && !attr) { - attr = new Parse.ACL({ '*': { read: true }, [obj.id]: { read: true, write: true }}); + attr = new Parse.ACL({ + '*': { read: true }, + [obj.id]: { read: true, write: true }, + }); } else if (type === 'Relation' && !attr && obj.id) { attr = new Parse.Relation(obj, name); attr.targetClassName = columns[name].targetClass; @@ -71,7 +103,7 @@ export default class BrowserRow extends Component { hidden = true; } } - let isRequired = requiredCols.includes(name); + const isRequired = requiredCols.includes(name); return ( + onEditSelectedRow={onEditSelectedRow} + showNote={this.props.showNote} + onRefresh={this.props.onRefresh} + scripts={this.props.scripts} + /> ); })}
diff --git a/src/components/Button/Button.example.js b/src/components/Button/Button.example.js index cba09baebd..900512b5d1 100644 --- a/src/components/Button/Button.example.js +++ b/src/components/Button/Button.example.js @@ -5,7 +5,7 @@ * This source code is licensed under the license found in the LICENSE file in * the root directory of this source tree. */ -import React from 'react'; +import React from 'react'; import Button from 'components/Button/Button.react'; export const component = Button; @@ -15,39 +15,46 @@ export const demos = [ name: 'Normal buttons', render: () => (
-
- ) - }, { + ), + }, + { name: 'Primary vs secondary buttons', render: () => (
-
- ) - }, { + ), + }, + { name: 'Disabled button', render: () => (
-
- ) - }, { + ), + }, + { name: 'Progress button', render: () => (
-
- ) - }, { + ), + }, + { name: 'Red progress button', render: () => (
-
- ) - } + ), + }, ]; diff --git a/src/components/Button/Button.react.js b/src/components/Button/Button.react.js index 80baeb6e4e..267dbdabe0 100644 --- a/src/components/Button/Button.react.js +++ b/src/components/Button/Button.react.js @@ -5,16 +5,16 @@ * This source code is licensed under the license found in the LICENSE file in * the root directory of this source tree. */ -import baseStyles from 'stylesheets/base.scss'; -import PropTypes from 'lib/PropTypes'; -import React from 'react'; -import styles from 'components/Button/Button.scss'; +import baseStyles from 'stylesheets/base.scss'; +import PropTypes from 'lib/PropTypes'; +import React from 'react'; +import styles from 'components/Button/Button.scss'; const noop = () => {}; -let Button = (props) => { +const Button = props => { const hasOnClick = props.onClick && !props.disabled; - let classes = [styles.button, baseStyles.unselectable]; + const classes = [styles.button, baseStyles.unselectable]; // if a button is disabled, that overrides any color selection if (props.disabled) { classes.push(styles.disabled); @@ -33,40 +33,45 @@ let Button = (props) => { classes.push(styles.progress); } } - let clickHandler = hasOnClick ? props.onClick : noop; + const clickHandler = hasOnClick ? props.onClick : noop; let styleOverride = null; if (props.width) { - styleOverride = { width: props.width, minWidth: props.width, ...props.additionalStyles }; + styleOverride = { + width: props.width, + minWidth: props.width, + ...props.additionalStyles, + }; } return ( ); -} +}; export default Button; Button.propTypes = { primary: PropTypes.bool.describe( 'Determines whether the button represents a Primary action. ' + - 'Primary buttons appear filled, while normal buttons are outlines.' + 'Primary buttons appear filled, while normal buttons are outlines.' ), disabled: PropTypes.bool.describe( 'Determines whether a button can be clicked. Disabled buttons will ' + - 'appear grayed out, and will not fire onClick events.' - ), - color: PropTypes.oneOf(['blue', 'green', 'red', 'white']).describe( - 'The color of the button.' - ), - onClick: PropTypes.func.describe( - 'A function to be called when the button is clicked.' + 'appear grayed out, and will not fire onClick events.' ), + color: PropTypes.oneOf(['blue', 'green', 'red', 'white']).describe('The color of the button.'), + onClick: PropTypes.func.describe('A function to be called when the button is clicked.'), value: PropTypes.string.isRequired.describe( 'The content of the button. This can be any renderable content.' ), diff --git a/src/components/CSRFInput/CSRFInput.react.js b/src/components/CSRFInput/CSRFInput.react.js index ff5f9e316e..748bd5c604 100644 --- a/src/components/CSRFInput/CSRFInput.react.js +++ b/src/components/CSRFInput/CSRFInput.react.js @@ -6,13 +6,13 @@ * the root directory of this source tree. */ import { getToken } from 'lib/CSRFManager'; -import React from 'react'; +import React from 'react'; // An invisible component that embeds a hidden input // containing the CSRF token into a form -let CSRFInput = () => ( +const CSRFInput = () => (
- +
); diff --git a/src/components/Calendar/Calendar.example.js b/src/components/Calendar/Calendar.example.js index 08aca0ee79..b35a8579dc 100644 --- a/src/components/Calendar/Calendar.example.js +++ b/src/components/Calendar/Calendar.example.js @@ -6,7 +6,7 @@ * the root directory of this source tree. */ import Calendar from 'components/Calendar/Calendar.react'; -import React from 'react'; +import React from 'react'; export const component = Calendar; @@ -14,8 +14,8 @@ export const demos = [ { render: () => (
- +
- ) - } + ), + }, ]; diff --git a/src/components/Calendar/Calendar.react.js b/src/components/Calendar/Calendar.react.js index 9ae8f8315c..e9749c9172 100644 --- a/src/components/Calendar/Calendar.react.js +++ b/src/components/Calendar/Calendar.react.js @@ -12,46 +12,58 @@ import { daysInMonth, WEEKDAYS, getDateMethod, -} from 'lib/DateUtils'; +} from 'lib/DateUtils'; import PropTypes from 'lib/PropTypes'; -import React from 'react'; -import styles from 'components/Calendar/Calendar.scss'; +import React from 'react'; +import styles from 'components/Calendar/Calendar.scss'; export default class Calendar extends React.Component { constructor(props) { super(); - let now = props.value || new Date(); + const now = props.value || new Date(); this.state = { - currentMonth: new Date(now[getDateMethod(props.local, 'getFullYear')](), now[getDateMethod(props.local, 'getMonth')](), 1) + currentMonth: new Date( + now[getDateMethod(props.local, 'getFullYear')](), + now[getDateMethod(props.local, 'getMonth')](), + 1 + ), }; } componentWillReceiveProps(props) { if (props.value) { this.setState({ - currentMonth: new Date(props.value[getDateMethod(props.local, 'getFullYear')](), props.value[getDateMethod(props.local, 'getMonth')](), 1) + currentMonth: new Date( + props.value[getDateMethod(props.local, 'getFullYear')](), + props.value[getDateMethod(props.local, 'getMonth')](), + 1 + ), }); } } handlePrev() { this.setState({ - currentMonth: prevMonth(this.state.currentMonth) + currentMonth: prevMonth(this.state.currentMonth), }); } handleNext() { this.setState({ - currentMonth: nextMonth(this.state.currentMonth) + currentMonth: nextMonth(this.state.currentMonth), }); } renderMonth() { return (
-
); } @@ -59,37 +71,45 @@ export default class Calendar extends React.Component { renderWeekdays() { return (
- {WEEKDAYS.map((w) => {w.substr(0, 2)})} + {WEEKDAYS.map(w => ( + {w.substr(0, 2)} + ))}
); } renderDays() { - let isValueMonth = ( + const isValueMonth = this.props.value && - this.props.value[getDateMethod(this.props.local, 'getFullYear')]() === this.state.currentMonth.getFullYear() && - this.props.value[getDateMethod(this.props.local, 'getMonth')]() === this.state.currentMonth.getMonth() - ); - let offset = this.state.currentMonth.getDay(); - let days = daysInMonth(this.state.currentMonth); - let labels = []; + this.props.value[getDateMethod(this.props.local, 'getFullYear')]() === + this.state.currentMonth.getFullYear() && + this.props.value[getDateMethod(this.props.local, 'getMonth')]() === + this.state.currentMonth.getMonth(); + const offset = this.state.currentMonth.getDay(); + const days = daysInMonth(this.state.currentMonth); + const labels = []; for (let i = 0; i < offset; i++) { labels.push(); } for (let i = 1; i <= days; i++) { - let isSelected = isValueMonth && (this.props.value[getDateMethod(this.props.local, 'getDate')]() === i); - let className = isSelected ? styles.selected : ''; - let onChange = this.props.onChange.bind( + const isSelected = + isValueMonth && this.props.value[getDateMethod(this.props.local, 'getDate')]() === i; + const className = isSelected ? styles.selected : ''; + const onChange = this.props.onChange.bind( null, - this.props.local ? - new Date(this.state.currentMonth.getFullYear(), this.state.currentMonth.getMonth(), i) : - new Date(Date.UTC(this.state.currentMonth.getFullYear(), this.state.currentMonth.getMonth(), i)) + this.props.local + ? new Date(this.state.currentMonth.getFullYear(), this.state.currentMonth.getMonth(), i) + : new Date( + Date.UTC(this.state.currentMonth.getFullYear(), this.state.currentMonth.getMonth(), i) + ) ); labels.push( - + ); } - let classes = [styles.days]; + const classes = [styles.days]; if (isValueMonth && this.props.shadeBefore) { classes.push(styles.shadeBefore); } @@ -111,16 +131,10 @@ export default class Calendar extends React.Component { } Calendar.propTypes = { - value: PropTypes.instanceOf(Date).describe( - 'The currently selected date' - ), + value: PropTypes.instanceOf(Date).describe('The currently selected date'), onChange: PropTypes.func.isRequired.describe( 'A callback fired when a new date is selected. It receives a Date object as its only parameter.' ), - shadeBefore: PropTypes.bool.describe( - 'Whether to shade the dates before the current selection' - ), - shadeAfter: PropTypes.bool.describe( - 'Whether to shade the dates after the current selection' - ), -} + shadeBefore: PropTypes.bool.describe('Whether to shade the dates before the current selection'), + shadeAfter: PropTypes.bool.describe('Whether to shade the dates after the current selection'), +}; diff --git a/src/components/CascadingView/CascadingView.example.js b/src/components/CascadingView/CascadingView.example.js index fe96c2b224..59e923eaa0 100644 --- a/src/components/CascadingView/CascadingView.example.js +++ b/src/components/CascadingView/CascadingView.example.js @@ -6,7 +6,7 @@ * the root directory of this source tree. */ import CascadingView from 'components/CascadingView/CascadingView.react'; -import React from 'react'; +import React from 'react'; export const component = CascadingView; @@ -15,27 +15,27 @@ export const demos = [ render: () => ( - + content={'Element with children'} + > +
Element without child
Also element without child
Element without child
- ) + ), }, { render: () => { - let contents = []; + const contents = []; for (let i = 0; i < 4; ++i) { contents.push( - + key={i} + > +
Element without child
Also element without child
@@ -43,11 +43,7 @@ export const demos = [
); } - return ( -
- {contents} -
- ); - } - } + return
{contents}
; + }, + }, ]; diff --git a/src/components/CascadingView/CascadingView.react.js b/src/components/CascadingView/CascadingView.react.js index 103f5e0ee2..1c45bfcf55 100644 --- a/src/components/CascadingView/CascadingView.react.js +++ b/src/components/CascadingView/CascadingView.react.js @@ -5,30 +5,37 @@ * This source code is licensed under the license found in the LICENSE file in * the root directory of this source tree. */ -import baseStyles from 'stylesheets/base.scss' -import PropTypes from 'lib/PropTypes'; -import React from 'react'; -import styles from 'components/CascadingView/CascadingView.scss'; +import baseStyles from 'stylesheets/base.scss'; +import PropTypes from 'lib/PropTypes'; +import React from 'react'; +import styles from 'components/CascadingView/CascadingView.scss'; export default class CascadingView extends React.Component { constructor() { super(); this.state = { - expanded: false + expanded: false, }; } render() { - let { content, className, children, style } = this.props; - let expander = ; - let childrenContainer = this.state.expanded ? (
-
{children}
-
) : null; - let classes = [styles.contentContainer]; + const { content, className, children, style } = this.props; + const expander = ( + + ); + const childrenContainer = this.state.expanded ? ( +
+
{children}
+
+ ) : null; + const classes = [styles.contentContainer]; if (className) { classes.push(className); } @@ -38,7 +45,8 @@ export default class CascadingView extends React.Component {
this.setState({ expanded: !this.state.expanded })}> + onClick={() => this.setState({ expanded: !this.state.expanded })} + > {content} {expander}
@@ -50,13 +58,10 @@ export default class CascadingView extends React.Component { CascadingView.propTypes = { content: PropTypes.node.isRequired.describe( - 'The content of the CascadingView itself. ' + - 'It can be any renderable content.' + 'The content of the CascadingView itself. ' + 'It can be any renderable content.' ), className: PropTypes.string.describe( 'A CSS class name to be applied to the collapsed CascadingView.' ), - children: PropTypes.node.isRequired.describe( - 'The children of CascadingView.' - ) + children: PropTypes.node.isRequired.describe('The children of CascadingView.'), }; diff --git a/src/components/CategoryList/CategoryList.react.js b/src/components/CategoryList/CategoryList.react.js index 82afff31c5..cf1a0e9106 100644 --- a/src/components/CategoryList/CategoryList.react.js +++ b/src/components/CategoryList/CategoryList.react.js @@ -23,7 +23,7 @@ export default class CategoryList extends React.Component { } componentDidMount() { - let listWrapper = this.listWrapperRef.current; + const listWrapper = this.listWrapperRef.current; if (listWrapper) { this.highlight = document.createElement('div'); this.highlight.className = styles.highlight; @@ -46,17 +46,17 @@ export default class CategoryList extends React.Component { if (this.highlight) { let height = 0; for (let i = 0; i < this.props.categories.length; i++) { - let c = this.props.categories[i]; - let id = c.id || c.name; + const c = this.props.categories[i]; + const id = c.id || c.name; if (id === this.props.current) { if (this.state.openClasses.includes(id)) { const query = new URLSearchParams(this.props.params); if (query.has('filters')) { - const queryFilter = query.get('filters') + const queryFilter = query.get('filters'); for (let i = 0; i < c.filters?.length; i++) { const filter = c.filters[i]; if (queryFilter === filter.filter) { - height += (i + 1) * 20 + height += (i + 1) * 20; break; } } @@ -66,8 +66,10 @@ export default class CategoryList extends React.Component { this.highlight.style.top = height + 'px'; return; } - if (this.state.openClasses.includes(id)) { - height = height + (20 * (c.filters.length + 1)) + if (id === 'classSeparator') { + height += 13; + } else if (this.state.openClasses.includes(id)) { + height = height + 20 * (c.filters.length + 1); } else { height += 20; } @@ -94,18 +96,18 @@ export default class CategoryList extends React.Component { } return (
- {this.props.categories.map((c) => { - let id = c.id || c.name; + {this.props.categories.map(c => { + const id = c.id || c.name; if (c.type === 'separator') { return
; } - let count = c.count; + const count = c.count; let className = id === this.props.current ? styles.active : ''; let selectedFilter = null; - if (this.state.openClasses.includes(id)) { + if (this.state.openClasses.includes(id) && id === this.props.current) { const query = new URLSearchParams(this.props.params); if (query.has('filters')) { - const queryFilter = query.get('filters') + const queryFilter = query.get('filters'); for (let i = 0; i < c.filters?.length; i++) { const filter = c.filters[i]; if (queryFilter === filter.filter) { @@ -116,7 +118,7 @@ export default class CategoryList extends React.Component { } } } - let link = generatePath(this.context, (this.props.linkPrefix || '') + (c.link || id)); + const link = generatePath(this.context, (this.props.linkPrefix || '') + (c.link || id)); return (
@@ -127,7 +129,7 @@ export default class CategoryList extends React.Component { {(c.filters || []).length !== 0 && ( this.toggleDropdown(e, id)} + onClick={e => this.toggleDropdown(e, id)} style={{ transform: this.state.openClasses.includes(id) ? 'scaleY(-1)' : 'scaleY(1)', }} @@ -137,12 +139,14 @@ export default class CategoryList extends React.Component { {this.state.openClasses.includes(id) && c.filters.map((filterData, index) => { const { name, filter } = filterData; - const url = `${this.props.linkPrefix}${c.name}?filters=${encodeURIComponent(filter)}`; + const url = `${this.props.linkPrefix}${c.name}?filters=${encodeURIComponent( + filter + )}`; return (
{ + onClick={e => { e.preventDefault(); this.props.filterClicked(url); }} @@ -152,7 +156,7 @@ export default class CategoryList extends React.Component { { + onClick={e => { e.preventDefault(); this.props.removeFilter(filterData); }} @@ -171,7 +175,9 @@ export default class CategoryList extends React.Component { } CategoryList.propTypes = { - categories: PropTypes.arrayOf(PropTypes.object).describe('Array of categories used to populate list.'), + categories: PropTypes.arrayOf(PropTypes.object).describe( + 'Array of categories used to populate list.' + ), current: PropTypes.string.describe('Id of current category to be highlighted.'), linkPrefix: PropTypes.string.describe('Link prefix used to generate link path.'), }; diff --git a/src/components/Chart/Chart.example.js b/src/components/Chart/Chart.example.js index 41f5e927a1..56b4447344 100644 --- a/src/components/Chart/Chart.example.js +++ b/src/components/Chart/Chart.example.js @@ -20,15 +20,37 @@ export const demos = [ data={{ 'Group A': { color: ChartColorSchemes[0], - points: [[1439424000000,51],[1439510400000,66],[1439596800000,36],[1439683200000,64],[1439769600000,46],[1439856000000,42],[1439942400000,45],[1440028800000,70],[1440115200000,7]] + points: [ + [1439424000000, 51], + [1439510400000, 66], + [1439596800000, 36], + [1439683200000, 64], + [1439769600000, 46], + [1439856000000, 42], + [1439942400000, 45], + [1440028800000, 70], + [1440115200000, 7], + ], }, 'Group B': { color: ChartColorSchemes[1], - points: [[1439424000000,40],[1439510400000,24],[1439596800000,12],[1439683200000,80],[1439769600000,98],[1439856000000,88],[1439942400000,84],[1440028800000,88],[1440115200000,18]] + points: [ + [1439424000000, 40], + [1439510400000, 24], + [1439596800000, 12], + [1439683200000, 80], + [1439769600000, 98], + [1439856000000, 88], + [1439942400000, 84], + [1440028800000, 88], + [1440115200000, 18], + ], }, - }} /> - ) - }, { + }} + /> + ), + }, + { render: () => ( value + ' arbitrary units'} /> - ) - } + formatter={value => value + ' arbitrary units'} + /> + ), + }, ]; diff --git a/src/components/Chart/Chart.react.js b/src/components/Chart/Chart.react.js index ea555273da..1d2861b061 100644 --- a/src/components/Chart/Chart.react.js +++ b/src/components/Chart/Chart.react.js @@ -5,15 +5,15 @@ * This source code is licensed under the license found in the LICENSE file in * the root directory of this source tree. */ -import * as Charting from 'lib/Charting.js' -import * as DateUtils from 'lib/DateUtils'; -import Position from 'lib/Position'; -import prettyNumber from 'lib/prettyNumber'; -import PropTypes from 'lib/PropTypes'; -import React from 'react'; -import Shape from 'components/Chart/Shape.react'; -import { shortMonth } from 'lib/DateUtils'; -import styles from 'components/Chart/Chart.scss'; +import * as Charting from 'lib/Charting.js'; +import * as DateUtils from 'lib/DateUtils'; +import Position from 'lib/Position'; +import prettyNumber from 'lib/prettyNumber'; +import PropTypes from 'lib/PropTypes'; +import React from 'react'; +import Shape from 'components/Chart/Shape.react'; +import { shortMonth } from 'lib/DateUtils'; +import styles from 'components/Chart/Chart.scss'; const MARGIN_TOP = 10; const MARGIN_RIGHT = 20; @@ -25,7 +25,7 @@ function sortPoints(a, b) { } function formatDate(date) { - let str = DateUtils.getMonth(date.getMonth()) + ' ' + date.getDate(); + const str = DateUtils.getMonth(date.getMonth()) + ' ' + date.getDate(); if (date.getUTCHours() === 0 && date.getUTCMinutes() === 0 && date.getUTCSeconds() === 0) { return str; } @@ -59,17 +59,17 @@ export default class Chart extends React.Component { } render() { - let { width, height, data } = this.props; - let plotting = {}; + const { width, height, data } = this.props; + const plotting = {}; let minX = Infinity; let maxX = -Infinity; let maxY = -Infinity; - let chartWidth = width - MARGIN_LEFT - MARGIN_RIGHT; - let chartHeight = height - MARGIN_TOP - MARGIN_BOTTOM; + const chartWidth = width - MARGIN_LEFT - MARGIN_RIGHT; + const chartHeight = height - MARGIN_TOP - MARGIN_BOTTOM; - for (let key in data) { - let ordered = data[key].points.map(([x, y]) => [x, y]).sort(sortPoints); + for (const key in data) { + const ordered = data[key].points.map(([x, y]) => [x, y]).sort(sortPoints); for (let i = 0; i < ordered.length; i++) { if (ordered[i][0] < minX) { minX = ordered[i][0]; @@ -83,34 +83,59 @@ export default class Chart extends React.Component { } plotting[key] = { data: ordered, index: data[key].index }; } - let timeBuckets = Charting.timeAxisBuckets(minX, maxX); - let valueBuckets = Charting.valueAxisBuckets(maxY || 10); - let groups = []; - for (let key in plotting) { - let color = data[key].color; - let index = data[key].index || 0; - let points = Charting.getDataPoints(chartWidth, chartHeight, timeBuckets, valueBuckets, plotting[key].data); - let path = p.join(' ')).join(' L')} style={{ stroke: color, fill: 'none', strokeWidth: 2 }} />; + const timeBuckets = Charting.timeAxisBuckets(minX, maxX); + const valueBuckets = Charting.valueAxisBuckets(maxY || 10); + const groups = []; + for (const key in plotting) { + const color = data[key].color; + const index = data[key].index || 0; + const points = Charting.getDataPoints( + chartWidth, + chartHeight, + timeBuckets, + valueBuckets, + plotting[key].data + ); + const path = ( + p.join(' ')).join(' L')} + style={{ stroke: color, fill: 'none', strokeWidth: 2 }} + /> + ); groups.push( {path} {points.map((p, i) => ( + style={{ cursor: 'pointer' }} + > ))} ); } - let labels = valueBuckets.slice(1, valueBuckets.length - 1); - let labelHeights = labels.map((label) => chartHeight * (1 - label / valueBuckets[valueBuckets.length - 1])); - let tickPoints = timeBuckets.map((t) => chartWidth * (t - timeBuckets[0]) / (timeBuckets[timeBuckets.length - 1] - timeBuckets[0])); + const labels = valueBuckets.slice(1, valueBuckets.length - 1); + const labelHeights = labels.map( + label => chartHeight * (1 - label / valueBuckets[valueBuckets.length - 1]) + ); + const tickPoints = timeBuckets.map( + t => + (chartWidth * (t - timeBuckets[0])) / (timeBuckets[timeBuckets.length - 1] - timeBuckets[0]) + ); let last = null; - let tickLabels = timeBuckets.map((t, i) => { + const tickLabels = timeBuckets.map((t, i) => { let text = ''; if (timeBuckets.length > 20 && i % 2 === 0) { return ''; @@ -128,11 +153,11 @@ export default class Chart extends React.Component { }); let popup = null; if (this.state.hoverValue !== null) { - let style = { + const style = { color: this.state.hoverColor, borderColor: this.state.hoverColor, }; - let classes = [styles.popup]; + const classes = [styles.popup]; if (this.state.hoverPosition.x < 200) { classes.push(styles.popupRight); } else { @@ -144,12 +169,15 @@ export default class Chart extends React.Component { style={{ left: this.state.hoverPosition.x, top: this.state.hoverPosition.y, - }}> -
+ }} + > +
{formatDate(this.state.hoverTime)}
-
{this.props.formatter ? this.props.formatter(this.state.hoverValue, this.state.hoverLabel) : this.state.hoverValue}
+
+ {this.props.formatter + ? this.props.formatter(this.state.hoverValue, this.state.hoverLabel) + : this.state.hoverValue} +
); @@ -157,15 +185,46 @@ export default class Chart extends React.Component { return (
- {labels.map((v, i) =>
{prettyNumber(v)}
)} + {labels.map((v, i) => ( +
+ {prettyNumber(v)} +
+ ))}
- {tickLabels.map((t, i) =>
{t}
)} + {tickLabels.map((t, i) => ( +
+ {t} +
+ ))}
- {labelHeights.map((h) => )} - - {tickPoints.map((t, i) => )} + + {labelHeights.map(h => ( + + ))} + + + + {tickPoints.map((t, i) => ( + + ))} + {groups} {popup} @@ -175,19 +234,15 @@ export default class Chart extends React.Component { } Chart.propTypes = { - width: PropTypes.number.isRequired.describe( - 'The width of the chart.' - ), - height: PropTypes.number.isRequired.describe( - 'The height of the chart.' - ), + width: PropTypes.number.isRequired.describe('The width of the chart.'), + height: PropTypes.number.isRequired.describe('The height of the chart.'), data: PropTypes.object.isRequired.describe( - 'The data to graph. It is a map of data names to objects containing two keys: ' + - '"color," the color to use for the lines, and "points," an array of tuples containing time-value data.' + 'The data to graph. It is a map of data names to objects containing two keys: ' + + '"color," the color to use for the lines, and "points," an array of tuples containing time-value data.' ), formatter: PropTypes.func.describe( 'An optional function for formatting the data description that appears in the popup. ' + - 'It receives the numeric value of a point and label, and should return a string. ' + - 'This is ideally used for providing descriptive units like "active installations."' - ) + 'It receives the numeric value of a point and label, and should return a string. ' + + 'This is ideally used for providing descriptive units like "active installations."' + ), }; diff --git a/src/components/Chart/Shape.react.js b/src/components/Chart/Shape.react.js index 7490470630..6f40c0978b 100644 --- a/src/components/Chart/Shape.react.js +++ b/src/components/Chart/Shape.react.js @@ -5,30 +5,88 @@ * This source code is licensed under the license found in the LICENSE file in * the root directory of this source tree. */ -import React from 'react'; +import React from 'react'; import styles from 'components/Chart/Chart.scss'; function joinPoints(points) { - return points.map((p) => p.join(',')).join(' '); + return points.map(p => p.join(',')).join(' '); } -let Shape = ({ x, y, fill, index }) => { - let style = { fill: fill, stroke: 'white', strokeWidth: 2 }; +const Shape = ({ x, y, fill, index }) => { + const style = { fill: fill, stroke: 'white', strokeWidth: 2 }; switch (index % 7) { case 0: return ; case 1: - return ; + return ( + + ); case 2: - return ; + return ( + + ); case 3: - return ; + return ( + + ); case 4: - return ; + return ( + + ); case 5: - return ; + return ( + + ); case 6: - return ; + return ( + + ); } }; diff --git a/src/components/Checkbox/Checkbox.example.js b/src/components/Checkbox/Checkbox.example.js index 387c5ad9c4..a199423ba0 100644 --- a/src/components/Checkbox/Checkbox.example.js +++ b/src/components/Checkbox/Checkbox.example.js @@ -5,7 +5,7 @@ * This source code is licensed under the license found in the LICENSE file in * the root directory of this source tree. */ -import React from 'react'; +import React from 'react'; import Checkbox from 'components/Checkbox/Checkbox.react'; export const component = Checkbox; @@ -15,15 +15,15 @@ export const demos = [ render: () => (
- +
- +
- +
- ) - } + ), + }, ]; diff --git a/src/components/Checkbox/Checkbox.react.js b/src/components/Checkbox/Checkbox.react.js index 8ca96fe600..69251749de 100644 --- a/src/components/Checkbox/Checkbox.react.js +++ b/src/components/Checkbox/Checkbox.react.js @@ -5,12 +5,12 @@ * This source code is licensed under the license found in the LICENSE file in * the root directory of this source tree. */ -import Icon from 'components/Icon/Icon.react'; -import React from 'react'; +import Icon from 'components/Icon/Icon.react'; +import React from 'react'; import styles from 'components/Checkbox/Checkbox.scss'; -let Checkbox = ({ label, checked, indeterminate, onChange }) => { - let classes = [styles.input]; +const Checkbox = ({ label, checked, indeterminate, onChange }) => { + const classes = [styles.input]; if (checked) { classes.push(styles.checked); } else if (indeterminate) { @@ -18,7 +18,7 @@ let Checkbox = ({ label, checked, indeterminate, onChange }) => { } let inner = null; if (checked) { - inner = ; + inner = ; } else if (indeterminate) { inner = ; } diff --git a/src/components/Chip/Chip.example.js b/src/components/Chip/Chip.example.js index 1e3072a06f..40b89ebc83 100644 --- a/src/components/Chip/Chip.example.js +++ b/src/components/Chip/Chip.example.js @@ -6,7 +6,7 @@ * the root directory of this source tree. */ import React from 'react'; -import Chip from 'components/Chip/Chip.react'; +import Chip from 'components/Chip/Chip.react'; export const component = Chip; @@ -14,10 +14,10 @@ export const demos = [ { render: () => (
- - - + + +
- ) - } + ), + }, ]; diff --git a/src/components/Chip/Chip.react.js b/src/components/Chip/Chip.react.js index c737699cf9..f12d6c5914 100644 --- a/src/components/Chip/Chip.react.js +++ b/src/components/Chip/Chip.react.js @@ -5,43 +5,36 @@ * This source code is licensed under the license found in the LICENSE file in * the root directory of this source tree. */ -import React from 'react'; -import styles from 'components/Chip/Chip.scss'; -import PropTypes from 'lib/PropTypes' -import Icon from 'components/Icon/Icon.react' +import React from 'react'; +import styles from 'components/Chip/Chip.scss'; +import PropTypes from 'lib/PropTypes'; +import Icon from 'components/Icon/Icon.react'; -let Chip = ({ value, onClose }) => ( +const Chip = ({ value, onClose }) => (
{value}
-
{ - try{ +
{ + try { e.stopPropagation(); e.nativeEvent.stopPropagation(); - } catch(e){ + } catch (e) { console.error(e); } onClose(value); - } - }> - - + }} + > +
); export default Chip; - Chip.propTypes = { onClose: PropTypes.func.isRequired.describe( 'A function called when the close button clicked. It receives the value of as the only parameter.' ), - value: PropTypes.string.isRequired.describe( - 'The string to be rendered inside chip.' - ) -} + value: PropTypes.string.isRequired.describe('The string to be rendered inside chip.'), +}; diff --git a/src/components/ChromeDatePicker/ChromeDatePicker.example.js b/src/components/ChromeDatePicker/ChromeDatePicker.example.js index f6da903ca1..46a8297efa 100644 --- a/src/components/ChromeDatePicker/ChromeDatePicker.example.js +++ b/src/components/ChromeDatePicker/ChromeDatePicker.example.js @@ -6,8 +6,8 @@ * the root directory of this source tree. */ import ChromeDatePicker from 'components/ChromeDatePicker/ChromeDatePicker.react'; -import { Directions } from 'lib/Constants'; -import React from 'react'; +import { Directions } from 'lib/Constants'; +import React from 'react'; export const component = ChromeDatePicker; @@ -23,7 +23,11 @@ class Demo extends React.Component { render() { return ( - + ); } } @@ -35,7 +39,7 @@ export const demos = [
- ) + ), }, { name: 'Right-aligned', @@ -43,6 +47,6 @@ export const demos = [
- ) - } + ), + }, ]; diff --git a/src/components/ChromeDatePicker/ChromeDatePicker.react.js b/src/components/ChromeDatePicker/ChromeDatePicker.react.js index a607b2f4b1..83b15aa4db 100644 --- a/src/components/ChromeDatePicker/ChromeDatePicker.react.js +++ b/src/components/ChromeDatePicker/ChromeDatePicker.react.js @@ -5,17 +5,15 @@ * This source code is licensed under the license found in the LICENSE file in * the root directory of this source tree. */ -import Calendar from 'components/Calendar/Calendar.react'; +import Calendar from 'components/Calendar/Calendar.react'; import { Directions } from 'lib/Constants'; -import Icon from 'components/Icon/Icon.react'; -import { - monthDayStringUTC -} from 'lib/DateUtils'; -import Popover from 'components/Popover/Popover.react'; -import Position from 'lib/Position'; -import PropTypes from 'lib/PropTypes'; -import React from 'react'; -import styles from 'components/ChromeDatePicker/ChromeDatePicker.scss'; +import Icon from 'components/Icon/Icon.react'; +import { monthDayStringUTC } from 'lib/DateUtils'; +import Popover from 'components/Popover/Popover.react'; +import Position from 'lib/Position'; +import PropTypes from 'lib/PropTypes'; +import React from 'react'; +import styles from 'components/ChromeDatePicker/ChromeDatePicker.scss'; export default class ChromeDatePicker extends React.Component { constructor() { @@ -26,7 +24,7 @@ export default class ChromeDatePicker extends React.Component { position: null, }; - this.wrapRef = React.createRef() + this.wrapRef = React.createRef(); } toggle() { @@ -34,13 +32,13 @@ export default class ChromeDatePicker extends React.Component { if (this.state.open) { return { open: false }; } - let pos = Position.inWindow(this.wrapRef.current); + const pos = Position.inWindow(this.wrapRef.current); if (this.props.align === Directions.RIGHT) { pos.x += this.wrapRef.current.clientWidth; } return { open: true, - position: pos + position: pos, }; }); } @@ -52,7 +50,7 @@ export default class ChromeDatePicker extends React.Component { close() { this.setState({ - open: false + open: false, }); } @@ -60,21 +58,23 @@ export default class ChromeDatePicker extends React.Component { let popover = null; let content = null; if (this.state.open) { - let classes = [styles.open]; + const classes = [styles.open]; if (this.props.align === Directions.RIGHT) { classes.push(styles.right); } popover = ( - +
- +
{`${monthDayStringUTC(this.props.value)}`} - +
@@ -83,7 +83,7 @@ export default class ChromeDatePicker extends React.Component { content = (
{`${monthDayStringUTC(this.props.value)}`} - +
); } @@ -98,9 +98,7 @@ export default class ChromeDatePicker extends React.Component { } ChromeDatePicker.propTypes = { - value: PropTypes.object.describe( - 'The Date value of the picker.' - ), + value: PropTypes.object.describe('The Date value of the picker.'), onChange: PropTypes.func.describe( 'A function called when the date picker is changed. It receives a new Date value.' ), diff --git a/src/components/ChromeDropdown/ChromeDropdown.example.js b/src/components/ChromeDropdown/ChromeDropdown.example.js index b8b095d424..4949adf963 100644 --- a/src/components/ChromeDropdown/ChromeDropdown.example.js +++ b/src/components/ChromeDropdown/ChromeDropdown.example.js @@ -6,7 +6,7 @@ * the root directory of this source tree. */ import ChromeDropdown from 'components/ChromeDropdown/ChromeDropdown.react'; -import React from 'react'; +import React from 'react'; export const component = ChromeDropdown; @@ -22,8 +22,9 @@ class DropdownDemo extends React.Component { this.setState({ color })} - options={['Blue', 'Purple']} /> + onChange={color => this.setState({ color })} + options={['Blue', 'Purple']} + /> ); } } @@ -41,8 +42,9 @@ class DropdownDemo2 extends React.Component { placeholder={'Choose a color'} value={this.state.color} color={this.state.color.toLowerCase()} - onChange={(color) => this.setState({ color })} - options={['Blue', 'Purple']} /> + onChange={color => this.setState({ color })} + options={['Blue', 'Purple']} + /> ); } } @@ -60,17 +62,18 @@ class DropdownDemo3 extends React.Component { placeholder={'Choose a color'} value={this.state.color} color={this.state.color} - onChange={(color) => this.setState({ color })} + onChange={color => this.setState({ color })} options={[ { key: 'blue', - value: 'Blue' + value: 'Blue', }, { key: 'purple', - value: 'Purple' - } - ]} /> + value: 'Purple', + }, + ]} + /> ); } } @@ -81,7 +84,7 @@ export const demos = [
- ) + ), }, { name: 'ChromeDropdown with placeholder', @@ -89,7 +92,7 @@ export const demos = [
- ) + ), }, { name: 'ChromeDropdown with object', @@ -97,6 +100,6 @@ export const demos = [
- ) - } + ), + }, ]; diff --git a/src/components/ChromeDropdown/ChromeDropdown.react.js b/src/components/ChromeDropdown/ChromeDropdown.react.js index d83c8f411f..38caf33659 100644 --- a/src/components/ChromeDropdown/ChromeDropdown.react.js +++ b/src/components/ChromeDropdown/ChromeDropdown.react.js @@ -5,11 +5,11 @@ * This source code is licensed under the license found in the LICENSE file in * the root directory of this source tree. */ -import Popover from 'components/Popover/Popover.react'; +import Popover from 'components/Popover/Popover.react'; import PropTypes from 'lib/PropTypes'; -import Position from 'lib/Position'; -import React from 'react'; -import styles from 'components/ChromeDropdown/ChromeDropdown.scss'; +import Position from 'lib/Position'; +import React from 'react'; +import styles from 'components/ChromeDropdown/ChromeDropdown.scss'; export default class ChromeDropdown extends React.Component { constructor() { @@ -25,7 +25,7 @@ export default class ChromeDropdown extends React.Component { componentWillReceiveProps(nextProps) { this.keyValueMap = {}; - nextProps.options.forEach((value) => { + nextProps.options.forEach(value => { if (value instanceof Object) { this.keyValueMap[value.key] = value.value; } @@ -41,18 +41,21 @@ export default class ChromeDropdown extends React.Component { select(value, e) { e.stopPropagation(); - this.setState({ - open: false, - selected: true, - }, () => { - this.props.onChange(value); - }); + this.setState( + { + open: false, + selected: true, + }, + () => { + this.props.onChange(value); + } + ); } render() { let widthStyle = { width: parseFloat(this.props.width || 140) }; - let styles = this.styles; - let color = this.props.color || 'purple'; + const styles = this.styles; + const color = this.props.color || 'purple'; let label = this.props.value; if (this.keyValueMap) { @@ -63,26 +66,40 @@ export default class ChromeDropdown extends React.Component { label = this.props.placeholder; } let content = ( -
this.setState({ open: true })}> +
this.setState({ open: true })} + >
{label}
); if (this.state.open) { - let position = Position.inWindow(this.dropdownRef.current); - let measuredWidth = parseFloat(this.dropdownRef.current.offsetWidth); + const position = Position.inWindow(this.dropdownRef.current); + const measuredWidth = parseFloat(this.dropdownRef.current.offsetWidth); widthStyle = { width: measuredWidth }; content = ( - this.setState({ open: false })}> -
- {this.props.options.map((o) => { + this.setState({ open: false })} + > +
+ {this.props.options.map(o => { let key = o; let value = o; if (o instanceof Object) { key = o.key; value = o.value; } - return
{value}
+ return ( +
+ {value} +
+ ); })}
@@ -98,25 +115,13 @@ export default class ChromeDropdown extends React.Component { } ChromeDropdown.propTypes = { - color: PropTypes.oneOf(['blue', 'purple']).describe( - 'Determines the color of the dropdown.' - ), - value: PropTypes.string.isRequired.describe( - 'The current value of the dropdown.' - ), + color: PropTypes.oneOf(['blue', 'purple']).describe('Determines the color of the dropdown.'), + value: PropTypes.string.isRequired.describe('The current value of the dropdown.'), options: PropTypes.array.isRequired.describe( 'An array of options available in the dropdown. Can be an array of string or array of { key, value }' ), - onChange: PropTypes.func.isRequired.describe( - 'A function called when the dropdown is changed.' - ), - width: PropTypes.string.describe( - 'An optional width override.' - ), - placeholder: PropTypes.string.describe( - 'Placeholder text used in place of default selection.' - ), - styles: PropTypes.object.describe( - 'Styles override used to provide dropdown with differnt skin.' - ), + onChange: PropTypes.func.isRequired.describe('A function called when the dropdown is changed.'), + width: PropTypes.string.describe('An optional width override.'), + placeholder: PropTypes.string.describe('Placeholder text used in place of default selection.'), + styles: PropTypes.object.describe('Styles override used to provide dropdown with differnt skin.'), }; diff --git a/src/components/CodeEditor/CodeEditor.example.js b/src/components/CodeEditor/CodeEditor.example.js index 87ba16cc77..4b9cb25b4b 100644 --- a/src/components/CodeEditor/CodeEditor.example.js +++ b/src/components/CodeEditor/CodeEditor.example.js @@ -13,8 +13,6 @@ export const component = CodeEditor; export const demos = [ { name: 'Simple code editor (only JS support)', - render: () => ( - - ) - } + render: () => , + }, ]; diff --git a/src/components/CodeEditor/CodeEditor.react.js b/src/components/CodeEditor/CodeEditor.react.js index a3231bf9a8..bf865041a9 100644 --- a/src/components/CodeEditor/CodeEditor.react.js +++ b/src/components/CodeEditor/CodeEditor.react.js @@ -56,5 +56,5 @@ export default class CodeEditor extends React.Component { CodeEditor.propTypes = { fontSize: PropTypes.number.describe('Font size of the editor'), - placeHolder: PropTypes.string.describe('Code place holder') + placeHolder: PropTypes.string.describe('Code place holder'), }; diff --git a/src/components/CodeSnippet/CodeSnippet.example.js b/src/components/CodeSnippet/CodeSnippet.example.js index 699be4da7a..0822076280 100644 --- a/src/components/CodeSnippet/CodeSnippet.example.js +++ b/src/components/CodeSnippet/CodeSnippet.example.js @@ -5,7 +5,7 @@ * This source code is licensed under the license found in the LICENSE file in * the root directory of this source tree. */ -import React from 'react'; +import React from 'react'; import CodeSnippet from 'components/CodeSnippet/CodeSnippet.react'; export const component = CodeSnippet; @@ -13,28 +13,24 @@ export const component = CodeSnippet; export const demos = [ { render() { - let source = `// Some comment here + const source = `// Some comment here Parse.Cloud.define('hello', function(req, resp) { let someVariable = "
"; let otherVariable = "
"; -});` +});`; - return ( - - ) - } + return ; + }, }, { name: 'Print JSON', render() { - let obj = { + const obj = { this: 'is awesome', - awesome: true + awesome: true, }; - return ( - - ) - } - } + return ; + }, + }, ]; diff --git a/src/components/CodeSnippet/CodeSnippet.react.js b/src/components/CodeSnippet/CodeSnippet.react.js index 85b57bbe89..521d09d5a4 100644 --- a/src/components/CodeSnippet/CodeSnippet.react.js +++ b/src/components/CodeSnippet/CodeSnippet.react.js @@ -6,8 +6,8 @@ * the root directory of this source tree. */ import PropTypes from 'lib/PropTypes'; -import React from 'react'; -import Prism from 'prismjs'; +import React from 'react'; +import Prism from 'prismjs'; import './CodeSnippet.css'; import 'prismjs/plugins/line-numbers/prism-line-numbers'; @@ -31,15 +31,17 @@ export default class CodeSnippet extends React.Component { } render() { - let { fullPage = true, lineNumbers = true } = this.props; - let classes = ['language-' + this.props.language]; + const { fullPage = true, lineNumbers = true } = this.props; + const classes = ['language-' + this.props.language]; if (lineNumbers) { classes.push('line-numbers'); } - let pageStyle = fullPage ? { minHeight: 'calc(100vh - 96px)'} : {}; + const pageStyle = fullPage ? { minHeight: 'calc(100vh - 96px)' } : {}; return ( -
-        {this.props.source}
+      
+        
+          {this.props.source}
+        
       
); } @@ -49,13 +51,11 @@ CodeSnippet.propTypes = { source: PropTypes.string.isRequired.describe( 'The source code to be rendered with syntax-highlighting.' ), - language: PropTypes.string.describe( - 'The programming language of the snippet.' - ), + language: PropTypes.string.describe('The programming language of the snippet.'), fullPage: PropTypes.bool.describe( 'Pass false if this component doesn\'t need to fill the whole page.' ), lineNumbers: PropTypes.bool.describe( 'Pass false if this component doesn\'t need to print line numbers.' - ) + ), }; diff --git a/src/components/ColumnsConfiguration/ColumnConfigurationItem.react.js b/src/components/ColumnsConfiguration/ColumnConfigurationItem.react.js index 279beb1926..d4b0f5d70a 100644 --- a/src/components/ColumnsConfiguration/ColumnConfigurationItem.react.js +++ b/src/components/ColumnsConfiguration/ColumnConfigurationItem.react.js @@ -7,37 +7,47 @@ import styles from 'components/ColumnsConfiguration/ColumnConfigurationItem.scss const DND_TYPE = 'ColumnConfigurationItem'; export default ({ name, handleColumnDragDrop, index, onChangeVisible, visible }) => { - const [ { isDragging}, drag ] = useDrag({ + const [{ isDragging }, drag] = useDrag({ item: { type: DND_TYPE, index }, - collect: monitor => ({ isDragging: !!monitor.isDragging() }) + collect: monitor => ({ isDragging: !!monitor.isDragging() }), }); - const [ { canDrop, isOver }, drop ] = useDrop({ + const [{ canDrop, isOver }, drop] = useDrop({ accept: DND_TYPE, drop: item => handleColumnDragDrop(item.index, index), canDrop: item => item.index !== index, collect: monitor => ({ isOver: !!monitor.isOver(), - canDrop: !!monitor.canDrop() - }) + canDrop: !!monitor.canDrop(), + }), }); - return drag(drop( -
onChangeVisible(!visible)}> -
- -
-
{name}
-
- -
-
- )); + return drag( + drop( +
onChangeVisible(!visible)} + > +
+ +
+
+ {name} +
+
+ +
+
+ ) + ); }; diff --git a/src/components/ColumnsConfiguration/ColumnsConfiguration.react.js b/src/components/ColumnsConfiguration/ColumnsConfiguration.react.js index 45c9389087..7328db1afb 100644 --- a/src/components/ColumnsConfiguration/ColumnsConfiguration.react.js +++ b/src/components/ColumnsConfiguration/ColumnsConfiguration.react.js @@ -1,6 +1,6 @@ import React from 'react'; -import { DndProvider } from 'react-dnd' -import HTML5Backend from 'react-dnd-html5-backend' +import { DndProvider } from 'react-dnd'; +import HTML5Backend from 'react-dnd-html5-backend'; import Button from 'components/Button/Button.react'; import ColumnConfigurationItem from 'components/ColumnsConfiguration/ColumnConfigurationItem.react'; @@ -16,7 +16,7 @@ export default class ColumnsConfiguration extends React.Component { super(); this.state = { - open: false + open: false, }; this.entryRef = React.createRef(); @@ -25,24 +25,24 @@ export default class ColumnsConfiguration extends React.Component { componentWillReceiveProps(props) { if (props.schema !== this.props.schema) { this.setState({ - open: false + open: false, }); } } toggle() { this.setState({ - open: !this.state.open - }) + open: !this.state.open, + }); } showAll() { let shouldReload = false; - let updatedOrder = this.props.order.map(field => { + const updatedOrder = this.props.order.map(field => { if (!shouldReload && !field.cached) { shouldReload = true; } - return { ...field, visible: true } + return { ...field, visible: true }; }); this.props.handleColumnsOrder(updatedOrder, shouldReload); } @@ -52,94 +52,107 @@ export default class ColumnsConfiguration extends React.Component { } autoSort() { - const defaultOrder = ['objectId', 'createdAt', 'updatedAt', 'ACL'] + const defaultOrder = ['objectId', 'createdAt', 'updatedAt', 'ACL']; const order = { default: [], - other: [] + other: [], }; for (const column of this.props.order) { const index = defaultOrder.indexOf(column.name); if (index !== -1) { order.default[index] = column; } else { - order.other.push(column) + order.other.push(column); } } - this.props.handleColumnsOrder([...order.default.filter(column => column), ...order.other.sort((a,b) => a.name.localeCompare(b.name))]); + this.props.handleColumnsOrder([ + ...order.default.filter(column => column), + ...order.other.sort((a, b) => a.name.localeCompare(b.name)), + ]); } render() { const { handleColumnDragDrop, handleColumnsOrder, order, disabled } = this.props; - const title =
- - Manage Columns -
+ const title = ( +
+ + Manage Columns +
+ ); - let entry =
- - Manage Columns -
+ let entry = ( +
+ + Manage Columns +
+ ); if (disabled) { - entry =
- - Manage Columns -
; + entry = ( +
+ + Manage Columns +
+ ); } let popover = null; if (this.state.open) { popover = ( - +
{title}
{order.map(({ name, visible, ...rest }, index) => { - return { - const updatedOrder = [...order]; - updatedOrder[index] = { - ...rest, - name, - visible - }; - let shouldReload = visible; - // these fields are always cached as they are never excluded from server - // therefore no need to make another request. - if (name === 'objectId' || name === 'createdAt' || name === 'updatedAt' || name === 'ACL') { - shouldReload = false; - } - if (this.props.className === '_User' && name === 'password') { - shouldReload = false; - } - if (updatedOrder[index].cached) { - shouldReload = false; - } - handleColumnsOrder(updatedOrder, shouldReload); - }} - handleColumnDragDrop={handleColumnDragDrop} /> + return ( + { + const updatedOrder = [...order]; + updatedOrder[index] = { + ...rest, + name, + visible, + }; + let shouldReload = visible; + // these fields are always cached as they are never excluded from server + // therefore no need to make another request. + if ( + name === 'objectId' || + name === 'createdAt' || + name === 'updatedAt' || + name === 'ACL' + ) { + shouldReload = false; + } + if (this.props.className === '_User' && name === 'password') { + shouldReload = false; + } + if (updatedOrder[index].cached) { + shouldReload = false; + } + handleColumnsOrder(updatedOrder, shouldReload); + }} + handleColumnDragDrop={handleColumnDragDrop} + /> + ); })}
-
diff --git a/src/components/ContextMenu/ContextMenu.example.js b/src/components/ContextMenu/ContextMenu.example.js index 10afe9c1c8..f3932767b3 100644 --- a/src/components/ContextMenu/ContextMenu.example.js +++ b/src/components/ContextMenu/ContextMenu.example.js @@ -14,35 +14,70 @@ export const demos = [ { name: 'Context menu', render: () => ( -
+
{ alert('C1 Item 1 clicked!') } }, - { text: 'C1 Item 2', callback: () => { alert('C1 Item 2 clicked!') } }, + text: 'Category 1', + items: [ { - text: 'Sub Category 1', items: [ - { text: 'SC1 Item 1', callback: () => { alert('SC1 Item 1 clicked!') } }, - { text: 'SC1 Item 2', callback: () => { alert('SC1 Item 2 clicked!') } }, - ] - } - ] + text: 'C1 Item 1', + callback: () => { + alert('C1 Item 1 clicked!'); + }, + }, + { + text: 'C1 Item 2', + callback: () => { + alert('C1 Item 2 clicked!'); + }, + }, + { + text: 'Sub Category 1', + items: [ + { + text: 'SC1 Item 1', + callback: () => { + alert('SC1 Item 1 clicked!'); + }, + }, + { + text: 'SC1 Item 2', + callback: () => { + alert('SC1 Item 2 clicked!'); + }, + }, + ], + }, + ], }, { - text: 'Category 2', items: [ - { text: 'C2 Item 1', callback: () => { alert('C2 Item 1 clicked!') } }, - { text: 'C2 Item 2', callback: () => { alert('C2 Item 2 clicked!') } } - ] - } + text: 'Category 2', + items: [ + { + text: 'C2 Item 1', + callback: () => { + alert('C2 Item 1 clicked!'); + }, + }, + { + text: 'C2 Item 2', + callback: () => { + alert('C2 Item 2 clicked!'); + }, + }, + ], + }, ]} />
- ) - } + ), + }, ]; diff --git a/src/components/ContextMenu/ContextMenu.react.js b/src/components/ContextMenu/ContextMenu.react.js index 611e1e0a99..04d973b7d9 100644 --- a/src/components/ContextMenu/ContextMenu.react.js +++ b/src/components/ContextMenu/ContextMenu.react.js @@ -9,22 +9,20 @@ import PropTypes from 'lib/PropTypes'; import React, { useState, useEffect, useRef } from 'react'; import styles from 'components/ContextMenu/ContextMenu.scss'; -const getPositionToFitVisibleScreen = (ref) => { +const getPositionToFitVisibleScreen = ref => { if (ref.current) { - const elBox = ref.current.getBoundingClientRect(); - const y = (elBox.y + elBox.height) < window.innerHeight ? - 0 : (0 - elBox.y + 100); + const y = elBox.y + elBox.height < window.innerHeight ? 0 : 0 - elBox.y + 100; // If there's a previous element show current next to it. // Try on right side first, then on left if there's no place. const prevEl = ref.current.previousSibling; if (prevEl) { const prevElBox = prevEl.getBoundingClientRect(); - const showOnRight = (prevElBox.x + prevElBox.width + elBox.width) < window.innerWidth; + const showOnRight = prevElBox.x + prevElBox.width + elBox.width < window.innerWidth; return { x: showOnRight ? prevElBox.width : -elBox.width, - y + y, }; } @@ -33,7 +31,6 @@ const getPositionToFitVisibleScreen = (ref) => { }; const MenuSection = ({ level, items, path, setPath, hide }) => { - const sectionRef = useRef(null); const [position, setPosition] = useState(); @@ -42,18 +39,21 @@ const MenuSection = ({ level, items, path, setPath, hide }) => { newPosition && setPosition(newPosition); }, [sectionRef]); - const style = position ? { - left: position.x, - top: position.y, - maxHeight: '80vh', - overflowY: 'scroll', - opacity: 1 - } : {}; - - return (
    - {items.map((item, index) => { - if (item.items) { - return ( + const style = position + ? { + left: position.x, + top: position.y, + maxHeight: '80vh', + overflowY: 'scroll', + opacity: 1, + } + : {}; + + return ( +
      + {items.map((item, index) => { + if (item.items) { + return (
    • { {item.text}
    • ); - } - return ( + } + return (
    • { {item.subtext && - {item.subtext}}
    • ); - })} -
    ); -} - -let ContextMenu = ({ x, y, items }) => { + })} +
+ ); +}; +const ContextMenu = ({ x, y, items }) => { const [path, setPath] = useState([0]); const [visible, setVisible] = useState(true); - useEffect(() => { setVisible(true); }, [items]); + useEffect(() => { + setVisible(true); + }, [items]); const hide = () => { setVisible(false); setPath([0]); - } + }; //#region Closing menu after clicking outside it @@ -114,38 +116,49 @@ let ContextMenu = ({ x, y, items }) => { //#endregion - if (!visible) { return null; } + if (!visible) { + return null; + } - const getItemsFromLevel = (level) => { + const getItemsFromLevel = level => { let result = items; for (let index = 1; index <= level; index++) { result = result[path[index]].items; } return result; - } + }; return ( -
+
{path.map((position, level) => { - return + return ( + + ); })}
); -} +}; ContextMenu.propTypes = { x: PropTypes.number.isRequired.describe('X context menu position.'), y: PropTypes.number.isRequired.describe('Y context menu position.'), - items: PropTypes.array.isRequired.describe('Array with tree representation of context menu items.'), -} + items: PropTypes.array.isRequired.describe( + 'Array with tree representation of context menu items.' + ), +}; export default ContextMenu; diff --git a/src/components/CreditCardInput/CreditCardInput.example.js b/src/components/CreditCardInput/CreditCardInput.example.js index 0d5cc55c9d..2d8585c8b1 100644 --- a/src/components/CreditCardInput/CreditCardInput.example.js +++ b/src/components/CreditCardInput/CreditCardInput.example.js @@ -6,9 +6,9 @@ * the root directory of this source tree. */ import CreditCardInput from 'components/CreditCardInput/CreditCardInput.react'; -import React from 'react'; -import Field from 'components/Field/Field.react'; -import Label from 'components/Label/Label.react'; +import React from 'react'; +import Field from 'components/Field/Field.react'; +import Label from 'components/Label/Label.react'; class Demo extends React.Component { constructor() { @@ -21,7 +21,8 @@ class Demo extends React.Component { this.setState({ value })} /> + onChange={value => this.setState({ value })} + /> ); } } @@ -32,15 +33,15 @@ export const demos = [ { render: () => (
- } input={} /> + } input={} />
- ) + ), }, { render: () => (
- } input={} /> + } input={} />
- ) - } + ), + }, ]; diff --git a/src/components/CreditCardInput/CreditCardInput.react.js b/src/components/CreditCardInput/CreditCardInput.react.js index c9eeb524cb..6376e40f9d 100644 --- a/src/components/CreditCardInput/CreditCardInput.react.js +++ b/src/components/CreditCardInput/CreditCardInput.react.js @@ -6,8 +6,8 @@ * the root directory of this source tree. */ import PropTypes from 'lib/PropTypes'; -import React from 'react'; -import styles from 'components/CreditCardInput/CreditCardInput.scss'; +import React from 'react'; +import styles from 'components/CreditCardInput/CreditCardInput.scss'; const VALID_REGEX = /^[\d ]*$/; @@ -26,7 +26,8 @@ class CreditCardInput extends React.Component { } render() { - let { value, lastFour, onChange } = this.props + let { value } = this.props; + const { lastFour, onChange } = this.props; let prefilled = false; if (value == null && lastFour) { prefilled = true; @@ -35,24 +36,25 @@ class CreditCardInput extends React.Component { return ( { + onFocus={() => { if (prefilled) { onChange(''); } }} onChange={e => { - let newValue = e.target.value; + const newValue = e.target.value; if (VALID_REGEX.test(newValue)) { onChange(newValue.replace(/\s/g, '')); - this.setState({cursorPosition: e.target.selectionStart}); + this.setState({ cursorPosition: e.target.selectionStart }); } else { //If they try to type a non-digit, don't move the cursor. - this.setState({cursorPosition: e.target.selectionStart - 1}); + this.setState({ cursorPosition: e.target.selectionStart - 1 }); } - }} /> + }} + /> ); } } @@ -60,13 +62,9 @@ class CreditCardInput extends React.Component { export default CreditCardInput; CreditCardInput.propTypes = { - value: PropTypes.string.describe( - 'The current value of the controlled input.' - ), + value: PropTypes.string.describe('The current value of the controlled input.'), lastFour: PropTypes.string.describe( 'If provided, and the current value is falsy, the input will render as "•••• •••• •••• {lastFour}"' ), - onChange: PropTypes.func.describe( - 'A function called when the input is changed.' - ) + onChange: PropTypes.func.describe('A function called when the input is changed.'), }; diff --git a/src/components/DataBrowserHeader/DataBrowserHeader.example.js b/src/components/DataBrowserHeader/DataBrowserHeader.example.js index 83d9e2a05c..c62e8beeb4 100644 --- a/src/components/DataBrowserHeader/DataBrowserHeader.example.js +++ b/src/components/DataBrowserHeader/DataBrowserHeader.example.js @@ -5,14 +5,14 @@ * This source code is licensed under the license found in the LICENSE file in * the root directory of this source tree. */ -import DataBrowserHeader from 'components/DataBrowserHeader/DataBrowserHeader.react'; -import HTML5Backend from 'react-dnd-html5-backend'; -import React from 'react'; -import { DndProvider } from 'react-dnd' +import DataBrowserHeader from 'components/DataBrowserHeader/DataBrowserHeader.react'; +import HTML5Backend from 'react-dnd-html5-backend'; +import React from 'react'; +import { DndProvider } from 'react-dnd'; export const component = DataBrowserHeader; -let lightBg = { background: 'rgba(224,224,234,0.10)' }; +const lightBg = { background: 'rgba(224,224,234,0.10)' }; class HeadersDemo extends React.Component { render() { @@ -20,22 +20,26 @@ class HeadersDemo extends React.Component {
- +
- +
- +
- +
- +
- +
@@ -45,8 +49,6 @@ class HeadersDemo extends React.Component { export const demos = [ { - render: () => ( - - ) - } + render: () => , + }, ]; diff --git a/src/components/DataBrowserHeader/DataBrowserHeader.react.js b/src/components/DataBrowserHeader/DataBrowserHeader.react.js index 2e17b297d5..04557dc9a4 100644 --- a/src/components/DataBrowserHeader/DataBrowserHeader.react.js +++ b/src/components/DataBrowserHeader/DataBrowserHeader.react.js @@ -5,14 +5,14 @@ * This source code is licensed under the license found in the LICENSE file in * the root directory of this source tree. */ -import PropTypes from 'lib/PropTypes'; -import React from 'react'; -import styles from 'components/DataBrowserHeader/DataBrowserHeader.scss'; -import baseStyles from 'stylesheets/base.scss'; +import PropTypes from 'lib/PropTypes'; +import React from 'react'; +import styles from 'components/DataBrowserHeader/DataBrowserHeader.scss'; +import baseStyles from 'stylesheets/base.scss'; import { DragSource, DropTarget } from 'react-dnd'; const Types = { - DATA_BROWSER_HEADER: 'dataBrowserHeader' + DATA_BROWSER_HEADER: 'dataBrowserHeader', }; const dataBrowserHeaderTarget = { @@ -33,7 +33,7 @@ const dataBrowserHeaderTarget = { props.moveDataBrowserHeader(dragIndex, hoverIndex); }, -} +}; const dataBrowserHeaderSource = { beginDrag(props) { @@ -41,21 +41,31 @@ const dataBrowserHeaderSource = { name: props.name, index: props.index, }; - } + }, }; @DropTarget(Types.DATA_BROWSER_HEADER, dataBrowserHeaderTarget, (connect, monitor) => ({ connectDropTarget: connect.dropTarget(), - isOver: monitor.isOver() + isOver: monitor.isOver(), })) @DragSource(Types.DATA_BROWSER_HEADER, dataBrowserHeaderSource, (connect, monitor) => ({ connectDragSource: connect.dragSource(), - isDragging: monitor.isDragging() + isDragging: monitor.isDragging(), })) class DataBrowserHeader extends React.Component { render() { - let { connectDragSource, connectDropTarget, name, type, targetClass, order, style, isDragging, isOver } = this.props; - let classes = [styles.header, baseStyles.unselectable]; + const { + connectDragSource, + connectDropTarget, + name, + type, + targetClass, + order, + style, + isDragging, + isOver, + } = this.props; + const classes = [styles.header, baseStyles.unselectable]; if (order) { classes.push(styles[order]); } @@ -65,27 +75,23 @@ class DataBrowserHeader extends React.Component { if (isDragging) { classes.push(styles.dragging); } - return connectDragSource(connectDropTarget( -
-
{name}
-
{targetClass ? `${type} <${targetClass}>` : type}
-
- )); + return connectDragSource( + connectDropTarget( +
+
{name}
+
{targetClass ? `${type} <${targetClass}>` : type}
+
+ ) + ); } } export default DataBrowserHeader; DataBrowserHeader.propTypes = { - name: PropTypes.string.isRequired.describe( - 'The name of the column.' - ), - type: PropTypes.string.describe( - 'The type of the column.' - ), - targetClass: PropTypes.string.describe( - 'The target class for a Pointer or Relation.' - ), + name: PropTypes.string.isRequired.describe('The name of the column.'), + type: PropTypes.string.describe('The type of the column.'), + targetClass: PropTypes.string.describe('The target class for a Pointer or Relation.'), order: PropTypes.oneOf(['ascending', 'descending']).describe( 'A sort ordering that displays as an arrow in the header.' ), diff --git a/src/components/DataBrowserHeaderBar/DataBrowserHeaderBar.react.js b/src/components/DataBrowserHeaderBar/DataBrowserHeaderBar.react.js index d021924386..e714759376 100644 --- a/src/components/DataBrowserHeaderBar/DataBrowserHeaderBar.react.js +++ b/src/components/DataBrowserHeaderBar/DataBrowserHeaderBar.react.js @@ -5,38 +5,49 @@ * This source code is licensed under the license found in the LICENSE file in * the root directory of this source tree. */ -import DataBrowserHeader from 'components/DataBrowserHeader/DataBrowserHeader.react'; -import DragHandle from 'components/DragHandle/DragHandle.react'; -import HTML5Backend from 'react-dnd-html5-backend'; -import React from 'react'; -import styles from 'components/DataBrowserHeaderBar/DataBrowserHeaderBar.scss'; -import { DndProvider } from 'react-dnd' +import DataBrowserHeader from 'components/DataBrowserHeader/DataBrowserHeader.react'; +import DragHandle from 'components/DragHandle/DragHandle.react'; +import HTML5Backend from 'react-dnd-html5-backend'; +import React from 'react'; +import styles from 'components/DataBrowserHeaderBar/DataBrowserHeaderBar.scss'; +import { DndProvider } from 'react-dnd'; export default class DataBrowserHeaderBar extends React.Component { render() { - let { headers, onResize, selectAll, onAddColumn, updateOrdering, readonly, preventSchemaEdits, selected, isDataLoaded } = this.props; - let elements = [ -
- {readonly - ? null - : selectAll(e.target.checked)} /> - } -
+ const { + headers, + onResize, + selectAll, + onAddColumn, + updateOrdering, + readonly, + preventSchemaEdits, + selected, + isDataLoaded, + } = this.props; + const elements = [ +
+ {readonly ? null : ( + selectAll(e.target.checked)} /> + )} +
, ]; headers.forEach(({ width, name, type, targetClass, order, visible, preventSort }, i) => { - if (!visible) return; - let wrapStyle = { width }; + if (!visible) { + return; + } + const wrapStyle = { width }; if (i % 2) { wrapStyle.background = '#726F85'; } else { wrapStyle.background = '#66637A'; } let onClick = null; - if (!preventSort && (type === 'String' || type === 'Number' || type === 'Date' || type === 'Boolean')) { + if ( + !preventSort && + (type === 'String' || type === 'Number' || type === 'Date' || type === 'Boolean') + ) { onClick = () => updateOrdering((order === 'descending' ? '' : '-') + name); } @@ -46,18 +57,15 @@ export default class DataBrowserHeaderBar extends React.Component { } elements.push( -
+
+ moveDataBrowserHeader={this.props.handleDragDrop} + />
); elements.push( @@ -66,18 +74,15 @@ export default class DataBrowserHeaderBar extends React.Component { }); if (onAddColumn) { - let finalStyle = {}; + const finalStyle = {}; if (headers.length % 2) { finalStyle.background = 'rgba(224,224,234,0.10)'; } elements.push( readonly || preventSchemaEdits ? null : ( -
-
@@ -86,8 +91,10 @@ export default class DataBrowserHeaderBar extends React.Component { } function renderSkeleton() { - if (isDataLoaded) return null; - var skeletons = [1, 0.9, 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1]; + if (isDataLoaded) { + return null; + } + const skeletons = [1, 0.9, 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1]; return (
{skeletons.map(function (opacity, index) { @@ -112,6 +119,6 @@ export default class DataBrowserHeaderBar extends React.Component { {renderSkeleton()}
- ) + ); } } diff --git a/src/components/DatePicker/DatePicker.example.js b/src/components/DatePicker/DatePicker.example.js index d7e3f55728..d5a57b5920 100644 --- a/src/components/DatePicker/DatePicker.example.js +++ b/src/components/DatePicker/DatePicker.example.js @@ -6,9 +6,9 @@ * the root directory of this source tree. */ import DatePicker from 'components/DatePicker/DatePicker.react'; -import Field from 'components/Field/Field.react'; -import Label from 'components/Label/Label.react'; -import React from 'react'; +import Field from 'components/Field/Field.react'; +import Label from 'components/Label/Label.react'; +import React from 'react'; export const component = DatePicker; @@ -23,9 +23,7 @@ class DatePickerDemo extends React.Component { } render() { - return ( - - ); + return ; } } @@ -34,9 +32,10 @@ export const demos = [ render: () => (
} - input={} /> + label={
- ) - } + ), + }, ]; diff --git a/src/components/DatePicker/DatePicker.react.js b/src/components/DatePicker/DatePicker.react.js index 6ae363adf9..f6718abcdb 100644 --- a/src/components/DatePicker/DatePicker.react.js +++ b/src/components/DatePicker/DatePicker.react.js @@ -5,23 +5,23 @@ * This source code is licensed under the license found in the LICENSE file in * the root directory of this source tree. */ -import Calendar from 'components/Calendar/Calendar.react'; +import Calendar from 'components/Calendar/Calendar.react'; import { Directions } from 'lib/Constants'; -import { MONTHS } from 'lib/DateUtils'; -import Popover from 'components/Popover/Popover.react'; -import Position from 'lib/Position'; -import React from 'react'; -import SliderWrap from 'components/SliderWrap/SliderWrap.react'; -import styles from 'components/DatePicker/DatePicker.scss'; +import { MONTHS } from 'lib/DateUtils'; +import Popover from 'components/Popover/Popover.react'; +import Position from 'lib/Position'; +import React from 'react'; +import SliderWrap from 'components/SliderWrap/SliderWrap.react'; +import styles from 'components/DatePicker/DatePicker.scss'; export default class DatePicker extends React.Component { constructor() { super(); this.state = { open: false, - position: null - } - this.inputRef = React.createRef() + position: null, + }; + this.inputRef = React.createRef(); } toggle() { @@ -31,32 +31,35 @@ export default class DatePicker extends React.Component { } return { open: true, - position: Position.inDocument(this.inputRef.current) + position: Position.inDocument(this.inputRef.current), }; }); } close() { this.setState({ - open: false + open: false, }); } render() { let popover = null; if (this.state.open) { - let width = this.inputRef.current.clientWidth; + const width = this.inputRef.current.clientWidth; popover = (
- { - this.setState({ open: false }, this.props.onChange.bind(null, newValue)); - }} /> + { + this.setState({ open: false }, this.props.onChange.bind(null, newValue)); + }} + />
- ) + ); } let content = null; @@ -65,11 +68,14 @@ export default class DatePicker extends React.Component { } else { content = (
- {`${MONTHS[this.props.value.getMonth()].substr(0, 3)} ${this.props.value.getDate()}, ${this.props.value.getFullYear()}`} + {`${MONTHS[this.props.value.getMonth()].substr( + 0, + 3 + )} ${this.props.value.getDate()}, ${this.props.value.getFullYear()}`}
); } - + return (
{content} diff --git a/src/components/DateRange/DateRange.example.js b/src/components/DateRange/DateRange.example.js index b9b169ffaf..43d4a3c093 100644 --- a/src/components/DateRange/DateRange.example.js +++ b/src/components/DateRange/DateRange.example.js @@ -5,9 +5,9 @@ * This source code is licensed under the license found in the LICENSE file in * the root directory of this source tree. */ -import DateRange from 'components/DateRange/DateRange.react'; +import DateRange from 'components/DateRange/DateRange.react'; import { Directions } from 'lib/Constants'; -import React from 'react'; +import React from 'react'; export const component = DateRange; @@ -23,7 +23,11 @@ class Demo extends React.Component { render() { return ( - + ); } } @@ -34,13 +38,13 @@ export const demos = [
- ) + ), }, { render: () => (
- ) - } + ), + }, ]; diff --git a/src/components/DateRange/DateRange.react.js b/src/components/DateRange/DateRange.react.js index 555b7eb6c8..c6439aa3bb 100644 --- a/src/components/DateRange/DateRange.react.js +++ b/src/components/DateRange/DateRange.react.js @@ -5,31 +5,27 @@ * This source code is licensed under the license found in the LICENSE file in * the root directory of this source tree. */ -import Calendar from 'components/Calendar/Calendar.react'; -import { Directions } from 'lib/Constants'; -import Icon from 'components/Icon/Icon.react'; -import { - monthDayStringUTC, - monthsFrom, - daysFrom -} from 'lib/DateUtils'; -import Popover from 'components/Popover/Popover.react'; -import Position from 'lib/Position'; -import PropTypes from 'lib/PropTypes'; -import React from 'react'; -import styles from 'components/DateRange/DateRange.scss'; +import Calendar from 'components/Calendar/Calendar.react'; +import { Directions } from 'lib/Constants'; +import Icon from 'components/Icon/Icon.react'; +import { monthDayStringUTC, monthsFrom, daysFrom } from 'lib/DateUtils'; +import Popover from 'components/Popover/Popover.react'; +import Position from 'lib/Position'; +import PropTypes from 'lib/PropTypes'; +import React from 'react'; +import styles from 'components/DateRange/DateRange.scss'; export default class DateRange extends React.Component { constructor(props) { super(); - let val = props.value || {}; + const val = props.value || {}; this.state = { open: false, position: null, start: val.start || monthsFrom(new Date(), -1), - end: val.end || new Date() + end: val.end || new Date(), }; this.wrapRef = React.createRef(); @@ -40,13 +36,13 @@ export default class DateRange extends React.Component { if (this.state.open) { return { open: false }; } - let pos = Position.inWindow(this.wrapRef.current); + const pos = Position.inWindow(this.wrapRef.current); if (this.props.align === Directions.RIGHT) { pos.x += this.wrapRef.current.clientWidth; } return { open: true, - position: pos + position: pos, }; }); } @@ -69,45 +65,48 @@ export default class DateRange extends React.Component { close() { this.setState({ - open: false + open: false, }); this.props.onChange({ start: this.state.start, end: this.state.end }); } rangeString() { - return ( - `${monthDayStringUTC(this.state.start)} - ${monthDayStringUTC(this.state.end)}` - ); + return `${monthDayStringUTC(this.state.start)} - ${monthDayStringUTC(this.state.end)}`; } render() { let popover = null; let content = null; if (this.state.open) { - let classes = [styles.open]; + const classes = [styles.open]; if (this.props.align === Directions.RIGHT) { classes.push(styles.right); } - let renderShade = ( + const renderShade = this.state.start.getFullYear() < this.state.end.getFullYear() || - this.state.start.getMonth() !== this.state.end.getMonth() - ); + this.state.start.getMonth() !== this.state.end.getMonth(); popover = ( - +
this.setStart(start)} - shadeAfter={renderShade} /> + onChange={start => this.setStart(start)} + shadeAfter={renderShade} + /> this.setEnd(end)} - shadeBefore={renderShade} /> + onChange={end => this.setEnd(end)} + shadeBefore={renderShade} + />
{this.rangeString()} - +
@@ -116,7 +115,7 @@ export default class DateRange extends React.Component { content = (
{this.rangeString()} - +
); } diff --git a/src/components/DateTimeEditor/DateTimeEditor.react.js b/src/components/DateTimeEditor/DateTimeEditor.react.js index f2af5dc61c..3799d845e0 100644 --- a/src/components/DateTimeEditor/DateTimeEditor.react.js +++ b/src/components/DateTimeEditor/DateTimeEditor.react.js @@ -6,9 +6,9 @@ * the root directory of this source tree. */ import DateTimePicker from 'components/DateTimePicker/DateTimePicker.react'; -import hasAncestor from 'lib/hasAncestor'; -import React from 'react'; -import styles from 'components/DateTimeEditor/DateTimeEditor.scss'; +import hasAncestor from 'lib/hasAncestor'; +import React from 'react'; +import styles from 'components/DateTimeEditor/DateTimeEditor.scss'; export default class DateTimeEditor extends React.Component { constructor(props) { @@ -18,7 +18,7 @@ export default class DateTimeEditor extends React.Component { open: false, position: null, value: props.value, - text: props.value.toISOString() + text: props.value.toISOString(), }; this.checkExternalClick = this.checkExternalClick.bind(this); @@ -55,7 +55,7 @@ export default class DateTimeEditor extends React.Component { } toggle() { - this.setState((state) => ({ open: !state.open })); + this.setState(state => ({ open: !state.open })); } inputDate(e) { @@ -66,22 +66,27 @@ export default class DateTimeEditor extends React.Component { if (this.state.text === this.props.value.toISOString()) { return; } - let date = new Date(this.state.text); + const date = new Date(this.state.text); if (isNaN(date.getTime())) { - this.setState({ value: this.props.value, text: this.props.value.toISOString() }); + this.setState({ + value: this.props.value, + text: this.props.value.toISOString(), + }); } else { if (this.state.text.endsWith('Z')) { this.setState({ value: date }); } else { - let utc = new Date(Date.UTC( - date.getFullYear(), - date.getMonth(), - date.getDate(), - date.getHours(), - date.getMinutes(), - date.getSeconds(), - date.getMilliseconds() - )); + const utc = new Date( + Date.UTC( + date.getFullYear(), + date.getMonth(), + date.getDate(), + date.getHours(), + date.getMinutes(), + date.getSeconds(), + date.getMilliseconds() + ) + ); this.setState({ value: utc }); } } @@ -95,8 +100,11 @@ export default class DateTimeEditor extends React.Component { this.setState({ value: value, text: value.toISOString() })} - close={() => this.setState({ open: false }, () => this.props.onCommit(this.state.value))} /> + onChange={value => this.setState({ value: value, text: value.toISOString() })} + close={() => + this.setState({ open: false }, () => this.props.onCommit(this.state.value)) + } + />
); } @@ -105,13 +113,14 @@ export default class DateTimeEditor extends React.Component {
e.target.select()} onClick={this.toggle.bind(this)} onChange={this.inputDate.bind(this)} - onBlur={this.commitDate.bind(this)} /> + onBlur={this.commitDate.bind(this)} + /> {popover}
); diff --git a/src/components/DateTimeEntry/DateTimeEntry.react.js b/src/components/DateTimeEntry/DateTimeEntry.react.js index 2da49d5f62..ee49f84e92 100644 --- a/src/components/DateTimeEntry/DateTimeEntry.react.js +++ b/src/components/DateTimeEntry/DateTimeEntry.react.js @@ -6,9 +6,9 @@ * the root directory of this source tree. */ import DateTimePicker from 'components/DateTimePicker/DateTimePicker.react'; -import Popover from 'components/Popover/Popover.react'; -import Position from 'lib/Position'; -import React from 'react'; +import Popover from 'components/Popover/Popover.react'; +import Position from 'lib/Position'; +import React from 'react'; export default class DateTimeEntry extends React.Component { constructor(props) { @@ -17,8 +17,8 @@ export default class DateTimeEntry extends React.Component { this.state = { open: false, position: null, - value: props.value.toISOString ? props.value.toISOString() : props.value - } + value: props.value.toISOString ? props.value.toISOString() : props.value, + }; this.rootRef = React.createRef(); this.inputRef = React.createRef(); @@ -26,7 +26,7 @@ export default class DateTimeEntry extends React.Component { componentWillReceiveProps(props) { this.setState({ - value: props.value.toISOString ? props.value.toISOString() : props.value + value: props.value.toISOString ? props.value.toISOString() : props.value, }); } @@ -39,23 +39,23 @@ export default class DateTimeEntry extends React.Component { } open() { - let node = this.rootRef.current; - let pos = Position.inDocument(node); + const node = this.rootRef.current; + const pos = Position.inDocument(node); pos.y += node.clientHeight; - let height = 230 + node.clientWidth * 0.14; + const height = 230 + node.clientWidth * 0.14; if (window.innerHeight - pos.y - height < 40) { pos.y = window.innerHeight - height - 40; } this.setState({ open: true, - position: pos + position: pos, }); } close() { this.setState({ - open: false + open: false, }); } @@ -67,19 +67,21 @@ export default class DateTimeEntry extends React.Component { if (this.state.value === this.props.value.toISOString()) { return; } - let date = new Date(this.state.value); + const date = new Date(this.state.value); if (isNaN(date.getTime())) { this.setState({ value: this.props.value.toISOString() }); } else if (!this.state.value.toLowerCase().endsWith('z')) { - let utc = new Date(Date.UTC( - date.getFullYear(), - date.getMonth(), - date.getDate(), - date.getHours(), - date.getMinutes(), - date.getSeconds(), - date.getMilliseconds() - )); + const utc = new Date( + Date.UTC( + date.getFullYear(), + date.getMonth(), + date.getDate(), + date.getHours(), + date.getMinutes(), + date.getSeconds(), + date.getMilliseconds() + ) + ); this.props.onChange(utc); } else { this.props.onChange(date); @@ -94,24 +96,31 @@ export default class DateTimeEntry extends React.Component { let popover = null; if (this.state.open) { popover = ( - + this.setState({ open: false })} /> + close={() => this.setState({ open: false })} + /> ); } - + return (
+ ref={this.inputRef} + /> {popover}
); diff --git a/src/components/DateTimeInput/DateTimeInput.example.js b/src/components/DateTimeInput/DateTimeInput.example.js index cb7cf53e90..9c293a0b18 100644 --- a/src/components/DateTimeInput/DateTimeInput.example.js +++ b/src/components/DateTimeInput/DateTimeInput.example.js @@ -6,9 +6,9 @@ * the root directory of this source tree. */ import DateTimeInput from 'components/DateTimeInput/DateTimeInput.react'; -import Field from 'components/Field/Field.react'; -import Label from 'components/Label/Label.react'; -import React from 'react'; +import Field from 'components/Field/Field.react'; +import Label from 'components/Label/Label.react'; +import React from 'react'; export const component = DateTimeInput; @@ -24,7 +24,11 @@ class DateTimeInputDemo extends React.Component { render() { return ( - + ); } } @@ -34,18 +38,20 @@ export const demos = [ render: () => (
} - input={} /> + label={
- ) + ), }, { render: () => (
} - input={} /> + label={
- ) - } + ), + }, ]; diff --git a/src/components/DateTimeInput/DateTimeInput.react.js b/src/components/DateTimeInput/DateTimeInput.react.js index 5c66e9c177..cd9f7a82d0 100644 --- a/src/components/DateTimeInput/DateTimeInput.react.js +++ b/src/components/DateTimeInput/DateTimeInput.react.js @@ -5,12 +5,12 @@ * This source code is licensed under the license found in the LICENSE file in * the root directory of this source tree. */ -import DateTimePicker from 'components/DateTimePicker/DateTimePicker.react'; -import { MONTHS, getDateMethod } from 'lib/DateUtils'; -import Popover from 'components/Popover/Popover.react'; -import Position from 'lib/Position'; -import React from 'react'; -import styles from 'components/DateTimeInput/DateTimeInput.scss'; +import DateTimePicker from 'components/DateTimePicker/DateTimePicker.react'; +import { MONTHS, getDateMethod } from 'lib/DateUtils'; +import Popover from 'components/Popover/Popover.react'; +import Position from 'lib/Position'; +import React from 'react'; +import styles from 'components/DateTimeInput/DateTimeInput.scss'; export default class DateTimeInput extends React.Component { constructor() { @@ -19,7 +19,7 @@ export default class DateTimeInput extends React.Component { this.state = { open: false, position: null, - } + }; this.inputRef = React.createRef(); } @@ -29,9 +29,9 @@ export default class DateTimeInput extends React.Component { if (this.state.open) { return { open: false }; } - let node = this.inputRef.current; + const node = this.inputRef.current; let pos = Position.inDocument(node); - let height = 230 + node.clientWidth * 0.14; + const height = 230 + node.clientWidth * 0.14; if (this.props.fixed) { pos = Position.inWindow(node); if (window.innerHeight - pos.y - height < 40) { @@ -44,14 +44,14 @@ export default class DateTimeInput extends React.Component { } return { open: true, - position: pos + position: pos, }; }); } close() { this.setState({ - open: false + open: false, }); } @@ -59,13 +59,18 @@ export default class DateTimeInput extends React.Component { let popover = null; if (this.state.open) { popover = ( - + this.setState({ open: false })} /> + close={() => this.setState({ open: false })} + /> ); } @@ -76,18 +81,28 @@ export default class DateTimeInput extends React.Component { } else { content = (
- {MONTHS[this.props.value[getDateMethod(this.props.local, 'getMonth')]()].substr(0, 3) + ' ' + this.props.value[getDateMethod(this.props.local, 'getDate')]()} + + {MONTHS[this.props.value[getDateMethod(this.props.local, 'getMonth')]()].substr(0, 3) + + ' ' + + this.props.value[getDateMethod(this.props.local, 'getDate')]()} + at - {this.props.value[getDateMethod(this.props.local, 'getHours')]()}:{(this.props.value[getDateMethod(this.props.local, 'getMinutes')]() < 10 ? '0' : '') + this.props.value[getDateMethod(this.props.local, 'getMinutes')]()} + {this.props.value[getDateMethod(this.props.local, 'getHours')]()}: + {(this.props.value[getDateMethod(this.props.local, 'getMinutes')]() < 10 ? '0' : '') + + this.props.value[getDateMethod(this.props.local, 'getMinutes')]()} {!this.props.local ? UTC : null}
); } - + return ( -
+
{content} {popover}
diff --git a/src/components/DateTimePicker/DateTimePicker.react.js b/src/components/DateTimePicker/DateTimePicker.react.js index a4fdabe5b2..2c8f582df5 100644 --- a/src/components/DateTimePicker/DateTimePicker.react.js +++ b/src/components/DateTimePicker/DateTimePicker.react.js @@ -5,33 +5,37 @@ * This source code is licensed under the license found in the LICENSE file in * the root directory of this source tree. */ -import Button from 'components/Button/Button.react'; -import Calendar from 'components/Calendar/Calendar.react'; +import Button from 'components/Button/Button.react'; +import Calendar from 'components/Calendar/Calendar.react'; import { hoursFrom, getDateMethod } from 'lib/DateUtils'; -import PropTypes from 'lib/PropTypes'; -import React from 'react'; -import styles from 'components/DateTimePicker/DateTimePicker.scss'; +import PropTypes from 'lib/PropTypes'; +import React from 'react'; +import styles from 'components/DateTimePicker/DateTimePicker.scss'; export default class DateTimePicker extends React.Component { constructor(props) { super(); - let timeRef = props.value || hoursFrom(new Date(), 1); + const timeRef = props.value || hoursFrom(new Date(), 1); this.state = { hours: String(timeRef[getDateMethod(props.local, 'getHours')]()), - minutes: (timeRef[getDateMethod(props.local, 'getMinutes')]() < 10 ? '0' : '') + String(timeRef[getDateMethod(props.local, 'getMinutes')]()), - } + minutes: + (timeRef[getDateMethod(props.local, 'getMinutes')]() < 10 ? '0' : '') + + String(timeRef[getDateMethod(props.local, 'getMinutes')]()), + }; } componentWillReceiveProps(props) { - let timeRef = props.value || hoursFrom(new Date(), 1); + const timeRef = props.value || hoursFrom(new Date(), 1); this.setState({ hours: String(timeRef[getDateMethod(props.local, 'getHours')]()), - minutes: (timeRef[getDateMethod(props.local, 'getMinutes')]() < 10 ? '0' : '') + String(timeRef[getDateMethod(props.local, 'getMinutes')]()), + minutes: + (timeRef[getDateMethod(props.local, 'getMinutes')]() < 10 ? '0' : '') + + String(timeRef[getDateMethod(props.local, 'getMinutes')]()), }); } changeHours(e) { - let hoursString = e.target.value; + const hoursString = e.target.value; if (hoursString === '') { return this.setState({ hours: '' }); } @@ -49,7 +53,7 @@ export default class DateTimePicker extends React.Component { } changeMinutes(e) { - let minutesString = e.target.value; + const minutesString = e.target.value; if (minutesString === '') { return this.setState({ minutes: '' }); } @@ -67,21 +71,24 @@ export default class DateTimePicker extends React.Component { } commitTime() { - let dateRef = this.props.value || new Date(); - let newDate = this.props.local ? new Date( - dateRef.getFullYear(), - dateRef.getMonth(), - dateRef.getDate(), - parseInt(this.state.hours, 10), - parseInt(this.state.minutes, 10) - ) : - new Date(Date.UTC( - dateRef.getUTCFullYear(), - dateRef.getUTCMonth(), - dateRef.getUTCDate(), - parseInt(this.state.hours, 10), - parseInt(this.state.minutes, 10) - )); + const dateRef = this.props.value || new Date(); + const newDate = this.props.local + ? new Date( + dateRef.getFullYear(), + dateRef.getMonth(), + dateRef.getDate(), + parseInt(this.state.hours, 10), + parseInt(this.state.minutes, 10) + ) + : new Date( + Date.UTC( + dateRef.getUTCFullYear(), + dateRef.getUTCMonth(), + dateRef.getUTCDate(), + parseInt(this.state.hours, 10), + parseInt(this.state.minutes, 10) + ) + ); this.props.onChange(newDate); if (this.props.close) { this.props.close(); @@ -90,32 +97,47 @@ export default class DateTimePicker extends React.Component { render() { return ( -
e.stopPropagation()} > - { - let timeRef = this.props.value || hoursFrom(new Date(), 1); - let newDate = this.props.local ? new Date( - newValue.getFullYear(), - newValue.getMonth(), - newValue.getDate(), - timeRef.getHours(), - timeRef.getMinutes() - ) : - new Date(Date.UTC( - newValue.getUTCFullYear(), - newValue.getUTCMonth(), - newValue.getUTCDate(), - timeRef.getUTCHours(), - timeRef.getUTCMinutes() - )); - this.props.onChange(newDate); - }} /> +
e.stopPropagation()} + > + { + const timeRef = this.props.value || hoursFrom(new Date(), 1); + const newDate = this.props.local + ? new Date( + newValue.getFullYear(), + newValue.getMonth(), + newValue.getDate(), + timeRef.getHours(), + timeRef.getMinutes() + ) + : new Date( + Date.UTC( + newValue.getUTCFullYear(), + newValue.getUTCMonth(), + newValue.getUTCDate(), + timeRef.getUTCHours(), + timeRef.getUTCMinutes() + ) + ); + this.props.onChange(newDate); + }} + />
-
- +
+ : - +
-
); @@ -123,19 +145,9 @@ export default class DateTimePicker extends React.Component { } DateTimePicker.propTypes = { - value: PropTypes.instanceOf(Date).describe( - 'The current date of the picker.' - ), - width: PropTypes.number.isRequired.describe( - 'The width of the calendar.' - ), - onChange: PropTypes.func.isRequired.describe( - 'A function to call when a new date is selected.' - ), - close: PropTypes.func.describe( - 'An optional function to call to close the calendar.' - ), - local: PropTypes.bool.describe( - 'An option flag to set when using a local DateTimeInput.' - ), + value: PropTypes.instanceOf(Date).describe('The current date of the picker.'), + width: PropTypes.number.isRequired.describe('The width of the calendar.'), + onChange: PropTypes.func.isRequired.describe('A function to call when a new date is selected.'), + close: PropTypes.func.describe('An optional function to call to close the calendar.'), + local: PropTypes.bool.describe('An option flag to set when using a local DateTimeInput.'), }; diff --git a/src/components/DonutChart/DonutChart.example.js b/src/components/DonutChart/DonutChart.example.js index 3f2e2ea9ea..22b72e4aae 100644 --- a/src/components/DonutChart/DonutChart.example.js +++ b/src/components/DonutChart/DonutChart.example.js @@ -6,31 +6,28 @@ * the root directory of this source tree. */ import DonutChart from 'components/DonutChart/DonutChart.react'; -import React from 'react'; +import React from 'react'; export const component = DonutChart; export const demos = [ { name: 'Simple DonutChart', - render: () => ( - - ) - }, { + render: () => , + }, + { name: 'DonutChart without Dominant Value', - render: () => ( - - ) - }, { - name: 'Progress Bar with DonutChart', + render: () => , + }, + { + name: 'Progress Bar with DonutChart', render: () => ( - ) - } + label="20/120GB" + /> + ), + }, ]; diff --git a/src/components/DonutChart/DonutChart.react.js b/src/components/DonutChart/DonutChart.react.js index a55c39d597..c7f1dfd9af 100644 --- a/src/components/DonutChart/DonutChart.react.js +++ b/src/components/DonutChart/DonutChart.react.js @@ -6,26 +6,23 @@ * the root directory of this source tree. */ import PropTypes from 'lib/PropTypes'; -import React from 'react'; -import styles from 'components/DonutChart/DonutChart.scss'; +import React from 'react'; +import styles from 'components/DonutChart/DonutChart.scss'; -const CHART_COLORS = [ - '#b9e88b', - '#fac786', - '#80eeef', - '#dfb3eb', - '#fd9fb0' -]; +const CHART_COLORS = ['#b9e88b', '#fac786', '#80eeef', '#dfb3eb', '#fd9fb0']; -const MONOCHROME_COLORS = [ - '#3b2c48', - '#e0e0ea' -]; +const MONOCHROME_COLORS = ['#3b2c48', '#e0e0ea']; -let DonutChart = ({ segments=[], diameter=200, label='', isMonochrome=false, printPercentage=false }) => { - let centerX = diameter / 2; - let centerY = centerX; - let radius = centerX * 0.9; +const DonutChart = ({ + segments = [], + diameter = 200, + label = '', + isMonochrome = false, + printPercentage = false, +}) => { + const centerX = diameter / 2; + const centerY = centerX; + const radius = centerX * 0.9; let lastX = centerX; let lastY = centerY - radius; @@ -36,18 +33,14 @@ let DonutChart = ({ segments=[], diameter=200, label='', isMonochrome=false, pri sum += segments[i]; } - let paths = []; + const paths = []; for (let i = 0; i < segments.length; ++i) { - let arc = segments[i] / sum * 2 * Math.PI; + const arc = (segments[i] / sum) * 2 * Math.PI; let angle = alpha - Math.min(arc, Math.PI); let endX = radius * Math.cos(angle) + centerX; let endY = -radius * Math.sin(angle) + centerY; - let path = [ - 'M', centerY, centerY, - 'L', lastX, lastY, - 'A', radius, radius, 0, 0, 1, endX, endY - ]; + let path = ['M', centerY, centerY, 'L', lastX, lastY, 'A', radius, radius, 0, 0, 1, endX, endY]; if (arc > Math.PI) { angle = alpha - arc; endX = radius * Math.cos(angle) + centerX; @@ -60,8 +53,12 @@ let DonutChart = ({ segments=[], diameter=200, label='', isMonochrome=false, pri + style={{ + fill: isMonochrome ? MONOCHROME_COLORS[i % 2] : CHART_COLORS[i], + transformOrigin: `${centerY}px ${centerX}px 0px`, + }} + key={`segment${i}`} + /> ); lastX = endX; @@ -74,11 +71,19 @@ let DonutChart = ({ segments=[], diameter=200, label='', isMonochrome=false, pri {paths} {segments.map((segment, i) => ( - - {printPercentage ? (segment / sum * 100).toFixed(2) + '%' : segment} + + {printPercentage ? ((segment / sum) * 100).toFixed(2) + '%' : segment} ))} - {label} + + {label} + ); }; @@ -86,19 +91,15 @@ let DonutChart = ({ segments=[], diameter=200, label='', isMonochrome=false, pri export default DonutChart; DonutChart.propTypes = { - 'segments': PropTypes.arrayOf(PropTypes.number).isRequired.describe( - 'Values of the DonutChart.' - ), - 'diameter': PropTypes.number.describe( - 'Width and height of the DonutChart.' - ), - 'label': PropTypes.string.describe( + segments: PropTypes.arrayOf(PropTypes.number).isRequired.describe('Values of the DonutChart.'), + diameter: PropTypes.number.describe('Width and height of the DonutChart.'), + label: PropTypes.string.describe( 'Additional string to be appended after each rendered value in DonutChart.' ), - 'isMonochrome': PropTypes.bool.describe( + isMonochrome: PropTypes.bool.describe( 'Whether the DonutChart is monochrome/bicolor (usually used for progress bar).' ), - 'printPercentage': PropTypes.bool.describe( + printPercentage: PropTypes.bool.describe( 'Whether the DonutChart should render percentage of each segment instead of the actual value.' - ) + ), }; diff --git a/src/components/DragHandle/DragHandle.example.js b/src/components/DragHandle/DragHandle.example.js index cb0571f2b6..4fbf2fa1a7 100644 --- a/src/components/DragHandle/DragHandle.example.js +++ b/src/components/DragHandle/DragHandle.example.js @@ -5,11 +5,11 @@ * This source code is licensed under the license found in the LICENSE file in * the root directory of this source tree. */ -import DataBrowserHeader from 'components/DataBrowserHeader/DataBrowserHeader.react'; -import DragHandle from 'components/DragHandle/DragHandle.react'; -import HTML5Backend from 'react-dnd-html5-backend'; -import React from 'react'; -import { DndProvider } from 'react-dnd' +import DataBrowserHeader from 'components/DataBrowserHeader/DataBrowserHeader.react'; +import DragHandle from 'components/DragHandle/DragHandle.react'; +import HTML5Backend from 'react-dnd-html5-backend'; +import React from 'react'; +import { DndProvider } from 'react-dnd'; export const component = DragHandle; @@ -22,14 +22,14 @@ class DragDemo extends React.Component { handleDrag(dx, dy) { this.setState(({ x, y }) => { - let newX = Math.max(0, Math.min(x + dx, 480)); - let newY = Math.max(0, Math.min(y + dy, 480)); + const newX = Math.max(0, Math.min(x + dx, 480)); + const newY = Math.max(0, Math.min(y + dy, 480)); return { x: newX, y: newY }; }); } render() { - let style = { + const style = { width: 20, height: 20, background: '#5298fc', @@ -37,31 +37,33 @@ class DragDemo extends React.Component { cursor: 'move', position: 'absolute', left: this.state.x, - top: this.state.y + top: this.state.y, }; return ( -
+
); } } -let lightBg = { background: 'rgba(224,224,234,0.10)' }; -let handleStyle = { +const lightBg = { background: 'rgba(224,224,234,0.10)' }; +const handleStyle = { position: 'relative', display: 'inline-block', width: 4, height: 30, marginLeft: -2, marginRight: -2, - cursor: 'ew-resize' + cursor: 'ew-resize', }; class HeadersDemo extends React.Component { @@ -69,14 +71,7 @@ class HeadersDemo extends React.Component { super(); this.state = { - widths: [ - 140, - 140, - 140, - 140, - 140, - 140 - ] + widths: [140, 140, 140, 140, 140, 140], }; } @@ -92,27 +87,31 @@ class HeadersDemo extends React.Component {
- +
- +
- +
- +
- +
- +
@@ -124,13 +123,10 @@ class HeadersDemo extends React.Component { export const demos = [ { name: 'Drag the ball', - render: () => ( - - ) - }, { + render: () => , + }, + { name: 'Data Browser Headers', - render: () => ( - - ) - } + render: () => , + }, ]; diff --git a/src/components/DragHandle/DragHandle.react.js b/src/components/DragHandle/DragHandle.react.js index 20304789a6..54741c0bc2 100644 --- a/src/components/DragHandle/DragHandle.react.js +++ b/src/components/DragHandle/DragHandle.react.js @@ -6,7 +6,7 @@ * the root directory of this source tree. */ import PropTypes from 'lib/PropTypes'; -import React from 'react'; +import React from 'react'; export default class DragHandle extends React.Component { constructor() { @@ -22,7 +22,7 @@ export default class DragHandle extends React.Component { this.y = 0; this.state = { - dragging: false + dragging: false, }; } @@ -77,5 +77,5 @@ export default class DragHandle extends React.Component { DragHandle.propTypes = { onDrag: PropTypes.func.isRequired.describe( 'A function called when the handle is dragged. It takes deltas for X and Y as the two parameters.' - ) -} + ), +}; diff --git a/src/components/Dropdown/Dropdown.example.js b/src/components/Dropdown/Dropdown.example.js index 15a76dfff0..ed091cc28c 100644 --- a/src/components/Dropdown/Dropdown.example.js +++ b/src/components/Dropdown/Dropdown.example.js @@ -5,11 +5,11 @@ * This source code is licensed under the license found in the LICENSE file in * the root directory of this source tree. */ -import Dropdown from 'components/Dropdown/Dropdown.react'; -import Field from 'components/Field/Field.react'; -import Label from 'components/Label/Label.react'; +import Dropdown from 'components/Dropdown/Dropdown.react'; +import Field from 'components/Field/Field.react'; +import Label from 'components/Label/Label.react'; import Option from 'components/Dropdown/Option.react'; -import React from 'react'; +import React from 'react'; export const component = Dropdown; @@ -26,14 +26,14 @@ class DropdownDemo extends React.Component { render() { return ( - - - - - - - - + + + + + + + + ); } @@ -42,14 +42,10 @@ class DropdownDemo extends React.Component { export const demos = [ { render: () => ( -
- } - input={} /> - } - input={} /> +
+ } input={} /> + } input={} />
- ) - } + ), + }, ]; diff --git a/src/components/Dropdown/Dropdown.react.js b/src/components/Dropdown/Dropdown.react.js index 3f444767f9..471cf1d31e 100644 --- a/src/components/Dropdown/Dropdown.react.js +++ b/src/components/Dropdown/Dropdown.react.js @@ -6,20 +6,20 @@ * the root directory of this source tree. */ import { Directions } from 'lib/Constants'; -import Popover from 'components/Popover/Popover.react'; -import Position from 'lib/Position'; -import PropTypes from 'lib/PropTypes'; -import React from 'react'; -import SliderWrap from 'components/SliderWrap/SliderWrap.react'; -import styles from 'components/Dropdown/Dropdown.scss'; +import Popover from 'components/Popover/Popover.react'; +import Position from 'lib/Position'; +import PropTypes from 'lib/PropTypes'; +import React from 'react'; +import SliderWrap from 'components/SliderWrap/SliderWrap.react'; +import styles from 'components/Dropdown/Dropdown.scss'; export default class Dropdown extends React.Component { constructor() { super(); this.state = { open: false, - position: null - } + position: null, + }; this.dropdownRef = React.createRef(); } @@ -35,14 +35,14 @@ export default class Dropdown extends React.Component { } return { open: true, - position: pos + position: pos, }; }); } close() { this.setState({ - open: false + open: false, }); } @@ -50,30 +50,40 @@ export default class Dropdown extends React.Component { if (value === this.props.value) { return this.setState({ open: false }); } - this.setState({ - open: false - }, () => { - this.props.onChange(value); - }); + this.setState( + { + open: false, + }, + () => { + this.props.onChange(value); + } + ); } render() { let popover = null; if (this.state.open && !this.props.disabled) { - let width = this.dropdownRef.current.clientWidth; - let popoverChildren = ( + const width = this.dropdownRef.current.clientWidth; + const popoverChildren = (
{React.Children.map(this.props.children, c => ( - + ))}
); - popover = - + popover = ( + {popoverChildren} - ; + + ); } let content = null; React.Children.forEach(this.props.children, c => { @@ -82,26 +92,25 @@ export default class Dropdown extends React.Component { } }); if (!content) { - content = ( -
- {this.props.placeHolder} -
- ); + content =
{this.props.placeHolder}
; } let dropdownStyle = {}; if (this.props.width) { dropdownStyle = { width: this.props.width, - float: 'left' + float: 'left', }; } - let dropdownClasses = [styles.dropdown]; + const dropdownClasses = [styles.dropdown]; if (this.props.disabled) { dropdownClasses.push(styles.disabled); } return (
-
+
{content}
{popover} @@ -114,9 +123,7 @@ Dropdown.propTypes = { onChange: PropTypes.func.isRequired.describe( 'A function called when the dropdown is changed. It receives the new value as the only parameter.' ), - value: PropTypes.string.describe( - 'The currently-selected value of this controlled input.' - ), + value: PropTypes.string.describe('The currently-selected value of this controlled input.'), disabled: PropTypes.bool.describe('Set to true to disable the dropdown.'), children: PropTypes.node.isRequired.describe( 'The children of Dropdown should only be
; +const Option = props =>
; export default Option; diff --git a/src/components/EmptyState/EmptyState.example.js b/src/components/EmptyState/EmptyState.example.js index 2c16c46331..8b1e014e6f 100644 --- a/src/components/EmptyState/EmptyState.example.js +++ b/src/components/EmptyState/EmptyState.example.js @@ -14,27 +14,29 @@ export const demos = [ { name: 'Example with Action callback', render: () => ( -
+
alert('CTA was clicked')}> + action={() => alert('CTA was clicked')} + >
- ) + ), }, { name: 'Example with Action href', render: () => ( -
+
+ action={'someLink'} + >
- ) - } -] + ), + }, +]; diff --git a/src/components/EmptyState/EmptyState.react.js b/src/components/EmptyState/EmptyState.react.js index 22be2eb529..4e27072db7 100644 --- a/src/components/EmptyState/EmptyState.react.js +++ b/src/components/EmptyState/EmptyState.react.js @@ -5,52 +5,46 @@ * This source code is licensed under the license found in the LICENSE file in * the root directory of this source tree. */ -import Button from 'components/Button/Button.react'; -import Icon from 'components/Icon/Icon.react'; -import PropTypes from 'lib/PropTypes'; -import React from 'react'; -import styles from 'components/EmptyState/EmptyState.scss'; +import Button from 'components/Button/Button.react'; +import Icon from 'components/Icon/Icon.react'; +import PropTypes from 'lib/PropTypes'; +import React from 'react'; +import styles from 'components/EmptyState/EmptyState.scss'; import stylesButton from 'components/Button/Button.scss'; -import baseStyles from 'stylesheets/base.scss'; +import baseStyles from 'stylesheets/base.scss'; -let ctaButton = (cta, action) => { +const ctaButton = (cta, action) => { if (cta) { if (action.constructor === String) { return ( - + {cta} ); } else { - return ( -
@@ -295,20 +334,23 @@ export default class ExplorerQueryComposer extends React.Component { { - let aggregates = this.state.aggregates; + onChange={val => { + const aggregates = this.state.aggregates; aggregates[index] = { op: val, - col: FIELD_LABELS[this.state.source][0] + col: FIELD_LABELS[this.state.source][0], }; this.setState({ aggregates }); }} - color='blue' - width='100%' /> + color="blue" + width="100%" + />
-
of
+
+ of +
{ @@ -321,13 +363,14 @@ export default class ExplorerQueryComposer extends React.Component { return true; } })} - onChange={(val) => { - let aggregates = this.state.aggregates; + onChange={val => { + const aggregates = this.state.aggregates; aggregates[index].col = val; this.setState({ aggregates }); }} - color='blue' - width='100%' /> + color="blue" + width="100%" + /> {deleteButton}
@@ -335,16 +378,17 @@ export default class ExplorerQueryComposer extends React.Component { ); } - renderGroup(grouping, index=0) { + renderGroup(grouping, index = 0) { let deleteButton = null; - let specialGroup = this.props.isTimeSeries && index === 0; + const specialGroup = this.props.isTimeSeries && index === 0; if (!specialGroup) { deleteButton = (
@@ -357,25 +401,28 @@ export default class ExplorerQueryComposer extends React.Component { { - let groups = this.state.groups; + onChange={val => { + const groups = this.state.groups; groups[index] = val; this.setState({ groups }); }} - color='blue' - width='100%' /> + color="blue" + width="100%" + /> {deleteButton}
); } - renderFilter(filter, index=0) { - let type = Object.prototype.hasOwnProperty.call(Constraints[filter.op], 'field') ? Constraints[filter.op].field : FIELD_TYPE[filter.col]; + renderFilter(filter, index = 0) { + const type = Object.prototype.hasOwnProperty.call(Constraints[filter.op], 'field') + ? Constraints[filter.op].field + : FIELD_TYPE[filter.col]; let constraintView = null; if (type === 'JSON') { - let isJSONView = filter.op === 'json_extract_scalar'; + const isJSONView = filter.op === 'json_extract_scalar'; let jsonView = null; if (isJSONView) { @@ -384,64 +431,67 @@ export default class ExplorerQueryComposer extends React.Component { jsonView = (
Constraints[c].name)} - onChange={(val) => { - let filters = this.state.filters; + options={FieldConstraints['JSONValue'].map(c => Constraints[c].name)} + onChange={val => { + const filters = this.state.filters; filters[index] = { col: filter.col, op: filter.op, json_path: filter.json_path, json_scalar_op: constraintLookup[val], - val: filter.val + val: filter.val, }; this.setState({ filters }); - }} /> + }} + /> { - let filters = this.state.filters; + onChange={e => { + const filters = this.state.filters; filters[index] = { col: filter.col, op: filter.op, json_path: filter.json_path, json_scalar_op: filter.json_scalar_op, - val: e.target.value + val: e.target.value, }; this.setState({ filters }); - }} /> + }} + />
); } - let constraintInputValue = isJSONView ? filter.json_path : filter.val; + const constraintInputValue = isJSONView ? filter.json_path : filter.val; constraintView = (
Constraints[c].name)} - onChange={(val) => { - let filters = this.state.filters; + options={availableFilters[filter.col].map(c => Constraints[c].name)} + onChange={val => { + const filters = this.state.filters; filters[index] = { col: filter.col, op: constraintLookup[val], - val: filter.val + val: filter.val, }; this.setState({ filters }); - }} /> + }} + /> { - let filters = this.state.filters; + onChange={e => { + const filters = this.state.filters; let newFilter = null; if (isJSONView) { newFilter = { @@ -449,19 +499,20 @@ export default class ExplorerQueryComposer extends React.Component { op: filter.op, val: filter.val, json_path: e.target.value, - json_scalar_op: filter.json_scalar_op + json_scalar_op: filter.json_scalar_op, }; } else { newFilter = { col: filter.col, op: filter.op, - val: e.target.value - } + val: e.target.value, + }; } filters[index] = newFilter; this.setState({ filters }); }} - ref={setFocus} /> + ref={setFocus} + />
{jsonView} @@ -472,25 +523,26 @@ export default class ExplorerQueryComposer extends React.Component { Constraints[c].name)} - onChange={(val) => { - let filters = this.state.filters; + options={availableFilters[filter.col].map(c => Constraints[c].name)} + onChange={val => { + const filters = this.state.filters; filters[index] = { col: filter.col, op: constraintLookup[val], - val: null + val: null, }; this.setState({ filters }); - }} /> + }} + /> - {fieldView(type, filter.val, (val) => { - let filters = this.state.filters; + {fieldView(type, filter.val, val => { + const filters = this.state.filters; filters[index] = { col: filter.col, op: filter.op, - val: val + val: val, }; this.setState({ filters }); })} @@ -503,28 +555,30 @@ export default class ExplorerQueryComposer extends React.Component {
Filter
{ - let filters = this.state.filters; + onChange={val => { + const filters = this.state.filters; filters[index] = { col: val, op: '$eq', - val: null + val: null, }; this.setState({ filters }); - }} /> + }} + /> {constraintView}
@@ -538,38 +592,41 @@ export default class ExplorerQueryComposer extends React.Component {
Sort by
{ - let orders = this.state.orders; + onChange={val => { + const orders = this.state.orders; orders[index] = { col: val, - asc: ORDER_LABELS[0] + asc: ORDER_LABELS[0], }; this.setState({ orders }); }} - color='blue' - width='100%' /> + color="blue" + width="100%" + />
{ - let orders = this.state.orders; + onChange={val => { + const orders = this.state.orders; orders[index].asc = val; this.setState({ orders }); }} - color='blue' - width='100%' /> + color="blue" + width="100%" + />
@@ -579,7 +636,8 @@ export default class ExplorerQueryComposer extends React.Component { } render() { - let { query, isNew, isTimeSeries, onDismiss } = this.props; + let { query } = this.props; + const { isNew, isTimeSeries, onDismiss } = this.props; query = query || {}; // First and foremost, let's not waste time if the query itself is not composable. @@ -594,11 +652,12 @@ export default class ExplorerQueryComposer extends React.Component {
@@ -611,24 +670,27 @@ export default class ExplorerQueryComposer extends React.Component { headerView = (
+ placeholder={'Give your query a name'} + />
@@ -637,14 +699,15 @@ export default class ExplorerQueryComposer extends React.Component { } else { headerView = (
-

{ this.state.name || 'Build a custom query' }

- { isNew ? null : ( +

{this.state.name || 'Build a custom query'}

+ {isNew ? null : (
)} @@ -665,9 +728,7 @@ export default class ExplorerQueryComposer extends React.Component { ); group = ( -
- {this.renderGroup(this.state.groups[0])} -
+
{this.renderGroup(this.state.groups[0])}
); } else { // On table/json view, we hide aggregate and group. And we also show limit. @@ -676,11 +737,12 @@ export default class ExplorerQueryComposer extends React.Component {
Limit
this.setState({ limit: event.nativeEvent.target.value })} /> + onChange={event => this.setState({ limit: event.nativeEvent.target.value })} + />
); @@ -692,22 +754,24 @@ export default class ExplorerQueryComposer extends React.Component { )); } - let offset = isTimeSeries ? 1 : 0; - let extraAggregateModels = isTimeSeries ? this.state.aggregates.slice(1) : this.state.aggregates; - let extraAggregates = extraAggregateModels.map((aggregate, i) => ( + const offset = isTimeSeries ? 1 : 0; + const extraAggregateModels = isTimeSeries + ? this.state.aggregates.slice(1) + : this.state.aggregates; + const extraAggregates = extraAggregateModels.map((aggregate, i) => (
{this.renderAggregate(aggregate, i + offset)}
)); - let extraGroupModels = isTimeSeries ? this.state.groups.slice(1) : this.state.groups; - let extraGroups = extraGroupModels.map((group, i) => ( + const extraGroupModels = isTimeSeries ? this.state.groups.slice(1) : this.state.groups; + const extraGroups = extraGroupModels.map((group, i) => (
{this.renderGroup(group, i + offset)}
)); - let filters = this.state.filters.map((filter, i) => ( + const filters = this.state.filters.map((filter, i) => (
{this.renderFilter(filter, i)}
@@ -718,10 +782,11 @@ export default class ExplorerQueryComposer extends React.Component { sortButton = (
); } else { footerButton = (
-
); @@ -762,9 +825,7 @@ export default class ExplorerQueryComposer extends React.Component { return (
-
- {headerView} -
+
{headerView}
@@ -773,8 +834,9 @@ export default class ExplorerQueryComposer extends React.Component { value={this.state.source} options={TABLE_SOURCES_LABEL} onChange={this.handleSourceChange.bind(this)} - color='blue' - width='100%' /> + color="blue" + width="100%" + />
@@ -790,54 +852,49 @@ export default class ExplorerQueryComposer extends React.Component {
-
- {footerButton} -
+
{footerButton}
); } } ExplorerQueryComposer.propTypes = { - query: PropTypes.object.describe( - 'The query to be edited by this composer.' - ), - onSave: PropTypes.func.isRequired.describe( - 'Function to be called on query created/saved.' - ), - onDismiss: PropTypes.func.describe( - 'Function to be called on dismiss button clicked.' - ), + query: PropTypes.object.describe('The query to be edited by this composer.'), + onSave: PropTypes.func.isRequired.describe('Function to be called on query created/saved.'), + onDismiss: PropTypes.func.describe('Function to be called on dismiss button clicked.'), isNew: PropTypes.bool.describe( 'True if the composer is trying to compose a new query. ' + - 'False if the composer is editing an existing one.' + 'False if the composer is editing an existing one.' ), isTimeSeries: PropTypes.bool.describe( 'If set to true, add default group (day, hour) and aggregate to the composer. ' + - 'Otherwise, render limit inside the composer.' - ) -} + 'Otherwise, render limit inside the composer.' + ), +}; diff --git a/src/components/ExplorerQueryPicker/ExplorerQueryPicker.example.js b/src/components/ExplorerQueryPicker/ExplorerQueryPicker.example.js index 22d2610fe9..a0063799c8 100644 --- a/src/components/ExplorerQueryPicker/ExplorerQueryPicker.example.js +++ b/src/components/ExplorerQueryPicker/ExplorerQueryPicker.example.js @@ -6,115 +6,126 @@ * the root directory of this source tree. */ import ExplorerQueryPicker from 'components/ExplorerQueryPicker/ExplorerQueryPicker.react'; -import React from 'react'; +import React from 'react'; export const component = ExplorerQueryPicker; export const demos = [ { render: () => { - let queries = [ + const queries = [ { name: 'Audience', children: [ { name: 'Daily Active Installations', - query: { }, - preset: true + query: {}, + preset: true, }, { name: 'Daily Active Users', - query: { }, - preset: true + query: {}, + preset: true, }, { name: 'Monthly Active Installations', - query: { }, - preset: true + query: {}, + preset: true, }, { name: 'Monthly Active Users', - query: { }, - preset: true - } - ] - }, { + query: {}, + preset: true, + }, + ], + }, + { name: 'Core', children: [ { name: 'Gogo Count', - query: { }, - preset: true + query: {}, + preset: true, }, { name: 'User Count', - query: { }, - preset: true + query: {}, + preset: true, }, { name: 'Installation Count', - query: { }, - preset: true - } - ] - }, { + query: {}, + preset: true, + }, + ], + }, + { name: 'Events', children: [ { name: 'API Requests', - query: { }, - preset: true + query: {}, + preset: true, }, { name: 'Analytics Requests', - query: { }, - preset: true + query: {}, + preset: true, }, { name: 'File Requests', - query: { }, - preset: true + query: {}, + preset: true, }, { name: 'Push Notifications', - query: { }, - preset: true + query: {}, + preset: true, }, { name: 'App Opens', - query: { }, - preset: true + query: {}, + preset: true, }, { name: 'Push Opens', - query: { }, - preset: true - } - ] - }, { + query: {}, + preset: true, + }, + ], + }, + { name: 'Recent Queries', children: [ { name: 'User Count Aggregate', - query: { } - } - ] - }, { + query: {}, + }, + ], + }, + { name: 'Saved Queries', children: [ { name: 'Gogo Queries', - query: { } + query: {}, }, { name: 'Saved Queries', - query: { } - } - ] - } + query: {}, + }, + ], + }, ]; - return {/* Do nothing */}} /> - } - } + return ( + { + /* Do nothing */ + }} + /> + ); + }, + }, ]; diff --git a/src/components/ExplorerQueryPicker/ExplorerQueryPicker.react.js b/src/components/ExplorerQueryPicker/ExplorerQueryPicker.react.js index 51a76b9ad6..24ac44ee47 100644 --- a/src/components/ExplorerQueryPicker/ExplorerQueryPicker.react.js +++ b/src/components/ExplorerQueryPicker/ExplorerQueryPicker.react.js @@ -5,40 +5,38 @@ * This source code is licensed under the license found in the LICENSE file in * the root directory of this source tree. */ -import baseStyles from 'stylesheets/base.scss'; -import Button from 'components/Button/Button.react'; +import baseStyles from 'stylesheets/base.scss'; +import Button from 'components/Button/Button.react'; import CascadingView from 'components/CascadingView/CascadingView.react'; -import PropTypes from 'lib/PropTypes'; -import React from 'react'; -import styles from 'components/ExplorerQueryPicker/ExplorerQueryPicker.scss'; +import PropTypes from 'lib/PropTypes'; +import React from 'react'; +import styles from 'components/ExplorerQueryPicker/ExplorerQueryPicker.scss'; -let ExplorerQueryPicker = ({ queries, onCompose, onSelect, onDelete }) => { +const ExplorerQueryPicker = ({ queries, onCompose, onSelect, onDelete }) => { return (

Choose a query to visualize

- {queries.map((queryGroup) => { + {queries.map(queryGroup => { let childrenView = null; if (queryGroup.children.length > 0) { childrenView = queryGroup.children.map((query, j) => { return ( -
+
- {query.preset ? null : } + {query.preset ? null : ( + + )}
); }); @@ -47,31 +45,23 @@ let ExplorerQueryPicker = ({ queries, onCompose, onSelect, onDelete }) => { if (!emptyMessage) { emptyMessage = `No query found in ${queryGroup.name}.`; } - childrenView = ( -
- {emptyMessage} -
- ); + childrenView =
{emptyMessage}
; } return ( + key={queryGroup.name} + > {childrenView} ); })}
-
-
@@ -83,16 +73,12 @@ export default ExplorerQueryPicker; ExplorerQueryPicker.propTypes = { queries: PropTypes.arrayOf(PropTypes.object).isRequired.describe( 'An array of queryGroups. Each querygroup should include the following fields: name, children. ' + - 'children of queryGroup contains an array of queries. Each query should include the following fields: ' + - 'name, query, (optional)preset.' + 'children of queryGroup contains an array of queries. Each query should include the following fields: ' + + 'name, query, (optional)preset.' ), onCompose: PropTypes.func.isRequired.describe( 'Function to be called when "Build a custom query" button is clicked.' ), - onSelect: PropTypes.func.describe( - 'Function to be called when a query is being selected.' - ), - onDelete: PropTypes.func.describe( - 'Function to be called when a query is being deleted.' - ) + onSelect: PropTypes.func.describe('Function to be called when a query is being selected.'), + onDelete: PropTypes.func.describe('Function to be called when a query is being deleted.'), }; diff --git a/src/components/Field/Field.example.js b/src/components/Field/Field.example.js index 8cb9068ca4..23de039927 100644 --- a/src/components/Field/Field.example.js +++ b/src/components/Field/Field.example.js @@ -5,10 +5,10 @@ * This source code is licensed under the license found in the LICENSE file in * the root directory of this source tree. */ -import React from 'react'; -import Field from 'components/Field/Field.react'; -import Label from 'components/Label/Label.react'; -import Toggle from 'components/Toggle/Toggle.react'; +import React from 'react'; +import Field from 'components/Field/Field.react'; +import Label from 'components/Label/Label.react'; +import Toggle from 'components/Toggle/Toggle.react'; import TextInput from 'components/TextInput/TextInput.react'; export const component = Field; @@ -17,17 +17,26 @@ export const demos = [ { render: () => ( } - input={ {}} />} /> - ) + label={ +
{label}
-
+
{input}
@@ -38,15 +38,13 @@ export default Field; Field.propTypes = { label: PropTypes.node.describe( 'The label content, placed on the left side of the Field. ' + - 'It can be any renderable content.' + 'It can be any renderable content.' ), input: PropTypes.node.describe( 'The input content, placed on the right side of the Field. ' + - 'It can be any renderable content.' - ), - className: PropTypes.string.describe( - 'A CSS class name to add to this field' + 'It can be any renderable content.' ), + className: PropTypes.string.describe('A CSS class name to add to this field'), labelWidth: PropTypes.number.describe( 'A percentage value for the width of the left label. It cannot be 0.' ), @@ -55,5 +53,5 @@ Field.propTypes = { ), height: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).describe( 'The height of the field. Can be a string containing any CSS unit, or a number of pixels. By default, it will expand to fit it\u2019s content, with a min-height of 80px.' - ) + ), }; diff --git a/src/components/Field/Field.scss b/src/components/Field/Field.scss index 116a490828..2978470710 100644 --- a/src/components/Field/Field.scss +++ b/src/components/Field/Field.scss @@ -14,6 +14,7 @@ border-width: 1px 1px 0 1px; min-height: 80px; background: white; + display: flex; &:last-of-type { border-bottom-width: 1px; @@ -25,16 +26,8 @@ } .left { - position: absolute; - left: 0; - height: 100%; -} - -.centered { - @include transform(translateY(-50%)); - position: absolute; - width: 100%; - top: 50%; + display: flex; + align-items: center; } .right { @@ -46,6 +39,7 @@ display: flex; justify-content: center; align-items: center; + flex: 1 } diff --git a/src/components/Fieldset/Fieldset.example.js b/src/components/Fieldset/Fieldset.example.js index 22521366e8..cb83ab0432 100644 --- a/src/components/Fieldset/Fieldset.example.js +++ b/src/components/Fieldset/Fieldset.example.js @@ -5,24 +5,23 @@ * This source code is licensed under the license found in the LICENSE file in * the root directory of this source tree. */ -import React from 'react'; -import Field from 'components/Field/Field.react'; +import React from 'react'; +import Field from 'components/Field/Field.react'; import Fieldset from 'components/Fieldset/Fieldset.react'; -import Label from 'components/Label/Label.react'; -import Toggle from 'components/Toggle/Toggle.react'; +import Label from 'components/Label/Label.react'; +import Toggle from 'components/Toggle/Toggle.react'; export const component = Fieldset; export const demos = [ { render: () => ( -
+
} - input={} /> + label={
- ) - } + ), + }, ]; diff --git a/src/components/Fieldset/Fieldset.react.js b/src/components/Fieldset/Fieldset.react.js index 62dee6d53d..bcab28ebaf 100644 --- a/src/components/Fieldset/Fieldset.react.js +++ b/src/components/Fieldset/Fieldset.react.js @@ -6,16 +6,14 @@ * the root directory of this source tree. */ import PropTypes from 'lib/PropTypes'; -import React from 'react'; -import styles from 'components/Fieldset/Fieldset.scss'; +import React from 'react'; +import styles from 'components/Fieldset/Fieldset.scss'; -let Fieldset = ({ legend, description, children }) => ( +const Fieldset = ({ legend, description, children }) => (
{legend}
{description}
-
- {children} -
+
{children}
); @@ -27,5 +25,5 @@ Fieldset.propTypes = { ), description: PropTypes.node.describe( 'The secondary header of the Fieldset. It can be any renderable content.' - ) + ), }; diff --git a/src/components/FileEditor/FileEditor.react.js b/src/components/FileEditor/FileEditor.react.js index abc87b654b..71c0af55fb 100644 --- a/src/components/FileEditor/FileEditor.react.js +++ b/src/components/FileEditor/FileEditor.react.js @@ -15,7 +15,7 @@ export default class FileEditor extends React.Component { super(); this.state = { - value: props.value + value: props.value, }; this.checkExternalClick = this.checkExternalClick.bind(this); @@ -28,7 +28,7 @@ export default class FileEditor extends React.Component { componentDidMount() { document.body.addEventListener('click', this.checkExternalClick); document.body.addEventListener('keypress', this.handleKey); - let fileInputElement = document.getElementById('fileInput'); + const fileInputElement = document.getElementById('fileInput'); if (fileInputElement) { fileInputElement.click(); } @@ -68,9 +68,9 @@ export default class FileEditor extends React.Component { } async handleChange(e) { - let file = e.target.files[0]; + const file = e.target.files[0]; if (file) { - let base64 = await this.getBase64(file); + const base64 = await this.getBase64(file); this.props.onCommit(new Parse.File(file.name, { base64 })); } } @@ -78,9 +78,18 @@ export default class FileEditor extends React.Component { render() { const file = this.props.value; return ( -
+ diff --git a/src/components/FileInput/FileInput.example.js b/src/components/FileInput/FileInput.example.js index ae85f567c4..d46e13ae35 100644 --- a/src/components/FileInput/FileInput.example.js +++ b/src/components/FileInput/FileInput.example.js @@ -5,10 +5,10 @@ * This source code is licensed under the license found in the LICENSE file in * the root directory of this source tree. */ -import React from 'react'; -import Field from 'components/Field/Field.react'; -import Fieldset from 'components/Fieldset/Fieldset.react'; -import Label from 'components/Label/Label.react'; +import React from 'react'; +import Field from 'components/Field/Field.react'; +import Fieldset from 'components/Fieldset/Fieldset.react'; +import Label from 'components/Label/Label.react'; import FileInput from 'components/FileInput/FileInput.react'; export const component = FileInput; @@ -18,18 +18,27 @@ export const demos = [ render: () => (
} - input={} /> + label={
- ) - } + ), + }, ]; diff --git a/src/components/FileInput/FileInput.react.js b/src/components/FileInput/FileInput.react.js index cff626fd95..a2ee18a830 100644 --- a/src/components/FileInput/FileInput.react.js +++ b/src/components/FileInput/FileInput.react.js @@ -5,13 +5,13 @@ * This source code is licensed under the license found in the LICENSE file in * the root directory of this source tree. */ -import React from 'react'; +import React from 'react'; import { escape } from 'lib/StringEscaping'; -import styles from 'components/FileInput/FileInput.scss'; +import styles from 'components/FileInput/FileInput.scss'; export default class FileInput extends React.Component { handleChange(e) { - let file = e.target.files[0]; + const file = e.target.files[0]; this.props.onChange(file); } @@ -27,10 +27,7 @@ export default class FileInput extends React.Component { } if (this.props.value.name && this.props.value.url) { return ( -
+ {escape(this.props.value.name)} ); @@ -38,7 +35,7 @@ export default class FileInput extends React.Component { } render() { - let inputProps = { + const inputProps = { type: 'file', value: '', disabled: this.props.disabled, @@ -47,13 +44,13 @@ export default class FileInput extends React.Component { if (this.props.accept) { inputProps.accept = this.props.accept; } - let label = this.renderLabel(); - let buttonStyles = [styles.button]; + const label = this.renderLabel(); + const buttonStyles = [styles.button]; if (this.props.disabled || this.props.uploading) { buttonStyles.push(styles.disabled); } if (label) { - buttonStyles.push(styles.withLabel) + buttonStyles.push(styles.withLabel); } return ( diff --git a/src/components/FileTree/FileTree.react.js b/src/components/FileTree/FileTree.react.js index 656a18fccf..fbfe0970c5 100644 --- a/src/components/FileTree/FileTree.react.js +++ b/src/components/FileTree/FileTree.react.js @@ -5,10 +5,10 @@ * This source code is licensed under the license found in the LICENSE file in * the root directory of this source tree. */ -import Icon from 'components/Icon/Icon.react'; +import Icon from 'components/Icon/Icon.react'; import { Link } from 'react-router-dom'; -import React from 'react'; -import styles from 'components/FileTree/FileTree.scss'; +import React from 'react'; +import styles from 'components/FileTree/FileTree.scss'; export default class FileTree extends React.Component { constructor(props) { @@ -16,7 +16,7 @@ export default class FileTree extends React.Component { let open = !props.name; if (props.current && props.name) { - let dirPath = (props.prefix || '') + props.name + '/'; + const dirPath = (props.prefix || '') + props.name + '/'; if (props.current.startsWith(dirPath)) { open = true; } @@ -31,8 +31,16 @@ export default class FileTree extends React.Component { let dir = null; if (this.props.name) { dir = ( -
this.setState((state) => ({ open: !state.open }))}> - +
this.setState(state => ({ open: !state.open }))} + > + {this.props.name}
); @@ -40,12 +48,12 @@ export default class FileTree extends React.Component { let content = null; if (this.state.open) { - let dirs = {}; - let files = []; - this.props.files.forEach((f) => { - let folderEnd = f.indexOf('/'); + const dirs = {}; + const files = []; + this.props.files.forEach(f => { + const folderEnd = f.indexOf('/'); if (folderEnd > -1) { - let folder = f.substr(0, folderEnd); + const folder = f.substr(0, folderEnd); if (!dirs[folder]) { dirs[folder] = []; } @@ -54,27 +62,29 @@ export default class FileTree extends React.Component { files.push(f); } }); - let folders = Object.keys(dirs); + const folders = Object.keys(dirs); folders.sort(); content = (
- {folders.map((f) => ( + {folders.map(f => ( + current={this.props.current} + /> ))} - {files.map((f) => { - let path = (this.props.name ? this.props.prefix + this.props.name + '/' : '') + f; - let isCurrent = this.props.current === path; + {files.map(f => { + const path = (this.props.name ? this.props.prefix + this.props.name + '/' : '') + f; + const isCurrent = this.props.current === path; return ( + to={{ pathname: this.props.linkPrefix + path }} + > {f} ); diff --git a/src/components/Filter/Filter.react.js b/src/components/Filter/Filter.react.js index d7bfd75e27..1d12def83b 100644 --- a/src/components/Filter/Filter.react.js +++ b/src/components/Filter/Filter.react.js @@ -5,11 +5,11 @@ * This source code is licensed under the license found in the LICENSE file in * the root directory of this source tree. */ -import * as Filters from 'lib/Filters'; -import { List, Map } from 'immutable'; -import PropTypes from 'lib/PropTypes'; -import React from 'react'; -import stringCompare from 'lib/stringCompare'; +import * as Filters from 'lib/Filters'; +import { List, Map } from 'immutable'; +import PropTypes from 'lib/PropTypes'; +import React from 'react'; +import stringCompare from 'lib/stringCompare'; import { CurrentApp } from 'context/currentApp'; function changeField(schema, filters, index, newField) { @@ -22,27 +22,27 @@ function changeField(schema, filters, index, newField) { const newFilter = new Map({ field: newField, constraint: useExisting ? constraint : Filters.FieldConstraints[schema[newField].type][0], - compareTo: (useExisting && typeof defaultCompare === typeof compare) ? compare : defaultCompare + compareTo: useExisting && typeof defaultCompare === typeof compare ? compare : defaultCompare, }); return filters.set(index, newFilter); } function changeConstraint(schema, filters, index, newConstraint, prevCompareTo) { - let field = filters.get(index).get('field'); + const field = filters.get(index).get('field'); let compareType = schema[field].type; if (Object.prototype.hasOwnProperty.call(Filters.Constraints[newConstraint], 'field')) { compareType = Filters.Constraints[newConstraint].field; } - let newFilter = new Map({ + const newFilter = new Map({ field: field, constraint: newConstraint, - compareTo: prevCompareTo ?? Filters.DefaultComparisons[compareType] - }) + compareTo: prevCompareTo ?? Filters.DefaultComparisons[compareType], + }); return filters.set(index, newFilter); } function changeCompareTo(schema, filters, index, type, newCompare) { - let newValue = newCompare; + const newValue = newCompare; return filters.set(index, filters.get(index).set('compareTo', newValue)); } @@ -50,24 +50,26 @@ function deleteRow(filters, index) { return filters.delete(index); } -let Filter = ({ schema, filters, renderRow, onChange, onSearch, blacklist, className }) => { +const Filter = ({ schema, filters, renderRow, onChange, onSearch, blacklist, className }) => { const currentApp = React.useContext(CurrentApp); blacklist = blacklist || []; - let available = Filters.availableFilters(schema, filters); + const available = Filters.availableFilters(schema, filters); return (
{filters.toArray().map((filter, i) => { - let field = filter.get('field'); - let constraint = filter.get('constraint'); - let compareTo = filter.get('compareTo'); + const field = filter.get('field'); + const constraint = filter.get('constraint'); + const compareTo = filter.get('compareTo'); - let fields = Object.keys(available).concat([]); + const fields = Object.keys(available).concat([]); if (fields.indexOf(field) < 0) { fields.push(field); } // Get the column preference of the current class. - const currentColumnPreference = currentApp.columnPreference ? currentApp.columnPreference[className] : null; + const currentColumnPreference = currentApp.columnPreference + ? currentApp.columnPreference[className] + : null; // Check if the preference exists. if (currentColumnPreference) { @@ -78,7 +80,7 @@ let Filter = ({ schema, filters, renderRow, onChange, onSearch, blacklist, class fields.sort((a, b) => { // Only "a" should sorted to the top. if (fieldsToSortToTop.includes(a) && !fieldsToSortToTop.includes(b)) { - return -1 + return -1; } // Only "b" should sorted to the top. if (!fieldsToSortToTop.includes(a) && fieldsToSortToTop.includes(b)) { @@ -96,7 +98,9 @@ let Filter = ({ schema, filters, renderRow, onChange, onSearch, blacklist, class fields.sort(); } - let constraints = Filters.FieldConstraints[schema[field].type].filter((c) => blacklist.indexOf(c) < 0); + const constraints = Filters.FieldConstraints[schema[field].type].filter( + c => blacklist.indexOf(c) < 0 + ); let compareType = schema[field].type; if (Object.prototype.hasOwnProperty.call(Filters.Constraints[constraint], 'field')) { compareType = Filters.Constraints[constraint].field; @@ -122,14 +126,14 @@ let Filter = ({ schema, filters, renderRow, onChange, onSearch, blacklist, class onChangeCompareTo: newCompare => { onChange(changeCompareTo(schema, filters, i, compareType, newCompare)); }, - onKeyDown: ({key}) => { + onKeyDown: ({ key }) => { if (key === 'Enter') { onSearch(); } }, onDeleteRow: () => { onChange(deleteRow(filters, i)); - } + }, }); })}
@@ -145,7 +149,5 @@ Filter.propTypes = { filters: PropTypes.instanceOf(List).isRequired.describe( 'An array of filter objects. Each filter contains "field", "comparator", and "compareTo" fields.' ), - renderRow: PropTypes.func.isRequired.describe( - 'A function for rendering a row of a filter.' - ) + renderRow: PropTypes.func.isRequired.describe('A function for rendering a row of a filter.'), }; diff --git a/src/components/FlowFooter/FlowFooter.react.js b/src/components/FlowFooter/FlowFooter.react.js index 7477486490..126e4a9371 100644 --- a/src/components/FlowFooter/FlowFooter.react.js +++ b/src/components/FlowFooter/FlowFooter.react.js @@ -5,17 +5,17 @@ * This source code is licensed under the license found in the LICENSE file in * the root directory of this source tree. */ -import React from 'react'; +import React from 'react'; import PropTypes from 'lib/PropTypes'; -import styles from 'components/FlowFooter/FlowFooter.scss'; +import styles from 'components/FlowFooter/FlowFooter.scss'; -let FlowFooter = ({ primary, secondary, errorMessage, borderTop, children }) => ( +const FlowFooter = ({ primary, secondary, errorMessage, borderTop, children }) => (
{secondary} {primary}
-
+
{errorMessage || children}
@@ -23,19 +23,11 @@ let FlowFooter = ({ primary, secondary, errorMessage, borderTop, children }) => export default FlowFooter; FlowFooter.propTypes = { - primary: PropTypes.node.describe( - 'A primary action Button.' - ), - secondary: PropTypes.node.describe( - 'A secondary action Button.' - ), - errorMessage: PropTypes.node.describe( - 'The error message of the flow.' - ), - borderTop: PropTypes.string.describe( - 'Style override for footer border-top.' - ), + primary: PropTypes.node.describe('A primary action Button.'), + secondary: PropTypes.node.describe('A secondary action Button.'), + errorMessage: PropTypes.node.describe('The error message of the flow.'), + borderTop: PropTypes.string.describe('Style override for footer border-top.'), children: PropTypes.node.describe( 'The text of the footer. tags will be rendered in bold.' ), -} +}; diff --git a/src/components/FlowView/FlowView.react.js b/src/components/FlowView/FlowView.react.js index 275f272b4e..cf2a9dbbf3 100644 --- a/src/components/FlowView/FlowView.react.js +++ b/src/components/FlowView/FlowView.react.js @@ -5,10 +5,10 @@ * This source code is licensed under the license found in the LICENSE file in * the root directory of this source tree. */ -import Button from 'components/Button/Button.react'; +import Button from 'components/Button/Button.react'; import FlowFooter from 'components/FlowFooter/FlowFooter.react'; -import PropTypes from 'lib/PropTypes'; -import React from 'react'; +import PropTypes from 'lib/PropTypes'; +import React from 'react'; import SaveButton from 'components/SaveButton/SaveButton.react'; export default class FlowView extends React.Component { @@ -23,21 +23,21 @@ export default class FlowView extends React.Component { } componentWillReceiveProps(props) { - let newChanges = {...this.state.changes}; - for (let k in props.initialFields) { + const newChanges = { ...this.state.changes }; + for (const k in props.initialFields) { if (this.state.changes[k] === props.initialFields[k]) { delete newChanges[k]; } } - this.setState({changes: newChanges}); + this.setState({ changes: newChanges }); } currentFields() { - let fields = {}; - for (let k in this.props.initialFields) { + const fields = {}; + for (const k in this.props.initialFields) { fields[k] = this.props.initialFields[k]; } - for (let k in this.state.changes) { + for (const k in this.state.changes) { fields[k] = this.state.changes[k]; } return fields; @@ -45,7 +45,7 @@ export default class FlowView extends React.Component { setField(key, value, preserveSavingState = false) { if (this.state.saveState !== SaveButton.States.SAVING) { - let newChanges = {...this.state.changes}; + const newChanges = { ...this.state.changes }; newChanges[key] = value; if (newChanges[key] === this.props.initialFields[key]) { delete newChanges[key]; @@ -72,7 +72,7 @@ export default class FlowView extends React.Component { } render() { - let { + const { inProgressText, submitText, showFooter = () => true, @@ -82,24 +82,24 @@ export default class FlowView extends React.Component { validate = () => '', onSubmit, afterSave = () => {}, - secondaryButton = () =>
); -let { ...otherPropTypes } = Button.propTypes; +const { ...otherPropTypes } = Button.propTypes; FormButton.propTypes = otherPropTypes; export default FormButton; diff --git a/src/components/FormModal/FormModal.react.js b/src/components/FormModal/FormModal.react.js index eb375e6545..422a3f7e96 100644 --- a/src/components/FormModal/FormModal.react.js +++ b/src/components/FormModal/FormModal.react.js @@ -5,10 +5,10 @@ * This source code is licensed under the license found in the LICENSE file in * the root directory of this source tree. */ -import React from 'react'; +import React from 'react'; import PropTypes from 'lib/PropTypes'; -import Modal from 'components/Modal/Modal.react'; -import FormNote from 'components/FormNote/FormNote.react'; +import Modal from 'components/Modal/Modal.react'; +import FormNote from 'components/FormNote/FormNote.react'; export default class FormModal extends React.Component { constructor() { @@ -20,7 +20,7 @@ export default class FormModal extends React.Component { } render() { - let { + const { children, open, submitText = 'Confirm', @@ -33,7 +33,7 @@ export default class FormModal extends React.Component { showErrors = true, ...modalProps } = this.props; - let showModal = open || this.state.inProgress; + const showModal = open || this.state.inProgress; if (!modalProps.type) { if (this.state.errorMessage.length > 0) { modalProps.type = Modal.Types.DANGER; @@ -41,57 +41,76 @@ export default class FormModal extends React.Component { modalProps.type = Modal.Types.VALID; } } - return (showModal ? { - this.setState({ - errorMessage: '', - inProgress: true, - }); - onSubmit().then(result => { + return showModal ? ( + { + this.setState({ + errorMessage: '', + inProgress: true, + }); + onSubmit() + .then(result => { + onClose(); + clearFields(); + onSuccess(result); + this.setState({ inProgress: false }); + }) + .catch(({ message, error, notice, errors = [] }) => { + this.setState({ + errorMessage: errors.join(' ') || message || error || notice || 'An error occurred', + inProgress: false, + }); + }); + }} + onCancel={() => { onClose(); clearFields(); - onSuccess(result); - this.setState({inProgress: false}); - }).catch(({ message, error, notice, errors = [] }) => { this.setState({ - errorMessage: errors.join(' ') || message || error || notice || 'An error occurred', - inProgress: false, + errorMessage: '', }); - }); - }} - onCancel={() => { - onClose(); - clearFields(); - this.setState({ - errorMessage: '', - }); - }} - disabled={!enabled} - canCancel={!this.state.inProgress} - progress={this.state.inProgress} > - {children} - {showErrors ? 0} - color='red' > - {this.state.errorMessage} - : null} - : null) + }} + disabled={!enabled} + canCancel={!this.state.inProgress} + progress={this.state.inProgress} + > + {children} + {showErrors ? ( + 0} color="red"> + {this.state.errorMessage} + + ) : null} + + ) : null; } } -let { ...forwardedModalProps} = Modal.propTypes; +const { ...forwardedModalProps } = Modal.propTypes; FormModal.propTypes = { ...forwardedModalProps, children: PropTypes.node.describe('The form elements to be rendered in the modal.'), open: PropTypes.bool.isRequired.describe('Whether or not to show the modal.'), - submitText: PropTypes.string.describe('The text to show on the CTA button. Defaults to "Confirm"'), - inProgressText: PropTypes.string.describe('The text to show to the CTA button while the request is in flight. Defaults to "Confirming\u2026".'), - onSubmit: PropTypes.func.isRequired.describe('A function that will be called when the user submits the form. This function must return a promise.'), - onSuccess: PropTypes.func.describe('A function that will be called with the result of the promise created in onSubmit.'), - onClose: PropTypes.func.isRequired.describe('A function that will be called when the modal is closing. After this function is called, the parent should not pass "true" to the "open" prop.'), - clearFields: PropTypes.func.describe('A function that should clear the state of the form fields inside the modal.'), + submitText: PropTypes.string.describe( + 'The text to show on the CTA button. Defaults to "Confirm"' + ), + inProgressText: PropTypes.string.describe( + 'The text to show to the CTA button while the request is in flight. Defaults to "Confirming\u2026".' + ), + onSubmit: PropTypes.func.isRequired.describe( + 'A function that will be called when the user submits the form. This function must return a promise.' + ), + onSuccess: PropTypes.func.describe( + 'A function that will be called with the result of the promise created in onSubmit.' + ), + onClose: PropTypes.func.isRequired.describe( + 'A function that will be called when the modal is closing. After this function is called, the parent should not pass "true" to the "open" prop.' + ), + clearFields: PropTypes.func.describe( + 'A function that should clear the state of the form fields inside the modal.' + ), enabled: PropTypes.bool.describe('Set to false to disable the confirm button.'), - showErrors: PropTypes.bool.describe('Set to false to hide errors if you are doing custom error handling (such as migration and new app modals).'), + showErrors: PropTypes.bool.describe( + 'Set to false to hide errors if you are doing custom error handling (such as migration and new app modals).' + ), }; diff --git a/src/components/FormNote/FormNote.react.js b/src/components/FormNote/FormNote.react.js index d1c2879a74..9eb754b5f6 100644 --- a/src/components/FormNote/FormNote.react.js +++ b/src/components/FormNote/FormNote.react.js @@ -11,7 +11,7 @@ import React from 'react'; import SliderWrap from 'components/SliderWrap/SliderWrap.react'; import styles from 'components/FormNote/FormNote.scss'; -let FormNote = ({ show, children, color, ...other }) => ( +const FormNote = ({ show, children, color, ...other }) => (
{children}
@@ -19,7 +19,7 @@ let FormNote = ({ show, children, color, ...other }) => ( FormNote.propTypes = { show: PropTypes.bool, - color: PropTypes.oneOf(['blue', 'green', 'orange', 'red']) + color: PropTypes.oneOf(['blue', 'green', 'orange', 'red']), }; export default FormNote; diff --git a/src/components/FormTable/FormTable.example.js b/src/components/FormTable/FormTable.example.js index 778dbb0a6b..f04429adcc 100644 --- a/src/components/FormTable/FormTable.example.js +++ b/src/components/FormTable/FormTable.example.js @@ -5,10 +5,10 @@ * This source code is licensed under the license found in the LICENSE file in * the root directory of this source tree. */ -import Field from 'components/Field/Field.react'; -import FormTable from 'components/FormTable/FormTable.react'; -import Label from 'components/Label/Label.react'; -import React from 'react'; +import Field from 'components/Field/Field.react'; +import FormTable from 'components/FormTable/FormTable.react'; +import Label from 'components/Label/Label.react'; +import React from 'react'; export const component = FormTable; @@ -16,58 +16,67 @@ export const demos = [ { render: () => ( } - input={} + input={ + { + alert('Delete button clicked.'); }, - { - key: 'important', - value: 'pay attention', - strong: true, - } - ], - }, - { - title: 'Title', - color: 'red', - onDelete: () => {alert('Delete button clicked.')}, - notes: [ - { - key: 'foo', - keyColor: 'red', - value: 'bar', - } - ] - } - ]} /> - } /> - ) + notes: [ + { + key: 'foo', + keyColor: 'red', + value: 'bar', + }, + ], + }, + ]} + /> + } + /> + ), }, { render: () => ( } - input={ - } /> - ) + label={