diff --git a/.eslintrc.json b/.eslintrc.json index 282e846..64851c6 100755 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,13 +1,13 @@ { "extends": [ - "standard", - "standard-react", - "prettier", - "prettier/react" +// "standard", +// "standard-react" +// "prettier", +// "prettier/react" ], "plugins": [ - "prettier", - "react" +// "prettier", +// "react" ], "parser": "babel-eslint", "rules": { @@ -16,20 +16,20 @@ "react/jsx-filename-extension": "off", "react/no-array-index-key": "off", "no-console": "off", - "camelcase": "off", - "prettier/prettier": [ - "error", - { - "printWidth": 120, - "tabWidth": 2, - "singleQuote": true, - "trailingComma": "none", - "bracketSpacing": true, - "jsxBracketSameLine": false, - "parser": "flow", - "semi": true - } - ] + "camelcase": "off" +// "prettier/prettier": [ +// "error", +// { +// "printWidth": 120, +// "tabWidth": 2, +// "singleQuote": true, +// "trailingComma": "none", +// "bracketSpacing": true, +// "jsxBracketSameLine": false, +// "parser": "flow", +// "semi": true +// } +// ] }, "parserOptions": { "ecmaVersion": 2016, diff --git a/package.json b/package.json index 3b91862..d320e07 100755 --- a/package.json +++ b/package.json @@ -92,10 +92,11 @@ }, "dependencies": { "firebase": "^6.6.1", + "flat": "^5.0.0", "prop-types": "^15.7.2", + "ra-realtime": "^2.8.6", "react": "16.9.0", - "react-admin": "^2.9.6", "react-dom": "^16.9.0", - "sort-by": "^1.2.0" + "react-router-redux": "~5.0.0-alpha.9" } } diff --git a/src/demo/App.js b/src/demo/App.js index ccb608c..0d5d171 100755 --- a/src/demo/App.js +++ b/src/demo/App.js @@ -1,11 +1,12 @@ import React from 'react'; import { Admin, Resource } from 'react-admin'; -import { RestProvider, AuthProvider, base64Uploader } from '../lib'; +// import { RestProvider, AuthProvider, base64Uploader } from '../lib'; +import { RestProvider, base64Uploader, createRealtimeSaga } from '../lib'; import { PostList, PostEdit, PostCreate } from './Posts'; import { UserList, UserEdit, UserCreate } from './Users'; -const firebaseConfig = { +let firebaseConfig = { apiKey: 'AIzaSyASpb1daoPZdpzY_-d1mkzgO-sxoBw6i9o', authDomain: 'react-admin-firestore-client.firebaseapp.com', databaseURL: 'https://react-admin-firestore-client.firebaseio.com', @@ -14,19 +15,32 @@ const firebaseConfig = { messagingSenderId: '796768771332' }; +firebaseConfig = { + apiKey: 'AIzaSyBPTNjD4I30GyYSpD4ixjY2ZQC7pGCyFEA', + authDomain: 'mlbot-257017.firebaseapp.com', + databaseURL: 'https://mlbot-257017.firebaseio.com', + projectId: 'mlbot-257017', + storageBucket: 'mlbot-257017.appspot.com', + messagingSenderId: '995641416070', + appId: '1:995641416070:web:aec2eea9eebd6b98b93407', + measurementId: 'G-G324D9KTNP' +}; + const trackedResources = [{ name: 'posts' }, { name: 'users' }]; -const authConfig = { - userProfilePath: '/users/', - userAdminProp: 'isAdmin' -}; +// const authConfig = { +// userProfilePath: '/users/', +// userAdminProp: 'isAdmin' +// }; // to run this demo locally, please feel free to disable authProvider to bypass login page const dataProvider = base64Uploader(RestProvider(firebaseConfig, { trackedResources })); +const realtimeSaga = createRealtimeSaga(dataProvider); + const App = () => ( - - + + ); diff --git a/src/demo/Posts.js b/src/demo/Posts.js index ca0a29f..37a5f8d 100755 --- a/src/demo/Posts.js +++ b/src/demo/Posts.js @@ -14,11 +14,20 @@ import { SimpleForm, TextInput, ImageInput, - ImageField + ImageField, + Filter } from 'react-admin'; +import Pagination from '../lib/Pagination'; + +const PostListFilter = props => ( + + + +); + export const PostList = props => ( - + } filters={}> @@ -58,3 +67,25 @@ export const PostCreate = props => ( ); + +/* +MACRO: +PIB +TX JUROS +CONSUMO +DEFICIT PUBLICO +CAMBIO +EMPREGO +INFLACAO + +SETORIAL: + +BALANCA DE PAGAMENTOS +PAUTA DE IN/OUT + +CONTABILIDADE SA'S + +EXTRATIVISTA +MINERAL +PAPEL-CELULOSE +*/ diff --git a/src/demo/Users.js b/src/demo/Users.js index 5096837..cca0355 100755 --- a/src/demo/Users.js +++ b/src/demo/Users.js @@ -12,8 +12,10 @@ import { ImageField } from 'react-admin'; +import Pagination from '../lib/Pagination'; + export const UserList = props => ( - + }> diff --git a/src/lib/CollectionGroupButtons.js b/src/lib/CollectionGroupButtons.js new file mode 100644 index 0000000..4ef4526 --- /dev/null +++ b/src/lib/CollectionGroupButtons.js @@ -0,0 +1,9 @@ +import React from "react"; +import { + ShowButton, + CloneButton, + EditButton, + DeleteButton, + DeleteWithUndoButton, + DeleteWithConfirmButton +} from 'react-admin'; diff --git a/src/lib/Pagination.js b/src/lib/Pagination.js new file mode 100644 index 0000000..319ea52 --- /dev/null +++ b/src/lib/Pagination.js @@ -0,0 +1,23 @@ +import React from 'react'; +import Button from '@material-ui/core/Button'; +import ChevronLeft from '@material-ui/icons/ChevronLeft'; +import ChevronRight from '@material-ui/icons/ChevronRight'; +import Toolbar from '@material-ui/core/Toolbar'; + +export default ({ page, perPage, total, setPage, ...props }) => { + // console.log(props); + return ( + + {page > 1 && + + } + {total > (perPage * page) && + + } + + ); +}; diff --git a/src/lib/createRealtimeSaga.js b/src/lib/createRealtimeSaga.js new file mode 100644 index 0000000..057b48d --- /dev/null +++ b/src/lib/createRealtimeSaga.js @@ -0,0 +1,67 @@ +import realtimeSaga from 'ra-realtime'; +import Methods from './methods.js'; + +const observeRequest = (dataProvider, onDataUpdated) => (type, resource, params) => { + return { + subscribe(observer) { + const snapshotParams = Object.assign({}, params); + + snapshotParams[Methods.snapshotFlag] = true; + + let isFirst = true; + + const cancelSnapshotsPromise = dataProvider(type, resource, snapshotParams).then(query => { + return query.onSnapshot( + snapshot => { + if (!isFirst) { + if (onDataUpdated) onDataUpdated(type, resource); + + // New data received, notify the observer + if (snapshot.docs) { + const docs = snapshot.docs.slice(params.pagination.perPage * (params.pagination.page - 1)); + + observer.next({ + data: docs.map(doc => { + const data = doc.data(); + + data.id = data.id || doc.id; + + return data + }), + total: docs.length + }); + } else { + + const data = snapshot.data(); + + data.id = data.id || snapshot.id; + + observer.next({ data }); + } + } + + isFirst = false; + }, + error => { + observer.error(error); // Ouch, an error occured, notify the observer + } + ); + }); + + const subscription = { + unsubscribe() { + // Clean up after ourselves + cancelSnapshotsPromise.then(cancelSnapshots => { + cancelSnapshots(); + // Notify the saga that we cleaned up everything + observer.complete(); + }); + } + }; + + return subscription; + } + }; +}; + +export default (dataProvider, onDataUpdated) => realtimeSaga(observeRequest(dataProvider, onDataUpdated)); diff --git a/src/lib/index.js b/src/lib/index.js index 404ee42..a01f2e3 100755 --- a/src/lib/index.js +++ b/src/lib/index.js @@ -2,5 +2,7 @@ import RestProvider from './RestProvider'; import AuthProvider from './AuthProvider'; import * as RAFirebaseMethods from './methods'; import base64Uploader from './Base64Uploader'; +// import Pagination from './Pagination'; +import createRealtimeSaga from './createRealtimeSaga'; -export { RestProvider, AuthProvider, RAFirebaseMethods, base64Uploader }; +export { RestProvider, AuthProvider, RAFirebaseMethods, base64Uploader, createRealtimeSaga }; diff --git a/src/lib/methods.js b/src/lib/methods.js index 0bcbdd7..92b79e3 100755 --- a/src/lib/methods.js +++ b/src/lib/methods.js @@ -1,8 +1,10 @@ import firebase from 'firebase/app'; import 'firebase/firestore'; import 'firebase/storage'; -import sortBy from 'sort-by'; -import { CREATE } from 'react-admin'; +import { CREATE, changeListParams } from 'react-admin'; +import flatten from 'flat'; + +const snapshotFlag = Symbol('snapshot'); const convertFileToBase64 = file => new Promise((resolve, reject) => { @@ -167,11 +169,14 @@ const getItemID = (params, type, resourceName, resourcePath, resourceData) => { const getOne = async (params, resourceName, resourceData) => { if (params.id) { - let result = await firebase + const query = firebase .firestore() .collection(resourceName) - .doc(params.id) - .get(); + .doc(params.id); + + if (params[snapshotFlag]) return query; + + const result = await query.get(); if (result.exists) { const data = result.data(); @@ -195,44 +200,101 @@ const getOne = async (params, resourceName, resourceData) => { * filter: { author_id: 12 } */ +const listCache = {}; + const getList = async (params, resourceName, resourceData) => { if (params.pagination) { let values = []; - let snapshots = await firebase - .firestore() - .collection(resourceName) - .get(); - for (const snapshot of snapshots.docs) { - const data = snapshot.data(); - if (data && data.id == null) { - data['id'] = snapshot.id; - } - values.push(data); + console.log(listCache); + let query = firebase.firestore(); + + if (resourceName.startsWith('.')) { + query = query.collectionGroup(resourceName.substring(1)) + } else { + query = query.collection(resourceName) } - if (params.filter) { - values = values.filter(item => { - let meetsFilters = true; - for (const key of Object.keys(params.filter)) { - meetsFilters = item[key] === params.filter[key]; + query = query.limit(params.pagination.perPage + 1);//+1 to know if there is next page + + console.log(params.filter); + params.filter = flatten(params.filter); + console.log(params.filter); + + Object.keys(params.filter).forEach(rawField => { + + let op = '=='; + let field = rawField; + console.log(op); + console.log(field); + + if (rawField.startsWith(' .')) { + if (rawField.endsWith('<')) { + op = "<"; + field = rawField.slice(2, -'<'.length); + query = query.orderBy(field) + } else if (rawField.endsWith('<=')) { + op = "<="; + field = rawField.slice(2, -'<='.length); + query = query.orderBy(field) + } else if (rawField.endsWith('==')) { + op = "=="; + field = rawField.slice(2, -'=='.length); + } else if (rawField.endsWith('>')) { + op = ">"; + field = rawField.slice(2, -'>'.length); + query = query.orderBy(field) + } else if (rawField.endsWith('>=')) { + op = ">="; + field = rawField.slice(2, -'>='.length); + query = query.orderBy(field) + } else if (rawField.endsWith(':array-contains')) { + op = "array-contains"; + field = rawField.slice(2, -':array-contains'.length); } - return meetsFilters; - }); + } + + console.log(op); + console.log(field); + + query = query.where(field, op, params.filter[rawField]); + }); + + // if (params.sort) query = query.orderBy(params.sort.field, params.sort.order.toLowerCase()); + + if (params.pagination.page > 1) { + + const startAt = listCache[resourceName]; + + if (startAt) { + query = query.startAt(startAt); + } else { + + params.pagination.page = 1; + + changeListParams(resourceName, params);//how to dispatch this? + return {data: [], total: 0} + } } - if (params.sort) { - values.sort(sortBy(`${params.sort.order === 'ASC' ? '-' : ''}${params.sort.field}`)); + if (params[snapshotFlag]) return query; + + let snapshots = await query.get(); + + for (const snapshot of snapshots.docs.slice(0, params.pagination.perPage)) { + const data = snapshot.data(); + if (data && data.id == null) { + data['id'] = snapshot.id; + } + if (resourceName.startsWith('.')) { + data['.path'] = snapshot.ref.path + } + values.push(data); } - const keys = values.map(i => i.id); - const { page, perPage } = params.pagination; - const _start = (page - 1) * perPage; - const _end = page * perPage; - const data = values ? values.slice(_start, _end) : []; - const ids = keys.slice(_start, _end) || []; - const total = values ? values.length : 0; - return { data, ids, total }; + listCache[resourceName] = snapshots.docs.pop(); + + return { data: values, total: snapshots.docs.length }; } else { throw new Error('Error processing request'); } @@ -240,11 +302,20 @@ const getList = async (params, resourceName, resourceData) => { const getMany = async (params, resourceName, resourceData) => { let data = []; - /* eslint-disable no-await-in-loop */ - for (const id of params.ids) { - let { data: item } = await getOne({ id }, resourceName, resourceData); - data.push(item); - } + + const collection = firebase.firestore().collection(resourceName); + + const snapshots = await Promise.all(params.ids.map(id => collection.doc(id).get())); + + snapshots.forEach(docRef => { + + const doc = docRef.data(); + + doc.id = doc.id || docRef.id; + + data.push(doc); + }); + return { data }; }; @@ -270,5 +341,6 @@ export default { getMany, getManyReference, addUploadFeature, - convertFileToBase64 + convertFileToBase64, + snapshotFlag };