├── .babelrc ├── .eslintrc ├── .github ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .npmignore ├── .vscode ├── launch.json └── settings.json ├── README.md ├── devdist └── index.html ├── package-lock.json ├── package.json ├── src ├── actions │ ├── ActionTypes.js │ ├── FetcherWrapper.js │ ├── __mocks__ │ │ └── FetcherWrapper.js │ ├── __tests__ │ │ └── items-actions.test.js │ ├── api │ │ └── TypeIconsConfiguration.js │ └── items-actions.js ├── components │ ├── alert │ │ └── ConfirmAlert.react.js │ ├── dynamic-controls │ │ ├── ArrayControl │ │ │ └── ArrayControl.react.js │ │ ├── Common │ │ │ └── Icon.react.js │ │ ├── ControlFactory.react.js │ │ ├── DateTime.react.js │ │ ├── Dropdown.react.js │ │ ├── Link │ │ │ ├── Card.react.js │ │ │ ├── Link.react.js │ │ │ ├── LinkSearch.react.js │ │ │ └── LinksControl.react.js │ │ ├── MultiOptions │ │ │ ├── ListOption.react.js │ │ │ ├── Multioptions.react.js │ │ │ └── SelectedItem.react.js │ │ ├── ObjectControl │ │ │ └── ObjectControl.react.js │ │ ├── PropertyControlHOC.js │ │ ├── TextArea.react.js │ │ └── TextBox.react.js │ └── endlessTable │ │ └── views │ │ ├── Cell.react.js │ │ ├── FillHandle.react.js │ │ ├── Footer.react.js │ │ ├── Header.react.js │ │ ├── HeaderCell.react.js │ │ ├── Row.react.js │ │ ├── Rows.react.js │ │ ├── Table.react.js │ │ ├── VirtualList.js │ │ └── utils │ │ └── index.js ├── example.js ├── i18n │ ├── en-US_messages.json │ ├── es-AR_messages.json │ └── pt-BR_messages.json ├── index.js ├── reducers │ ├── items-reducer.js │ └── stateShape.js ├── redux │ ├── configureStore.js │ ├── constants.js │ └── rootReducer.js ├── table │ ├── actions.js │ ├── components │ │ └── Row │ │ │ ├── Form.js │ │ │ ├── FormSection.js │ │ │ └── InputTypes │ │ │ └── Checkbox.js │ ├── constants.js │ ├── containers │ │ ├── FormContainer.js │ │ └── TableContainer.js │ └── reducer.js ├── toolBar │ ├── __tests__ │ │ └── reducer.spec.js │ ├── actions.js │ ├── components │ │ ├── ColumnFilter │ │ │ └── index.js │ │ ├── SaveButton │ │ │ └── index.js │ │ ├── StateFilters │ │ │ └── index.js │ │ └── ToolBar │ │ │ └── index.js │ ├── constants.js │ ├── containers │ │ └── ToolBarContainer.js │ ├── logic.js │ ├── reducer.js │ └── utils.js └── utils │ ├── KeyMap.js │ ├── icons.js │ └── utils.js ├── webpack.dev.config.js ├── webpack.prod.config.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "production": { 4 | "presets": [ 5 | "react", 6 | [ 7 | "es2015" 8 | ] 9 | ], 10 | "plugins": [ 11 | "external-helpers", 12 | "transform-class-properties", 13 | "transform-object-rest-spread", 14 | [ 15 | "transform-runtime", 16 | { 17 | "polyfill": false, 18 | "regenerator": true 19 | } 20 | ] 21 | ] 22 | }, 23 | "test": { 24 | "presets": [ 25 | "react", 26 | [ 27 | "es2015" 28 | ] 29 | ], 30 | "plugins": [ 31 | "transform-es2015-modules-commonjs" 32 | ] 33 | }, 34 | "development": { 35 | "presets": [ 36 | "react", 37 | [ 38 | "es2015" 39 | ] 40 | ], 41 | "plugins": [ 42 | "external-helpers", 43 | "transform-class-properties", 44 | "transform-object-rest-spread", 45 | [ 46 | "transform-runtime", 47 | { 48 | "polyfill": false, 49 | "regenerator": true 50 | } 51 | ] 52 | ] 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "globals": { 3 | "$": true 4 | }, 5 | "env": { 6 | "jest": true 7 | }, 8 | "extends": [ 9 | "eslint-config-vtex", 10 | "eslint-config-vtex-react" 11 | ] 12 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Expected Behavior 4 | 5 | 6 | 7 | ## Current Behavior 8 | 9 | 10 | 11 | ## Possible Solution 12 | 13 | 14 | 15 | ## Steps to Reproduce (for bugs) 16 | 17 | 18 | 1. 19 | 2. 20 | 3. 21 | 4. 22 | 23 | ## Context 24 | 25 | 26 | 27 | ## Your Environment 28 | 29 | * Version used: 30 | * Environment name and version (e.g. Chrome 39, node.js 5.4): 31 | * Operating System and version (desktop or mobile): 32 | * Link to your project: -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | #### What is the purpose of this pull request? 2 | 3 | 4 | #### What problem is this solving? 5 | 6 | 7 | #### How should this be manually tested? 8 | 9 | #### Screenshots or example usage 10 | 11 | #### Types of changes 12 | - [ ] Bug fix (non-breaking change which fixes an issue) 13 | - [ ] New feature (non-breaking change which adds functionality) 14 | - [ ] Breaking change (fix or feature that would cause existing functionality to change) 15 | - [ ] Requires change to documentation, which has been updated accordingly. 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | 6 | # builds 7 | build 8 | dist 9 | 10 | # misc 11 | .DS_Store 12 | .env 13 | .env.local 14 | .env.development.local 15 | .env.test.local 16 | .env.production.local 17 | 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* 21 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | coverage/ 2 | scripts/ 3 | example/ 4 | test/ 5 | rollup.config.* 6 | .* 7 | devdist/ 8 | example_production/ 9 | src/example.js 10 | webpack.* 11 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "node", 6 | "request": "launch", 7 | "name": "Run tests", 8 | "program": "${workspaceRoot}/node_modules/.bin/jest" 9 | }, 10 | { 11 | "name": "JEST Tests", 12 | "type": "node", 13 | "request": "launch", 14 | "program": "${workspaceRoot}/bin/www", 15 | "stopOnEntry": false, 16 | "args": [], 17 | "cwd": "${workspaceRoot}", 18 | "preLaunchTask": null, 19 | "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/babel-node", 20 | "runtimeArgs": ["--nolazy"], 21 | "env": { 22 | "NODE_ENV": "development", 23 | "ASSET_HOST": "http://localhost:8081", 24 | "NO_WEBPACK_MIDDLEWARE": "true" 25 | }, 26 | "console": "internalConsole", 27 | "sourceMaps": true, 28 | "outFiles": [] 29 | } 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # react-jsonschema-table 3 | 4 | > Simple usage react component stylesheet table with inifnite scroll for massive data consumption and line editing using JSONSchema as data structure. 5 | 6 | ### Work In Progress ! 7 | 8 | [![NPM](https://img.shields.io/npm/v/react-modern-library-boilerplate.svg)](https://www.npmjs.com/package/react-modern-library-boilerplate) [![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com) 9 | 10 | ## Install 11 | 12 | ```bash 13 | npm install --save react-jsonschema-table 14 | ``` 15 | 16 | ## Simple Usage 17 | 18 | ```js 19 | import React, { Component } from 'react' 20 | 21 | import Table from 'react-jsonschema-table' 22 | 23 | const schema = { 24 | properties: { 25 | name: { 26 | type: 'string', 27 | title: 'Name', 28 | }, 29 | lastName: { 30 | type: 'string', 31 | title: 'LastName', 32 | }, 33 | email: { 34 | type: 'string', 35 | format: 'email', 36 | title: 'Email', 37 | } 38 | } 39 | } 40 | 41 | class Example extends Component { 42 | render () { 43 | return ( 44 | 45 | ) 46 | } 47 | } 48 | ``` 49 | ## API 50 | 51 | **schema:** Is the JSONSchema that contains the estructure and validation rules of the rendered data. 52 | 53 | **items**: An array of document objectcs compliant to the schema format. exemple: 54 | ```js 55 | items: [{ 56 | virtualId: 0, // integer 57 | document: { // actual document 58 | name: 'Jhon', 59 | lastName: 'Doe', 60 | email: 'jhon@doe.com', 61 | id: '2a08db19-894c-4d1a-82b6-f4abe2ebbe33' 62 | }, // compliant to schema, you can have extra fields, they will not show on the Table but will be considered in callbacks 63 | status: 'loaded' // string (one of 'loaded', 'loading', 'lazzy','new','invalid') 64 | }] 65 | ``` 66 | 67 | **pagination**: boolean default true. If JsonschemaTable should paginate items for better handling massive amounts of items. (callback will be called when user has scrolled until 80% of items) 68 | 69 | **getMoreItems**: function callback so JsonschemaTable can let you know it needs to load more items if pagination is activated 70 | 71 | **shouldSaveData**: boolean default true that activates the save feature. (which can be configured with the following function) 72 | 73 | **stagingItemsCallback**: function that return all the staging documents when 'save' button is clicked, so you can save them to your API or whatever. 74 | 75 | **checkedItemsCallback**: function that return all the checked documents when 'delete' button is clicked, so you can delete them in your API or whatever. 76 | 77 | **toolbarConfigs**: object to configure and toggle toolbar functions. example: 78 | ```js 79 | toolbarConfigs: { 80 | hideStateFilterBtn: true, 81 | hideColumnsVisibilityBtn: true, 82 | hideDownloadBtn: true, 83 | hideNewLineBtn: true, 84 | hideDeleteBtn: true, 85 | hideUndoBtn: true, 86 | hideSaveBtn: true, 87 | } 88 | ``` 89 | 90 | ## Local setup for developing 91 | 92 | Setup project 93 | 94 | ```bash 95 | npm i 96 | ``` 97 | 98 | Run example 99 | 100 | ```bash 101 | npm start 102 | ``` 103 | 104 | ## License 105 | 106 | MIT © [VTEX](https://github.com/vtex) 107 | -------------------------------------------------------------------------------- /devdist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Example React JSONSchema Table 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-jsonschema-table", 3 | "version": "0.0.2-beta.63", 4 | "description": "Simple usage react component stylesheet table with inifnite scroll for massive data consumption", 5 | "author": "Alejandro Osorio", 6 | "contributors": [ 7 | "Alejandro Osorio", 8 | "Guilherme Freitas" 9 | ], 10 | "license": "MIT", 11 | "repository": { 12 | "type": "git", 13 | "url": "git@github.com:vtex/react-jsonschema-table.git" 14 | }, 15 | "main": "dist/index.js", 16 | "module": "dist/index.js", 17 | "jsnext:main": "dist/index.js", 18 | "scripts": { 19 | "test": "jest", 20 | "test:watch": "npm test -- --watch", 21 | "build": "NODE_ENV=production webpack --progress --colors --config ./webpack.prod.config.js", 22 | "start": "NODE_ENV=development webpack-dev-server --progress --colors --config ./webpack.dev.config.js", 23 | "prepublish": "npm run build", 24 | "predeploy": "npm run prepublish && cd example_production && npm start" 25 | }, 26 | "dependencies": { 27 | "@fortawesome/fontawesome": "^1.1.3", 28 | "@fortawesome/fontawesome-free-solid": "^5.0.6", 29 | "@vtex/styleguide": "2.0.0-rc.34", 30 | "ajv": "^5.2.2", 31 | "ajv-i18n": "^2.1.1", 32 | "intl": "^1.2.5", 33 | "json2csv": "^3.5.0", 34 | "moment": "^2.18.1", 35 | "react-datez": "^1.3.5", 36 | "react-dropzone": "^3.13.4", 37 | "react-hotkeys": "^0.10.0", 38 | "react-intl": "^2.3.0", 39 | "react-onclickoutside": "^6.7.1", 40 | "react-redux": "^5.0.6", 41 | "react-widgets": "^4.2.3", 42 | "react-widgets-moment": "^4.0.12", 43 | "redux": "^3.7.2", 44 | "redux-logger": "^3.0.6", 45 | "redux-persist": "^5.9.1", 46 | "underscore": "^1.8.3", 47 | "vtex-tachyons": "^2.4.0" 48 | }, 49 | "peerDependencies": { 50 | "prop-types": "^15.5.4", 51 | "react": "^16.x", 52 | "react-dom": "^16.x" 53 | }, 54 | "devDependencies": { 55 | "babel-core": "^6.26.0", 56 | "babel-eslint": "^7.2.3", 57 | "babel-jest": "^21.2.0", 58 | "babel-loader": "^7.1.2", 59 | "babel-plugin-external-helpers": "^6.22.0", 60 | "babel-plugin-react-transform": "^2.0.2", 61 | "babel-plugin-transform-class-properties": "^6.24.1", 62 | "babel-plugin-transform-es2015-modules-commonjs": "^6.26.0", 63 | "babel-plugin-transform-object-rest-spread": "^6.23.0", 64 | "babel-plugin-transform-runtime": "^6.23.0", 65 | "babel-preset-env": "^1.6.0", 66 | "babel-preset-es2015": "^6.24.1", 67 | "babel-preset-react": "^6.24.1", 68 | "babel-preset-stage-0": "^6.24.1", 69 | "babel-preset-stage-1": "^6.24.1", 70 | "babel-preset-stage-2": "^6.24.1", 71 | "concurrently": "^3.5.1", 72 | "css-loader": "^0.28.10", 73 | "enzyme": "^3.1.0", 74 | "eslint": "^4.9.0", 75 | "eslint-config-vtex": "^7.0.0", 76 | "eslint-config-vtex-react": "^3.0.3", 77 | "file-loader": "^1.1.9", 78 | "gh-pages": "^1.0.0", 79 | "image-webpack-loader": "^4.1.0", 80 | "jest": "^21.2.1", 81 | "react": "^16.2.0", 82 | "react-dom": "^16.2.0", 83 | "react-hot-loader": "^4.1.2", 84 | "react-test-renderer": "^16.0.0", 85 | "redux-mock-store": "^1.3.0", 86 | "style-loader": "^0.20.2", 87 | "url-loader": "^0.6.2", 88 | "webpack": "^3.11.0", 89 | "webpack-cli": "^2.0.14", 90 | "webpack-dev-server": "^2.11.1" 91 | }, 92 | "babel": { 93 | "presets": [ 94 | "env", 95 | "react", 96 | "stage-2" 97 | ] 98 | }, 99 | "jest": { 100 | "verbose": true, 101 | "transform": { 102 | "^.+\\.js$": "babel-jest" 103 | }, 104 | "globals": { 105 | "NODE_ENV": "test" 106 | }, 107 | "moduleFileExtensions": [ 108 | "js", 109 | "jsx" 110 | ] 111 | }, 112 | "files": [ 113 | "dist" 114 | ] 115 | } 116 | -------------------------------------------------------------------------------- /src/actions/ActionTypes.js: -------------------------------------------------------------------------------- 1 | export const ITEMS_LOAD_BEGAN = 'ITEMS_LOAD_BEGAN' 2 | export const ITEMS_LOAD_SUCCESS = 'ITEMS_LOAD_SUCCESS' 3 | export const ITEMS_LOAD_FAIL = 'ITEMS_LOAD_FAIL' 4 | export const SAVE_ITEMS_CHANGES_BEGAN = 'SAVE_ITEMS_CHANGES' 5 | export const SAVE_ITEMS_CHANGES_COMPLETE = 'SAVE_ITEMS_CHANGES_COMPLETE' 6 | export const SAVE_ITEMS_CHANGES_FAIL = 'SAVE_ITEMS_CHANGES_FAIL' 7 | export const REMOVE_ITEM = 'REMOVE_ITEM' 8 | export const ADD_ITEM = 'ADD_ITEM' 9 | export const UPDATE_ITEM = 'UPDATE_ITEM' 10 | export const SELECT_CELL = 'SELECT_CELL' 11 | export const EDIT_CELL = 'EDIT_CELL' 12 | export const EXIT_EDIT_CELL = 'EXIT_EDIT_CELL' 13 | export const CLEAN_SELECTION = 'CLEAN_SELECTION' 14 | export const SELECT_FILLHANDLE_CELLS_RANGE = 'SELECT_FILLHANDLE_CELLS_RANGE' 15 | export const CLEAN_MASS_SELECTION = 'CLEAN_MASS_SELECTION' 16 | export const SELECT_CELLS_RANGE = 'SELECT_CELLS_RANGE' 17 | export const EXPORT_CHECKED_ITEMS = 'EXPORT_CHECKED_ITEMS' 18 | export const ITEM_STAGING = 'ITEM_STAGING' 19 | export const MARK_ITEM_TO_REMOVE = 'MARK_ITEM_TO_REMOVE' 20 | export const CHECK_ITEM_CHANGE = 'CHECK_ITEM' 21 | export const CANCEL_STAGING = 'CANCEL_STAGING' 22 | export const OPEN_DOCUMENT_IN_FORM = 'OPEN_DOCUMENT_IN_FORM' 23 | export const DELETE_CHECKED_ITEMS = 'DELETE_CHECKED_ITEMS' 24 | export const CHANGE_COLUMN_VISIBILITY = 'CHANGE_COLUMN_VISIBILITY' 25 | export const VIEW_ALL_COLUMNS = 'VIEW_ALL_COLUMNS' 26 | export const CHANGE_ROW_STATUS = 'CHANGE_ROW_STATUS' 27 | export const PASTE_DATA = 'PASTE_DATA' 28 | export const COPY_FROM_SELECTED_RANGE = 'COPY_FROM_SELECTED_RANGE' 29 | export const UNDO_CHANGE = 'UNDO_CHANGE' 30 | export const REDO_CHANGE = 'REDO_CHANGE' 31 | export const CHANGE_CHECKED_FILTER = 'CHECKED_FILTER_CHANGE' 32 | export const CHANGE_STAGING_FILTER = 'CHANGE_STAGING_FILTER' 33 | export const CHANGE_INVALID_ITEMS_FILTER = 'CHANGE_INVALID_ITEMS_FILTER' 34 | export const RECEIVE_ITEMS_FROM_PROPS = 'RECEIVE_ITEMS_FROM_PROPS' 35 | -------------------------------------------------------------------------------- /src/actions/FetcherWrapper.js: -------------------------------------------------------------------------------- 1 | let _fetcher = null 2 | 3 | export function SetFetcher(implementation) { 4 | _fetcher = implementation 5 | } 6 | 7 | export function GetFetcher() { 8 | return _fetcher 9 | } 10 | -------------------------------------------------------------------------------- /src/actions/__mocks__/FetcherWrapper.js: -------------------------------------------------------------------------------- 1 | export function GetFetcher() { 2 | return { 3 | getItems: () => { 4 | return new Promise((resolve, reject) => { 5 | process.nextTick(() => 6 | resolve({ 7 | items: [{ id: '1' }, { id: '2' }], 8 | totalRows: 2, 9 | rowStart: 0, 10 | }) 11 | ) 12 | }) 13 | }, 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/actions/__tests__/items-actions.test.js: -------------------------------------------------------------------------------- 1 | import configureMockStore from 'redux-mock-store' 2 | // import thunk from 'redux-thunk' 3 | import * as actions from '../items-actions' 4 | import * as types from '../ActionTypes' 5 | 6 | jest.mock('../FetcherWrapper') 7 | 8 | // const middlewares = [thunk] 9 | const middlewares = [] 10 | const mockStore = configureMockStore(middlewares) 11 | 12 | describe('actions', () => { 13 | afterEach 14 | it('WHEN FetchItems Action is called, the response MUST have the items list and the total rows', () => { 15 | const expectedActions = [ 16 | { 17 | type: types.ITEMS_LOAD_BEGAN, 18 | }, 19 | { 20 | type: types.ITEMS_LOAD_SUCCESS, 21 | items: [{ id: '1' }, { id: '2' }], 22 | totalRows: 2, 23 | rowStart: 0, 24 | }, 25 | ] 26 | const store = mockStore({}) 27 | return store 28 | .dispatch(actions.fetchItems()) 29 | .then(() => expect(store.getActions()).toEqual(expectedActions)) 30 | }) 31 | }) 32 | -------------------------------------------------------------------------------- /src/actions/api/TypeIconsConfiguration.js: -------------------------------------------------------------------------------- 1 | export default { 2 | string: { icon: 'font' }, 3 | text: { icon: 'align-left' }, 4 | boolean: { icon: 'check-square-o' }, 5 | date: { icon: 'calendar' }, 6 | phone: { icon: 'phone' }, 7 | option: { icon: 'ident' }, 8 | multioptions: { icon: 'list-ul' }, 9 | object: { icon: 'object-group' }, 10 | integer: { icon: 'font' }, 11 | array: { icon: 'list-ul' }, 12 | } 13 | -------------------------------------------------------------------------------- /src/actions/items-actions.js: -------------------------------------------------------------------------------- 1 | import * as types from './ActionTypes' 2 | import { GetFetcher } from './FetcherWrapper' 3 | 4 | const fetchItemsRequest = () => ({ 5 | type: types.ITEMS_LOAD_BEGAN, 6 | }) 7 | 8 | const fetchItemsSucess = (items, totalRows, rowStart, sort, where) => ({ 9 | type: types.ITEMS_LOAD_SUCCESS, 10 | items: items, 11 | totalRows: totalRows, 12 | rowStart: rowStart, 13 | sort: sort, 14 | where: where, 15 | }) 16 | 17 | const fetchItemsFailure = error => ({ 18 | type: types.ITEMS_LOAD_FAIL, 19 | error: error, 20 | }) 21 | 22 | const SaveChangesRequest = () => ({ 23 | type: types.ITEMS_LOAD_BEGAN, 24 | }) 25 | 26 | const saveChangesSucess = (items, totalRows, rowStart, sort, where) => ({ 27 | type: types.ITEMS_LOAD_SUCCESS, 28 | items: items, 29 | totalRows: totalRows, 30 | rowStart: rowStart, 31 | sort: sort, 32 | where: where, 33 | }) 34 | 35 | const saveChangesFailure = error => ({ 36 | type: types.ITEMS_LOAD_FAIL, 37 | error: error, 38 | }) 39 | 40 | export function addItem(id, schema, lang) { 41 | return { type: types.ADD_ITEM, id, schema, lang } 42 | } 43 | 44 | export function removeItem(index, schema, lang) { 45 | return { type: types.REMOVE_ITEM, index, schema, lang } 46 | } 47 | 48 | export function updateItem(id, schema, changes, lang) { 49 | return { type: types.UPDATE_ITEM, id, schema, changes, lang } 50 | } 51 | 52 | export function exportCheckedItems(fields, entityId) { 53 | return { 54 | type: types.EXPORT_CHECKED_ITEMS, 55 | payload: { 56 | fields, 57 | entityId, 58 | }, 59 | } 60 | } 61 | 62 | export function checkItemChange(id, checked) { 63 | return { type: types.CHECK_ITEM_CHANGE, id, checked } 64 | } 65 | 66 | export function saveChanges(context, schema) { 67 | return (dispatch, getState) => { 68 | const items = getState().items.staging 69 | const fetcher = GetFetcher() 70 | dispatch(SaveChangesRequest(items, context, schema)) 71 | return fetcher 72 | .saveChanges(items, context, schema) 73 | .then(response => 74 | dispatch( 75 | saveChangesSucess( 76 | response.items, 77 | response.totalRows, 78 | response.rowStart, 79 | sort, 80 | where 81 | ) 82 | ) 83 | ) 84 | .catch(ex => dispatch(saveChangesFailure(ex))) 85 | } 86 | } 87 | 88 | export function discardChanges() { 89 | return { type: types.CANCEL_STAGING } 90 | } 91 | 92 | export function undo(schema, lang) { 93 | return { type: types.UNDO_CHANGE, schema, lang } 94 | } 95 | export function redo(schema, lang) { 96 | return { type: types.REDO_CHANGE, schema, lang } 97 | } 98 | 99 | export function deleteCheckedItems() { 100 | return { type: types.DELETE_CHECKED_ITEMS } 101 | } 102 | 103 | export function fetchItems(context, fields, skip, size, where, sort) { 104 | return dispatch => { 105 | const fetcher = GetFetcher() 106 | dispatch(fetchItemsRequest(context, fields, skip, size, where, sort)) 107 | return fetcher 108 | .getItems(context, fields, skip, size, where, sort) 109 | .then(response => 110 | dispatch( 111 | fetchItemsSucess( 112 | response.items, 113 | response.totalRows, 114 | response.rowStart, 115 | sort, 116 | where 117 | ) 118 | ) 119 | ) 120 | .catch(ex => dispatch(fetchItemsFailure(ex))) 121 | } 122 | } 123 | 124 | export function preLoadItems(items) { 125 | const loadedItems = items.filter(i => i.status === 'loaded' && i.document !== null) 126 | return fetchItemsSucess( 127 | loadedItems, 128 | loadedItems.length, 129 | 0, 130 | ) 131 | } 132 | 133 | export function copyFromSelectedRange(changes, schema, lang) { 134 | return { type: types.COPY_FROM_SELECTED_RANGE, changes, schema, lang } 135 | } 136 | 137 | export function receiveItemsFromProps(items) { 138 | return { 139 | type: types.RECEIVE_ITEMS_FROM_PROPS, 140 | payload: { 141 | items, 142 | } 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/components/alert/ConfirmAlert.react.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Modal from '@vtex/styleguide/lib/Modal' 3 | import Button from '@vtex/styleguide/lib/Button' 4 | import PropTypes from 'prop-types' 5 | import { FormattedMessage } from 'react-intl' 6 | 7 | class ConfirmAlert extends React.Component { 8 | constructor(props) { 9 | super(props) 10 | this.state = { 11 | show: true, 12 | } 13 | this.handleHideDialog = this.handleHideDialog.bind(this) 14 | } 15 | 16 | componentWillReceiveProps(nextProps) { 17 | this.setState({ show: nextProps.show }) 18 | } 19 | 20 | render() { 21 | return ( 22 | 27 |
28 |
29 | 30 |
31 |
{this.props.message}
32 |
33 | 34 | 37 | 38 | 39 | 42 | 43 |
44 |
45 |
46 | ) 47 | } 48 | 49 | handleHideDialog() { 50 | this.setState({ show: false }) 51 | if (this.props.onCancel) { 52 | this.props.onCancel() 53 | } 54 | } 55 | } 56 | 57 | ConfirmAlert.propTypes = { 58 | onCancel: PropTypes.func, 59 | onConfirm: PropTypes.func, 60 | message: PropTypes.object, 61 | show: PropTypes.bool, 62 | } 63 | 64 | export default ConfirmAlert 65 | -------------------------------------------------------------------------------- /src/components/dynamic-controls/ArrayControl/ArrayControl.react.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import ReactDOM from 'react-dom' 4 | import _ from 'underscore' 5 | import { HotKeys } from 'react-hotkeys' 6 | import ControlFactory from '../ControlFactory.react' 7 | import Icon from '../Common/Icon.react' 8 | import Modal from '@vtex/styleguide/lib/Modal' 9 | import { FormattedMessage } from 'react-intl' 10 | 11 | class ArrayControl extends React.Component { 12 | constructor(props) { 13 | super(props) 14 | this.state = { showModal: false } 15 | } 16 | componentDidUpdate() { 17 | if ( 18 | this.props.isEditing && 19 | !this.state.showModal && 20 | this.props.renderType === 'cell' 21 | ) { 22 | this.props.onExitEdit() 23 | if (window && document) { 24 | ReactDOM.findDOMNode(this).focus() 25 | } 26 | } 27 | } 28 | componentWillReceiveProps(nextProps) { 29 | if (nextProps.isEditing && nextProps.renderType === 'cell') { 30 | this.setState({ showModal: true }) 31 | } 32 | } 33 | 34 | render() { 35 | const handlers = { 36 | moveUp: this.onArrow, 37 | moveDown: this.onArrow, 38 | moveRight: this.onArrow, 39 | moveLeft: this.onArrow, 40 | } 41 | 42 | const borderColor = this.props.validationErrors && 43 | this.props.validationErrors.length > 0 44 | ? 'b--red' 45 | : 'b--silver' 46 | 47 | if (this.props.isEditing) { 48 | if (this.state.showModal) { 49 | return ( 50 |
53 | {this.props.value 54 | ? `"${this.getI18nStr('Array.items.count')}" : ${this.props.value.length}` 55 | : null} 56 | { 59 | this.arrayModal = ref 60 | }} 61 | isOpen 62 | onClose={this.handleCloseModal} 63 | > 64 |
65 | {this.props.title || this.props.label} 66 |
67 |
68 | { 71 | this.modalLinksBody = ref 72 | }} 73 | handlers={handlers} 74 | > 75 | {this.renderArrayItems()} 76 | 77 |
78 |
79 |
80 | ) 81 | } 82 | return ( 83 | 87 | {this.renderArrayItems()} 88 | 89 | ) 90 | } 91 | return ( 92 |
95 | {this.props.value 96 | ? `"${this.getI18nStr('Array.items.count')}" : ${this.props.value.length}` 97 | : null} 98 |
99 | ) 100 | } 101 | 102 | renderArrayItems() { 103 | const itemsSchema = this.props.items 104 | const itemsToRender = [] 105 | _.each(this.props.value, (value, index) => { 106 | const props = { ...itemsSchema } 107 | props.value = value 108 | props.path = `${this.props.path}[${index}]` 109 | props.fieldName = this.props.fieldName 110 | props.isEditing = true 111 | props.key = `${this.props.fieldName}[${index}].${this.props.id}` 112 | props.id = index 113 | props.validationErrors = this.props.validationErrors 114 | ? _.filter(this.props.validationErrors, error => { 115 | return error.dataPath.includes(`${this.props.path}[${index}]`) 116 | }) 117 | : [] 118 | props.setChanges = this.setItemchange 119 | props.renderType = 'form' 120 | const borderColor = props.validationErrors && 121 | props.validationErrors.length > 0 122 | ? 'b--red' 123 | : 'b--silver' 124 | const iconColor = props.validationErrors && 125 | props.validationErrors.length > 0 126 | ? 'bg-red' 127 | : 'bg-black-90' 128 | 129 | itemsToRender.push( 130 |
134 | 140 | 141 | 147 |
148 | ) 149 | }) 150 | itemsToRender.push( 151 | 159 | ) 160 | return itemsToRender 161 | } 162 | setItemchange = (id, itemChanges) => { 163 | const updatedValue = itemChanges[this.props.fieldName].value 164 | let newValue 165 | switch(this.props.items.type) { 166 | case 'object': 167 | newValue = {} 168 | newValue[id] = updatedValue 169 | break; 170 | case 'array': 171 | case 'string': 172 | newValue = [] 173 | newValue = newValue.concat(this.props.value) 174 | newValue[id] = updatedValue 175 | break; 176 | default: 177 | newValue = updatedValue 178 | break; 179 | } 180 | this.props.setChange(newValue) 181 | }; 182 | onArrow() {} 183 | 184 | handleAddItem = () => { 185 | const newValue = this.props.value || [] 186 | const newItem = this.props.items.type === 'object' 187 | ? {} 188 | : this.props.items.type === 'array' ? [] : '' 189 | newValue.push(newItem) 190 | this.props.setChange(newValue) 191 | }; 192 | 193 | handleCloseModal = () => { 194 | this.setState({ showModal: false }) 195 | if (window && document) { 196 | ReactDOM.findDOMNode(this).focus() 197 | } 198 | }; 199 | 200 | handleRemoveLink = index => { 201 | const newValue = this.props.value ? this.props.value.slice() : [] 202 | newValue.splice(index, 1) 203 | this.props.setChange(newValue) 204 | }; 205 | 206 | getI18nStr(id) { 207 | return this.context.intl.formatMessage({ id: id }) 208 | } 209 | } 210 | ArrayControl.contextTypes = { 211 | intl: PropTypes.object.isRequired, 212 | } 213 | 214 | ArrayControl.propTypes = { 215 | items: PropTypes.object, 216 | value: PropTypes.array, 217 | path: PropTypes.string, 218 | label: PropTypes.string, 219 | title: PropTypes.string, 220 | validationErrors: PropTypes.array, 221 | hasError: PropTypes.bool, 222 | children: PropTypes.node, 223 | field: PropTypes.string, 224 | setChange: PropTypes.func, 225 | setChanges: PropTypes.func, 226 | isEditing: PropTypes.bool, 227 | isSelected: PropTypes.bool, 228 | isFocus: PropTypes.bool, 229 | renderType: PropTypes.string, 230 | properties: PropTypes.object, 231 | fieldName: PropTypes.string, 232 | id: PropTypes.string, 233 | onExitEdit: PropTypes.func, 234 | } 235 | export default ArrayControl 236 | -------------------------------------------------------------------------------- /src/components/dynamic-controls/Common/Icon.react.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | 4 | class Icon extends React.Component { 5 | constructor(props) { 6 | super(props) 7 | this.handleClick = this.handleClick.bind(this) 8 | } 9 | 10 | render() { 11 | return 12 | } 13 | 14 | handleClick() { 15 | if (this.props.onClick)this.props.onClick(this.props.id) 16 | } 17 | } 18 | 19 | Icon.propTypes = { 20 | id: PropTypes.any, 21 | className: PropTypes.string, 22 | onClick: PropTypes.func, 23 | } 24 | 25 | export default Icon 26 | -------------------------------------------------------------------------------- /src/components/dynamic-controls/ControlFactory.react.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import TextArea from './TextArea.react' 4 | import Checkbox from 'table/components/Row/InputTypes/Checkbox' 5 | import TextBox from './TextBox.react' 6 | import Dropdown from './Dropdown.react' 7 | import Multioptions from './MultiOptions/Multioptions.react' 8 | import ObjectControl from './ObjectControl/ObjectControl.react' 9 | // import Attachments from './Attachments/Attachments.react' 10 | import Link from './Link/Link.react' 11 | import DateTime from './DateTime.react' 12 | import ArrayControl from './ArrayControl/ArrayControl.react' 13 | import _ from 'underscore' 14 | 15 | class ControlFactory extends React.Component { 16 | render() { 17 | if (!this.props.type) { 18 | throw new Error('O tipo do campo não foi definido') 19 | } 20 | 21 | const controlConfig = this.getControlConfiguration(this.props) 22 | 23 | const props = { 24 | ...this.props, 25 | setChange: this.setChange, 26 | hasError: this.props.validationErrors.length > 0, 27 | } 28 | 29 | const errorMessage = 30 | this.props.renderType === 'form' && props.hasError ? ( 31 | 32 | {_.map(this.props.validationErrors, error => { 33 | return

