79
src/components/molecules/ActionSettings.tsx
Normal file
79
src/components/molecules/ActionSettings.tsx
Normal file
@@ -0,0 +1,79 @@
|
||||
import React, { useRef } from 'react';
|
||||
import styled from "styled-components";
|
||||
import { Button } from "@mui/material";
|
||||
import { ActionDescription } from "../organisms/RightSidePanel";
|
||||
import * as Settings from "./action-settings";
|
||||
import { useSocketStore } from "../../context/socket";
|
||||
|
||||
interface ActionSettingsProps {
|
||||
action: string;
|
||||
}
|
||||
|
||||
export const ActionSettings = ({ action }: 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} />;
|
||||
case 'script':
|
||||
return <Settings.ScriptSettings ref={settingsRef} />;
|
||||
case 'enqueueLinks':
|
||||
return <Settings.EnqueueLinksSettings ref={settingsRef} />;
|
||||
case 'mouse.click':
|
||||
return <Settings.ClickOnCoordinatesSettings ref={settingsRef} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const handleSubmit = (event: React.SyntheticEvent) => {
|
||||
event.preventDefault();
|
||||
//get the data from settings
|
||||
const settings = settingsRef.current?.getSettings();
|
||||
//Send notification to the server and generate the pair
|
||||
socket?.emit(`action`, {
|
||||
action,
|
||||
settings
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<ActionDescription>Action settings:</ActionDescription>
|
||||
<ActionSettingsWrapper action={action}>
|
||||
<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>
|
||||
);
|
||||
};
|
||||
|
||||
const ActionSettingsWrapper = styled.div<{ action: string }>`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: ${({ action }) => action === 'script' ? 'stretch' : 'center'};;
|
||||
justify-content: center;
|
||||
margin-top: 20px;
|
||||
`;
|
||||
@@ -0,0 +1,39 @@
|
||||
import React, { forwardRef, useImperativeHandle } from 'react';
|
||||
import { Stack, TextField } from "@mui/material";
|
||||
import { WarningText } from '../../atoms/texts';
|
||||
import InfoIcon from "@mui/icons-material/Info";
|
||||
|
||||
export const ClickOnCoordinatesSettings = forwardRef((props, ref) => {
|
||||
const [settings, setSettings] = React.useState<number[]>([0, 0]);
|
||||
useImperativeHandle(ref, () => ({
|
||||
getSettings() {
|
||||
return settings;
|
||||
}
|
||||
}));
|
||||
|
||||
return (
|
||||
<Stack direction="column">
|
||||
<TextField
|
||||
sx={{ marginLeft: '15px', marginRight: '15px' }}
|
||||
type="number"
|
||||
label="X"
|
||||
onChange={(e) => setSettings(prevState => ([Number(e.target.value), prevState[1]]))}
|
||||
required
|
||||
defaultValue={settings[0]}
|
||||
/>
|
||||
<TextField
|
||||
sx={{ margin: '15px' }}
|
||||
type="number"
|
||||
label="Y"
|
||||
onChange={(e) => setSettings(prevState => ([prevState[0], Number(e.target.value)]))}
|
||||
required
|
||||
defaultValue={settings[1]}
|
||||
/>
|
||||
<WarningText>
|
||||
<InfoIcon color='warning' />
|
||||
The click function will click on the given coordinates.
|
||||
You need to put the coordinates by yourself.
|
||||
</WarningText>
|
||||
</Stack>
|
||||
);
|
||||
});
|
||||
32
src/components/molecules/action-settings/enqueueLinks.tsx
Normal file
32
src/components/molecules/action-settings/enqueueLinks.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import React, { forwardRef, useImperativeHandle } from 'react';
|
||||
import { Stack, TextField } from "@mui/material";
|
||||
import { WarningText } from "../../atoms/texts";
|
||||
import WarningIcon from "@mui/icons-material/Warning";
|
||||
import InfoIcon from "@mui/icons-material/Info";
|
||||
|
||||
export const EnqueueLinksSettings = 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"
|
||||
required
|
||||
onChange={(e) => setSettings(e.target.value)}
|
||||
/>
|
||||
<WarningText>
|
||||
<InfoIcon color='warning' />
|
||||
Reads elements targeted by the selector and stores their links in a queue.
|
||||
Those pages are then processed using the same workflow as the initial page
|
||||
(in parallel if the maxConcurrency parameter is greater than 1).
|
||||
</WarningText>
|
||||
</Stack>
|
||||
);
|
||||
});
|
||||
17
src/components/molecules/action-settings/index.ts
Normal file
17
src/components/molecules/action-settings/index.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { ScrollSettings } from './scroll';
|
||||
import { ScreenshotSettings } from "./screenshot";
|
||||
import { ScrapeSettings } from "./scrape";
|
||||
import { ScrapeSchemaSettings } from "./scrapeSchema";
|
||||
import { ScriptSettings } from "./script";
|
||||
import { EnqueueLinksSettings } from "./enqueueLinks";
|
||||
import { ClickOnCoordinatesSettings } from "./clickOnCoordinates";
|
||||
|
||||
export {
|
||||
ScrollSettings,
|
||||
ScreenshotSettings,
|
||||
ScrapeSettings,
|
||||
ScrapeSchemaSettings,
|
||||
ScriptSettings,
|
||||
EnqueueLinksSettings,
|
||||
ClickOnCoordinatesSettings,
|
||||
};
|
||||
30
src/components/molecules/action-settings/scrape.tsx
Normal file
30
src/components/molecules/action-settings/scrape.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import React, { forwardRef, useImperativeHandle } from 'react';
|
||||
import { Stack, TextField } from "@mui/material";
|
||||
import { WarningText } from '../../atoms/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>
|
||||
);
|
||||
});
|
||||
25
src/components/molecules/action-settings/scrapeSchema.tsx
Normal file
25
src/components/molecules/action-settings/scrapeSchema.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import React, { forwardRef, useCallback, useImperativeHandle, useRef } from 'react';
|
||||
import { WarningText } from "../../atoms/texts";
|
||||
import InfoIcon from "@mui/icons-material/Info";
|
||||
import { KeyValueForm } from "../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>
|
||||
);
|
||||
});
|
||||
126
src/components/molecules/action-settings/screenshot.tsx
Normal file
126
src/components/molecules/action-settings/screenshot.tsx
Normal 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 "../../atoms/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;
|
||||
}
|
||||
`;
|
||||
63
src/components/molecules/action-settings/script.tsx
Normal file
63
src/components/molecules/action-settings/script.tsx
Normal file
@@ -0,0 +1,63 @@
|
||||
import React, { forwardRef, useImperativeHandle } from 'react';
|
||||
import Editor from 'react-simple-code-editor';
|
||||
// @ts-ignore
|
||||
import { highlight, languages } from 'prismjs/components/prism-core';
|
||||
import 'prismjs/components/prism-clike';
|
||||
import 'prismjs/components/prism-javascript';
|
||||
import 'prismjs/themes/prism.css';
|
||||
import styled from "styled-components";
|
||||
import InfoIcon from '@mui/icons-material/Info';
|
||||
import { WarningText } from "../../atoms/texts";
|
||||
|
||||
export const ScriptSettings = forwardRef((props, ref) => {
|
||||
const [code, setCode] = React.useState('');
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
getSettings() {
|
||||
return code;
|
||||
}
|
||||
}));
|
||||
|
||||
return (
|
||||
<EditorWrapper>
|
||||
<WarningText>
|
||||
<InfoIcon color='warning' />
|
||||
Allows to run an arbitrary asynchronous function evaluated at the server
|
||||
side accepting the current page instance argument.
|
||||
</WarningText>
|
||||
<StyledEditor
|
||||
placeholder="Type some code…"
|
||||
value={code}
|
||||
onValueChange={code => setCode(code)}
|
||||
highlight={code => highlight(code, languages.js)}
|
||||
padding={10}
|
||||
style={{
|
||||
fontFamily: '"Fira code", "Fira Mono", monospace',
|
||||
fontSize: 12,
|
||||
background: '#f0f0f0',
|
||||
}}
|
||||
/>
|
||||
</EditorWrapper>
|
||||
);
|
||||
});
|
||||
|
||||
const EditorWrapper = styled.div`
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
/** hard-coded height */
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const StyledEditor = styled(Editor)`
|
||||
white-space: pre;
|
||||
caret-color: #fff;
|
||||
min-width: 100%;
|
||||
min-height: 100%;
|
||||
float: left;
|
||||
& > textarea,
|
||||
& > pre {
|
||||
outline: none;
|
||||
white-space: pre !important;
|
||||
}
|
||||
`;
|
||||
21
src/components/molecules/action-settings/scroll.tsx
Normal file
21
src/components/molecules/action-settings/scroll.tsx
Normal 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))}
|
||||
/>
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user