diff --git a/cli/.npmignore b/cli/.npmignore new file mode 100644 index 00000000..c2658d7d --- /dev/null +++ b/cli/.npmignore @@ -0,0 +1 @@ +node_modules/ diff --git a/cli/index.js b/cli/index.js new file mode 100755 index 00000000..926b6fbc --- /dev/null +++ b/cli/index.js @@ -0,0 +1,170 @@ +#!/usr/bin/env node + +const chalk = require("chalk"); +const clear = require("clear"); +const { printLogo } = require("./logo"); +const { Command } = require("commander"); +const terminal = require("./lib/terminal"); +const inquirer = require("./lib/inquirer"); +const Configstore = require("configstore"); +const config = new Configstore("firetable"); +const { directoryExists } = require("./lib/files"); +const program = new Command(); +program.version("0.2.0"); + +//TODO: validate if all the required packages exist +const systemHealthCheck = async () => { + const versions = await terminal.getRequiredVersions(); + console.log(versions); + //throw new Error(chalk.red("missing something")); +}; +// checks the current directory of the cli app +const directoryCheck = async () => { + let directory = "firetable/www"; + const isInsideFiretableFolder = directoryExists("www"); + const firetableAppExists = directoryExists("firetable/www"); + if (isInsideFiretableFolder) { + directory = "www"; + } + if (!isInsideFiretableFolder && !firetableAppExists) { + console.log(chalk.red("Firetable app not detected.")); + console.log( + `please ensure you are in the correct directory or run:${chalk.bold( + chalk.yellow("firetable init") + )} to get started` + ); + return; + } + const nodeModulesAvailable = directoryExists(`${directory}/node_modules`); + if (!nodeModulesAvailable) { + await terminal.installFiretableAppPackages(directory); + } + return directory; +}; + +const deploy2firebase = async (directory = "firetable/www") => { + const projectId = config.get("firebaseProjectId"); + let hostTarget = config.get("firebaseHostTarget"); + if (hostTarget) { + const { changeTarget } = await inquirer.askChangeFirebaseHostTarget( + hostTarget + ); + if (changeTarget) { + const response = await inquirer.askFirebaseHostTarget(projectId); + hostTarget = response.hostTarget; + } + } else { + const response = await inquirer.askFirebaseHostTarget(projectId); + hostTarget = response.hostTarget; + } + await terminal.buildFiretable(directory); + await terminal.setFirebaseHostingTarget(projectId, hostTarget, directory); + await terminal.deployToFirebaseHosting(projectId, directory); + config.set("firebaseHostTarget", hostTarget); + console.log( + chalk.green( + `\u{1F973}\u{1F973}\u{1F973} \n Firetable has been successfully deployed to 'https://${hostTarget}.web.app' \n \u{1F973}\u{1F973}\u{1F973}` + ) + ); +}; + +program + .command("init") + .description( + "clones firetable repo, installs the npm packages and set the environment variables" + ) + .action(async () => { + try { + clear(); + printLogo(); + // check if all the required packages are available on the machine + await systemHealthCheck(); + const firebaseProjects = await terminal.getFirebaseProjects(); + + const { projectId } = await inquirer.selectFirebaseProject( + firebaseProjects + ); + config.set("firebaseProjectId", projectId); + let envVariables = { + projectId, + firebaseWebApiKey: "-", + algoliaAppId: "_", + algoliaSearchKey: "_", + }; + const includeAlgolia = await inquirer.installAlgolia(); + if (includeAlgolia.installAlgolia) { + const algoliaKey = await inquirer.askAlgoliaVariables(); + envVariables = { ...envVariables, ...algoliaKey }; + } + // clone firetable repo and install app dependencies + await terminal.cloneFiretable(); + + // set environment variables + await terminal.setFiretableENV(envVariables); + let firetableAppId; + + const existingFiretableAppId = await terminal.getExistingFiretableApp( + projectId + ); + if (existingFiretableAppId) { + firetableAppId = existingFiretableAppId; + } else { + firetableAppId = await terminal.createFiretableWebApp(projectId); + } + + const webAppConfig = await terminal.getFiretableWebAppConfig( + firetableAppId + ); + + await terminal.createFirebaseAppConfigFile(webAppConfig); + + console.log(chalk.green("environment variables were set successfully")); + console.log( + chalk.green("Success: Firetable has been successfully set up!") + ); + console.log( + `you can run:${chalk.bold( + "firetable local" + )} command to run your firetable instance locally` + ); + console.log( + `you can run:${chalk.bold( + "firetable deploy" + )} to deploy you your firetable app to firebase hosting` + ); + } catch (error) { + console.log(chalk.red("FAILED:")); + console.log(error); + } + }); + +program + .command("local") + .description("run your firetable instance locally") + .action(async () => { + try { + // check directory for firetable + let directory = await directoryCheck(); + if (!directory) return; + terminal.startFiretableLocally(directory); + } catch (error) { + console.log(chalk.red("FAILED:")); + console.log(error); + } + }); + +program + .command("deploy") + .description("deploys firetable to a firebase hosting site") + .action(async () => { + try { + // check directory for firetable + let directory = await directoryCheck(); + if (!directory) return; + await deploy2firebase(directory); + } catch (error) { + console.log(chalk.red("FAILED:")); + console.log(error); + } + }); +program.parse(process.argv); diff --git a/cli/lib/files.js b/cli/lib/files.js new file mode 100644 index 00000000..0f402a1b --- /dev/null +++ b/cli/lib/files.js @@ -0,0 +1,12 @@ +const fs = require("fs"); +const path = require("path"); + +module.exports = { + getCurrentDirectoryBase: () => { + return path.basename(process.cwd()); + }, + + directoryExists: filePath => { + return fs.existsSync(filePath); + }, +}; diff --git a/cli/lib/inquirer.js b/cli/lib/inquirer.js new file mode 100644 index 00000000..f51786bf --- /dev/null +++ b/cli/lib/inquirer.js @@ -0,0 +1,78 @@ +const inquirer = require("inquirer"); + +module.exports = { + selectFirebaseProject: projects => + inquirer.prompt({ + name: "projectId", + type: "list", + message: "Select the firebase project you want deploy to:", + choices: projects, + validate: function(value) { + if (value.length) { + return true; + } else { + return "Please enter your the firebase project id you would like to deploy to.\n or you can create new project here:https://console.firebase.google.com/project"; + } + }, + }), + installAlgolia: () => { + const questions = [ + { + type: "confirm", + name: "installAlgolia", + message: + "do you want integrate firetable with algolia? \n You can add it later on, if you don't have it setup yet", + }, + ]; + return inquirer.prompt(questions); + }, + + askAlgoliaVariables: () => { + const questions = [ + { + name: "algoliaAppId", + type: "input", + message: "Enter your Algolia App ID:", + validate: function(value) { + if (value.length) { + return true; + } else { + return "Please enter your Algolia App ID.\n or you can create new project here:https://algolia.com "; + } + }, + }, + { + name: "algoliaSearchKey", + type: "input", + message: "Enter your Algolia search key(not the admin key)", + validate: function(value) { + if (value.length > 5) { + return true; + } else { + return "Please enter a valid api key "; + } + }, + }, + ]; + return inquirer.prompt(questions); + }, + deployToFirebaseHosting: () => + inquirer.prompt({ + type: "confirm", + name: "deployToFirebase", + message: "Do you want deploy firetable on firebase hosting?", + }), + askChangeFirebaseHostTarget: hostTarget => + inquirer.prompt({ + type: "confirm", + name: "changeTarget", + default: false, + message: `do you want to change your current host target (${hostTarget}) ?`, + }), + askFirebaseHostTarget: projectId => + inquirer.prompt({ + type: "input", + name: "hostTarget", + message: `where do you want to deploy?\n you can find your available sites or create new one here: 'https://console.firebase.google.com/u/0/project/${projectId}/hosting' \n enter your site name: `, + }), +}; diff --git a/cli/lib/repo.js b/cli/lib/repo.js new file mode 100644 index 00000000..dd9692a1 --- /dev/null +++ b/cli/lib/repo.js @@ -0,0 +1,67 @@ +const CLI = require("clui"); +const fs = require("fs"); +const git = require("simple-git/promise")(); +const Spinner = CLI.Spinner; +const touch = require("touch"); +const _ = require("lodash"); + +const inquirer = require("./inquirer"); +const gh = require("./github"); + +module.exports = { + createRemoteRepo: async () => { + const github = gh.getInstance(); + const answers = await inquirer.askRepoDetails(); + + const data = { + name: answers.name, + description: answers.description, + private: answers.visibility === "private", + }; + + const status = new Spinner("Creating remote repository..."); + status.start(); + + try { + const response = await github.repos.createForAuthenticatedUser(data); + return response.data.ssh_url; + } finally { + status.stop(); + } + }, + + createGitignore: async () => { + const filelist = _.without(fs.readdirSync("."), ".git", ".gitignore"); + + if (filelist.length) { + const answers = await inquirer.askIgnoreFiles(filelist); + + if (answers.ignore.length) { + fs.writeFileSync(".gitignore", answers.ignore.join("\n")); + } else { + touch(".gitignore"); + } + } else { + touch(".gitignore"); + } + }, + + setupRepo: async url => { + const status = new Spinner( + "Initializing local repository and pushing to remote..." + ); + status.start(); + + try { + git + .init() + .then(git.add(".gitignore")) + .then(git.add("./*")) + .then(git.commit("Initial commit")) + .then(git.addRemote("origin", url)) + .then(git.push("origin", "master")); + } finally { + status.stop(); + } + }, +}; diff --git a/cli/lib/terminal.js b/cli/lib/terminal.js new file mode 100644 index 00000000..cdbfdf94 --- /dev/null +++ b/cli/lib/terminal.js @@ -0,0 +1,206 @@ +var exec = require("child_process").exec; +const CLI = require("clui"); +const Spinner = CLI.Spinner; +// appId regex \d:[0-9]*:web:[0-z]* + +// firetable app Regex │ firetable-app.* +function execute(command, callback) { + exec(command, function(error, stdout, stderr) { + //console.log({ error, stdout, stderr }); + callback(stdout); + }); +} + +module.exports.getRequiredVersions = () => + new Promise((resolve, reject) => { + const checkingVersionsStatus = new Spinner( + "Checking the versions of required system packages, please wait..." + ); + checkingVersionsStatus.start(); + execute("git --version", function(git) { + execute("node --version", function(node) { + execute("yarn --version", function(yarn) { + execute("firebase --version", function(firebase) { + checkingVersionsStatus.stop(); + resolve({ + node: node.replace("\n", ""), + git: git.replace("\n", ""), + yarn: yarn.replace("\n", ""), + firebase: firebase.replace("\n", ""), + }); + }); + }); + }); + }); + }); + +module.exports.getGitUser = function(callback) { + execute("git config --global user.name", function(name) { + execute("git config --global user.email", function(email) { + callback({ + name: name.replace("\n", ""), + email: email.replace("\n", ""), + }); + }); + }); +}; + +module.exports.cloneFiretable = () => + new Promise((resolve, reject) => { + const cloningStatus = new Spinner( + "cloning the firetable repository, please wait..." + ); + cloningStatus.start(); + execute("git clone https://github.com/AntlerVC/firetable.git", function() { + cloningStatus.stop(); + const installingPackagesStatus = new Spinner("installing packages"); + installingPackagesStatus.start(); + execute("cd firetable/www;yarn;", function(results) { + installingPackagesStatus.stop(); + resolve(results); + }); + }); + }); + +module.exports.setFiretableENV = envVariables => + new Promise((resolve, reject) => { + const status = new Spinner("setting environment variables, please wait..."); + status.start(); + const command = `cd firetable/www;node createDotEnv ${envVariables.projectId} ${envVariables.firebaseWebApiKey} ${envVariables.algoliaAppId} ${envVariables.algoliaSearchKey}`; + execute(command, function() { + status.stop(); + resolve(true); + }); + }); + +module.exports.setFirebaseHostingTarget = ( + projectId, + hostingTarget, + directory = "firetable/www" +) => + new Promise((resolve, reject) => { + const status = new Spinner("setting environment variables, please wait..."); + status.start(); + const command = `cd ${directory};echo '{}' > .firebaserc;yarn target ${hostingTarget} --project ${projectId}`; + execute(command, function() { + status.stop(); + resolve(true); + }); + }); + +module.exports.deployToFirebaseHosting = ( + projectId, + directory = "firetable/www" +) => + new Promise((resolve, reject) => { + const status = new Spinner("deploying to firebase hosting, please wait..."); + status.start(); + const command = `cd ${directory};firebase deploy --project ${projectId} --only hosting`; + execute(command, function(results) { + if (results.includes("Error:")) { + throw new Error(results); + } + status.stop(); + resolve(true); + }); + }); + +module.exports.startFiretableLocally = directory => + new Promise((resolve, reject) => { + const status = new Spinner("Starting firetable locally, please wait..."); + status.start(); + execute(`cd ${directory};yarn local`, function() { + status.stop(); + resolve(true); + }); + }); +module.exports.installFiretableAppPackages = directory => + new Promise((resolve, reject) => { + const status = new Spinner("Installing firetable app packages..."); + status.start(); + execute(`cd ${directory};yarn`, function() { + status.stop(); + resolve(true); + }); + }); +module.exports.buildFiretable = (directory = "firetable/www") => + new Promise((resolve, reject) => { + const status = new Spinner( + "Building firetable, this one might take a while \u{1F602}..." + ); + status.start(); + execute(`cd ${directory};yarn build`, function() { + status.stop(); + resolve(true); + }); + }); + +module.exports.getFirebaseProjects = () => + new Promise((resolve, reject) => { + const status = new Spinner("Getting your firebase projects..."); + status.start(); + execute(`firebase projects:list`, function(results) { + status.stop(); + //console.log(results); + if (results.includes("Failed to authenticate")) { + throw new Error(results); + } + const projects = results.match(/(?<=│.*│ )[0-z,-]*(?= *│ \d)/g); + resolve(projects); + }); + }); + +module.exports.getExistingFiretableApp = projectId => + new Promise((resolve, reject) => { + const status = new Spinner("Checking for existing firetable web app..."); + status.start(); + execute(`firebase apps:list WEB --project ${projectId}`, function(results) { + status.stop(); + const firetableApp = results.match(/│ firetable-app.*/); + if (firetableApp) { + resolve(firetableApp[0].match(/\d:[0-9]*:web:[0-z]*/)[0]); + } else { + resolve(false); + } + }); + }); + +module.exports.createFiretableWebApp = projectId => + new Promise((resolve, reject) => { + const status = new Spinner(`Creating a firetable web app in ${projectId}`); + status.start(); + execute( + `firebase apps:create --project ${projectId} web firetable-app`, + function(results) { + status.stop(); + resolve(results.match(/(?<=ID: ).*/)[0]); + } + ); + }); + +module.exports.getFiretableWebAppConfig = webAppId => + new Promise((resolve, reject) => { + const status = new Spinner(`getting your web app config`); + status.start(); + execute(`firebase apps:sdkconfig WEB ${webAppId}`, function(results) { + status.stop(); + const config = results.match(/{(.*)([\s\S]*)}/)[0]; + resolve(config); + }); + }); + +module.exports.createFirebaseAppConfigFile = config => + new Promise((resolve, reject) => { + const status = new Spinner(`creatING firebase config file.`); + status.start(); + execute( + `cd firetable/www/src/firebase; echo 'export default ${config.replace( + /\n/g, + "" + )}' > config.ts`, + function(results) { + status.stop(); + resolve(results); + } + ); + }); diff --git a/cli/logo.js b/cli/logo.js new file mode 100644 index 00000000..0f76f015 --- /dev/null +++ b/cli/logo.js @@ -0,0 +1,52 @@ +const chalk = require("chalk"); +const figlet = require("figlet"); + +const logo = `tttttttttttttttttttttttttttttttttttttttt +tttttttttttttttttttttttttttttttttttttttt +tttttttttttttttttttttttt tttt +tttttttttttttttttttttttt A tttt +tttttttttttttttttttttttt AA AA tttt +tttttttttttttttttttttttt AA AA tttt +tttttttttttttttttttttttt AAA AAA tttt +tttttttttttttt tttt +tttttttttttttt tttttttt ttttttt tttt +tttttttttttttt tttttttt ttttttt tttt +tttttttttttttt tttttttt ttttttt tttt +tttt tttt +tttt ttttttt tttttttt ttttttt tttt +tttt ttttttt tttttttt ttttttt tttt +tttt ttttttt tttttttt ttttttt tttt +tttt tttt +tttttttttttttttttttttttttttttttttttttttt +tttttttttttttttttttttttttttttttttttttttt`; + +const logo1 = `++++++++++++++++++++++++++++++++++++++++ +++++++++++++++++++++++++++++++++++++++++ +++++++++++++++++++++++++ ++++ +++++++++++++++++++++++++ A ++++ +++++++++++++++++++++++++ AA AA ++++ +++++++++++++++++++++++++ AA AA ++++ +++++++++++++++++++++++++ AAA AAA ++++ +++++++++++++++ ++++ +++++++++++++++ ++++++++ +++++++ ++++ +++++++++++++++ ++++++++ +++++++ ++++ +++++++++++++++ ++++++++ +++++++ ++++ +++++ ++++ +++++ +++++++ ++++++++ +++++++ ++++ +++++ +++++++ ++++++++ +++++++ ++++ +++++ +++++++ ++++++++ +++++++ ++++ +++++ ++++ +++++++++++++++++++++++++++++++++++++++++ +++++++++++++++++++++++++++++++++++++++++`; + +module.exports.printLogo = () => { + console.log(chalk.red(logo1)); + console.log( + chalk.white( + figlet.textSync("FIRETABLE", { + font: "rounded", + horizontalLayout: "full", + }) + ) + ); +}; 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 045ccd73..d4ebe848 100644 --- a/www/package.json +++ b/www/package.json @@ -64,9 +64,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 4aa56a0d..a5bae310 100644 --- a/www/src/components/Table/ColumnMenu/ConfigFields/CodeEditor.tsx +++ b/www/src/components/Table/ColumnMenu/ConfigFields/CodeEditor.tsx @@ -29,11 +29,11 @@ const useStyles = makeStyles(theme => }, }) ); + export default function CodeEditor(props: any) { const { handleChange, script } = props; - const { tableState } = useFiretableContext(); - console.log({ script }); + const classes = useStyles(); const editorRef = useRef(); 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/config.ts b/www/src/firebase/config.ts new file mode 100644 index 00000000..faffdac0 --- /dev/null +++ b/www/src/firebase/config.ts @@ -0,0 +1,7 @@ +export default { + apiKey: process.env.REACT_APP_FIREBASE_PROJECT_WEB_API_KEY, + authDomain: `${process.env.REACT_APP_FIREBASE_PROJECT_ID}.firebaseapp.com`, + databaseURL: `https://${process.env.REACT_APP_FIREBASE_PROJECT_ID}.firebaseio.com`, + projectId: process.env.REACT_APP_FIREBASE_PROJECT_ID, + storageBucket: `${process.env.REACT_APP_FIREBASE_PROJECT_ID}.appspot.com`, +}; 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/firebase/index.ts b/www/src/firebase/index.ts index 1c0ff00a..7f5a1a48 100644 --- a/www/src/firebase/index.ts +++ b/www/src/firebase/index.ts @@ -4,15 +4,9 @@ import "firebase/firestore"; import "firebase/functions"; import "firebase/storage"; -const config = { - apiKey: process.env.REACT_APP_FIREBASE_PROJECT_WEB_API_KEY, - authDomain: `${process.env.REACT_APP_FIREBASE_PROJECT_ID}.firebaseapp.com`, - databaseURL: `https://${process.env.REACT_APP_FIREBASE_PROJECT_ID}.firebaseio.com`, - projectId: process.env.REACT_APP_FIREBASE_PROJECT_ID, - storageBucket: `${process.env.REACT_APP_FIREBASE_PROJECT_ID}.appspot.com`, -}; +import appConfig from "./config"; -firebase.initializeApp(config); +firebase.initializeApp(appConfig); export const auth = firebase.auth(); @@ -29,5 +23,4 @@ export const googleProvider = new firebase.auth.GoogleAuthProvider().setCustomPa } ); - 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={