{error.message}

34 | })} 35 |
36 | ) : null 37 | 38 | return ( 39 |
44 | {React.createElement( 45 | controlConfig.control, 46 | props, 47 | controlConfig.children 48 | )} 49 | {this.props.children} 50 | {errorMessage} 51 |
52 | ) 53 | } 54 | 55 | getControlConfiguration(definition) { 56 | const configuration = this.getControl(definition) 57 | const controlName = configuration.control.name 58 | ? configuration.control.name 59 | : configuration.control.displayName 60 | return configuration 61 | } 62 | 63 | getControl(definition) { 64 | var configuration = {} 65 | var type = Array.isArray(definition.type) 66 | ? definition.type[0] 67 | : definition.type 68 | 69 | switch (type) { 70 | case 'boolean': 71 | configuration.control = Checkbox 72 | break 73 | case 'string': 74 | if (definition.enum) { 75 | configuration.control = Dropdown 76 | } else if (definition.format === 'date-time') { 77 | configuration.control = DateTime 78 | } else if (definition.multiLine) { 79 | configuration.control = TextArea 80 | } else if (definition.link) { 81 | configuration.control = Link 82 | } else if (definition.media) { 83 | configuration.control = Attachments 84 | } else { 85 | configuration.control = TextBox 86 | } 87 | 88 | break 89 | case 'number': 90 | configuration.control = TextBox 91 | break 92 | case 'integer': 93 | configuration.control = TextBox 94 | break 95 | case 'array': 96 | if (definition.items.enum) { 97 | configuration.control = Multioptions 98 | } else if ( 99 | definition.items.type === 'string' && 100 | definition.items.properties && 101 | definition.items.properties.link 102 | ) { 103 | configuration.control = Link 104 | } else if ( 105 | definition.items.type === 'string' && 106 | definition.items.media 107 | ) { 108 | configuration.control = Attachments 109 | } else { 110 | configuration.control = ArrayControl 111 | } 112 | break 113 | case 'object': 114 | configuration.control = ObjectControl 115 | break 116 | } 117 | return configuration 118 | } 119 | 120 | setChange = newValue => { 121 | const changes = { 122 | [this.props.fieldName]: { 123 | value: newValue, 124 | }, 125 | } 126 | this.props.setChanges(this.props.id, changes) 127 | } 128 | 129 | getI18nStr(id) { 130 | return this.context.intl.formatMessage({ id: id }) 131 | } 132 | } 133 | 134 | ControlFactory.contextTypes = { 135 | intl: PropTypes.object.isRequired, 136 | } 137 | 138 | ControlFactory.propTypes = { 139 | validationErrors: PropTypes.array, 140 | isFocus: PropTypes.bool, 141 | isEditing: PropTypes.bool, 142 | renderType: PropTypes.string, 143 | value: PropTypes.any, 144 | type: PropTypes.oneOfType([PropTypes.string, PropTypes.array]), 145 | className: PropTypes.string, 146 | children: PropTypes.node, 147 | setChanges: PropTypes.func, 148 | fieldName: PropTypes.string, 149 | path: PropTypes.string, 150 | pattern: PropTypes.string, 151 | id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), 152 | isRequired: PropTypes.bool, 153 | } 154 | 155 | export default ControlFactory 156 | -------------------------------------------------------------------------------- /src/components/dynamic-controls/DateTime.react.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | // import { ReactDatez } from 'react-datez' 3 | // import 'react-datez/dist/css/react-datez.css' 4 | import { HotKeys } from 'react-hotkeys' 5 | import ReactDOM from 'react-dom' 6 | import PropTypes from 'prop-types' 7 | 8 | import moment from 'moment' 9 | 10 | class DateTime extends React.Component { 11 | constructor(props) { 12 | super(props) 13 | this.state = { 14 | date: props.value ? new Date(props.value) : null, 15 | dateStr: props.value ? this.prettierDate(props.value) : '', 16 | } 17 | } 18 | 19 | componentDidUpdate() { 20 | if (this.props.isEditing && window && document) { 21 | ReactDOM.findDOMNode(this.picker).focus() 22 | } 23 | } 24 | 25 | componentWillReceiveProps(nextProps) { 26 | var value = nextProps.value ? nextProps.value : null 27 | this.setState({ value: value }) 28 | } 29 | 30 | prettierDate(date) { 31 | return moment(date).format('MM-DD-YYYY').replace(/-/g, '/') 32 | } 33 | 34 | render() { 35 | window.momento = moment 36 | const handlers = { 37 | moveUp: this.onArrow, 38 | moveDown: this.onArrow, 39 | moveRight: this.onArrow, 40 | moveLeft: this.onArrow, 41 | } 42 | const borderColor = this.props.renderType === 'cell' 43 | ? this.props.hasError ? 'b--red' : 'b--blue' 44 | : this.props.hasError ? 'b--red br3' : 'b--silver br3' 45 | return ( 46 | { 48 | this.pickerContainer = ref 49 | }} 50 | className={ 51 | this.props.isEditing 52 | ? ` w-100 bw1 ba ${borderColor} h-inherit edit-mode` 53 | : `flex items-center h-inherit w-100 view-mode${ 54 | this.props.isFocus 55 | ? ` bw1 ba ${ 56 | borderColor 57 | } bg-lightest-blue selected-view-mode` 58 | : this.props.hasError ? 'bw1 ba b--red' : ''}` 59 | } 60 | // onBlur={this.handleBlur} 61 | onDoubleClick={this.props.onEditCell} 62 | handlers={handlers} 63 | > 64 |
65 | {/* { 67 | this.picker = ref 68 | }} 69 | inputStyle={{ width: '100%', marginLeft: '5px' }} 70 | allowPast 71 | value={this.state.date} 72 | handleChange={this.handleChange} 73 | /> */} 74 | { 82 | this.picker = ref 83 | }} 84 | onChange={this.handleChange} 85 | onClick={this.handleInputClick} 86 | onBlur={this.handleBlur} 87 | /> 88 |
89 |
90 | ) 91 | } 92 | 93 | handleBlur = () => { 94 | const newValue = this.state.date ? this.state.date.toISOString() : '' 95 | if ( 96 | (!this.props.value && !this.state.date) || newValue === this.props.value 97 | ) { 98 | return 99 | } 100 | this.props.setChange(newValue) 101 | }; 102 | 103 | // handleChange = (date, dateStr) => { 104 | 105 | // const selecteddate = date ? new Date(date) : '' 106 | // const newValue = selecteddate ? selecteddate.toISOString() : '' 107 | 108 | // this.setState({ date: date, dateStr: newValue }) 109 | // this.props.setChange(newValue) 110 | // }; 111 | 112 | handleChange = (e) => { 113 | const typedDate = e.target.value 114 | 115 | const selecteddate = typedDate ? new Date(typedDate) : typedDate 116 | 117 | this.setState({ date: selecteddate, dateStr: typedDate }) 118 | this.props.setChange(typedDate) 119 | }; 120 | 121 | onEnter = () => { 122 | if (this.props.isEditing) { 123 | this.props.setChange(this.state.value) 124 | if (this.props.onExitEditCell) { 125 | this.props.onExitEditCell(this.props.cell) 126 | } 127 | } 128 | }; 129 | 130 | onArrow = () => {}; 131 | } 132 | 133 | DateTime.propTypes = { 134 | hasError: PropTypes.bool, 135 | renderType: PropTypes.string, 136 | isFocus: PropTypes.bool, 137 | isEditing: PropTypes.bool, 138 | value: PropTypes.any, 139 | onExitEditCell: PropTypes.func, 140 | onEditCell: PropTypes.func, 141 | onExitEdit: PropTypes.func, 142 | setChange: PropTypes.func, 143 | cell: PropTypes.object, 144 | } 145 | 146 | export default DateTime 147 | -------------------------------------------------------------------------------- /src/components/dynamic-controls/Dropdown.react.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import DropdownList from 'react-widgets/lib/DropdownList' 4 | import { HotKeys } from 'react-hotkeys' 5 | import PropTypes from 'prop-types' 6 | 7 | class Dropdown extends React.Component { 8 | constructor(props) { 9 | super(props) 10 | this.state = { 11 | value: props.value, 12 | } 13 | this.onEnter = this.onEnter.bind(this) 14 | } 15 | 16 | componentDidUpdate() { 17 | if (!this.state.open && window && document) { 18 | ReactDOM.findDOMNode(this.dropdownlistcontainer).focus() 19 | } 20 | } 21 | 22 | componentWillReceiveProps(nextProps) { 23 | if (nextProps.isFocus && !this.props.isFocus) { 24 | this.props.onEditCell() 25 | } 26 | } 27 | 28 | render() { 29 | const handlers = { 30 | stageChanges: this.onEnter, 31 | } 32 | const dropdownHandlers = { 33 | stageChanges: this.onCancelKey, 34 | moveUp: this.onCancelKey, 35 | moveDown: this.onCancelKey, 36 | moveRight: this.onCancelKey, 37 | moveLeft: this.onCancelKey, 38 | } 39 | // const borderColor = this.props.hasError ? 'b--red' : 'b--blue' 40 | const borderColor = this.props.renderType === 'cell' 41 | ? this.props.hasError ? 'b--red' : 'b--blue' 42 | : this.props.hasError ? 'b--red br3' : 'b--silver br3' 43 | var control 44 | if (this.props.isFocus || this.props.isEditing) { 45 | control = ( 46 | { 48 | this.dropdownlistcontainer = ref 49 | }} 50 | className={`flex items-center h-inherit bw1 ba edit-mode ${borderColor}`} 51 | handlers={handlers} 52 | > 53 | 54 | 63 | 64 | 65 | ) 66 | } else { 67 | control = ( 68 |
{ 70 | this.dropdownlistcontainer = ref 71 | }} 72 | className={ 73 | this.props.hasError 74 | ? 'flex items-center w-100 h-inherit truncate outline-0 ba bw1 b--red pl05' 75 | : 'flex items-center w-100 h-inherit truncate outline-0 pl3' 76 | } 77 | > 78 | {this.props.value} 79 |
80 | ) 81 | } 82 | 83 | return control 84 | } 85 | handleChange = val => { 86 | this.props.setChange(val) 87 | }; 88 | onCancelKey = () => {}; 89 | onEnter = () => { 90 | this.setState({ open: true }) 91 | }; 92 | handleToggle = isOpen => { 93 | this.setState({ open: isOpen }) 94 | }; 95 | } 96 | 97 | Dropdown.propTypes = { 98 | hasError: PropTypes.bool, 99 | renderType: PropTypes.string, 100 | isFocus: PropTypes.bool, 101 | isEditing: PropTypes.bool, 102 | value: PropTypes.any, 103 | onEditCell: PropTypes.func, 104 | setChange: PropTypes.func, 105 | cell: PropTypes.object, 106 | enum: PropTypes.any, 107 | } 108 | 109 | export default Dropdown 110 | -------------------------------------------------------------------------------- /src/components/dynamic-controls/Link/Card.react.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | 4 | class Card extends React.Component { 5 | render() { 6 | const values = [] 7 | if (typeof this.props.displayValue === 'object') { 8 | values.push(this.renderObjectValues(this.props.displayValue)) 9 | } else { 10 | values.push(this.props.displayValue) 11 | } 12 | return ( 13 |
14 | {values} 15 | {this.props.children} 16 |
17 | ) 18 | } 19 | 20 | renderObjectValues(value) { 21 | const values = [] 22 | Object.keys(value).forEach(key => { 23 | if (value[key] && typeof value[key] === 'object') { 24 | const children = this.renderObjectValues(value[key]) 25 | values.push( 26 |
27 | {key + ':'} 28 |
{children}
29 |
30 | ) 31 | } else { 32 | values.push( 33 |
34 | {key + ':'} 35 | {value[key]} 36 |
37 | ) 38 | } 39 | }) 40 | return values 41 | } 42 | 43 | handleClick = () => { 44 | if (this.props.onClick) { 45 | this.props.onClick(this.props.id) 46 | } 47 | }; 48 | } 49 | 50 | Card.propTypes = { 51 | children: PropTypes.node, 52 | id: PropTypes.string, 53 | displayValue: PropTypes.any, 54 | onClick: PropTypes.func, 55 | className: PropTypes.string, 56 | isEditing: PropTypes.bool, 57 | } 58 | 59 | export default Card 60 | -------------------------------------------------------------------------------- /src/components/dynamic-controls/Link/LinkSearch.react.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import _ from 'underscore' 4 | import Card from './Card.react' 5 | import { 6 | FormattedMessage, 7 | injectIntl, 8 | intlShape, 9 | defineMessages, 10 | } from 'react-intl' 11 | import { HotKeys } from 'react-hotkeys' 12 | // import Actions from 'actions/Actions' 13 | // import Store from 'stores/VTableStore' 14 | 15 | class LinkSearch extends React.Component { 16 | constructor(props) { 17 | super(props) 18 | 19 | this.state = { 20 | searchValue: '', 21 | focusedIndex: -1, 22 | } 23 | 24 | // Actions.documentLoadPage.completed.listen(this.onSearchResult) 25 | } 26 | 27 | componentDidUpdate() { 28 | if ( 29 | this.props.focusedDiv === 'searchControl' && 30 | this.state.focusedIndex === -1 31 | ) { 32 | this.searchInput.focus() 33 | } 34 | } 35 | 36 | componentDidMount() { 37 | if (this.props.userTypedText) { 38 | this.setState({ searchValue: this.props.userTypedText }) 39 | } 40 | } 41 | 42 | componentWillReceiveProps(nextProps) { 43 | if (nextProps.focusedDiv === 'searchControl' && nextProps.up === true) { 44 | const index = this.state.searchResultValue && 45 | this.state.searchResultValue.length > 0 46 | ? this.state.searchResultValue.length - 1 47 | : this.state.searchValue ? 0 : this.state.focusedIndex 48 | this.setState({ focusedIndex: index }) 49 | } 50 | } 51 | 52 | render() { 53 | const that = this 54 | const searchResults = [] 55 | var searchResultDiv 56 | 57 | const handlers = { 58 | moveDown: this.onMoveDown, 59 | moveUp: this.onMoveUp, 60 | selectItem: this.onEnterSearchResult, 61 | } 62 | 63 | const messages = defineMessages({ 64 | searchPlaceholder: { 65 | id: 'Link.search.placeholder', 66 | defaultMessage: 'Search by keyword' 67 | } 68 | }) 69 | 70 | if (this.state.searchValue && this.state.searchResultValue) { 71 | _.each(this.state.searchResultValue, function(item, index) { 72 | if (item) { 73 | var associated = _.some(that.props.associtedLinks, function(link) { 74 | return link.id === item.id 75 | }) 76 | var rowCard = ( 77 | { 79 | that['searchCard' + index] = ref 80 | }} 81 | key={'card-item-show-' + index} 82 | onClick={associated ? null : that.onAssociateLink} 83 | displayValue={item} 84 | id={item.id} 85 | className={ 86 | associated 87 | ? 'relative pl1 bw1 b--silver br3 ma3 h-25 ba bg-light-gray' 88 | : that.state.focusedIndex === index 89 | ? 'relative pl1 bw1 b--silver br3 ma3 h-25 ba dim pointer shadow-1' 90 | : 'relative pl1 bw1 b--silver br3 ma3 h-25 ba dim pointer' 91 | } 92 | /> 93 | ) 94 | searchResults.push(rowCard) 95 | } 96 | }) 97 | searchResultDiv = ( 98 |
99 |
100 |

