Merge branch 'improving-syntax-selection-ui' into 'dev'
Improving syntax selection UI See merge request ligolang/ligo!445
This commit is contained in:
commit
bbe7a29881
@ -1,5 +1,5 @@
|
||||
(*_*
|
||||
name: Cameligo Contract
|
||||
name: CameLIGO Contract
|
||||
language: cameligo
|
||||
compile:
|
||||
entrypoint: main
|
||||
|
@ -1,5 +1,5 @@
|
||||
(*_*
|
||||
name: Pascaligo Contract
|
||||
name: PascaLIGO Contract
|
||||
language: pascaligo
|
||||
compile:
|
||||
entrypoint: main
|
||||
|
@ -1,5 +1,5 @@
|
||||
(*_*
|
||||
name: Reasonligo Contract
|
||||
name: ReasonLIGO Contract
|
||||
language: reasonligo
|
||||
compile:
|
||||
entrypoint: main
|
||||
|
@ -1,84 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
sodipodi:docname="ligo_run.svg"
|
||||
id="svg4550"
|
||||
version="1.1"
|
||||
viewBox="0 0 193.35434 193.35434"
|
||||
height="193.35434"
|
||||
width="193.35434"
|
||||
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
|
||||
style="fill:none">
|
||||
<metadata
|
||||
id="metadata4554">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="2560"
|
||||
inkscape:window-height="1400"
|
||||
id="namedview4552"
|
||||
showgrid="false"
|
||||
inkscape:zoom="1.9624573"
|
||||
inkscape:cx="24.54412"
|
||||
inkscape:cy="104.17717"
|
||||
inkscape:window-x="-12"
|
||||
inkscape:window-y="-12"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg4550"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0" />
|
||||
<circle
|
||||
cx="96.67717"
|
||||
cy="96.67717"
|
||||
r="74"
|
||||
id="circle4541"
|
||||
style="stroke:url(#paint0_linear);stroke-width:45.35433197;stroke-miterlimit:4;stroke-dasharray:none" />
|
||||
<defs
|
||||
id="defs4548">
|
||||
<linearGradient
|
||||
id="paint0_linear"
|
||||
x1="100"
|
||||
y1="54"
|
||||
x2="100"
|
||||
y2="254"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(-3.322834,-57.322834)">
|
||||
<stop
|
||||
stop-color="#3AA0FF"
|
||||
id="stop4543" />
|
||||
<stop
|
||||
offset="1"
|
||||
stop-color="#0072DC"
|
||||
id="stop4545" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path
|
||||
d="M 137.61977,80.627036 58.899178,68.296666 88.24752,141.00143 137.6208,80.624736 Z"
|
||||
id="path4537"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#fc683a;stroke-width:2.53620434" />
|
||||
</svg>
|
Before Width: | Height: | Size: 2.4 KiB |
121
tools/webide/packages/client/src/components/editable-title.tsx
Normal file
121
tools/webide/packages/client/src/components/editable-title.tsx
Normal file
@ -0,0 +1,121 @@
|
||||
import { faPencilAlt } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import styled, { css } from 'styled-components';
|
||||
|
||||
import { Tooltip } from './tooltip';
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
const Input = styled.input<{ visible?: boolean }>`
|
||||
position: absolute;
|
||||
border-radius: var(--border_radius);
|
||||
opacity: 0;
|
||||
height: 2em;
|
||||
width: 0;
|
||||
border: none;
|
||||
font-size: 1em;
|
||||
|
||||
outline: none;
|
||||
z-index: 1;
|
||||
|
||||
${props =>
|
||||
props.visible &&
|
||||
css`
|
||||
padding-left: 0.5em;
|
||||
opacity: 1;
|
||||
width: 15em;
|
||||
`}
|
||||
`;
|
||||
|
||||
const Label = styled.div<{ visible?: boolean }>`
|
||||
color: var(--blue);
|
||||
opacity: 0;
|
||||
|
||||
${props =>
|
||||
props.visible &&
|
||||
css`
|
||||
opacity: 1;
|
||||
`}
|
||||
`;
|
||||
|
||||
const PencilIcon = ({ ...props }) => (
|
||||
<FontAwesomeIcon {...props} icon={faPencilAlt} size="sm"></FontAwesomeIcon>
|
||||
);
|
||||
|
||||
const Pencil = styled(PencilIcon)<{ visible: boolean }>`
|
||||
margin-left: 10px;
|
||||
cursor: pointer;
|
||||
color: var(--label_foreground);
|
||||
opacity: 0;
|
||||
|
||||
:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
${props =>
|
||||
props.visible &&
|
||||
css`
|
||||
opacity: 0.5;
|
||||
`}
|
||||
`;
|
||||
|
||||
export const EditableTitleComponent = (props: {
|
||||
id?: string;
|
||||
title: string;
|
||||
onChanged?: (value: string) => void;
|
||||
className?: string;
|
||||
}) => {
|
||||
const [newTitle, setNewTitle] = useState(props.title);
|
||||
const [showInput, setShowInput] = useState(false);
|
||||
const inputEl = useRef<HTMLInputElement>(null);
|
||||
|
||||
const notifyChanged = () => {
|
||||
if (props.onChanged && props.title !== newTitle) {
|
||||
props.onChanged(newTitle);
|
||||
}
|
||||
|
||||
setShowInput(false);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setNewTitle(props.title);
|
||||
}, [props.title]);
|
||||
|
||||
return (
|
||||
<Container id={props.id} className={props.className}>
|
||||
<Input
|
||||
ref={inputEl}
|
||||
visible={showInput}
|
||||
value={newTitle}
|
||||
onChange={event => setNewTitle(event.target.value)}
|
||||
onBlur={_ => notifyChanged()}
|
||||
onKeyDown={event => {
|
||||
if (event.key === 'Enter') {
|
||||
notifyChanged();
|
||||
} else if (event.key === 'Escape') {
|
||||
setNewTitle(props.title);
|
||||
setShowInput(false);
|
||||
}
|
||||
}}
|
||||
></Input>
|
||||
<Label visible={!showInput}>{newTitle ? newTitle : 'Untitled'}</Label>
|
||||
<div>
|
||||
<Pencil
|
||||
visible={!showInput}
|
||||
onClick={() => {
|
||||
if (inputEl.current) {
|
||||
inputEl.current.select();
|
||||
inputEl.current.setSelectionRange(0, 99999);
|
||||
setShowInput(true);
|
||||
}
|
||||
}}
|
||||
></Pencil>
|
||||
<Tooltip>Rename</Tooltip>
|
||||
</div>
|
||||
</Container>
|
||||
);
|
||||
};
|
@ -1,6 +1,10 @@
|
||||
import React from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { AppState } from '../redux/app';
|
||||
import { ChangeTitleAction } from '../redux/editor';
|
||||
import { EditableTitleComponent } from './editable-title';
|
||||
import { MonacoComponent } from './monaco';
|
||||
import { ShareComponent } from './share';
|
||||
import { SyntaxSelectComponent } from './syntax-select';
|
||||
@ -12,20 +16,44 @@ const Container = styled.div`
|
||||
const Header = styled.div`
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
|
||||
min-height: 2.5em;
|
||||
border-bottom: 5px solid var(--blue_trans1);
|
||||
`;
|
||||
|
||||
const Subheader = styled.div`
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
background: var(--blue_trans1);
|
||||
border: 5px solid rgba(0, 0, 0, 0);
|
||||
border-top: none;
|
||||
padding: 0 10px;
|
||||
`;
|
||||
|
||||
export const EditorComponent = () => {
|
||||
const dispatch = useDispatch();
|
||||
const title = useSelector<AppState, string>(state => state.editor.title);
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Header>
|
||||
<SyntaxSelectComponent></SyntaxSelectComponent>
|
||||
<ShareComponent></ShareComponent>
|
||||
</Header>
|
||||
<Subheader>
|
||||
<EditableTitleComponent
|
||||
id="editor-title"
|
||||
title={title}
|
||||
onChanged={value => {
|
||||
dispatch({ ...new ChangeTitleAction(value) });
|
||||
}}
|
||||
></EditableTitleComponent>
|
||||
<SyntaxSelectComponent></SyntaxSelectComponent>
|
||||
</Subheader>
|
||||
<MonacoComponent></MonacoComponent>
|
||||
</Container>
|
||||
);
|
||||
|
@ -3,12 +3,13 @@ import { useDispatch, useSelector } from 'react-redux';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { AppState } from '../redux/app';
|
||||
import { ChangeDirtyAction, EditorState } from '../redux/editor';
|
||||
import { ChangeSelectedAction, ExamplesState } from '../redux/examples';
|
||||
import { getExample } from '../services/api';
|
||||
|
||||
const bgColor = 'transparent';
|
||||
const borderSize = '5px';
|
||||
const verticalPadding = '0.8em';
|
||||
const verticalPadding = '0.6em';
|
||||
|
||||
const Container = styled.div`
|
||||
flex: 0.5;
|
||||
@ -16,7 +17,7 @@ const Container = styled.div`
|
||||
flex-direction: column;
|
||||
`;
|
||||
|
||||
const MenuItem = styled.div<{ selected: boolean }>`
|
||||
const MenuItem = styled.div<{ selected?: boolean }>`
|
||||
padding: ${verticalPadding} 0 ${verticalPadding} 1em;
|
||||
height: 1.5em;
|
||||
display: flex;
|
||||
@ -27,25 +28,12 @@ const MenuItem = styled.div<{ selected: boolean }>`
|
||||
border-left: ${`${borderSize} solid ${bgColor}`};
|
||||
border-left-color: ${props => (props.selected ? 'var(--blue)' : bgColor)};
|
||||
|
||||
:first-child {
|
||||
margin-top: ${props => (props.selected ? '0' : `-${borderSize}`)};
|
||||
}
|
||||
|
||||
:hover {
|
||||
background-color: ${props =>
|
||||
props.selected ? 'var(--blue_trans1)' : 'var(--blue_trans2)'};
|
||||
border-left: ${`${borderSize} solid ${bgColor}`};
|
||||
border-left-color: ${props =>
|
||||
props.selected ? 'var(--blue)' : 'transparent'};
|
||||
:first-child {
|
||||
margin-top: ${props => (props.selected ? '0' : `-${borderSize}`)};
|
||||
padding-top: ${props =>
|
||||
props.selected
|
||||
? `${verticalPadding}`
|
||||
: `calc(${verticalPadding} - ${borderSize})`};
|
||||
border-top: ${props =>
|
||||
props.selected ? '' : `${borderSize} solid var(--blue_opaque1)`};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@ -57,44 +45,45 @@ const MenuContainer = styled.div`
|
||||
box-sizing: border-box;
|
||||
`;
|
||||
|
||||
const Header = styled.div<{ firstChildSelected: boolean }>`
|
||||
border-bottom: ${props =>
|
||||
props.firstChildSelected ? '' : '5px solid var(--blue_trans1)'};
|
||||
const Header = styled.div`
|
||||
min-height: 2.5em;
|
||||
padding: 0 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-weight: 600;
|
||||
`;
|
||||
|
||||
export const Examples = () => {
|
||||
const examples = useSelector<AppState, ExamplesState['list']>(
|
||||
(state: AppState) => state.examples.list
|
||||
);
|
||||
const selectedExample = useSelector<AppState, ExamplesState['selected']>(
|
||||
(state: AppState) => state.examples.selected
|
||||
const editorDirty = useSelector<AppState, EditorState['dirty']>(
|
||||
(state: AppState) => state.editor.dirty
|
||||
);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Header
|
||||
firstChildSelected={
|
||||
!!selectedExample && examples[0].id === selectedExample.id
|
||||
}
|
||||
>
|
||||
<span>Examples</span>
|
||||
</Header>
|
||||
<Header>Examples</Header>
|
||||
<MenuContainer>
|
||||
{examples.map(example => {
|
||||
return (
|
||||
<MenuItem
|
||||
id={example.id}
|
||||
key={example.id}
|
||||
selected={!!selectedExample && example.id === selectedExample.id}
|
||||
onClick={async () => {
|
||||
const response = await getExample(example.id);
|
||||
|
||||
if (
|
||||
!editorDirty ||
|
||||
window.confirm(
|
||||
'Are you sure you want to navigate away? Data you have entered will be lost.\n\nPress OK to continue or Cancel to stay on the current page.\n\n'
|
||||
)
|
||||
) {
|
||||
dispatch({ ...new ChangeSelectedAction(response) });
|
||||
dispatch({ ...new ChangeDirtyAction(false) });
|
||||
}
|
||||
}}
|
||||
>
|
||||
{example.name}
|
||||
|
@ -6,9 +6,9 @@ const Container = styled.div`
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
padding: 0.5em 1em;
|
||||
padding: 0.2em 1em;
|
||||
font-family: 'DM Sans', 'Open Sans', sans-serif;
|
||||
box-shadow: 0px 2px 3px 0px rgba(0, 0, 0, 0.3);
|
||||
box-shadow: 0px 1px 1px 0px rgba(0, 0, 0, 0.3);
|
||||
`;
|
||||
|
||||
const Group = styled.div`
|
||||
|
@ -12,12 +12,12 @@ export const HGroup = styled.div`
|
||||
|
||||
export const Label = styled.label`
|
||||
font-size: 1em;
|
||||
color: rgba(153, 153, 153, 1);
|
||||
color: var(--label_foreground);
|
||||
`;
|
||||
|
||||
export const Input = styled.input`
|
||||
margin: 0.3em 0 0.7em 0;
|
||||
background-color: #eff7ff;
|
||||
background-color: var(--input_background);
|
||||
border-style: none;
|
||||
border-bottom: 5px solid #e1f1ff;
|
||||
padding: 0.5em;
|
||||
@ -33,7 +33,7 @@ export const Input = styled.input`
|
||||
export const Textarea = styled.textarea`
|
||||
resize: vertical;
|
||||
margin: 0.3em 0 0.7em 0;
|
||||
background-color: #eff7ff;
|
||||
background-color: var(--input_background);
|
||||
border-style: none;
|
||||
border-bottom: 5px solid #e1f1ff;
|
||||
padding: 0.5em;
|
||||
|
@ -4,7 +4,8 @@ import { useDispatch, useStore } from 'react-redux';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { AppState } from '../redux/app';
|
||||
import { ChangeCodeAction } from '../redux/editor';
|
||||
import { ChangeCodeAction, ChangeDirtyAction } from '../redux/editor';
|
||||
import { ClearSelectedAction } from '../redux/examples';
|
||||
|
||||
const Container = styled.div`
|
||||
height: var(--content_height);
|
||||
@ -53,8 +54,14 @@ export const MonacoComponent = () => {
|
||||
}
|
||||
});
|
||||
|
||||
let shouldDispatchCodeChangedAction = true;
|
||||
|
||||
const { dispose } = editor.onDidChangeModelContent(() => {
|
||||
if (shouldDispatchCodeChangedAction) {
|
||||
dispatch({ ...new ChangeCodeAction(editor.getValue()) });
|
||||
dispatch({ ...new ChangeDirtyAction(true) });
|
||||
dispatch({ ...new ClearSelectedAction() });
|
||||
}
|
||||
});
|
||||
|
||||
cleanupFunc.push(dispose);
|
||||
@ -64,7 +71,9 @@ export const MonacoComponent = () => {
|
||||
const { editor: editorState }: AppState = store.getState();
|
||||
|
||||
if (editorState.code !== editor.getValue()) {
|
||||
shouldDispatchCodeChangedAction = false;
|
||||
editor.setValue(editorState.code);
|
||||
shouldDispatchCodeChangedAction = true;
|
||||
}
|
||||
|
||||
if (editorState.language !== model.getModeId()) {
|
||||
|
@ -12,7 +12,7 @@ import { Tooltip } from './tooltip';
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
@ -81,9 +81,9 @@ const Input = styled.input<{ visible?: boolean }>`
|
||||
opacity: 0;
|
||||
height: 2em;
|
||||
width: 2em;
|
||||
transform: translateX(-0.3em);
|
||||
transform: translateX(0.3em);
|
||||
border: none;
|
||||
padding: 0 1em;
|
||||
padding-left: 2em;
|
||||
font-size: 1em;
|
||||
color: white;
|
||||
|
||||
|
@ -8,6 +8,7 @@ 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;
|
||||
@ -22,14 +23,19 @@ const Header = styled.div`
|
||||
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
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>
|
||||
);
|
||||
@ -140,8 +146,11 @@ export const SyntaxSelectComponent = () => {
|
||||
</List>
|
||||
</OutsideClickHandler>
|
||||
<Header id="syntax-select" onClick={() => open(true)}>
|
||||
<span>{OPTIONS[language]}</span>
|
||||
<Label>
|
||||
{OPTIONS[language]}
|
||||
<Arrow rotate={opened}></Arrow>
|
||||
</Label>
|
||||
<Tooltip>Select syntax</Tooltip>
|
||||
</Header>
|
||||
</Container>
|
||||
);
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { createElement, useEffect, useRef, useState } from 'react';
|
||||
import React, { createElement, useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { render } from 'react-dom';
|
||||
import styled from 'styled-components';
|
||||
|
||||
@ -64,7 +64,8 @@ export const Tooltip = (props: { position?: Position; children: any }) => {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const [isTooltipVisible, setTooltipVisible] = useState(false);
|
||||
|
||||
const renderTooltip = (visible: boolean, triggerRect: ClientRect) => {
|
||||
const renderTooltip = useCallback(
|
||||
(visible: boolean, triggerRect: ClientRect) => {
|
||||
const tooltip = createElement(
|
||||
StyledTooltip,
|
||||
{
|
||||
@ -76,7 +77,9 @@ export const Tooltip = (props: { position?: Position; children: any }) => {
|
||||
);
|
||||
|
||||
render(tooltip, document.getElementById(TOOLTIP_CONTAINER_ID));
|
||||
};
|
||||
},
|
||||
[props.position, props.children]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (ref.current) {
|
||||
@ -98,7 +101,7 @@ export const Tooltip = (props: { position?: Position; children: any }) => {
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
}, [isTooltipVisible, renderTooltip]);
|
||||
|
||||
return <div ref={ref}></div>;
|
||||
};
|
||||
|
@ -10,6 +10,8 @@
|
||||
--blue_opaque1: #dbeaff;
|
||||
--blue_trans2: rgba(14, 116, 255, 0.08); /* #eff7ff; */
|
||||
|
||||
--input_background: #eff7ff;
|
||||
|
||||
--grey: #888;
|
||||
|
||||
--box-shadow: 1px 3px 10px 0px rgba(153, 153, 153, 0.4); /* or #999999 */
|
||||
@ -46,7 +48,7 @@
|
||||
--font_ghost_weight: 700;
|
||||
--font_ghost_color: rgba(153, 153, 153, 0.5); /* or #CFCFCF */
|
||||
|
||||
--content_height: 85vh;
|
||||
--content_height: 80vh;
|
||||
|
||||
--tooltip_foreground: white;
|
||||
--tooltip_background: rgba(0, 0, 0, 0.75) /*#404040*/;
|
||||
|
@ -3,12 +3,16 @@ import { Language } from './types';
|
||||
|
||||
export enum ActionType {
|
||||
ChangeLanguage = 'editor-change-language',
|
||||
ChangeCode = 'editor-change-code'
|
||||
ChangeCode = 'editor-change-code',
|
||||
ChangeDirty = 'editor-change-dirty',
|
||||
ChangeTitle = 'editor-change-title'
|
||||
}
|
||||
|
||||
export interface EditorState {
|
||||
language: Language;
|
||||
code: string;
|
||||
title: string;
|
||||
dirty: boolean;
|
||||
}
|
||||
|
||||
export class ChangeLanguageAction {
|
||||
@ -21,14 +25,28 @@ export class ChangeCodeAction {
|
||||
constructor(public payload: EditorState['code']) {}
|
||||
}
|
||||
|
||||
export class ChangeDirtyAction {
|
||||
public readonly type = ActionType.ChangeDirty;
|
||||
constructor(public payload: EditorState['dirty']) {}
|
||||
}
|
||||
|
||||
export class ChangeTitleAction {
|
||||
public readonly type = ActionType.ChangeTitle;
|
||||
constructor(public payload: EditorState['title']) {}
|
||||
}
|
||||
|
||||
type Action =
|
||||
| ChangeCodeAction
|
||||
| ChangeLanguageAction
|
||||
| ChangeDirtyAction
|
||||
| ChangeTitleAction
|
||||
| ChangeSelectedExampleAction;
|
||||
|
||||
const DEFAULT_STATE: EditorState = {
|
||||
language: Language.CameLigo,
|
||||
code: ''
|
||||
code: '',
|
||||
title: '',
|
||||
dirty: false
|
||||
};
|
||||
|
||||
export default (state = DEFAULT_STATE, action: Action): EditorState => {
|
||||
@ -36,7 +54,9 @@ export default (state = DEFAULT_STATE, action: Action): EditorState => {
|
||||
case ExamplesActionType.ChangeSelected:
|
||||
return {
|
||||
...state,
|
||||
...(!action.payload ? DEFAULT_STATE : action.payload.editor)
|
||||
...(!action.payload
|
||||
? DEFAULT_STATE
|
||||
: { ...action.payload.editor, title: action.payload.name })
|
||||
};
|
||||
case ActionType.ChangeLanguage:
|
||||
return {
|
||||
@ -48,6 +68,17 @@ export default (state = DEFAULT_STATE, action: Action): EditorState => {
|
||||
...state,
|
||||
code: action.payload
|
||||
};
|
||||
}
|
||||
case ActionType.ChangeDirty:
|
||||
return {
|
||||
...state,
|
||||
dirty: action.payload
|
||||
};
|
||||
case ActionType.ChangeTitle:
|
||||
return {
|
||||
...state,
|
||||
title: action.payload
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { ExampleState } from './example';
|
||||
|
||||
export enum ActionType {
|
||||
ChangeSelected = 'examples-change-selected'
|
||||
ChangeSelected = 'examples-change-selected',
|
||||
ClearSelected = 'examples-clear-selected'
|
||||
}
|
||||
|
||||
export interface ExampleItem {
|
||||
@ -19,13 +20,25 @@ export class ChangeSelectedAction {
|
||||
constructor(public payload: ExamplesState['selected']) {}
|
||||
}
|
||||
|
||||
type Action = ChangeSelectedAction;
|
||||
export class ClearSelectedAction {
|
||||
public readonly type = ActionType.ClearSelected;
|
||||
}
|
||||
|
||||
type Action = ChangeSelectedAction | ClearSelectedAction;
|
||||
|
||||
export const DEFAULT_STATE: ExamplesState = {
|
||||
selected: null,
|
||||
list: []
|
||||
};
|
||||
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
DEFAULT_STATE.list = [
|
||||
{ id: 'MzkMQ1oiVHJqbcfUuVFKTw', name: 'CameLIGO Contract' },
|
||||
{ id: 'FEb62HL7onjg1424eUsGSg', name: 'PascaLIGO Contract' },
|
||||
{ id: 'JPhSOehj_2MFwRIlml0ymQ', name: 'ReasonLIGO Contract' }
|
||||
];
|
||||
}
|
||||
|
||||
export default (state = DEFAULT_STATE, action: Action): ExamplesState => {
|
||||
switch (action.type) {
|
||||
case ActionType.ChangeSelected:
|
||||
@ -33,6 +46,12 @@ export default (state = DEFAULT_STATE, action: Action): ExamplesState => {
|
||||
...state,
|
||||
selected: action.payload
|
||||
};
|
||||
}
|
||||
case ActionType.ClearSelected:
|
||||
return {
|
||||
...state,
|
||||
selected: null
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
@ -15,7 +15,7 @@ import {
|
||||
ChangeParametersAction as ChangeDryRunParametersAction,
|
||||
ChangeStorageAction as ChangeDryRunStorageAction,
|
||||
} from './dry-run';
|
||||
import { ActionType as EditorActionType, ChangeCodeAction, ChangeLanguageAction } from './editor';
|
||||
import { ActionType as EditorActionType, ChangeCodeAction, ChangeLanguageAction, ChangeTitleAction } from './editor';
|
||||
import {
|
||||
ActionType as EvaluateFunctionActionType,
|
||||
ChangeEntrypointAction as ChangeEvaluateFunctionEntrypointAction,
|
||||
@ -25,6 +25,7 @@ import {
|
||||
ActionType as EvaluateValueActionType,
|
||||
ChangeEntrypointAction as ChangeEvaluateValueEntrypointAction,
|
||||
} from './evaluate-value';
|
||||
import { ActionType as ExamplesActionType, ChangeSelectedAction as ChangeSelectedExampleAction } from './examples';
|
||||
|
||||
export enum ActionType {
|
||||
ChangeShareLink = 'share-change-link'
|
||||
@ -41,6 +42,7 @@ export class ChangeShareLinkAction {
|
||||
|
||||
type Action =
|
||||
| ChangeShareLinkAction
|
||||
| ChangeTitleAction
|
||||
| ChangeCodeAction
|
||||
| ChangeLanguageAction
|
||||
| ChangeCompileEntrypointAction
|
||||
@ -53,7 +55,8 @@ type Action =
|
||||
| ChangeDryRunStorageAction
|
||||
| ChangeEvaluateFunctionEntrypointAction
|
||||
| ChangeEvaluateFunctionParametersAction
|
||||
| ChangeEvaluateValueEntrypointAction;
|
||||
| ChangeEvaluateValueEntrypointAction
|
||||
| ChangeSelectedExampleAction;
|
||||
|
||||
const DEFAULT_STATE: ShareState = {
|
||||
link: ''
|
||||
@ -61,6 +64,8 @@ const DEFAULT_STATE: ShareState = {
|
||||
|
||||
export default (state = DEFAULT_STATE, action: Action): ShareState => {
|
||||
switch (action.type) {
|
||||
case EditorActionType.ChangeTitle:
|
||||
case ExamplesActionType.ChangeSelected:
|
||||
case EditorActionType.ChangeCode:
|
||||
case EditorActionType.ChangeLanguage:
|
||||
case CompileActionType.ChangeEntrypoint:
|
||||
|
@ -1,6 +1,6 @@
|
||||
const proxy = require('http-proxy-middleware');
|
||||
|
||||
module.exports = function(app) {
|
||||
module.exports = function (app) {
|
||||
app.use(
|
||||
'/api',
|
||||
proxy({
|
||||
@ -8,4 +8,12 @@ module.exports = function(app) {
|
||||
changeOrigin: true
|
||||
})
|
||||
);
|
||||
|
||||
app.use(
|
||||
'/static/examples',
|
||||
proxy({
|
||||
target: 'http://localhost:8080',
|
||||
changeOrigin: true
|
||||
})
|
||||
);
|
||||
};
|
||||
|
@ -24,7 +24,7 @@ describe('Share', () => {
|
||||
await responseCallback;
|
||||
|
||||
const actualShareLink = await page.evaluate(getInputValue, 'share-link');
|
||||
const expectedShareLink = `${API_HOST}/p/sIpy-2D9ExpCojwuBNw_-g`;
|
||||
const expectedShareLink = `${API_HOST}/p/WxKPBq9-mkZ_kq4cMHXfCQ`;
|
||||
|
||||
expect(actualShareLink).toEqual(expectedShareLink);
|
||||
done();
|
||||
|
@ -10,6 +10,8 @@ interface ShareBody {
|
||||
editor: {
|
||||
language: string;
|
||||
code: string;
|
||||
dirty: boolean;
|
||||
title: string;
|
||||
};
|
||||
compile: {
|
||||
entrypoint: string;
|
||||
@ -39,7 +41,9 @@ const validateRequest = (body: any): { value: ShareBody; error: any } => {
|
||||
editor: joi
|
||||
.object({
|
||||
language: joi.string().required(),
|
||||
code: joi.string().required()
|
||||
code: joi.string().required(),
|
||||
dirty: joi.boolean().optional(),
|
||||
title: joi.string().allow('')
|
||||
})
|
||||
.required(),
|
||||
compile: joi.object({
|
||||
|
@ -24,7 +24,9 @@ export async function loadDefaultState(appBundleDirectory: string) {
|
||||
deploy: {},
|
||||
evaluateValue: {},
|
||||
evaluateFunction: {},
|
||||
editor: {},
|
||||
editor: {
|
||||
title: ''
|
||||
},
|
||||
examples: {
|
||||
selected: null,
|
||||
list: examplesList
|
||||
@ -59,9 +61,9 @@ export async function loadDefaultState(appBundleDirectory: string) {
|
||||
};
|
||||
defaultState.editor = {
|
||||
...defaultState.editor,
|
||||
...defaultExample.editor
|
||||
...defaultExample.editor,
|
||||
title: defaultExample.name
|
||||
};
|
||||
defaultState.examples.selected = defaultExample;
|
||||
}
|
||||
|
||||
return defaultState;
|
||||
|
@ -1,3 +1,3 @@
|
||||
import { SchemaMigrationV1 } from './share-v1';
|
||||
import { SchemaMigrationV2 } from './share-v2';
|
||||
|
||||
export default new SchemaMigrationV1();
|
||||
export default new SchemaMigrationV2();
|
||||
|
96
tools/webide/packages/server/src/schemas/share-v2.ts
Normal file
96
tools/webide/packages/server/src/schemas/share-v2.ts
Normal file
@ -0,0 +1,96 @@
|
||||
import joi from '@hapi/joi';
|
||||
|
||||
import { Migration } from './migration';
|
||||
import { SchemaMigrationV1, SchemaV1 } from './share-v1';
|
||||
|
||||
export type Version = 'v2';
|
||||
|
||||
export interface SchemaV2 {
|
||||
version: Version;
|
||||
state: {
|
||||
editor: {
|
||||
language: string;
|
||||
code: string;
|
||||
dirty: boolean;
|
||||
title: string;
|
||||
};
|
||||
compile: {
|
||||
entrypoint: string;
|
||||
};
|
||||
dryRun: {
|
||||
entrypoint: string;
|
||||
parameters: string;
|
||||
storage: string;
|
||||
};
|
||||
deploy: {
|
||||
entrypoint: string;
|
||||
storage: string;
|
||||
useTezBridge?: boolean;
|
||||
};
|
||||
evaluateValue: {
|
||||
entrypoint: string;
|
||||
};
|
||||
evaluateFunction: {
|
||||
entrypoint: string;
|
||||
parameters: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export class SchemaMigrationV2 extends Migration {
|
||||
readonly VERSION: Version = 'v2';
|
||||
|
||||
protected readonly schema = joi.object({
|
||||
version: joi
|
||||
.string()
|
||||
.required()
|
||||
.allow(this.VERSION),
|
||||
state: joi.object({
|
||||
editor: joi
|
||||
.object({
|
||||
language: joi.string().required(),
|
||||
code: joi.string().required(),
|
||||
dirty: joi.boolean().optional(),
|
||||
title: joi.string().allow('')
|
||||
})
|
||||
.required(),
|
||||
compile: joi.object({
|
||||
entrypoint: joi.string().allow('')
|
||||
}),
|
||||
dryRun: joi.object({
|
||||
entrypoint: joi.string().allow(''),
|
||||
parameters: joi.any().allow(''),
|
||||
storage: joi.any().allow('')
|
||||
}),
|
||||
deploy: joi.object({
|
||||
entrypoint: joi.string().allow(''),
|
||||
storage: joi.any().allow(''),
|
||||
useTezBridge: joi.boolean().optional()
|
||||
}),
|
||||
evaluateValue: joi.object({
|
||||
entrypoint: joi.string().allow('')
|
||||
}),
|
||||
evaluateFunction: joi.object({
|
||||
entrypoint: joi.string().allow(''),
|
||||
parameters: joi.any().allow('')
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
protected readonly previous = new SchemaMigrationV1();
|
||||
|
||||
protected migrate(data: SchemaV1): SchemaV2 {
|
||||
return {
|
||||
...data,
|
||||
version: this.VERSION,
|
||||
state: {
|
||||
...data.state,
|
||||
editor: {
|
||||
...data.state.editor,
|
||||
dirty: false,
|
||||
title: ''
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user