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 @@
-
-
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.map(example => {
return (
);
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: ''
+ }
+ }
+ };
+ }
+}