/* * QQQ - Low-code Application Framework for Engineers. * Copyright (C) 2021-2022. Kingsrook, LLC * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States * contact@kingsrook.com * https://github.com/Kingsrook/ * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ import {QJobError} from "@kingsrook/qqq-frontend-core/lib/model/processes/QJobError"; import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord"; import {ToggleButton, ToggleButtonGroup, Typography} from "@mui/material"; import Alert from "@mui/material/Alert"; import Box from "@mui/material/Box"; import Card from "@mui/material/Card"; import Grid from "@mui/material/Grid"; import Snackbar from "@mui/material/Snackbar"; import TextField from "@mui/material/TextField"; import FormData from "form-data"; import React, {useEffect, useReducer, useState} from "react"; import AceEditor from "react-ace"; import {QCancelButton, QSaveButton} from "qqq/components/buttons/DefaultButtons"; import ScriptDocsForm from "qqq/components/scripts/ScriptDocsForm"; import ScriptTestForm from "qqq/components/scripts/ScriptTestForm"; import Client from "qqq/utils/qqq/Client"; import "ace-builds/src-noconflict/mode-javascript"; import "ace-builds/src-noconflict/theme-github"; import "ace-builds/src-noconflict/ext-language_tools"; export interface ScriptEditorProps { title: string; scriptId: number; contents: string; closeCallback: any; tableName: string; fieldName: string; recordId: any; scriptDefinition: any; scriptTypeRecord: QRecord; } const qController = Client.getInstance(); function ScriptEditor({title, scriptId, contents, closeCallback, tableName, fieldName, recordId, scriptDefinition, scriptTypeRecord}: ScriptEditorProps): JSX.Element { const [closing, setClosing] = useState(false); const [updatedCode, setUpdatedCode] = useState(contents) const [commitMessage, setCommitMessage] = useState("") const [openTool, setOpenTool] = useState(null); const [errorAlert, setErrorAlert] = useState("") const [, forceUpdate] = useReducer((x) => x + 1, 0); useEffect(() => { // @ts-ignore // eslint-disable-next-line import/namespace const langTools = ace.require("ace/ext/language_tools"); const myCompleter = { // @ts-ignore getCompletions: function (editor, session, pos, prefix, callback) { // @ts-ignore let completions = []; // todo - get from backend, based on the script type completions.push({value: "api.query(", meta: "Search for records in a table."}); completions.push({value: "api.insert(", meta: "Create one or more records in a table."}); completions.push({value: "api.update(", meta: "Update one or more records in a table."}); completions.push({value: "api.delete(", meta: "Remove one or more records from a table."}); completions.push({value: "api.newRecord(", meta: "Create a new QRecord object."}); completions.push({value: "api.newQueryInput(", meta: "Create a new QueryInput object."}); completions.push({value: "api.newQueryFilter(", meta: "Create a new QueryFilter object."}); completions.push({value: "api.newFilterCriteria(", meta: "Create a new FilterCriteria object."}); completions.push({value: "api.newFilterOrderBy(", meta: "Create a new FilterOrderBy object."}); completions.push({value: "getValue(", meta: "Get a value from a record"}); completions.push({value: "logger.log(", meta: "Write a Script Log Line"}); // @ts-ignore callback(null, completions); } }; langTools.addCompleter(myCompleter); const preventUnload = (event: BeforeUnloadEvent) => { // NOTE: This message isn't used in modern browsers, but is required const message = "Are you sure you want to leave?"; event.preventDefault(); event.returnValue = message; }; window.addEventListener("beforeunload", preventUnload); return () => { window.removeEventListener("beforeunload", preventUnload); }; }, []); const changeOpenTool = (event: React.MouseEvent, newValue: string | null) => { setOpenTool(newValue); // need this to make Ace recognize new height. setTimeout(() => { window.dispatchEvent(new Event("resize")) }, 100); }; const saveClicked = () => { setClosing(true); (async () => { const formData = new FormData(); formData.append("scriptId", scriptId); formData.append("contents", updatedCode); formData.append("commitMessage", commitMessage); ////////////////////////////////////////////////////////////////// // we don't want this job to go async, so, pass a large timeout // ////////////////////////////////////////////////////////////////// formData.append("_qStepTimeoutMillis", 60 * 1000); const formDataHeaders = { "content-type": "multipart/form-data; boundary=--------------------------320289315924586491558366", }; try { const processResult = await qController.processInit("storeScriptRevision", formData, formDataHeaders); console.log("process result"); console.log(processResult); if (processResult instanceof QJobError) { const jobError = processResult as QJobError setErrorAlert(jobError.userFacingError ?? jobError.error) setClosing(false); return; } closeCallback(null, "saved", "Saved New Script Version"); } catch(e) { // @ts-ignore setErrorAlert(e.message ?? "Unexpected error saving script") setClosing(false); } })(); } const cancelClicked = () => { setClosing(true); closeCallback(null, "cancelled"); } const updateCode = (value: string, event: any) => { console.log("Updating code") setUpdatedCode(value); forceUpdate(); } const updateCommitMessage = (event: React.ChangeEvent) => { setCommitMessage(event.target.value); } return ( { if (reason === "clickaway") { return; } setErrorAlert("") }} anchorOrigin={{vertical: "top", horizontal: "center"}}> setErrorAlert("")}> {errorAlert} {title} Tools: Test Docs { openTool && { openTool == "test" && } { openTool == "docs" && } } ); } export default ScriptEditor;