feat: create action ui directory

This commit is contained in:
amhsirak
2025-01-09 20:15:27 +05:30
parent 8c5a5d7993
commit 78e9ac1fd8
8 changed files with 1 additions and 1 deletions

View File

@@ -0,0 +1,141 @@
import React from 'react';
import styled from 'styled-components';
import { Typography, FormControlLabel, Checkbox, Box } from '@mui/material';
import { useActionContext } from '../../context/browserActions';
import MaxunLogo from "../../assets/maxunlogo.png";
import { useTranslation } from 'react-i18next';
interface CustomBoxContainerProps {
isDarkMode: boolean;
}
const CustomBoxContainer = styled.div<CustomBoxContainerProps>`
position: relative;
min-width: 250px;
width: auto;
min-height: 100px;
height: auto;
border-radius: 5px;
background-color: ${({ isDarkMode }) => (isDarkMode ? '#313438' : 'white')};
color: ${({ isDarkMode }) => (isDarkMode ? 'white' : 'black')};
margin: 80px 13px 25px 13px;
box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.1);
`;
const Triangle = styled.div<CustomBoxContainerProps>`
position: absolute;
top: -15px;
left: 50%;
transform: translateX(-50%);
width: 0;
height: 0;
border-left: 20px solid transparent;
border-right: 20px solid transparent;
border-bottom: 20px solid ${({ isDarkMode }) => (isDarkMode ? '#313438' : 'white')};
`;
const Logo = styled.img`
position: absolute;
top: -80px;
left: 50%;
transform: translateX(-50%);
width: 70px;
height: auto;
border-radius: 5px;
`;
const Content = styled.div`
padding: 20px;
text-align: left;
`;
const ActionDescriptionBox = ({ isDarkMode }: { isDarkMode: boolean }) => {
const { t } = useTranslation();
const { getText, getScreenshot, getList, captureStage } = useActionContext() as {
getText: boolean;
getScreenshot: boolean;
getList: boolean;
captureStage: 'initial' | 'pagination' | 'limit' | 'complete';
};
const messages = [
{ stage: 'initial' as const, text: t('action_description.list_stages.initial') },
{ stage: 'pagination' as const, text: t('action_description.list_stages.pagination') },
{ stage: 'limit' as const, text: t('action_description.list_stages.limit') },
{ stage: 'complete' as const, text: t('action_description.list_stages.complete') },
];
const stages = messages.map(({ stage }) => stage);
const currentStageIndex = stages.indexOf(captureStage);
const renderActionDescription = () => {
if (getText) {
return (
<>
<Typography variant="subtitle2" gutterBottom>{t('action_description.text.title')}</Typography>
<Typography variant="body2" gutterBottom>{t('action_description.text.description')}</Typography>
</>
);
} else if (getScreenshot) {
return (
<>
<Typography variant="subtitle2" gutterBottom>{t('action_description.screenshot.title')}</Typography>
<Typography variant="body2" gutterBottom>{t('action_description.screenshot.description')}</Typography>
</>
);
} else if (getList) {
return (
<>
<Typography variant="subtitle2" gutterBottom>{t('action_description.list.title')}</Typography>
<Typography variant="body2" gutterBottom>
{t('action_description.list.description')}
</Typography>
<Box>
{messages.map(({ stage, text }, index) => (
<FormControlLabel
key={stage}
control={
<Checkbox
checked={index < currentStageIndex}
disabled
sx={{
color: isDarkMode ? 'white' : 'default',
'&.Mui-checked': {
color: isDarkMode ? '#90caf9' : '#1976d2',
},
}}
/>
}
label={
<Typography variant="body2" gutterBottom color={isDarkMode ? 'white' : 'textPrimary'}>
{text}
</Typography>
}
/>
))}
</Box>
</>
);
} else {
return (
<>
<Typography variant="subtitle2" gutterBottom>{t('action_description.default.title')}</Typography>
<Typography variant="body2" gutterBottom>{t('action_description.default.description')}</Typography>
</>
);
}
};
return (
<CustomBoxContainer isDarkMode={isDarkMode}>
<Logo src={MaxunLogo} alt="Maxun Logo" />
<Triangle isDarkMode={isDarkMode} />
<Content>
{renderActionDescription()}
</Content>
</CustomBoxContainer>
);
};
export default ActionDescriptionBox;

