1
0
mirror of https://github.com/sasjs/server.git synced 2025-12-10 19:34:34 +00:00

Merge pull request #236 from sasjs/fix-studio

fix: issues fixed in studio page
This commit is contained in:
Allan Bowe
2022-07-26 21:48:20 +01:00
committed by GitHub
7 changed files with 112 additions and 20 deletions

View File

@@ -22,8 +22,14 @@ const FilePathInputModal = ({
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const value = event.target.value
const regex = /\.(exe|sh|htaccess)$/i
if (regex.test(value)) {
const specialChars = /[`!@#$%^&*()_+\-=[\]{};':"\\|,<>?~]/
const fileExtension = /\.(exe|sh|htaccess)$/i
if (specialChars.test(value)) {
setHasError(true)
setErrorText('can not have special characters')
} else if (fileExtension.test(value)) {
setHasError(true)
setErrorText('can not save file with extensions [exe, sh, htaccess]')
} else {

View File

@@ -1,4 +1,4 @@
import React, { useState } from 'react'
import React, { useState, useEffect } from 'react'
import { Button, DialogActions, DialogContent, TextField } from '@mui/material'
@@ -12,6 +12,7 @@ type NameInputModalProps = {
isFolder: boolean
actionLabel: string
action: (name: string) => void
defaultName?: string
}
const NameInputModal = ({
@@ -20,12 +21,17 @@ const NameInputModal = ({
title,
isFolder,
actionLabel,
action
action,
defaultName
}: NameInputModalProps) => {
const [name, setName] = useState('')
const [hasError, setHasError] = useState(false)
const [errorText, setErrorText] = useState('')
useEffect(() => {
if (defaultName) setName(defaultName)
}, [defaultName])
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const value = event.target.value

View File

@@ -208,6 +208,7 @@ const TreeViewNode = ({
action={
nameInputModalActionLabel === 'Add' ? addFileFolder : renameFileFolder
}
defaultName={node.relativePath.split('/').pop()}
/>
<Menu
open={contextMenu !== null}

View File

@@ -14,7 +14,8 @@ import {
Select,
SelectChangeEvent,
Tab,
Tooltip
Tooltip,
Typography
} from '@mui/material'
import { styled } from '@mui/material/styles'
@@ -39,7 +40,7 @@ import FilePathInputModal from '../../components/filePathInputModal'
import BootstrapSnackbar, { AlertSeverityType } from '../../components/snackbar'
import Modal from '../../components/modal'
import usePrompt from '../../utils/usePrompt'
import { usePrompt, useStateWithCallback } from '../../utils/hooks'
const StyledTabPanel = styled(TabPanel)(() => ({
padding: '10px'
@@ -74,7 +75,7 @@ const SASjsEditor = ({
const [snackbarSeverity, setSnackbarSeverity] = useState<AlertSeverityType>(
AlertSeverityType.Success
)
const [prevFileContent, setPrevFileContent] = useState('')
const [prevFileContent, setPrevFileContent] = useStateWithCallback('')
const [fileContent, setFileContent] = useState('')
const [log, setLog] = useState('')
const [ctrlPressed, setCtrlPressed] = useState(false)
@@ -102,7 +103,7 @@ const SASjsEditor = ({
usePrompt(
'Changes you made may not be saved.',
prevFileContent !== fileContent
prevFileContent !== fileContent && !!selectedFilePath
)
useEffect(() => {
@@ -134,10 +135,21 @@ const SASjsEditor = ({
})
.finally(() => setIsLoading(false))
} else {
setFileContent('')
const content = localStorage.getItem('fileContent') ?? ''
setFileContent(content)
}
setLog('')
setWebout('')
setTab('1')
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectedFilePath])
useEffect(() => {
if (fileContent.length && !selectedFilePath) {
localStorage.setItem('fileContent', fileContent)
}
}, [fileContent, selectedFilePath])
useEffect(() => {
if (runTimes.includes(selectedFileExtension))
setSelectedRunTime(selectedFileExtension)
@@ -211,6 +223,10 @@ const SASjsEditor = ({
const saveFile = (filePath?: string) => {
setIsLoading(true)
if (filePath) {
filePath = filePath.startsWith('/') ? filePath : `/${filePath}`
}
const formData = new FormData()
const stringBlob = new Blob([fileContent], { type: 'text/plain' })
@@ -223,10 +239,22 @@ const SASjsEditor = ({
axiosPromise
.then(() => {
if (filePath) {
if (filePath && fileContent === prevFileContent) {
// when fileContent and prevFileContent is same,
// callback function in setPrevFileContent method is not called
// because behind the scene useEffect hook is being used
// for calling callback function, and it's only fired when the
// new value is not equal to old value.
// So, we'll have to explicitly update the selected file path
setSelectedFilePath(filePath, true)
} else {
setPrevFileContent(fileContent, () => {
if (filePath) {
setSelectedFilePath(filePath, true)
}
})
}
setPrevFileContent(fileContent)
setSnackbarMessage('File saved!')
setSnackbarSeverity(AlertSeverityType.Success)
setOpenSnackbar(true)
@@ -312,9 +340,14 @@ const SASjsEditor = ({
<TabList onChange={handleTabChange} centered>
<StyledTab label="Code" value="1" />
<StyledTab label="Log" value="2" />
<Tooltip title="Displays content from the _webout fileref">
<StyledTab label="Webout" value="3" />
</Tooltip>
<StyledTab
label={
<Tooltip title="Displays content from the _webout fileref">
<Typography>Webout</Typography>
</Tooltip>
}
value="3"
/>
</TabList>
</Box>
@@ -324,6 +357,8 @@ const SASjsEditor = ({
>
<Box sx={{ display: 'flex', justifyContent: 'center' }}>
<RunMenu
fileContent={fileContent}
prevFileContent={prevFileContent}
selectedFilePath={selectedFilePath}
selectedRunTime={selectedRunTime}
runTimes={runTimes}
@@ -423,6 +458,8 @@ export default SASjsEditor
type RunMenuProps = {
selectedFilePath: string
fileContent: string
prevFileContent: string
selectedRunTime: string
runTimes: string[]
handleChangeRunTime: (event: SelectChangeEvent) => void
@@ -431,6 +468,8 @@ type RunMenuProps = {
const RunMenu = ({
selectedFilePath,
fileContent,
prevFileContent,
selectedRunTime,
runTimes,
handleChangeRunTime,
@@ -463,10 +502,21 @@ const RunMenu = ({
</Tooltip>
{selectedFilePath ? (
<Box sx={{ marginLeft: '10px' }}>
<Tooltip title="Launch program in new window">
<IconButton onClick={launchProgram}>
<RocketLaunch />
</IconButton>
<Tooltip
title={
fileContent !== prevFileContent
? 'Save file before launching program'
: 'Launch program in new window'
}
>
<span>
<IconButton
disabled={fileContent !== prevFileContent}
onClick={launchProgram}
>
<RocketLaunch />
</IconButton>
</span>
</Tooltip>
</Box>
) : (

View File

@@ -0,0 +1,2 @@
export * from './usePrompt'
export * from './useStateWithCallback'

View File

@@ -2,7 +2,7 @@ import { useEffect, useCallback, useContext } from 'react'
import { UNSAFE_NavigationContext as NavigationContext } from 'react-router-dom'
import { History, Blocker, Transition } from 'history'
function useBlocker(blocker: Blocker, when = true) {
const useBlocker = (blocker: Blocker, when = true) => {
const navigator = useContext(NavigationContext).navigator as History
useEffect(() => {
@@ -24,7 +24,7 @@ function useBlocker(blocker: Blocker, when = true) {
}, [navigator, blocker, when])
}
export default function usePrompt(message: string, when = true) {
export const usePrompt = (message: string, when = true) => {
const blocker = useCallback(
(tx) => {
if (window.confirm(message)) tx.retry()

View File

@@ -0,0 +1,27 @@
import { useState, useEffect, useRef } from 'react'
export const useStateWithCallback = <T>(
initialValue: T
): [T, (newValue: T, callback?: () => void) => void] => {
const callbackRef = useRef<any>(null)
const [value, setValue] = useState(initialValue)
useEffect(() => {
if (typeof callbackRef.current === 'function') {
callbackRef.current()
callbackRef.current = null
}
}, [value])
const setValueWithCallback = (newValue: T, callback?: () => void) => {
callbackRef.current = callback
setValue(newValue)
}
return [value, setValueWithCallback]
}
export default useStateWithCallback