101 | {'Resultados' + 102 | (this.state.totalRows > this.state.searchResultValue.length 103 | ? ' (Mostrando 10 de' + this.state.totalRows + ')' 104 | : '')} 105 |

106 | {searchResults} 107 |
108 | ) 109 | } 110 | return ( 111 | 112 | 113 | { 115 | this.searchInput = ref 116 | }} 117 | type="search" 118 | className="ml3 w-90 h-100 bn" 119 | placeholder={ 120 | this.props.intl.formatMessage(messages.searchPlaceholder) 121 | } 122 | onKeyUp={this.handleSearchKeyUp} 123 | value={this.state.searchValue} 124 | onChange={this.handleSearchValueChange} 125 | /> 126 | {searchResultDiv} 127 | {this.state.searchValue.length > 0 ? 128 |
129 |

130 | 131 |

132 | 141 |
: null 142 | } 143 |
144 | ) 145 | } 146 | 147 | onMoveUp = () => { 148 | if ( 149 | this.state.searchValue && 150 | this.state.searchResultValue.length === 0 && 151 | this.state.focusedIndex === 0 152 | ) { 153 | this.setState({ focusedIndex: -1 }) 154 | return 155 | } else if (this.state.focusedIndex === -1) { 156 | this.props.onMoveUp() 157 | return 158 | } 159 | var index = this.state.focusedIndex - 1 160 | this.setState({ focusedIndex: index }) 161 | if (index > -1) { 162 | var className = this['searchCard' + index].props.className 163 | if (className === 'row-card-disabled') { 164 | this.onMoveUp() 165 | } 166 | } 167 | }; 168 | 169 | onMoveDown = () => { 170 | if (this.state.searchValue && this.state.searchResultValue.length === 0) { 171 | if (this.state.focusedIndex === -1) { 172 | this.setState({ focusedIndex: 0 }) 173 | } else { 174 | this.setState({ focusedIndex: -1 }) 175 | this.props.onMoveDown() 176 | } 177 | } else if ( 178 | !this.state.searchResultValue || 179 | this.state.searchResultValue.length === this.state.focusedIndex + 1 180 | ) { 181 | this.setState({ focusedIndex: -1 }) 182 | this.props.onMoveDown() 183 | } else { 184 | var index = this.state.focusedIndex + 1 185 | var className = this['searchCard' + index].props.className 186 | this.setState({ focusedIndex: index }) 187 | if (className === 'row-card-disabled') { 188 | this.onMoveDown() 189 | } 190 | } 191 | }; 192 | 193 | onEnterSearchResult = () => { 194 | this.props.onAssociateLink( 195 | this.state.searchResultValue[this.state.focusedIndex] 196 | ) 197 | }; 198 | 199 | onAssociateLink = id => { 200 | const item = _.find(this.state.searchResultValue, item => { 201 | return item.id === id 202 | }) 203 | this.props.onAssociateLink(item) 204 | }; 205 | 206 | handleAssociateValue = () => { 207 | this.props.onAssociateLink(this.state.searchValue) 208 | }; 209 | 210 | handleSearchKeyUp = e => { 211 | var regex = new RegExp('^[a-zA-Z0-9]+$') 212 | var str = String.fromCharCode(!e.charCode ? e.which : e.charCode) 213 | if ( 214 | regex.test(str) || e.charCode === 32 || e.which === 8 || e.charCode === 8 215 | ) { 216 | e.preventDefault() 217 | if (this.timeout) { 218 | clearTimeout(this.timeout) 219 | this.timeout = null 220 | } 221 | this.timeout = setTimeout( 222 | function() { 223 | this.onMakeSearch() 224 | }.bind(this), 225 | 1000 226 | ) 227 | } 228 | }; 229 | 230 | onMakeSearch = () => { 231 | if (!this.state.searchValue) { 232 | this.setState({ focusedIndex: -1, searchResultValue: null }) 233 | return 234 | } 235 | // var relatedModel = this.props.UIschema.models[this.props.context] 236 | // this.timeout = null 237 | // // Do the search 238 | // var filter = 239 | // '_keyword=*' + this.state.searchValue + '*' 240 | // Actions.documentLoadPage( 241 | // this.props.context, 242 | // relatedModel, 243 | // null, 244 | // 0, 245 | // 10, 246 | // filter 247 | // ) 248 | }; 249 | 250 | onSearchResult = (dataEntityName, docs, rowStart, totalRows) => { 251 | if (this.state.searchValue) { 252 | // var allItemsLoaded = totalRows < docs.length 253 | this.setState({ 254 | searchResultValue: docs, 255 | expandSearchResultPanel: true, 256 | totalRows, 257 | focusedIndex: -1, 258 | }) 259 | } 260 | }; 261 | 262 | handleSearchValueChange = e => { 263 | this.setState({ searchValue: e.target.value }) 264 | }; 265 | } 266 | 267 | LinkSearch.propTypes = { 268 | intl: intlShape.isRequired, 269 | linkedField: PropTypes.string, 270 | context: PropTypes.object, 271 | onMoveDown: PropTypes.func, 272 | onMoveUp: PropTypes.func, 273 | onAssociateLink: PropTypes.func, 274 | focusedDiv: PropTypes.string, 275 | up: PropTypes.bool, 276 | userTypedText: PropTypes.string, 277 | } 278 | 279 | export default injectIntl(LinkSearch) 280 | -------------------------------------------------------------------------------- /src/components/dynamic-controls/Link/LinksControl.react.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import PropTypes from 'prop-types' 4 | import LinkSearch from './LinkSearch.react' 5 | import { FormattedMessage } from 'react-intl' 6 | import { HotKeys } from 'react-hotkeys' 7 | import Card from './Card.react' 8 | import Icon from '../Common/Icon.react' 9 | import _ from 'underscore' 10 | 11 | class LinksControl extends React.Component { 12 | constructor(props) { 13 | super(props) 14 | this.state = { 15 | focusedIndex: -1, 16 | } 17 | } 18 | 19 | componentDidUpdate() { 20 | if (!this.state.focusedDiv) { 21 | if (window && document) { 22 | ReactDOM.findDOMNode(this.searchControl).focus() 23 | } 24 | this.setState({ focusedDiv: 'searchControl', focusedIndex: -1 }) 25 | } 26 | } 27 | 28 | render() { 29 | const allRowCards = [] 30 | _.each(this.props.value, (item, index) => { 31 | allRowCards.push( 32 | 43 | 55 | 64 | 65 | ) 66 | }) 67 | const associatedDivHandlers = { 68 | moveDown: this.handleMoveDown, 69 | moveUp: this.handleMoveUp, 70 | selectItem: this.handleOpenLink, 71 | delete: this.handleRemoveLink, 72 | } 73 | 74 | const addLinkdivHandlers = { 75 | moveDown: this.handleMoveDown, 76 | moveUp: this.handleMoveUp, 77 | selectItem: this.handleAddLink, 78 | } 79 | 80 | return ( 81 |
82 | { 84 | this.searchControl = div 85 | }} 86 | context={this.props.relatedContext} 87 | linkedField={this.props.linked_field} 88 | associtedLinks={this.props.value} 89 | onMoveDown={this.handleMoveDown} 90 | onMoveUp={this.handleMoveUp} 91 | onAssociateLink={this.handleAssociateLink} 92 | focusedDiv={this.state.focusedDiv} 93 | up={this.state.up} 94 | userTypedText={this.props.userTypedText} 95 | /> 96 |
97 |