View File

@@ -0,0 +1,72 @@
import React, { useRef } from 'react';
import styled from "styled-components";
import { Button } from "@mui/material";
import * as Settings from "./action-settings";
import { useSocketStore } from "../../context/socket";
interface ActionSettingsProps {
action: string;
darkMode?: boolean;
}
export const ActionSettings = ({ action, darkMode = false }: ActionSettingsProps) => {
const settingsRef = useRef<{ getSettings: () => object }>(null);
const { socket } = useSocketStore();
const DisplaySettings = () => {
switch (action) {
case "screenshot":
return <Settings.ScreenshotSettings ref={settingsRef} />;
case 'scroll':
return <Settings.ScrollSettings ref={settingsRef} />;
case 'scrape':
return <Settings.ScrapeSettings ref={settingsRef} />;
case 'scrapeSchema':
return <Settings.ScrapeSchemaSettings ref={settingsRef} />;
default:
return null;
}
};
const handleSubmit = (event: React.SyntheticEvent) => {
event.preventDefault();
const settings = settingsRef.current?.getSettings();
socket?.emit(`action`, {
action,
settings
});
};
return (
<div>
<ActionSettingsWrapper action={action} darkMode={darkMode}>
<form onSubmit={handleSubmit}>
<DisplaySettings />
<Button
variant="outlined"
type="submit"
sx={{
display: "table-cell",
float: "right",
marginRight: "15px",
marginTop: "20px",
}}
>
Add Action
</Button>
</form>
</ActionSettingsWrapper>
</div>
);
};
// Ensure that the Wrapper accepts the darkMode prop for styling adjustments.
const ActionSettingsWrapper = styled.div<{ action: string; darkMode: boolean }>`
display: flex;
flex-direction: column;
align-items: ${({ action }) => (action === 'script' ? 'stretch' : 'center')};
justify-content: center;
margin-top: 20px;
background-color: ${({ darkMode }) => (darkMode ? '#1E1E1E' : 'white')};
color: ${({ darkMode }) => (darkMode ? 'white' : 'black')};
`;

View File

@@ -0,0 +1,11 @@
import { ScrollSettings } from './scroll';
import { ScreenshotSettings } from "./screenshot";
import { ScrapeSettings } from "./scrape";
import { ScrapeSchemaSettings } from "./scrapeSchema";
export {
ScrollSettings,
ScreenshotSettings,
ScrapeSettings,
ScrapeSchemaSettings,
};

View File

@@ -0,0 +1,30 @@
import React, { forwardRef, useImperativeHandle } from 'react';
import { Stack, TextField } from "@mui/material";
import { WarningText } from '../../ui/texts';
import InfoIcon from "@mui/icons-material/Info";
export const ScrapeSettings = forwardRef((props, ref) => {
const [settings, setSettings] = React.useState<string>('');
useImperativeHandle(ref, () => ({
getSettings() {
return settings;
}
}));
return (
<Stack direction="column">
<TextField
sx={{ marginLeft: '15px', marginRight: '15px' }}
type="string"
label="Selector"
onChange={(e) => setSettings(e.target.value)}
/>
<WarningText>
<InfoIcon color='warning' />
The scrape function uses heuristic algorithm to automatically scrape only important data from the page.
If a selector is used it will scrape and automatically parse all available
data inside of the selected element(s).
</WarningText>
</Stack>
);
});

View File

@@ -0,0 +1,25 @@
import React, { forwardRef, useCallback, useImperativeHandle, useRef } from 'react';
import { WarningText } from "../../ui/texts";
import InfoIcon from "@mui/icons-material/Info";
import { KeyValueForm } from "../../recorder/KeyValueForm";
export const ScrapeSchemaSettings = forwardRef((props, ref) => {
const keyValueFormRef = useRef<{ getObject: () => object }>(null);
useImperativeHandle(ref, () => ({
getSettings() {
const settings = keyValueFormRef.current?.getObject() as Record<string, string>
return settings;
}
}));
return (
<div>
<WarningText>
<InfoIcon color='warning' />
The interpreter scrapes the data from a webpage into a "curated" table.
</WarningText>
<KeyValueForm ref={keyValueFormRef} />
</div>
);
});

