diff --git a/www/app.yaml b/www/app.yaml new file mode 100644 index 00000000..26a101fe --- /dev/null +++ b/www/app.yaml @@ -0,0 +1,13 @@ +# [START runtime] +runtime: nodejs +env: flex +# [END runtime] + +# [START handlers] +handlers: + - url: / + static_files: build/index.html + upload: build/index.html + - url: /(.*)$ + static_files: build/\1 + upload: build/(.*) diff --git a/www/dispatch.yaml b/www/dispatch.yaml new file mode 100644 index 00000000..e88789df --- /dev/null +++ b/www/dispatch.yaml @@ -0,0 +1,5 @@ +dispatch: + - url: "firetable-magic.uc.r.appspot.com/" + service: default + - url: "*.firetable.io/" + service: default diff --git a/www/package.json b/www/package.json index 792fec89..8d6962aa 100644 --- a/www/package.json +++ b/www/package.json @@ -63,9 +63,11 @@ "yup": "^0.27.0" }, "scripts": { - "start": "react-scripts start", + "start": "serve -s build", + "prestart": "npm install -g serve", + "local": "react-scripts start", "build": "react-scripts build", - "test": "react-scripts test", + "test": "react-scripts test --env=jsdom", "eject": "react-scripts eject", "env": "node createDotEnv", "target": "firebase target:apply hosting firetable", diff --git a/www/src/App.tsx b/www/src/App.tsx index 9037faa5..6ea26fcf 100644 --- a/www/src/App.tsx +++ b/www/src/App.tsx @@ -72,7 +72,12 @@ const App: React.FC = () => { ( @@ -86,8 +91,8 @@ const App: React.FC = () => { render={() => } /> } + path={routes.tableGroupWithId} + render={() => } /> diff --git a/www/src/components/ConnectTableSelect/PopupContents.tsx b/www/src/components/ConnectTableSelect/PopupContents.tsx index 139af606..981572e6 100644 --- a/www/src/components/ConnectTableSelect/PopupContents.tsx +++ b/www/src/components/ConnectTableSelect/PopupContents.tsx @@ -28,7 +28,6 @@ const searchClient = algoliasearch( process.env.REACT_APP_ALGOLIA_APP_ID ?? "", process.env.REACT_APP_ALGOLIA_SEARCH_API_KEY ?? "" ); -console.log("SEARCH CLIENT", searchClient); export interface IPopupContentsProps extends Omit {} @@ -60,8 +59,6 @@ export default function PopupContents({ const [search] = useDebouncedCallback( async (query: string) => { if (!algoliaIndex) return; - console.log("SEARCH", query, algoliaIndex, row); - const data = { ...userClaims, ...row }; const filters = config?.filters ? config?.filters.replace(/\{\{(.*?)\}\}/g, (m, k) => data[k]) diff --git a/www/src/components/Navigation.tsx b/www/src/components/Navigation.tsx index 2609c2cd..0cdaa37f 100644 --- a/www/src/components/Navigation.tsx +++ b/www/src/components/Navigation.tsx @@ -75,8 +75,9 @@ export default function Navigation({ // Get the table path, including filtering for user permissions const getTablePath = (table: Table): LinkProps["to"] => { if (!table || !userClaims) return ""; - - return table.collection; + return table.isCollectionGroup + ? `/tableGroup/${table.collection}` + : `/table/${table.collection}`; }; const currentCollection = tableCollection.split("/")[0]; @@ -160,6 +161,7 @@ export default function Navigation({ disabled={sectionName === section} // onClick={handleSectionClick(sectionName)} component={Link} + replace={true} to={getTablePath(tables[0])} color="inherit" className={classes.sectionButton} diff --git a/www/src/components/SideDrawer/Form/Fields/Action.tsx b/www/src/components/SideDrawer/Form/Fields/Action.tsx index 16a8c025..537d9e5a 100644 --- a/www/src/components/SideDrawer/Form/Fields/Action.tsx +++ b/www/src/components/SideDrawer/Form/Fields/Action.tsx @@ -54,8 +54,6 @@ function Action({ const snack = useContext(SnackContext); const handleRun = () => { setIsRunning(true); - console.log("RUN"); - cloudFunction( config.callableName, { diff --git a/www/src/components/Table/ColumnMenu/ConfigFields/CodeEditor.tsx b/www/src/components/Table/ColumnMenu/ConfigFields/CodeEditor.tsx index 2a7b4821..048672be 100644 --- a/www/src/components/Table/ColumnMenu/ConfigFields/CodeEditor.tsx +++ b/www/src/components/Table/ColumnMenu/ConfigFields/CodeEditor.tsx @@ -37,9 +37,9 @@ const useStyles = makeStyles(theme => }, }) ); + export default function CodeEditor(props: any) { const { handleChange, script } = props; - console.log({ script }); const classes = useStyles(); const editor = useRef(null); const handleResize = () => { diff --git a/www/src/components/Table/ColumnMenu/ConfigFields/ColumnSelector.tsx b/www/src/components/Table/ColumnMenu/ConfigFields/ColumnSelector.tsx index 1a3f026b..f83f3821 100644 --- a/www/src/components/Table/ColumnMenu/ConfigFields/ColumnSelector.tsx +++ b/www/src/components/Table/ColumnMenu/ConfigFields/ColumnSelector.tsx @@ -28,7 +28,6 @@ const ColumnSelector = ({ }; useEffect(() => { if (table) { - console.log({ table }); getColumns(table); } }, [table]); diff --git a/www/src/components/Table/ColumnMenu/Settings.tsx b/www/src/components/Table/ColumnMenu/Settings.tsx index 737837e5..dca7216b 100644 --- a/www/src/components/Table/ColumnMenu/Settings.tsx +++ b/www/src/components/Table/ColumnMenu/Settings.tsx @@ -219,7 +219,6 @@ export default function FormDialog({ update => { - console.log(key, update); setNewConfig({ ...newConfig, [key]: update }); }} config={newConfig} diff --git a/www/src/components/Table/ExportCSV.tsx b/www/src/components/Table/ExportCSV.tsx index cbaf8337..82df6d76 100644 --- a/www/src/components/Table/ExportCSV.tsx +++ b/www/src/components/Table/ExportCSV.tsx @@ -21,6 +21,7 @@ import { SnackContext } from "contexts/snackContext"; import { useFiretableContext } from "contexts/firetableContext"; import { db } from "../../firebase"; import { FieldType } from "constants/fields"; +import { isCollectionGroup } from "util/fns"; const ITEM_HEIGHT = 48; const ITEM_PADDING_TOP = 8; @@ -159,7 +160,9 @@ export default function ExportCSV() { duration: 5000, }); - let query: any = db.collection(tableState?.tablePath!); + let query: any = isCollectionGroup() + ? db.collectionGroup(tableState?.tablePath!) + : db.collection(tableState?.tablePath!); // add filters tableState?.filters.forEach(filter => { query = query.where(filter.key, filter.operator, filter.value); diff --git a/www/src/components/Table/Filters/DocSelector.tsx b/www/src/components/Table/Filters/DocSelector.tsx index fb8dbd97..cb1ab00e 100644 --- a/www/src/components/Table/Filters/DocSelector.tsx +++ b/www/src/components/Table/Filters/DocSelector.tsx @@ -13,7 +13,6 @@ const AlgoliaSelect = (props: any) => { } = props; const [searchState] = useAlgolia(algoliaIndex, algoliaKey, filters); - console.log(filters); const [options, setOptions] = useState([]); useEffect(() => { if (Array.isArray(searchState.results?.hits)) { diff --git a/www/src/components/Table/MigrateButton.tsx b/www/src/components/Table/MigrateButton.tsx index aec58bef..44ac6625 100644 --- a/www/src/components/Table/MigrateButton.tsx +++ b/www/src/components/Table/MigrateButton.tsx @@ -9,8 +9,6 @@ const MigrateButton = ({ columns, needsMigration }) => { const configDocPath = tableState?.config.tableConfig.path; const handleColumnMigration = async () => { - console.log({ columns }); - const newColumns = columns.reduce((acc, currCol, currIndex) => { const baseCol = { ...currCol, diff --git a/www/src/components/Table/TableHeader.tsx b/www/src/components/Table/TableHeader.tsx index 795021b0..e9fb8ce0 100644 --- a/www/src/components/Table/TableHeader.tsx +++ b/www/src/components/Table/TableHeader.tsx @@ -9,6 +9,8 @@ import { Typography, Button, } from "@material-ui/core"; + +import { isCollectionGroup } from "../../util/fns"; import AddIcon from "@material-ui/icons/Add"; import Filters from "./Filters"; @@ -84,25 +86,27 @@ export default function TableHeader({ className={classes.root} > - - - + {!isCollectionGroup() && ( + + + + )} {/* */} @@ -150,9 +154,11 @@ export default function TableHeader({ - - - + {!isCollectionGroup() && ( + + + + )} diff --git a/www/src/components/Table/index.tsx b/www/src/components/Table/index.tsx index e198c8e4..b147f424 100644 --- a/www/src/components/Table/index.tsx +++ b/www/src/components/Table/index.tsx @@ -44,12 +44,7 @@ export type FiretableColumn = Column & { [key: string]: any; }; -interface ITableProps { - collection: string; - filters: FireTableFilter[]; -} - -export default function Table({ collection, filters }: ITableProps) { +export default function Table() { const classes = useStyles(); const theme = useTheme(); const finalColumnClasses = useFinalColumnStyles(); @@ -64,12 +59,6 @@ export default function Table({ collection, filters }: ITableProps) { const { userDoc } = useAppContext(); const userDocHiddenFields = userDoc.state.doc?.tables[`${tableState?.tablePath}`]?.hiddenFields ?? []; - useEffect(() => { - if (tableActions && tableState && tableState.tablePath !== collection) { - tableActions.table.set(collection, filters); - if (sideDrawerRef?.current) sideDrawerRef.current.setCell!(null); - } - }, [collection]); const rowsContainerRef = useRef(null); // Gets more rows when scrolled down. @@ -138,7 +127,7 @@ export default function Table({ collection, filters }: ITableProps) { const rows = tableState.rows; const rowGetter = (rowIdx: number) => rows[rowIdx]; - const inSubTable = collection.split("/").length > 1; + const inSubTable = tableState.tablePath.split("/").length > 1; let tableWidth: any = `calc(100% - ${ DRAWER_COLLAPSED_WIDTH @@ -152,7 +141,7 @@ export default function Table({ collection, filters }: ITableProps) { */} - {inSubTable && } + {inSubTable && } )} + } + label={"isCollectionGroup"} + labelPlacement="start" + value={formState.isCollectionGroup} + onChange={e => + handleChange("isCollectionGroup", !formState.isCollectionGroup) + } + // classes={{ root: classes.formControlLabel, label: classes.label }} + /> handleChange("section", e.target.value)} diff --git a/www/src/constants/routes.ts b/www/src/constants/routes.ts index 367a8a64..aca187b0 100644 --- a/www/src/constants/routes.ts +++ b/www/src/constants/routes.ts @@ -5,7 +5,10 @@ export enum routes { signOut = "/signOut", table = "/table", + tableGroup = "/tableGroup", + tableWithId = "/table/:id", + tableGroupWithId = "/tableGroup/:id", grid = "/grid", gridWithId = "/grid/:id", editor = "/editor", diff --git a/www/src/contexts/firetableContext.tsx b/www/src/contexts/firetableContext.tsx index 8b398e56..cecb7888 100644 --- a/www/src/contexts/firetableContext.tsx +++ b/www/src/contexts/firetableContext.tsx @@ -19,6 +19,7 @@ export type Table = { roles: string[]; description: string; section: string; + isCollectionGroup: boolean; }; interface FiretableContextProps { diff --git a/www/src/firebase/experiment.ts b/www/src/firebase/experiment.ts new file mode 100644 index 00000000..80cad6f7 --- /dev/null +++ b/www/src/firebase/experiment.ts @@ -0,0 +1,43 @@ +import firebase from "firebase/app"; +import "firebase/auth"; +import "firebase/firestore"; +import "firebase/functions"; +import "firebase/storage"; + +export let auth: any = false; + +export let db: any = false; + +export let bucket: any = false; +export let functions: any = false; + +export let googleProvider: any = false; + +console.log(`fetching config for ${window.location.hostname.split(".")[0]}`); +fetch( + `https://us-central1-firetable-magic.cloudfunctions.net/getWebAppConfig?projectId=${window.location.hostname.split( + "." + )[0] ?? "antler-vc"}` +) + .then(async response => { + const config = await response.json(); + console.log({ config }); + firebase.initializeApp(config); + auth = firebase.auth(); + + db = firebase.firestore(); + // db.settings({ cacheSizeBytes: firebase.firestore.CACHE_SIZE_UNLIMITED }); + // db.enablePersistence({ synchronizeTabs: true }); + + bucket = firebase.storage(); + functions = firebase.functions(); + + googleProvider = new firebase.auth.GoogleAuthProvider().setCustomParameters( + { + prompt: "select_account", + } + ); + }) + .catch(err => console.log(err)); + +export const deleteField = firebase.firestore.FieldValue.delete; diff --git a/www/src/hooks/useFiretable/index.ts b/www/src/hooks/useFiretable/index.ts index 0b023841..47b0b4b4 100644 --- a/www/src/hooks/useFiretable/index.ts +++ b/www/src/hooks/useFiretable/index.ts @@ -49,11 +49,6 @@ const useFiretable = ( orderBy, }); - /**Subscribes to _FIRETABLE_/settings/schema to have table configuration pre-cached */ - const [settingsState, setttingsDispatch] = useTable({ - path: "_FIRETABLE_/settings/schema", - }); - /** set collection path of table */ const setTable = (collectionName: string, filters: FireTableFilter[]) => { if (collectionName !== tableState.path || filters !== tableState.filters) { diff --git a/www/src/hooks/useFiretable/useTable.tsx b/www/src/hooks/useFiretable/useTable.tsx index 5c37a68b..4a092eab 100644 --- a/www/src/hooks/useFiretable/useTable.tsx +++ b/www/src/hooks/useFiretable/useTable.tsx @@ -7,6 +7,7 @@ import firebase from "firebase/app"; import { FireTableFilter, FiretableOrderBy } from "."; import { SnackContext } from "../../contexts/snackContext"; import { cloudFunction } from "../../firebase/callables"; +import { isCollectionGroup } from "util/fns"; const CAP = 1000; // safety paramter sets the upper limit of number of docs fetched by this hook const serverTimestamp = firebase.firestore.FieldValue.serverTimestamp; var characters = @@ -87,7 +88,9 @@ const useTable = (initialOverrides: any) => { }); let query: | firebase.firestore.CollectionReference - | firebase.firestore.Query = db.collection(tableState.path); + | firebase.firestore.Query = isCollectionGroup() + ? db.collectionGroup(tableState.path) + : db.collection(tableState.path); filters.forEach(filter => { query = query.where(filter.key, filter.operator, filter.value); diff --git a/www/src/hooks/useFiretable/useTableConfig.ts b/www/src/hooks/useFiretable/useTableConfig.ts index 72956b8b..2e4810a3 100644 --- a/www/src/hooks/useFiretable/useTableConfig.ts +++ b/www/src/hooks/useFiretable/useTableConfig.ts @@ -6,7 +6,7 @@ import _camelCase from "lodash/camelCase"; import _findIndex from "lodash/findIndex"; import _find from "lodash/find"; import _sortBy from "lodash/sortBy"; -import { arrayMover } from "../../util/fns"; +import { arrayMover, isCollectionGroup } from "../../util/fns"; import { db, deleteField } from "../../firebase"; //import @@ -14,10 +14,9 @@ import { db, deleteField } from "../../firebase"; const formatPathRegex = /\/[^\/]+\/([^\/]+)/g; const formatPath = (tablePath: string) => { - return `_FIRETABLE_/settings/schema/${tablePath.replace( - formatPathRegex, - "/subTables/$1" - )}`; + return `_FIRETABLE_/settings/${ + isCollectionGroup() ? "groupSchema" : "schema" + }/${tablePath.replace(formatPathRegex, "/subTables/$1")}`; }; const useTableConfig = (tablePath?: string) => { const [tableConfigState, documentDispatch] = useDoc({ @@ -50,7 +49,6 @@ const useTableConfig = (tablePath?: string) => { const add = (name: string, type: FieldType, data?: any) => { //TODO: validation const { columns } = tableConfigState; - console.log({ columns }); const newIndex = Object.keys(columns).length; let updatedColumns = columns; const key = _camelCase(name); diff --git a/www/src/util/fns.ts b/www/src/util/fns.ts index aaeafd37..e8e0a5dc 100644 --- a/www/src/util/fns.ts +++ b/www/src/util/fns.ts @@ -53,3 +53,8 @@ export const sanitiseRowData = (rowData: any) => { }); return rowData; }; + +export const isCollectionGroup = () => { + const pathName = window.location.pathname.split("/")[1]; + return pathName === "tableGroup"; +}; diff --git a/www/src/views/TableView.tsx b/www/src/views/TableView.tsx index 550715f4..af8b9476 100644 --- a/www/src/views/TableView.tsx +++ b/www/src/views/TableView.tsx @@ -1,5 +1,6 @@ -import React from "react"; +import React, { useEffect } from "react"; import queryString from "query-string"; +import { useFiretableContext } from "contexts/firetableContext"; import { Hidden } from "@material-ui/core"; @@ -13,7 +14,7 @@ import useRouter from "hooks/useRouter"; export default function TableView() { const router = useRouter(); const tableCollection = decodeURIComponent(router.match.params.id); - + const { tableState, tableActions, sideDrawerRef } = useFiretableContext(); let filters: FireTableFilter[] = []; const parsed = queryString.parse(router.location.search); if (typeof parsed.filters === "string") { @@ -23,14 +24,20 @@ export default function TableView() { //TODO: json schema validator } + useEffect(() => { + if ( + tableActions && + tableState && + tableState.tablePath !== tableCollection + ) { + tableActions.table.set(tableCollection, filters); + if (sideDrawerRef?.current) sideDrawerRef.current.setCell!(null); + } + }, [tableCollection]); + if (!tableState?.tablePath) return <>; return ( - - +
diff --git a/www/src/views/TablesView.tsx b/www/src/views/TablesView.tsx index 68a519e7..0386b16a 100644 --- a/www/src/views/TablesView.tsx +++ b/www/src/views/TablesView.tsx @@ -89,6 +89,7 @@ const TablesView = () => { roles: string[]; name: string; section: string; + isCollectionGroup: boolean; }; }>({ mode: null, @@ -137,7 +138,9 @@ const TablesView = () => { } bodyContent={table.description} primaryLink={{ - to: `${routes.table}/${table.collection}`, + to: `${ + table.isCollectionGroup ? routes.tableGroup : routes.table + }/${table.collection}`, label: "Open", }} secondaryAction={