98 | 99 |

100 | { 102 | this.associatedCards = ref 103 | }} 104 | handlers={associatedDivHandlers} 105 | onFocus={this.handleFocusAssociatedDiv} 106 | > 107 | {allRowCards} 108 | 109 |
110 | { 114 | this.addLink = ref 115 | }} 116 | > 117 | 128 | 129 |
130 | ) 131 | } 132 | 133 | handleFocusAssociatedDiv = () => { 134 | if (this.state.focusedDiv === 'associatedCards') { 135 | // se o controle perdeu o foco e esta voltando mas não foi selecionado outro div não mexe no focusedIndex 136 | return 137 | } else if (this.state.focusedDiv === 'addLink') { 138 | this.setState({ 139 | focusedIndex: this.props.value.length - 1, 140 | focusedDiv: 'associatedCards', 141 | }) 142 | } else { 143 | this.setState({ focusedIndex: 0, focusedDiv: 'associatedCards' }) 144 | } 145 | }; 146 | 147 | handleAddLink = () => { 148 | this.props.onAddLink() 149 | }; 150 | 151 | handleAssociateLink = item => { 152 | this.props.onAssociateLink(item) 153 | }; 154 | 155 | handleFocusAddLinkDiv = () => { 156 | this.setState({ focusedDiv: 'addLink', focusedIndex: null }) 157 | }; 158 | 159 | handleMoveDown = () => { 160 | switch (this.state.focusedDiv) { 161 | case 'addLink': 162 | if (window && document) { 163 | ReactDOM.findDOMNode(this.searchControl).focus() 164 | } 165 | this.setState({ 166 | focusedIndex: -1, 167 | focusedDiv: 'searchControl', 168 | up: false, 169 | }) 170 | break 171 | case 'searchControl': 172 | if (this.props.value.length === this.state.focusedIndex + 1) { 173 | if (window && document) { 174 | ReactDOM.findDOMNode(this.addLink).focus() 175 | } 176 | this.setState({ 177 | focusedIndex: -1, 178 | focusedDiv: 'addLink', 179 | }) 180 | } else { 181 | if (window && document) { 182 | ReactDOM.findDOMNode(this.associatedCards).focus() 183 | } 184 | this.setState({ focusedIndex: 0, focusedDiv: 'associatedCards' }) 185 | } 186 | break 187 | case 'associatedCards': 188 | if (this.props.value.length === this.state.focusedIndex + 1) { 189 | ReactDOM.findDOMNode(this.addLink).focus() 190 | this.setState({ 191 | focusedIndex: -1, 192 | focusedDiv: 'addLink', 193 | }) 194 | } else { 195 | this.setState({ focusedIndex: this.state.focusedIndex + 1 }) 196 | } 197 | } 198 | }; 199 | 200 | handleMoveUp = () => { 201 | switch (this.state.focusedDiv) { 202 | case 'addLink': 203 | if (this.props.value && this.props.value.length > 0) { 204 | if (window && document) { 205 | ReactDOM.findDOMNode(this.associatedCards).focus() 206 | } 207 | this.setState({ 208 | focusedIndex: this.props.value.length - 1, 209 | focusedDiv: 'associatedCards', 210 | }) 211 | } else { 212 | if (window && document) { 213 | ReactDOM.findDOMNode(this.searchControl).focus() 214 | } 215 | this.setState({ 216 | focusedIndex: -1, 217 | focusedDiv: 'searchControl', 218 | up: true, 219 | }) 220 | } 221 | break 222 | case 'searchControl': 223 | if (window && document) { 224 | ReactDOM.findDOMNode(this.addLink).focus() 225 | } 226 | this.setState({ focusedIndex: -1, focusedDiv: 'addLink' }) 227 | break 228 | case 'associatedCards': 229 | if (this.state.focusedIndex === 0) { 230 | if (window && document) { 231 | ReactDOM.findDOMNode(this.searchControl).focus() 232 | } 233 | this.setState({ 234 | focusedIndex: -1, 235 | focusedDiv: 'searchControl', 236 | up: true, 237 | }) 238 | } else { 239 | this.setState({ focusedIndex: this.state.focusedIndex - 1 }) 240 | } 241 | } 242 | }; 243 | 244 | handleOpenLink = () => { 245 | var item = this.props.value[this.state.focusedIndex] 246 | this.props.onOpenLink(item.id) 247 | }; 248 | 249 | handleRemoveLink = () => { 250 | var item = this.props.value[this.state.focusedIndex] 251 | this.props.onRemoveLink(item.id) 252 | }; 253 | } 254 | 255 | LinksControl.propTypes = { 256 | children: PropTypes.node, 257 | onAddLink: PropTypes.func, 258 | onOpenLink: PropTypes.func, 259 | onRemoveLink: PropTypes.func, 260 | onAssociateLink: PropTypes.func, 261 | linked_field: PropTypes.string, 262 | relatedContext: PropTypes.object, 263 | value: PropTypes.array, 264 | userTypedText: PropTypes.string, 265 | } 266 | 267 | export default LinksControl 268 | -------------------------------------------------------------------------------- /src/components/dynamic-controls/MultiOptions/ListOption.react.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | 4 | const ListOption = function(props) { 5 | return ( 6 | 7 | {props.item} 8 | 9 | ) 10 | } 11 | ListOption.propTypes = { 12 | item: PropTypes.string, 13 | } 14 | export default ListOption 15 | -------------------------------------------------------------------------------- /src/components/dynamic-controls/MultiOptions/Multioptions.react.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import PropTypes from 'prop-types' 4 | import Multiselect from 'react-widgets/lib/Multiselect' 5 | import { HotKeys } from 'react-hotkeys' 6 | import SelectedItem from './SelectedItem.react' 7 | import ListOption from './ListOption.react' 8 | 9 | class MultiOptions extends React.Component { 10 | constructor(props) { 11 | super(props) 12 | this.state = { 13 | open: false, 14 | } 15 | } 16 | componentDidUpdate(prevProps) { 17 | if (this.props.isEditing && !prevProps.isEditing) { 18 | if (window && document) { 19 | ReactDOM.findDOMNode(this.multiSelect).focus() 20 | } 21 | } 22 | } 23 | render() { 24 | const multiSelectHandlers = { 25 | stageChanges: this.handleEnter, 26 | moveDown: this.onCancelKey, 27 | moveUp: this.onCancelKey, 28 | moveLeft: this.onCancelKey, 29 | moveRight: this.onCancelKey, 30 | } 31 | const border = this.props.renderType === 'cell' 32 | ? `bw1 ba ${this.props.hasError ? 'b--red ' : 'b--blue '}` 33 | : `bw1 ba ${this.props.hasError ? 'b--red br3 ' : 'b--silver br3 '}` 34 | var control = null 35 | var value = this.props.value ? this.props.value : [] 36 | if (this.props.isEditing || this.props.isFocus) { 37 | control = ( 38 | 46 | { 49 | this.multiSelect = ref 50 | }} 51 | 52 | open={this.state.open} 53 | onToggle={this.handleToggle} 54 | data={this.props.items.enum} 55 | tagComponent={SelectedItem} 56 | itemComponent={ListOption} 57 | value={value} 58 | onChange={this.handleChange} 59 | /> 60 | 61 | ) 62 | } else { 63 | control = ( 64 |
71 | {value.length ? value.join(', ') : ''} 72 |
73 | ) 74 | } 75 | return control 76 | } 77 | onCancelKey = () => {}; 78 | handleChange = val => { 79 | this.props.setChange(val) 80 | }; 81 | handleToggle = isOpen => { 82 | if (this.props.isEditing) { 83 | this.setState({ open: isOpen }) 84 | } 85 | }; 86 | handleClick = () => { 87 | if (!this.props.isEditing) { 88 | this.props.onEditCell() 89 | } 90 | }; 91 | handleEnter = e => { 92 | if (!this.props.isEditing) { 93 | this.props.onEditCell() 94 | } else { 95 | if (!this.state.open) { 96 | e.stopPropagation() 97 | this.setState({ open: true }) 98 | } 99 | } 100 | }; 101 | handleCancelKey = () => {}; 102 | } 103 | MultiOptions.propTypes = { 104 | hasError: PropTypes.bool, 105 | renderType: PropTypes.string, 106 | value: PropTypes.array, 107 | onEditCell: PropTypes.func, 108 | setChange: PropTypes.func, 109 | isEditing: PropTypes.bool, 110 | items: PropTypes.object, 111 | isFocus: PropTypes.bool, 112 | } 113 | 114 | MultiOptions.defaultProps = { 115 | value: [], 116 | } 117 | 118 | export default MultiOptions 119 | -------------------------------------------------------------------------------- /src/components/dynamic-controls/MultiOptions/SelectedItem.react.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | 4 | const SelectedItem = function(props) { 5 | return ( 6 | 7 | {props.item} 8 | 9 | ) 10 | } 11 | SelectedItem.propTypes = { 12 | item: PropTypes.string, 13 | } 14 | export default SelectedItem 15 | -------------------------------------------------------------------------------- /src/components/dynamic-controls/ObjectControl/ObjectControl.react.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDom from 'react-dom' 3 | import PropTypes from 'prop-types' 4 | import { HotKeys } from 'react-hotkeys' 5 | import ControlFactory from '../ControlFactory.react' 6 | import _ from 'underscore' 7 | 8 | class ObjectControl extends React.Component { 9 | componentDidUpdate() { 10 | if (this.props.isEditing && this.props.renderType === 'cell') { 11 | if (window && document) { 12 | ReactDom.findDOMNode(this).focus() 13 | } 14 | } 15 | } 16 | render() { 17 | const className = this.mountClassName() 18 | const handlers = { 19 | moveUp: this.onArrow, 20 | moveDown: this.onArrow, 21 | moveRight: this.onArrow, 22 | moveLeft: this.onArrow, 23 | } 24 | const control = this.props.isEditing 25 | ? ( 30 |
31 | {this.renderObjectProperties()} 32 |
33 |
) 34 | : (
35 |
36 | {this.props.value 37 | ? this.renderObjectValues(this.props.value, this.props.properties) 38 | : ''} 39 |
40 |
) 41 | return control 42 | } 43 | handleClick = e => { 44 | e.stopPropagation() 45 | }; 46 | 47 | handleCloseModal = () => {}; 48 | setChange = () => { 49 | this.props.setChange(this.state.value) 50 | }; 51 | 52 | renderObjectValues = (value, schema) => { 53 | if (!value || !schema) { 54 | return 55 | } 56 | let stringValue = '' 57 | Object.keys(value).forEach(key => { 58 | if (schema[key]) { 59 | if (value[key] && typeof value[key] === 'object') { 60 | const children = this.renderObjectValues( 61 | value[key], 62 | schema[key].properties 63 | ) 64 | stringValue += `${JSON.stringify(schema[key].title || key)}:${children}, ` 65 | } else { 66 | stringValue += `${JSON.stringify(schema[key].title || key)}:${JSON.stringify(value[key])}, ` 67 | } 68 | } 69 | }) 70 | return stringValue 71 | }; 72 | 73 | renderObjectProperties = () => { 74 | const itemsToRender = [] 75 | const schemaProps = this.props.properties 76 | Object.keys(schemaProps).forEach(key => { 77 | const propertyProps = schemaProps[key] 78 | const props = { ...this.props, ...propertyProps } 79 | props.fieldName = key 80 | // Clean the children of the parent control 81 | props.children = null 82 | props.path = `${this.props.path}.${key}` 83 | props.field = this.props.fieldName 84 | props.isEditing = true 85 | props.key = `${this.props.fieldName}.${key}.${this.props.id}` 86 | props.value = this.props.value ? this.props.value[key] : null 87 | props.id = this.props.id 88 | props.validationErrors = this.props.validationErrors 89 | ? _.filter(this.props.validationErrors, error => { 90 | return error.dataPath.includes(`${this.props.path}.${key}`) 91 | }) 92 | : [] 93 | props.setChanges = this.setPropertychange 94 | props.renderType = 'form' 95 | 96 | itemsToRender.push( 97 | 103 | ) 104 | itemsToRender.push() 105 | }) 106 | return itemsToRender 107 | }; 108 | 109 | setPropertychange = (id, propChanges) => { 110 | const newValue = { ...this.props.value } 111 | Object.keys(propChanges).map(prop => { 112 | newValue[prop] = propChanges[prop].value 113 | }) 114 | this.props.setChange(newValue) 115 | }; 116 | 117 | mountClassName = () => { 118 | const borderWidth = this.props.isEditing 119 | ? this.props.renderType === 'cell' ? 'bw1' : 'bw1' 120 | : this.props.isFocus ? 'bw1 flex' : 'flex' 121 | const border = this.props.isEditing 122 | ? this.props.renderType === 'cell' 123 | ? `ba ${this.props.hasError ? 'b--red ' : 'b--blue '}` 124 | : `ba ${this.props.hasError ? 'b--red br3 ' : 'b--silver br3'}` 125 | : this.props.isFocus 126 | ? `ba ${this.props.hasError ? 'b--red ' : 'b--blue '} pl05` 127 | : this.props.hasError ? 'ba b--red pl05' : 'pl2' 128 | const backGroundColor = this.props.isEditing 129 | ? 'bg-white' 130 | : this.props.isFocus ? 'bg-lightest-blue' : '' 131 | const height = this.props.isEditing ? '' : 'h-inherit' 132 | const zIndex = this.props.isEditing ? 'z-3' : 'z-2' 133 | return `w-100 ${height} ${backGroundColor} ${zIndex} ${border} ${borderWidth}` 134 | }; 135 | 136 | onArrow() {} 137 | } 138 | 139 | ObjectControl.propTypes = { 140 | path: PropTypes.string, 141 | validationErrors: PropTypes.array, 142 | hasError: PropTypes.bool, 143 | children: PropTypes.node, 144 | field: PropTypes.string, 145 | value: PropTypes.object, 146 | setChange: PropTypes.func, 147 | setChanges: PropTypes.func, 148 | isEditing: PropTypes.bool, 149 | isSelected: PropTypes.bool, 150 | isFocus: PropTypes.bool, 151 | renderType: PropTypes.string, 152 | properties: PropTypes.object, 153 | fieldName: PropTypes.string, 154 | id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), 155 | } 156 | 157 | export default ObjectControl 158 | -------------------------------------------------------------------------------- /src/components/dynamic-controls/PropertyControlHOC.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | 4 | const propertyControl = (WrappedComponent, setChanges) => class 5 | extends React.Component { 6 | static propTypes = { 7 | field: PropTypes.string, 8 | fieldName: PropTypes.string, 9 | id: PropTypes.string, 10 | }; 11 | setPropertychange = (id, propChanges) => { 12 | const changes = {} 13 | changes[this.props.field] = {} 14 | Object.keys(propChanges).map(prop => { 15 | const propValue = {} 16 | propValue[prop] = propChanges[prop].value 17 | changes[this.props.field] = { value: propValue } 18 | }) 19 | setChanges(this.props.id, changes) 20 | }; 21 | 22 | render() { 23 | const props = Object.assign({}, this.props) 24 | props.setChanges = this.setPropertychange 25 | return 26 | } 27 | } 28 | 29 | export default propertyControl 30 | -------------------------------------------------------------------------------- /src/components/dynamic-controls/TextArea.react.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { HotKeys } from 'react-hotkeys' 3 | import PropTypes from 'prop-types' 4 | 5 | class TextArea extends React.Component { 6 | constructor(props) { 7 | super(props) 8 | this.state = { 9 | value: props.value, 10 | } 11 | } 12 | 13 | componentWillReceiveProps(nextProps) { 14 | if (this.props.isEditing && !nextProps.isEditing && !nextProps.isFocus) { 15 | this.handleBlur() 16 | } 17 | if ( 18 | this.props.isFocus && 19 | typeof nextProps.userTypedText !== 'undefined' && 20 | nextProps.userTypedText !== null 21 | ) { 22 | this.props.onEditCell() 23 | } 24 | this.setState({ value: nextProps.value }) 25 | } 26 | 27 | componentDidUpdate(prevProps) { 28 | if (this.props.isEditing) { 29 | this.refTextArea.focus() 30 | if ( 31 | !prevProps.isEditing && 32 | typeof prevProps.userTypedText !== 'undefined' && 33 | prevProps.userTypedText !== null 34 | ) { 35 | this.setState({ value: prevProps.userTypedText }) 36 | } 37 | } 38 | } 39 | 40 | render() { 41 | const handlers = { 42 | moveUp: this.onArrow, 43 | moveDown: this.onArrow, 44 | moveRight: this.onArrow, 45 | moveLeft: this.onArrow, 46 | exitEdit: this.onEscape, 47 | } 48 | const borderColor = this.props.hasError ? 'b--red' : 'b--blue' 49 | const formBorderColor = this.props.hasError ? 'b--red' : 'b--silver' 50 | 51 | var control = null 52 | if (this.props.isEditing) { 53 | control = ( 54 | 55 |