View File

@@ -0,0 +1,126 @@
import React, { forwardRef, useImperativeHandle } from 'react';
import { InputLabel, MenuItem, TextField, Select, FormControl } from "@mui/material";
import { ScreenshotSettings as Settings } from "../../../shared/types";
import styled from "styled-components";
import { SelectChangeEvent } from "@mui/material/Select/Select";
import { Dropdown } from "../../ui/DropdownMui";
export const ScreenshotSettings = forwardRef((props, ref) => {
const [settings, setSettings] = React.useState<Settings>({});
useImperativeHandle(ref, () => ({
getSettings() {
return settings;
}
}));
const handleInput = (event: React.ChangeEvent<HTMLInputElement>) => {
const { id, value, type } = event.target;
let parsedValue: any = value;
if (type === "number") {
parsedValue = parseInt(value);
};
setSettings({
...settings,
[id]: parsedValue,
});
};
const handleSelect = (event: SelectChangeEvent) => {
const { name, value } = event.target;
let parsedValue: any = value;
if (value === "true" || value === "false") {
parsedValue = value === "true";
};
setSettings({
...settings,
[name]: parsedValue,
});
};
return (
<SettingsWrapper>
<Dropdown
id="type"
label="type"
value={settings.type || "png"}
handleSelect={handleSelect}
>
<MenuItem value="jpeg">jpeg</MenuItem>
<MenuItem value="png">png</MenuItem>
</Dropdown>
{settings.type === "jpeg" ?
<TextField
type="number"
id="quality"
label="quality"
size='small'
InputProps={{ inputProps: { min: 0, max: 100 } }}
onChange={handleInput}
/> : null
}
<TextField
type="number"
id="timeout"
label="timeout"
size='small'
onChange={handleInput}
defaultValue='30000'
/>
<Dropdown
id="animations"
label="animations"
value={settings.animations || 'allow'}
handleSelect={handleSelect}
>
<MenuItem value="disabled">disabled</MenuItem>
<MenuItem value="allow">allow</MenuItem>
</Dropdown>
{settings.type === "png" ?
<Dropdown
id="omitBackground"
label="omitBackground"
value={settings.omitBackground?.toString() || "false"}
handleSelect={handleSelect}
>
<MenuItem value="true">true</MenuItem>
<MenuItem value="false">false</MenuItem>
</Dropdown>
: null
}
<Dropdown
id="caret"
label="caret"
value={settings.caret || "hide"}
handleSelect={handleSelect}
>
<MenuItem value="hide">hide</MenuItem>
<MenuItem value="initial">initial</MenuItem>
</Dropdown>
<Dropdown
id="fullPage"
label="fullPage"
value={settings.fullPage?.toString() || "false"}
handleSelect={handleSelect}
>
<MenuItem value="true">true</MenuItem>
<MenuItem value="false">false</MenuItem>
</Dropdown>
<Dropdown
id="scale"
label="scale"
value={settings.scale || "device"}
handleSelect={handleSelect}
>
<MenuItem value="css">css</MenuItem>
<MenuItem value="device">device</MenuItem>
</Dropdown>
</SettingsWrapper>
);
});
const SettingsWrapper = styled.div`
margin-left: 15px;
* {
margin-bottom: 10px;
}
`;

View File

@@ -0,0 +1,21 @@
import React, { forwardRef, useImperativeHandle } from 'react';
import { TextField } from "@mui/material";
export const ScrollSettings = forwardRef((props, ref) => {
const [settings, setSettings] = React.useState<number>(0);
useImperativeHandle(ref, () => ({
getSettings() {
return settings;
}
}));
return (
<TextField
sx={{ marginLeft: '15px' }}
type="number"
label="Number of pages"
required
onChange={(e) => setSettings(parseInt(e.target.value))}
/>
);
});