├── .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 | [](https://www.npmjs.com/package/react-modern-library-boilerplate) [](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 |
68 | )
69 | } else {
70 | control = (
71 | {
80 | this.refTextArea = ref
81 | }}
82 | onDoubleClick={this.props.onEditCell}
83 | onKeyDown={this.handleKeyDown}
84 | >
85 |
{this.state.value}
86 |
87 | )
88 | }
89 | return control
90 | }
91 |
92 | handleBlur = () => {
93 | if (this.props.value !== this.state.value) {
94 | this.props.setChange(this.state.value)
95 | }
96 | }
97 |
98 | onEscape = () => {
99 | if (this.props.isEditing) {
100 | if (this.props.value !== this.state.value) {
101 | this.props.setChange(this.state.value)
102 | }
103 | if (this.props.onExitEditCell) {
104 | this.props.onExitEditCell(this.props.cell)
105 | }
106 | }
107 | }
108 |
109 | onArrow = () => {}
110 |
111 | handleChange = e => {
112 | this.setState({ value: e.target.value })
113 | }
114 | }
115 |
116 | TextArea.propTypes = {
117 | hasError: PropTypes.bool,
118 | renderType: PropTypes.string,
119 | value: PropTypes.string,
120 | isEditing: PropTypes.bool,
121 | isFocus: PropTypes.bool,
122 | cell: PropTypes.object,
123 | onExitEditCell: PropTypes.func,
124 | onEditCell: PropTypes.func,
125 | onExitEdit: PropTypes.func,
126 | setChange: PropTypes.func,
127 | userTypedText: PropTypes.string,
128 | }
129 |
130 | export default TextArea
131 |
--------------------------------------------------------------------------------
/src/components/dynamic-controls/TextBox.react.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { HotKeys } from 'react-hotkeys'
3 | import PropTypes from 'prop-types'
4 |
5 | var MASK_REGEX = {
6 | '9': /\d/,
7 | A: /[A-Za-z\u0410-\u044f\u0401\u0451\xc0-\xff\xb5]/,
8 | '*': /[\dA-Za-z\u0410-\u044f\u0401\u0451\xc0-\xff\xb5]/,
9 | }
10 |
11 | var MASK_CHARS = Object.keys(MASK_REGEX)
12 | var PTRN_REGEX = new RegExp(`[${MASK_CHARS.join(',')}]`, 'g')
13 |
14 | class TextBox extends React.Component {
15 | constructor(props) {
16 | super(props)
17 | const type = this.props.type === ('number' || 'integer') ? 'number' : 'text'
18 | this.state = { type: type }
19 | }
20 |
21 | componentDidUpdate(prevProps) {
22 | if (this.props.isEditing && !prevProps.isEditing) {
23 | this.refTextBox.focus()
24 | this.refTextBox.setSelectionRange(this.mask.cursor, this.mask.cursor)
25 | if (
26 | typeof prevProps.userTypedText !== 'undefined' &&
27 | prevProps.userTypedText !== null
28 | ) {
29 | this.processValue(prevProps.userTypedText, this.props.isEditing)
30 | }
31 | }
32 | if (this.props.mask && this.props.isEditing) {
33 | this.refTextBox.focus()
34 | this.refTextBox.setSelectionRange(this.mask.cursor, this.mask.cursor)
35 | }
36 | // if (this.props.isEditing) {
37 | // console.log('TextBoxFocus')
38 | // this.refTextBox.focus()
39 | // if (this.props.mask) {
40 | // this.refTextBox.focus()
41 | // this.refTextBox.setSelectionRange(this.mask.cursor, this.mask.cursor)
42 | // }
43 | // if (
44 | // !prevProps.isEditing &&
45 | // typeof prevProps.userTypedText !== 'undefined' &&
46 | // prevProps.userTypedText !== null
47 | // ) {
48 | // this.processValue(prevProps.userTypedText, this.props.isEditing)
49 | // }
50 | // }
51 | }
52 |
53 | componentWillReceiveProps(nextProps) {
54 | if (this.props.isEditing && !nextProps.isEditing && !nextProps.isFocus) {
55 | this.handleBlur()
56 | }
57 | if (
58 | this.props.isFocus &&
59 | typeof nextProps.userTypedText !== 'undefined' &&
60 | nextProps.userTypedText !== null
61 | ) {
62 | this.props.onEditCell()
63 | }
64 | this.processValue(nextProps.value, nextProps.isEditing)
65 | }
66 |
67 | componentWillMount() {
68 | // Calcula o pattern de visualização e define o regex de validação de cada caracter da mascara
69 | var pattern, mask, rexps
70 | if (this.props.mask) {
71 | mask = this.props.mask
72 | pattern = mask.replace(PTRN_REGEX, '_')
73 | rexps = {}
74 | mask.split('').forEach(function(c, i) {
75 | if (MASK_CHARS.indexOf(c) !== -1) {
76 | rexps[i + 1] = MASK_REGEX[c]
77 | }
78 | })
79 | }
80 | this.mask = {
81 | cursor: 0,
82 | pattern: pattern,
83 | mask: mask,
84 | rexps: rexps,
85 | }
86 | this.processValue(this.props.value, this.props.isEditing)
87 | }
88 |
89 | render() {
90 | const handlers = {
91 | stageChanges: this.onEnter,
92 | moveUp: this.onArrow,
93 | moveDown: this.onArrow,
94 | moveRight: this.onArrow,
95 | moveLeft: this.onArrow,
96 | exitEdit: this.onExitEdit,
97 | }
98 | var control = null
99 | const borderColor = this.props.hasError ? 'b--red' : 'b--blue'
100 | const formBorderColor = this.props.hasError ? 'b--red' : 'b--silver'
101 | if (this.props.isEditing) {
102 | var error = null
103 | control = (
104 |
112 | {
117 | this.refTextBox = ref
118 | }}
119 | onChange={this.handleChange}
120 | onClick={this.handleInputClick}
121 | onBlur={this.handleBlur}
122 | />
123 | {error}
124 |
125 | )
126 | } else {
127 | control = (
128 |
138 |
{this.state.maskedValue}
139 |
140 | )
141 | }
142 |
143 | return control
144 | }
145 |
146 | processValue(initialValue, isEditing) {
147 | // o initialValue pode ser passado mascarado ou não, por isso são criadas duas variaves
148 | // newValue que contem o dado sem mascara e newMaskedValue que contem o valor mascaado
149 | var value = initialValue || ''
150 | var newValue = this.props.mask ? '' : value
151 | var newMaskedValue = this.props.mask ? '' : value
152 | var cursorMax = newValue.length
153 |
154 | if (this.props.mask && value) {
155 | var mask = this.mask.mask
156 | var cursorMin = 0
157 | var nextChar
158 |
159 | for (var i = 0, j = 0; i < mask.length;) {
160 | // Se o caracter estiver presente na mascara mas não esta na lista de caracteres validos
161 | // coloca o valor da mascara no valor
162 | if (MASK_CHARS.indexOf(mask[i]) === -1) {
163 | newMaskedValue += mask[i]
164 | if (mask[i] === value[j]) {
165 | j++
166 | }
167 | i++
168 | } else {
169 | nextChar = value.substr(j++, 1)
170 | if (cursorMin === 0) {
171 | cursorMin = i
172 | }
173 | if (nextChar) {
174 | if (this.mask.rexps[newMaskedValue.length + 1].test(nextChar)) {
175 | newMaskedValue += nextChar
176 | newValue += nextChar
177 | cursorMax = newMaskedValue.length
178 | i++
179 | }
180 | } else {
181 | newMaskedValue = newMaskedValue.substr(0, cursorMax)
182 | if (isEditing) {
183 | newMaskedValue += this.mask.pattern.slice(cursorMax)
184 | }
185 | break
186 | }
187 | }
188 | }
189 | cursorMax = Math.max(cursorMax, cursorMin)
190 | }
191 | this.setState({ maskedValue: newMaskedValue, value: newValue })
192 | this.mask.cursor = cursorMax
193 | }
194 |
195 | handleBlur = () => {
196 | console.log('textBoxBlur')
197 | if (!this.state.value && !this.props.value) return
198 | if (this.props.value !== this.state.value) {
199 | const value =
200 | this.props.type === 'integer' || this.props.type === 'number'
201 | ? Number(this.state.value)
202 | : this.state.value
203 | this.props.setChange(value)
204 | }
205 | }
206 |
207 | handleEdit = ev => {
208 | this.props.onEditCell(ev)
209 | }
210 |
211 | handleChange = e => {
212 | this.processValue(e.target.value, this.props.isEditing)
213 | }
214 |
215 | onEnter = () => {
216 | if (this.props.isEditing) {
217 | this.handleBlur()
218 | if (this.props.onExitEditCell) {
219 | this.props.onExitEditCell(this.props.cell)
220 | }
221 | }
222 | }
223 |
224 | handleInputClick = () => {
225 | this.refTextBox.focus()
226 | }
227 |
228 | onExitEdit = () => {
229 | this.setState({ value: this.props.value })
230 | if (this.props.onExitEditCell) {
231 | this.props.onExitEditCell(this.props.cell)
232 | }
233 | }
234 |
235 | onArrow = () => {}
236 | }
237 |
238 | TextBox.propTypes = {
239 | hasError: PropTypes.bool,
240 | renderType: PropTypes.string,
241 | errorMessage: PropTypes.string,
242 | userTypedText: PropTypes.string,
243 | mask: PropTypes.string,
244 | isFocus: PropTypes.bool,
245 | isEditing: PropTypes.bool,
246 | value: PropTypes.any,
247 | onExitEditCell: PropTypes.func,
248 | onEditCell: PropTypes.func,
249 | onExitEdit: PropTypes.func,
250 | setChange: PropTypes.func,
251 | cell: PropTypes.object,
252 | type: PropTypes.string,
253 | }
254 |
255 | TextBox.defaultProps = {
256 | value: '',
257 | }
258 |
259 | export default TextBox
260 |
--------------------------------------------------------------------------------
/src/components/endlessTable/views/Cell.react.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDom from 'react-dom'
3 | import PropTypes from 'prop-types'
4 | import ControlFactory from '../../dynamic-controls/ControlFactory.react'
5 | import { HotKeys } from 'react-hotkeys'
6 | import FillHandle from './FillHandle.react'
7 |
8 | class Cell extends React.Component {
9 | constructor(props) {
10 | super(props)
11 | this.state = { userTypedText: null }
12 | }
13 | shouldComponentUpdate(nextProps, nextState) {
14 | return (
15 | this.props.isFocus !== nextProps.isFocus ||
16 | this.props.isEditing !== nextProps.isEditing ||
17 | this.props.value !== nextProps.value ||
18 | this.props.isSelected !== nextProps.isSelected ||
19 | this.props.isFillHandleSelected !== nextProps.isFillHandleSelected ||
20 | nextState.userTypedText !== this.state.userTypedText ||
21 | this.props.isFillHandleCell !== nextProps.isFillHandleCell
22 | )
23 | }
24 |
25 | componentWillReceiveProps() {
26 | this.setState({ userTypedText: null })
27 | }
28 |
29 | componentDidUpdate() {
30 | if (this.props.isFocus && !this.props.isEditing) {
31 | if (window && document) {
32 | ReactDom.findDOMNode(this.cellControl).focus()
33 | }
34 | }
35 | }
36 |
37 | render() {
38 | const fillHandle =
39 | this.props.isFillHandleCell && !this.props.isEditing ? (
40 |
41 | ) : null
42 | const sharedProps = {
43 | show: this.props.isFocus && this.props.validationErrors.length > 0,
44 | target: () => this.cellControl,
45 | }
46 | const popover = (
47 |
55 |
59 |
60 |
61 | {this.getI18nStr('Cell.validation.error')}
62 |
63 | {/*
*/}
72 |
73 | {this.props.validationErrors.length > 0
74 | ? this.props.validationErrors[0].message
75 | : ''}
76 |
77 |
78 |
79 |
80 | )
81 | return (
82 | {
98 | this.cellControl = ref
99 | }}
100 | id={`row${this.props.cell.row}col${this.props.cell.col}`}
101 | >
102 | {
110 | this.controlFactory = ref
111 | }}
112 | >
113 | {fillHandle}
114 |
115 | {popover}
116 |
117 | )
118 | }
119 |
120 | handleFocus = () => {
121 | if (this.props.onFocusCell && !this.props.isFocus) {
122 | this.props.onFocusCell(this.props.cell)
123 | }
124 | }
125 |
126 | handleKeyPress = e => {
127 | if (this.props.isEditing) return
128 | var regex = new RegExp('^[a-zA-Z0-9]+$')
129 | var str = String.fromCharCode(!e.charCode ? e.which : e.charCode)
130 | if (regex.test(str) || e.charCode === 32) {
131 | e.preventDefault()
132 | this.setState({ userTypedText: str })
133 | }
134 | }
135 |
136 | handleKeyDown = e => {
137 | if (this.props.isEditing) return
138 | if (e.which === 8 || e.charCode === 8) {
139 | e.preventDefault()
140 | this.setState({ userTypedText: '' })
141 | }
142 | }
143 |
144 | handleMouseEnter = () => {
145 | if (this.props.onSelectCell) {
146 | this.props.onSelectCell(this.props.cell)
147 | }
148 | }
149 |
150 | handleEditCell = () => {
151 | if (!this.props.isEditing && this.props.onEditCell) {
152 | this.props.onEditCell(this.props.cell)
153 | }
154 | }
155 |
156 | handleExitEditCell = () => {
157 | if (this.props.onExitEditCell) {
158 | this.props.onExitEditCell(this.props.cell)
159 | }
160 | }
161 |
162 | getI18nStr(id) {
163 | return this.context.intl.formatMessage({ id: id })
164 | }
165 | }
166 | Cell.contextTypes = {
167 | intl: PropTypes.object.isRequired,
168 | }
169 |
170 | Cell.propTypes = {
171 | validationErrors: PropTypes.array,
172 | columnsCount: PropTypes.number,
173 | value: PropTypes.any,
174 | isEditing: PropTypes.bool,
175 | isFocus: PropTypes.bool,
176 | isSelected: PropTypes.bool,
177 | isFillHandleCell: PropTypes.bool,
178 | isFillHandleSelected: PropTypes.bool,
179 | cell: PropTypes.object,
180 | id: PropTypes.string,
181 | onSelectCell: PropTypes.func,
182 | onFocusCell: PropTypes.func,
183 | onEditCell: PropTypes.func,
184 | onExitEditCell: PropTypes.func,
185 | onFillHandleDown: PropTypes.func,
186 | width: PropTypes.number,
187 | }
188 |
189 | export default Cell
190 |
--------------------------------------------------------------------------------
/src/components/endlessTable/views/FillHandle.react.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 |
4 | const FillHandle = function(props) {
5 | return (
6 |
19 | )
20 | }
21 |
22 | FillHandle.propTypes = {
23 | onMouseDown: PropTypes.func,
24 | }
25 |
26 | export default FillHandle
27 |
--------------------------------------------------------------------------------
/src/components/endlessTable/views/Footer.react.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import {FormattedMessage} from 'react-intl'
4 |
5 | const Footer = function(props) {
6 | return (
7 |
9 | {props.totalRows}
10 |
11 | )
12 | }
13 |
14 | Footer.propTypes = {
15 | totalRows: PropTypes.number,
16 | }
17 |
18 | export default Footer
19 |
--------------------------------------------------------------------------------
/src/components/endlessTable/views/Header.react.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 |
4 | class Header extends React.Component {
5 | constructor(props) {
6 | super(props)
7 | this.state = {
8 | isHoveringIndexHeaderCell: false
9 | }
10 | this.shouldComponentUpdate = this.shouldComponentUpdate.bind(this)
11 | }
12 |
13 | shouldComponentUpdate(nextProps) {
14 | var shouldUpdate = false
15 |
16 | shouldUpdate = this.props.context !== nextProps.context
17 | if (shouldUpdate) {
18 | return shouldUpdate
19 | }
20 |
21 | shouldUpdate = this.props.isChecking !== nextProps.isChecking
22 | if (shouldUpdate) {
23 | return shouldUpdate
24 | }
25 |
26 | shouldUpdate =
27 | this.props.children &&
28 | nextProps.children &&
29 | this.props.children.length !== nextProps.children.length
30 |
31 | // return shouldUpdate
32 | return true
33 | }
34 |
35 | render() {
36 | const { isHoveringIndexHeaderCell } = this.state
37 | const { checkedItems, items } = this.props
38 | const areAllRowsChecked = !!(checkedItems.length >= items.length)
39 |
40 | return (
41 |
45 |
46 |
this.setState({ isHoveringIndexHeaderCell: true })}
50 | onMouseLeave={() => this.setState({ isHoveringIndexHeaderCell: false })}
51 | >
52 |
53 |
60 |
61 |
62 | {this.props.children}
63 |
64 |
65 | )
66 | }
67 |
68 | handleClearCheckedRows = () => {
69 | this.props.onCheckRowChange(null, false)
70 | }
71 |
72 | handleCheckAllRows = () => {
73 | this.props.onCheckRowChange(null, true)
74 | }
75 |
76 | getInlineStyle = () => {
77 | var style = {}
78 | var scrollLeft = this.props.scrollLeft
79 | if (scrollLeft) {
80 | style.left = `${scrollLeft * -1}px`
81 | }
82 | return style
83 | }
84 | }
85 | Header.propTypes = {
86 | context: PropTypes.object,
87 | onCheckRowChange: PropTypes.func,
88 | scrollLeft: PropTypes.number,
89 | isChecking: PropTypes.bool,
90 | children: PropTypes.any,
91 | }
92 |
93 | export default Header
94 |
--------------------------------------------------------------------------------
/src/components/endlessTable/views/HeaderCell.react.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 |
4 | class HeaderCell extends React.Component {
5 | constructor(props) {
6 | super(props)
7 | this._getInlineStyle = this._getInlineStyle.bind(this)
8 | this.handleSort = this.handleSort.bind(this)
9 | this.handleSortAsc = this.handleSortAsc.bind(this)
10 | this.handleSortDesc = this.handleSortDesc.bind(this)
11 | this.state = { sort: 'ASC', sortIcon: 'asc', inactive: true }
12 | }
13 |
14 | render() {
15 | return (
16 |
22 |
{this.props.value.props.children}
23 | {this.props.isIndex && this.props.type !== 'object' ? (
24 | this.state.inactive ? (
25 |
26 |
27 |
33 |
34 |
35 |
41 |
42 |
43 | ) : (
44 |
45 |
53 |
54 | )
55 | ) : null}
56 |
57 | )
58 | }
59 | handleSortAsc() {
60 | this.props.onHandleSort(this.props.fieldName, 'ASC')
61 | this.setState({ sort: 'ASC', sortIcon: 'asc', inactive: false })
62 | }
63 | handleSortDesc() {
64 | this.props.onHandleSort(this.props.fieldName, 'DESC')
65 | this.setState({ sort: 'DESC', sortIcon: 'desc', inactive: false })
66 | }
67 |
68 | handleSort() {
69 | if (this.state.sort === 'ASC') {
70 | this.props.onHandleSort(this.props.fieldName, 'DESC')
71 | this.setState({ sort: 'DESC', sortIcon: 'desc', inactive: false })
72 | } else {
73 | this.props.onHandleSort(this.props.fieldName, 'ASC')
74 | this.setState({ sort: 'ASC', sortIcon: 'asc', inactive: false })
75 | }
76 | }
77 |
78 | _getInlineStyle() {
79 | var style = {}
80 | if (this.props.width) {
81 | style.width = `${this.props.width}px`
82 | style.maxWidth = `${this.props.width}px`
83 | style.height = '35px'
84 | }
85 |
86 | return style
87 | }
88 | }
89 |
90 | HeaderCell.propTypes = {
91 | width: PropTypes.any,
92 | value: PropTypes.object,
93 | isIndex: PropTypes.bool,
94 | fieldName: PropTypes.string,
95 | onHandleSort: PropTypes.func,
96 | type: PropTypes.string,
97 | index: PropTypes.number,
98 | lastCellIndex: PropTypes.number,
99 | }
100 |
101 | export default HeaderCell
102 |
--------------------------------------------------------------------------------
/src/components/endlessTable/views/Row.react.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDom from 'react-dom'
3 | import _ from 'underscore'
4 | import { HotKeys } from 'react-hotkeys'
5 | import PropTypes from 'prop-types'
6 | import Cell from './Cell.react'
7 |
8 | class Row extends React.Component {
9 | constructor(props) {
10 | super(props)
11 |
12 | this.state = {
13 | isHoveringIndexCell: false,
14 | }
15 | }
16 |
17 | shouldComponentUpdate(nextProps, nextState) {
18 | if (this.props.columns !== nextProps.columns) return true
19 | if (this.state.isHoveringIndexCell !== nextState.isHoveringIndexCell) return true
20 | if (
21 | (nextProps.focusedCell &&
22 | this.props.item.virtualID === nextProps.focusedCell.row) ||
23 | (this.props.focusedCell &&
24 | this.props.item.virtualID === this.props.focusedCell.row)
25 | ) {
26 | return true
27 | }
28 | if (
29 | (nextProps.selectionRange &&
30 | nextProps.selectionRange.cellA &&
31 | nextProps.selectionRange.cellB &&
32 | this.isRowInSelectionRange(
33 | this.props.item.virtualID,
34 | nextProps.selectionRange.cellA.row,
35 | nextProps.selectionRange.cellB.row
36 | )) ||
37 | (this.props.selectionRange &&
38 | this.props.selectionRange.cellA &&
39 | this.props.selectionRange.cellB &&
40 | this.isRowInSelectionRange(
41 | this.props.item.virtualID,
42 | this.props.selectionRange.cellA.row,
43 | this.props.selectionRange.cellB.row
44 | ))
45 | ) {
46 | return true
47 | }
48 | if (
49 | (nextProps.selectionFillHandleRange &&
50 | nextProps.selectionFillHandleRange.cellA &&
51 | nextProps.selectionFillHandleRange.cellB &&
52 | this.isRowInSelectionRange(
53 | this.props.item.virtualID,
54 | nextProps.selectionFillHandleRange.cellA.row,
55 | nextProps.selectionFillHandleRange.cellB.row
56 | )) ||
57 | (this.props.selectionFillHandleRange &&
58 | nextProps.selectionFillHandleRange.cellA &&
59 | nextProps.selectionFillHandleRange.cellB &&
60 | this.isRowInSelectionRange(
61 | this.props.item.virtualID,
62 | this.props.selectionFillHandleRange.cellA.row,
63 | this.props.selectionFillHandleRange.cellB.row
64 | ))
65 | ) {
66 | return true
67 | }
68 | var shouldUpdate = false
69 | var currentProps = this.props
70 | var attrs = _.filter(_.allKeys(nextProps.item.document), function (key) {
71 | return key[0] !== '_'
72 | })
73 | shouldUpdate =
74 | !shouldUpdate &&
75 | _.some(attrs, function (key) {
76 | return (
77 | (nextProps.item.document && !currentProps.item.document) ||
78 | (!nextProps.item.document && currentProps.item.document) ||
79 | nextProps.item.document[key] !== currentProps.item.document[key]
80 | )
81 | })
82 | shouldUpdate =
83 | shouldUpdate || !_.isEqual(currentProps.item.focus, nextProps.item.focus)
84 | shouldUpdate =
85 | shouldUpdate || currentProps.isChecking !== nextProps.isChecking
86 | shouldUpdate =
87 | shouldUpdate || currentProps.item.isChecked !== nextProps.item.isChecked
88 | shouldUpdate =
89 | shouldUpdate || currentProps.item.status !== nextProps.item.status
90 | return shouldUpdate
91 | }
92 |
93 | isRowInSelectionRange = (row, rowA, rowB) => {
94 | var min = Math.min(rowA, rowB)
95 | var max = Math.max(rowA, rowB)
96 | return row >= min && row <= max
97 | }
98 |
99 | isCellInSelectionRange = (cell, cellA, cellB) => {
100 | if (!cell || !cellA || !cellB) return false
101 | var minRow = Math.min(cellA.row, cellB.row)
102 | var maxRow = Math.max(cellA.row, cellB.row)
103 | var minCol = Math.min(cellA.col, cellB.col)
104 | var maxCol = Math.max(cellA.col, cellB.col)
105 | return (
106 | cell.col >= minCol &&
107 | cell.col <= maxCol &&
108 | cell.row >= minRow &&
109 | cell.row <= maxRow
110 | )
111 | }
112 |
113 | render() {
114 | var item = this.props.item
115 | var itemDocument = item.document
116 | var virtualID = item.virtualID
117 | var columns = this.props.columns || []
118 | var selectionRange = this.props.selectionRange
119 | var fillHandleCell =
120 | selectionRange.cellA && selectionRange.cellB
121 | ? {
122 | row: Math.max(selectionRange.cellA.row, selectionRange.cellB.row),
123 | col: Math.max(selectionRange.cellA.col, selectionRange.cellB.col),
124 | }
125 | : null
126 | if (!itemDocument) {
127 | itemDocument = {}
128 | }
129 | const handlers = {
130 | editCell: this.onEnter,
131 | exitEdit: this.onEscape,
132 | rename: this.onEnter,
133 | openForm: this.onOpenForm,
134 | checkRow: this.onSpace,
135 | removeRow: this.handleRemove,
136 | }
137 | return (
138 | {
140 | this.rowlist = input
141 | }}
142 | handlers={handlers}
143 | className="flex relative no-underline mv0"
144 | style={{ height: '44px' }}
145 | >
146 | this.setState({ isHoveringIndexCell: true })}
148 | onMouseLeave={() => this.setState({ isHoveringIndexCell: false })}
149 | className="flex items-center justify-center bg-near-white bl br bb b--silver"
150 | style={{ minWidth: '50px', height: '44px' }} >
151 |
155 | {
157 | this.rowCheckBox = input
158 | }}
159 | type="checkbox"
160 | onChange={this.handleCheckRow}
161 | checked={item.isChecked || false}
162 | />
163 |
164 |
168 | {new Intl.NumberFormat().format(virtualID + 1)}
169 |
170 |
171 | {columns.map((column, colIndex) => {
172 | const props = this.createPropsObject(
173 | column,
174 | columns.length,
175 | item,
176 | colIndex,
177 | fillHandleCell
178 | )
179 | return (
180 | |
185 | )
186 | })}
187 |
188 | )
189 | }
190 |
191 | createPropsObject = (
192 | column,
193 | columnsCount,
194 | item,
195 | colIndex,
196 | fillHandleCell
197 | ) => {
198 | return _.extend(column, {
199 | columnsCount: columnsCount,
200 | path: `.${column.fieldName}`,
201 | validationErrors: item.validationErrors
202 | ? _.filter(item.validationErrors, error => {
203 | return (
204 | error.dataPath.includes(`.${column.fieldName}[`) ||
205 | error.dataPath.includes(`.${column.fieldName}.`) ||
206 | error.dataPath === `.${column.fieldName}`
207 | )
208 | })
209 | : [],
210 | width: column.width,
211 | cell: { row: item.virtualID, col: colIndex },
212 | value: item.document[column.fieldName],
213 | linkedValue: document[`${column.fieldName}_linked`],
214 | id: item.document.id,
215 | onSelectCell: this.props.onSelectCell,
216 | onFillHandleDown: this.props.onFillHandleDown,
217 | onFillHandleUp: this.props.onFillHandleUp,
218 | onExitEditCell: this.onExitEditCell,
219 | onFocusCell: this.onFocusCell,
220 | onEditCell: this.props.onEditCell,
221 | showLabel: false,
222 | isSelected:
223 | this.props.selectionRange &&
224 | this.isCellInSelectionRange(
225 | { row: item.virtualID, col: colIndex },
226 | this.props.selectionRange.cellA,
227 | this.props.selectionRange.cellB
228 | ),
229 | isFillHandleSelected:
230 | this.props.selectionFillHandleRange &&
231 | this.isCellInSelectionRange(
232 | { row: item.virtualID, col: colIndex },
233 | this.props.selectionFillHandleRange.cellA,
234 | this.props.selectionFillHandleRange.cellB
235 | ),
236 | isFillHandleCell:
237 | fillHandleCell &&
238 | item.virtualID === fillHandleCell.row &&
239 | colIndex === fillHandleCell.col,
240 | isFocus:
241 | this.props.focusedCell &&
242 | this.props.focusedCell.row === item.virtualID &&
243 | this.props.focusedCell.col === colIndex,
244 | isEditing:
245 | this.props.editingCell &&
246 | this.props.editingCell.row === item.virtualID &&
247 | this.props.editingCell.col === colIndex,
248 | })
249 | }
250 |
251 | handleCheckRow = (ev) => {
252 | this.props.onCheckRowChange(this.props.item.document.id, ev.target.checked)
253 | }
254 |
255 | handleEdit = () => {
256 | this.props.onEditItem(this.props.item)
257 | }
258 |
259 | handleRemove = () => {
260 | this.props.onRemove(this.props.item, this.props.context, this.onFormClose)
261 | }
262 |
263 | onFocusCell = (cell) => {
264 | this.props.onFocusCell(cell)
265 | }
266 |
267 | onEditCell = (cell) => {
268 | this.props.onEditCell(cell)
269 | }
270 |
271 | onExitEditCell = (cell) => {
272 | this.props.onExitEditCell(cell)
273 | }
274 |
275 | onEnter = () => {
276 | this.onEditCell(this.props.focusedCell)
277 | }
278 |
279 | onEscape = () => {
280 | this.onExitEditCell(this.props.editingCell)
281 | }
282 |
283 | onFormClose = () => {
284 | // ReactDom.findDOMNode(this).focus()
285 | if (this.focus) {
286 | this.focus()
287 | } else { console.log('No this to focus on Row.react.js') }
288 | }
289 |
290 | onOpenForm = () => {
291 | // como os componentes filhos não implementam esta key, é possivél abrir um formulario com uma celula em modo de edição
292 | // Por isso antes de abrir o formulario vamos validar que não tem celulas em modo de edição
293 | if (!this.props.editingCell) {
294 | this.props.onEdit(this.props.item, this.props.context, this.onFormClose)
295 | }
296 | }
297 |
298 | onSpace = () => {
299 | var checked
300 | if (window && document) { // wat
301 | checked = ReactDom.findDOMNode(this.rowCheckBox).checked
302 | }
303 | this.props.onCheckRowChange(this.props.item.document.id, !checked)
304 | }
305 | }
306 |
307 | Row.propTypes = {
308 | item: PropTypes.object,
309 | context: PropTypes.object,
310 | onCheckRowChange: PropTypes.func,
311 | onEditCell: PropTypes.func,
312 | onExitEditCell: PropTypes.func,
313 | onEdit: PropTypes.func,
314 | onEditItem: PropTypes.func,
315 | onSelectCell: PropTypes.func,
316 | onFocusCell: PropTypes.func,
317 | onRemove: PropTypes.func,
318 | setChanges: PropTypes.func,
319 | columns: PropTypes.array,
320 | isChecking: PropTypes.bool,
321 | renderValue: PropTypes.any,
322 | focusedCell: PropTypes.object,
323 | editingCell: PropTypes.object,
324 | selectionRange: PropTypes.object,
325 | selectionFillHandleRange: PropTypes.object,
326 | onFillHandleDown: PropTypes.func,
327 | onFillHandleUp: PropTypes.func,
328 | }
329 |
330 | export default Row
331 |
--------------------------------------------------------------------------------
/src/components/endlessTable/views/Table.react.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | import { HotKeys } from 'react-hotkeys'
4 | import Rows from './Rows.react'
5 | import Header from './Header.react'
6 | import HeaderCell from './HeaderCell.react'
7 | import PropTypes from 'prop-types'
8 |
9 | class Table extends React.Component {
10 | constructor(props) {
11 | super(props)
12 | this.state = {
13 | scrollDiv: null,
14 | }
15 | }
16 |
17 | handleScroll = () => {
18 | if (window && document) {
19 | var header = ReactDOM.findDOMNode(this.header)
20 | var scrollDiv = ReactDOM.findDOMNode(this.scrollDiv)
21 | header.style.left = `-${scrollDiv.scrollLeft}px`
22 | }
23 | }
24 |
25 | handleScrollVertically = pixels => {
26 | if (window && document) {
27 | ReactDOM.findDOMNode(this.scrollDiv).scrollTop += pixels
28 | }
29 | }
30 |
31 | render() {
32 | var columns = []
33 |
34 | Object.keys(this.props.schema.properties).forEach(key => {
35 | if (this.props.hiddenFields.includes(key)) {
36 | return
37 | }
38 | columns.push(
39 | Object.assign({}, { fieldName: key }, this.props.schema.properties[key])
40 | )
41 | })
42 |
43 | return (
44 |
45 |
46 | {
51 | this.header = ref
52 | }}
53 | >
54 | {this.getHeader()}
55 |
56 |
57 |
{
62 | this.scrollDiv = div
63 | !this.state.scrollDiv && this.setState({ scrollDiv: div })
64 | }}
65 | >
66 | {this.state.scrollDiv ? (
67 |
68 |
76 |
77 | ) : null // loader
78 | }
79 |
80 |
81 | )
82 | }
83 |
84 | scrollToEnd = () => {
85 | var scrollDiv = this.scrollDiv
86 | scrollDiv.scrollTop = scrollDiv.scrollHeight
87 | }
88 |
89 | handleFetchItems = skip => {
90 | this.props.onFetchItems(
91 | this.props.context,
92 | this.props.UIschema.fields,
93 | skip,
94 | this.props.fetchSize || 100,
95 | this.props.where,
96 | this.props.sort
97 | )
98 | }
99 |
100 | getHeader() {
101 | const that = this
102 | const header = []
103 | const schema = this.props.schema
104 | if (schema) {
105 | Object.keys(schema.properties).forEach((key, index) => {
106 | if (this.props.hiddenFields.includes(key)) {
107 | return
108 | }
109 | var fieldDef = schema.properties[key]
110 | var label = (
111 |
112 | {/* */}
113 | {fieldDef.title || key}
114 |
115 | )
116 | if (!fieldDef.width) {
117 | fieldDef.width = 200
118 | }
119 | header.push(
120 |
129 | )
130 | })
131 | }
132 |
133 | return header
134 | }
135 | }
136 |
137 | Table.propTypes = {
138 | onEditItem: PropTypes.func,
139 | context: PropTypes.object.isRequired,
140 | schema: PropTypes.object.isRequired,
141 | UIschema: PropTypes.object.isRequired,
142 | onCheckRow: PropTypes.func,
143 | fetchSize: PropTypes.number,
144 | where: PropTypes.string,
145 | sort: PropTypes.string,
146 | onFetchItems: PropTypes.func.isRequired,
147 | hiddenFields: PropTypes.array,
148 | }
149 |
150 | export default Table
151 |
--------------------------------------------------------------------------------
/src/components/endlessTable/views/VirtualList.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | import utils from './utils'
4 | import PropTypes from 'prop-types'
5 |
6 | export default class VirtualList extends React.Component {
7 | constructor(props) {
8 | super(props)
9 | this.state = {
10 | items: props.items || [],
11 | bufferStart: 0,
12 | height: 0,
13 | }
14 | this.onScroll = this.onScroll.bind(this)
15 | }
16 |
17 | getVirtualState(props) {
18 | // default values
19 | var state = {
20 | items: props.items || [],
21 | bufferStart: 0,
22 | height: 0,
23 | }
24 |
25 | // early return if nothing to render
26 | if (
27 | typeof props.container === 'undefined' ||
28 | props.items.length === 0 ||
29 | props.itemHeight <= 0
30 | ) {
31 | return state
32 | }
33 |
34 | var items = props.items
35 |
36 | state.height = props.items.length * props.itemHeight + props.itemHeight * 5
37 |
38 | var viewBox = this.viewBox(props)
39 |
40 | // no space to render
41 | if (viewBox.height <= 0) {
42 | return state
43 | }
44 |
45 | viewBox.top = utils.viewTop(props.container)
46 | viewBox.bottom = viewBox.top + viewBox.height
47 |
48 | var listBox = this.listBox(props)
49 |
50 | var renderStats = VirtualList.getItems(
51 | viewBox,
52 | listBox,
53 | props.itemHeight,
54 | items.length,
55 | props.itemBuffer
56 | )
57 |
58 | // no items to render
59 | if (renderStats.itemsInView.length === 0) {
60 | return state
61 | }
62 |
63 | state.items = items.slice(
64 | renderStats.firstItemIndex,
65 | renderStats.lastItemIndex + 1
66 | )
67 | state.bufferStart = renderStats.firstItemIndex * props.itemHeight
68 |
69 | return state
70 | }
71 |
72 | shouldComponentUpdate(nextProps, nextState) {
73 | if (this.state.bufferStart !== nextState.bufferStart) {
74 | return true
75 | }
76 |
77 | if (this.state.height !== nextState.height) {
78 | return true
79 | }
80 |
81 | var equal = utils.areArraysEqual(this.state.items, nextState.items)
82 |
83 | return !equal
84 | }
85 | viewBox(nextProps) {
86 | return (this.view = this.view || this._getViewBox(nextProps))
87 | }
88 | _getViewBox(nextProps) {
89 | return {
90 | height: typeof nextProps.container.innerHeight !== 'undefined'
91 | ? nextProps.container.innerHeight
92 | : nextProps.container.clientHeight,
93 | }
94 | }
95 | _getListBox(nextProps) {
96 | var list
97 | if (window && document) { // wat
98 | list = ReactDOM.findDOMNode(this)
99 | }
100 |
101 | var top = list ? utils.topDifference(list, nextProps.container) : 0
102 |
103 | var height = nextProps.itemHeight * nextProps.items.length
104 |
105 | return {
106 | top: top,
107 | height: height,
108 | bottom: top + height,
109 | }
110 | }
111 | listBox(nextProps) {
112 | return (this.list = this.list || this._getListBox(nextProps))
113 | }
114 | componentWillReceiveProps(nextProps) {
115 | // clear caches
116 | this.view = this.list = null
117 |
118 | var state = this.getVirtualState(nextProps)
119 |
120 | this.props.container.removeEventListener('scroll', this.onScrollDebounced)
121 |
122 | this.onScrollDebounced = utils.debounce(
123 | this.onScroll,
124 | nextProps.scrollDelay,
125 | false
126 | )
127 |
128 | nextProps.container.addEventListener('scroll', this.onScrollDebounced)
129 |
130 | this.setState(state)
131 | }
132 | componentWillMount() {
133 | this.onScrollDebounced = utils.debounce(
134 | this.onScroll,
135 | this.props.scrollDelay,
136 | false
137 | )
138 | }
139 | componentDidMount() {
140 | var state = this.getVirtualState(this.props)
141 |
142 | this.setState(state)
143 |
144 | this.props.container.addEventListener('scroll', this.onScrollDebounced)
145 | }
146 | componentWillUnmount() {
147 | this.props.container.removeEventListener('scroll', this.onScrollDebounced)
148 |
149 | this.view = this.list = null
150 | }
151 | onScroll() {
152 | var state = this.getVirtualState(this.props)
153 |
154 | this.setState(state)
155 | }
156 |
157 | // in case you need to get the currently visible items
158 | visibleItems() {
159 | return this.state.items
160 | }
161 | render() {
162 | console.log('rendering virtual list with this state:', this.state)
163 | return (
164 |
171 | {this.state.items.map(this.props.renderItem)}
172 |
173 | )
174 | }
175 | }
176 |
177 | VirtualList.propTypes = {
178 | renderItem: PropTypes.func,
179 | container: PropTypes.any,
180 | scrollDelay: PropTypes.number,
181 | }
182 |
183 | VirtualList.getBox = function getBox(view, list) {
184 | list.height = list.height || list.bottom - list.top
185 |
186 | return {
187 | top: Math.max(0, Math.min(view.top - list.top)),
188 | bottom: Math.max(0, Math.min(list.height, view.bottom - list.top)),
189 | }
190 | }
191 |
192 | VirtualList.getItems = function(
193 | viewBox,
194 | listBox,
195 | itemHeight,
196 | itemCount,
197 | itemBuffer
198 | ) {
199 | if (itemCount === 0 || itemHeight === 0) {
200 | return {
201 | itemsInView: 0,
202 | }
203 | }
204 |
205 | // list is below viewport
206 | if (viewBox.bottom < listBox.top) {
207 | return {
208 | itemsInView: 0,
209 | }
210 | }
211 |
212 | // list is above viewport
213 | if (viewBox.top > listBox.bottom) {
214 | return {
215 | itemsInView: 0,
216 | }
217 | }
218 |
219 | var listViewBox = VirtualList.getBox(viewBox, listBox)
220 |
221 | var firstItemIndex = Math.max(
222 | 0,
223 | Math.floor(listViewBox.top / itemHeight) - itemBuffer
224 | )
225 | var lastItemIndex =
226 | Math.min(
227 | itemCount,
228 | Math.ceil(listViewBox.bottom / itemHeight) + itemBuffer
229 | ) - 1
230 |
231 | var itemsInView = lastItemIndex - firstItemIndex + 1
232 |
233 | var result = {
234 | firstItemIndex: firstItemIndex,
235 | lastItemIndex: lastItemIndex,
236 | itemsInView: itemsInView,
237 | }
238 |
239 | return result
240 | }
241 | VirtualList.propTypes = {
242 | items: PropTypes.array.isRequired,
243 | itemHeight: PropTypes.number.isRequired,
244 | renderItem: PropTypes.func.isRequired,
245 | container: PropTypes.object.isRequired,
246 | tagName: PropTypes.string.isRequired,
247 | scrollDelay: PropTypes.number,
248 | itemBuffer: PropTypes.number,
249 | }
250 | VirtualList.defaultProps = {
251 | container: typeof window !== 'undefined' ? window : undefined,
252 | tagName: 'div',
253 | scrollDelay: 0,
254 | itemBuffer: 0,
255 | }
256 |
--------------------------------------------------------------------------------
/src/components/endlessTable/views/utils/index.js:
--------------------------------------------------------------------------------
1 | function areArraysEqual(a, b) {
2 | if (!a || !b) {
3 | return false
4 | }
5 |
6 | if (a.length !== b.length) {
7 | return false
8 | }
9 |
10 | for (var i = 0, length = a.length; i < length; i++) {
11 | if (a[i] !== b[i]) {
12 | return false
13 | }
14 | }
15 |
16 | return true
17 | }
18 |
19 | function topFromWindow(element) {
20 | if (!element || element === window) {
21 | return 0
22 | }
23 |
24 | return element.offsetTop + topFromWindow(element.offsetParent)
25 | }
26 |
27 | function topDifference(element, container) {
28 | return topFromWindow(element) - topFromWindow(container)
29 | }
30 |
31 | function viewTop(element) {
32 | var viewTop
33 | if (element === window) {
34 | viewTop = window.pageYOffset
35 | if (!viewTop && document) {
36 | viewTop = document.documentElement.scrollTop
37 | }
38 | if (!viewTop && document) {
39 | viewTop = document.body.scrollTop
40 | }
41 | } else {
42 | viewTop = element.scrollY
43 | if (!viewTop) {
44 | viewTop = element.scrollTop
45 | }
46 | }
47 | return !viewTop ? 0 : viewTop
48 | }
49 |
50 | function debounce(func, wait, immediate) {
51 | if (!wait) {
52 | return func
53 | }
54 |
55 | var timeout
56 |
57 | return function() {
58 | var context = this
59 | var args = arguments
60 |
61 | var later = function() {
62 | timeout = null
63 |
64 | if (!immediate) {
65 | func.apply(context, args)
66 | }
67 | }
68 |
69 | var callNow = immediate && !timeout
70 |
71 | clearTimeout(timeout)
72 | timeout = setTimeout(later, wait)
73 |
74 | if (callNow) {
75 | func.apply(context, args)
76 | }
77 | }
78 | }
79 |
80 | module.exports = {
81 | areArraysEqual: areArraysEqual,
82 | topDifference: topDifference,
83 | topFromWindow: topFromWindow,
84 | viewTop: viewTop,
85 | debounce: debounce,
86 | }
87 |
--------------------------------------------------------------------------------
/src/example.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import ReactDOM from 'react-dom'
3 | import { hot } from 'react-hot-loader'
4 | import faker from 'faker'
5 |
6 | import JsonSchemaTable from './index.js'
7 |
8 | const schema = {
9 | properties: {
10 | name: {
11 | type: 'string',
12 | title: 'Name',
13 | },
14 | email: {
15 | type: 'string',
16 | format: 'email',
17 | title: 'Email',
18 | },
19 | birthdate: {
20 | type: 'string',
21 | format: 'date-time',
22 | },
23 | address: {
24 | type: 'object',
25 | title: 'Address',
26 | properties: {
27 | street: {
28 | type: 'string',
29 | title: 'Street',
30 | },
31 | postalcode: {
32 | type: 'string',
33 | title: 'PostalCode',
34 | },
35 | number: {
36 | type: 'number',
37 | title: 'Number',
38 | },
39 | },
40 | },
41 | isActive: {
42 | type: 'boolean',
43 | title: 'Active',
44 | },
45 | },
46 | }
47 |
48 | const UIschema = {
49 | title: 'Users',
50 | fields: {
51 | name: {
52 | width: 200,
53 | },
54 | email: {
55 | width: 300,
56 | },
57 | address: {
58 | width: 300,
59 | },
60 | birthdate: {
61 | width: 300,
62 | },
63 | isActive: {
64 | width: 300,
65 | },
66 | },
67 | list: ['email', 'name', 'birthdate', 'address', 'isActive'],
68 | editor: {
69 | settings: {
70 | sections: [
71 | {
72 | name: 'Personal Data',
73 | fields: [
74 | 'name',
75 | 'email',
76 | 'birthdate',
77 | 'address',
78 | 'isActive',
79 | ],
80 | },
81 | ],
82 | },
83 | },
84 | }
85 |
86 | const fakeData = new Array(50).fill(true).map((item, index) => {
87 | return {
88 | document: {
89 | email: faker.internet.email().toLowerCase(),
90 | name: faker.name.findName(),
91 | birthdate: faker.date.past(),
92 | isActive: faker.random.boolean(),
93 | address: {
94 | street: faker.address.streetName(),
95 | postalcode: faker.address.zipCode(),
96 | number: faker.random.number(),
97 | }
98 | },
99 | }
100 | })
101 |
102 | class App extends Component {
103 | render() {
104 | return (
105 |
106 |
{
109 | console.log('save this staging documents:', docs)
110 | }}
111 | checkedItemsCallback={docs => {
112 | console.log('delete this checked documents:', docs)
113 | }}
114 | items={fakeData}
115 | context={{}}
116 | UIschema={UIschema}
117 | >
118 | Children prop
119 |
120 |
121 | )
122 | }
123 | }
124 |
125 | export default hot(module)(App)
126 |
127 | ReactDOM.render(, document.getElementById('app'))
128 |
--------------------------------------------------------------------------------
/src/i18n/en-US_messages.json:
--------------------------------------------------------------------------------
1 | {
2 | "view.handleSaveAll.addNotification.message": "Existem documentos inválidos. A gravação não pode ser realizada",
3 | "ConfirmAlert.title": "Warning",
4 | "ConfirmAlert.footer.yes": "Yes",
5 | "ConfirmAlert.footer.or": "or",
6 | "ConfirmAlert.footer.no": "No",
7 | "Attachments.modal.title": "Attachments",
8 | "Link.searchResults.not.found": "No results have been found",
9 | "Link.links": "Links",
10 | "Link.associate.new.entry": "Associate with new record",
11 | "Link.associate.just.value": "Set value without link",
12 | "Link.links.with": "Links with",
13 | "Array.add.new.item": "Add new item",
14 | "Array.items.count": "Items Count",
15 | "Cell.validation.error": "Validation Error",
16 | "ControlFactory.validateValue.error.invalid": "The field value is invalid, pattern:",
17 | "ControlFactory.validateValue.error.required": "The field is required",
18 | "Footer.records": "Records",
19 | "Filter.add.filter": "Add filter",
20 | "Filter.filter": "Filter",
21 | "Form.historic": "History",
22 | "FloatToolbar.renderCancelStagingConfirmation.message.title": "All unsaved and unsaved changes will be lost!",
23 | "FloatToolbar.renderCancelStagingConfirmation.message.subtitle": "Do you really want to cancel this operation?",
24 | "FloatToolbar._getCancelImportConfirmAlert.message.title": "When you cancel, all imported data will be discarded!",
25 | "FloatToolbar._getCancelImportConfirmAlert.message.subtitle": "Do you really want to cancel this operation?",
26 | "FloatToolbar._getSavingMessage.modal.title": "Saving Changes…",
27 | "FloatToolbar._getSavingMessage.modal.subtitle": "Please wait while your changes are synchronized with the MasterData.",
28 | "FixedToolbar.isImport.message": "You are in DATA IMPORTATION mode. The records below were pasted from the 'Clipboard'.",
29 | "FixedToolbar.new.line": "New line",
30 | "FixedToolbar.save": "Save",
31 | "FixedToolbar.saving": "Saving...",
32 | "FixedToolbar.renderCancelStagingConfirmation.message.title": "All unsaved and unsaved changes will be lost!",
33 | "FixedToolbar.renderCancelStagingConfirmation.message.subtitle": "Do you really want to cancel this operation?",
34 | "ColumnsToShow.show.all": "View all",
35 | "Main.input.placeholder": "Add filter",
36 | "FixedToolbar.ColumnsToShow.columns": "Hide columns",
37 | "FixedToolbar.StateFilters.filters": "State Filters",
38 | "FixedToolbar.StateFilters.checked": "Checked",
39 | "FixedToolbar.StateFilters.edited": "Edited",
40 | "FixedToolbar.StateFilters.invalid": "Invalid",
41 | "FixedToolbar.download": "Download",
42 | "FixedToolbar.deleteChecked": "Delete Checked",
43 | "FixedToolbar.undo": "Reset Changes",
44 | "Contextualmenu.copy": "Copy",
45 | "Contextualmenu.paste": "Paste",
46 | "LogicalOperator.and": "AND",
47 | "LogicalOperator.or": "OR",
48 | "ComparisonOperator.contains": "Contains",
49 | "ComparisonOperator.between": "Between",
50 | "Home.apps": "Applications"
51 | }
52 |
--------------------------------------------------------------------------------
/src/i18n/es-AR_messages.json:
--------------------------------------------------------------------------------
1 | {
2 | "view.handleSaveAll.addNotification.message": "Existem documentos inválidos. A gravação não pode ser realizada",
3 | "ConfirmAlert.title": "Atención",
4 | "ConfirmAlert.footer.yes": "Sí",
5 | "ConfirmAlert.footer.or": "o",
6 | "ConfirmAlert.footer.no": "No",
7 | "Attachments.modal.title": "Anexos",
8 | "Link.searchResults.not.found": "Resultados no encontrados",
9 | "Link.links": "Links",
10 | "Link.associate.new.entry": "Asociar con Nuevo Registro",
11 | "Link.links.with": "Links con",
12 | "Array.add.new.item": "Adicionar elemento",
13 | "Array.items.count": "Cantidad de Elementos",
14 | "Cell.validation.error": "Error de Validación",
15 | "ControlFactory.validateValue.error.invalid": "El valor del campo es invalido, pattern:",
16 | "ControlFactory.validateValue.error.required": "El campo es requerido",
17 | "Footer.records": "Registros",
18 | "Filter.add.filter": "Añadir filtro",
19 | "Filter.filter": "Filtrar",
20 | "Form.historic": "Historia",
21 | "FloatToolbar.renderCancelStagingConfirmation.message.title": "¡Todos los cambios realizados y no guardados se perderán!",
22 | "FloatToolbar.renderCancelStagingConfirmation.message.subtitle": "¿Desea realmente cancelar esta operación?",
23 | "FloatToolbar._getCancelImportConfirmAlert.message.title": "Al cancelar, todos los datos importados serán descartados!",
24 | "FloatToolbar._getCancelImportConfirmAlert.message.subtitle": "¿Desea realmente cancelar esta operación?",
25 | "FloatToolbar._getSavingMessage.modal.title": "Guardar los cambios…",
26 | "FloatToolbar._getSavingMessage.modal.subtitle": "Espere mientras los cambios se sincronizan con el MasterData.",
27 | "FixedToolbar.isImport.message": "Usted está en el modo de IMPORTACIÓN DE DATOS. Los siguientes registros se pegaron desde el 'Portapapeles'.",
28 | "FixedToolbar.new.line": "Nueva Línea",
29 | "FixedToolbar.save": "Guardar",
30 | "FixedToolbar.saving": "Guardando...",
31 | "FixedToolbar.renderCancelStagingConfirmation.message.title": "¡Todos los cambios realizados y no guardados se perderán!",
32 | "FixedToolbar.renderCancelStagingConfirmation.message.subtitle": "¿Desea realmente cancelar esta operación?",
33 | "ColumnsToShow.show.all": "Ver todas",
34 | "Main.input.placeholder": "Añadir filtro",
35 | "FixedToolbar.ColumnsToShow.columns": "Ocultar columnas",
36 | "FixedToolbar.StateFilters.filters": "Filtros de estado",
37 | "FixedToolbar.StateFilters.checked": "Marcados",
38 | "FixedToolbar.StateFilters.edited": "Editados",
39 | "FixedToolbar.StateFilters.invalid": "Invalidos",
40 | "FixedToolbar.download": "Download",
41 | "FixedToolbar.deleteChecked": "Delete Checked",
42 | "FixedToolbar.undo": "Undo Changes",
43 | "Contextualmenu.copy": "Copiar",
44 | "Contextualmenu.paste": "Pegar",
45 | "LogicalOperator.and": "Y",
46 | "LogicalOperator.or": "O",
47 | "ComparisonOperator.contains": "Contiene",
48 | "ComparisonOperator.between": "Entre",
49 | "home.apps": "Aplicaciones"
50 | }
51 |
--------------------------------------------------------------------------------
/src/i18n/pt-BR_messages.json:
--------------------------------------------------------------------------------
1 | {
2 | "view.handleSaveAll.addNotification.message": "Existem documentos inválidos. A gravação não pode ser realizada",
3 | "ConfirmAlert.title": "Atenção",
4 | "ConfirmAlert.footer.yes": "Sim",
5 | "ConfirmAlert.footer.or": "ou",
6 | "ConfirmAlert.footer.no": "Não",
7 | "Attachments.modal.title": "Anexos",
8 | "Link.searchResults.not.found": "Resultados Não Encontrados",
9 | "Link.links": "Links",
10 | "Link.associate.new.entry": "Associar com Novo Registro",
11 | "Link.links.with": "Links com",
12 | "Array.add.new.item": "Adicionar item",
13 | "Array.items.count": "Quantidade de Itens",
14 | "Cell.validation.error": "Erro de Validação",
15 | "ControlFactory.validateValue.error.invalid": "O valor do campo é invalido, pattern:",
16 | "ControlFactory.validateValue.error.required": "O campo é obrigatorio",
17 | "Footer.records": "Registros",
18 | "Filter.add.filter": "Adicionar filtro",
19 | "Filter.filter": "Filtrar",
20 | "Form.historic": "Histórico",
21 | "FloatToolbar.renderCancelStagingConfirmation.message.title": "Todas as alterações realizadas e não salvas serão perdidas!",
22 | "FloatToolbar.renderCancelStagingConfirmation.message.subtitle": "Deseja realmente cancelar essa operação?",
23 | "FloatToolbar._getCancelImportConfirmAlert.message.title": "Ao cancelar, todos os dados importados serão descartados!",
24 | "FloatToolbar._getCancelImportConfirmAlert.message.subtitle": "Deseja realmente cancelar essa operação?",
25 | "FloatToolbar._getSavingMessage.modal.title": "Salvando as alterações…",
26 | "FloatToolbar._getSavingMessage.modal.subtitle": "Aguarde enquanto as alterações são sincronizadas com o MasterData.",
27 | "FixedToolbar.isImport.message": "Você está no modo de IMPORTAÇÃO DE DADOS. Os registros abaixo foram colados a partir da 'Área de transferência'.",
28 | "FixedToolbar.new.line": "Nova Linha",
29 | "FixedToolbar.save": "Salvar",
30 | "FixedToolbar.saving": "Salvando...",
31 | "FixedToolbar.renderCancelStagingConfirmation.message.title": "Todas as alterações realizadas e não salvas serão perdidas!",
32 | "FixedToolbar.renderCancelStagingConfirmation.message.subtitle": "Deseja realmente cancelar essa operação?",
33 | "ColumnsToShow.show.all": "Exibir todas",
34 | "Main.input.placeholder": "Adicionar filtro",
35 | "FixedToolbar.ColumnsToShow.columns": "Esconder colunas",
36 | "FixedToolbar.StateFilters.filters": "Filtros de estado",
37 | "FixedToolbar.StateFilters.checked": "Marcados",
38 | "FixedToolbar.StateFilters.edited": "Editados",
39 | "FixedToolbar.StateFilters.invalid": "Invalidos",
40 | "FixedToolbar.download": "Download",
41 | "FixedToolbar.deleteChecked": "Deletar marcadas",
42 | "FixedToolbar.undo": "Desfazer mudanças",
43 | "Contextualmenu.copy": "Copiar",
44 | "Contextualmenu.paste": "Colar",
45 | "LogicalOperator.and": "E",
46 | "LogicalOperator.or": "OU",
47 | "ComparisonOperator.contains": "Contém",
48 | "ComparisonOperator.between": "Entre",
49 | "Home.apps": "Aplicações"
50 | }
51 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import { HotKeys } from 'react-hotkeys'
4 | import keyMap from 'utils/KeyMap'
5 | import 'utils/icons'
6 | import 'vtex-tachyons'
7 | import { Provider } from 'react-redux'
8 | import { IntlProvider } from 'react-intl'
9 |
10 | import { SetFetcher } from 'actions/FetcherWrapper'
11 | import configureStore from 'redux/configureStore'
12 | import ToolBarContainer from 'toolBar/containers/ToolBarContainer'
13 | import TableContainer from 'table/containers/TableContainer'
14 | import FormContainer from 'table/containers/FormContainer'
15 | import enUSMessages from 'i18n/en-US_messages.json'
16 | import { undo, redo, receiveItemsFromProps } from 'actions/items-actions'
17 |
18 | const { store } = configureStore()
19 |
20 | class JsonSchemaTable extends React.Component {
21 | constructor(props) {
22 | super(props)
23 | SetFetcher(props.fetcher)
24 | if (props.items && props.items.length > 0) {
25 | // initial items load
26 | store.dispatch(receiveItemsFromProps(props.items))
27 | }
28 | }
29 |
30 | componentDidUpdate() {
31 | // more items received by props (not the final solution)
32 | // To do: fix 'getMoreItems'
33 | store.dispatch(receiveItemsFromProps(this.props.items))
34 | }
35 |
36 | render() {
37 | const { schema } = this.props
38 |
39 | if (!schema || Object.keys(schema).length === 0) {
40 | return jsonschema table cannot render without a jsonschema!
41 | }
42 |
43 | const lang = 'en' // To do: i18 for realz
44 |
45 | const handleUndo = () => {
46 | store.dispatch(undo(schema, lang))
47 | }
48 |
49 | const handleRedo = () => {
50 | store.dispatch(redo(schema, lang))
51 | }
52 |
53 | const handlers = {
54 | undo: handleUndo,
55 | redo: handleRedo,
56 | }
57 |
58 | return (
59 |
60 |
61 |
62 |
71 | {this.props.children}
72 | {
74 | this.table = ref
75 | }}
76 | context={this.props.context}
77 | UIschema={this.props.UIschema}
78 | schema={this.props.schema}
79 | fetchSize={this.props.fetchSize}
80 | lang={lang}
81 | />
82 |
88 |
89 |
90 |
91 | )
92 | }
93 | handleGetNotLoadedDocument = () => {}
94 | }
95 |
96 | JsonSchemaTable.propTypes = {
97 | fetcher: PropTypes.object,
98 | UIschema: PropTypes.object,
99 | schema: PropTypes.object,
100 | context: PropTypes.object,
101 | fetchSize: PropTypes.number,
102 | lang: PropTypes.string,
103 | stagingItemsCallback: PropTypes.func,
104 | checkedItemsCallback: PropTypes.func,
105 | items: PropTypes.arrayOf(PropTypes.object),
106 | toolbarConfigs: PropTypes.object,
107 | children: PropTypes.node,
108 | }
109 |
110 | JsonSchemaTable.contextTypes = {
111 | store: PropTypes.object,
112 | }
113 |
114 | export default JsonSchemaTable
115 |
--------------------------------------------------------------------------------
/src/reducers/items-reducer.js:
--------------------------------------------------------------------------------
1 | import * as types from '../actions/ActionTypes'
2 | import { STATUS } from 'table/constants'
3 | import json2csv from 'json2csv'
4 | import Ajv from 'ajv'
5 | import ajvLocalize from 'ajv-i18n'
6 | const ajv = new Ajv({ allErrors: true })
7 |
8 | const initialState = {
9 | source: [],
10 | staging: {},
11 | historyChanges: [],
12 | historyIndex: 0,
13 | where: null,
14 | sort: null,
15 | isFetching: false,
16 | invalidItems: [],
17 | checkedItems: [],
18 | stagingItems: [],
19 | }
20 | export default (state = initialState, action) => {
21 | switch (action.type) {
22 | case types.ITEMS_LOAD_BEGAN: {
23 | return Object.assign({}, state, { isFetching: true })
24 | }
25 |
26 | case types.ITEMS_LOAD_FAIL: {
27 | return Object.assign({}, state, {
28 | isFetching: false,
29 | errors: action.errors,
30 | })
31 | }
32 |
33 | case types.RECEIVE_ITEMS_FROM_PROPS: {
34 | const { items } = action.payload
35 | return {
36 | ...state,
37 | source: items,
38 | }
39 | }
40 |
41 | case types.EXPORT_CHECKED_ITEMS: {
42 | const { fields, entityId } = action.payload
43 | const { checkedItems, source, staging } = state
44 | const documentsToExport = []
45 |
46 | checkedItems.forEach(key => {
47 | const sourceDoc = source.find(o => o.document.id === key) || {}
48 | const stagDoc = staging[key] || {}
49 |
50 | const doc = {
51 | ...sourceDoc.document,
52 | ...stagDoc.document,
53 | }
54 | documentsToExport.push(doc)
55 | })
56 |
57 | json2csv({ data: documentsToExport, fields: fields }, function(err, csv) {
58 | if (err) console.log(err)
59 | var csvData = new Blob([csv], { type: 'text/csv;charset=utf-8;' })
60 | var csvURL = window.URL.createObjectURL(csvData)
61 | var tempLink = document.createElement('a')
62 | tempLink.href = csvURL
63 | tempLink.setAttribute(
64 | 'download',
65 | entityId + '-' + Date.now() + '.csv'
66 | )
67 | tempLink.click()
68 | })
69 |
70 | return {
71 | ...state,
72 | checkedItems: [],
73 | }
74 | }
75 |
76 | case types.ITEMS_LOAD_SUCCESS: {
77 | const { items, sort, where, totalRows, rowStart } = action
78 | const newState = Object.assign({}, state)
79 | newState.isFetching = false
80 | if (newState.where !== where || newState.sort !== sort) {
81 | newState.source = []
82 | }
83 | newState.sort = sort
84 | newState.where = where
85 |
86 | // Verify if the items list length is equal to the totalRows
87 | while (newState.source.length < totalRows) {
88 | const newItem = {
89 | virtualIndex: newState.source.length,
90 | document: null,
91 | status: STATUS.LAZZY,
92 | }
93 | newState.source.push(newItem)
94 | }
95 |
96 | items.forEach((item, index) => {
97 | // Get the item from the list and sets the document attibute and changes the document Status to LOADED
98 | var initialItem = newState.source[index + rowStart]
99 | initialItem.document = item.document
100 | initialItem.status = STATUS.LOADED
101 | })
102 |
103 | return newState
104 | }
105 |
106 | case types.REMOVE_ITEM: {
107 | const { rowIndex, schema, lang } = action
108 | const newState = Object.assign({}, state)
109 | const documentId = state.source[rowIndex].document.id
110 | addStaging(newState, documentId, STATUS.DELETED, null, schema, lang)
111 | addToHistoryChanges(newState, documentId, STATUS.DELETED, null)
112 | return newState
113 | }
114 |
115 | case types.DELETE_CHECKED_ITEMS: {
116 | const newState = Object.assign({}, state)
117 | newState.checkedItems.slice().reverse().forEach((item, index, ref) => {
118 | if (newState.staging[item]) {
119 | delete newState.staging[item]
120 | const stagingIndex = newState.stagingItems.indexOf(item)
121 | newState.stagingItems.splice(stagingIndex, 1)
122 | newState.checkedItems.splice(ref.length - 1 - index, 1)
123 | } else {
124 | // To Do: delete document in API (not staged)
125 | }
126 | })
127 | return newState
128 | }
129 |
130 | case types.ADD_ITEM: {
131 | const { id, schema, lang } = action
132 | const newState = Object.assign({}, state)
133 | addStaging(newState, id, STATUS.NEW, { id: { value: id } }, schema, lang)
134 | addToHistoryChanges(newState, id, STATUS.NEW, null)
135 | return newState
136 | }
137 |
138 | case types.UPDATE_ITEM: {
139 | const { id, schema, changes, lang } = action
140 | const newState = Object.assign({}, state)
141 | const changesKey = Object.keys(changes)[0]
142 | let hydratedChanges = changes
143 | if (schema.properties[changesKey].type === 'object' && state.staging[id].document[changesKey]) {
144 | // TO DO: make deep merge if field is object inside object inside object...
145 | hydratedChanges[changesKey] = {
146 | value: {
147 | ...state.staging[id].document[changesKey],
148 | ...changes[changesKey].value,
149 | }
150 | }
151 | }
152 | addStaging(newState, id, null, hydratedChanges, schema, lang, state)
153 | addToHistoryChanges(newState, id, null, hydratedChanges)
154 | return newState
155 | }
156 |
157 | case types.CANCEL_STAGING: {
158 | const newState = Object.assign({}, state)
159 | newState.staging = {}
160 | newState.stagingItems = []
161 | newState.invalidItems = []
162 | return newState
163 | }
164 |
165 | case types.SAVE_ITEMS_CHANGES_BEGAN: {
166 | return Object.assign({}, state, { isFetching: true })
167 | }
168 |
169 | case types.SAVE_ITEMS_CHANGES_FAIL: {
170 | return Object.assign({}, state, {
171 | isFetching: false,
172 | errors: action.errors,
173 | })
174 | }
175 |
176 | case types.CHECK_ITEM_CHANGE: {
177 | const { id, checked } = action
178 | const newState = Object.assign({}, state)
179 | if (checked && !newState.checkedItems.includes(id)) {
180 | console.log('>> marca esse: ', id)
181 | newState.checkedItems.push(id)
182 | }
183 | if (checked && !id) {
184 | console.log('>> marca tudo')
185 | newState.source.forEach(doc => {
186 | if (!newState.checkedItems.includes(doc.document.id)) {
187 | newState.checkedItems.push(doc.document.id)
188 | }
189 | })
190 | }
191 | if (!checked) {
192 | if (!id) {
193 | console.log('>> desmarca tudo')
194 | newState.checkedItems = []
195 | }
196 | else {
197 | console.log('>> desmarca esse: ', id)
198 | newState.checkedItems = newState.checkedItems.filter(
199 | item => item !== id
200 | )
201 | }
202 | }
203 | return newState
204 | }
205 |
206 | case types.SAVE_ITEMS_CHANGES_COMPLETE: {
207 | const { errors, notSavedIds } = action
208 | const newState = Object.assign({}, state, {
209 | isFetching: false,
210 | errors: errors,
211 | })
212 | console.log(newState.staging)
213 | const stagingIds = Object.keys(newState.staging)
214 | console.log('stagingIds:', stagingIds)
215 | for (const documentId of stagingIds) {
216 | console.log('documentId', documentId)
217 | if (notSavedIds && notSavedIds.includes(documentId)) {
218 | continue
219 | }
220 | const stagingItem = newState.staging[documentId]
221 | console.log('stagingItem', documentId)
222 | if (stagingItem.status === STATUS.LOADED) {
223 | const loadedItem = newState.source.find(
224 | item => item.document && item.document.id === documentId
225 | )
226 | loadedItem.document = Object.assign(
227 | {},
228 | loadedItem.document,
229 | stagingItem.document
230 | )
231 | delete newState.staging[documentId]
232 | newState.stagingItems = newState.stagingItems.filter(
233 | stagingId => stagingId !== documentId
234 | )
235 | continue
236 | }
237 |
238 | if (stagingItem.status === STATUS.NEW) {
239 | const newItem = Object.assign(
240 | {},
241 | { document: newState.staging[documentId].document },
242 | {
243 | status: STATUS.LOADED,
244 | }
245 | )
246 | newState.source.push(newItem)
247 | delete newState.staging[documentId]
248 | newState.stagingItems = newState.stagingItems.filter(
249 | stagingId => stagingId !== documentId
250 | )
251 | continue
252 | }
253 |
254 | if (stagingItem.status === STATUS.DELETED) {
255 | newState.source = newState.source.filter(
256 | item => item.document.id !== documentId
257 | )
258 | delete newState.staging[documentId]
259 | newState.stagingItems = newState.stagingItems.filter(
260 | stagingId => stagingId !== documentId
261 | )
262 | continue
263 | }
264 | }
265 | return newState
266 | }
267 |
268 | case types.COPY_FROM_SELECTED_RANGE: {
269 | const { changes, schema, lang } = action
270 | const newState = Object.assign({}, state)
271 |
272 | changes.forEach(change => {
273 | addStaging(
274 | newState,
275 | change.id,
276 | change.changes['id'] ? STATUS.NEW : null,
277 | change.changes,
278 | schema,
279 | lang
280 | )
281 | })
282 | return newState
283 | }
284 | case types.PASTE_DATA: {
285 | return state
286 | }
287 |
288 | case types.UNDO_CHANGE: {
289 | const newState = Object.assign({}, state)
290 | const { schema, lang } = action
291 |
292 | newState.staging = {}
293 |
294 | if (newState.historyIndex === 0) {
295 | return
296 | }
297 |
298 | newState.historyIndex--
299 |
300 | if (newState.historyIndex === 0) {
301 | newState.stagingItems = []
302 | newState.invalidItems = []
303 | }
304 |
305 | for (let i = 0; i < newState.historyIndex; i++) {
306 | addStaging(
307 | newState,
308 | newState.historyChanges[i].id,
309 | newState.historyChanges[i].status,
310 | newState.historyChanges[i].changes,
311 | schema,
312 | lang
313 | )
314 | }
315 | return newState
316 | }
317 | case types.REDO_CHANGE: {
318 | const newState = Object.assign({}, state)
319 | const { schema, lang } = action
320 |
321 | if (newState.historyIndex === newState.historyChanges.length) {
322 | return
323 | }
324 |
325 | addStaging(
326 | newState,
327 | newState.historyChanges[newState.historyIndex].id,
328 | newState.historyChanges[newState.historyIndex].status,
329 | newState.historyChanges[newState.historyIndex].changes,
330 | schema,
331 | lang
332 | )
333 | newState.historyIndex++
334 | return newState
335 | }
336 |
337 | default:
338 | return state
339 | }
340 | }
341 |
342 | const addToHistoryChanges = (newState, id, status, changes) => {
343 | newState.historyChanges.push({ id: id, status: status, changes: changes })
344 | newState.historyIndex++
345 | }
346 |
347 | const addStaging = (newState, id, status, changes, schema, lang, state) => {
348 | const { staging, source } = newState
349 | const newStagingDocument = {}
350 | const item = source.find(item => item.document && item.document.id === id)
351 | if (changes) {
352 | Object.keys(changes).forEach(fieldName => {
353 | const change = changes[fieldName]
354 | const field = schema[fieldName]
355 | if (field && field.type === 'object') {
356 | newStagingDocument[fieldName] = {}
357 | Object.keys(change.value).forEach(key => {
358 | newStagingDocument[fieldName][key] = change.value[key]
359 | })
360 | } else {
361 | newStagingDocument[fieldName] = change.value
362 | }
363 | })
364 | }
365 |
366 | newState.stagingItems.push(id)
367 |
368 | if (!staging[id]) {
369 | staging[id] = {}
370 | }
371 |
372 | // Shallow merge of the existing staging with the new staging
373 |
374 | staging[id].document = {
375 | ...staging[id].document,
376 | ...newStagingDocument
377 | }
378 |
379 | // If the item has status DELETED, keep that status
380 | const sourceItem = state && state.source.find(i => i.document.id === id)
381 | staging[id].status = sourceItem ? STATUS.STAGING : status || staging[id].status
382 |
383 | // Validate the document usign the JSONSchema
384 |
385 | const documentToValidate = Object.assign(
386 | {},
387 | item ? item.document : {},
388 | staging[id].document
389 | )
390 | staging[id].validationErrors = validateDocument(
391 | schema,
392 | lang,
393 | documentToValidate
394 | )
395 |
396 | if (staging[id].validationErrors) {
397 | newState.invalidItems.push(id)
398 | } else {
399 | newState.invalidItems = newState.invalidItems.filter(item => item !== id)
400 | }
401 | }
402 |
403 | const validateDocument = (schema, lang, documentToValidate) => {
404 | if (window && document) {
405 | const validate = ajv.compile(schema)
406 | const valid = validate(documentToValidate)
407 | if (!valid) {
408 | validate.errors.forEach(error => {
409 | if (error.keyword === 'required') {
410 | error.dataPath = `.${error.params.missingProperty}`
411 | }
412 | })
413 | }
414 | ajvLocalize[lang](validate.errors)
415 | return validate.errors
416 | } return [] // review this fallback
417 | }
418 |
--------------------------------------------------------------------------------
/src/reducers/stateShape.js:
--------------------------------------------------------------------------------
1 | {
2 | source:{}, //Items carregados da API
3 | staging: {}, // ultimas alteracoes feitas nos items
4 | invalid: {},
5 | selected: {},
6 | historyChanges:[], // hostoticp de altracoes, undo redo
7 | historyIndex, // index da ultima mudanca aplicada ao staging
8 | sort, // expressao de como esta ordenado UIState ????
9 | where, // expressao dos filtros aplicados aos dados UIState???
10 | errorMessages: [] // mensagens de error na store UIState?????
11 | viewMode: {} // Default, staging, hasEditedItems, hasInvalidItems, hasCheckedItems UIState?????
12 | filter{filteredStatus:[]} // Filtros ativos segundo o estado do item. UIState?????
13 | }
14 |
--------------------------------------------------------------------------------
/src/redux/configureStore.js:
--------------------------------------------------------------------------------
1 | import { createStore, compose, applyMiddleware } from 'redux'
2 | // import { persistStore, persistReducer } from 'redux-persist'
3 | // import storage from 'redux-persist/lib/storage'
4 | // import thunk from 'redux-thunk'
5 | import rootReducer from './rootReducer'
6 | import logger from 'redux-logger'
7 |
8 | export default initialState => {
9 | // const persistConfig = {
10 | // key: 'RJST',
11 | // storage,
12 | // }
13 |
14 | // const persistedReducer = persistReducer(persistConfig, rootReducer)
15 | const hasDevTools = typeof window !== 'undefined' && window.devToolsExtension
16 | const store = createStore(
17 | rootReducer,
18 | initialState,
19 | hasDevTools
20 | ? compose(
21 | // applyMiddleware(thunk, logger),
22 | applyMiddleware(logger),
23 | window.devToolsExtension()
24 | )
25 | : applyMiddleware(logger)
26 | )
27 |
28 | // const persistor = persistStore(store)
29 |
30 | // if (module.hot) {
31 | // module.hot.accept('../reducers', () => {
32 | // const nextRootReducer = require('../reducers/index.js').default
33 | // store.replaceReducer(nextRootReducer)
34 | // })
35 | // }
36 | return { store }
37 | // return { store, persistor }
38 | }
39 |
--------------------------------------------------------------------------------
/src/redux/constants.js:
--------------------------------------------------------------------------------
1 | export const PROJECT_NAME = 'RJST' // stands for React Json Schema Table :)
2 |
--------------------------------------------------------------------------------
/src/redux/rootReducer.js:
--------------------------------------------------------------------------------
1 | import items from '../reducers/items-reducer'
2 | import table from 'table/reducer'
3 | import filter from 'toolBar/reducer'
4 |
5 | const rootReducer = (state = {}, action) => {
6 | const { selectionRange, selectionFillHandleRange } = state.table || {}
7 |
8 | return {
9 | filter: filter(state.filter, action),
10 | table: table(state.table, action),
11 | items: items(state.items, {
12 | ...action,
13 | selectionRange,
14 | selectionFillHandleRange,
15 | }),
16 | }
17 | }
18 |
19 | export default rootReducer
20 |
--------------------------------------------------------------------------------
/src/table/actions.js:
--------------------------------------------------------------------------------
1 | import { PROJECT_NAME } from '../redux/constants'
2 |
3 | /** ACTION TYPES **/
4 |
5 | const NAME = `${PROJECT_NAME}/table`
6 |
7 | export const TABLE_ACTIONS = {
8 | SELECT_CELL: `${NAME}/SELECT_CELL`,
9 | EDIT_CELL: `${NAME}/EDIT_CELL`,
10 | EXIT_EDIT_CELL: `${NAME}/EXIT_EDIT_CELL`,
11 | CLEAN_SELECTION: `${NAME}/CLEAN_SELECTION`,
12 | SELECT_CELLS_RANGE: `${NAME}/SELECT_CELLS_RANGE`,
13 | SELECT_FILLHANDLE_CELLS_RANGE: `${NAME}/SELECT_FILLHANDLE_CELLS_RANGE`,
14 | SHOW_MODAL: `${NAME}/form/SHOW_MODAL`,
15 | HIDE_MODAL: `${NAME}/form/HIDE_MODAL`,
16 | }
17 |
18 | /** ACTION CREATORS **/
19 |
20 | export function selectCell(cell) {
21 | return {
22 | type: TABLE_ACTIONS.SELECT_CELL,
23 | payload: {
24 | cell,
25 | }
26 | }
27 | }
28 |
29 | export function editCell(cell) {
30 | return {
31 | type: TABLE_ACTIONS.EDIT_CELL,
32 | payload: {
33 | cell,
34 | }
35 | }
36 | }
37 |
38 | export function exitEditCell(cell) {
39 | return {
40 | type: TABLE_ACTIONS.EXIT_EDIT_CELL,
41 | payload: {
42 | cell,
43 | },
44 | }
45 | }
46 |
47 | export function cleanSelection() {
48 | return {
49 | type: TABLE_ACTIONS.CLEAN_SELECTION,
50 | payload: {},
51 | }
52 | }
53 |
54 | export function selectCellsRange(cellA, cellB) {
55 | return {
56 | type: TABLE_ACTIONS.SELECT_CELLS_RANGE,
57 | payload: {
58 | cellA,
59 | cellB
60 | }
61 | }
62 | }
63 |
64 | export function selectFillHandleRange(cellA, cellB) {
65 | return {
66 | type: TABLE_ACTIONS.SELECT_FILLHANDLE_CELLS_RANGE,
67 | payload: {
68 | cellA,
69 | cellB
70 | }
71 | }
72 | }
73 |
74 | export function showFormModal(document) {
75 | return {
76 | type: TABLE_ACTIONS.SHOW_MODAL,
77 | payload: {
78 | showModal: true,
79 | selectedItem: document,
80 | },
81 | }
82 | }
83 |
84 | export function hideFormModal() {
85 | return {
86 | type: TABLE_ACTIONS.HIDE_MODAL,
87 | payload: {
88 | showModal: false,
89 | },
90 | }
91 | }
92 |
93 | export const actionCreators = {
94 | selectCell,
95 | editCell,
96 | exitEditCell,
97 | cleanSelection,
98 | selectCellsRange,
99 | selectFillHandleRange,
100 | showFormModal,
101 | hideFormModal,
102 | }
103 |
--------------------------------------------------------------------------------
/src/table/components/Row/Form.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types'
2 | import React, { Component } from 'react'
3 | import { HotKeys } from 'react-hotkeys'
4 | import Modal from '@vtex/styleguide/lib/Modal'
5 |
6 | import FormSection from './FormSection'
7 |
8 | class Form extends Component {
9 | constructor(props) {
10 | super(props)
11 |
12 | this.state = {
13 | showModal: props.showModal,
14 | }
15 | }
16 |
17 | handleCloseModal = () => {
18 | this.props.hideFormModal()
19 |
20 | if (this.state.callback) {
21 | this.state.callback()
22 | }
23 | }
24 |
25 | handleModalExited = () => {
26 | // TODO Não esta sendo chamada essa função no onExited do modal
27 | if (this.state.callback) {
28 | this.state.callback()
29 | }
30 | }
31 |
32 | getLabel = fieldName => {
33 | const fieldDefinition = this.props.schema.properties[fieldName]
34 |
35 | return {`${fieldName} (${fieldDefinition.type}):`}
36 | }
37 |
38 | setChange = change => {
39 | const {
40 | context,
41 | document: { id },
42 | } = this.state
43 |
44 | this.props.setChanges(id, change, context)
45 | }
46 |
47 | setChanges = (id, changes) => {
48 | this.props.setChanges(id, changes, this.state.context)
49 | }
50 |
51 | render() {
52 | const { schema, showModal, selectedItem, UIschema } = this.props
53 | const { validationErrors } = this.state
54 |
55 | if (!showModal || !selectedItem) {
56 | return null
57 | }
58 |
59 | const handlers = {
60 | closeForm: this.closeModal,
61 | }
62 |
63 | const item = selectedItem.document
64 |
65 | const sections = UIschema.editor.settings.sections
66 |
67 | const labels = sections.reduce((labelAcc, currSection) => {
68 | const currSectionLabels = currSection.fields.reduce(
69 | (sectionAcc, currField) => ({
70 | ...sectionAcc,
71 | [currField]: this.getLabel(currField),
72 | }),
73 | {}
74 | )
75 |
76 | return { ...labelAcc, ...currSectionLabels }
77 | }, {})
78 |
79 | return (
80 |
81 |
82 |
83 |
84 |
{`${UIschema.title}: {${item.id}}`}
85 |
86 |
87 |
88 |
{
90 | if (div) {
91 | setTimeout(() => div.scrollTo(0, 0), 10)
92 | }
93 | }}
94 | className="dib w-100 pt1 overflow-y-scroll vh-75"
95 | >
96 | {sections.map((section, index) => (
97 |
109 | ))}
110 |
111 |
112 |
113 |
114 | )
115 | }
116 | }
117 |
118 | Form.propTypes = {
119 | hideFormModal: PropTypes.func,
120 | schema: PropTypes.any,
121 | selectedItem: PropTypes.any,
122 | setChanges: PropTypes.func,
123 | showFormModal: PropTypes.func,
124 | showModal: PropTypes.bool,
125 | UIschema: PropTypes.any,
126 | validationErrors: PropTypes.any,
127 | }
128 |
129 | export default Form
130 |
--------------------------------------------------------------------------------
/src/table/components/Row/FormSection.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types'
2 | import React, { Fragment } from 'react'
3 |
4 | import ControlFactory from 'components/dynamic-controls/ControlFactory.react'
5 |
6 | const FormSection = ({
7 | item,
8 | labels,
9 | schema,
10 | section,
11 | setChanges,
12 | validationErrors,
13 | }) => (
14 |
15 |
{section.name}
16 | {section.fields.map((control, index) => {
17 | const label = labels[control]
18 | const value = (item && item[control]) || null
19 |
20 | return (
21 |
22 |
23 |
37 | error.dataPath.includes(`.${control}`)
38 | )
39 | : []
40 | }
41 | value={value}
42 | {...schema.properties[control]}
43 | />
44 |
45 | )
46 | })}
47 |
48 | )
49 |
50 | FormSection.propTypes = {
51 | item: PropTypes.object,
52 | labels: PropTypes.any,
53 | onOpenLink: PropTypes.func,
54 | schema: PropTypes.any,
55 | section: PropTypes.shape({
56 | fields: PropTypes.arrayOf(PropTypes.string.isRequired).isRequired,
57 | name: PropTypes.string.isRequired,
58 | }).isRequired,
59 | setChanges: PropTypes.func,
60 | UIschema: PropTypes.any,
61 | validationErrors: PropTypes.array,
62 | }
63 |
64 | export default FormSection
65 |
--------------------------------------------------------------------------------
/src/table/components/Row/InputTypes/Checkbox.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types'
2 | import React from 'react'
3 | import ReactDOM from 'react-dom'
4 | import { HotKeys } from 'react-hotkeys'
5 |
6 | import Toggle from '@vtex/styleguide/lib/Toggle'
7 |
8 | class Checkbox extends React.Component {
9 | constructor(props) {
10 | super(props)
11 |
12 | this.state = { isChecked: !!props.value }
13 | }
14 |
15 | componentDidUpdate(prevProps) {
16 | const { isFocus, isEditing } = this.props
17 |
18 | if (isFocus && !prevProps.isFocus) {
19 | this.props.onEditCell()
20 | }
21 |
22 | if (isEditing && window && document) {
23 | ReactDOM.findDOMNode(this.el).focus()
24 | }
25 |
26 | if (prevProps.value !== this.props.value) {
27 | this.setState({ isChecked: !!this.props.value })
28 | }
29 | }
30 |
31 | handleChange = e => {
32 | const { isEditing, setChange } = this.props
33 |
34 | e.preventDefault()
35 |
36 | if (isEditing) {
37 | setChange(!this.state.isChecked)
38 |
39 | this.setState({ isChecked: !this.state.isChecked })
40 | }
41 | }
42 |
43 | render() {
44 | const { isChecked } = this.state
45 | const handlers = {
46 | space: this.handleChange,
47 | stageChanges: this.handleChange,
48 | }
49 |
50 | return (
51 | {
53 | this.el = el
54 | }}
55 | className={`w-100 h-100 flex items-center justify-center outline-0 ${
56 | this.props.isFocus ? 'bw1 ba b--blue' : ''
57 | }`}
58 | handlers={handlers}
59 | >
60 |
61 |
62 |
63 |
64 | )
65 | }
66 | }
67 |
68 | Checkbox.propTypes = {
69 | hasError: PropTypes.bool,
70 | isEditing: PropTypes.bool,
71 | isFocus: PropTypes.bool,
72 | onEditCell: PropTypes.func,
73 | renderType: PropTypes.string,
74 | setChange: PropTypes.func.isRequired,
75 | value: PropTypes.bool,
76 | }
77 |
78 | export default Checkbox
79 |
--------------------------------------------------------------------------------
/src/table/constants.js:
--------------------------------------------------------------------------------
1 | // LISTA DE STATUS POSSÍVEIS PARA UM ITEM
2 | export const STATUS = {
3 | LAZZY: 'lazzy',
4 | NEW: 'new',
5 | LOADED: 'loaded',
6 | STAGING: 'staging',
7 | DELETED: 'deleted',
8 | IMPORTED: 'imported',
9 | SESUPDATED: 'sesupdated',
10 | SELECTED: 'selected',
11 | INVALID: 'invalid',
12 | }
13 |
--------------------------------------------------------------------------------
/src/table/containers/FormContainer.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux'
2 | import { bindActionCreators } from 'redux'
3 |
4 | import { updateItem } from 'actions/items-actions'
5 | import { actionCreators } from 'table/actions'
6 | import Form from 'table/components/Row/Form'
7 |
8 | const mapStateToProps = state => {
9 | return {
10 | showModal: state.table.form.showModal || false,
11 | selectedItem: state.table.form.selectedItem,
12 | }
13 | }
14 |
15 | const mapDispatchToProps = (dispatch, ownProps) => {
16 | const bindedActions = bindActionCreators(actionCreators, dispatch)
17 |
18 | return {
19 | ...bindedActions,
20 | setChanges: (id, changes) => {
21 | // TO DO: put lang here correctly, as prop
22 | dispatch(updateItem(id, ownProps.schema, changes, 'en'))
23 | },
24 | }
25 | }
26 |
27 | export default connect(mapStateToProps, mapDispatchToProps)(Form)
28 |
--------------------------------------------------------------------------------
/src/table/containers/TableContainer.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux'
2 | import { bindActionCreators } from 'redux'
3 |
4 | import Table from 'components/endlessTable/views/Table.react'
5 | import { STATUS } from 'table/constants'
6 | import { actionCreators } from 'table/actions'
7 | import {
8 | removeItem,
9 | fetchItems,
10 | checkItemChange,
11 | updateItem,
12 | copyFromSelectedRange,
13 | } from 'actions/items-actions'
14 |
15 | const mapStateToProps = (state, ownProps) => {
16 | return {
17 | form: state.table.form,
18 | context: ownProps.context,
19 | fetchSize: ownProps.fetchSize,
20 | UIschema: ownProps.UIschema,
21 | schema: ownProps.schema,
22 | where: state.items.where,
23 | sort: state.items.sort,
24 | items: ListITems(state),
25 | focusedCell: state.table.focusedCell,
26 | editingCell: state.table.editingCell,
27 | selectionRange: state.table.selectionRange,
28 | selectionFillHandleRange: state.table.selectionFillHandleRange,
29 | isChecking: state.items.checkedItems && state.items.checkedItems.length > 0,
30 | checkedItems: state.items.checkedItems,
31 | hiddenFields: state.filter.hiddenFields,
32 | }
33 | }
34 |
35 | function ListITems(state) {
36 | var returnValue = []
37 | const items = state.items.source || []
38 | const newItems = []
39 | if (state.items.staging) {
40 | Object.keys(state.items.staging).forEach(id => {
41 | if (state.items.staging[id].status === STATUS.NEW) {
42 | newItems.push(state.items.staging[id])
43 | }
44 | })
45 | }
46 | const allItems = [...items, ...newItems]
47 |
48 | allItems.forEach(item => {
49 | let staging
50 | if (item.document) {
51 | staging = state.items.staging[item.document.id] || {}
52 | } else {
53 | staging = {}
54 | }
55 |
56 | // shallow merge
57 | var newItem = Object.assign({}, item, staging)
58 | // little deeper merge. TO DO: make deep merge...
59 | newItem.document = {
60 | ...item.document || {},
61 | ...staging.document || {},
62 | }
63 |
64 | if (state.items.checkedItems.includes(newItem.document.id)) {
65 | newItem.isChecked = true
66 | }
67 | if (
68 | (state.filter.isStagingFilterActive &&
69 | !state.items.stagingItems.includes(newItem.document.id)) ||
70 | (state.filter.isInvalidFilterActive &&
71 | !state.items.invalidItems.includes(newItem.document.id)) ||
72 | (state.filter.isSelectedFilterActive &&
73 | !state.items.checkedItems.includes(newItem.document.id))
74 | ) {
75 | return
76 | }
77 |
78 | newItem.virtualID = returnValue.length
79 | returnValue.push(newItem)
80 | })
81 | return returnValue
82 | }
83 |
84 | const mapDispatchToProps = (dispatch, ownProps) => {
85 | const bindedActions = bindActionCreators(actionCreators, dispatch)
86 |
87 | return {
88 | ...bindedActions,
89 |
90 | onEditItem: (doc) => {
91 | dispatch(actionCreators.showFormModal(doc))
92 | },
93 |
94 | onFetchItems: (context, fields, skip, size, where, sort) => {
95 | dispatch(fetchItems(context, fields, skip, size, where, sort))
96 | },
97 |
98 | onRemove: index => {
99 | dispatch(removeItem(index, ownProps.schema, ownProps.lang))
100 | },
101 |
102 | onCheckRowChange: (id, isChecked) => {
103 | dispatch(checkItemChange(id, isChecked))
104 | },
105 |
106 | onSelectCellsRange: (cellA, cellB) => {
107 | dispatch(actionCreators.selectCellsRange(cellA, cellB))
108 | },
109 |
110 | onSelectFillHandleRange: (cellA, cellB) => {
111 | dispatch(actionCreators.selectFillHandleRange(cellA, cellB))
112 | },
113 |
114 | onFocusCell: cell => {
115 | dispatch(actionCreators.selectCell(cell))
116 | },
117 |
118 | onEditCell: cell => {
119 | dispatch(actionCreators.editCell(cell))
120 | },
121 |
122 | onExitEditCell: cell => {
123 | dispatch(actionCreators.exitEditCell(cell))
124 | },
125 | setChanges: (id, changes) => {
126 | dispatch(updateItem(id, ownProps.schema, changes, ownProps.lang))
127 | },
128 |
129 | onCopyFromSelectedRange: changes => {
130 | dispatch(copyFromSelectedRange(changes, ownProps.schema, ownProps.lang))
131 | },
132 | }
133 | }
134 |
135 | export default connect(mapStateToProps, mapDispatchToProps)(Table)
136 |
--------------------------------------------------------------------------------
/src/table/reducer.js:
--------------------------------------------------------------------------------
1 | import { TABLE_ACTIONS } from 'table/actions'
2 |
3 | const initialState = {
4 | focusedCell: null,
5 | editingCell: null,
6 | selectionFillHandleRange: { cellA: null, cellB: null },
7 | selectionRange: { cellA: null, cellB: null },
8 | form: {
9 | showModal: false,
10 | selectedItem: {},
11 | },
12 | }
13 |
14 | export default (state = initialState, action) => {
15 | switch (action.type) {
16 | case TABLE_ACTIONS.SELECT_CELL: {
17 | return {
18 | ...state,
19 | focusedCell: action.payload.cell,
20 | selectionRange: {
21 | cellA: action.payload.cell,
22 | cellB: action.payload.cell
23 | },
24 | selectionFillHandleRange: {
25 | cellA: null,
26 | cellB: null
27 | },
28 | editingCell: null,
29 | }
30 | }
31 |
32 | case TABLE_ACTIONS.EDIT_CELL: {
33 | return {
34 | ...state,
35 | selectionRange: {
36 | cellA: action.payload.cell,
37 | cellB: action.payload.cell,
38 | },
39 | editingCell: action.payload.cell,
40 | }
41 | }
42 |
43 | case TABLE_ACTIONS.EXIT_EDIT_CELL: {
44 | return {
45 | ...state,
46 | editingCell: null,
47 | }
48 | }
49 |
50 | case TABLE_ACTIONS.CLEAN_SELECTION: {
51 | return {
52 | ...state,
53 | focusedCell: null,
54 | editingCell: null,
55 | }
56 | }
57 |
58 | case TABLE_ACTIONS.SELECT_CELLS_RANGE: {
59 | return {
60 | ...state,
61 | selectionRange: {
62 | cellA: action.payload.cellA,
63 | cellB: action.payload.cellB
64 | },
65 | selectionFillHandleRange: {
66 | cellA: null,
67 | cellB: null
68 | },
69 | }
70 | }
71 |
72 | case TABLE_ACTIONS.SELECT_FILLHANDLE_CELLS_RANGE: {
73 | const { cellA, cellB } = action
74 | return {
75 | ...state,
76 | selectionFillHandleRange: {
77 | cellA: action.payload.cellA,
78 | cellB: action.payload.cellB
79 | },
80 | }
81 | }
82 |
83 | case TABLE_ACTIONS.SHOW_MODAL:
84 | case TABLE_ACTIONS.HIDE_MODAL:
85 | return {
86 | ...state,
87 | form: {
88 | selectedItem: action.payload.selectedItem || {},
89 | showModal: action.payload.showModal,
90 | },
91 | }
92 |
93 | default:
94 | return state
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/src/toolBar/__tests__/reducer.spec.js:
--------------------------------------------------------------------------------
1 | import reducer from 'toolBar/reducer'
2 | import { TOOLBAR_ACTIONS } from 'toolbar/actions'
3 |
4 | describe('Filter Reducer', () => {
5 | it('WHEN unkown action is passed THEN must return the initial state', () => {
6 | expect(reducer(undefined, {})).toEqual({
7 | isSelectedFilterActive: false,
8 | isStagingFilterActive: false,
9 | isInvalidFilterActive: false,
10 | hiddenFields: [],
11 | })
12 | })
13 |
14 | it('WHEN TOGGLE_CHECKED_FILTER action THEN the isSelectedFilterActive property change to True', () => {
15 | expect(
16 | reducer(
17 | {
18 | isSelectedFilterActive: false,
19 | isStagingFilterActive: false,
20 | isInvalidFilterActive: false,
21 | hiddenFields: [],
22 | },
23 | { type: TOOLBAR_ACTIONS.TOGGLE_CHECKED_FILTER }
24 | )
25 | ).toEqual({
26 | isSelectedFilterActive: true,
27 | isStagingFilterActive: false,
28 | isInvalidFilterActive: false,
29 | hiddenFields: [],
30 | })
31 | })
32 |
33 | it('WHEN TOGGLE_CHECKED_FILTER action THEN the isSelectedFilterActive property change to False', () => {
34 | expect(
35 | reducer(
36 | {
37 | isSelectedFilterActive: true,
38 | isStagingFilterActive: false,
39 | isInvalidFilterActive: false,
40 | hiddenFields: [],
41 | },
42 | { type: TOOLBAR_ACTIONS.TOGGLE_CHECKED_FILTER }
43 | )
44 | ).toEqual({
45 | isSelectedFilterActive: false,
46 | isStagingFilterActive: false,
47 | isInvalidFilterActive: false,
48 | hiddenFields: [],
49 | })
50 | })
51 |
52 | it('WHEN TOGGLE_INVALID_ITEMS_FILTER action THEN the isInvalidFilterActive property change to True', () => {
53 | expect(
54 | reducer(
55 | {
56 | isSelectedFilterActive: false,
57 | isStagingFilterActive: false,
58 | isInvalidFilterActive: false,
59 | hiddenFields: [],
60 | },
61 | { type: TOOLBAR_ACTIONS.TOGGLE_INVALID_ITEMS_FILTER }
62 | )
63 | ).toEqual({
64 | isSelectedFilterActive: false,
65 | isStagingFilterActive: false,
66 | isInvalidFilterActive: true,
67 | hiddenFields: [],
68 | })
69 | })
70 |
71 | it('WHEN TOGGLE_STAGING_FILTER action THEN the isStagingFilterActive property change to True', () => {
72 | expect(
73 | reducer(
74 | {
75 | isSelectedFilterActive: false,
76 | isStagingFilterActive: false,
77 | isInvalidFilterActive: false,
78 | hiddenFields: [],
79 | },
80 | { type: TOOLBAR_ACTIONS.TOGGLE_STAGING_FILTER }
81 | )
82 | ).toEqual({
83 | isSelectedFilterActive: false,
84 | isStagingFilterActive: true,
85 | isInvalidFilterActive: false,
86 | hiddenFields: [],
87 | })
88 | })
89 |
90 | it('WHEN CHANGE_COLUMN_VISIBILITY of "fieldA" to visible=false action THEN the hiddenFields contains "fieldA"', () => {
91 | expect(
92 | reducer(
93 | {
94 | isSelectedFilterActive: false,
95 | isStagingFilterActive: false,
96 | isInvalidFilterActive: false,
97 | hiddenFields: [],
98 | },
99 | {
100 | type: TOOLBAR_ACTIONS.CHANGE_COLUMN_VISIBILITY,
101 | field: 'fieldA',
102 | visible: false,
103 | }
104 | )
105 | ).toEqual({
106 | isSelectedFilterActive: false,
107 | isStagingFilterActive: false,
108 | isInvalidFilterActive: false,
109 | hiddenFields: ['fieldA'],
110 | })
111 | })
112 |
113 | it('WHEN CHANGE_COLUMN_VISIBILITY of "fieldA" to visible=true action THEN the hiddenFields does not contains "fieldA"', () => {
114 | expect(
115 | reducer(
116 | {
117 | isSelectedFilterActive: false,
118 | isStagingFilterActive: false,
119 | isInvalidFilterActive: false,
120 | hiddenFields: ['fieldA', 'fieldB', 'fieldC'],
121 | },
122 | {
123 | type: TOOLBAR_ACTIONS.CHANGE_COLUMN_VISIBILITY,
124 | field: 'fieldA',
125 | visible: true,
126 | }
127 | )
128 | ).toEqual({
129 | isSelectedFilterActive: false,
130 | isStagingFilterActive: false,
131 | isInvalidFilterActive: false,
132 | hiddenFields: ['fieldB', 'fieldC'],
133 | })
134 | })
135 | })
136 |
--------------------------------------------------------------------------------
/src/toolBar/actions.js:
--------------------------------------------------------------------------------
1 | import { PROJECT_NAME } from '../redux/constants'
2 |
3 | /** ACTION TYPES **/
4 |
5 | const NAME = `${PROJECT_NAME}/toolBar`
6 |
7 | export const TOOLBAR_ACTIONS = {
8 | TOGGLE_STAGING_FILTER: `${NAME}/TOGGLE_STAGING_FILTER`,
9 | TOGGLE_INVALID_ITEMS_FILTER: `${NAME}/TOGGLE_INVALID_ITEMS_FILTER`,
10 | TOGGLE_CHECKED_FILTER: `${NAME}/TOGGLE_CHECKED_FILTER`,
11 | CHANGE_COLUMN_VISIBILITY: `${NAME}/CHANGE_COLUMN_VISIBILITY`,
12 | SHOW_ALL_COLUMNS: `${NAME}/SHOW_ALL_COLUMNS`,
13 | CANCEL_STAGING: `${NAME}/CANCEL_STAGING`,
14 | }
15 |
16 | /** ACTION CREATORS **/
17 |
18 | export function toggleStagingFilter() { // former changeStagingFilter
19 | return {
20 | type: TOOLBAR_ACTIONS.TOGGLE_STAGING_FILTER,
21 | payload: {},
22 | }
23 | }
24 |
25 | export function toggleInvalidItemsFilter() { // former changeInvalidItemsFilter
26 | return {
27 | type: TOOLBAR_ACTIONS.TOGGLE_INVALID_ITEMS_FILTER,
28 | payload: {},
29 | }
30 | }
31 |
32 | export function toggleCheckedItemsFilter() { // former changeCheckedItemsFilter
33 | return {
34 | type: TOOLBAR_ACTIONS.TOGGLE_CHECKED_FILTER,
35 | payload: {},
36 | }
37 | }
38 |
39 | export function changeColumnVisibility(field, visible) {
40 | return {
41 | type: TOOLBAR_ACTIONS.CHANGE_COLUMN_VISIBILITY,
42 | payload: {
43 | field,
44 | visible,
45 | },
46 | }
47 | }
48 |
49 | export function showAllColumns() { // former viewAllColumns
50 | return {
51 | type: TOOLBAR_ACTIONS.SHOW_ALL_COLUMNS,
52 | payload: {},
53 | }
54 | }
55 |
56 | export function cancelStaging() {
57 | return {
58 | type: TOOLBAR_ACTIONS.CANCEL_STAGING,
59 | payload: {},
60 | }
61 | }
62 |
63 | export const actionCreators = {
64 | toggleStagingFilter,
65 | toggleInvalidItemsFilter,
66 | toggleCheckedItemsFilter,
67 | changeColumnVisibility,
68 | showAllColumns,
69 | cancelStaging,
70 | }
--------------------------------------------------------------------------------
/src/toolBar/components/ColumnFilter/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import { FormattedMessage } from 'react-intl'
4 |
5 | class ColumnFilter extends React.Component {
6 | render() {
7 | if (!this.props.UIschema) {
8 | return null
9 | }
10 |
11 | const columns = []
12 |
13 | this.props.UIschema.list.forEach(field => {
14 | var isChecked = !this.props.hiddenFields.includes(field)
15 | columns.push(
16 |
17 |
28 |
29 | )
30 | })
31 |
32 | return (
33 |
46 | )
47 | }
48 | handleShowHideColumnClick = ev => {
49 | this.props.onChangeColumnVisibility(ev.target.value, ev.target.checked)
50 | }
51 | handleShowAllColumns = () => {
52 | this.props.onViewAllColumns()
53 | }
54 | }
55 |
56 | ColumnFilter.propTypes = {
57 | context: PropTypes.object,
58 | hiddenFields: PropTypes.array,
59 | isSelected: PropTypes.bool,
60 | UIschema: PropTypes.object,
61 | schema: PropTypes.object,
62 | onChangeColumnVisibility: PropTypes.func,
63 | onViewAllColumns: PropTypes.func,
64 | }
65 |
66 | export default ColumnFilter
67 |
--------------------------------------------------------------------------------
/src/toolBar/components/SaveButton/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import { FormattedMessage } from 'react-intl'
4 |
5 | class SaveButton extends React.Component {
6 | constructor(props) {
7 | super(props)
8 | this.state = {
9 | isSaving: false,
10 | }
11 | }
12 |
13 | render() {
14 | const { customStyle } = this.props
15 | return (
16 |
33 | )
34 | }
35 |
36 | componentWillReceiveProps(nextProps) {
37 | this.setState({ disabled: nextProps.disabled })
38 | }
39 |
40 | handleClick = () => {
41 | if (this.props.disabled || this.state.isSaving) {
42 | return
43 | }
44 | this.props.handleSaveAll()
45 | }
46 | }
47 |
48 | SaveButton.propTypes = {
49 | handleSaveAll: PropTypes.func,
50 | disabled: PropTypes.bool,
51 | customStyle: PropTypes.object,
52 | }
53 |
54 | export default SaveButton
55 |
--------------------------------------------------------------------------------
/src/toolBar/components/StateFilters/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import PropTypes from 'prop-types'
3 | import { injectIntl, intlShape } from 'react-intl'
4 | import Toggle from '@vtex/styleguide/lib/Toggle'
5 |
6 | class StateFilters extends Component {
7 | handleCheckFilterClick = ev => {
8 | this.props.onChangeCheckedItemsFilter(ev.target.checked)
9 | }
10 |
11 | handleStagingFilterClick = ev => {
12 | this.props.onChangeStagingFilter(ev.target.checked)
13 | }
14 |
15 | handleOnlyWithErrorFilterClick = ev => {
16 | this.props.onChangeInvalidItemsFilter(ev.target.checked)
17 | }
18 |
19 | render() {
20 | const {
21 | intl,
22 | isSelected,
23 | isSelectedFilterActive,
24 | hasCheckedItems,
25 | isStagingFilterActive,
26 | hasEditedItems,
27 | isInvalidFilterActive,
28 | hasInvalidItems,
29 | } = this.props
30 | return (
31 |
36 |
37 |
38 |
43 |
44 |
45 |
46 |
51 |
52 |
53 |
54 |
59 |
60 |
61 |
62 | )
63 | }
64 | }
65 |
66 | StateFilters.propTypes = {
67 | isSelected: PropTypes.bool,
68 | hasCheckedItems: PropTypes.bool,
69 | hasEditedItems: PropTypes.bool,
70 | hasInvalidItems: PropTypes.bool,
71 | isStagingFilterActive: PropTypes.bool,
72 | isInvalidFilterActive: PropTypes.bool,
73 | isSelectedFilterActive: PropTypes.bool,
74 | onChangeCheckedItemsFilter: PropTypes.func,
75 | onChangeStagingFilter: PropTypes.func,
76 | onChangeInvalidItemsFilter: PropTypes.func,
77 | intl: intlShape.isRequired,
78 | }
79 |
80 | export default injectIntl(StateFilters)
81 |
--------------------------------------------------------------------------------
/src/toolBar/components/ToolBar/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import { FormattedMessage } from 'react-intl'
4 |
5 | import ColumnFilter from 'toolBar/components/ColumnFilter'
6 | import StateFilters from 'toolBar/components/StateFilters'
7 | import ConfirmAlert from 'components/alert/ConfirmAlert.react.js'
8 | import SaveButton from 'toolBar/components/SaveButton'
9 |
10 | class FixedToolbar extends React.Component {
11 | constructor(props) {
12 | super(props)
13 | this.state = {
14 | isFilterSelected: props.isFilterSelected || false,
15 | isSortSelected: props.isSortSelected || false,
16 | isColumnsToShowSelected: props.isColumnsToShowSelected || false,
17 | isImport: props.isImport || false,
18 | isCancelEditConfirm: props.isCancelEditConfirm || false,
19 | }
20 | }
21 |
22 | render() {
23 | const { toolbarConfigs } = this.props
24 | const { isColumnsToShowSelected } = this.state
25 | const areAnyColumnsHidden = this.props.hiddenFields.length > 0
26 | const areAnyfilterselected =
27 | this.props.isSelectedFilterActive ||
28 | this.props.isInvalidFilterActive ||
29 | this.props.isStagingFilterActive
30 | var toolBarContent = null
31 |
32 | if (this.state.isImport) {
33 | toolBarContent = (
34 |
39 | )
40 | } else {
41 | toolBarContent = (
42 |
43 |
44 | {/* COLUMN FILTERS BUTTON */}
45 | {toolbarConfigs &&
46 | toolbarConfigs.hideColumnsVisibilityBtn ? null : (
47 |
69 | )}
70 | {/* STATE FILTERS BUTTON */}
71 | {toolbarConfigs && toolbarConfigs.hideStateFilterBtn ? null : (
72 |
73 |
90 |
98 |
99 |
100 |
101 |
102 |
103 |
104 | )}
105 |
106 | {/* DOWNLOAD CHECKED ROWS AS CSV BUTTON */}
107 | {toolbarConfigs && (toolbarConfigs.hideDownloadBtn || !this.props.hasCheckedItems)
108 | ? null
109 | : (
110 |
111 |
115 |
116 |
117 |
118 |
119 |
120 |
121 | )}
122 | {/* NEW LINE BUTTON */}
123 | {toolbarConfigs && toolbarConfigs.hideNewLineBtn ? null : (
124 |
125 |
129 |
130 |
131 |
132 |
133 |
134 |
135 | )}
136 | {/* DELETE CHECKED ROWS BUTTON */}
137 | {toolbarConfigs && (toolbarConfigs.hideDeleteBtn || !this.props.hasCheckedItems)
138 | ? null
139 | : (
140 |
141 |
145 |
146 |
147 |
148 |
149 |
150 |
151 | )}
152 | {/* UNDO ALL CHANGES BUTTON */}
153 | {toolbarConfigs && (toolbarConfigs.hideUndoBtn || !this.props.hasEditedItems)
154 | ? null
155 | : (
156 |
157 |
162 |
167 |
172 |
173 |
174 |
175 |
176 | )}
177 | {/* SAVE ALL BUTTON */}
178 | {toolbarConfigs &&
179 | (toolbarConfigs.hideSaveBtn || !this.props.hasEditedItems)
180 | ? null
181 | :
182 |
186 |
187 | }
188 | {this.renderCancelStagingConfirmation()}
189 |
190 | )
191 | }
192 |
193 | return toolBarContent
194 | }
195 |
196 | clearSelection = () => {
197 | this.setState({
198 | isColumnsToShowSelected: false,
199 | isFilterSelected: false,
200 | isSortSelected: false,
201 | })
202 | }
203 |
204 | handleColumnsToShowClick = () => {
205 | var currentValue = this.state.isColumnsToShowSelected
206 | this.clearSelection()
207 | this.setState({ isColumnsToShowSelected: !currentValue })
208 | }
209 |
210 | handleFiltersClick = () => {
211 | var currentValue = this.state.isFilterSelected
212 | this.clearSelection()
213 | this.setState({ isFilterSelected: !currentValue })
214 | }
215 |
216 | handleSortClick = () => {
217 | var currentValue = this.state.isSortSelected
218 | this.clearSelection()
219 | this.setState({ isSortSelected: !currentValue })
220 | }
221 |
222 | handleSaveAll = () => {
223 | this.setState({ isSavingMode: true })
224 | // this.props.onSave()
225 | this.props.stagingItemsCallback(this.props.items.staging)
226 | }
227 |
228 | handleExport = () => {
229 | this.props.onExport()
230 | }
231 |
232 | handleDeleteCheckedRows = () => {
233 | this.props.onDeleteCheckedRows(this.props.context)
234 | this.props.checkedItemsCallback(this.props.items.checkedItems)
235 | }
236 |
237 | handleCancelStaging = () => {
238 | this.setState({ isCancelEditConfirm: true })
239 | }
240 |
241 | renderCancelStagingConfirmation = () => {
242 | if (this.state.isCancelEditConfirm) {
243 | var message = (
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 | )
253 | return (
254 |
260 | )
261 | }
262 | }
263 |
264 | handleCancelStagingConfirm = () => {
265 | this.handleCancelStagingCancel()
266 | this.props.cancelStaging(this.props.context)
267 | this.props.onCancelStaging(this.props.context)
268 | }
269 |
270 | handleCancelStagingCancel = () => {
271 | this.setState({ isCancelEditConfirm: false })
272 | }
273 | }
274 |
275 | FixedToolbar.propTypes = {
276 | context: PropTypes.object,
277 | hasEditedItems: PropTypes.bool,
278 | hasInvalidItems: PropTypes.bool,
279 | hasCheckedItems: PropTypes.bool,
280 | isStagingFilterActive: PropTypes.bool,
281 | isInvalidFilterActive: PropTypes.bool,
282 | isSelectedFilterActive: PropTypes.bool,
283 | UIschema: PropTypes.object,
284 | schema: PropTypes.object,
285 | onExport: PropTypes.func,
286 | onSave: PropTypes.func,
287 | onAdd: PropTypes.func,
288 | onDeleteCheckedRows: PropTypes.func,
289 | cancelStaging: PropTypes.func,
290 | onCancelStaging: PropTypes.func,
291 | toggleCheckedItemsFilter: PropTypes.func,
292 | toggleStagingFilter: PropTypes.func,
293 | toggleInvalidItemsFilter: PropTypes.func,
294 | showAllColumns: PropTypes.func,
295 | hiddenFields: PropTypes.array,
296 | changeColumnVisibility: PropTypes.func,
297 | indexedFields: PropTypes.array,
298 | stagingItemsCallback: PropTypes.func,
299 | checkedItemsCallback: PropTypes.func,
300 | items: PropTypes.object,
301 | toolbarConfigs: PropTypes.object,
302 | }
303 |
304 | export default FixedToolbar
305 |
--------------------------------------------------------------------------------
/src/toolBar/constants.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vtex/react-jsonschema-table/92f4ef5d256fef2e2569db72fdd2724068cb4781/src/toolBar/constants.js
--------------------------------------------------------------------------------
/src/toolBar/containers/ToolBarContainer.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux'
2 | import { bindActionCreators } from 'redux'
3 | import ToolBar from 'toolBar/components/ToolBar'
4 | import {
5 | addItem,
6 | exportCheckedItems,
7 | saveChanges,
8 | deleteCheckedItems,
9 | discardChanges,
10 | } from 'actions/items-actions'
11 | import { actionCreators } from 'toolBar/actions'
12 | import uuid from 'uuid'
13 |
14 | const mapStateToProps = (state, ownProps) => {
15 | return {
16 | context: ownProps.context,
17 | hasEditedItems:
18 | state.items.stagingItems && state.items.stagingItems.length > 0,
19 | hasInvalidItems:
20 | state.items.invalidItems && state.items.invalidItems.length > 0,
21 | hasCheckedItems:
22 | state.items.checkedItems && state.items.checkedItems.length > 0,
23 | isSelectedFilterActive: state.filter.isSelectedFilterActive,
24 | isStagingFilterActive: state.filter.isStagingFilterActive,
25 | isInvalidFilterActive: state.filter.isInvalidFilterActive,
26 | UIschema: ownProps.UIschema,
27 | schema: ownProps.schema,
28 | hiddenFields: state.filter.hiddenFields,
29 | items: state.items,
30 | stagingItemsCallback: ownProps.stagingItemsCallback,
31 | checkedItemsCallback: ownProps.checkedItemsCallback,
32 | toolbarConfigs: ownProps.toolbarConfigs,
33 | }
34 | }
35 |
36 | const mapDispatchToProps = (dispatch, ownProps) => {
37 | const bindedActions = bindActionCreators(actionCreators, dispatch)
38 | return {
39 | ...bindedActions,
40 |
41 | onExport: () => {
42 | dispatch(exportCheckedItems(ownProps.UIschema.list, ownProps.UIschema.title))
43 | },
44 |
45 | onSave: () => {
46 | dispatch(saveChanges(ownProps.UIschema, ownProps.context))
47 | },
48 |
49 | onAdd: () => {
50 | dispatch(addItem(uuid.v4(), ownProps.schema, ownProps.lang))
51 | },
52 |
53 | onDeleteCheckedRows: () => {
54 | dispatch(deleteCheckedItems())
55 | },
56 |
57 | onCancelStaging: () => {
58 | dispatch(discardChanges())
59 | },
60 | }
61 | }
62 |
63 | export default connect(mapStateToProps, mapDispatchToProps)(ToolBar)
64 |
--------------------------------------------------------------------------------
/src/toolBar/logic.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vtex/react-jsonschema-table/92f4ef5d256fef2e2569db72fdd2724068cb4781/src/toolBar/logic.js
--------------------------------------------------------------------------------
/src/toolBar/reducer.js:
--------------------------------------------------------------------------------
1 | import { TOOLBAR_ACTIONS } from './actions'
2 |
3 | const initialState = {
4 | isSelectedFilterActive: false,
5 | isStagingFilterActive: false,
6 | isInvalidFilterActive: false,
7 | hiddenFields: [],
8 | }
9 |
10 | export default (state = initialState, action) => {
11 | switch (action.type) {
12 | case TOOLBAR_ACTIONS.CHANGE_COLUMN_VISIBILITY:
13 | const { field, visible } = action.payload
14 | return {
15 | ...state,
16 | hiddenFields: visible
17 | ? state.hiddenFields.filter(_field => _field !== field)
18 | : [ ...state.hiddenFields, field ]
19 | }
20 | case TOOLBAR_ACTIONS.VIEW_ALL_COLUMNS:
21 | return {
22 | ...state,
23 | hiddenFields: [],
24 | }
25 | case TOOLBAR_ACTIONS.CANCEL_STAGING:
26 | return {
27 | ...state,
28 | isSelectedFilterActive: false,
29 | isStagingFilterActive: false,
30 | isInvalidFilterActive: false,
31 | }
32 | case TOOLBAR_ACTIONS.TOGGLE_CHECKED_FILTER:
33 | const chackToggled = !state.isSelectedFilterActive
34 | return {
35 | ...state,
36 | isSelectedFilterActive: chackToggled,
37 | isStagingFilterActive: chackToggled ? state.isStagingFilterActive : chackToggled,
38 | isInvalidFilterActive: chackToggled ? state.isInvalidFilterActive : chackToggled,
39 | }
40 | case TOOLBAR_ACTIONS.TOGGLE_STAGING_FILTER:
41 | const stagingToggled = !state.isStagingFilterActive
42 | return {
43 | ...state,
44 | isSelectedFilterActive: stagingToggled ? state.isSelectedFilterActive : stagingToggled,
45 | isStagingFilterActive: stagingToggled,
46 | isInvalidFilterActive: stagingToggled ? state.isInvalidFilterActive : stagingToggled,
47 | }
48 | case TOOLBAR_ACTIONS.TOGGLE_INVALID_ITEMS_FILTER:
49 | const invalidToggled = !state.isInvalidFilterActive
50 | return {
51 | ...state,
52 | isSelectedFilterActive: invalidToggled ? state.isSelectedFilterActive : invalidToggled,
53 | isStagingFilterActive: invalidToggled ? state.isStagingFilterActive : invalidToggled,
54 | isInvalidFilterActive: invalidToggled,
55 | }
56 | default:
57 | return state
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/toolBar/utils.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vtex/react-jsonschema-table/92f4ef5d256fef2e2569db72fdd2724068cb4781/src/toolBar/utils.js
--------------------------------------------------------------------------------
/src/utils/KeyMap.js:
--------------------------------------------------------------------------------
1 | export default {
2 | editCell: 'enter',
3 | rename: 'f2',
4 | moveUp: 'up',
5 | moveDown: 'down',
6 | moveLeft: ['left', 'shift+tab'],
7 | moveRight: ['right', 'tab'],
8 | massSelectDown: 'shift+down',
9 | massSelectUp: 'shift+up',
10 | massSelectLeft: 'shift+left',
11 | massSelectRight: 'shift+right',
12 | initSelection: { sequence: 'shift', action: 'keydown' },
13 | endSelection: { sequence: 'shift', action: 'keyup' },
14 | exitEdit: 'escape',
15 | closeForm: 'escape',
16 | openForm: 'shift+space',
17 | stageChanges: 'enter',
18 | removeRow: 'shift+del',
19 | addNew: 'mod+a',
20 | checkRow: 'mod+space',
21 | selectItem: 'enter',
22 | save: ['mod+s'],
23 | delete: ['del', 'backspace'],
24 | undo: ['mod+z'],
25 | redo: ['mod+shift+z', 'mod+y'],
26 | konami: 'up up down down left right left right b a',
27 | space: 'space',
28 | copy: ['mod+c'],
29 | paste: ['mod+v'],
30 | pasteRows: 'mod+shift+v',
31 | }
32 |
--------------------------------------------------------------------------------
/src/utils/icons.js:
--------------------------------------------------------------------------------
1 | import fontawesome from '@fortawesome/fontawesome'
2 | import faCheck from '@fortawesome/fontawesome-free-solid/faCheck'
3 | import faExclamation from '@fortawesome/fontawesome-free-solid/faExclamation'
4 | import faPencil from '@fortawesome/fontawesome-free-solid/faPencilAlt'
5 | import facolumns from '@fortawesome/fontawesome-free-solid/faColumns'
6 | import faDownload from '@fortawesome/fontawesome-free-solid/faCloudDownloadAlt'
7 | import faTrash from '@fortawesome/fontawesome-free-solid/faTrash'
8 | import faUndo from '@fortawesome/fontawesome-free-solid/faUndo'
9 | import faSearch from '@fortawesome/fontawesome-free-solid/faSearch'
10 | import faCheckSquare from '@fortawesome/fontawesome-free-solid/faCheckSquare'
11 | import faSquare from '@fortawesome/fontawesome-free-solid/faSquare'
12 | import faFilter from '@fortawesome/fontawesome-free-solid/faFilter'
13 | import faPlusSquare from '@fortawesome/fontawesome-free-solid/faPlusSquare'
14 | import faSave from '@fortawesome/fontawesome-free-solid/faSave'
15 | import faTimes from '@fortawesome/fontawesome-free-solid/faTimes'
16 | import faPlus from '@fortawesome/fontawesome-free-solid/faPlus'
17 | import faExpand from '@fortawesome/fontawesome-free-solid/faExpand'
18 |
19 | fontawesome.library.add(
20 | facolumns,
21 | faCheck,
22 | faPencil,
23 | faExclamation,
24 | faTrash,
25 | faDownload,
26 | faUndo,
27 | faSearch,
28 | faSquare,
29 | faCheckSquare,
30 | faFilter,
31 | faPlusSquare,
32 | faSave,
33 | faTimes,
34 | faPlus,
35 | faExpand
36 | )
--------------------------------------------------------------------------------
/src/utils/utils.js:
--------------------------------------------------------------------------------
1 | var utils = {
2 | extend() {
3 | var options
4 | var name
5 | var src
6 | var copy
7 | var clone
8 | var target = arguments[0] || {}
9 | var i = 1
10 | var length = arguments.length
11 | var deep = false
12 |
13 | if (target.constructor === Boolean) {
14 | deep = target
15 | target = arguments[1] || {}
16 | i = 2
17 | }
18 |
19 | for (; i < length; i++) {
20 | // Only deal with non-null/undefined values
21 | if ((options = arguments[i]) != null) {
22 | // Extend the base object
23 | for (name in options) {
24 | src = target[name]
25 | copy = options[name]
26 |
27 | // Prevent never-ending loop
28 | if (target === copy) {
29 | continue
30 | }
31 |
32 | // Recurse if we're merging plain objects or arrays
33 | if (
34 | deep &&
35 | copy &&
36 | (copy.constructor === Object || copy.constructor === Array)
37 | ) {
38 | clone = src && src.constructor === copy.constructor
39 | ? src
40 | : new copy.constructor()
41 | // Never move original objects, clone them
42 | // If is an array clear the target
43 | if (clone.constructor === Array) {
44 | clone = []
45 | }
46 | target[name] = this.extend(deep, clone, copy)
47 |
48 | // Don't bring in undefined values
49 | } else if (copy !== undefined) {
50 | target[name] = copy
51 | }
52 | }
53 | }
54 | }
55 | // Return the modified object
56 | return target
57 | },
58 | }
59 |
60 | export default utils
61 |
--------------------------------------------------------------------------------
/webpack.dev.config.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack');
2 | const path = require('path');
3 |
4 | module.exports = {
5 | entry: [
6 | 'react-hot-loader/patch',
7 | './src/example.js'
8 | ],
9 | devtool: 'source-map',
10 | module: {
11 | rules: [
12 | {
13 | test: /\.(js|jsx)$/,
14 | exclude: /node_modules/,
15 | use: ['babel-loader']
16 | },
17 | {
18 | test: /\.(less|css)$/,
19 | use: ["style-loader", "css-loader"]
20 | },
21 | {
22 | test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
23 | loader: "url-loader?limit=10000&mimetype=application/font-woff"
24 | },
25 | {
26 | test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
27 | loader: "file-loader"
28 | },
29 | {
30 | test: /\.(gif|png|jpe?g|svg)$/i,
31 | use: [
32 | 'file-loader',
33 | {
34 | loader: 'image-webpack-loader',
35 | options: {
36 | bypassOnDebug: true,
37 | },
38 | },
39 | ],
40 | }
41 | ]
42 | },
43 | resolve: {
44 | modules: [
45 | path.resolve(__dirname, 'src'),
46 | 'node_modules',
47 | ],
48 | extensions: ['*', '.js', '.jsx']
49 | },
50 | output: {
51 | path: __dirname + '/devdist',
52 | publicPath: '/',
53 | filename: 'index.js'
54 | },
55 | plugins: [
56 | new webpack.HotModuleReplacementPlugin()
57 | ],
58 | devServer: {
59 | contentBase: './devdist',
60 | hot: true
61 | }
62 | };
--------------------------------------------------------------------------------
/webpack.prod.config.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack');
2 | const path = require('path');
3 |
4 | module.exports = {
5 | entry: [
6 | './src/index.js'
7 | ],
8 | devtool: 'source-map',
9 | module: {
10 | rules: [
11 | {
12 | test: /\.(js|jsx)$/,
13 | exclude: /node_modules/,
14 | use: ['babel-loader']
15 | },
16 | {
17 | test: /\.css$/,
18 | include: /node_modules/,
19 | use: [{
20 | loader: "css-loader/locals" // translates CSS into CommonJS
21 | }]
22 | },
23 | {
24 | test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
25 | loader: "url-loader?limit=10000&mimetype=application/font-woff"
26 | },
27 | {
28 | test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
29 | loader: "file-loader"
30 | },
31 | {
32 | test: /\.(gif|png|jpe?g|svg)$/i,
33 | use: [
34 | 'file-loader',
35 | {
36 | loader: 'image-webpack-loader',
37 | options: {
38 | bypassOnDebug: true,
39 | },
40 | },
41 | ],
42 | }
43 | ]
44 | },
45 | resolve: {
46 | extensions: ['*', '.js', '.jsx'],
47 | modules: [
48 | path.resolve(__dirname, 'src'),
49 | 'node_modules',
50 | ],
51 | },
52 | output: {
53 | path: __dirname + '/dist',
54 | publicPath: '/',
55 | filename: 'index.js',
56 | libraryExport: 'default',
57 | libraryTarget: 'commonjs2',
58 | }
59 | };
--------------------------------------------------------------------------------