Merge branch 'tezos-client-command' into 'dev'
Tezos client command See merge request ligolang/ligo!497
This commit is contained in:
commit
6203427d3f
@ -25,6 +25,7 @@
|
||||
"redux": "^4.0.4",
|
||||
"redux-devtools": "^3.5.0",
|
||||
"redux-thunk": "^2.3.0",
|
||||
"slugify": "^1.4.0",
|
||||
"styled-components": "^4.4.0",
|
||||
"typescript": "3.6.4"
|
||||
},
|
||||
|
@ -7,15 +7,17 @@ import { DeployAction } from '../../redux/actions/deploy';
|
||||
import { DryRunAction } from '../../redux/actions/dry-run';
|
||||
import { EvaluateFunctionAction } from '../../redux/actions/evaluate-function';
|
||||
import { EvaluateValueAction } from '../../redux/actions/evaluate-value';
|
||||
import { GenerateCommandAction } from '../../redux/actions/generate-command';
|
||||
import { AppState } from '../../redux/app';
|
||||
import { ChangeDispatchedAction, ChangeSelectedAction, CommandState } from '../../redux/command';
|
||||
import { Command } from '../../redux/types';
|
||||
import { CommandSelectComponent } from './command-select';
|
||||
import { Option, Select } from '../form/select';
|
||||
import { CompilePaneComponent } from './compile-pane';
|
||||
import { DeployPaneComponent } from './deploy-pane';
|
||||
import { DryRunPaneComponent } from './dry-run-pane';
|
||||
import { EvaluateFunctionPaneComponent } from './evaluate-function-pane';
|
||||
import { EvaluateValuePaneComponent } from './evaluate-value-pane';
|
||||
import { GenerateCommandPaneComponent } from './generate-command-pane';
|
||||
|
||||
const Container = styled.div<{ visible?: boolean }>`
|
||||
position: absolute;
|
||||
@ -58,8 +60,8 @@ const RunButton = styled.div`
|
||||
background-color: var(--orange);
|
||||
`;
|
||||
|
||||
const CommandPaneContainer = styled.div`
|
||||
padding-top: 1em;
|
||||
const SelectCommand = styled(Select)`
|
||||
flex: 2;
|
||||
`;
|
||||
|
||||
function createAction(command: Command) {
|
||||
@ -74,6 +76,8 @@ function createAction(command: Command) {
|
||||
return new EvaluateValueAction();
|
||||
case Command.EvaluateFunction:
|
||||
return new EvaluateFunctionAction();
|
||||
case Command.GenerateCommand:
|
||||
return new GenerateCommandAction();
|
||||
default:
|
||||
throw new Error('Unsupported command');
|
||||
}
|
||||
@ -97,12 +101,20 @@ export const ConfigureTabComponent = (props: {
|
||||
return (
|
||||
<Container visible={props.selected}>
|
||||
<CommonActionsGroup>
|
||||
<CommandSelectComponent
|
||||
selected={command}
|
||||
<SelectCommand
|
||||
id="command-select"
|
||||
value={command}
|
||||
onChange={command => {
|
||||
dispatch({ ...new ChangeSelectedAction(command) });
|
||||
}}
|
||||
></CommandSelectComponent>
|
||||
>
|
||||
<Option value={Command.Compile}>Compile</Option>
|
||||
<Option value={Command.Deploy}>Deploy</Option>
|
||||
<Option value={Command.DryRun}>Dry Run</Option>
|
||||
<Option value={Command.EvaluateFunction}>Evaluate Function</Option>
|
||||
<Option value={Command.EvaluateValue}>Evaluate Value</Option>
|
||||
<Option value={Command.GenerateCommand}>Generate Command</Option>
|
||||
</SelectCommand>
|
||||
<RunButton
|
||||
id="run"
|
||||
onClick={() => {
|
||||
@ -120,7 +132,6 @@ export const ConfigureTabComponent = (props: {
|
||||
Run
|
||||
</RunButton>
|
||||
</CommonActionsGroup>
|
||||
<CommandPaneContainer>
|
||||
{(command === Command.Compile && (
|
||||
<CompilePaneComponent></CompilePaneComponent>
|
||||
)) ||
|
||||
@ -135,8 +146,10 @@ export const ConfigureTabComponent = (props: {
|
||||
)) ||
|
||||
(command === Command.EvaluateValue && (
|
||||
<EvaluateValuePaneComponent></EvaluateValuePaneComponent>
|
||||
)) ||
|
||||
(command === Command.GenerateCommand && (
|
||||
<GenerateCommandPaneComponent></GenerateCommandPaneComponent>
|
||||
))}
|
||||
</CommandPaneContainer>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
@ -0,0 +1,85 @@
|
||||
import React from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { AppState } from '../../redux/app';
|
||||
import {
|
||||
ChangeCommandAction,
|
||||
ChangeEntrypointAction,
|
||||
ChangeStorageAction,
|
||||
ChangeToolAction,
|
||||
GenerateCommandState,
|
||||
} from '../../redux/generate-command';
|
||||
import { Tool, ToolCommand } from '../../redux/types';
|
||||
import { AccessFunctionLabel, Group, Input, Label, Textarea } from '../form/inputs';
|
||||
import { Option, Select } from '../form/select';
|
||||
|
||||
const Container = styled.div`
|
||||
overflow: auto;
|
||||
`;
|
||||
|
||||
export const GenerateCommandPaneComponent = () => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const tool = useSelector<AppState, GenerateCommandState['tool']>(
|
||||
state => state.generateCommand.tool
|
||||
);
|
||||
|
||||
const command = useSelector<AppState, GenerateCommandState['command']>(
|
||||
state => state.generateCommand.command
|
||||
);
|
||||
|
||||
const entrypoint = useSelector<AppState, GenerateCommandState['entrypoint']>(
|
||||
state => state.generateCommand.entrypoint
|
||||
);
|
||||
|
||||
const storage = useSelector<AppState, GenerateCommandState['storage']>(
|
||||
state => state.generateCommand.storage
|
||||
);
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Group>
|
||||
<Label>Tool</Label>
|
||||
<Select
|
||||
id="tool"
|
||||
value={tool}
|
||||
onChange={value => dispatch({ ...new ChangeToolAction(value) })}
|
||||
>
|
||||
<Option value={Tool.TezosClient}>Tezos Client</Option>
|
||||
</Select>
|
||||
</Group>
|
||||
<Group>
|
||||
<Label>Command</Label>
|
||||
<Select
|
||||
id="tool-command"
|
||||
value={command}
|
||||
onChange={value => dispatch({ ...new ChangeCommandAction(value) })}
|
||||
>
|
||||
<Option value={ToolCommand.Originate}>Originate</Option>
|
||||
</Select>
|
||||
</Group>
|
||||
<Group>
|
||||
<AccessFunctionLabel htmlFor="entrypoint"></AccessFunctionLabel>
|
||||
<Input
|
||||
id="entrypoint"
|
||||
value={entrypoint}
|
||||
onChange={ev =>
|
||||
dispatch({ ...new ChangeEntrypointAction(ev.target.value) })
|
||||
}
|
||||
></Input>
|
||||
</Group>
|
||||
<Group>
|
||||
<Label htmlFor="storage">Storage</Label>
|
||||
<Textarea
|
||||
id="storage"
|
||||
rows={9}
|
||||
value={storage}
|
||||
onChange={ev =>
|
||||
dispatch({ ...new ChangeStorageAction(ev.target.value) })
|
||||
}
|
||||
></Textarea>
|
||||
</Group>
|
||||
</Container>
|
||||
);
|
||||
};
|
@ -3,11 +3,12 @@ import { useDispatch, useSelector } from 'react-redux';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { AppState } from '../../redux/app';
|
||||
import { ChangeTitleAction } from '../../redux/editor';
|
||||
import { ChangeLanguageAction, ChangeTitleAction, EditorState } from '../../redux/editor';
|
||||
import { Language } from '../../redux/types';
|
||||
import { Option, Select } from '../form/select';
|
||||
import { ShareComponent } from '../share';
|
||||
import { EditableTitleComponent } from './editable-title';
|
||||
import { MonacoComponent } from './monaco';
|
||||
import { SyntaxSelectComponent } from './syntax-select';
|
||||
|
||||
const Container = styled.div`
|
||||
flex: 2;
|
||||
@ -35,6 +36,9 @@ const StyledEditableTitleComponent = styled(EditableTitleComponent)`
|
||||
export const EditorComponent = () => {
|
||||
const dispatch = useDispatch();
|
||||
const title = useSelector<AppState, string>(state => state.editor.title);
|
||||
const language = useSelector<AppState, EditorState['language']>(
|
||||
state => state.editor.language
|
||||
);
|
||||
|
||||
return (
|
||||
<Container>
|
||||
@ -49,7 +53,17 @@ export const EditorComponent = () => {
|
||||
}}
|
||||
></StyledEditableTitleComponent>
|
||||
</LeftActions>
|
||||
<SyntaxSelectComponent></SyntaxSelectComponent>
|
||||
<Select
|
||||
id="syntax-select"
|
||||
value={language}
|
||||
onChange={language => {
|
||||
dispatch({ ...new ChangeLanguageAction(language) });
|
||||
}}
|
||||
>
|
||||
<Option value={Language.PascaLigo}>PascaLIGO</Option>
|
||||
<Option value={Language.CameLigo}>CameLIGO</Option>
|
||||
<Option value={Language.ReasonLIGO}>ReasonLIGO</Option>
|
||||
</Select>
|
||||
</Header>
|
||||
<MonacoComponent></MonacoComponent>
|
||||
</Container>
|
||||
|
@ -1,157 +0,0 @@
|
||||
import { faCaretDown } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import React, { useState } from 'react';
|
||||
import OutsideClickHandler from 'react-outside-click-handler';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import styled, { css } from 'styled-components';
|
||||
|
||||
import { AppState } from '../../redux/app';
|
||||
import { ChangeLanguageAction, EditorState } from '../../redux/editor';
|
||||
import { Language } from '../../redux/types';
|
||||
import { Tooltip } from '../tooltip';
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
min-width: 10em;
|
||||
`;
|
||||
|
||||
const Header = styled.div`
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
|
||||
flex: 1;
|
||||
display: flex;
|
||||
height: 2em;
|
||||
padding: 0 0.5em;
|
||||
|
||||
border: 1px solid var(--blue_trans1);
|
||||
`;
|
||||
|
||||
const Label = styled.div`
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
`;
|
||||
|
||||
const ArrowIcon = ({ rotate, ...props }: { rotate: boolean }) => (
|
||||
<FontAwesomeIcon {...props} icon={faCaretDown} size="lg"></FontAwesomeIcon>
|
||||
);
|
||||
|
||||
const Arrow = styled(ArrowIcon)`
|
||||
margin-left: 0.5em;
|
||||
pointer-events: none;
|
||||
color: var(--blue_trans1);
|
||||
transition: transform 0.15s ease-in;
|
||||
|
||||
${(props: { rotate: boolean }) =>
|
||||
props.rotate &&
|
||||
css`
|
||||
transform: rotate(180deg);
|
||||
`};
|
||||
`;
|
||||
|
||||
const List = styled.ul`
|
||||
position: absolute;
|
||||
list-style-type: none;
|
||||
background-color: white;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-shadow: 1px 3px 10px 0px rgba(153, 153, 153, 0.4);
|
||||
border-radius: 3px;
|
||||
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
transition: opacity 0.15s ease-in;
|
||||
|
||||
${(props: { visible: boolean }) =>
|
||||
props.visible &&
|
||||
css`
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
`}
|
||||
`;
|
||||
|
||||
const Option = styled.li`
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 2em;
|
||||
padding: 0 0.5em;
|
||||
|
||||
&:first-child {
|
||||
border-radius: 3px 3px 0 0;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-radius: 0 0 3px 3px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--blue_trans2);
|
||||
font-weight: 600;
|
||||
}
|
||||
`;
|
||||
|
||||
export const SyntaxSelectComponent = () => {
|
||||
const OPTIONS = {
|
||||
[Language.PascaLigo]: 'PascaLIGO',
|
||||
[Language.CameLigo]: 'CameLIGO',
|
||||
[Language.ReasonLIGO]: 'ReasonLIGO'
|
||||
};
|
||||
|
||||
const moveOptionToTop = (option: Language) => {
|
||||
return Object.keys(OPTIONS).reduce((list, entry) => {
|
||||
if (entry === option) {
|
||||
list.unshift(entry);
|
||||
} else {
|
||||
list.push(entry as Language);
|
||||
}
|
||||
return list;
|
||||
}, [] as Language[]);
|
||||
};
|
||||
|
||||
const language = useSelector<AppState, EditorState['language']>(
|
||||
state => state.editor.language
|
||||
);
|
||||
const dispatch = useDispatch();
|
||||
const [opened, open] = useState(false);
|
||||
|
||||
const selectOption = (option: Language) => {
|
||||
if (language !== option) {
|
||||
dispatch({ ...new ChangeLanguageAction(option) });
|
||||
}
|
||||
open(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<OutsideClickHandler onOutsideClick={() => open(false)}>
|
||||
<List visible={opened}>
|
||||
{moveOptionToTop(language).map(option => (
|
||||
<Option
|
||||
id={option}
|
||||
key={option}
|
||||
onClick={() => selectOption(option)}
|
||||
>
|
||||
<span>{OPTIONS[option]}</span>
|
||||
</Option>
|
||||
))}
|
||||
</List>
|
||||
</OutsideClickHandler>
|
||||
<Header id="syntax-select" onClick={() => open(true)}>
|
||||
<Label>
|
||||
{OPTIONS[language]}
|
||||
<Arrow rotate={opened}></Arrow>
|
||||
</Label>
|
||||
<Tooltip>Select syntax</Tooltip>
|
||||
</Header>
|
||||
</Container>
|
||||
);
|
||||
};
|
@ -3,7 +3,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import React, { useState } from 'react';
|
||||
import styled, { css } from 'styled-components';
|
||||
|
||||
const Container = styled.div<{ checked: boolean }>`
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
@ -44,7 +44,6 @@ export const CheckboxComponent = (props: {
|
||||
return (
|
||||
<Container
|
||||
className={props.className}
|
||||
checked={isChecked}
|
||||
onClick={() => {
|
||||
const newState = !isChecked;
|
||||
|
||||
|
@ -4,17 +4,20 @@ import styled from 'styled-components';
|
||||
export const Group = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 0.7em 0 0.7em 0;
|
||||
`;
|
||||
|
||||
export const HGroup = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 0.4em 0 0.4em 0;
|
||||
`;
|
||||
|
||||
export const Label = styled.label`
|
||||
font-size: 1em;
|
||||
color: var(--label_foreground);
|
||||
user-select: none;
|
||||
margin: 0.3em 0 0.3em 0;
|
||||
`;
|
||||
|
||||
export const Hint = styled.span`
|
||||
@ -25,7 +28,7 @@ export const Hint = styled.span`
|
||||
export const AccessFunctionLabel = (props: any) => {
|
||||
return (
|
||||
<Label {...props}>
|
||||
Access Function
|
||||
Access function
|
||||
<br />
|
||||
<Hint>The function name from where your contract will start</Hint>
|
||||
</Label>
|
||||
@ -33,7 +36,7 @@ export const AccessFunctionLabel = (props: any) => {
|
||||
};
|
||||
|
||||
export const Input = styled.input`
|
||||
margin: 0.3em 0 0.7em 0;
|
||||
/* margin: 0.3em 0 0.7em 0; */
|
||||
background-color: var(--input_background);
|
||||
border-style: none;
|
||||
border-bottom: 5px solid #e1f1ff;
|
||||
@ -49,7 +52,7 @@ export const Input = styled.input`
|
||||
|
||||
export const Textarea = styled.textarea`
|
||||
resize: vertical;
|
||||
margin: 0.3em 0 0.7em 0;
|
||||
/* margin: 0.3em 0 0.7em 0; */
|
||||
background-color: var(--input_background);
|
||||
border-style: none;
|
||||
border-bottom: 5px solid #e1f1ff;
|
||||
|
@ -1,17 +1,13 @@
|
||||
import { faCaretDown } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import React, { useState } from 'react';
|
||||
import React, { FunctionComponentElement, useState } from 'react';
|
||||
import OutsideClickHandler from 'react-outside-click-handler';
|
||||
import styled, { css } from 'styled-components';
|
||||
|
||||
import { Command } from '../../redux/types';
|
||||
|
||||
const Container = styled.div`
|
||||
flex: 2;
|
||||
display: flex;
|
||||
position: relative;
|
||||
min-width: 8em;
|
||||
z-index: 2;
|
||||
`;
|
||||
|
||||
const Header = styled.div`
|
||||
@ -20,8 +16,8 @@ const Header = styled.div`
|
||||
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
min-height: 2em;
|
||||
padding: 0 0.5em;
|
||||
|
||||
@ -33,6 +29,7 @@ const ArrowIcon = ({ rotate, ...props }: { rotate: boolean }) => (
|
||||
);
|
||||
|
||||
const Arrow = styled(ArrowIcon)`
|
||||
z-index: 1;
|
||||
pointer-events: none;
|
||||
color: var(--blue_trans1);
|
||||
transition: transform 0.15s ease-in;
|
||||
@ -45,6 +42,7 @@ const Arrow = styled(ArrowIcon)`
|
||||
`;
|
||||
|
||||
const List = styled.ul`
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
list-style-type: none;
|
||||
background-color: white;
|
||||
@ -66,7 +64,7 @@ const List = styled.ul`
|
||||
`}
|
||||
`;
|
||||
|
||||
const Option = styled.li`
|
||||
const OptionContainer = styled.li`
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
|
||||
@ -90,56 +88,73 @@ const Option = styled.li`
|
||||
}
|
||||
`;
|
||||
|
||||
export const CommandSelectComponent = (props: {
|
||||
selected: Command;
|
||||
onChange?: (value: Command) => void;
|
||||
}) => {
|
||||
const OPTIONS = {
|
||||
[Command.Compile]: 'Compile',
|
||||
[Command.Deploy]: 'Deploy',
|
||||
[Command.DryRun]: 'Dry Run',
|
||||
[Command.EvaluateFunction]: 'Evaluate Function',
|
||||
[Command.EvaluateValue]: 'Evaluate Value'
|
||||
interface OptionProps {
|
||||
value: string;
|
||||
children: string;
|
||||
}
|
||||
|
||||
type OptionElement = FunctionComponentElement<OptionProps>;
|
||||
|
||||
export const Option = (props: OptionProps) => {
|
||||
// This is an empty component. It's used as a way to get option information into its parent. It is not inserted into the DOM.
|
||||
return <></>;
|
||||
};
|
||||
|
||||
const moveOptionToTop = (option: Command) => {
|
||||
return Object.keys(OPTIONS).reduce((list, entry) => {
|
||||
if (entry === option) {
|
||||
export const Select = (props: {
|
||||
id: string;
|
||||
value: any;
|
||||
children: OptionElement[] | OptionElement;
|
||||
onChange?: (value: any) => void;
|
||||
className?: string;
|
||||
}) => {
|
||||
const [isOpen, setOpen] = useState(false);
|
||||
|
||||
const options = Array.isArray(props.children)
|
||||
? props.children
|
||||
: [props.children];
|
||||
|
||||
const labelLookup = new Map(
|
||||
options.map(
|
||||
child => [child.props.value, child.props.children] as [string, string]
|
||||
)
|
||||
);
|
||||
|
||||
const moveOptionToTop = (value: string) => {
|
||||
return options.reduce((list, entry) => {
|
||||
if (entry.props.value === value) {
|
||||
list.unshift(entry);
|
||||
} else {
|
||||
list.push(entry as Command);
|
||||
list.push(entry);
|
||||
}
|
||||
return list;
|
||||
}, [] as Command[]);
|
||||
}, [] as OptionElement[]);
|
||||
};
|
||||
|
||||
const [opened, open] = useState(false);
|
||||
|
||||
const selectOption = (option: Command) => {
|
||||
if (props.selected !== option && props.onChange) {
|
||||
props.onChange(option);
|
||||
const selectOption = (option: OptionElement) => {
|
||||
if (props.value !== option.props.value && props.onChange) {
|
||||
props.onChange(option.props.value);
|
||||
}
|
||||
open(false);
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<OutsideClickHandler onOutsideClick={() => open(false)}>
|
||||
<List visible={opened}>
|
||||
{moveOptionToTop(props.selected).map(option => (
|
||||
<Option
|
||||
id={option}
|
||||
key={option}
|
||||
<Container className={props.className}>
|
||||
<OutsideClickHandler onOutsideClick={() => setOpen(false)}>
|
||||
<List visible={isOpen}>
|
||||
{moveOptionToTop(props.value).map((option: OptionElement) => (
|
||||
<OptionContainer
|
||||
id={option.props.value}
|
||||
key={option.props.value}
|
||||
onClick={() => selectOption(option)}
|
||||
>
|
||||
<span>{OPTIONS[option]}</span>
|
||||
</Option>
|
||||
<span>{option.props.children}</span>
|
||||
</OptionContainer>
|
||||
))}
|
||||
</List>
|
||||
</OutsideClickHandler>
|
||||
<Header id="command-select" onClick={() => open(true)}>
|
||||
<span>{OPTIONS[props.selected]}</span>
|
||||
<Arrow rotate={opened}></Arrow>
|
||||
<Header id={props.id} onClick={() => setOpen(true)}>
|
||||
<span>{labelLookup.get(props.value)}</span>
|
||||
<Arrow rotate={isOpen}></Arrow>
|
||||
</Header>
|
||||
</Container>
|
||||
);
|
@ -1,83 +0,0 @@
|
||||
import { faCheck } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import React, { useState } from 'react';
|
||||
import styled, { css } from 'styled-components';
|
||||
|
||||
const Container = styled.div<{ checked: boolean }>`
|
||||
position: relative;
|
||||
height: 2em;
|
||||
width: 3.5em;
|
||||
border-radius: 1em;
|
||||
background-color: var(--blue_trans1);
|
||||
border: 1px solid var(--blue);
|
||||
transition: background-color 0.2s ease-in;
|
||||
|
||||
${props =>
|
||||
props.checked &&
|
||||
css`
|
||||
background-color: var(--blue);
|
||||
`};
|
||||
`;
|
||||
|
||||
const Button = styled.div<{ checked: boolean }>`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
|
||||
height: 2em;
|
||||
width: 2em;
|
||||
background-color: white;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
right: calc(1.5em);
|
||||
transition: right 0.2s ease-in;
|
||||
|
||||
${props =>
|
||||
props.checked &&
|
||||
css`
|
||||
right: 0;
|
||||
`};
|
||||
`;
|
||||
|
||||
const CheckIcon = ({ visible, ...props }: { visible: boolean }) => (
|
||||
<FontAwesomeIcon {...props} icon={faCheck}></FontAwesomeIcon>
|
||||
);
|
||||
|
||||
const Check = styled(CheckIcon)`
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
opacity: 1;
|
||||
transition: opacity 0.2s ease-in;
|
||||
color: var(--blue);
|
||||
|
||||
${props =>
|
||||
!props.visible &&
|
||||
css`
|
||||
opacity: 0;
|
||||
`}
|
||||
`;
|
||||
|
||||
export const ToggleComponent = (props: {
|
||||
checked: boolean;
|
||||
onChanged: (value: boolean) => void;
|
||||
className?: string;
|
||||
}) => {
|
||||
const [isChecked, setChecked] = useState(props.checked);
|
||||
|
||||
return (
|
||||
<Container className={props.className} checked={isChecked}>
|
||||
<Button
|
||||
checked={isChecked}
|
||||
onClick={() => {
|
||||
const newState = !isChecked;
|
||||
|
||||
setChecked(newState);
|
||||
props.onChanged(newState);
|
||||
}}
|
||||
>
|
||||
<Check visible={isChecked}></Check>
|
||||
</Button>
|
||||
</Container>
|
||||
);
|
||||
};
|
@ -5,6 +5,7 @@ import styled from 'styled-components';
|
||||
import { AppState } from '../../redux/app';
|
||||
import { ResultState } from '../../redux/result';
|
||||
import { OutputToolbarComponent } from './output-toolbar';
|
||||
import { copyOutput, downloadOutput } from './utils';
|
||||
|
||||
const Container = styled.div<{ visible?: boolean }>`
|
||||
display: flex;
|
||||
@ -23,35 +24,6 @@ const Pre = styled.pre`
|
||||
margin: 0;
|
||||
`;
|
||||
|
||||
function copyOutput(el: HTMLElement | null) {
|
||||
if (el) {
|
||||
const range = document.createRange();
|
||||
range.selectNodeContents(el);
|
||||
|
||||
const selection = window.getSelection();
|
||||
|
||||
if (selection) {
|
||||
selection.removeAllRanges();
|
||||
selection.addRange(range);
|
||||
document.execCommand('copy');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function downloadOutput(output: string) {
|
||||
const anchor = document.createElement('a');
|
||||
anchor.setAttribute(
|
||||
'href',
|
||||
`data:text/plain;charset=utf-8,${encodeURIComponent(output)}`
|
||||
);
|
||||
anchor.setAttribute('download', 'output.txt');
|
||||
|
||||
anchor.style.display = 'none';
|
||||
document.body.appendChild(anchor);
|
||||
anchor.click();
|
||||
document.body.removeChild(anchor);
|
||||
}
|
||||
|
||||
export const CompileOutputPane = () => {
|
||||
const output = useSelector<AppState, ResultState['output']>(
|
||||
state => state.result.output
|
||||
@ -62,6 +34,7 @@ export const CompileOutputPane = () => {
|
||||
return (
|
||||
<Container>
|
||||
<OutputToolbarComponent
|
||||
showTryMichelson={true}
|
||||
onCopy={() => copyOutput(preRef.current)}
|
||||
onDownload={() => downloadOutput(output)}
|
||||
></OutputToolbarComponent>
|
||||
|
@ -0,0 +1,45 @@
|
||||
import React, { useRef } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { AppState } from '../../redux/app';
|
||||
import { ResultState } from '../../redux/result';
|
||||
import { OutputToolbarComponent } from './output-toolbar';
|
||||
import { copyOutput, downloadOutput } from './utils';
|
||||
|
||||
const Container = styled.div<{ visible?: boolean }>`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
`;
|
||||
|
||||
const Output = styled.div`
|
||||
flex: 1;
|
||||
padding: 0.5em;
|
||||
display: flex;
|
||||
overflow: auto;
|
||||
`;
|
||||
|
||||
const Pre = styled.pre`
|
||||
margin: 0;
|
||||
`;
|
||||
|
||||
export const GenerateCommandOutputPane = () => {
|
||||
const output = useSelector<AppState, ResultState['output']>(
|
||||
state => state.result.output
|
||||
);
|
||||
|
||||
const preRef = useRef<HTMLPreElement>(null);
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<OutputToolbarComponent
|
||||
onCopy={() => copyOutput(preRef.current)}
|
||||
onDownload={() => downloadOutput(output)}
|
||||
></OutputToolbarComponent>
|
||||
<Output id="output">
|
||||
<Pre ref={preRef}>{output}</Pre>
|
||||
</Output>
|
||||
</Container>
|
||||
);
|
||||
};
|
@ -8,6 +8,7 @@ import { ResultState } from '../../redux/result';
|
||||
import { Command } from '../../redux/types';
|
||||
import { CompileOutputPane } from './compile-output-pane';
|
||||
import { DeployOutputPane } from './deploy-output-pane';
|
||||
import { GenerateCommandOutputPane } from './generate-command-output-pane';
|
||||
import { Loading } from './loading';
|
||||
import { OutputPane } from './output-pane';
|
||||
|
||||
@ -41,14 +42,21 @@ export const OutputTab = (props: {
|
||||
const loading = useSelector<AppState, LoadingState['loading']>(
|
||||
state => state.loading.loading
|
||||
);
|
||||
const output = useSelector<AppState, ResultState['output']>(
|
||||
state => state.result.output
|
||||
);
|
||||
|
||||
const renderResult = () => {
|
||||
if (loading) {
|
||||
return <Loading onCancel={props.onCancel}></Loading>;
|
||||
} else if (!output) {
|
||||
return <></>;
|
||||
} else if (command === Command.Compile) {
|
||||
return <CompileOutputPane></CompileOutputPane>;
|
||||
} else if (command === Command.Deploy) {
|
||||
return <DeployOutputPane></DeployOutputPane>;
|
||||
} else if (command === Command.GenerateCommand) {
|
||||
return <GenerateCommandOutputPane></GenerateCommandOutputPane>;
|
||||
}
|
||||
|
||||
return <OutputPane></OutputPane>;
|
||||
|
@ -24,6 +24,7 @@ const Link = styled.a`
|
||||
`;
|
||||
|
||||
export const OutputToolbarComponent = (props: {
|
||||
showTryMichelson?: boolean;
|
||||
onCopy?: () => void;
|
||||
onDownload?: () => void;
|
||||
}) => {
|
||||
@ -41,7 +42,8 @@ export const OutputToolbarComponent = (props: {
|
||||
<FontAwesomeIcon icon={faDownload}></FontAwesomeIcon>
|
||||
<Tooltip>Download</Tooltip>
|
||||
</Item>
|
||||
<Divider></Divider>
|
||||
{props.showTryMichelson && <Divider></Divider>}
|
||||
{props.showTryMichelson && (
|
||||
<Item>
|
||||
<Link
|
||||
target="_blank"
|
||||
@ -53,6 +55,7 @@ export const OutputToolbarComponent = (props: {
|
||||
View in Try-Michelson IDE
|
||||
</Link>
|
||||
</Item>
|
||||
)}
|
||||
</Toolbar>
|
||||
);
|
||||
};
|
||||
|
28
tools/webide/packages/client/src/components/output/utils.ts
Normal file
28
tools/webide/packages/client/src/components/output/utils.ts
Normal file
@ -0,0 +1,28 @@
|
||||
export function copyOutput(el: HTMLElement | null) {
|
||||
if (el) {
|
||||
const range = document.createRange();
|
||||
range.selectNodeContents(el);
|
||||
|
||||
const selection = window.getSelection();
|
||||
|
||||
if (selection) {
|
||||
selection.removeAllRanges();
|
||||
selection.addRange(range);
|
||||
document.execCommand('copy');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function downloadOutput(output: string) {
|
||||
const anchor = document.createElement('a');
|
||||
anchor.setAttribute(
|
||||
'href',
|
||||
`data:text/plain;charset=utf-8,${encodeURIComponent(output)}`
|
||||
);
|
||||
anchor.setAttribute('download', 'output.txt');
|
||||
|
||||
anchor.style.display = 'none';
|
||||
document.body.appendChild(anchor);
|
||||
anchor.click();
|
||||
document.body.removeChild(anchor);
|
||||
}
|
@ -0,0 +1,112 @@
|
||||
import { Tezos } from '@taquito/taquito';
|
||||
import { Dispatch } from 'redux';
|
||||
import slugify from 'slugify';
|
||||
|
||||
import { compileContract, compileStorage, getErrorMessage } from '../../services/api';
|
||||
import { AppState } from '../app';
|
||||
import { MichelsonFormat } from '../compile';
|
||||
import { DoneLoadingAction, UpdateLoadingAction } from '../loading';
|
||||
import { ChangeOutputAction } from '../result';
|
||||
import { Command } from '../types';
|
||||
import { CancellableAction } from './cancellable';
|
||||
|
||||
const URL = 'https://api.tez.ie/keys/carthagenet/';
|
||||
const AUTHORIZATION_HEADER = 'Bearer ligo-ide';
|
||||
|
||||
export async function fetchRandomPrivateKey(): Promise<string> {
|
||||
const response = await fetch(URL, {
|
||||
method: 'POST',
|
||||
headers: { Authorization: AUTHORIZATION_HEADER }
|
||||
});
|
||||
|
||||
return response.text();
|
||||
}
|
||||
|
||||
export class GenerateCommandAction extends CancellableAction {
|
||||
getAction() {
|
||||
return async (dispatch: Dispatch, getState: () => AppState) => {
|
||||
dispatch({ ...new UpdateLoadingAction('Compiling contract...') });
|
||||
|
||||
try {
|
||||
const { editor, generateCommand } = getState();
|
||||
|
||||
const michelsonCodeJson = await compileContract(
|
||||
editor.language,
|
||||
editor.code,
|
||||
generateCommand.entrypoint,
|
||||
MichelsonFormat.Json
|
||||
);
|
||||
|
||||
const michelsonCode = await compileContract(
|
||||
editor.language,
|
||||
editor.code,
|
||||
generateCommand.entrypoint
|
||||
);
|
||||
|
||||
if (this.isCancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch({ ...new UpdateLoadingAction('Compiling storage...') });
|
||||
const michelsonStorageJson = await compileStorage(
|
||||
editor.language,
|
||||
editor.code,
|
||||
generateCommand.entrypoint,
|
||||
generateCommand.storage,
|
||||
MichelsonFormat.Json
|
||||
);
|
||||
|
||||
const michelsonStorage = await compileStorage(
|
||||
editor.language,
|
||||
editor.code,
|
||||
generateCommand.entrypoint,
|
||||
generateCommand.storage
|
||||
);
|
||||
|
||||
if (this.isCancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch({ ...new UpdateLoadingAction('Estimating burn cap...') });
|
||||
|
||||
await Tezos.importKey(await fetchRandomPrivateKey());
|
||||
|
||||
const estimate = await Tezos.estimate.originate({
|
||||
code: JSON.parse(michelsonCodeJson.result),
|
||||
init: JSON.parse(michelsonStorageJson.result)
|
||||
});
|
||||
|
||||
if (this.isCancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const title = slugify(editor.title).toLowerCase() || 'untitled';
|
||||
const output = `tezos-client \\
|
||||
${generateCommand.command} \\
|
||||
contract \\
|
||||
${title} \\
|
||||
transferring 0 \\
|
||||
from $YOUR_SOURCE_ACCOUNT \\
|
||||
running '${michelsonCode.result.trim()}' \\
|
||||
--init '${michelsonStorage.result.trim()}' \\
|
||||
--burn-cap ${estimate.burnFeeMutez / 1000000}`;
|
||||
|
||||
dispatch({
|
||||
...new ChangeOutputAction(output, Command.GenerateCommand)
|
||||
});
|
||||
} catch (ex) {
|
||||
if (this.isCancelled()) {
|
||||
return;
|
||||
}
|
||||
dispatch({
|
||||
...new ChangeOutputAction(
|
||||
`Error: ${getErrorMessage(ex)}`,
|
||||
Command.GenerateCommand
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
dispatch({ ...new DoneLoadingAction() });
|
||||
};
|
||||
}
|
||||
}
|
@ -8,6 +8,7 @@ import editor, { EditorState } from './editor';
|
||||
import evaluateFunction, { EvaluateFunctionState } from './evaluate-function';
|
||||
import evaluateValue, { EvaluateValueState } from './evaluate-value';
|
||||
import examples, { ExamplesState } from './examples';
|
||||
import generateCommand, { GenerateCommandState } from './generate-command';
|
||||
import loading, { LoadingState } from './loading';
|
||||
import result, { ResultState } from './result';
|
||||
import share, { ShareState } from './share';
|
||||
@ -20,6 +21,7 @@ export interface AppState {
|
||||
deploy: DeployState;
|
||||
evaluateFunction: EvaluateFunctionState;
|
||||
evaluateValue: EvaluateValueState;
|
||||
generateCommand: GenerateCommandState;
|
||||
result: ResultState;
|
||||
command: CommandState;
|
||||
examples: ExamplesState;
|
||||
@ -34,6 +36,7 @@ export default combineReducers({
|
||||
deploy,
|
||||
evaluateFunction,
|
||||
evaluateValue,
|
||||
generateCommand,
|
||||
result,
|
||||
command,
|
||||
examples,
|
||||
|
81
tools/webide/packages/client/src/redux/generate-command.ts
Normal file
81
tools/webide/packages/client/src/redux/generate-command.ts
Normal file
@ -0,0 +1,81 @@
|
||||
import { Tool, ToolCommand } from './types';
|
||||
|
||||
export enum ActionType {
|
||||
ChangeTool = 'generate-command-change-tool',
|
||||
ChangeCommand = 'generate-command-change-command',
|
||||
ChangeEntrypoint = 'generate-command-change-entrypoint',
|
||||
ChangeStorage = 'generate-command-change-storage'
|
||||
}
|
||||
|
||||
export interface GenerateCommandState {
|
||||
tool: Tool;
|
||||
command: ToolCommand;
|
||||
entrypoint: string;
|
||||
originationAccount: string;
|
||||
storage: string;
|
||||
burnCap: number;
|
||||
}
|
||||
|
||||
export class ChangeToolAction {
|
||||
public readonly type = ActionType.ChangeTool;
|
||||
constructor(public payload: GenerateCommandState['tool']) {}
|
||||
}
|
||||
|
||||
export class ChangeCommandAction {
|
||||
public readonly type = ActionType.ChangeCommand;
|
||||
constructor(public payload: GenerateCommandState['command']) {}
|
||||
}
|
||||
|
||||
export class ChangeEntrypointAction {
|
||||
public readonly type = ActionType.ChangeEntrypoint;
|
||||
constructor(public payload: GenerateCommandState['entrypoint']) {}
|
||||
}
|
||||
|
||||
export class ChangeStorageAction {
|
||||
public readonly type = ActionType.ChangeStorage;
|
||||
constructor(public payload: GenerateCommandState['storage']) {}
|
||||
}
|
||||
|
||||
type Action =
|
||||
| ChangeToolAction
|
||||
| ChangeCommandAction
|
||||
| ChangeEntrypointAction
|
||||
| ChangeStorageAction;
|
||||
|
||||
const DEFAULT_STATE: GenerateCommandState = {
|
||||
tool: Tool.TezosClient,
|
||||
command: ToolCommand.Originate,
|
||||
entrypoint: '',
|
||||
storage: '',
|
||||
originationAccount: '',
|
||||
burnCap: 0
|
||||
};
|
||||
|
||||
export default (
|
||||
state = DEFAULT_STATE,
|
||||
action: Action
|
||||
): GenerateCommandState => {
|
||||
switch (action.type) {
|
||||
case ActionType.ChangeTool:
|
||||
return {
|
||||
...state,
|
||||
tool: action.payload
|
||||
};
|
||||
case ActionType.ChangeCommand:
|
||||
return {
|
||||
...state,
|
||||
command: action.payload
|
||||
};
|
||||
case ActionType.ChangeEntrypoint:
|
||||
return {
|
||||
...state,
|
||||
entrypoint: action.payload
|
||||
};
|
||||
case ActionType.ChangeStorage:
|
||||
return {
|
||||
...state,
|
||||
storage: action.payload
|
||||
};
|
||||
}
|
||||
return state;
|
||||
};
|
@ -9,5 +9,14 @@ export enum Command {
|
||||
DryRun = 'dry-run',
|
||||
EvaluateValue = 'evaluate-value',
|
||||
EvaluateFunction = 'evaluate-function',
|
||||
Deploy = 'deploy'
|
||||
Deploy = 'deploy',
|
||||
GenerateCommand = 'generate-command'
|
||||
}
|
||||
|
||||
export enum Tool {
|
||||
TezosClient = 'tezos-client'
|
||||
}
|
||||
|
||||
export enum ToolCommand {
|
||||
Originate = 'originate'
|
||||
}
|
||||
|
@ -43,6 +43,9 @@ export async function compileStorage(
|
||||
storage: string,
|
||||
format?: string
|
||||
) {
|
||||
// For whatever reason, storage set by examples is not treated as a string. So we convert it here.
|
||||
storage = `${storage}`;
|
||||
|
||||
const response = await axios.post('/api/compile-storage', {
|
||||
syntax,
|
||||
code,
|
||||
|
52
tools/webide/packages/server/src/handlers/compile-storage.ts
Normal file
52
tools/webide/packages/server/src/handlers/compile-storage.ts
Normal file
@ -0,0 +1,52 @@
|
||||
import joi from '@hapi/joi';
|
||||
import { Request, Response } from 'express';
|
||||
|
||||
import { CompilerError, LigoCompiler } from '../ligo-compiler';
|
||||
import { logger } from '../logger';
|
||||
|
||||
interface CompileBody {
|
||||
syntax: string;
|
||||
code: string;
|
||||
entrypoint: string;
|
||||
storage: string;
|
||||
format?: string;
|
||||
}
|
||||
|
||||
const validateRequest = (body: any): { value: CompileBody; error: any } => {
|
||||
return joi
|
||||
.object({
|
||||
syntax: joi.string().required(),
|
||||
code: joi.string().required(),
|
||||
entrypoint: joi.string().required(),
|
||||
storage: joi.string().required(),
|
||||
format: joi.string().optional()
|
||||
})
|
||||
.validate(body);
|
||||
};
|
||||
|
||||
export async function compileStorageHandler(req: Request, res: Response) {
|
||||
const { error, value: body } = validateRequest(req.body);
|
||||
|
||||
if (error) {
|
||||
res.status(400).json({ error: error.message });
|
||||
} else {
|
||||
try {
|
||||
const michelsonStorage = await new LigoCompiler().compileStorage(
|
||||
body.syntax,
|
||||
body.code,
|
||||
body.entrypoint,
|
||||
body.format || 'text',
|
||||
body.storage
|
||||
);
|
||||
|
||||
res.send({ result: michelsonStorage });
|
||||
} catch (ex) {
|
||||
if (ex instanceof CompilerError) {
|
||||
res.status(400).json({ error: ex.message });
|
||||
} else {
|
||||
logger.error(ex);
|
||||
res.sendStatus(500);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -4,6 +4,7 @@ import { dirname, join } from 'path';
|
||||
|
||||
import { compileContractHandler } from './handlers/compile-contract';
|
||||
import { compileExpressionHandler } from './handlers/compile-expression';
|
||||
import { compileStorageHandler } from './handlers/compile-storage';
|
||||
import { deployHandler } from './handlers/deploy';
|
||||
import { dryRunHandler } from './handlers/dry-run';
|
||||
import { evaluateValueHandler } from './handlers/evaluate-value';
|
||||
@ -51,6 +52,7 @@ app.get(
|
||||
);
|
||||
app.post('/api/compile-contract', compileContractHandler);
|
||||
app.post('/api/compile-expression', compileExpressionHandler);
|
||||
app.post('/api/compile-storage', compileStorageHandler);
|
||||
app.post('/api/dry-run', dryRunHandler);
|
||||
app.post('/api/share', shareHandler);
|
||||
app.post('/api/evaluate-value', evaluateValueHandler);
|
||||
|
@ -10706,6 +10706,11 @@ slice-ansi@^2.1.0:
|
||||
astral-regex "^1.0.0"
|
||||
is-fullwidth-code-point "^2.0.0"
|
||||
|
||||
slugify@^1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/slugify/-/slugify-1.4.0.tgz#c9557c653c54b0c7f7a8e786ef3431add676d2cb"
|
||||
integrity sha512-FtLNsMGBSRB/0JOE2A0fxlqjI6fJsgHGS13iTuVT28kViI4JjUiNqp/vyis0ZXYcMnpR3fzGNkv+6vRlI2GwdQ==
|
||||
|
||||
snakeize@^0.1.0:
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/snakeize/-/snakeize-0.1.0.tgz#10c088d8b58eb076b3229bb5a04e232ce126422d"
|
||||
|
Loading…
Reference in New Issue
Block a user