diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 63edb31..4794d91 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -42,7 +42,7 @@ import { Sick, Token, } from "@mui/icons-material"; -import React, { useCallback, useEffect, useState } from "react"; +import React, { useCallback, useEffect, useRef, useState } from "react"; import { storeAtom } from "./store"; import { useAtom } from "jotai"; import { getList } from "./shared"; @@ -53,6 +53,7 @@ import HistoryDialog from "./components/History/HistoryDialog"; import HistoryList from "./components/History/HistoryList"; import MarkdownDiv from "./components/Common/MarkdownDiv"; import InlineBox from "./components/Common/InlineBox"; +import useFunixHistory from "./shared/useFunixHistory"; const drawerWidth = 240; @@ -205,6 +206,7 @@ const App = () => { }, setStore, ] = useAtom(storeAtom); + const { getHistories } = useFunixHistory(); const funixBackend: string | undefined = process.env.REACT_APP_FUNIX_BACKEND; const selectedFunctionSecret: string | null = selectedFunction?.secret @@ -226,6 +228,7 @@ const App = () => { const [tempAppSecret, setTempAppSecret] = useState(appSecret); const [functionListWidth, setFunctionListWidth] = useState(drawerWidth); const [onResizing, setOnResizing] = useState(false); + const historyLoaded = useRef(false); const handlePointerMoveLeftSidebar = useCallback((e: PointerEvent | any) => { setFunctionListWidth(e.clientX - document.body.offsetLeft); @@ -263,6 +266,27 @@ const App = () => { }); }, [window.location.origin]); + useEffect(() => { + if (historyLoaded.current) { + return; + } + getHistories().then((histories) => { + setStore((store) => ({ + ...store, + histories, + })); + }); + }, [historyLoaded]); + + window.addEventListener("funix-history-update", () => { + getHistories().then((histories) => { + setStore((store) => ({ + ...store, + histories, + })); + }); + }); + useEffect(() => { if (typeof backendURL === "undefined") return; setStore((store) => { @@ -576,8 +600,8 @@ const App = () => { spacing={2} > - Power by Funix.io, minimally - building apps in Python + Power by Funix.io, + minimally building apps in Python diff --git a/frontend/src/components/FunixFunction/InputPanel.tsx b/frontend/src/components/FunixFunction/InputPanel.tsx index e296e05..8390fcd 100644 --- a/frontend/src/components/FunixFunction/InputPanel.tsx +++ b/frontend/src/components/FunixFunction/InputPanel.tsx @@ -6,8 +6,7 @@ import { FormControlLabel, Grid, } from "@mui/material"; -import Card from "@mui/material/Card"; -// eslint-disable-next-line @typescript-eslint/ban-ts-comment +import Card from "@mui/material/Card"; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore import Form from "@rjsf/material-ui/v5"; import React, { useEffect, useState } from "react"; @@ -128,8 +127,18 @@ const InputPanel = (props: { } : form : form; + const isLarge = + Object.values(props.detail.schema.properties).findIndex((value) => { + const newValue = value as unknown as any; + const largeWidgets = ["image", "video", "audio", "file"]; + if ("items" in newValue) { + return largeWidgets.includes(newValue.items.widget); + } else { + return largeWidgets.includes(newValue.widget); + } + }) !== -1; - if (saveHistory) { + if (saveHistory && !isLarge) { try { await setInput(now, props.preview.name, props.preview.path, newForm); } catch (error) { diff --git a/frontend/src/components/FunixFunction/ObjectFieldExtendedTemplate.tsx b/frontend/src/components/FunixFunction/ObjectFieldExtendedTemplate.tsx index 6e96446..47891bc 100644 --- a/frontend/src/components/FunixFunction/ObjectFieldExtendedTemplate.tsx +++ b/frontend/src/components/FunixFunction/ObjectFieldExtendedTemplate.tsx @@ -520,7 +520,7 @@ const ObjectFieldExtendedTemplate = (props: ObjectFieldProps) => { const result = props.properties.filter( (prop: any) => prop.name === rowItem.argument ); - console.log(rowItem, result); + // console.log(rowItem, result); if (result.length === 0) { rowElement = null; } else { diff --git a/frontend/src/components/History/HistoryDialog.tsx b/frontend/src/components/History/HistoryDialog.tsx index 2a8881e..e300ac6 100644 --- a/frontend/src/components/History/HistoryDialog.tsx +++ b/frontend/src/components/History/HistoryDialog.tsx @@ -30,7 +30,7 @@ import { FileDownload, Preview, } from "@mui/icons-material"; -import React, { useEffect } from "react"; +import React from "react"; import { Timeline, TimelineConnector, @@ -101,7 +101,9 @@ const HistoryDialog = (props: { open: boolean; setOpen: (open: boolean) => void; }) => { - const { getHistories, clearHistory, removeHistory, setHistoryNameAndPath } = + const [{ histories }] = useAtom(storeAtom); + + const { clearHistory, removeHistory, setHistoryNameAndPath } = useFunixHistory(); const [{ functions }, setStore] = useAtom(storeAtom); const [isAscending, setAscending] = React.useState(true); @@ -114,6 +116,7 @@ const HistoryDialog = (props: { const [singleCollapsedMap, setSingleCollapsedMap] = React.useState< Record> >({}); + // const loaded = useRef(false); // const [backdropOpen, setBackdropOpen] = React.useState(false); // const [rewindSnackbarOpen, setRewindSnackbarOpen] = React.useState(false); @@ -138,15 +141,19 @@ const HistoryDialog = (props: { // backHistory(history); // }; - const [histories, setHistories] = React.useState([]); - - useEffect(() => { - getHistories().then((h) => setHistories(h)); - }, []); - - window.addEventListener("funix-history-update", () => { - getHistories().then((h) => setHistories(h)); - }); + // const [histories, setHistories] = React.useState([]); + // + // useEffect(() => { + // if (loaded.current) { + // return; + // } + // getHistories().then((h) => setHistories(h)); + // loaded.current = true; + // }, [loaded]); + // + // window.addEventListener("funix-history-update", () => { + // getHistories().then((h) => setHistories(h)); + // }); return ( <> @@ -243,9 +250,7 @@ const HistoryDialog = (props: { diff --git a/frontend/src/components/History/HistoryList.tsx b/frontend/src/components/History/HistoryList.tsx index 01f098f..522744b 100644 --- a/frontend/src/components/History/HistoryList.tsx +++ b/frontend/src/components/History/HistoryList.tsx @@ -30,29 +30,19 @@ import React, { useEffect, useState } from "react"; import { exportHistory } from "../../shared"; const HistoryList = () => { - const { getHistories, setHistoryNameAndPath, removeHistory } = - useFunixHistory(); - const [{ selectedFunction }, setStore] = useAtom(storeAtom); + const { setHistoryNameAndPath, removeHistory } = useFunixHistory(); + const [{ selectedFunction, histories }, setStore] = useAtom(storeAtom); const [selectedHistoryTimestamp, setSelectedHistoryTimestamp] = useState(-1); const [selectedHistory, setSelectedHistory] = useState(null); const [renameDialogOpen, setRenameDialogOpen] = useState(false); const [tempRename, setTempRename] = useState(""); const [anchorEl, setAnchorEl] = useState(null); - const [histories, setHistories] = useState([]); const open = Boolean(anchorEl); useEffect(() => { setSelectedHistoryTimestamp(-1); }, [selectedFunction]); - useEffect(() => { - getHistories().then((h) => setHistories(h)); - }, []); - - window.addEventListener("funix-history-update", () => { - getHistories().then((h) => setHistories(h)); - }); - if (selectedFunction === null || histories.length === 0) { return ( @@ -133,7 +123,7 @@ const HistoryList = () => { { if (selectedHistory !== null) { - removeHistory(selectedHistory.uuid); + removeHistory(selectedHistory.timestamp); } setSelectedHistory(null); setAnchorEl(null); diff --git a/frontend/src/components/History/HistoryUtils.tsx b/frontend/src/components/History/HistoryUtils.tsx index 0cd72f8..387e0c3 100644 --- a/frontend/src/components/History/HistoryUtils.tsx +++ b/frontend/src/components/History/HistoryUtils.tsx @@ -21,10 +21,10 @@ export type HistoryInfoProps = { export const getHistoryInfo = (history: History): HistoryInfoProps => { let status: HistoryEnum; - if (history.input === null) { + if (history.input === null || history.input === undefined) { status = HistoryEnum.Unknown; } else { - if (history.output === null) { + if (history.output === null || history.output === undefined) { status = HistoryEnum.Pending; } else { status = HistoryEnum.Done; @@ -32,8 +32,10 @@ export const getHistoryInfo = (history: History): HistoryInfoProps => { } const hasSecret = - history.input !== null ? "__funix_secret" in history.input : false; - if (history.output !== null) { + history.input !== null && history.input !== undefined + ? "__funix_secret" in history.input + : false; + if (history.output !== null && history.output !== undefined) { try { let output = history.output; if (typeof history.output === "string") { diff --git a/frontend/src/shared/useFunixHistory.ts b/frontend/src/shared/useFunixHistory.ts index 4386b43..982982e 100644 --- a/frontend/src/shared/useFunixHistory.ts +++ b/frontend/src/shared/useFunixHistory.ts @@ -13,20 +13,45 @@ export type History = { uuid: string; }; +const setHistory = async (historyKey: string, history: History) => { + await localforage.setItem(historyKey, history); + window.dispatchEvent(new CustomEvent("funix-history-update")); +}; + +const getHistoryOrMakeAnEmptyOne = async ( + historyKey: string, + keys: string[], + functionName: string, + functionPath: string, + timestamp: number +) => { + return keys.indexOf(historyKey) !== -1 + ? await localforage.getItem(historyKey) + : { + input: null, + output: null, + functionName, + functionPath, + name: null, + timestamp, + uuid: uuid4(), + }; +}; + const useFunixHistory = () => { const getHistories = async () => { - return localforage - .getItem("funix-history") - .then((value) => { - if (value) { - // By my hand, I think I will handle it (oh, e, hah - return JSON.parse(value as string) as History[]; - } - return [] as History[]; - }) - .catch(() => { - return [] as History[]; - }); + const histories: History[] = []; + const keys = await localforage.keys(); + const historyKeys = keys.filter((key) => key.startsWith("funix-history-")); + + for (const historyKey of historyKeys) { + const history = await localforage.getItem(historyKey); + if (history !== null) { + histories.push(history); + } + } + + return histories.sort((a, b) => b.timestamp - a.timestamp); }; const setInput = async ( @@ -35,37 +60,22 @@ const useFunixHistory = () => { functionPath: string, input: Record ) => { - getHistories().then((histories) => { - const index = histories.findIndex((h) => h.timestamp === timestamp); - if (index !== -1) { - histories[index].input = input; - } else { - histories.push({ - input, - output: null, - functionName, - functionPath, - name: null, - timestamp, - uuid: uuid4(), - }); - } - localforage - .setItem("funix-history", JSON.stringify(histories)) - .then(() => { - window.dispatchEvent(new CustomEvent("funix-history-update")); - }) - .catch((error) => { - enqueueSnackbar( - "Cannot save input to history, check your console for more information", - { - variant: "error", - } - ); - console.error("Funix History Error:"); - console.error(error); - }); - }); + const historyKey = `funix-history-${timestamp}`; + + const history: History | null = await getHistoryOrMakeAnEmptyOne( + historyKey, + await localforage.keys(), + functionName, + functionPath, + timestamp + ); + + if (history !== null) { + await setHistory(historyKey, { + ...history, + input, + }); + } }; const setOutput = async ( @@ -74,106 +84,52 @@ const useFunixHistory = () => { functionPath: string, output: PostCallResponse | string ) => { - getHistories().then((histories) => { - const index = histories.findIndex((h) => h.timestamp === timestamp); - let newOutput = output; - if (typeof output === "string") { - try { - newOutput = JSON.parse(output); - } catch (e) { - newOutput = output; - } - } - if (index !== -1) { - histories[index].output = newOutput; - } else { - histories.push({ - input: null, - output: newOutput, - functionName, - functionPath, - name: null, - timestamp, - uuid: uuid4(), - }); + const historyKey = `funix-history-${timestamp}`; + + let newOutput = output; + if (typeof output === "string") { + try { + newOutput = JSON.parse(output); + } catch (e) { + newOutput = output; } - localforage - .setItem("funix-history", JSON.stringify(histories)) - .then(() => { - window.dispatchEvent(new CustomEvent("funix-history-update")); - }) - .catch((error) => { - enqueueSnackbar( - "Cannot save output to history, check your console for more information", - { - variant: "error", - } - ); - console.error("Funix History Error:"); - console.error(error); - }); - }); + } + + const history: History | null = await getHistoryOrMakeAnEmptyOne( + historyKey, + await localforage.keys(), + functionName, + functionPath, + timestamp + ); + + if (history !== null) { + await setHistory(historyKey, { + ...history, + output: newOutput, + }); + } }; - const setInputOutput = ( + const setInputOutput = async ( timestamp: number, functionName: string, functionPath: string, input: Record, output: string | PostCallResponse ) => { - getHistories().then((histories) => { - const index = histories.findIndex((h) => h.timestamp === timestamp); - let newOutput = output; - if (typeof output === "string") { - try { - newOutput = JSON.parse(output); - } catch (e) { - newOutput = output; - } - } - if (index !== -1) { - histories[index].input = input; - histories[index].output = newOutput; - } else { - histories.push({ - input, - output: newOutput, - functionName, - functionPath, - name: null, - timestamp, - uuid: uuid4(), - }); - } - localforage - .setItem("funix-history", JSON.stringify(histories)) - .then(() => { - window.dispatchEvent(new CustomEvent("funix-history-update")); - }) - .catch((error) => { - enqueueSnackbar( - "Cannot save input and output to history, check your console for more information", - { - variant: "error", - } - ); - console.error("Funix History Error:"); - console.error(error); - }); - }); + await setInput(timestamp, functionName, functionPath, input); + await setOutput(timestamp, functionName, functionPath, output); }; - const removeHistory = (uuid: string) => { - getHistories().then((histories) => { - localforage - .setItem( - "funix-history", - JSON.stringify(histories.filter((h) => h.uuid !== uuid)) - ) - .then(() => { + const removeHistory = (timestamp: number) => { + localforage.keys().then((keys) => { + const historyKey = `funix-history-${timestamp}`; + if (keys.indexOf(historyKey) !== -1) { + localforage.removeItem(historyKey).then(() => { window.dispatchEvent(new CustomEvent("funix-history-update")); }); + } }); }; @@ -182,22 +138,39 @@ const useFunixHistory = () => { name: string, path: string ) => { - getHistories().then((histories) => { - const index = histories.findIndex((h) => h.timestamp === timestamp); - if (index !== -1) { - histories[index].name = name; - histories[index].functionPath = path; - } - localforage - .setItem("funix-history", JSON.stringify(histories)) - .then(() => { - window.dispatchEvent(new CustomEvent("funix-history-update")); + localforage.keys().then((keys) => { + const historyKey = `funix-history-${timestamp}`; + if (keys.indexOf(historyKey) !== -1) { + localforage.getItem(historyKey).then((history) => { + if (history !== null) { + const newHistory = { + ...history, + name, + functionPath: path, + }; + localforage + .setItem(historyKey, newHistory) + .then(() => { + window.dispatchEvent(new CustomEvent("funix-history-update")); + }) + .catch((error) => { + enqueueSnackbar( + "Cannot save input to history, check your console for more information", + { + variant: "error", + } + ); + console.error("Funix History Error:"); + console.error(error); + }); + } }); + } }); }; const clearHistory = () => { - localforage.removeItem("funix-history").then(() => { + localforage.clear().then(() => { window.dispatchEvent(new CustomEvent("funix-history-update")); }); }; diff --git a/frontend/src/store.ts b/frontend/src/store.ts index a0d8a0a..4d8a573 100644 --- a/frontend/src/store.ts +++ b/frontend/src/store.ts @@ -15,6 +15,7 @@ export type Store = { backConsensus: boolean[]; saveHistory: boolean; appSecret: null | string; + histories: History[]; }; export const storeAtom = atom({ @@ -30,4 +31,5 @@ export const storeAtom = atom({ backConsensus: [false, false, false], saveHistory: true, appSecret: null, + histories: [], });