diff --git a/.gitignore b/.gitignore
index 4d29575d..f21726c7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -17,6 +17,7 @@
.env.development.local
.env.test.local
.env.production.local
+.env
npm-debug.log*
yarn-debug.log*
diff --git a/README.md b/README.md
index db3c4c5a..7928d225 100644
--- a/README.md
+++ b/README.md
@@ -1,48 +1,63 @@
## Firetable
-Firtable is a simple CMS for Google Firebase.
+Firetable is a simple CMS for Google Firebase.
-This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
+## Setup instructions
-## Available Scripts
+create a firebase project
-In the project directory, you can run:
+- enable firestore
+- enable google auth
+ create an algolia project
-### `npm start`
+Cloud functions setup
-Runs the app in the development mode.
-Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
+set environment variables
-The page will reload if you make edits.
-You will also see any lint errors in the console.
+```
+firebase functions:config:set algolia.app=YOUR_APP_ID algolia.key=ADMIN_API_KEY
+```
-### `npm test`
+deploy the following callable cloud functions to update and delete algolia records
-Launches the test runner in the interactive watch mode.
-See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
+```
+const functions = require("firebase-functions");
+const env = functions.config();
+const algolia = require("algoliasearch");
+exports.updateAlgoliaRecord = functions.https.onCall(async (data, context) => {
+ const client = algolia(env.algolia.appid, env.algolia.apikey);
+ const index = client.initIndex(data.collection);
+ await index.partialUpdateObject(Object.assign({ objectID: data.id }, data.doc));
+ return true;
+});
-### `npm run build`
+exports.deleteAlgoliaRecord = functions.https.onCall(async (data, context) => {
+ const client = algolia(env.algolia.appid, env.algolia.apikey);
+ const index = client.initIndex(data.collection);
+ await index.deleteObject(data.id);
+ return true;
+});
+```
-Builds the app for production to the `build` folder.
-It correctly bundles React in production mode and optimizes the build for the best performance.
+Clone repo
-The build is minified and the filenames include the hashes.
-Your app is ready to be deployed!
+add .env file to the project directory
-See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
+```
+REACT_APP_FIREBASE_PROJECT_NAME =
+REACT_APP_FIREBASE_PROJECT_KEY =
+REACT_APP_ALGOLIA_APP_ID =
+REACT_APP_ALGOLIA_SEARCH_KEY =
+```
-### `npm run eject`
+install dependencies
-**Note: this is a one-way operation. Once you `eject`, you can’t go back!**
+```
+yarn
+```
-If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
+Run project locally
-Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
-
-You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
-
-## Learn More
-
-You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
-
-To learn React, check out the [React documentation](https://reactjs.org/).
+```
+yarn start
+```
diff --git a/ROADMAP.md b/ROADMAP.md
index 5b3b556b..4d8fc760 100644
--- a/ROADMAP.md
+++ b/ROADMAP.md
@@ -1,6 +1,6 @@
# Firetable Roadmap
-## POC
+## POC ✅
### Initial fields:
@@ -10,7 +10,7 @@
- phone(string) ✅
- url(string) ✅
- Number(number) ✅
-- long text(string)
+- long text(string) ✅
### Functionality:
@@ -25,44 +25,50 @@
### additional fields:
-- single select(string)
-- [https://material-ui.com/components/chips/#chip-array] Multiple select(array of strings)
+- single select(string)✅
+- Multiple select(array of strings)✅
- date(Firebase timestamp)✅
- time(Firebase timestamp)✅
-- file(firebase storage url string)
-- image(firebase storage url string)
+- file (single) 🏗️(missing status indicator)
+- image (single) 🏗️(missing status indicator)
- single select reference(DocReference)
- multi select reference(DocReference)
- rating ✅
### Functionality:
-- Hide/Show columns
-- Delete columns
-- Edit columns
-- Delete tables
-- Edit tables
-- Hide tables
+- Delete columns✅
+- Edit columns✅
- Fixed column
+- Hide/Show columns
- resizable column ✅
- keyboard Navigation:
- Up key to move to the cell above ✅
- Down key to move to the cell bellow, if last cell create a new row ✅
- Tab to go to the next cell ✅
- column / table Create/edit validation
+- Delete tables
+- Edit tables
+- Hide tables
+- On new table add, refresh view to the table view✅
+- import csv to table
## V1
### additional fields:
+- file (multi)
+- image (multi)
- Duration
- Percentage(number)
+- Slider(number)
- Table(Document[])
- Rich Text(html string)
### Functionality:
- Sort rows
+- reorder columns
- Locked columns
- Table view only mode
- SubCollection tables
@@ -89,7 +95,6 @@
### Functionality:
-- import csv to table
- Themes
- Table templates
- Dialog View of a row
diff --git a/package.json b/package.json
index ac3c1c55..2746855f 100644
--- a/package.json
+++ b/package.json
@@ -8,6 +8,7 @@
"@material-ui/icons": "^4.4.1",
"@material-ui/lab": "^4.0.0-alpha.26",
"@material-ui/pickers": "^3.2.5",
+ "@types/algoliasearch": "^3.34.2",
"@types/jest": "24.0.18",
"@types/lodash": "^4.14.138",
"@types/node": "12.7.4",
@@ -15,10 +16,15 @@
"@types/react": "^16.9.2",
"@types/react-data-grid": "^4.0.3",
"@types/react-dom": "16.9.0",
+ "@types/react-instantsearch-dom": "^5.2.6",
"@types/react-router-dom": "^4.3.5",
"@types/react-sortable-hoc": "^0.6.5",
"@types/react-virtualized": "^9.21.4",
+<<<<<<< HEAD
"@types/xlsx": "^0.0.36",
+=======
+ "algoliasearch": "^3.34.0",
+>>>>>>> 388c726eacf3bd64f948944e2771b63a8afddedc
"array-move": "^2.1.0",
"attr-accept": "^1.1.3",
"convert-csv-to-json": "^0.0.15",
diff --git a/src/.env b/src/.env
deleted file mode 100644
index f59a37b8..00000000
--- a/src/.env
+++ /dev/null
@@ -1,2 +0,0 @@
-SKIP_PREFLIGHT_CHECK=true
-REACT_APP_ENV='PRODUCTION'
\ No newline at end of file
diff --git a/src/App.tsx b/src/App.tsx
index 00152814..168327e7 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -7,7 +7,7 @@ import AuthView from "./views/AuthView";
import TableView from "./views/TableView";
import TablesView from "./views/TablesView";
-import { BrowserRouter as Router, Route } from "react-router-dom";
+import { Route } from "react-router-dom";
import { AuthProvider } from "./AuthProvider";
import CustomBrowserRouter from "./util/CustomBrowserRouter";
import PrivateRoute from "./util/PrivateRoute";
diff --git a/src/components/ColumnDialog.tsx b/src/components/ColumnDialog.tsx
deleted file mode 100644
index b94b6103..00000000
--- a/src/components/ColumnDialog.tsx
+++ /dev/null
@@ -1,74 +0,0 @@
-import React, { useState, useEffect } from "react";
-import _camelCase from "lodash/camelCase";
-
-import {
- Button,
- TextField,
- Dialog,
- DialogActions,
- DialogContent,
- DialogContentText,
- DialogTitle,
- Fab,
-} from "@material-ui/core";
-import AddIcon from "@material-ui/icons/Add";
-
-// TODO: Create an interface for props
-export default function ColumnDialog(props: any) {
- const { classes, columnName, updateColumn } = props;
- const [open, setOpen] = React.useState(false);
-
- function handleClickOpen() {
- setOpen(true);
- }
-
- function handleClose() {
- setOpen(false);
- }
- function handleUpdate() {
- // updateColumn(tableName, collectionName);
- handleClose();
- }
-
- return (
-
-
-
-
-
- New table
-
- Create a new Table
- {
- // setTableName(e.target.value);
- }}
- margin="dense"
- id="name"
- label="Table Name"
- type="text"
- fullWidth
- />
-
-
-
- Cancel
-
-
- update
-
-
-
-
- );
-}
diff --git a/src/components/CreateTableDialog.tsx b/src/components/CreateTableDialog.tsx
index a016e46a..fbf8c045 100644
--- a/src/components/CreateTableDialog.tsx
+++ b/src/components/CreateTableDialog.tsx
@@ -1,7 +1,7 @@
import React, { useState, useEffect } from "react";
import AddIcon from "@material-ui/icons/Add";
import _camelCase from "lodash/camelCase";
-
+import useRouter from "../hooks/useRouter";
import {
Button,
TextField,
@@ -15,6 +15,7 @@ import {
// TODO: Create an interface for props
export default function CreateTableDialog(props: any) {
+ const router = useRouter();
const { classes, createTable } = props;
const [open, setOpen] = React.useState(false);
const [tableName, setTableName] = useState("");
@@ -33,6 +34,7 @@ export default function CreateTableDialog(props: any) {
}
function handleCreate() {
createTable(tableName, collectionName);
+ router.history.push(collectionName);
handleClose();
}
diff --git a/src/components/Fields/CheckBox.tsx b/src/components/Fields/CheckBox.tsx
index 7b68ee17..47e59bad 100644
--- a/src/components/Fields/CheckBox.tsx
+++ b/src/components/Fields/CheckBox.tsx
@@ -16,7 +16,7 @@ const CheckBox = (props: Props) => {
name={`checkBox-controlled-${row.id}`}
checked={!!value}
onChange={e => {
- onSubmit(row.ref, !value);
+ onSubmit(!value);
}}
/>
);
diff --git a/src/components/Fields/Date.tsx b/src/components/Fields/Date.tsx
index 12d43a0a..e659edd6 100644
--- a/src/components/Fields/Date.tsx
+++ b/src/components/Fields/Date.tsx
@@ -22,7 +22,7 @@ const Date = (props: Props) => {
const { value, row, onSubmit, fieldType } = props;
function handleDateChange(date: Date | null) {
if (date) {
- onSubmit(row.ref, date);
+ onSubmit(date);
}
}
diff --git a/src/components/Fields/DocSelect.tsx b/src/components/Fields/DocSelect.tsx
new file mode 100644
index 00000000..e24134b0
--- /dev/null
+++ b/src/components/Fields/DocSelect.tsx
@@ -0,0 +1,120 @@
+import React, { useState, useEffect } from "react";
+import SearchIcon from "@material-ui/icons/Search";
+import IconButton from "@material-ui/core/IconButton";
+import { createStyles, makeStyles, Theme } from "@material-ui/core/styles";
+import Popper from "@material-ui/core/Popper";
+import Paper from "@material-ui/core/Paper";
+import ClickAwayListener from "@material-ui/core/ClickAwayListener";
+import algoliasearch from "algoliasearch/lite";
+import { TextField } from "@material-ui/core";
+
+const searchClient = algoliasearch(
+ process.env.REACT_APP_ALGOLIA_APP_ID
+ ? process.env.REACT_APP_ALGOLIA_APP_ID
+ : "",
+ process.env.REACT_APP_ALGOLIA_SEARCH_KEY
+ ? process.env.REACT_APP_ALGOLIA_SEARCH_KEY
+ : ""
+);
+
+const useStyles = makeStyles((theme: Theme) =>
+ createStyles({
+ root: {
+ position: "relative",
+
+ display: "flex",
+ flexWrap: "wrap",
+ },
+ typography: {
+ padding: theme.spacing(2),
+ },
+ textArea: {
+ fontSize: 14,
+ minWidth: 230,
+ },
+ paper: { minWidth: 200 },
+ })
+);
+interface Props {
+ value: any;
+ row: { ref: firebase.firestore.DocumentReference; id: string };
+ onSubmit: Function;
+ collectionPath: string;
+}
+
+const DocSelect = (props: Props) => {
+ const { value, row, onSubmit, collectionPath } = props;
+ const [query, setQuery] = useState(value ? value : "");
+ const [hits, setHits] = useState<{}>([]);
+ const algoliaIndex = searchClient.initIndex(collectionPath);
+ const search = async (query: string) => {
+ const resp = await algoliaIndex.search({ query });
+ setHits(resp.hits);
+ };
+ useEffect(() => {
+ search(query);
+ }, [query]);
+
+ const [anchorEl, setAnchorEl] = useState(null);
+ const classes = useStyles();
+
+ const handleClick = (
+ event: React.MouseEvent
+ ) => {
+ setAnchorEl(event.currentTarget);
+ };
+
+ const open = Boolean(anchorEl);
+ const id = open ? "no-transition-popper" : undefined;
+ const onClickAway = (event: any) => {
+ if (event.target.id !== id) {
+ // onSubmit();
+ // setAnchorEl(null);
+ }
+ };
+ return (
+
+
+
+
+
+
+ {value}
+
+
+ {
+ setQuery(e.target.value);
+ }}
+ />
+
+
+ {/*
+
+ */}
+
+ {/* */}
+
+
+
+
+
+
+ );
+};
+
+const Hit = (props: any) => {
+ return (
+
+
{props.hit.firstName}
+
{props.hit.email}
+
+ );
+};
+
+export default DocSelect;
diff --git a/src/components/Fields/File.tsx b/src/components/Fields/File.tsx
new file mode 100644
index 00000000..3e75e68a
--- /dev/null
+++ b/src/components/Fields/File.tsx
@@ -0,0 +1,71 @@
+import React, { useCallback } from "react";
+import { useDropzone } from "react-dropzone";
+import useUploader from "../../hooks/useFiretable/useUploader";
+
+import { FieldType } from ".";
+import Chip from "@material-ui/core/Chip";
+import { createStyles, makeStyles, Theme } from "@material-ui/core/styles";
+
+// TODO: indicate state completion / error
+// TODO: Create an interface for props
+
+interface Props {
+ value: any;
+ row: { ref: firebase.firestore.DocumentReference; id: string };
+ onSubmit: Function;
+ fieldType: FieldType;
+ fieldName: string;
+}
+
+const useStyles = makeStyles((theme: Theme) =>
+ createStyles({
+ root: {
+ display: "flex",
+ flexWrap: "wrap",
+ },
+ chip: {},
+ })
+);
+
+const File = (props: Props) => {
+ const { fieldName, value, row, onSubmit } = props;
+ const classes = useStyles();
+ const [uploaderState, upload] = useUploader();
+ const onDrop = useCallback(acceptedFiles => {
+ // Do something with the files
+ const imageFile = acceptedFiles[0];
+ if (imageFile) {
+ upload(row.ref, fieldName, [imageFile]);
+ }
+ }, []);
+
+ const { getRootProps, getInputProps, isDragActive } = useDropzone({
+ onDrop,
+ multiple: false,
+ });
+ const handleDelete = () => {
+ onSubmit([]);
+ };
+
+ return (
+
+
+ {value && value.length !== 0 ? (
+
{
+ window.open(value[0].downloadURL);
+ }}
+ onDelete={handleDelete}
+ />
+ ) : isDragActive ? (
+ Drop the files here ...
+ ) : (
+ click to select files
+ )}
+
+ );
+};
+export default File;
diff --git a/src/components/Fields/Image.tsx b/src/components/Fields/Image.tsx
index 3ed8ecfc..358dc90e 100644
--- a/src/components/Fields/Image.tsx
+++ b/src/components/Fields/Image.tsx
@@ -11,17 +11,18 @@ interface Props {
row: { ref: firebase.firestore.DocumentReference; id: string };
onSubmit: Function;
fieldType: FieldType;
+ fieldName: string;
}
-const Image = (props: any) => {
- const { columnData, cellData, cellActions, rowData, rowIndex } = props;
+const Image = (props: Props) => {
+ const { fieldName, value, row } = props;
const [uploaderState, upload] = useUploader();
const [localImage, setLocalImage] = useState(null);
const onDrop = useCallback(acceptedFiles => {
// Do something with the files
const imageFile = acceptedFiles[0];
if (imageFile) {
- upload(rowData.ref, columnData.fieldName, [imageFile]);
+ upload(row.ref, fieldName, [imageFile]);
let url = URL.createObjectURL(imageFile);
setLocalImage(url);
}
@@ -37,10 +38,10 @@ const Image = (props: any) => {
{localImage ? (
-
+
- ) : cellData ? (
-
+ ) : value ? (
+
) : isDragActive ? (
Drop the files here ...
) : (
diff --git a/src/components/Fields/LongText.tsx b/src/components/Fields/LongText.tsx
index ab30c21f..aa83bf05 100644
--- a/src/components/Fields/LongText.tsx
+++ b/src/components/Fields/LongText.tsx
@@ -1,22 +1,89 @@
-import React from "react";
+import React, { useState } from "react";
import ExpandIcon from "@material-ui/icons/AspectRatio";
import IconButton from "@material-ui/core/IconButton";
+import Typography from "@material-ui/core/Typography";
+import Button from "@material-ui/core/Button";
+import { createStyles, makeStyles, Theme } from "@material-ui/core/styles";
+import Popper from "@material-ui/core/Popper";
+import Fade from "@material-ui/core/Fade";
+import Paper from "@material-ui/core/Paper";
+import TextareaAutosize from "@material-ui/core/TextareaAutosize";
+import ClickAwayListener from "@material-ui/core/ClickAwayListener";
+import { onSubmit } from "components/Table/grid-fns";
+const useStyles = makeStyles((theme: Theme) =>
+ createStyles({
+ root: {
+ position: "relative",
+
+ display: "flex",
+ flexWrap: "wrap",
+ },
+ typography: {
+ padding: theme.spacing(2),
+ },
+ textArea: {
+ fontSize: 14,
+ minWidth: 230,
+ },
+ })
+);
interface Props {
- value: firebase.firestore.Timestamp | null;
- row: any;
+ value: any;
+ row: { ref: firebase.firestore.DocumentReference; id: string };
onSubmit: Function;
}
-const UrlLink = (props: any) => {
- const { value, cellActions } = props;
- return value ? (
- <>
-
-
-
- {value}
- >
- ) : null;
+const LongText = (props: Props) => {
+ const { value, row, onSubmit } = props;
+ const [text, setText] = useState(value ? value : "");
+
+ const [anchorEl, setAnchorEl] = useState(null);
+ const classes = useStyles();
+
+ const handleClick = (
+ event: React.MouseEvent
+ ) => {
+ setAnchorEl(event.currentTarget);
+ };
+
+ const open = Boolean(anchorEl);
+ const id = open ? "no-transition-popper" : undefined;
+ const onClickAway = (event: any) => {
+ if (event.target.id !== id) {
+ onSubmit(text);
+ setAnchorEl(null);
+ }
+ };
+ return (
+
+
+
+
+
+
+ {text}
+
+
+
+ {
+ setText(e.target.value);
+ }}
+ />
+
+
+
+
+
+
+ );
};
-export default UrlLink;
+export default LongText;
diff --git a/src/components/Fields/MultiSelect.tsx b/src/components/Fields/MultiSelect.tsx
new file mode 100644
index 00000000..d9f27430
--- /dev/null
+++ b/src/components/Fields/MultiSelect.tsx
@@ -0,0 +1,118 @@
+import React from "react";
+import EditIcon from "@material-ui/icons/Edit";
+import WarningIcon from "@material-ui/icons/Warning";
+import { Select } from "@material-ui/core";
+import {
+ createStyles,
+ makeStyles,
+ useTheme,
+ Theme,
+} from "@material-ui/core/styles";
+
+import Input from "@material-ui/core/Input";
+import Grid from "@material-ui/core/Grid";
+import MenuItem from "@material-ui/core/MenuItem";
+import Chip from "@material-ui/core/Chip";
+import Typography from "@material-ui/core/Typography";
+
+const useStyles = makeStyles((theme: Theme) =>
+ createStyles({
+ root: {
+ display: "flex",
+ flexWrap: "wrap",
+ },
+ formControl: {
+ margin: theme.spacing(1),
+ minWidth: 120,
+ maxWidth: 300,
+ },
+ chips: {
+ display: "flex",
+ flexWrap: "wrap",
+ },
+ chip: {
+ margin: 2,
+ },
+ noLabel: {
+ marginTop: theme.spacing(3),
+ },
+ noOptions: {
+ position: "absolute",
+ top: -15,
+ },
+ })
+);
+interface Props {
+ value: string[] | null;
+ row: { ref: firebase.firestore.DocumentReference; id: string };
+ options: string[];
+ onSubmit: Function;
+}
+const ITEM_HEIGHT = 48;
+const ITEM_PADDING_TOP = 8;
+const MenuProps = {
+ PaperProps: {
+ style: {
+ maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP,
+ width: 250,
+ },
+ },
+};
+
+const MultiSelect = (props: Props) => {
+ const classes = useStyles();
+
+ const { value, row, options, onSubmit } = props;
+ const handleChange = (e: any, v: any) => {
+ if (!value) {
+ // creates new array
+ onSubmit([v.props.value]);
+ } else if (!value.includes(v.props.value)) {
+ // adds to array
+ onSubmit([...value, v.props.value]);
+ } else {
+ // removes from array
+ let _updatedValues = [...value];
+ const index = _updatedValues.indexOf(v.props.value);
+ _updatedValues.splice(index, 1);
+ onSubmit(_updatedValues);
+ }
+ };
+
+ if (options && options.length !== 0)
+ return (
+ }
+ renderValue={selected => (
+
+ {(selected as string[]).map(value => (
+
+ ))}
+
+ )}
+ MenuProps={MenuProps}
+ >
+ {options.map(option => (
+
+ {option}
+
+ ))}
+
+ );
+ else
+ return (
+
+ {/*
+
+ {" "} */}
+
+ add options!
+
+
+ );
+};
+export default MultiSelect;
diff --git a/src/components/Fields/Rating.tsx b/src/components/Fields/Rating.tsx
index 8d5f9bb3..2f57bac7 100644
--- a/src/components/Fields/Rating.tsx
+++ b/src/components/Fields/Rating.tsx
@@ -17,7 +17,7 @@ const Rating = (props: Props) => {
name={`rating-controlled-${row.id}`}
value={value}
onChange={(event, newValue) => {
- onSubmit(row.ref, newValue);
+ onSubmit(newValue);
}}
/>
);
diff --git a/src/components/Fields/UrlLink.tsx b/src/components/Fields/UrlLink.tsx
index 37a519fe..95f3cbe0 100644
--- a/src/components/Fields/UrlLink.tsx
+++ b/src/components/Fields/UrlLink.tsx
@@ -11,7 +11,7 @@ const UrlLink = (props: Props) => {
return value ? (
<>
-
+
{value}
>
diff --git a/src/components/Fields/index.tsx b/src/components/Fields/index.tsx
index 5aaf080f..9ab3723c 100644
--- a/src/components/Fields/index.tsx
+++ b/src/components/Fields/index.tsx
@@ -28,6 +28,10 @@ export enum FieldType {
rating = "RATING",
image = "IMAGE",
file = "FILE",
+ singleSelect = "SINGLE_SELECT",
+ multiSelect = "MULTI_SELECT",
+ documentSelect = "DOCUMENT_SELECT",
+ documentsSelect = "DOCUMENTS_SELECT",
}
export const FIELDS = [
@@ -43,6 +47,10 @@ export const FIELDS = [
{ icon: , name: "Rating", type: FieldType.rating },
{ icon: , name: "Image", type: FieldType.image },
{ icon: , name: "File", type: FieldType.file },
+ { icon: , name: "Single Select", type: FieldType.singleSelect },
+ { icon: , name: "Multi Select", type: FieldType.multiSelect },
+ { icon: , name: "Doc Select", type: FieldType.documentSelect },
+ { icon: , name: "Docs Select", type: FieldType.documentsSelect },
];
export const getFieldIcon = (type: FieldType) => {
diff --git a/src/components/Navigation.tsx b/src/components/Navigation.tsx
index 0b63eb23..9ad06eaf 100644
--- a/src/components/Navigation.tsx
+++ b/src/components/Navigation.tsx
@@ -18,6 +18,7 @@ import CreateTableDialog from "./CreateTableDialog";
import useSettings from "../hooks/useSettings";
import useRouter from "../hooks/useRouter";
+import TablesContext from "../contexts/tablesContext";
const useStyles = makeStyles(theme =>
createStyles({
@@ -63,69 +64,71 @@ const Navigation = (props: any) => {
const classes = useStyles();
const [settings, createTable] = useSettings();
return (
-
-
-
-
- {props.header}
-
-
- {props.children}
-
-
-
-
-
- {!settings.tables ? (
- <>
-
-
-
-
- >
- ) : (
- <>
- {settings.tables.map(
- (table: { name: string; collection: string }) => (
- {
- router.history.push(table.collection);
- }}
- className={classes.button}
- >
- {table.name}
-
- )
- )}
- >
- )}
+
+
+
+
+
+ {props.header}
+
+
+ {props.children}
+
+
+
+
+
+ {!settings.tables ? (
+ <>
+
+
+
+
+ >
+ ) : (
+ <>
+ {settings.tables.map(
+ (table: { name: string; collection: string }) => (
+ {
+ router.history.push(table.collection);
+ }}
+ className={classes.button}
+ >
+ {table.name}
+
+ )
+ )}
+ >
+ )}
-
-
-
-
-
+
+
+
+
+
+
);
};
export default Navigation;
diff --git a/src/components/Table/ColumnEditor/DocInput.tsx b/src/components/Table/ColumnEditor/DocInput.tsx
new file mode 100644
index 00000000..794440cf
--- /dev/null
+++ b/src/components/Table/ColumnEditor/DocInput.tsx
@@ -0,0 +1,170 @@
+import React, { useContext, useEffect } from "react";
+import { createStyles, makeStyles, Theme } from "@material-ui/core/styles";
+import Chip from "@material-ui/core/Chip";
+import { TextField, Grid, Divider, Select } from "@material-ui/core";
+import TablesContext from "../../../contexts/tablesContext";
+import MenuItem from "@material-ui/core/MenuItem";
+import useTableConfig from "../../../hooks/useFiretable/useTableConfig";
+import FormControl from "@material-ui/core/FormControl";
+import InputLabel from "@material-ui/core/InputLabel";
+import Input from "@material-ui/core/Input";
+
+const useStyles = makeStyles(Theme =>
+ createStyles({
+ root: {
+ display: "flex",
+ justifyContent: "center",
+ flexWrap: "wrap",
+ maxWidth: 300,
+ padding: Theme.spacing(1),
+ },
+ chip: {
+ margin: Theme.spacing(0.5),
+ },
+ formControl: {
+ margin: Theme.spacing(1),
+ minWidth: 120,
+ maxWidth: 300,
+ },
+ chips: {
+ display: "flex",
+ flexWrap: "wrap",
+ },
+ })
+);
+
+const ITEM_HEIGHT = 48;
+const ITEM_PADDING_TOP = 8;
+const MenuProps = {
+ PaperProps: {
+ style: {
+ maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP,
+ width: 250,
+ },
+ },
+};
+
+export default function DocInput(props: any) {
+ const { collectionPath, setValue } = props;
+ const [tableConfig, tableConfigActions] = useTableConfig(collectionPath);
+
+ const [columns, setColumns] = React.useState<{ key: string; name: string }[]>(
+ []
+ );
+ const [primaryKeys, setPrimaryKeys] = React.useState([]);
+ const [secondaryKeys, setSecondaryKeys] = React.useState([]);
+
+ useEffect(() => {
+ console.log(tableConfig);
+ setColumns(tableConfig.columns);
+ }, [tableConfig.columns]);
+
+ const classes = useStyles();
+ const tables = useContext(TablesContext);
+ const onChange = (e: any, v: any) => {
+ setValue("collectionPath", v.props.value);
+ setPrimaryKeys([]);
+ setSecondaryKeys([]);
+ setColumns([]);
+ tableConfigActions.setTable(v.props.value);
+ };
+ useEffect(() => {
+ setValue("resultsConfig", {
+ primaryKeys,
+ secondaryKeys,
+ });
+ }, [primaryKeys, secondaryKeys]);
+ if (tables.value)
+ return (
+ <>
+
+ {tables.value.map((table: { collection: string; table: string }) => {
+ return (
+
+ <>{table.collection}>
+
+ );
+ })}
+
+
+
+ Primary Text
+
+ ) => {
+ setPrimaryKeys(event.target.value as string[]);
+ }}
+ input={ }
+ renderValue={selected => (
+
+ {(selected as string[]).map(value => (
+
+ ))}
+
+ )}
+ MenuProps={MenuProps}
+ >
+ {columns &&
+ columns.length !== 0 &&
+ columns.map(column => (
+
+ {column.name}
+
+ ))}
+
+
+
+
+
+ Secondary Text
+
+ ) => {
+ setSecondaryKeys(event.target.value as string[]);
+ }}
+ input={ }
+ renderValue={selected => (
+
+ {(selected as string[]).map(value => (
+
+ ))}
+
+ )}
+ MenuProps={MenuProps}
+ >
+ {columns &&
+ columns.length !== 0 &&
+ columns.map(column => (
+
+ {column.name}
+
+ ))}
+
+
+ >
+ );
+ else return
;
+}
diff --git a/src/components/Table/ColumnEditor/SelectOptionsInput.tsx b/src/components/Table/ColumnEditor/SelectOptionsInput.tsx
new file mode 100644
index 00000000..8ded85e2
--- /dev/null
+++ b/src/components/Table/ColumnEditor/SelectOptionsInput.tsx
@@ -0,0 +1,70 @@
+import React, { useEffect } from "react";
+import { createStyles, makeStyles, Theme } from "@material-ui/core/styles";
+import Chip from "@material-ui/core/Chip";
+import { TextField, Grid, Divider } from "@material-ui/core";
+import _includes from "lodash/includes";
+import _camelCase from "lodash/camelCase";
+
+const useStyles = makeStyles(Theme =>
+ createStyles({
+ root: {
+ display: "flex",
+ justifyContent: "center",
+ flexWrap: "wrap",
+ maxWidth: 300,
+ padding: Theme.spacing(1),
+ },
+ chip: {
+ margin: Theme.spacing(0.5),
+ },
+ })
+);
+
+export default function SelectOptionsInput(props: any) {
+ const { options, setValue } = props;
+ const classes = useStyles();
+
+ const handleAdd = (newOption: string) => {
+ // setOptions([...options, newOption]);
+ setValue("options", [...options, newOption]);
+ };
+ const handleDelete = (optionToDelete: string) => () => {
+ const newOptions = options.filter(
+ (option: string) => option !== optionToDelete
+ );
+ setValue("options", newOptions);
+ };
+
+ useEffect(() => {
+ setValue({ data: { options } });
+ }, [options]);
+
+ return (
+
+
+ {
+ const value = e.target.value;
+ if (e.key === "Enter") {
+ handleAdd(value);
+ e.target.value = "";
+ }
+ }}
+ />
+
+
+ {options.map((option: string) => {
+ return (
+
+ );
+ })}
+
+
+ );
+}
diff --git a/src/components/Table/HeaderPopper.tsx b/src/components/Table/ColumnEditor/index.tsx
similarity index 56%
rename from src/components/Table/HeaderPopper.tsx
rename to src/components/Table/ColumnEditor/index.tsx
index 4b6b3f24..d11eed7c 100644
--- a/src/components/Table/HeaderPopper.tsx
+++ b/src/components/Table/ColumnEditor/index.tsx
@@ -1,4 +1,4 @@
-import React, { useEffect } from "react";
+import React, { useEffect, useContext, useState } from "react";
import Button from "@material-ui/core/Button";
import Typography from "@material-ui/core/Typography";
import InputLabel from "@material-ui/core/InputLabel";
@@ -11,8 +11,8 @@ import Fade from "@material-ui/core/Fade";
import Paper from "@material-ui/core/Paper";
import ClickAwayListener from "@material-ui/core/ClickAwayListener";
import { createStyles, makeStyles } from "@material-ui/core/styles";
-import { TextField, Grid } from "@material-ui/core";
-import { FieldsDropDown, isFieldType } from "../Fields";
+import { TextField, Grid, Select } from "@material-ui/core";
+import { FieldsDropDown, isFieldType, FieldType } from "../../Fields";
import ToggleButton from "@material-ui/lab/ToggleButton";
import ToggleButtonGroup from "@material-ui/lab/ToggleButtonGroup";
@@ -20,9 +20,12 @@ import LockIcon from "@material-ui/icons/Lock";
import LockOpenIcon from "@material-ui/icons/LockOpen";
import VisibilityIcon from "@material-ui/icons/Visibility";
import VisibilityOffIcon from "@material-ui/icons/VisibilityOff";
-import FormatItalicIcon from "@material-ui/icons/FormatItalic";
import FormatUnderlinedIcon from "@material-ui/icons/FormatUnderlined";
import FormatColorFillIcon from "@material-ui/icons/FormatColorFill";
+import DeleteIcon from "@material-ui/icons/Delete";
+import SelectOptionsInput from "./SelectOptionsInput";
+
+import DocInput from "./DocInput";
const useStyles = makeStyles(Theme =>
createStyles({
@@ -66,13 +69,16 @@ const useStyles = makeStyles(Theme =>
})
);
-const HeaderPopper = (props: any) => {
+const ColumnEditor = (props: any) => {
const { anchorEl, column, handleClose, actions } = props;
- const [values, setValues] = React.useState({
+
+ const [values, setValues] = useState({
type: null,
name: "",
+ options: [],
+ collectionPath: "",
});
- const [flags, setFlags] = React.useState(() => [""]);
+ const [flags, setFlags] = useState(() => [""]);
const classes = useStyles();
function handleChange(
@@ -91,7 +97,7 @@ const HeaderPopper = (props: any) => {
};
useEffect(() => {
- if (column && !column.isNew)
+ if (column && !column.isNew) {
setValues(oldValues => ({
...oldValues,
name: column.name,
@@ -99,11 +105,25 @@ const HeaderPopper = (props: any) => {
key: column.key,
isNew: column.isNew,
}));
+ if (column.options) {
+ setValue("options", column.options);
+ } else {
+ setValue("options", []);
+ }
+ if (column.collectionPath) {
+ setValue("collectionPath", column.collectionPath);
+ }
+ }
}, [column]);
+ const clearValues = () => {
+ setValues({ type: null, name: "", options: [], collectionPath: "" });
+ };
const onClickAway = (event: any) => {
- const dropDownClicked = isFieldType(event.target.dataset.value);
- if (!dropDownClicked) {
+ const elementId = event.target.id;
+ console.log(event, elementId);
+ if (!elementId.includes("select")) {
handleClose();
+ clearValues();
}
};
const handleToggle = (
@@ -114,9 +134,43 @@ const HeaderPopper = (props: any) => {
};
const createNewColumn = () => {
- const { name, type } = values;
- actions.add(name, type);
+ const { name, type, options, collectionPath } = values;
+
+ actions.add(name, type, { options, collectionPath });
+
handleClose();
+ clearValues();
+ };
+ const deleteColumn = () => {
+ actions.remove(props.column.idx);
+ handleClose();
+ clearValues();
+ };
+
+ const updateColumn = () => {
+ let updatables: { field: string; value: any }[] = [
+ { field: "name", value: values.name },
+ { field: "type", value: values.type },
+ // { field: "resizable", value: flags.includes("resizable") },
+ ];
+ if (
+ values.type === FieldType.multiSelect ||
+ values.type === FieldType.singleSelect
+ ) {
+ updatables.push({ field: "options", value: values.options });
+ }
+ if (
+ values.type === FieldType.documentSelect ||
+ values.type === FieldType.documentsSelect
+ ) {
+ updatables.push({
+ field: "collectionPath",
+ value: values.collectionPath,
+ });
+ }
+ actions.update(props.column.idx, updatables);
+ handleClose();
+ clearValues();
};
if (column) {
@@ -132,6 +186,8 @@ const HeaderPopper = (props: any) => {
+ {/*
+ // TODO: functional flags
{
onChange={handleToggle}
arial-label="column settings"
>
-
+
{flags.includes("editable") ? (
) : (
)}
-
+
{flags.includes("visible") ? (
) : (
)}
-
+
-
+
-
+ */}
{
setValue("name", e.target.value);
}}
@@ -174,10 +229,42 @@ const HeaderPopper = (props: any) => {
Field Type
{FieldsDropDown(values.type, handleChange)}
- {column.isNew && (
- Add
+
+ {(values.type === FieldType.singleSelect ||
+ values.type === FieldType.multiSelect) && (
+
)}
-
+ {(values.type === FieldType.documentSelect ||
+ values.type === FieldType.documentsSelect) && (
+
+ )}
+ {column.isNew ? (
+ Add
+ ) : (
+ update
+ )}
+ {!column.isNew && (
+
+ Delete
+
+ )}
+ {
+ handleClose();
+ clearValues();
+ }}
+ >
cancel
@@ -192,4 +279,4 @@ const HeaderPopper = (props: any) => {
return
;
};
-export default HeaderPopper;
+export default ColumnEditor;
diff --git a/src/components/Table/grid-fns.tsx b/src/components/Table/grid-fns.tsx
new file mode 100644
index 00000000..4f20fb0f
--- /dev/null
+++ b/src/components/Table/grid-fns.tsx
@@ -0,0 +1,153 @@
+import React from "react";
+import { FieldType } from "../Fields";
+
+import Date from "../Fields/Date";
+import Rating from "../Fields/Rating";
+import CheckBox from "../Fields/CheckBox";
+import UrlLink from "../Fields/UrlLink";
+
+import { Editors } from "react-data-grid-addons";
+import MultiSelect from "../Fields/MultiSelect";
+import Image from "../Fields/Image";
+import File from "../Fields/File";
+import LongText from "../Fields/LongText";
+import DocSelect from "../Fields/DocSelect";
+import { CLOUD_FUNCTIONS } from "firebase/callables";
+import { functions } from "../../firebase";
+
+const algoliaUpdateDoc = functions.httpsCallable(
+ CLOUD_FUNCTIONS.updateAlgoliaRecord
+);
+const { AutoComplete } = Editors;
+
+export const editable = (fieldType: FieldType) => {
+ switch (fieldType) {
+ case FieldType.date:
+ case FieldType.dateTime:
+ case FieldType.rating:
+ case FieldType.checkBox:
+ case FieldType.multiSelect:
+ case FieldType.image:
+ case FieldType.file:
+ case FieldType.longText:
+ case FieldType.documentSelect:
+ return false;
+ default:
+ return true;
+ }
+};
+export const onSubmit = (key: string, row: any) => async (value: any) => {
+ const collection = row.ref.parent.path;
+ const data = { collection, id: row.ref.id, doc: { [key]: value } };
+ if (value !== null || value !== undefined) {
+ row.ref.update({ [key]: value });
+ const callableRes = await algoliaUpdateDoc(data);
+ console.log(callableRes);
+ }
+};
+
+export const DateFormatter = (key: string, fieldType: FieldType) => (
+ props: any
+) => {
+ return (
+
+ );
+};
+
+export const onGridRowsUpdated = (props: any) => {
+ const { fromRowData, updated } = props;
+ onSubmit(Object.keys(updated)[0], fromRowData)(Object.values(updated)[0]);
+};
+export const onCellSelected = (args: any) => {
+ console.log(args);
+};
+export const cellFormatter = (column: any) => {
+ const { type, key, options } = column;
+ switch (type) {
+ case FieldType.date:
+ case FieldType.dateTime:
+ return DateFormatter(key, type);
+ case FieldType.rating:
+ return (props: any) => {
+ return (
+
+ );
+ };
+ case FieldType.checkBox:
+ return (props: any) => {
+ return ;
+ };
+ case FieldType.url:
+ return (props: any) => {
+ return ;
+ };
+ case FieldType.multiSelect:
+ return (props: any) => {
+ return (
+
+ );
+ };
+ case FieldType.image:
+ return (props: any) => {
+ return (
+
+ );
+ };
+ case FieldType.file:
+ return (props: any) => {
+ return (
+
+ );
+ };
+ case FieldType.longText:
+ return (props: any) => {
+ return ;
+ };
+ case FieldType.documentSelect:
+ return (props: any) => {
+ return (
+
+ );
+ };
+ default:
+ return false;
+ }
+};
+
+export const singleSelectEditor = (options: string[]) => {
+ if (options) {
+ const _options = options.map(option => ({
+ id: option,
+ value: option,
+ title: option,
+ text: option,
+ }));
+ return ;
+ }
+
+ return ;
+};
diff --git a/src/components/Table/index.tsx b/src/components/Table/index.tsx
index a7d9f661..90e2837e 100644
--- a/src/components/Table/index.tsx
+++ b/src/components/Table/index.tsx
@@ -1,20 +1,26 @@
-import React, { useState } from "react";
+import React, { useState, useEffect } from "react";
import ReactDataGrid from "react-data-grid";
-
import useFiretable from "../../hooks/useFiretable";
-import Typography from "@material-ui/core/Typography";
-
import { createStyles, Theme, makeStyles } from "@material-ui/core/styles";
-import IconButton from "@material-ui/core/IconButton";
+
import Button from "@material-ui/core/Button";
-import EditIcon from "@material-ui/icons/Edit";
-import HeaderPopper from "./HeaderPopper";
import { FieldType, getFieldIcon } from "../Fields";
-import Date from "../Fields/Date";
-import Rating from "../Fields/Rating";
-import CheckBox from "../Fields/CheckBox";
-import UrlLink from "../Fields/UrlLink";
+import ColumnEditor from "./ColumnEditor/index";
+import { functions } from "../../firebase";
+
+import {
+ cellFormatter,
+ onCellSelected,
+ onGridRowsUpdated,
+ singleSelectEditor,
+ editable,
+} from "./grid-fns";
+
+import { CLOUD_FUNCTIONS } from "firebase/callables";
+const deleteAlgoliaRecord = functions.httpsCallable(
+ CLOUD_FUNCTIONS.deleteAlgoliaRecord
+);
const useStyles = makeStyles(Theme =>
createStyles({
@@ -32,66 +38,12 @@ const useStyles = makeStyles(Theme =>
})
);
-const copyPaste = (props: any) => {
- console.log(props);
-};
-const editable = (fieldType: FieldType) => {
- switch (fieldType) {
- case FieldType.date:
- case FieldType.dateTime:
- case FieldType.rating:
- case FieldType.checkBox:
- return false;
-
- default:
- return true;
- }
-};
-const onSubmit = (key: string) => (
- ref: firebase.firestore.DocumentReference,
- value: any
-) => {
- if (value !== null || value !== undefined) {
- ref.update({ [key]: value });
- }
-};
-
-const DateFormatter = (key: string, fieldType: FieldType) => (props: any) => {
- return ;
-};
-
-const formatter = (fieldType: FieldType, key: string) => {
- switch (fieldType) {
- case FieldType.date:
- case FieldType.dateTime:
- return DateFormatter(key, fieldType);
- case FieldType.rating:
- return (props: any) => {
- return (
-
- );
- };
- case FieldType.checkBox:
- return (props: any) => {
- return ;
- };
- case FieldType.url:
- return (props: any) => {
- return ;
- };
- default:
- return false;
- }
-};
-
function Table(props: any) {
const { collection } = props;
const { tableState, tableActions } = useFiretable(collection);
-
+ useEffect(() => {
+ tableActions.set(collection);
+ }, [collection]);
const classes = useStyles();
const [anchorEl, setAnchorEl] = useState(null);
@@ -109,14 +61,6 @@ function Table(props: any) {
setHeader(headerProps);
};
- const onGridRowsUpdated = (props: any) => {
- const { fromRowData, updated } = props;
- fromRowData.ref.update(updated);
- };
- const onCellSelected = (args: any) => {
- // handleCloseHeader();
- console.log(args);
- };
const headerRenderer = (props: any) => {
const { column } = props;
switch (column.key) {
@@ -152,13 +96,14 @@ function Table(props: any) {
if (tableState.columns) {
let columns = tableState.columns.map((column: any) => ({
width: 220,
- resizable: true,
- key: column.fieldName,
- name: column.columnName,
editable: editable(column.type),
- resizeable: true,
+ resizable: true,
headerRenderer: headerRenderer,
- formatter: formatter(column.type, column.fieldName),
+ formatter: cellFormatter(column),
+ editor:
+ column.type === FieldType.singleSelect
+ ? singleSelectEditor(column.options)
+ : false,
...column,
}));
columns.push({
@@ -167,25 +112,52 @@ function Table(props: any) {
name: "Add column",
width: 160,
headerRenderer: headerRenderer,
+ formatter: (props: any) => (
+ {
+ props.row.ref.delete();
+ await deleteAlgoliaRecord({
+ id: props.row.ref.id,
+ collection: props.row.ref.parent.path,
+ });
+ }}
+ >
+ Delete row
+
+ ),
});
- const rows = tableState.rows;
+ const rows = tableState.rows; //.map((row: any) => ({ height: 100, ...row }));
return (
<>
rows[i]}
rowsCount={rows.length}
onGridRowsUpdated={onGridRowsUpdated}
enableCellSelect={true}
- onCellCopyPaste={copyPaste}
minHeight={500}
onCellSelected={onCellSelected}
onColumnResize={(idx, width) =>
tableActions.column.resize(idx, width)
}
+ emptyRowsView={() => {
+ return (
+
+
no data to show
+ Add Row
+
+ );
+ }}
/>
Add Row
- ({
+ value: undefined,
+});
+
+export default TablesContext;
diff --git a/src/firebase/callables.ts b/src/firebase/callables.ts
index faf0123e..483f03cb 100644
--- a/src/firebase/callables.ts
+++ b/src/firebase/callables.ts
@@ -1,6 +1,9 @@
import { functions } from "./index";
-export enum CLOUD_FUNCTIONS {}
+export enum CLOUD_FUNCTIONS {
+ updateAlgoliaRecord = "updateAlgoliaRecord",
+ deleteAlgoliaRecord = "deleteAlgoliaRecord",
+}
export const cloudFunction = (
name: string,
diff --git a/src/firebase/config.ts b/src/firebase/config.ts
deleted file mode 100644
index d099a088..00000000
--- a/src/firebase/config.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-const STAGING_PROJECT_NAME = "antler-vc";
-const PRODUCTION_PROJECT_NAME = STAGING_PROJECT_NAME;
-
-const stagingKey = "AIzaSyCADXbyMviWpJ_jPp4leEYMffL70Ahxo_k";
-const productionKey = stagingKey;
-
-export const stagingConfig = {
- apiKey: stagingKey,
- authDomain: `${STAGING_PROJECT_NAME}.firebaseapp.com`,
- databaseURL: `https://${STAGING_PROJECT_NAME}.firebaseio.com`,
- projectId: STAGING_PROJECT_NAME,
- storageBucket: `${STAGING_PROJECT_NAME}.appspot.com`,
- messagingSenderId: "236015562107",
-};
-
-export const productionConfig = stagingConfig;
diff --git a/src/firebase/index.ts b/src/firebase/index.ts
index 75601f09..28182a90 100644
--- a/src/firebase/index.ts
+++ b/src/firebase/index.ts
@@ -4,18 +4,18 @@ import "firebase/firestore";
import "firebase/functions";
import "firebase/storage";
-import { productionConfig, stagingConfig } from "./config";
+const config = {
+ apiKey: process.env.REACT_APP_FIREBASE_PROJECT_KEY,
+ authDomain: `${process.env.REACT_APP_FIREBASE_PROJECT_NAME}.firebaseapp.com`,
+ databaseURL: `https://${process.env.REACT_APP_FIREBASE_PROJECT_NAME}.firebaseio.com`,
+ projectId: process.env.REACT_APP_FIREBASE_PROJECT_NAME,
+ storageBucket: `${process.env.REACT_APP_FIREBASE_PROJECT_NAME}.appspot.com`,
+};
-if (process.env.REACT_APP_ENV === "PRODUCTION") {
- console.log("production");
- firebase.initializeApp(productionConfig);
-} else {
- console.log("staging");
- firebase.initializeApp(stagingConfig);
-}
+firebase.initializeApp(config);
export const auth = firebase.auth();
export const db = firebase.firestore();
export const bucket = firebase.storage();
-export const functions = firebase.app().functions();
+export const functions = firebase.functions();
export const googleProvider = new firebase.auth.GoogleAuthProvider();
diff --git a/src/hooks/useFiretable/index.ts b/src/hooks/useFiretable/index.ts
index 696178f3..744e10f0 100644
--- a/src/hooks/useFiretable/index.ts
+++ b/src/hooks/useFiretable/index.ts
@@ -1,16 +1,19 @@
import useTable from "./useTable";
import useTableConfig from "./useTableConfig";
-import useCell, { Cell } from "./useCell";
export type FiretableActions = {
- cell: { set: Function; update: Function };
- column: { add: Function; resize: Function; rename: Function };
+ column: {
+ add: Function;
+ resize: Function;
+ rename: Function;
+ remove: Function;
+ update: Function;
+ };
row: { add: any; delete: Function };
- table: { set: Function };
+ set: Function;
};
export type FiretableState = {
- cell: Cell;
columns: any;
rows: any;
};
@@ -20,27 +23,28 @@ const useFiretable = (collectionName: string) => {
const [tableState, tableActions] = useTable({
path: collectionName,
});
- const [cellState, cellActions] = useCell({});
const setTable = (collectionName: string) => {
configActions.setTable(collectionName);
tableActions.setTable(collectionName);
- cellActions.set(null);
};
const state: FiretableState = {
- cell: cellState.cell,
columns: tableConfig.columns,
rows: tableState.rows,
};
const actions: FiretableActions = {
- cell: { ...cellActions },
column: {
add: configActions.add,
resize: configActions.resize,
rename: configActions.rename,
+ update: configActions.update,
+ remove: configActions.remove,
},
- row: { add: tableActions.addRow, delete: tableActions.deleteRow },
- table: { set: setTable },
+ row: {
+ add: tableActions.addRow,
+ delete: tableActions.deleteRow,
+ },
+ set: setTable,
};
return { tableState: state, tableActions: actions };
diff --git a/src/hooks/useFiretable/useCell.ts b/src/hooks/useFiretable/useCell.ts
deleted file mode 100644
index 7ac756ed..00000000
--- a/src/hooks/useFiretable/useCell.ts
+++ /dev/null
@@ -1,57 +0,0 @@
-import { db } from "../../firebase";
-import { useEffect, useReducer } from "react";
-import equals from "ramda/es/equals";
-
-export type Cell = {
- fieldName: string;
- rowIndex: number;
- docRef: firebase.firestore.DocumentReference;
- value: any;
-};
-
-const cellReducer = (prevState: any, newProps: any) => {
- return { ...prevState, ...newProps };
-};
-const cellIntialState = {
- prevCell: null,
- cell: null,
-};
-
-const updateCell = (cell: Cell) => {
- cell.docRef.update({ [cell.fieldName]: cell.value });
-};
-const useCell = (intialOverrides: any) => {
- const [cellState, cellDispatch] = useReducer(cellReducer, {
- ...cellIntialState,
- ...intialOverrides,
- });
- useEffect(() => {
- const { prevCell, updatedValue } = cellState;
- // check for change
- if (
- updatedValue !== null &&
- updatedValue !== undefined &&
- prevCell &&
- prevCell.value !== updatedValue
- ) {
- updateCell({ ...prevCell, value: updatedValue });
- cellDispatch({ updatedValue: null });
- }
- }, [cellState.cell]);
- const set = (cell: Cell | null) => {
- cellDispatch({ prevCell: cellState.cell, cell });
- };
- const update = (value: any) => {
- cellDispatch({ updatedValue: value });
- };
-
- const updateFirestore = (cell: Cell) => {
- cellDispatch({ cell: null });
- updateCell(cell);
- };
- const actions = { set, update, updateFirestore };
-
- return [cellState, actions];
-};
-
-export default useCell;
diff --git a/src/hooks/useFiretable/useTable.ts b/src/hooks/useFiretable/useTable.ts
index 0d03883c..c8eddb9f 100644
--- a/src/hooks/useFiretable/useTable.ts
+++ b/src/hooks/useFiretable/useTable.ts
@@ -2,7 +2,6 @@ import { db } from "../../firebase";
import { useEffect, useReducer } from "react";
import equals from "ramda/es/equals";
-import { Cell } from "./useCell";
import firebase from "firebase/app";
const CAP = 500;
@@ -11,7 +10,7 @@ const tableReducer = (prevState: any, newProps: any) => {
switch (newProps.type) {
case "more":
if (prevState.limit < prevState.cap)
- // rows count hardcap
+ // rows count hardCap
return { ...prevState, limit: prevState.limit + 10 };
else return { ...prevState };
default:
@@ -21,7 +20,7 @@ const tableReducer = (prevState: any, newProps: any) => {
return { ...prevState, ...newProps };
}
};
-const tableIntialState = {
+const tableInitialState = {
rows: [],
prevFilters: null,
prevPath: null,
@@ -34,10 +33,10 @@ const tableIntialState = {
cap: CAP,
};
-const useTable = (intialOverrides: any) => {
+const useTable = (initialOverrides: any) => {
const [tableState, tableDispatch] = useReducer(tableReducer, {
- ...tableIntialState,
- ...intialOverrides,
+ ...tableInitialState,
+ ...initialOverrides,
});
const getRows = (
filters: {
@@ -144,6 +143,7 @@ const useTable = (intialOverrides: any) => {
updatedAt: firebase.firestore.FieldValue.serverTimestamp(),
});
};
+
const tableActions = { deleteRow, setTable, addRow };
return [tableState, tableActions];
};
diff --git a/src/hooks/useFiretable/useTableConfig.ts b/src/hooks/useFiretable/useTableConfig.ts
index 3bc35de1..de0eee3d 100644
--- a/src/hooks/useFiretable/useTableConfig.ts
+++ b/src/hooks/useFiretable/useTableConfig.ts
@@ -2,6 +2,7 @@ import { useEffect } from "react";
import useDoc, { DocActions } from "../useDoc";
import { FieldType } from "../../components/Fields";
import _camelCase from "lodash/camelCase";
+
const useTableConfig = (tablePath: string) => {
const [tableConfigState, documentDispatch] = useDoc({
path: `${tablePath}/_FIRETABLE_`,
@@ -11,17 +12,18 @@ const useTableConfig = (tablePath: string) => {
if (doc && columns !== doc.columns) {
documentDispatch({ columns: doc.columns });
}
- }, [tableConfigState]);
+ }, [tableConfigState.doc]);
+
const setTable = (table: string) => {
documentDispatch({ path: `${table}/_FIRETABLE_`, columns: [], doc: null });
};
- const add = (name: string, type: FieldType) => {
+ const add = (name: string, type: FieldType, data?: any) => {
//TODO: validation
const { columns } = tableConfigState;
const key = _camelCase(name);
documentDispatch({
action: DocActions.update,
- data: { columns: [...columns, { name, key, type }] },
+ data: { columns: [...columns, { name, key, type, ...data }] },
});
};
const resize = (index: number, width: number) => {
@@ -29,12 +31,25 @@ const useTableConfig = (tablePath: string) => {
columns[index].width = width;
documentDispatch({ action: DocActions.update, data: { columns } });
};
- const rename = () => {};
+ const update = (index: number, updatables: any) => {
+ const { columns } = tableConfigState;
+ updatables.forEach((updatable: any) => {
+ columns[index][updatable.field] = updatable.value;
+ });
+ documentDispatch({ action: DocActions.update, data: { columns } });
+ };
+
+ const remove = (index: number) => {
+ const { columns } = tableConfigState;
+ columns.splice(index, 1);
+ documentDispatch({ action: DocActions.update, data: { columns } });
+ };
const actions = {
+ update,
add,
resize,
- rename,
setTable,
+ remove,
};
return [tableConfigState, actions];
};
diff --git a/src/hooks/useSettings.ts b/src/hooks/useSettings.ts
index 045a6541..d7bbb06e 100644
--- a/src/hooks/useSettings.ts
+++ b/src/hooks/useSettings.ts
@@ -17,10 +17,17 @@ const useSettings = () => {
const createTable = (name: string, collection: string) => {
const { tables } = settingsState;
// updates the setting doc
- documentDispatch({
- action: DocActions.update,
- data: { tables: [...tables, { name, collection }] },
- });
+ if (tables) {
+ documentDispatch({
+ action: DocActions.update,
+ data: { tables: [...tables, { name, collection }] },
+ });
+ } else {
+ db.doc("_FIRETABLE_/settings").set(
+ { tables: [{ name, collection }] },
+ { merge: true }
+ );
+ }
//create the firetable collection doc with empty columns
db.collection(collection)
.doc("_FIRETABLE_")
diff --git a/src/views/AuthView.tsx b/src/views/AuthView.tsx
index 8b220d8e..8f924a6f 100644
--- a/src/views/AuthView.tsx
+++ b/src/views/AuthView.tsx
@@ -1,11 +1,9 @@
import React from "react";
-import { maxWidth } from "@material-ui/system";
import {
makeStyles,
createStyles,
Card,
- CardActions,
CardContent,
Button,
Typography,
diff --git a/src/views/TableView.tsx b/src/views/TableView.tsx
index 2984c80b..d3906987 100644
--- a/src/views/TableView.tsx
+++ b/src/views/TableView.tsx
@@ -1,4 +1,4 @@
-import React, { useEffect } from "react";
+import React from "react";
import Navigation from "../components/Navigation";
import Table from "../components/Table";
diff --git a/src/views/TablesView.tsx b/src/views/TablesView.tsx
index a44edd19..12405279 100644
--- a/src/views/TablesView.tsx
+++ b/src/views/TablesView.tsx
@@ -36,7 +36,7 @@ const useStyles = makeStyles(() =>
// TODO: Create an interface for props
const TablesView = (props: any) => {
- const [settings, createTable] = useSettings();
+ const [settings] = useSettings();
const tables = settings.tables;
const classes = useStyles();
const router = useRouter();
diff --git a/yarn.lock b/yarn.lock
index bdec8af9..a43973eb 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1586,6 +1586,18 @@
"@svgr/plugin-svgo" "^4.3.1"
loader-utils "^1.2.3"
+"@types/algoliasearch-helper@*":
+ version "2.26.1"
+ resolved "https://registry.yarnpkg.com/@types/algoliasearch-helper/-/algoliasearch-helper-2.26.1.tgz#60cf377e7cb4bd9a55f7eba35182792763230a24"
+ integrity sha512-JN1wq/yLxxBcc6MeSe57F9Aqv8wL964L0nBOUTSQ5OECzWxaECuGYV06VnGKn/c+9AGB97RAgqx2PUbYflZNqA==
+ dependencies:
+ "@types/algoliasearch" "*"
+
+"@types/algoliasearch@*", "@types/algoliasearch@^3.34.2":
+ version "3.34.2"
+ resolved "https://registry.yarnpkg.com/@types/algoliasearch/-/algoliasearch-3.34.2.tgz#f3daed26185a21b77632c28835e340147532f575"
+ integrity sha512-a+ztY3iL+Dpor7wYaF4CO6obUYcVEyXue1ppQklP1VCUP+VGZyzMcYiZodNs9DFV1HEOW5VCLTIqiZ4ikQpKzA==
+
"@types/babel__core@^7.1.0":
version "7.1.3"
resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.3.tgz#e441ea7df63cd080dfcd02ab199e6d16a735fc30"
@@ -1730,6 +1742,22 @@
dependencies:
"@types/react" "*"
+"@types/react-instantsearch-core@*":
+ version "5.2.9"
+ resolved "https://registry.yarnpkg.com/@types/react-instantsearch-core/-/react-instantsearch-core-5.2.9.tgz#c8050dedefcb86ff01a0ff584c1801f5b165fe4a"
+ integrity sha512-tem7Vc8R1SZlt5hxW/xAJT3jUbfwBEg6ZYf9lWr8DtccFRZkzbC8dVhV9os3Fq73hqYXWImB/sS3/wfRqwdW2g==
+ dependencies:
+ "@types/algoliasearch-helper" "*"
+ "@types/react" "*"
+
+"@types/react-instantsearch-dom@^5.2.6":
+ version "5.2.6"
+ resolved "https://registry.yarnpkg.com/@types/react-instantsearch-dom/-/react-instantsearch-dom-5.2.6.tgz#b47fc2c19b1aa8df06a95f67d01f5485abace8f0"
+ integrity sha512-gx7QBnL+rG50IUaIoj2SVwXT4O5HVOayQoEqiWF1RU1py43E+Wtlyi6WCdTEfEOz0sj6kWNEXAPMhfz+vjl9mQ==
+ dependencies:
+ "@types/react" "*"
+ "@types/react-instantsearch-core" "*"
+
"@types/react-router-dom@^4.3.5":
version "4.3.5"
resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-4.3.5.tgz#72f229967690c890d00f96e6b85e9ee5780db31f"
@@ -2100,6 +2128,11 @@ after@0.8.2:
resolved "https://registry.yarnpkg.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f"
integrity sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=
+agentkeepalive@^2.2.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-2.2.0.tgz#c5d1bd4b129008f1163f236f86e5faea2026e2ef"
+ integrity sha1-xdG9SxKQCPEWPyNvhuX66iAm4u8=
+
ajv-errors@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-1.0.1.tgz#f35986aceb91afadec4102fbd85014950cefa64d"
@@ -2120,6 +2153,27 @@ ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.5.5:
json-schema-traverse "^0.4.1"
uri-js "^4.2.2"
+algoliasearch@^3.34.0:
+ version "3.34.0"
+ resolved "https://registry.yarnpkg.com/algoliasearch/-/algoliasearch-3.34.0.tgz#02eb97bd6718e3a2c71121b9c3b655a35a4ba645"
+ integrity sha512-s8LDedkTWTAWR5uCWgJzGxDkCrqiej5iE4Tc2iCV+ONOO35i5qnVdieKg5gv2VDXBE7IP0YoqfAq/CC0V8PA+Q==
+ dependencies:
+ agentkeepalive "^2.2.0"
+ debug "^2.6.9"
+ envify "^4.0.0"
+ es6-promise "^4.1.0"
+ events "^1.1.0"
+ foreach "^2.0.5"
+ global "^4.3.2"
+ inherits "^2.0.1"
+ isarray "^2.0.1"
+ load-script "^1.0.0"
+ object-keys "^1.0.11"
+ querystring-es3 "^0.2.1"
+ reduce "^1.0.1"
+ semver "^5.1.0"
+ tunnel-agent "^0.6.0"
+
alphanum-sort@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3"
@@ -4334,6 +4388,11 @@ dom-storage@2.1.0:
resolved "https://registry.yarnpkg.com/dom-storage/-/dom-storage-2.1.0.tgz#00fb868bc9201357ea243c7bcfd3304c1e34ea39"
integrity sha512-g6RpyWXzl0RR6OTElHKBl7nwnK87GUyZMYC7JWsB/IA73vpqK2K6LT39x4VepLxlSsWBFrPVLnsSR5Jyty0+2Q==
+dom-walk@^0.1.0:
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.1.tgz#672226dc74c8f799ad35307df936aba11acd6018"
+ integrity sha1-ZyIm3HTI95mtNTB9+TaroRrNYBg=
+
domain-browser@^1.1.1:
version "1.2.0"
resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda"
@@ -4610,6 +4669,14 @@ entities@^2.0.0:
resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.0.tgz#68d6084cab1b079767540d80e56a39b423e4abf4"
integrity sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw==
+envify@^4.0.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/envify/-/envify-4.1.0.tgz#f39ad3db9d6801b4e6b478b61028d3f0b6819f7e"
+ integrity sha512-IKRVVoAYr4pIx4yIWNsz9mOsboxlNXiu7TNBnem/K/uTHdkyzXWDzHCK7UTolqBbgaBz0tQHsD3YNls0uIIjiw==
+ dependencies:
+ esprima "^4.0.0"
+ through "~2.3.4"
+
errno@^0.1.3, errno@~0.1.7:
version "0.1.7"
resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.7.tgz#4684d71779ad39af177e3f007996f7c67c852618"
@@ -4667,6 +4734,11 @@ es6-iterator@2.0.3, es6-iterator@~2.0.3:
es5-ext "^0.10.35"
es6-symbol "^3.1.1"
+es6-promise@^4.1.0:
+ version "4.2.8"
+ resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a"
+ integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==
+
es6-symbol@^3.1.1, es6-symbol@~3.1.1:
version "3.1.2"
resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.2.tgz#859fdd34f32e905ff06d752e7171ddd4444a7ed1"
@@ -4927,6 +4999,11 @@ eventemitter3@^3.0.0:
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.2.tgz#2d3d48f9c346698fce83a85d7d664e98535df6e7"
integrity sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==
+events@^1.1.0:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924"
+ integrity sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=
+
events@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/events/-/events-3.0.0.tgz#9a0a0dfaf62893d92b875b8f2698ca4114973e88"
@@ -5372,6 +5449,11 @@ for-own@^0.1.3:
dependencies:
for-in "^1.0.1"
+foreach@^2.0.5:
+ version "2.0.5"
+ resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99"
+ integrity sha1-C+4AUBiusmDQo6865ljdATbsG5k=
+
forever-agent@~0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
@@ -5633,6 +5715,14 @@ global-prefix@^3.0.0:
kind-of "^6.0.2"
which "^1.3.1"
+global@^4.3.2:
+ version "4.4.0"
+ resolved "https://registry.yarnpkg.com/global/-/global-4.4.0.tgz#3e7b105179006a323ed71aafca3e9c57a5cc6406"
+ integrity sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==
+ dependencies:
+ min-document "^2.19.0"
+ process "^0.11.10"
+
globals@^11.1.0, globals@^11.7.0:
version "11.12.0"
resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e"
@@ -6598,6 +6688,11 @@ isarray@2.0.1:
resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.1.tgz#a37d94ed9cda2d59865c9f76fe596ee1f338741e"
integrity sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=
+isarray@^2.0.1:
+ version "2.0.5"
+ resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723"
+ integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==
+
isexe@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
@@ -7490,11 +7585,14 @@ lines-and-columns@^1.1.6:
resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00"
integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=
+<<<<<<< HEAD
listenercount@~1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/listenercount/-/listenercount-1.0.1.tgz#84c8a72ab59c4725321480c975e6508342e70937"
integrity sha1-hMinKrWcRyUyFIDJdeZQg0LnCTc=
+=======
+>>>>>>> 388c726eacf3bd64f948944e2771b63a8afddedc
load-json-file@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0"
@@ -7526,6 +7624,11 @@ load-json-file@^4.0.0:
pify "^3.0.0"
strip-bom "^3.0.0"
+load-script@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/load-script/-/load-script-1.0.0.tgz#0491939e0bee5643ee494a7e3da3d2bac70c6ca4"
+ integrity sha1-BJGTngvuVkPuSUp+PaPSuscMbKQ=
+
loader-fs-cache@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/loader-fs-cache/-/loader-fs-cache-1.0.2.tgz#54cedf6b727e1779fd8f01205f05f6e88706f086"
@@ -7871,6 +7974,13 @@ mimic-fn@^2.0.0:
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
+min-document@^2.19.0:
+ version "2.19.0"
+ resolved "https://registry.yarnpkg.com/min-document/-/min-document-2.19.0.tgz#7bd282e3f5842ed295bb748cdd9f1ffa2c824685"
+ integrity sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=
+ dependencies:
+ dom-walk "^0.1.0"
+
mini-create-react-context@^0.3.0:
version "0.3.2"
resolved "https://registry.yarnpkg.com/mini-create-react-context/-/mini-create-react-context-0.3.2.tgz#79fc598f283dd623da8e088b05db8cddab250189"
@@ -8368,7 +8478,7 @@ object-is@^1.0.1:
resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.0.1.tgz#0aa60ec9989a0b3ed795cf4d06f62cf1ad6539b6"
integrity sha1-CqYOyZiaCz7Xlc9NBvYs8a1lObY=
-object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.1.1:
+object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.1.0, object-keys@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==
@@ -9887,7 +9997,7 @@ qs@~6.5.2:
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==
-querystring-es3@^0.2.0:
+querystring-es3@^0.2.0, querystring-es3@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73"
integrity sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=
@@ -10363,6 +10473,13 @@ recursive-readdir@2.2.2:
dependencies:
minimatch "3.0.4"
+reduce@^1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/reduce/-/reduce-1.0.2.tgz#0cd680ad3ffe0b060e57a5c68bdfce37168d361b"
+ integrity sha512-xX7Fxke/oHO5IfZSk77lvPa/7bjMh9BuCk4OOoX5XTXrM7s0Z+MkPfSDfz0q7r91BhhGSs8gii/VEN/7zhCPpQ==
+ dependencies:
+ object-keys "^1.1.0"
+
redux@^3.7.1:
version "3.7.2"
resolved "https://registry.yarnpkg.com/redux/-/redux-3.7.2.tgz#06b73123215901d25d065be342eb026bc1c8537b"
@@ -10843,7 +10960,7 @@ semver-compare@^1.0.0:
resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc"
integrity sha1-De4hahyUGrN+nvsXiPavxf9VN/w=
-"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0:
+"semver@2 || 3 || 4 || 5", semver@^5.1.0, semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0:
version "5.7.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
@@ -11743,7 +11860,7 @@ through2@^2.0.0:
readable-stream "~2.3.6"
xtend "~4.0.1"
-through@^2.3.6:
+through@^2.3.6, through@~2.3.4:
version "2.3.8"
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=