diff --git a/www/src/components/Table/TableHeader/Sparks/SparkList.tsx b/www/src/components/Table/TableHeader/Sparks/SparkList.tsx index a2537830..5abf65c6 100644 --- a/www/src/components/Table/TableHeader/Sparks/SparkList.tsx +++ b/www/src/components/Table/TableHeader/Sparks/SparkList.tsx @@ -1,16 +1,60 @@ -import { Box, Button, Typography } from "@material-ui/core"; +import { + Box, + Button, + makeStyles, + Menu, + MenuItem, + Typography, +} from "@material-ui/core"; import React, { useState } from "react"; -import { parseSparkConfig, serialiseSpark, ISpark } from "./utils"; +import { + parseSparkConfig, + serialiseSpark, + sparkTypes, + ISpark, + ISparkType, +} from "./utils"; import EmptyState from "components/EmptyState"; import AddIcon from "@material-ui/icons/Add"; +import EmptyIcon from "@material-ui/icons/AddBox"; +import { useRef } from "react"; + +const useStyles = makeStyles((theme) => ({ + hoverableEmptyState: { + borderRadius: theme.spacing(1), + cursor: "pointer", + padding: theme.spacing(2), + "&:hover": { + background: theme.palette.background.paper, + }, + }, +})); export interface ISparkListProps { sparks: ISpark[]; - handleAddSpark: () => void; + handleAddSpark: (type: ISparkType) => void; } export default function SparkList({ sparks, handleAddSpark }: ISparkListProps) { + const [anchorEl, setAnchorEl] = useState(null); + const addButtonRef = useRef(null); + const classes = useStyles(); + const activeSparkCount = sparks.filter((spark) => spark.active).length; + + const handleAddButton = () => { + setAnchorEl(addButtonRef.current); + }; + + const handleChooseAddType = (type: ISparkType) => { + handleClose(); + handleAddSpark(type); + }; + + const handleClose = () => { + setAnchorEl(null); + }; + return ( <> SPARKS ({activeSparkCount}/{sparks.length}) - + + {sparkTypes.map((type) => ( + { + handleChooseAddType(type); + }} + > + {type} + + ))} + {sparks.length === 0 && ( )} diff --git a/www/src/components/Table/TableHeader/Sparks/SparkModal.tsx b/www/src/components/Table/TableHeader/Sparks/SparkModal.tsx index f6a37d2f..b10f4d57 100644 --- a/www/src/components/Table/TableHeader/Sparks/SparkModal.tsx +++ b/www/src/components/Table/TableHeader/Sparks/SparkModal.tsx @@ -1,6 +1,5 @@ import React, { useState } from "react"; -import { parseSparkConfig, serialiseSpark, ISpark } from "./utils"; -import EmptyState from "components/EmptyState"; +import { emptySparkObject, ISpark, ISparkType, triggerTypes } from "./utils"; import BackIcon from "@material-ui/icons/ArrowBack"; import Modal from "components/Modal"; import CodeEditor from "../../editors/CodeEditor"; @@ -99,37 +98,30 @@ function TabPanel(props: TabPanelProps) { export interface ISparkModalProps { // sparks: ISpark[]; handleClose: () => void; - handleSave: () => void; + handleSave: (sparkObject: ISpark) => void; + mode: "add" | "update"; + type: ISparkType; } export default function SparkModal({ handleClose, handleSave, + mode, + type, }: ISparkModalProps) { - const [activated, setActivated] = useState(false); + const [sparkObject, setSparkObject] = useState( + mode === "add" ? emptySparkObject(type) : emptySparkObject(type) + ); const [tabIndex, setTabIndex] = useState(0); const [firestoreFields, setFirestoreFields] = useState([]); const classes = useStyles(); const { tableState } = useFiretableContext(); + const columns = Object.keys(tableState?.columns ?? {}); - const handleChange = (event: React.ChangeEvent<{}>, newValue: number) => { + const handleChange = (_, newValue: number) => { setTabIndex(newValue); }; - const handleNewField = () => { - setFirestoreFields([...firestoreFields, ""]); - }; - - const handleUpdateField = (newValue, index) => { - setFirestoreFields( - firestoreFields.map((value, i) => (i === index ? newValue : value)) - ); - }; - - const handleRemoveField = (index) => { - setFirestoreFields(firestoreFields.filter((_, i) => i !== index)); - }; - return ( { + setSparkObject({ + ...sparkObject, + name: event.target.value, + }); + }} /> @@ -171,11 +170,16 @@ export default function SparkModal({ alignItems="center" className={classes.hoverable} onClick={() => { - setActivated(!activated); + setSparkObject({ + ...sparkObject, + active: !sparkObject.active, + }); }} > - - Spark is {!activated && "de"}activated + + + Spark is {!sparkObject.active && "de"}activated + @@ -183,7 +187,7 @@ export default function SparkModal({ - {["create", "update", "delete"].map((trigger, index) => ( + {triggerTypes.map((trigger) => ( { + if (sparkObject.triggers.includes(trigger)) { + setSparkObject({ + ...sparkObject, + triggers: sparkObject.triggers.filter( + (t) => t !== trigger + ), + }); + } else { + setSparkObject({ + ...sparkObject, + triggers: [...sparkObject.triggers, trigger], + }); + } + }} > { - console.log("yay"); - }} + checked={sparkObject.triggers.includes(trigger)} name={trigger} /> {trigger} @@ -255,52 +271,89 @@ export default function SparkModal({ Required Fields (Optional) - {tableState?.columns && - Object.keys(tableState?.columns).map((trigger) => ( - - { - console.log("yay"); - }} - name={trigger} - /> - {trigger} - - ))} - {firestoreFields.map((trigger, index) => ( - - { - handleRemoveField(index); - }} - > - - - { - handleUpdateField(event.target.value, index); - }} + {columns.map((field) => ( + { + if (sparkObject.requiredFields.includes(field)) { + setSparkObject({ + ...sparkObject, + requiredFields: sparkObject.requiredFields.filter( + (t) => t !== field + ), + }); + } else { + setSparkObject({ + ...sparkObject, + requiredFields: [ + ...sparkObject.requiredFields, + field, + ], + }); + } + }} + > + + {field} ))} + {sparkObject.requiredFields.map((trigger, index) => { + const isFiretableColumn = columns.includes(trigger); + if (isFiretableColumn) { + return null; + } + + return ( + + { + setSparkObject({ + ...sparkObject, + requiredFields: sparkObject.requiredFields.filter( + (t) => t !== trigger + ), + }); + }} + > + + + { + setSparkObject({ + ...sparkObject, + requiredFields: sparkObject.requiredFields.map( + (value, i) => + i === index ? event.target.value : value + ), + }); + }} + /> + + ); + })} @@ -311,10 +364,13 @@ export default function SparkModal({ Conditions { - // setLocalSparks(newValue); + setSparkObject({ + ...sparkObject, + shouldRun: newValue, + }); }} onValideStatusUpdate={({ isValid }) => { // setIsSparksValid(isValid); @@ -335,10 +391,13 @@ export default function SparkModal({ {/* TODO break spark body fields into editable UI components */} { - // setLocalSparks(newValue); + setSparkObject({ + ...sparkObject, + sparkBody: newValue, + }); }} onValideStatusUpdate={({ isValid }) => { // setIsSparksValid(isValid); @@ -356,8 +415,10 @@ export default function SparkModal({ } actions={{ primary: { - children: "Update Settings", - onClick: handleSave, + children: mode === "add" ? "Save" : "Update", + onClick: () => { + handleSave(sparkObject); + }, }, }} /> diff --git a/www/src/components/Table/TableHeader/Sparks/index.tsx b/www/src/components/Table/TableHeader/Sparks/index.tsx index dc5914ec..51898d02 100644 --- a/www/src/components/Table/TableHeader/Sparks/index.tsx +++ b/www/src/components/Table/TableHeader/Sparks/index.tsx @@ -25,7 +25,7 @@ import CodeEditor from "../../editors/CodeEditor"; import SparkList from "./SparkList"; import SparkModal from "./SparkModal"; -import { parseSparkConfig, serialiseSpark, ISpark } from "./utils"; +import { serialiseSpark, sparkTypes, ISpark, ISparkType } from "./utils"; export default function SparksEditor() { const snack = useSnackContext(); @@ -38,7 +38,10 @@ export default function SparksEditor() { const [localSparks, setLocalSparks] = useState(currentSparks); const [open, setOpen] = useState(false); const [isSparksValid, setIsSparksValid] = useState(true); - const [sparkModalOpen, setSparkModalOpen] = useState(false); + const [sparkModal, setSparkModal] = useState<{ + mode: "add" | "update"; + type: ISparkType; + } | null>(null); const snackLogContext = useSnackLogContext(); const tablePathTokens = @@ -104,6 +107,14 @@ export default function SparksEditor() { }); }; + const handleAddSpark = (sparkObject: ISpark) => { + tableActions?.table.updateConfig("sparkObjects", [ + ...currentSparkObjects, + sparkObject, + ]); + setSparkModal(null); + }; + return ( <> { - setSparkModalOpen(true); + handleAddSpark={(type: ISparkType) => { + setSparkModal({ mode: "add", type }); }} /> @@ -150,14 +161,14 @@ export default function SparksEditor() { /> )} - {sparkModalOpen && ( + {sparkModal && ( { - setSparkModalOpen(false); - }} - handleSave={() => { - console.log("SAVE"); + setSparkModal(null); }} + handleSave={handleAddSpark} + mode={sparkModal.mode} + type={sparkModal.type} /> )} diff --git a/www/src/components/Table/TableHeader/Sparks/utils.ts b/www/src/components/Table/TableHeader/Sparks/utils.ts index f5a3bf14..d42ead40 100644 --- a/www/src/components/Table/TableHeader/Sparks/utils.ts +++ b/www/src/components/Table/TableHeader/Sparks/utils.ts @@ -1,24 +1,57 @@ +type ISparkType = + | "task" + | "docSync" + | "historySnapshot" + | "algoliaIndex" + | "meiliIndex" + | "bigqueryIndex" + | "slackMessage" + | "sendgridEmail" + | "apiCall" + | "twilioMessage"; + +type ISparkTrigger = "create" | "update" | "delete"; + interface ISpark { // firetable meta fields name: string; active: boolean; // ft build fields - triggers: "create" | "update" | "delete"; - type: - | "task" - | "docSync" - | "historySnapshot" - | "algoliaIndex" - | "meiliIndex" - | "bigqueryIndex" - | "slackMessage" - | "sendgridEmail" - | "apiCall" - | "twilioMessage"; - shouldRun: boolean; - requiredFields?: string[]; - sparkBody: any; + triggers: ISparkTrigger[]; + type: ISparkType; + requiredFields: string[]; + sparkBody: string; + shouldRun: boolean | string; + + // TODO break spark body into editable fields +} + +const triggerTypes: ISparkTrigger[] = ["create", "update", "delete"]; + +const sparkTypes: ISparkType[] = [ + "task", + "docSync", + "historySnapshot", + "algoliaIndex", + "meiliIndex", + "bigqueryIndex", + "slackMessage", + "sendgridEmail", + "apiCall", + "twilioMessage", +]; + +function emptySparkObject(type: ISparkType): ISpark { + return { + name: "", + active: false, + triggers: [], + type, + sparkBody: "{}", + requiredFields: [], + shouldRun: "return true;", + }; } /* Convert spark objects into a single ft-build readable string */ @@ -45,5 +78,11 @@ function parseSparkConfig(sparkConfig): ISpark[] { } } -export { parseSparkConfig, serialiseSpark }; -export type { ISpark }; +export { + parseSparkConfig, + serialiseSpark, + sparkTypes, + triggerTypes, + emptySparkObject, +}; +export type { ISpark, ISparkType }; diff --git a/www/src/hooks/useFiretable/index.ts b/www/src/hooks/useFiretable/index.ts index f58d6c26..8f9daf8e 100644 --- a/www/src/hooks/useFiretable/index.ts +++ b/www/src/hooks/useFiretable/index.ts @@ -77,6 +77,7 @@ const useFiretable = ( rowHeight: tableConfig.rowHeight, webhooks: tableConfig.doc?.webhooks, sparks: tableConfig.doc?.sparks, + sparkObjects: tableConfig.doc?.sparkObjects, tableConfig, }, rows: tableState.rows,