diff --git a/tools/webide/packages/client/examples/cameligo/arithmetic-contract.ligo b/tools/webide/packages/client/examples/cameligo/arithmetic-contract.ligo index ac674c6ef..3bcd5660c 100644 --- a/tools/webide/packages/client/examples/cameligo/arithmetic-contract.ligo +++ b/tools/webide/packages/client/examples/cameligo/arithmetic-contract.ligo @@ -1,5 +1,5 @@ (*_* - name: Cameligo Contract + name: CameLIGO Contract language: cameligo compile: entrypoint: main diff --git a/tools/webide/packages/client/examples/pascaligo/arithmetic-contract.ligo b/tools/webide/packages/client/examples/pascaligo/arithmetic-contract.ligo index d533ba6e4..3eb38c48b 100644 --- a/tools/webide/packages/client/examples/pascaligo/arithmetic-contract.ligo +++ b/tools/webide/packages/client/examples/pascaligo/arithmetic-contract.ligo @@ -1,5 +1,5 @@ (*_* - name: Pascaligo Contract + name: PascaLIGO Contract language: pascaligo compile: entrypoint: main diff --git a/tools/webide/packages/client/examples/reasonligo/arithmetic-contract.ligo b/tools/webide/packages/client/examples/reasonligo/arithmetic-contract.ligo index 646d9c3a7..ecb06a569 100644 --- a/tools/webide/packages/client/examples/reasonligo/arithmetic-contract.ligo +++ b/tools/webide/packages/client/examples/reasonligo/arithmetic-contract.ligo @@ -1,5 +1,5 @@ (*_* - name: Reasonligo Contract + name: ReasonLIGO Contract language: reasonligo compile: entrypoint: main diff --git a/tools/webide/packages/client/ligo_run.svg b/tools/webide/packages/client/ligo_run.svg deleted file mode 100644 index 642bb327a..000000000 --- a/tools/webide/packages/client/ligo_run.svg +++ /dev/null @@ -1,84 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - diff --git a/tools/webide/packages/client/src/components/editable-title.tsx b/tools/webide/packages/client/src/components/editable-title.tsx new file mode 100644 index 000000000..3f9472032 --- /dev/null +++ b/tools/webide/packages/client/src/components/editable-title.tsx @@ -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 }) => ( + +); + +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(null); + + const notifyChanged = () => { + if (props.onChanged && props.title !== newTitle) { + props.onChanged(newTitle); + } + + setShowInput(false); + }; + + useEffect(() => { + setNewTitle(props.title); + }, [props.title]); + + return ( + + setNewTitle(event.target.value)} + onBlur={_ => notifyChanged()} + onKeyDown={event => { + if (event.key === 'Enter') { + notifyChanged(); + } else if (event.key === 'Escape') { + setNewTitle(props.title); + setShowInput(false); + } + }} + > + +
+ { + if (inputEl.current) { + inputEl.current.select(); + inputEl.current.setSelectionRange(0, 99999); + setShowInput(true); + } + }} + > + Rename +
+
+ ); +}; diff --git a/tools/webide/packages/client/src/components/editor.tsx b/tools/webide/packages/client/src/components/editor.tsx index dd3624a73..3d2843126 100644 --- a/tools/webide/packages/client/src/components/editor.tsx +++ b/tools/webide/packages/client/src/components/editor.tsx @@ -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(state => state.editor.title); + return (
-
+ + { + dispatch({ ...new ChangeTitleAction(value) }); + }} + > + +
); diff --git a/tools/webide/packages/client/src/components/examples.tsx b/tools/webide/packages/client/src/components/examples.tsx index 59c193b54..fec8c13c6 100644 --- a/tools/webide/packages/client/src/components/examples.tsx +++ b/tools/webide/packages/client/src/components/examples.tsx @@ -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( (state: AppState) => state.examples.list ); - const selectedExample = useSelector( - (state: AppState) => state.examples.selected + const editorDirty = useSelector( + (state: AppState) => state.editor.dirty ); + const dispatch = useDispatch(); return ( -
- Examples -
+
Examples
{examples.map(example => { return ( { const response = await getExample(example.id); - dispatch({ ...new ChangeSelectedAction(response) }); + 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} diff --git a/tools/webide/packages/client/src/components/header.tsx b/tools/webide/packages/client/src/components/header.tsx index 0298fe0dd..b4eb4bc45 100644 --- a/tools/webide/packages/client/src/components/header.tsx +++ b/tools/webide/packages/client/src/components/header.tsx @@ -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` diff --git a/tools/webide/packages/client/src/components/inputs.tsx b/tools/webide/packages/client/src/components/inputs.tsx index cd68c6d8d..22eabca0a 100644 --- a/tools/webide/packages/client/src/components/inputs.tsx +++ b/tools/webide/packages/client/src/components/inputs.tsx @@ -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; diff --git a/tools/webide/packages/client/src/components/monaco.tsx b/tools/webide/packages/client/src/components/monaco.tsx index 8aef9ad33..7007cc832 100644 --- a/tools/webide/packages/client/src/components/monaco.tsx +++ b/tools/webide/packages/client/src/components/monaco.tsx @@ -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(() => { - dispatch({ ...new ChangeCodeAction(editor.getValue()) }); + if (shouldDispatchCodeChangedAction) { + dispatch({ ...new ChangeCodeAction(editor.getValue()) }); + dispatch({ ...new ChangeDirtyAction(true) }); + dispatch({ ...new ClearSelectedAction() }); + } }); cleanupFunc.push(dispose); @@ -64,11 +71,13 @@ 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()) { - if (editorState.language === 'reasonligo') { + if (editorState.language === 'reasonligo') { monaco.editor.setModelLanguage(model, 'javascript'); } else { monaco.editor.setModelLanguage(model, editorState.language); diff --git a/tools/webide/packages/client/src/components/share.tsx b/tools/webide/packages/client/src/components/share.tsx index bb81591eb..06974784c 100644 --- a/tools/webide/packages/client/src/components/share.tsx +++ b/tools/webide/packages/client/src/components/share.tsx @@ -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; diff --git a/tools/webide/packages/client/src/components/syntax-select.tsx b/tools/webide/packages/client/src/components/syntax-select.tsx index e24ec4a70..d3fd4f794 100644 --- a/tools/webide/packages/client/src/components/syntax-select.tsx +++ b/tools/webide/packages/client/src/components/syntax-select.tsx @@ -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 }) => ( ); @@ -140,8 +146,11 @@ export const SyntaxSelectComponent = () => {
open(true)}> - {OPTIONS[language]} - + + Select syntax
); diff --git a/tools/webide/packages/client/src/components/tooltip.tsx b/tools/webide/packages/client/src/components/tooltip.tsx index 6bbef8e27..6aad6036d 100644 --- a/tools/webide/packages/client/src/components/tooltip.tsx +++ b/tools/webide/packages/client/src/components/tooltip.tsx @@ -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,19 +64,22 @@ export const Tooltip = (props: { position?: Position; children: any }) => { const ref = useRef(null); const [isTooltipVisible, setTooltipVisible] = useState(false); - const renderTooltip = (visible: boolean, triggerRect: ClientRect) => { - const tooltip = createElement( - StyledTooltip, - { - visible, - x: calcX(triggerRect, props.position), - y: calcY(triggerRect, props.position) - }, - props.children - ); + const renderTooltip = useCallback( + (visible: boolean, triggerRect: ClientRect) => { + const tooltip = createElement( + StyledTooltip, + { + visible, + x: calcX(triggerRect, props.position), + y: calcY(triggerRect, props.position) + }, + props.children + ); - render(tooltip, document.getElementById(TOOLTIP_CONTAINER_ID)); - }; + 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
; }; diff --git a/tools/webide/packages/client/src/index.css b/tools/webide/packages/client/src/index.css index aaa5a1f7b..6f15a1d73 100644 --- a/tools/webide/packages/client/src/index.css +++ b/tools/webide/packages/client/src/index.css @@ -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*/; diff --git a/tools/webide/packages/client/src/redux/editor.ts b/tools/webide/packages/client/src/redux/editor.ts index f1582dd01..462c07bb8 100644 --- a/tools/webide/packages/client/src/redux/editor.ts +++ b/tools/webide/packages/client/src/redux/editor.ts @@ -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; } - return state; }; diff --git a/tools/webide/packages/client/src/redux/examples.ts b/tools/webide/packages/client/src/redux/examples.ts index 98bc0a481..7e631cbc9 100644 --- a/tools/webide/packages/client/src/redux/examples.ts +++ b/tools/webide/packages/client/src/redux/examples.ts @@ -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; } - return state; }; diff --git a/tools/webide/packages/client/src/redux/share.ts b/tools/webide/packages/client/src/redux/share.ts index 5c807e48c..78a7b11ad 100644 --- a/tools/webide/packages/client/src/redux/share.ts +++ b/tools/webide/packages/client/src/redux/share.ts @@ -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: diff --git a/tools/webide/packages/client/src/setupProxy.js b/tools/webide/packages/client/src/setupProxy.js index b50059100..2e5650b40 100644 --- a/tools/webide/packages/client/src/setupProxy.js +++ b/tools/webide/packages/client/src/setupProxy.js @@ -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 + }) + ); }; diff --git a/tools/webide/packages/e2e/test/share.spec.js b/tools/webide/packages/e2e/test/share.spec.js index 40b84fd11..dd73d64db 100644 --- a/tools/webide/packages/e2e/test/share.spec.js +++ b/tools/webide/packages/e2e/test/share.spec.js @@ -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(); diff --git a/tools/webide/packages/server/src/handlers/share.ts b/tools/webide/packages/server/src/handlers/share.ts index 722e9d9c6..18bc13627 100644 --- a/tools/webide/packages/server/src/handlers/share.ts +++ b/tools/webide/packages/server/src/handlers/share.ts @@ -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({ diff --git a/tools/webide/packages/server/src/load-state.ts b/tools/webide/packages/server/src/load-state.ts index 2b71128d1..bb62e5a65 100644 --- a/tools/webide/packages/server/src/load-state.ts +++ b/tools/webide/packages/server/src/load-state.ts @@ -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; diff --git a/tools/webide/packages/server/src/schemas/share-latest.ts b/tools/webide/packages/server/src/schemas/share-latest.ts index 9125b413a..02caeddb0 100644 --- a/tools/webide/packages/server/src/schemas/share-latest.ts +++ b/tools/webide/packages/server/src/schemas/share-latest.ts @@ -1,3 +1,3 @@ -import { SchemaMigrationV1 } from './share-v1'; +import { SchemaMigrationV2 } from './share-v2'; -export default new SchemaMigrationV1(); +export default new SchemaMigrationV2(); diff --git a/tools/webide/packages/server/src/schemas/share-v2.ts b/tools/webide/packages/server/src/schemas/share-v2.ts new file mode 100644 index 000000000..1ebeacc64 --- /dev/null +++ b/tools/webide/packages/server/src/schemas/share-v2.ts @@ -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: '' + } + } + }; + } +}