Merge pull request #157 from AntlerVC/develop

Develop
This commit is contained in:
AntlerEngineering
2020-08-11 21:42:42 +10:00
committed by GitHub
34 changed files with 765 additions and 89 deletions

1
cli/.npmignore Normal file
View File

@@ -0,0 +1 @@
node_modules/

170
cli/index.js Executable file
View File

@@ -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);

12
cli/lib/files.js Normal file
View File

@@ -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);
},
};

78
cli/lib/inquirer.js Normal file
View File

@@ -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: `,
}),
};

67
cli/lib/repo.js Normal file
View File

@@ -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();
}
},
};

206
cli/lib/terminal.js Normal file
View File

@@ -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);
}
);
});

52
cli/logo.js Normal file
View File

@@ -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",
})
)
);
};

13
www/app.yaml Normal file
View File

@@ -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/(.*)

5
www/dispatch.yaml Normal file
View File

@@ -0,0 +1,5 @@
dispatch:
- url: "firetable-magic.uc.r.appspot.com/"
service: default
- url: "*.firetable.io/"
service: default

View File

@@ -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",

View File

@@ -72,7 +72,12 @@ const App: React.FC = () => {
<PrivateRoute
exact
path={[routes.home, routes.tableWithId, routes.gridWithId]}
path={[
routes.home,
routes.tableWithId,
routes.tableGroupWithId,
routes.gridWithId,
]}
render={() => (
<FiretableContextProvider>
<Switch>
@@ -86,8 +91,8 @@ const App: React.FC = () => {
render={() => <TableView />}
/>
<PrivateRoute
path={routes.gridWithId}
render={() => <GridView />}
path={routes.tableGroupWithId}
render={() => <TableView />}
/>
</Switch>
</FiretableContextProvider>

View File

@@ -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<IConnectTableSelectProps, "className" | "TextFieldProps"> {}
@@ -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])

View File

@@ -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}

View File

@@ -54,8 +54,6 @@ function Action({
const snack = useContext(SnackContext);
const handleRun = () => {
setIsRunning(true);
console.log("RUN");
cloudFunction(
config.callableName,
{

View File

@@ -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<any>();

View File

@@ -28,7 +28,6 @@ const ColumnSelector = ({
};
useEffect(() => {
if (table) {
console.log({ table });
getColumns(table);
}
}, [table]);

View File

@@ -219,7 +219,6 @@ export default function FormDialog({
<ConfigForm
type={type}
handleChange={key => update => {
console.log(key, update);
setNewConfig({ ...newConfig, [key]: update });
}}
config={newConfig}

View File

@@ -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);

View File

@@ -13,7 +13,6 @@ const AlgoliaSelect = (props: any) => {
} = props;
const [searchState] = useAlgolia(algoliaIndex, algoliaKey, filters);
console.log(filters);
const [options, setOptions] = useState<any[]>([]);
useEffect(() => {
if (Array.isArray(searchState.results?.hits)) {

View File

@@ -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,

View File

@@ -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}
>
<MigrateButton needsMigration={needsMigration} columns={tempColumns} />
<Grid item>
<Button
onClick={() => {
const initialVal = tempColumns.reduce((acc, currCol) => {
if (currCol.type === FieldType.checkbox) {
return { ...acc, [currCol.key]: false };
} else {
return acc;
}
}, {});
tableActions?.row.add(initialVal);
}}
variant="contained"
color="primary"
startIcon={<AddIcon />}
>
Add Row
</Button>
</Grid>
{!isCollectionGroup() && (
<Grid item>
<Button
onClick={() => {
const initialVal = tempColumns.reduce((acc, currCol) => {
if (currCol.type === FieldType.checkbox) {
return { ...acc, [currCol.key]: false };
} else {
return acc;
}
}, {});
tableActions?.row.add(initialVal);
}}
variant="contained"
color="primary"
startIcon={<AddIcon />}
>
Add Row
</Button>
</Grid>
)}
<Grid item>{/* <HiddenFields /> */}</Grid>
<Grid item>
@@ -150,9 +154,11 @@ export default function TableHeader({
<Grid item />
<Grid item>
<ImportCSV />
</Grid>
{!isCollectionGroup() && (
<Grid item>
<ImportCSV />
</Grid>
)}
<Grid item>
<ExportCSV />

View File

@@ -44,12 +44,7 @@ export type FiretableColumn = Column<any> & {
[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<HTMLDivElement>(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) {
<Hotkeys selectedCell={selectedCell} />
</Suspense> */}
{inSubTable && <SubTableBreadcrumbs collection={collection} />}
{inSubTable && <SubTableBreadcrumbs collection={tableState.tablePath} />}
<TableHeader
rowHeight={rowHeight}

View File

@@ -11,11 +11,12 @@ import {
DialogTitle,
TextField,
Button,
Select,
Typography,
Switch,
FormControlLabel,
} from "@material-ui/core";
import { useFiretableContext } from "../contexts/firetableContext";
import OptionsInput from "./Table/ColumnMenu/ConfigFields/OptionsInput";
export enum TableSettingsDialogModes {
create,
update,
@@ -31,6 +32,7 @@ export interface ICreateTableDialogProps {
section: string;
description: string;
name: string;
isCollectionGroup: boolean;
} | null;
}
@@ -40,6 +42,7 @@ const FORM_EMPTY_STATE = {
collection: "",
description: "",
roles: ["ADMIN"],
isCollectionGroup: false,
};
export default function TableSettingsDialog({
mode,
@@ -66,7 +69,11 @@ export default function TableSettingsDialog({
settingsActions?.createTable(formState);
if (router.location.pathname === "/") {
router.history.push(`table/${formState.collection}`);
router.history.push(
`${formState.isCollectionGroup ? "tableGroup" : "table"}/${
formState.collection
}`
);
} else {
router.history.push(formState.collection);
}
@@ -123,6 +130,16 @@ export default function TableSettingsDialog({
/>
)}
<FormControlLabel
control={<Switch />}
label={"isCollectionGroup"}
labelPlacement="start"
value={formState.isCollectionGroup}
onChange={e =>
handleChange("isCollectionGroup", !formState.isCollectionGroup)
}
// classes={{ root: classes.formControlLabel, label: classes.label }}
/>
<TextField
value={formState.section}
onChange={e => handleChange("section", e.target.value)}

View File

@@ -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",

View File

@@ -19,6 +19,7 @@ export type Table = {
roles: string[];
description: string;
section: string;
isCollectionGroup: boolean;
};
interface FiretableContextProps {

View File

@@ -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`,
};

View File

@@ -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;

View File

@@ -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;

View File

@@ -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) {

View File

@@ -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);

View File

@@ -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);

View File

@@ -53,3 +53,8 @@ export const sanitiseRowData = (rowData: any) => {
});
return rowData;
};
export const isCollectionGroup = () => {
const pathName = window.location.pathname.split("/")[1];
return pathName === "tableGroup";
};

View File

@@ -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 (
<Navigation tableCollection={tableCollection}>
<Table
key={tableCollection}
collection={tableCollection}
filters={filters}
/>
<Table key={tableCollection} />
<Hidden smDown>
<SideDrawer />
</Hidden>

View File

@@ -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={