From e290f3ef94a1b65d0e090553563b16df6818e871 Mon Sep 17 00:00:00 2001 From: Max Daunarovich Date: Fri, 18 Mar 2022 20:21:21 +0300 Subject: [PATCH 01/49] Add hooks for language server --- src/hooks/useLanguageServer.ts | 72 ++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 src/hooks/useLanguageServer.ts diff --git a/src/hooks/useLanguageServer.ts b/src/hooks/useLanguageServer.ts new file mode 100644 index 00000000..43db9073 --- /dev/null +++ b/src/hooks/useLanguageServer.ts @@ -0,0 +1,72 @@ +import { useEffect, useState } from 'react'; +import { CadenceLanguageServer, Callbacks } from 'util/language-server'; +import { MonacoServices } from 'monaco-languageclient/lib/monaco-services'; +import * as monaco from 'monaco-editor'; + +let monacoServicesInstalled = false; + +async function startLanguageServer(serverProps: any, setServer: any) { + const { callbacks, getCode } = serverProps; + const server = await CadenceLanguageServer.create(callbacks); + + new Promise((resolve, reject) => { + let checkInterval = setInterval(() => { + // .toServer() method is populated by language server + // if it was not properly started or in progress it will be "null" + if (callbacks.toServer !== null) { + clearInterval(checkInterval); + callbacks.getAddressCode = getCode; + setServer(server); + } + }, 100); + }); + setServer(server); +} + +export default function useLanguageServer(props: any) { + // Language Server Callbacks + let callbacks: Callbacks = { + // The actual callback will be set as soon as the language server is initialized + toServer: null, + + // The actual callback will be set as soon as the language server is initialized + onClientClose: null, + + // The actual callback will be set as soon as the language client is initialized + onServerClose: null, + + // The actual callback will be set as soon as the language client is initialized + toClient: null, + + //@ts-ignore + getAddressCode(address: string): string | undefined { + // we will set it once it is instantiated + }, + }; + + // Base state handler + const [languageServer, setLanguageServer] = useState(null); + const [languageClient, setLanguageClient] = useState(null); + const [initialized, setInitialized] = useState(false); + + const { getCode } = props; + const restartServer = () => { + // TODO: Clean global variables, so we could ensure there is no duplication of events and messages + startLanguageServer({ callbacks, getCode }, setLanguageServer); + }; + useEffect(() => { + // The Monaco Language Client services have to be installed globally, once. + // An editor must be passed, which is only used for commands. + // As the Cadence language server is not providing any commands this is OK + + console.log('Installing monaco services'); + if (!monacoServicesInstalled) { + monacoServicesInstalled = true; + MonacoServices.install(monaco); + } + + restartServer(); + }, []); + + return [languageClient, languageServer, initialized, restartServer]; +} From 1ab08d47c51ed8582a0188d982e92628286425ef Mon Sep 17 00:00:00 2001 From: Max Daunarovich Date: Fri, 18 Mar 2022 20:21:42 +0300 Subject: [PATCH 02/49] Editor layout components --- src/containers/Editor/layout-components.tsx | 30 +++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 src/containers/Editor/layout-components.tsx diff --git a/src/containers/Editor/layout-components.tsx b/src/containers/Editor/layout-components.tsx new file mode 100644 index 00000000..7eda35a9 --- /dev/null +++ b/src/containers/Editor/layout-components.tsx @@ -0,0 +1,30 @@ +import styled from "@emotion/styled"; +import {WithShowProps} from "containers/Editor/components"; + +export const ProjectInfoContainer = styled.div` + display: ${({ show }) => (show ? 'block' : 'none')}; + margin: 0.2rem 1rem 0rem 1rem; + min-width: 500px; + margin-top: 1rem; +`; + +export const ProjectHeading = styled.div` + font-size: 2rem; + font-weight: 700; + margin-top: 0.25rem; + padding: 1rem; +`; + +export const ProjectDescription = styled.div` + font-size: 1.2rem; + margin: 1rem; + margin-top: 2rem; + padding: 0.5rem; + border-radius: 2px; + font-style: italic; +`; + +export const ReadmeHtmlContainer = styled.div` + margin: 1rem; + margin-top: 0rem; +`; \ No newline at end of file From fd1f7ee018f5068d4a39ba9fcc2f0f5967d3237e Mon Sep 17 00:00:00 2001 From: Max Daunarovich Date: Fri, 18 Mar 2022 20:22:05 +0300 Subject: [PATCH 03/49] Move portions out of editor components --- src/containers/Editor/components.tsx | 105 +++------------------------ 1 file changed, 11 insertions(+), 94 deletions(-) diff --git a/src/containers/Editor/components.tsx b/src/containers/Editor/components.tsx index 1ca9ac04..9589dc37 100644 --- a/src/containers/Editor/components.tsx +++ b/src/containers/Editor/components.tsx @@ -11,8 +11,7 @@ import { Heading } from 'layout/Heading'; import { EntityType, ActiveEditor } from 'providers/Project'; import { useProject } from 'providers/Project/projectHooks'; import { PLACEHOLDER_DESCRIPTION, PLACEHOLDER_TITLE } from "providers/Project/projectDefault"; -import {Account, Project} from 'api/apollo/generated/graphql'; - +import {Account, Project} from 'api/apollo/generated/graphql' import debounce from 'util/debounce'; import Mixpanel from 'util/mixpanel'; @@ -31,11 +30,16 @@ import { Label, } from 'components/Arguments/SingleArgument/styles'; import { Markdown } from 'components/Markdown'; +import { + ProjectInfoContainer, + ProjectDescription, + ProjectHeading, + ReadmeHtmlContainer +} from './layout-components' import { decodeText } from "util/readme"; -import { CadenceLanguageServer, Callbacks } from "util/language-server"; -import { MonacoServices } from "monaco-languageclient/lib/monaco-services"; import * as monaco from "monaco-editor"; +import useLanguageServer from "../../hooks/useLanguageServer" export interface WithShowProps { show: boolean; @@ -165,33 +169,6 @@ function getActiveId(project: Project, active: ActiveEditor): string { } } -const ProjectInfoContainer = styled.div` - display: ${({ show }) => (show ? 'block' : 'none')}; - margin: 0.2rem 1rem 0rem 1rem; - min-width: 500px; - margin-top: 1rem; -`; - -const ProjectHeading = styled.div` - font-size: 2rem; - font-weight: 700; - margin-top: 0.25rem; - padding: 1rem; -`; - -const ProjectDescription = styled.div` - font-size: 1.2rem; - margin: 1rem; - margin-top: 2rem; - padding: 0.5rem; - border-radius: 2px; - font-style: italic; -`; - -const ReadmeHtmlContainer = styled.div` - margin: 1rem; - margin-top: 0rem; -`; const usePrevious = (value: any) => { const ref = useRef(); @@ -211,8 +188,6 @@ const compareContracts = (prev: Account[], current: Account[]) => { return true } -let monacoServicesInstalled = false; - const MAX_DESCRIPTION_SIZE = Math.pow(1024, 2) // 1mb of storage can be saved into readme field const calculateSize = (readme: string) => { const { size } = new Blob([readme]) @@ -254,36 +229,6 @@ const EditorContainer: React.FC = ({ } }, [isLoading, active, projectAccess.project]); - - // The Monaco Language Client services have to be installed globally, once. - // An editor must be passed, which is only used for commands. - // As the Cadence language server is not providing any commands this is OK - - if (!monacoServicesInstalled) { - monacoServicesInstalled = true; - MonacoServices.install(monaco); - } - - // We will move callbacks out - let callbacks : Callbacks = { - // The actual callback will be set as soon as the language server is initialized - toServer: null, - - // The actual callback will be set as soon as the language server is initialized - onClientClose: null, - - // The actual callback will be set as soon as the language client is initialized - onServerClose: null, - - // The actual callback will be set as soon as the language client is initialized - toClient: null, - - //@ts-ignore - getAddressCode(address: string): string | undefined { - // we will set it once it is instantiated - } - }; - const getCode = (project: any) => (address: string) =>{ const number = parseInt(address, 16); if (!number) { @@ -298,37 +243,9 @@ const EditorContainer: React.FC = ({ return code } - const [serverReady, setServerReady] = useState(false) - const [serverCallbacks, setServerCallbacks] = useState(callbacks) - const [languageServer, setLanguageServer] = useState(null) - const initLanguageServer = async ()=>{ - const server = await CadenceLanguageServer.create(callbacks) - setLanguageServer(server) - } - - useEffect(()=>{ - // Init language server - initLanguageServer() - - let checkInterval = setInterval(()=>{ - // .toServer() method is populated by language server - // if it was not properly started or in progress it will be "null" - if (callbacks.toServer !== null){ - clearInterval(checkInterval); - setServerReady(true) - callbacks.getAddressCode = getCode(project) - setServerCallbacks(callbacks) - } - }, 300) - // TODO: Check if we can reinstantiate language server after accounts has been changed - },[]) - - const reloadServer = async ()=>{ - serverCallbacks.getAddressCode = getCode(project) - const server = await CadenceLanguageServer.create(serverCallbacks) - setServerCallbacks(serverCallbacks) - setLanguageServer(server) - } + const [languageClient, languageServer, initialized, reloadServer] = useLanguageServer({ + getCode + }); const previousProjectState = usePrevious(project) From cc2f3142c30a8c97eeb5be13eea7bb9b9141ddf0 Mon Sep 17 00:00:00 2001 From: Max Daunarovich Date: Fri, 18 Mar 2022 20:22:20 +0300 Subject: [PATCH 04/49] Disable some of the Typescript features to speed up development --- tsconfig.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tsconfig.json b/tsconfig.json index 2cee9c65..f2802fda 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,8 +8,10 @@ "moduleResolution": "node", "allowSyntheticDefaultImports": true, "noImplicitAny": true, +/* "noUnusedLocals": true, "noUnusedParameters": true, +*/ "removeComments": false, "preserveConstEnums": true, "sourceMap": true, From 6275f436dd3c3fe4c415b9d58abd926c404a011c Mon Sep 17 00:00:00 2001 From: Max Daunarovich Date: Mon, 21 Mar 2022 21:34:35 +0300 Subject: [PATCH 05/49] Isolate monaco editor and language server/client --- src/components/CadenceEditor.tsx | 18 +-- src/components/MonacoEditor/components.tsx | 76 ++++++++++ src/components/MonacoEditor/index.tsx | 154 +++++++++++++++++++++ src/containers/Editor/components.tsx | 19 +-- src/hooks/useLanguageServer.ts | 12 +- tsconfig.json | 2 +- 6 files changed, 257 insertions(+), 24 deletions(-) create mode 100644 src/components/MonacoEditor/components.tsx create mode 100644 src/components/MonacoEditor/index.tsx diff --git a/src/components/CadenceEditor.tsx b/src/components/CadenceEditor.tsx index 1b6ef59f..fe0a281b 100644 --- a/src/components/CadenceEditor.tsx +++ b/src/components/CadenceEditor.tsx @@ -116,9 +116,6 @@ type CadenceEditorProps = { show: boolean; onChange: any; activeId: string; - languageServer: CadenceLanguageServer - callbacks: Callbacks - serverReady: boolean }; type CadenceEditorState = { @@ -169,9 +166,10 @@ class CadenceEditor extends React.Component< async componentDidMount() { await this.initEditor() - if (this.props.serverReady) { + // TODO: refactor +/* if (this.props.serverReady) { await this.loadLanguageClient() - } + }*/ } async initEditor() { @@ -197,7 +195,7 @@ class CadenceEditor extends React.Component< this.editor.focus(); } - private async loadLanguageClient() { +/* private async loadLanguageClient() { this.callbacks = this.props.callbacks; const clientId = this.props.activeId; if(!this.clients[clientId]){ @@ -218,8 +216,7 @@ class CadenceEditor extends React.Component< } else { this.languageClient = this.clients[clientId] } - - } + }*/ private async getParameters() { await this.languageClient.onReady(); @@ -321,6 +318,9 @@ class CadenceEditor extends React.Component< await this.swapMonacoEditor(prevProps.activeId, this.props.activeId) } + + // TODO: refactor + /* const serverStatusChanged = this.props.serverReady !== prevProps.serverReady const activeIdChanged = this.props.activeId !== prevProps.activeId const typeChanged = this.props.type !== prevProps.type @@ -329,7 +329,7 @@ class CadenceEditor extends React.Component< if (this.props.callbacks.toServer !== null) { await this.loadLanguageClient() } - } + }*/ } async swapMonacoEditor(prev: any, current: any) { diff --git a/src/components/MonacoEditor/components.tsx b/src/components/MonacoEditor/components.tsx new file mode 100644 index 00000000..cf9da444 --- /dev/null +++ b/src/components/MonacoEditor/components.tsx @@ -0,0 +1,76 @@ +import styled from '@emotion/styled'; +import { keyframes } from '@emotion/core'; + +const blink = keyframes` + 50% { + opacity: 0.5; + } +`; + +export const EditorContainer = styled.div` + width: 100%; + height: 100%; + position: relative; + + .drag-box { + width: fit-content; + height: fit-content; + position: absolute; + right: 30px; + top: 0; + z-index: 12; + } + + .constraints { + width: 96vw; + height: 90vh; + position: fixed; + left: 2vw; + right: 2vw; + top: 2vw; + bottom: 2vw; + pointer-events: none; + } + + .playground-syntax-error-hover { + background-color: rgba(238, 67, 30, 0.1); + } + + .playground-syntax-error-hover-selection { + background-color: rgba(238, 67, 30, 0.3); + border-radius: 3px; + animation: ${blink} 1s ease-in-out infinite; + } + + .playground-syntax-warning-hover { + background-color: rgb(238, 169, 30, 0.1); + } + + .playground-syntax-warning-hover-selection { + background-color: rgb(238, 169, 30, 0.3); + border-radius: 3px; + animation: ${blink} 1s ease-in-out infinite; + } + + .playground-syntax-info-hover { + background-color: rgb(85, 238, 30, 0.1); + } + + .playground-syntax-info-hover-selection { + background-color: rgb(85, 238, 30, 0.3); + border-radius: 3px; + animation: ${blink} 1s ease-in-out infinite; + } + + .playground-syntax-hint-hover, + .playground-syntax-unknown-hover { + background-color: rgb(160, 160, 160, 0.1); + } + + .playground-syntax-hint-hover-selection, + .playground-syntax-unknown-hover-selection { + background-color: rgb(160, 160, 160, 0.3); + border-radius: 3px; + animation: ${blink} 1s ease-in-out infinite; + } +`; diff --git a/src/components/MonacoEditor/index.tsx b/src/components/MonacoEditor/index.tsx new file mode 100644 index 00000000..4d6edef7 --- /dev/null +++ b/src/components/MonacoEditor/index.tsx @@ -0,0 +1,154 @@ +import React, { useState, useEffect, useRef } from 'react'; +import * as monaco from 'monaco-editor'; +import configureCadence, { CADENCE_LANGUAGE_ID } from 'util/cadence'; +import { EditorContainer } from './components'; +import { useProject } from 'providers/Project/projectHooks'; +import useLanguageServer from '../../hooks/useLanguageServer'; + +const MONACO_CONTAINER_ID = 'monaco-container'; + +type EditorState = { + model: any; + viewState: any; +}; + +const EnhancedEditor = (props: any) => { + const project = useProject(); + + const cadenceInitiated = useRef(false); + const [editor, setEditor] = useState(null); + + const { languageClient } = useLanguageServer({ + getCode: (index) => { + console.log('Requesting code for: ', { index }); + }, + }); + + const [editorStates, setEditorStates] = useState({}); + + const getOrCreateEditorState = (id: string, code: string): EditorState => { + const existingState = editorStates[id]; + + if (existingState !== undefined) { + return existingState; + } + + const newState = { + model: monaco.editor.createModel(code, CADENCE_LANGUAGE_ID), + viewState: null, + }; + + setEditorStates({ + ...editorStates, + [id]: newState, + }); + + return newState; + }; + + const getActiveCode = () => { + const { active } = project; + const { accounts, scriptTemplates, transactionTemplates } = project.project; + + const { type, index } = active; + let code, id; + switch (type) { + case 1: + code = accounts[index].draftCode; + id = accounts[index].id; + break; + case 2: + code = transactionTemplates[index].script; + id = transactionTemplates[index].id + break; + case 3: + code = scriptTemplates[index].script; + id = scriptTemplates[index].id + break; + default: + code = 'not support type'; + } + return [code, id]; + }; + + // TODO: tie-in Monaco with project updates + // TODO: tie-in language-client server with project updates + // TODO: delay language client checks + // TODO: add manual request to trigger check + + useEffect(() => { + if (cadenceInitiated.current === false) { + console.log('Init Cadence'); + // Step1 - Configure global monaco to support Cadence + configureCadence(); + cadenceInitiated.current = true; + } else { + console.log('No need :)'); + } + }, []); + + useEffect(() => { + if(editor){ + const [code, newId] = getActiveCode(); + const newState = getOrCreateEditorState(newId, code); + + editor.setModel(newState.model); + editor.restoreViewState(newState.viewState); + editor.focus(); + } + }, [project.active]); + + const destroyEditor = () => { + editor.dispose(); + }; + + const initEditor = async () => { + const container = document.getElementById(MONACO_CONTAINER_ID); + const editor = monaco.editor.create(container, { + theme: 'vs-light', + language: CADENCE_LANGUAGE_ID, + minimap: { + enabled: false, + }, + }); + + setEditor(editor); + + editor.onDidChangeModelContent((event: any) => { + console.log(editor.getValue()); + console.log({ event }); + }); + + const model = monaco.editor.createModel( + 'hello, world', + CADENCE_LANGUAGE_ID, + ); + const state: EditorState = { + model, + viewState: null, + }; + editor.setModel(state.model); + editor.restoreViewState(state.viewState); + editor.focus(); + + window.addEventListener('resize', () => { + console.log('update'); + editor && editor.layout(); + }); + }; + + useEffect(() => { + // Drop returned Promise + initEditor().then(); + + return () => { + if (editor) { + destroyEditor(); + } + }; + }, []); + + return ; +}; + +export default EnhancedEditor; diff --git a/src/containers/Editor/components.tsx b/src/containers/Editor/components.tsx index 9589dc37..59f48bc3 100644 --- a/src/containers/Editor/components.tsx +++ b/src/containers/Editor/components.tsx @@ -17,7 +17,7 @@ import debounce from 'util/debounce'; import Mixpanel from 'util/mixpanel'; import { default as FlowButton } from 'components/Button'; -import CadenceEditor from 'components/CadenceEditor'; +// import CadenceEditor from 'components/CadenceEditor'; import TransactionBottomBar from 'components/TransactionBottomBar'; import ScriptBottomBar from 'components/ScriptBottomBar'; import { Version } from 'components/CadenceVersion'; @@ -38,8 +38,7 @@ import { } from './layout-components' import { decodeText } from "util/readme"; -import * as monaco from "monaco-editor"; -import useLanguageServer from "../../hooks/useLanguageServer" +import EnhancedEditor from "components/MonacoEditor" export interface WithShowProps { show: boolean; @@ -243,10 +242,6 @@ const EditorContainer: React.FC = ({ return code } - const [languageClient, languageServer, initialized, reloadServer] = useLanguageServer({ - getCode - }); - const previousProjectState = usePrevious(project) // This hook will listen for project updates and if one of the contracts has been changed, @@ -257,7 +252,7 @@ const EditorContainer: React.FC = ({ const previousAccounts = previousProjectState.accounts || [] const equal = compareContracts(previousAccounts, project.accounts) if (!equal){ - reloadServer() + // reloadServer() } } }, [project]) @@ -341,16 +336,16 @@ const EditorContainer: React.FC = ({ )} {/* This is Cadence Editor */} - onEditorChange(code)} show={!isReadmeEditor} - languageServer={languageServer} - callbacks={serverCallbacks} - serverReady={serverReady} + />*/} + diff --git a/src/hooks/useLanguageServer.ts b/src/hooks/useLanguageServer.ts index 43db9073..2c7c1f07 100644 --- a/src/hooks/useLanguageServer.ts +++ b/src/hooks/useLanguageServer.ts @@ -20,7 +20,6 @@ async function startLanguageServer(serverProps: any, setServer: any) { } }, 100); }); - setServer(server); } export default function useLanguageServer(props: any) { @@ -54,6 +53,7 @@ export default function useLanguageServer(props: any) { // TODO: Clean global variables, so we could ensure there is no duplication of events and messages startLanguageServer({ callbacks, getCode }, setLanguageServer); }; + useEffect(() => { // The Monaco Language Client services have to be installed globally, once. // An editor must be passed, which is only used for commands. @@ -68,5 +68,13 @@ export default function useLanguageServer(props: any) { restartServer(); }, []); - return [languageClient, languageServer, initialized, restartServer]; + useEffect(()=>{ + if(!languageServer){ + console.log("language server is not ready. waiting...") + } else { + console.log('language server is up. initiate client!') + } + },[languageServer]) + + return {languageClient, languageServer, initialized, restartServer}; } diff --git a/tsconfig.json b/tsconfig.json index f2802fda..6b98cb35 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,8 +7,8 @@ "allowJs": false, "moduleResolution": "node", "allowSyntheticDefaultImports": true, - "noImplicitAny": true, /* + "noImplicitAny": false, "noUnusedLocals": true, "noUnusedParameters": true, */ From eadcecdcce74823691f6403a1262bf07b52d2466 Mon Sep 17 00:00:00 2001 From: Max Daunarovich Date: Thu, 24 Mar 2022 17:43:48 +0300 Subject: [PATCH 06/49] Update language server hook --- src/hooks/useLanguageServer.ts | 60 ++++++++++++++++++++++++++-------- 1 file changed, 46 insertions(+), 14 deletions(-) diff --git a/src/hooks/useLanguageServer.ts b/src/hooks/useLanguageServer.ts index 2c7c1f07..91f4b9f0 100644 --- a/src/hooks/useLanguageServer.ts +++ b/src/hooks/useLanguageServer.ts @@ -1,12 +1,13 @@ -import { useEffect, useState } from 'react'; +import { useEffect, useState, useRef } from 'react'; import { CadenceLanguageServer, Callbacks } from 'util/language-server'; import { MonacoServices } from 'monaco-languageclient/lib/monaco-services'; import * as monaco from 'monaco-editor'; +import { createCadenceLanguageClient } from 'util/language-client'; let monacoServicesInstalled = false; -async function startLanguageServer(serverProps: any, setServer: any) { - const { callbacks, getCode } = serverProps; +async function startLanguageServer(callbacks: any, getCode: any, ops) { + const { setLanguageServer, setCallbacks } = ops; const server = await CadenceLanguageServer.create(callbacks); new Promise((resolve, reject) => { @@ -14,17 +15,37 @@ async function startLanguageServer(serverProps: any, setServer: any) { // .toServer() method is populated by language server // if it was not properly started or in progress it will be "null" if (callbacks.toServer !== null) { + console.log(callbacks.toServer); clearInterval(checkInterval); callbacks.getAddressCode = getCode; - setServer(server); + setCallbacks(callbacks); + setLanguageServer(server); } }, 100); }); } +const launchLanguageClient = async ( + callbacks, + languageServer, + setLanguageClient, +) => { + if (!languageServer) { + console.log('language server is not ready. waiting...'); + } else { + console.log('language server is up. initiate client!'); + console.log(callbacks); + const newClient = createCadenceLanguageClient(callbacks); + newClient.start(); + await newClient.onReady(); + console.log('language client is up!'); + setLanguageClient(newClient); + } +}; + export default function useLanguageServer(props: any) { // Language Server Callbacks - let callbacks: Callbacks = { + let initialCallbacks: Callbacks = { // The actual callback will be set as soon as the language server is initialized toServer: null, @@ -45,13 +66,21 @@ export default function useLanguageServer(props: any) { // Base state handler const [languageServer, setLanguageServer] = useState(null); + const [languageServerOpen, setLanguageServerOpen] = useState(false); + const [languageClient, setLanguageClient] = useState(null); + const [languageClientOpen, setLanguageClientOpen] = useState(false); + const [initialized, setInitialized] = useState(false); + const [callbacks, setCallbacks] = useState(initialCallbacks); const { getCode } = props; const restartServer = () => { // TODO: Clean global variables, so we could ensure there is no duplication of events and messages - startLanguageServer({ callbacks, getCode }, setLanguageServer); + startLanguageServer(callbacks, getCode, { + setLanguageServer, + setCallbacks, + }); }; useEffect(() => { @@ -68,13 +97,16 @@ export default function useLanguageServer(props: any) { restartServer(); }, []); - useEffect(()=>{ - if(!languageServer){ - console.log("language server is not ready. waiting...") - } else { - console.log('language server is up. initiate client!') - } - },[languageServer]) + useEffect(() => { + launchLanguageClient(callbacks, languageServer, setLanguageClient).then(); + }, [languageServer]); - return {languageClient, languageServer, initialized, restartServer}; + return { + languageClient, + languageClientOpen, + languageServer, + languageServerOpen, + initialized, + restartServer, + }; } From 355e80eacf545b59826a0b575ccf00c4f79b3859 Mon Sep 17 00:00:00 2001 From: Max Daunarovich Date: Thu, 24 Mar 2022 17:44:08 +0300 Subject: [PATCH 07/49] Create CadenceChecker context provider --- src/providers/CadenceChecker/index.tsx | 36 ++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 src/providers/CadenceChecker/index.tsx diff --git a/src/providers/CadenceChecker/index.tsx b/src/providers/CadenceChecker/index.tsx new file mode 100644 index 00000000..556b86f8 --- /dev/null +++ b/src/providers/CadenceChecker/index.tsx @@ -0,0 +1,36 @@ +import React, { createContext } from 'react'; +import useLanguageServer from '../../hooks/useLanguageServer'; +import { useProject } from 'providers/Project/projectHooks'; + +export const CadenceCheckerContext: React.Context = createContext(null); + +export default function CadenceChecker(props) { + const project = useProject(); + + // Connect project to cadence checker hook + const cadenceChecker = useLanguageServer({ + getCode: (address) => { + const { accounts } = project.project; + + const number = parseInt(address, 16); + if (!number) { + return; + } + + const index = number - 1; + if (index < 0 || index >= accounts.length) { + return; + } + let code = accounts[index].draftCode; + return code; + }, + }); + + // render + const { children } = props; + return ( + + {children} + + ); +} From a1a9baeffd431fc9f1e34a21c51f195bbeac22d6 Mon Sep 17 00:00:00 2001 From: Max Daunarovich Date: Thu, 24 Mar 2022 18:18:04 +0300 Subject: [PATCH 08/49] Add cadence checker to context --- src/containers/Editor/index.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/containers/Editor/index.tsx b/src/containers/Editor/index.tsx index af2d4dba..47bdd4ff 100644 --- a/src/containers/Editor/index.tsx +++ b/src/containers/Editor/index.tsx @@ -1,9 +1,12 @@ import React from 'react'; +import { Redirect } from '@reach/router'; + import { ProjectProvider } from 'providers/Project'; +import CadenceChecker from 'providers/CadenceChecker'; + import EditorLayout from './layout'; import { Base } from 'layout/Base'; import { LOCAL_PROJECT_ID } from 'util/url'; -import { Redirect} from '@reach/router'; const Playground: any = (props: any) => { const { projectId } = props; @@ -16,7 +19,9 @@ const Playground: any = (props: any) => { return ( - + + + ); From bc2cd338c47c1d83ae9aa535c047094e6b5418d6 Mon Sep 17 00:00:00 2001 From: Max Daunarovich Date: Thu, 24 Mar 2022 18:18:17 +0300 Subject: [PATCH 09/49] Remove getCode dependency --- src/providers/CadenceChecker/index.tsx | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/src/providers/CadenceChecker/index.tsx b/src/providers/CadenceChecker/index.tsx index 556b86f8..308d5dd9 100644 --- a/src/providers/CadenceChecker/index.tsx +++ b/src/providers/CadenceChecker/index.tsx @@ -1,30 +1,11 @@ import React, { createContext } from 'react'; import useLanguageServer from '../../hooks/useLanguageServer'; -import { useProject } from 'providers/Project/projectHooks'; export const CadenceCheckerContext: React.Context = createContext(null); export default function CadenceChecker(props) { - const project = useProject(); - // Connect project to cadence checker hook - const cadenceChecker = useLanguageServer({ - getCode: (address) => { - const { accounts } = project.project; - - const number = parseInt(address, 16); - if (!number) { - return; - } - - const index = number - 1; - if (index < 0 || index >= accounts.length) { - return; - } - let code = accounts[index].draftCode; - return code; - }, - }); + const cadenceChecker = useLanguageServer(); // render const { children } = props; From adfb096aacb8478592c10f835df9b5f02870e6b6 Mon Sep 17 00:00:00 2001 From: Max Daunarovich Date: Thu, 24 Mar 2022 18:18:44 +0300 Subject: [PATCH 10/49] Use ref for monaco editor to prevent destroying it --- src/components/MonacoEditor/index.tsx | 53 +++++++++++++-------------- 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/src/components/MonacoEditor/index.tsx b/src/components/MonacoEditor/index.tsx index 4d6edef7..3a59c632 100644 --- a/src/components/MonacoEditor/index.tsx +++ b/src/components/MonacoEditor/index.tsx @@ -1,9 +1,8 @@ -import React, { useState, useEffect, useRef } from 'react'; +import React, { useState, useEffect, useRef, useContext } from 'react'; import * as monaco from 'monaco-editor'; import configureCadence, { CADENCE_LANGUAGE_ID } from 'util/cadence'; import { EditorContainer } from './components'; import { useProject } from 'providers/Project/projectHooks'; -import useLanguageServer from '../../hooks/useLanguageServer'; const MONACO_CONTAINER_ID = 'monaco-container'; @@ -16,13 +15,7 @@ const EnhancedEditor = (props: any) => { const project = useProject(); const cadenceInitiated = useRef(false); - const [editor, setEditor] = useState(null); - - const { languageClient } = useLanguageServer({ - getCode: (index) => { - console.log('Requesting code for: ', { index }); - }, - }); + const editor = useRef(null); const [editorStates, setEditorStates] = useState({}); @@ -45,7 +38,6 @@ const EnhancedEditor = (props: any) => { return newState; }; - const getActiveCode = () => { const { active } = project; const { accounts, scriptTemplates, transactionTemplates } = project.project; @@ -59,17 +51,25 @@ const EnhancedEditor = (props: any) => { break; case 2: code = transactionTemplates[index].script; - id = transactionTemplates[index].id + id = transactionTemplates[index].id; break; case 3: code = scriptTemplates[index].script; - id = scriptTemplates[index].id + id = scriptTemplates[index].id; break; default: code = 'not support type'; } return [code, id]; }; + const updateActiveCode = (event) => { + if (editor) { + // TODO: delay updates a bit... + const currentValue = editor.current.getValue(); + const { updateAccountDraftCode } = project; + updateAccountDraftCode(currentValue).then(); + } + }; // TODO: tie-in Monaco with project updates // TODO: tie-in language-client server with project updates @@ -88,23 +88,23 @@ const EnhancedEditor = (props: any) => { }, []); useEffect(() => { - if(editor){ + if (editor.current) { const [code, newId] = getActiveCode(); const newState = getOrCreateEditorState(newId, code); - editor.setModel(newState.model); - editor.restoreViewState(newState.viewState); - editor.focus(); + editor.current.setModel(newState.model); + editor.current.restoreViewState(newState.viewState); + editor.current.focus(); } }, [project.active]); const destroyEditor = () => { - editor.dispose(); + editor.current.dispose(); }; const initEditor = async () => { const container = document.getElementById(MONACO_CONTAINER_ID); - const editor = monaco.editor.create(container, { + editor.current = monaco.editor.create(container, { theme: 'vs-light', language: CADENCE_LANGUAGE_ID, minimap: { @@ -112,11 +112,10 @@ const EnhancedEditor = (props: any) => { }, }); - setEditor(editor); - - editor.onDidChangeModelContent((event: any) => { - console.log(editor.getValue()); - console.log({ event }); + // Setup even listener when code is updated + editor.current.onDidChangeModelContent((event) => { + console.log('update', event); + updateActiveCode(event); }); const model = monaco.editor.createModel( @@ -127,13 +126,13 @@ const EnhancedEditor = (props: any) => { model, viewState: null, }; - editor.setModel(state.model); - editor.restoreViewState(state.viewState); - editor.focus(); + editor.current.setModel(state.model); + editor.current.restoreViewState(state.viewState); + editor.current.focus(); window.addEventListener('resize', () => { console.log('update'); - editor && editor.layout(); + editor && editor.current.layout(); }); }; From 46b7a2a49c31d66caf015f92eee0663341e28136 Mon Sep 17 00:00:00 2001 From: Max Daunarovich Date: Thu, 24 Mar 2022 18:19:07 +0300 Subject: [PATCH 11/49] Debug failing code --- src/hooks/useLanguageServer.ts | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/src/hooks/useLanguageServer.ts b/src/hooks/useLanguageServer.ts index 91f4b9f0..a4c3f85e 100644 --- a/src/hooks/useLanguageServer.ts +++ b/src/hooks/useLanguageServer.ts @@ -3,6 +3,7 @@ import { CadenceLanguageServer, Callbacks } from 'util/language-server'; import { MonacoServices } from 'monaco-languageclient/lib/monaco-services'; import * as monaco from 'monaco-editor'; import { createCadenceLanguageClient } from 'util/language-client'; +import {useProject} from "providers/Project/projectHooks"; let monacoServicesInstalled = false; @@ -43,7 +44,10 @@ const launchLanguageClient = async ( } }; -export default function useLanguageServer(props: any) { +export default function useLanguageServer() { + const project = useProject(); + const accountUpdates = useRef(1) + // Language Server Callbacks let initialCallbacks: Callbacks = { // The actual callback will be set as soon as the language server is initialized @@ -74,7 +78,25 @@ export default function useLanguageServer(props: any) { const [initialized, setInitialized] = useState(false); const [callbacks, setCallbacks] = useState(initialCallbacks); - const { getCode } = props; + const getCode = (address) => { + console.log(`Version ${accountUpdates.current}`) + const { accounts } = project.project; + + const number = parseInt(address, 16); + if (!number) { + return; + } + + const index = number - 1; + if (index < 0 || index >= accounts.length) { + return; + } + let code = accounts[index].draftCode; + + console.log(code) + return code; + }; + const restartServer = () => { // TODO: Clean global variables, so we could ensure there is no duplication of events and messages startLanguageServer(callbacks, getCode, { @@ -83,6 +105,12 @@ export default function useLanguageServer(props: any) { }); }; + useEffect(()=>{ + accountUpdates.current += 1; + console.log(accountUpdates.current) + callbacks.getAddressCode = getCode; + },[project.project.accounts]) + useEffect(() => { // The Monaco Language Client services have to be installed globally, once. // An editor must be passed, which is only used for commands. From b3d41e1ab4d5beaf764bc7a871362f8f85457052 Mon Sep 17 00:00:00 2001 From: Max Daunarovich Date: Thu, 24 Mar 2022 18:23:13 +0300 Subject: [PATCH 12/49] More debug --- src/hooks/useLanguageServer.ts | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/hooks/useLanguageServer.ts b/src/hooks/useLanguageServer.ts index a4c3f85e..3fde982b 100644 --- a/src/hooks/useLanguageServer.ts +++ b/src/hooks/useLanguageServer.ts @@ -3,7 +3,7 @@ import { CadenceLanguageServer, Callbacks } from 'util/language-server'; import { MonacoServices } from 'monaco-languageclient/lib/monaco-services'; import * as monaco from 'monaco-editor'; import { createCadenceLanguageClient } from 'util/language-client'; -import {useProject} from "providers/Project/projectHooks"; +import { useProject } from 'providers/Project/projectHooks'; let monacoServicesInstalled = false; @@ -46,7 +46,8 @@ const launchLanguageClient = async ( export default function useLanguageServer() { const project = useProject(); - const accountUpdates = useRef(1) + window.project = project + const accountUpdates = useRef(1); // Language Server Callbacks let initialCallbacks: Callbacks = { @@ -79,7 +80,7 @@ export default function useLanguageServer() { const [callbacks, setCallbacks] = useState(initialCallbacks); const getCode = (address) => { - console.log(`Version ${accountUpdates.current}`) + console.log(`Version ${accountUpdates.current}`); const { accounts } = project.project; const number = parseInt(address, 16); @@ -92,8 +93,8 @@ export default function useLanguageServer() { return; } let code = accounts[index].draftCode; - - console.log(code) + + console.log(code); return code; }; @@ -105,11 +106,14 @@ export default function useLanguageServer() { }); }; - useEffect(()=>{ + useEffect(() => { accountUpdates.current += 1; - console.log(accountUpdates.current) - callbacks.getAddressCode = getCode; - },[project.project.accounts]) + console.log(accountUpdates.current); + setCallbacks({ + ...callbacks, + getAddressCode: getCode, + }); + }, [project.project.accounts]); useEffect(() => { // The Monaco Language Client services have to be installed globally, once. From 6c34350f54b2e611fec90014421cd50477ee4562 Mon Sep 17 00:00:00 2001 From: Max Daunarovich Date: Mon, 28 Mar 2022 20:28:21 +0300 Subject: [PATCH 13/49] Debug errors --- src/hooks/useLanguageServer.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/hooks/useLanguageServer.ts b/src/hooks/useLanguageServer.ts index 3fde982b..2511f9be 100644 --- a/src/hooks/useLanguageServer.ts +++ b/src/hooks/useLanguageServer.ts @@ -16,7 +16,7 @@ async function startLanguageServer(callbacks: any, getCode: any, ops) { // .toServer() method is populated by language server // if it was not properly started or in progress it will be "null" if (callbacks.toServer !== null) { - console.log(callbacks.toServer); +// console.log(callbacks.toServer); clearInterval(checkInterval); callbacks.getAddressCode = getCode; setCallbacks(callbacks); @@ -99,6 +99,7 @@ export default function useLanguageServer() { }; const restartServer = () => { + console.log("%c LS: Restarting",'color: #9727e9') // TODO: Clean global variables, so we could ensure there is no duplication of events and messages startLanguageServer(callbacks, getCode, { setLanguageServer, @@ -109,10 +110,15 @@ export default function useLanguageServer() { useEffect(() => { accountUpdates.current += 1; console.log(accountUpdates.current); - setCallbacks({ +/* setCallbacks({ ...callbacks, getAddressCode: getCode, - }); + });*/ +/* if(languageClient){ + languageClient.stop() + }*/ + // launchLanguageClient(callbacks, languageServer, setLanguageClient).then(); + restartServer() }, [project.project.accounts]); useEffect(() => { @@ -130,7 +136,8 @@ export default function useLanguageServer() { }, []); useEffect(() => { - launchLanguageClient(callbacks, languageServer, setLanguageClient).then(); + // TODO: postpone with this update + // launchLanguageClient(callbacks, languageServer, setLanguageClient).then(); }, [languageServer]); return { From 603352856325893302dd3f7b264c0f3a55aaf1d6 Mon Sep 17 00:00:00 2001 From: Max Daunarovich Date: Tue, 29 Mar 2022 20:20:37 +0300 Subject: [PATCH 14/49] Trying to find update bug --- src/components/MonacoEditor/index.tsx | 43 ++++++++++++++++++++------- src/containers/Editor/components.tsx | 4 +-- src/hooks/useLanguageServer.ts | 6 ++-- 3 files changed, 37 insertions(+), 16 deletions(-) diff --git a/src/components/MonacoEditor/index.tsx b/src/components/MonacoEditor/index.tsx index 3a59c632..6b4e998b 100644 --- a/src/components/MonacoEditor/index.tsx +++ b/src/components/MonacoEditor/index.tsx @@ -1,8 +1,9 @@ -import React, { useState, useEffect, useRef, useContext } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; import * as monaco from 'monaco-editor'; import configureCadence, { CADENCE_LANGUAGE_ID } from 'util/cadence'; import { EditorContainer } from './components'; import { useProject } from 'providers/Project/projectHooks'; +import { EntityType } from 'providers/Project'; const MONACO_CONTAINER_ID = 'monaco-container'; @@ -11,8 +12,38 @@ type EditorState = { viewState: any; }; +const updateActiveCode = (editor, project, event) => { + console.log(project.active); + + if (editor) { + // TODO: delay updates a bit... + const currentValue = editor.getValue(); + const { active } = project; + + console.log('+++++++++++++++++++++++++', project.active); + + // TODO: finish transactions + switch (active.type) { + case EntityType.Account: + console.log('Update account'); + const { updateAccountDraftCode } = project; + updateAccountDraftCode(currentValue).then(); + break; + case EntityType.ScriptTemplate: + console.log('Update script'); + const { updateScriptTemplate } = project; + const { id } = project.project.scriptTemplates[active.index]; + updateScriptTemplate(id, currentValue, '---').then(); + break; + default: + break; + } + } +}; + const EnhancedEditor = (props: any) => { const project = useProject(); + console.log('------------------------>', project.active); const cadenceInitiated = useRef(false); const editor = useRef(null); @@ -62,14 +93,6 @@ const EnhancedEditor = (props: any) => { } return [code, id]; }; - const updateActiveCode = (event) => { - if (editor) { - // TODO: delay updates a bit... - const currentValue = editor.current.getValue(); - const { updateAccountDraftCode } = project; - updateAccountDraftCode(currentValue).then(); - } - }; // TODO: tie-in Monaco with project updates // TODO: tie-in language-client server with project updates @@ -115,7 +138,7 @@ const EnhancedEditor = (props: any) => { // Setup even listener when code is updated editor.current.onDidChangeModelContent((event) => { console.log('update', event); - updateActiveCode(event); + updateActiveCode(editor.current, project, event); }); const model = monaco.editor.createModel( diff --git a/src/containers/Editor/components.tsx b/src/containers/Editor/components.tsx index 59f48bc3..0bf13940 100644 --- a/src/containers/Editor/components.tsx +++ b/src/containers/Editor/components.tsx @@ -344,9 +344,7 @@ const EditorContainer: React.FC = ({ onChange={(code: string, _: any) => onEditorChange(code)} show={!isReadmeEditor} />*/} - + diff --git a/src/hooks/useLanguageServer.ts b/src/hooks/useLanguageServer.ts index 2511f9be..b478e6c0 100644 --- a/src/hooks/useLanguageServer.ts +++ b/src/hooks/useLanguageServer.ts @@ -99,7 +99,7 @@ export default function useLanguageServer() { }; const restartServer = () => { - console.log("%c LS: Restarting",'color: #9727e9') + console.log("%c LS: Starting...",'color: #9727e9') // TODO: Clean global variables, so we could ensure there is no duplication of events and messages startLanguageServer(callbacks, getCode, { setLanguageServer, @@ -118,7 +118,7 @@ export default function useLanguageServer() { languageClient.stop() }*/ // launchLanguageClient(callbacks, languageServer, setLanguageClient).then(); - restartServer() + // restartServer() }, [project.project.accounts]); useEffect(() => { @@ -137,7 +137,7 @@ export default function useLanguageServer() { useEffect(() => { // TODO: postpone with this update - // launchLanguageClient(callbacks, languageServer, setLanguageClient).then(); + launchLanguageClient(callbacks, languageServer, setLanguageClient).then(); }, [languageServer]); return { From d0ffe47ee3ad8ea7a036686745189467243cc036 Mon Sep 17 00:00:00 2001 From: Max Daunarovich Date: Wed, 30 Mar 2022 18:27:27 +0300 Subject: [PATCH 15/49] Update packages --- babel.config.js | 14 +++--- package-lock.json | 117 ++++++++++++++++++++++------------------------ package.json | 6 +-- 3 files changed, 66 insertions(+), 71 deletions(-) diff --git a/babel.config.js b/babel.config.js index 0d543298..db2a9c9d 100644 --- a/babel.config.js +++ b/babel.config.js @@ -1,7 +1,7 @@ -// babel.config.js -module.exports = { - presets: [ - ['@babel/preset-env', {targets: {node: 'current'}}], - '@babel/preset-typescript' - ] -}; \ No newline at end of file +// babel.config.js +module.exports = { + presets: [ + ['@babel/preset-env', { targets: { node: 'current' } }], + '@babel/preset-typescript', + ], +}; diff --git a/package-lock.json b/package-lock.json index 3d32e6bf..ee1ae48c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,8 +26,8 @@ "jszip": "^3.5.0", "markdown-to-jsx": "^7.1.3", "mixpanel-browser": "^2.39.0", - "monaco-editor": "^0.22.3", - "monaco-languageclient": "^0.13.1-next.9", + "monaco-editor": "^0.33.0", + "monaco-languageclient": "^0.18.1", "react": "^16.13.1", "react-dom": "^16.13.1", "react-ga": "^3.1.2", @@ -72,7 +72,7 @@ "handlebars": "^4.7.6", "html-webpack-plugin": "^4.4.1", "jest": "^26.6.3", - "monaco-editor-webpack-plugin": "^3.0.0", + "monaco-editor-webpack-plugin": "^7.0.1", "prettier": "^2.2.1", "style-loader": "^1.2.1", "ts-loader": "^8.0.3", @@ -15604,27 +15604,28 @@ } }, "node_modules/monaco-editor": { - "version": "0.22.3", - "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.22.3.tgz", - "integrity": "sha512-RM559z2CJbczZ3k2b+ouacMINkAYWwRit4/vs0g2X/lkYefDiu0k2GmgWjAuiIpQi+AqASPOKvXNmYc8KUSvVQ==" + "version": "0.33.0", + "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.33.0.tgz", + "integrity": "sha512-VcRWPSLIUEgQJQIE0pVT8FcGBIgFoxz7jtqctE+IiCxWugD0DwgyQBcZBhdSrdMC84eumoqMZsGl2GTreOzwqw==" }, "node_modules/monaco-editor-webpack-plugin": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/monaco-editor-webpack-plugin/-/monaco-editor-webpack-plugin-3.1.0.tgz", - "integrity": "sha512-TP5NkCAV0OeFTry5k/d60KR7CkhTXL4kgJKtE3BzjgbDb5TGEPEhoKmHBrSa6r7Oc0sNbPLZhKD/TP2ig7A+/A==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/monaco-editor-webpack-plugin/-/monaco-editor-webpack-plugin-7.0.1.tgz", + "integrity": "sha512-M8qIqizltrPlIbrb73cZdTWfU9sIsUVFvAZkL3KGjAHmVWEJ0hZKa/uad14JuOckc0GwnCaoGHvMoYtJjVyCzw==", "dev": true, "dependencies": { - "loader-utils": "^2.0.0" + "loader-utils": "^2.0.2" + }, + "peerDependencies": { + "monaco-editor": ">= 0.31.0", + "webpack": "^4.5.0 || 5.x" } }, "node_modules/monaco-editor-webpack-plugin/node_modules/json5": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", - "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", + "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", "dev": true, - "dependencies": { - "minimist": "^1.2.5" - }, "bin": { "json5": "lib/cli.js" }, @@ -15633,9 +15634,9 @@ } }, "node_modules/monaco-editor-webpack-plugin/node_modules/loader-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", - "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.2.tgz", + "integrity": "sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==", "dev": true, "dependencies": { "big.js": "^5.2.2", @@ -15647,18 +15648,15 @@ } }, "node_modules/monaco-languageclient": { - "version": "0.13.1-next.9", - "resolved": "https://registry.npmjs.org/monaco-languageclient/-/monaco-languageclient-0.13.1-next.9.tgz", - "integrity": "sha512-VXzrrlB7RbpvrsgDJw8jmgGqSoFnAWQTOgwwIop7D25O9GB4A0HriyiyTumeOd7JJAQRLyserzSbfrRlBDD+aQ==", + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/monaco-languageclient/-/monaco-languageclient-0.18.1.tgz", + "integrity": "sha512-8x5YbYhtnS5xZ6+Xkk6lJcuMxLdKmc8Y7flW/u5MmFm5/qbe+uUV5vsmqPjcbJy6gAJF4pjFd0PtfhTtOiaxnw==", "dependencies": { "glob-to-regexp": "^0.4.1", "vscode-jsonrpc": "6.0.0", "vscode-languageclient": "7.0.0", - "vscode-languageserver-textdocument": "^1.0.1", - "vscode-uri": "^3.0.2" - }, - "engines": { - "vscode": "^1.50.0" + "vscode-languageserver-textdocument": "^1.0.4", + "vscode-uri": "^3.0.3" } }, "node_modules/move-concurrently": { @@ -20253,9 +20251,9 @@ } }, "node_modules/vscode-languageserver-textdocument": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.1.tgz", - "integrity": "sha512-UIcJDjX7IFkck7cSkNNyzIz5FyvpQfY7sdzVy+wkKN/BLaD4DQ0ppXQrKePomCxTS7RrolK1I0pey0bG9eh8dA==" + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.4.tgz", + "integrity": "sha512-/xhqXP/2A2RSs+J8JNXpiiNVvvNM0oTosNVmQnunlKvq9o4mupHOBAnnzH0lwIPKazXKvAKsVp1kr+H/K4lgoQ==" }, "node_modules/vscode-languageserver-types": { "version": "3.16.0", @@ -20263,9 +20261,9 @@ "integrity": "sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA==" }, "node_modules/vscode-uri": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.2.tgz", - "integrity": "sha512-jkjy6pjU1fxUvI51P+gCsxg1u2n8LSt0W6KrCNQceaziKzff74GoWmjVG46KieVzybO1sttPQmYfrwSHey7GUA==" + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.3.tgz", + "integrity": "sha512-EcswR2S8bpR7fD0YPeS7r2xXExrScVMxg4MedACaWHEtx9ftCF/qHG1xGkolzTPcEmjTavCQgbVzHUIdTMzFGA==" }, "node_modules/vue-template-compiler": { "version": "2.6.12", @@ -34710,32 +34708,29 @@ } }, "monaco-editor": { - "version": "0.22.3", - "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.22.3.tgz", - "integrity": "sha512-RM559z2CJbczZ3k2b+ouacMINkAYWwRit4/vs0g2X/lkYefDiu0k2GmgWjAuiIpQi+AqASPOKvXNmYc8KUSvVQ==" + "version": "0.33.0", + "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.33.0.tgz", + "integrity": "sha512-VcRWPSLIUEgQJQIE0pVT8FcGBIgFoxz7jtqctE+IiCxWugD0DwgyQBcZBhdSrdMC84eumoqMZsGl2GTreOzwqw==" }, "monaco-editor-webpack-plugin": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/monaco-editor-webpack-plugin/-/monaco-editor-webpack-plugin-3.1.0.tgz", - "integrity": "sha512-TP5NkCAV0OeFTry5k/d60KR7CkhTXL4kgJKtE3BzjgbDb5TGEPEhoKmHBrSa6r7Oc0sNbPLZhKD/TP2ig7A+/A==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/monaco-editor-webpack-plugin/-/monaco-editor-webpack-plugin-7.0.1.tgz", + "integrity": "sha512-M8qIqizltrPlIbrb73cZdTWfU9sIsUVFvAZkL3KGjAHmVWEJ0hZKa/uad14JuOckc0GwnCaoGHvMoYtJjVyCzw==", "dev": true, "requires": { - "loader-utils": "^2.0.0" + "loader-utils": "^2.0.2" }, "dependencies": { "json5": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", - "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", + "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "dev": true }, "loader-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", - "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.2.tgz", + "integrity": "sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==", "dev": true, "requires": { "big.js": "^5.2.2", @@ -34746,15 +34741,15 @@ } }, "monaco-languageclient": { - "version": "0.13.1-next.9", - "resolved": "https://registry.npmjs.org/monaco-languageclient/-/monaco-languageclient-0.13.1-next.9.tgz", - "integrity": "sha512-VXzrrlB7RbpvrsgDJw8jmgGqSoFnAWQTOgwwIop7D25O9GB4A0HriyiyTumeOd7JJAQRLyserzSbfrRlBDD+aQ==", + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/monaco-languageclient/-/monaco-languageclient-0.18.1.tgz", + "integrity": "sha512-8x5YbYhtnS5xZ6+Xkk6lJcuMxLdKmc8Y7flW/u5MmFm5/qbe+uUV5vsmqPjcbJy6gAJF4pjFd0PtfhTtOiaxnw==", "requires": { "glob-to-regexp": "^0.4.1", "vscode-jsonrpc": "6.0.0", "vscode-languageclient": "7.0.0", - "vscode-languageserver-textdocument": "^1.0.1", - "vscode-uri": "^3.0.2" + "vscode-languageserver-textdocument": "^1.0.4", + "vscode-uri": "^3.0.3" } }, "move-concurrently": { @@ -38604,9 +38599,9 @@ } }, "vscode-languageserver-textdocument": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.1.tgz", - "integrity": "sha512-UIcJDjX7IFkck7cSkNNyzIz5FyvpQfY7sdzVy+wkKN/BLaD4DQ0ppXQrKePomCxTS7RrolK1I0pey0bG9eh8dA==" + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.4.tgz", + "integrity": "sha512-/xhqXP/2A2RSs+J8JNXpiiNVvvNM0oTosNVmQnunlKvq9o4mupHOBAnnzH0lwIPKazXKvAKsVp1kr+H/K4lgoQ==" }, "vscode-languageserver-types": { "version": "3.16.0", @@ -38614,9 +38609,9 @@ "integrity": "sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA==" }, "vscode-uri": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.2.tgz", - "integrity": "sha512-jkjy6pjU1fxUvI51P+gCsxg1u2n8LSt0W6KrCNQceaziKzff74GoWmjVG46KieVzybO1sttPQmYfrwSHey7GUA==" + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.3.tgz", + "integrity": "sha512-EcswR2S8bpR7fD0YPeS7r2xXExrScVMxg4MedACaWHEtx9ftCF/qHG1xGkolzTPcEmjTavCQgbVzHUIdTMzFGA==" }, "vue-template-compiler": { "version": "2.6.12", diff --git a/package.json b/package.json index e99af0d1..de04849a 100644 --- a/package.json +++ b/package.json @@ -31,8 +31,8 @@ "jszip": "^3.5.0", "markdown-to-jsx": "^7.1.3", "mixpanel-browser": "^2.39.0", - "monaco-editor": "^0.22.3", - "monaco-languageclient": "^0.13.1-next.9", + "monaco-editor": "^0.33.0", + "monaco-languageclient": "^0.18.1", "react": "^16.13.1", "react-dom": "^16.13.1", "react-ga": "^3.1.2", @@ -77,7 +77,7 @@ "handlebars": "^4.7.6", "html-webpack-plugin": "^4.4.1", "jest": "^26.6.3", - "monaco-editor-webpack-plugin": "^3.0.0", + "monaco-editor-webpack-plugin": "^7.0.1", "prettier": "^2.2.1", "style-loader": "^1.2.1", "ts-loader": "^8.0.3", From 1e730497bbef8d8f93fc26895026f7080b7b7f8e Mon Sep 17 00:00:00 2001 From: Max Daunarovich Date: Wed, 30 Mar 2022 18:28:29 +0300 Subject: [PATCH 16/49] Update imports --- src/components/Arguments/types.tsx | 2 +- src/components/MonacoEditor/index.tsx | 4 ++-- src/hooks/useLanguageServer.ts | 6 +++--- src/util/language-client.ts | 5 ++--- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/components/Arguments/types.tsx b/src/components/Arguments/types.tsx index e11113a9..24095555 100644 --- a/src/components/Arguments/types.tsx +++ b/src/components/Arguments/types.tsx @@ -1,4 +1,4 @@ -import * as monaco from 'monaco-editor'; +import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; import { EntityType } from 'providers/Project'; import { CadenceProblem, diff --git a/src/components/MonacoEditor/index.tsx b/src/components/MonacoEditor/index.tsx index 6b4e998b..dfeebb81 100644 --- a/src/components/MonacoEditor/index.tsx +++ b/src/components/MonacoEditor/index.tsx @@ -1,9 +1,9 @@ import React, { useEffect, useRef, useState } from 'react'; -import * as monaco from 'monaco-editor'; +import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; import configureCadence, { CADENCE_LANGUAGE_ID } from 'util/cadence'; import { EditorContainer } from './components'; import { useProject } from 'providers/Project/projectHooks'; -import { EntityType } from 'providers/Project'; +import debounce from 'util/debounce'; const MONACO_CONTAINER_ID = 'monaco-container'; diff --git a/src/hooks/useLanguageServer.ts b/src/hooks/useLanguageServer.ts index b478e6c0..2716f4a6 100644 --- a/src/hooks/useLanguageServer.ts +++ b/src/hooks/useLanguageServer.ts @@ -1,16 +1,16 @@ -import { useEffect, useState, useRef } from 'react'; +import {useEffect, useState, useRef, useMemo} from 'react'; import { CadenceLanguageServer, Callbacks } from 'util/language-server'; import { MonacoServices } from 'monaco-languageclient/lib/monaco-services'; -import * as monaco from 'monaco-editor'; +import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; import { createCadenceLanguageClient } from 'util/language-client'; import { useProject } from 'providers/Project/projectHooks'; +import debounce from "util/debounce"; let monacoServicesInstalled = false; async function startLanguageServer(callbacks: any, getCode: any, ops) { const { setLanguageServer, setCallbacks } = ops; const server = await CadenceLanguageServer.create(callbacks); - new Promise((resolve, reject) => { let checkInterval = setInterval(() => { // .toServer() method is populated by language server diff --git a/src/util/language-client.ts b/src/util/language-client.ts index a6346c9d..c86b43b4 100644 --- a/src/util/language-client.ts +++ b/src/util/language-client.ts @@ -11,7 +11,7 @@ import { PartialMessageInfo } from "vscode-jsonrpc" import {ConnectionErrorHandler} from "monaco-languageclient/src/connection" -import {ConnectionCloseHandler} from "monaco-languageclient" +import {ConnectionCloseHandler, CloseAction, createConnection, ErrorAction, MonacoLanguageClient} from "monaco-languageclient" export function createCadenceLanguageClient(callbacks: Callbacks) { const logger: Logger = { @@ -66,14 +66,13 @@ export function createCadenceLanguageClient(callbacks: Callbacks) { }) }, dispose() { + console.log("-------------------------->", "Language Client is closed. Do something!") callbacks.onClientClose() } } const messageConnection = createMessageConnection(reader, writer, logger) - const {CloseAction, createConnection, ErrorAction, MonacoLanguageClient} = require("monaco-languageclient"); - return new MonacoLanguageClient({ name: "Cadence Language Client", clientOptions: { From 356d2647ae5e53cbd949d0653ea5a06c68973d69 Mon Sep 17 00:00:00 2001 From: Max Daunarovich Date: Wed, 30 Mar 2022 18:28:51 +0300 Subject: [PATCH 17/49] Show language client and server status in the top bar --- src/components/CadenceVersion.tsx | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/components/CadenceVersion.tsx b/src/components/CadenceVersion.tsx index 42442182..452c1ff4 100644 --- a/src/components/CadenceVersion.tsx +++ b/src/components/CadenceVersion.tsx @@ -1,12 +1,14 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useContext } from 'react'; +import { CadenceCheckerContext } from 'providers/CadenceChecker'; const API = process.env.PLAYGROUND_API; export const Version = () => { const [version, setVersion] = useState('--'); + const { languageClient, languageServer } = useContext(CadenceCheckerContext); useEffect(() => { - getCadenceVerion(); + getCadenceVerion().then() }, []); const url = `${API}/utils/version`; @@ -16,7 +18,14 @@ export const Version = () => { setVersion(version); }; - return( - Cadence: {version} - ) + const lsStatus = languageServer ? 'ON' : "OFF"; + const lcStatus = languageClient ? 'ON' : "OFF"; + + return ( + <> + LS: {lsStatus} + LC: {lcStatus} + Cadence: {version} + + ); }; From 62b125af5550173b447b3dd829cf3ad348f9f013 Mon Sep 17 00:00:00 2001 From: Max Daunarovich Date: Wed, 30 Mar 2022 18:29:00 +0300 Subject: [PATCH 18/49] Sorta stable version --- src/components/MonacoEditor/index.tsx | 52 ++++++++------------------- src/hooks/useLanguageServer.ts | 47 ++++++++++++------------ src/util/language-server.ts | 9 +++++ 3 files changed, 49 insertions(+), 59 deletions(-) diff --git a/src/components/MonacoEditor/index.tsx b/src/components/MonacoEditor/index.tsx index dfeebb81..da59920c 100644 --- a/src/components/MonacoEditor/index.tsx +++ b/src/components/MonacoEditor/index.tsx @@ -12,41 +12,11 @@ type EditorState = { viewState: any; }; -const updateActiveCode = (editor, project, event) => { - console.log(project.active); - - if (editor) { - // TODO: delay updates a bit... - const currentValue = editor.getValue(); - const { active } = project; - - console.log('+++++++++++++++++++++++++', project.active); - - // TODO: finish transactions - switch (active.type) { - case EntityType.Account: - console.log('Update account'); - const { updateAccountDraftCode } = project; - updateAccountDraftCode(currentValue).then(); - break; - case EntityType.ScriptTemplate: - console.log('Update script'); - const { updateScriptTemplate } = project; - const { id } = project.project.scriptTemplates[active.index]; - updateScriptTemplate(id, currentValue, '---').then(); - break; - default: - break; - } - } -}; - const EnhancedEditor = (props: any) => { const project = useProject(); - console.log('------------------------>', project.active); - const cadenceInitiated = useRef(false); const editor = useRef(null); + const editorOnChange = useRef(null) const [editorStates, setEditorStates] = useState({}); @@ -99,6 +69,12 @@ const EnhancedEditor = (props: any) => { // TODO: delay language client checks // TODO: add manual request to trigger check + // Method to use, when model was changed + const onChange = debounce((event) => { + // TODO: figure out, why title for transactions and scripts is empty here... + project.active.onChange(editor.current.getValue(), "", "","") + }, 150); + useEffect(() => { if (cadenceInitiated.current === false) { console.log('Init Cadence'); @@ -109,9 +85,14 @@ const EnhancedEditor = (props: any) => { console.log('No need :)'); } }, []); - useEffect(() => { if (editor.current) { + + if(editorOnChange.current){ + editorOnChange.current.dispose() + editorOnChange.current = editor.current.onDidChangeModelContent(onChange); + } + const [code, newId] = getActiveCode(); const newState = getOrCreateEditorState(newId, code); @@ -136,10 +117,7 @@ const EnhancedEditor = (props: any) => { }); // Setup even listener when code is updated - editor.current.onDidChangeModelContent((event) => { - console.log('update', event); - updateActiveCode(editor.current, project, event); - }); + editorOnChange.current = editor.current.onDidChangeModelContent(onChange); const model = monaco.editor.createModel( 'hello, world', @@ -154,7 +132,7 @@ const EnhancedEditor = (props: any) => { editor.current.focus(); window.addEventListener('resize', () => { - console.log('update'); + console.log('Resize Editor Layout'); editor && editor.current.layout(); }); }; diff --git a/src/hooks/useLanguageServer.ts b/src/hooks/useLanguageServer.ts index 2716f4a6..366e1341 100644 --- a/src/hooks/useLanguageServer.ts +++ b/src/hooks/useLanguageServer.ts @@ -21,6 +21,8 @@ async function startLanguageServer(callbacks: any, getCode: any, ops) { callbacks.getAddressCode = getCode; setCallbacks(callbacks); setLanguageServer(server); + console.log(server) + console.log("%c LS: Is Up!",'color: #00FF00') } }, 100); }); @@ -31,15 +33,10 @@ const launchLanguageClient = async ( languageServer, setLanguageClient, ) => { - if (!languageServer) { - console.log('language server is not ready. waiting...'); - } else { - console.log('language server is up. initiate client!'); - console.log(callbacks); + if (languageServer) { const newClient = createCadenceLanguageClient(callbacks); newClient.start(); await newClient.onReady(); - console.log('language client is up!'); setLanguageClient(newClient); } }; @@ -99,28 +96,33 @@ export default function useLanguageServer() { }; const restartServer = () => { - console.log("%c LS: Starting...",'color: #9727e9') - // TODO: Clean global variables, so we could ensure there is no duplication of events and messages + console.log("Restarting server...") + startLanguageServer(callbacks, getCode, { setLanguageServer, setCallbacks, }); }; - useEffect(() => { + const debouncedServerRestart = useMemo( + () => debounce(restartServer, 150), + [languageServer] + ) + + // Restart server, when accounts are changed + useEffect(debouncedServerRestart, [project.project.accounts]); + + + useEffect(()=>{ accountUpdates.current += 1; - console.log(accountUpdates.current); -/* setCallbacks({ - ...callbacks, - getAddressCode: getCode, - });*/ -/* if(languageClient){ - languageClient.stop() - }*/ - // launchLanguageClient(callbacks, languageServer, setLanguageClient).then(); - // restartServer() + console.log({accountUpdates}) + if(languageServer){ + console.log("Update code getter") + languageServer.updateCodeGetter(getCode) + } }, [project.project.accounts]); + useEffect(() => { // The Monaco Language Client services have to be installed globally, once. // An editor must be passed, which is only used for commands. @@ -128,16 +130,17 @@ export default function useLanguageServer() { console.log('Installing monaco services'); if (!monacoServicesInstalled) { - monacoServicesInstalled = true; MonacoServices.install(monaco); + monacoServicesInstalled = true; } restartServer(); }, []); useEffect(() => { - // TODO: postpone with this update - launchLanguageClient(callbacks, languageServer, setLanguageClient).then(); + if(!languageClient){ + launchLanguageClient(callbacks, languageServer, setLanguageClient).then(); + } }, [languageServer]); return { diff --git a/src/util/language-server.ts b/src/util/language-server.ts index a72555f2..e794f2aa 100644 --- a/src/util/language-server.ts +++ b/src/util/language-server.ts @@ -161,4 +161,13 @@ export class CadenceLanguageServer { window[this.functionName('onClientClose')]() } } + + updateCodeGetter(newMethod){ + window[this.functionName('getAddressCode')] = (address: string): string | undefined => { + if (!newMethod) { + return undefined + } + return newMethod(address) + } + } } From 6af647a2289ad9740a2e39df587e917b8ab01698 Mon Sep 17 00:00:00 2001 From: Max Daunarovich Date: Wed, 30 Mar 2022 21:08:50 +0300 Subject: [PATCH 19/49] Working code refresh --- src/components/MonacoEditor/index.tsx | 64 ++++++++++++++++++++++----- src/hooks/useLanguageServer.ts | 2 +- 2 files changed, 55 insertions(+), 11 deletions(-) diff --git a/src/components/MonacoEditor/index.tsx b/src/components/MonacoEditor/index.tsx index da59920c..5d84e73a 100644 --- a/src/components/MonacoEditor/index.tsx +++ b/src/components/MonacoEditor/index.tsx @@ -16,7 +16,12 @@ const EnhancedEditor = (props: any) => { const project = useProject(); const cadenceInitiated = useRef(false); const editor = useRef(null); - const editorOnChange = useRef(null) + const editorOnChange = useRef(null); + + const lastEdit = useRef({ + type: 8, + index: 8, + }); const [editorStates, setEditorStates] = useState({}); @@ -72,7 +77,7 @@ const EnhancedEditor = (props: any) => { // Method to use, when model was changed const onChange = debounce((event) => { // TODO: figure out, why title for transactions and scripts is empty here... - project.active.onChange(editor.current.getValue(), "", "","") + project.active.onChange(editor.current.getValue(), '-----', '', ''); }, 150); useEffect(() => { @@ -85,22 +90,61 @@ const EnhancedEditor = (props: any) => { console.log('No need :)'); } }, []); + useEffect(() => { + console.log('Active item updated!'); if (editor.current) { - - if(editorOnChange.current){ - editorOnChange.current.dispose() - editorOnChange.current = editor.current.onDidChangeModelContent(onChange); + // Remove tracking of model updates to prevent re-rendering + if (editorOnChange.current) { + editorOnChange.current.dispose(); } const [code, newId] = getActiveCode(); const newState = getOrCreateEditorState(newId, code); - editor.current.setModel(newState.model); - editor.current.restoreViewState(newState.viewState); - editor.current.focus(); + console.log({ lastEdit: lastEdit.current }); + + // Active editor changed + // - Check if last changed is different from current + console.log({ + lastT: lastEdit.current.type, + lastI: lastEdit.current.index, + activeT: project.active.type, + activeI: project.active.index, + }); + if ( + lastEdit.current.type == project.active.type && + lastEdit.current.index == project.active.index + ) { + editor.current.setModel(newState.model); + editor.current.restoreViewState(newState.viewState); + editor.current.focus(); + console.log('Same stuff, do nothing for now...'); + } else { + console.log('-------------------->', 'Add new line at the end'); + // - Add new line at the end of the model + newState.model.setValue(code + '\n'); + lastEdit.current = { + type: project.active.type, + index: project.active.index, + }; + + // - Mark last edited as type, index, edited = true + editor.current.setModel(newState.model); + editor.current.restoreViewState(newState.viewState); + editor.current.focus(); + + setTimeout(() => { + newState.model.setValue(code); + editor.current.setModel(newState.model); + editor.current.restoreViewState(newState.viewState); + editor.current.focus(); + }, 120); + } + + editorOnChange.current = editor.current.onDidChangeModelContent(onChange); } - }, [project.active]); + }, [project.active, project.project.accounts]); const destroyEditor = () => { editor.current.dispose(); diff --git a/src/hooks/useLanguageServer.ts b/src/hooks/useLanguageServer.ts index 366e1341..ec463310 100644 --- a/src/hooks/useLanguageServer.ts +++ b/src/hooks/useLanguageServer.ts @@ -110,7 +110,7 @@ export default function useLanguageServer() { ) // Restart server, when accounts are changed - useEffect(debouncedServerRestart, [project.project.accounts]); + useEffect(debouncedServerRestart, [project.project.accounts, project.active]); useEffect(()=>{ From aaab9007aae3bbddeead2d1ed5a8e93fb01b1d1c Mon Sep 17 00:00:00 2001 From: Max Daunarovich Date: Fri, 1 Apr 2022 20:05:28 +0300 Subject: [PATCH 20/49] Fix how changes are saved. Remove console logs --- src/components/MonacoEditor/components.tsx | 8 +++++- src/components/MonacoEditor/index.tsx | 31 +++++----------------- src/containers/Editor/components.tsx | 3 ++- src/hooks/useLanguageServer.ts | 21 +++------------ src/providers/Project/index.tsx | 26 +++++++++--------- 5 files changed, 33 insertions(+), 56 deletions(-) diff --git a/src/components/MonacoEditor/components.tsx b/src/components/MonacoEditor/components.tsx index cf9da444..086dee88 100644 --- a/src/components/MonacoEditor/components.tsx +++ b/src/components/MonacoEditor/components.tsx @@ -7,10 +7,16 @@ const blink = keyframes` } `; -export const EditorContainer = styled.div` +interface EditorContainerProps{ + show: boolean; +} + +export const EditorContainer = styled.div` width: 100%; height: 100%; position: relative; + + display: ${({show = true}) => show ? "block" : "none"}; .drag-box { width: fit-content; diff --git a/src/components/MonacoEditor/index.tsx b/src/components/MonacoEditor/index.tsx index 5d84e73a..c1cfe3f2 100644 --- a/src/components/MonacoEditor/index.tsx +++ b/src/components/MonacoEditor/index.tsx @@ -64,20 +64,16 @@ const EnhancedEditor = (props: any) => { id = scriptTemplates[index].id; break; default: - code = 'not support type'; + code = ''; } return [code, id]; }; - // TODO: tie-in Monaco with project updates - // TODO: tie-in language-client server with project updates - // TODO: delay language client checks - // TODO: add manual request to trigger check - // Method to use, when model was changed const onChange = debounce((event) => { // TODO: figure out, why title for transactions and scripts is empty here... - project.active.onChange(editor.current.getValue(), '-----', '', ''); + // @ts-ignore + project.active.onChange(editor.current.getValue()); }, 150); useEffect(() => { @@ -92,7 +88,6 @@ const EnhancedEditor = (props: any) => { }, []); useEffect(() => { - console.log('Active item updated!'); if (editor.current) { // Remove tracking of model updates to prevent re-rendering if (editorOnChange.current) { @@ -101,17 +96,6 @@ const EnhancedEditor = (props: any) => { const [code, newId] = getActiveCode(); const newState = getOrCreateEditorState(newId, code); - - console.log({ lastEdit: lastEdit.current }); - - // Active editor changed - // - Check if last changed is different from current - console.log({ - lastT: lastEdit.current.type, - lastI: lastEdit.current.index, - activeT: project.active.type, - activeI: project.active.index, - }); if ( lastEdit.current.type == project.active.type && lastEdit.current.index == project.active.index @@ -119,9 +103,7 @@ const EnhancedEditor = (props: any) => { editor.current.setModel(newState.model); editor.current.restoreViewState(newState.viewState); editor.current.focus(); - console.log('Same stuff, do nothing for now...'); } else { - console.log('-------------------->', 'Add new line at the end'); // - Add new line at the end of the model newState.model.setValue(code + '\n'); lastEdit.current = { @@ -161,10 +143,11 @@ const EnhancedEditor = (props: any) => { }); // Setup even listener when code is updated - editorOnChange.current = editor.current.onDidChangeModelContent(onChange); + // editorOnChange.current = editor.current.onDidChangeModelContent(onChange); + const [code] = getActiveCode(); const model = monaco.editor.createModel( - 'hello, world', + code, CADENCE_LANGUAGE_ID, ); const state: EditorState = { @@ -192,7 +175,7 @@ const EnhancedEditor = (props: any) => { }; }, []); - return ; + return ; }; export default EnhancedEditor; diff --git a/src/containers/Editor/components.tsx b/src/containers/Editor/components.tsx index 0bf13940..1fbc484e 100644 --- a/src/containers/Editor/components.tsx +++ b/src/containers/Editor/components.tsx @@ -327,6 +327,7 @@ const EditorContainer: React.FC = ({ setDescriptionOverflow(overflow) setReadme(readme); if(!overflow){ + console.log({title, description, readme}) updateProject(title, description, readme); } }} @@ -344,7 +345,7 @@ const EditorContainer: React.FC = ({ onChange={(code: string, _: any) => onEditorChange(code)} show={!isReadmeEditor} />*/} - + diff --git a/src/hooks/useLanguageServer.ts b/src/hooks/useLanguageServer.ts index ec463310..8481424a 100644 --- a/src/hooks/useLanguageServer.ts +++ b/src/hooks/useLanguageServer.ts @@ -68,16 +68,10 @@ export default function useLanguageServer() { // Base state handler const [languageServer, setLanguageServer] = useState(null); - const [languageServerOpen, setLanguageServerOpen] = useState(false); - const [languageClient, setLanguageClient] = useState(null); - const [languageClientOpen, setLanguageClientOpen] = useState(false); - - const [initialized, setInitialized] = useState(false); const [callbacks, setCallbacks] = useState(initialCallbacks); const getCode = (address) => { - console.log(`Version ${accountUpdates.current}`); const { accounts } = project.project; const number = parseInt(address, 16); @@ -91,7 +85,6 @@ export default function useLanguageServer() { } let code = accounts[index].draftCode; - console.log(code); return code; }; @@ -109,18 +102,15 @@ export default function useLanguageServer() { [languageServer] ) - // Restart server, when accounts are changed + // Restart server, when active item or accounts are changed useEffect(debouncedServerRestart, [project.project.accounts, project.active]); - - useEffect(()=>{ - accountUpdates.current += 1; - console.log({accountUpdates}) + // TODO: ensure we need this one... +/* useEffect(()=>{ if(languageServer){ - console.log("Update code getter") languageServer.updateCodeGetter(getCode) } - }, [project.project.accounts]); + }, [project.project.accounts]);*/ useEffect(() => { @@ -145,10 +135,7 @@ export default function useLanguageServer() { return { languageClient, - languageClientOpen, languageServer, - languageServerOpen, - initialized, restartServer, }; } diff --git a/src/providers/Project/index.tsx b/src/providers/Project/index.tsx index c43d58d8..4ddb7a5a 100644 --- a/src/providers/Project/index.tsx +++ b/src/providers/Project/index.tsx @@ -1,4 +1,4 @@ -import React, { createContext, useState } from 'react'; +import React, {createContext, useState, useRef, useMemo} from 'react'; import { useApolloClient, useQuery } from '@apollo/react-hooks'; import { navigate, useLocation, Redirect } from '@reach/router'; import ProjectMutator from './projectMutator'; @@ -220,13 +220,13 @@ export const ProjectProvider: React.FC = ({ return res; }; - const updateActiveScriptTemplate = async (script: string, title: string) => { + const updateActiveScriptTemplate = async (script: string) => { clearTimeout(timeout); setIsSaving(true); const res = await mutator.updateScriptTemplate( project.scriptTemplates[active.index].id, script, - title, + project.scriptTemplates[active.index].title, ); timeout = setTimeout(() => { setIsSaving(false); @@ -234,16 +234,13 @@ export const ProjectProvider: React.FC = ({ return res; }; - const updateActiveTransactionTemplate = async ( - script: string, - title: string, - ) => { + const updateActiveTransactionTemplate = async (script: string) => { clearTimeout(timeout); setIsSaving(true); const res = await mutator.updateTransactionTemplate( project.transactionTemplates[active.index].id, script, - title, + project.transactionTemplates[active.index].title, ); timeout = setTimeout(() => { setIsSaving(false); @@ -331,15 +328,15 @@ export const ProjectProvider: React.FC = ({ return { type: active.type, index: active.index, - onChange: (code: any, title: string) => - updateActiveTransactionTemplate(code, title), + onChange: (code: any) => + updateActiveTransactionTemplate(code), }; case EntityType.ScriptTemplate: return { type: active.type, index: active.index, - onChange: (code: any, title: string) => - updateActiveScriptTemplate(code, title), + onChange: (code: any) => + updateActiveScriptTemplate(code), }; case EntityType.Readme: return { @@ -352,7 +349,10 @@ export const ProjectProvider: React.FC = ({ } }; - const activeEditor = getActiveEditor(); + const activeEditor = useMemo( + getActiveEditor, + [active.type, active.index] + ) const location = useLocation(); if (isLoading) return null; From b0c6fb1d6584d67c1d7ce04e84c6a50bd0560960 Mon Sep 17 00:00:00 2001 From: Max Daunarovich Date: Tue, 5 Apr 2022 01:17:29 +0300 Subject: [PATCH 21/49] Remove mobile boundaries --- src/App.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index e374e25b..8261fb7c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -50,7 +50,7 @@ const App: React.FC = () => { - +{/* */} @@ -58,8 +58,7 @@ const App: React.FC = () => { - {version} - +{/* */} From e414e177aa96058cab13bc85343a4cffe878aa77 Mon Sep 17 00:00:00 2001 From: Max Daunarovich Date: Tue, 5 Apr 2022 01:17:52 +0300 Subject: [PATCH 22/49] Clean up language server --- src/hooks/useLanguageServer.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/hooks/useLanguageServer.ts b/src/hooks/useLanguageServer.ts index 8481424a..5a4d5b9a 100644 --- a/src/hooks/useLanguageServer.ts +++ b/src/hooks/useLanguageServer.ts @@ -84,7 +84,6 @@ export default function useLanguageServer() { return; } let code = accounts[index].draftCode; - return code; }; @@ -102,15 +101,14 @@ export default function useLanguageServer() { [languageServer] ) - // Restart server, when active item or accounts are changed - useEffect(debouncedServerRestart, [project.project.accounts, project.active]); - - // TODO: ensure we need this one... -/* useEffect(()=>{ + useEffect(()=>{ if(languageServer){ languageServer.updateCodeGetter(getCode) } - }, [project.project.accounts]);*/ + },[project.project.accounts]) + + // TODO: Disable this, once the cadence language server package is updated + // useEffect(debouncedServerRestart, [project.project.accounts, project.active]); useEffect(() => { From 0e4ad7b7d68bb2cd0732268160413b6bd5e7bdeb Mon Sep 17 00:00:00 2001 From: Max Daunarovich Date: Tue, 5 Apr 2022 01:18:26 +0300 Subject: [PATCH 23/49] Refactor components. Create new ControlPanel component for Arguments --- .../CadenceEditor/ControlPanel/components.tsx | 232 ++++++++++++++++++ .../CadenceEditor/ControlPanel/index.tsx | 93 +++++++ .../CadenceEditor/ControlPanel/types.ts | 3 + .../components.tsx | 0 .../{MonacoEditor => CadenceEditor}/index.tsx | 97 ++++---- ...CadenceEditor.tsx => CadenceEditorOld.tsx} | 0 src/containers/Editor/components.tsx | 5 +- 7 files changed, 378 insertions(+), 52 deletions(-) create mode 100644 src/components/CadenceEditor/ControlPanel/components.tsx create mode 100644 src/components/CadenceEditor/ControlPanel/index.tsx create mode 100644 src/components/CadenceEditor/ControlPanel/types.ts rename src/components/{MonacoEditor => CadenceEditor}/components.tsx (100%) rename src/components/{MonacoEditor => CadenceEditor}/index.tsx (62%) rename src/components/{CadenceEditor.tsx => CadenceEditorOld.tsx} (100%) diff --git a/src/components/CadenceEditor/ControlPanel/components.tsx b/src/components/CadenceEditor/ControlPanel/components.tsx new file mode 100644 index 00000000..788ddd1b --- /dev/null +++ b/src/components/CadenceEditor/ControlPanel/components.tsx @@ -0,0 +1,232 @@ +import React, { forwardRef } from 'react'; +import { motion } from 'framer-motion'; +import styled from 'styled-components'; +import theme from '../../../theme'; + +export const MotionBox = forwardRef((props: any, ref: any) => { + const { children } = props; + return ( + + {children} + + ); +}); + +interface HoverPanelProps { + width?: string; +} + +export const HoverPanel = styled.div` + min-width: 300px; + max-width: 500px; + padding: 20px; + border-radius: 4px; + background-color: #fff; + box-shadow: 10px 10px 20px #c9c9c9, -10px -10px 20px #ffffff; +`; + +export const Heading = styled.div` + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 16px; +`; + +interface TitleProps { + lineColor?: string; +} + +export const Title = styled.div` + font-size: 10px; + font-weight: bold; + text-transform: uppercase; + letter-spacing: 0.1em; + position: relative; + color: #919191; + + &:after { + opacity: 0.5; + content: ''; + display: block; + position: absolute; + left: 0; + background: ${(props: any) => props.lineColor || theme.colors.primary}; + height: 3px; + width: 1rem; + bottom: -6px; + border-radius: 3px; + } +`; + +export const Controls = styled.div` + display: flex; + justify-content: flex-end; + align-items: center; + cursor: pointer; +`; + +export const Badge = styled.div` + display: flex; + align-items: center; + justify-content: center; + color: #fff; + font-weight: bold; + font-size: 12px; + margin-right: 5px; + + --size: 16px; + width: var(--size); + height: var(--size); + border-radius: var(--size); + + span { + transform: translateY(1px); + } + + background-color: #ee431e; + &.green { + background-color: ${theme.colors.primary}; + color: #222; + } +`; + +interface ListProps { + hidden?: boolean; +} +export const List = styled.div` + display: ${({ hidden }) => (hidden ? 'none' : 'grid')}; + grid-gap: 12px; + grid-template-columns: 100%; + margin-bottom: 24px; + max-height: 350px; + overflow-y: auto; +`; + +export const SignersContainer = styled.div` + margin-bottom: 20px; +`; + +interface ControlContainerProps { + isOk: boolean; + progress: boolean; +} +export const ControlContainer = styled.div` + display: flex; + align-items: center; + justify-content: space-between; + color: ${({ isOk, progress }) => { + switch (true) { + case progress: + return '#a2a2a2'; + case isOk: + return '#2bb169'; + default: + return '#EE431E'; + } + }}; +`; + +export const ToastContainer = styled.div` + z-index: 1000; + position: fixed; + bottom: 40px; + left: 80px; + display: flex; + align-items: center; + justify-content: space-between; + color: ${theme.colors.darkPrimary}; +`; + +export const StatusMessage = styled.div` + @keyframes spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } + } + + display: flex; + justify-content: flex-start; + font-size: 16px; + svg { + margin-right: 5px; + } + + svg.spin { + animation: spin 0.5s linear infinite; + } +`; + +export const ErrorsContainer = styled.div` + display: grid; + grid-gap: 10px; + grid-template-columns: 100%; + margin-bottom: 12px; +`; + +export const SingleError = styled.div` + cursor: pointer; + display: flex; + align-items: baseline; + box-sizing: border-box; + padding: 10px; + border-radius: 4px; + font-size: 14px; + &:hover { + background-color: rgba(244, 57, 64, 0.15); + + &.hint-warning { + background-color: rgb(238, 169, 30, 0.15); + } + + &.hint-info { + background-color: rgb(85, 238, 30, 0.15); + } + } +`; + +export const ErrorIndex = styled.div` + width: 20px; + height: 20px; + background-color: rgba(0, 0, 0, 0.15); + display: flex; + align-items: center; + justify-content: center; + border-radius: 20px; + margin-right: 8px; + flex: 0 0 auto; +`; + +export const ErrorMessage = styled.p` + line-height: 1.2; + word-break: break-word; + span { + background-color: rgba(0, 0, 0, 0.05); + padding: 2px 6px; + border-radius: 3px; + margin: 3px 3px 3px 5px; + line-height: 20px; + .suggestion { + background-color: ${theme.colors.primary}; + } + } +`; + +export const SignersError = styled.p` + cursor: pointer; + display: flex; + align-items: center; + box-sizing: border-box; + margin: 10px 0; + color: ${theme.colors.error}; + svg { + margin-right: 0.5em; + } +`; diff --git a/src/components/CadenceEditor/ControlPanel/index.tsx b/src/components/CadenceEditor/ControlPanel/index.tsx new file mode 100644 index 00000000..c094091f --- /dev/null +++ b/src/components/CadenceEditor/ControlPanel/index.tsx @@ -0,0 +1,93 @@ +import React, { useRef, useState } from 'react'; +import { + ActionButton, + ArgumentsList, + ArgumentsTitle, + Signers, +} from 'components/Arguments/components'; +import { EntityType } from 'providers/Project'; +import { useProject } from 'providers/Project/projectHooks'; + +import { IValue } from './types'; +import { MotionBox, HoverPanel } from './components'; +import { ControlContainer, StatusMessage } from 'components/Arguments/styles'; +import { FaRegCheckCircle, FaRegTimesCircle, FaSpinner } from 'react-icons/fa'; +import { ExecuteCommandRequest } from 'monaco-languageclient'; +import { ResultType } from 'api/apollo/generated/graphql'; + +const ControlPanel = (props) => { + const { project, active, isSavingCode, lastSigners } = useProject(); + + const { type } = active; + const list = []; + + const [selected, updateSelectedAccounts] = useState([]); + const [errors, setErrors] = useState({}); + const [expanded, setExpanded] = useState(true); + const [values, setValue] = useState({}); + const [processingStatus, setProcessingStatus] = useState(false); + + const numberOfErrors = 0; + const constraintsRef = useRef(); + const validCode = true; + const signers = 5; + + const haveErrors = false; + const isOk = !haveErrors && validCode !== undefined && !!validCode; + let statusIcon = isOk ? : ; + let statusMessage = isOk ? 'Ready' : 'Fix errors'; + const progress = isSavingCode || processingStatus; + + if (progress) { + statusIcon = ; + statusMessage = 'Please, wait...'; + } + + const send = () => { + console.log('send'); + }; + + return ( + <> +
+ + + {validCode && ( + <> + {list.length > 0 && ( + <> + + + + + ); +}; + +export default ControlPanel; diff --git a/src/components/CadenceEditor/ControlPanel/types.ts b/src/components/CadenceEditor/ControlPanel/types.ts new file mode 100644 index 00000000..505d4a2c --- /dev/null +++ b/src/components/CadenceEditor/ControlPanel/types.ts @@ -0,0 +1,3 @@ +export interface IValue { + [key: string]: string; +} \ No newline at end of file diff --git a/src/components/MonacoEditor/components.tsx b/src/components/CadenceEditor/components.tsx similarity index 100% rename from src/components/MonacoEditor/components.tsx rename to src/components/CadenceEditor/components.tsx diff --git a/src/components/MonacoEditor/index.tsx b/src/components/CadenceEditor/index.tsx similarity index 62% rename from src/components/MonacoEditor/index.tsx rename to src/components/CadenceEditor/index.tsx index c1cfe3f2..7a5f5cd7 100644 --- a/src/components/MonacoEditor/index.tsx +++ b/src/components/CadenceEditor/index.tsx @@ -4,20 +4,20 @@ import configureCadence, { CADENCE_LANGUAGE_ID } from 'util/cadence'; import { EditorContainer } from './components'; import { useProject } from 'providers/Project/projectHooks'; import debounce from 'util/debounce'; +import { EntityType } from 'providers/Project'; +import ControlPanel from './ControlPanel'; const MONACO_CONTAINER_ID = 'monaco-container'; -type EditorState = { - model: any; - viewState: any; -}; +type EditorState = { model: any; viewState: any }; -const EnhancedEditor = (props: any) => { +const CadenceEditor = (props: any) => { const project = useProject(); const cadenceInitiated = useRef(false); const editor = useRef(null); const editorOnChange = useRef(null); + // We will specify type as index as non-existent numbers to prevent collision with existing enums const lastEdit = useRef({ type: 8, index: 8, @@ -25,6 +25,14 @@ const EnhancedEditor = (props: any) => { const [editorStates, setEditorStates] = useState({}); + const saveEditorState = (id: string, viewState: any) => { + setEditorStates({ + ...editorStates, + [id]: viewState, + }); + }; + + // This method is used to retrieve previous MonacoEditor state const getOrCreateEditorState = (id: string, code: string): EditorState => { const existingState = editorStates[id]; @@ -44,6 +52,8 @@ const EnhancedEditor = (props: any) => { return newState; }; + + // "getActiveCode" is used to read Cadence code from active(selected) item const getActiveCode = () => { const { active } = project; const { accounts, scriptTemplates, transactionTemplates } = project.project; @@ -51,49 +61,46 @@ const EnhancedEditor = (props: any) => { const { type, index } = active; let code, id; switch (type) { - case 1: - code = accounts[index].draftCode; + case EntityType.Account: + code = accounts[index].draftCode; // TODO: replace this to ".deployedCode", when UI is in place id = accounts[index].id; break; - case 2: + case EntityType.TransactionTemplate: code = transactionTemplates[index].script; id = transactionTemplates[index].id; break; - case 3: + case EntityType.ScriptTemplate: code = scriptTemplates[index].script; id = scriptTemplates[index].id; break; default: code = ''; + id = 8; } return [code, id]; }; // Method to use, when model was changed - const onChange = debounce((event) => { - // TODO: figure out, why title for transactions and scripts is empty here... + const debouncedModelChange = debounce((event) => { + // we will ignore text line, cause onChange is different for readme and other scripts // @ts-ignore project.active.onChange(editor.current.getValue()); }, 150); + useEffect(configureCadence, []); useEffect(() => { - if (cadenceInitiated.current === false) { - console.log('Init Cadence'); - // Step1 - Configure global monaco to support Cadence - configureCadence(); - cadenceInitiated.current = true; - } else { - console.log('No need :)'); - } - }, []); - - useEffect(() => { + // TODO: save/restore view state with: + // - use ref to track current active id + // - const oldState = editor.current.saveViewState(); if (editor.current) { // Remove tracking of model updates to prevent re-rendering if (editorOnChange.current) { editorOnChange.current.dispose(); } + // To pick up new changes in accounts, we will track project's active item and then add and remove + // new line at EOF to trick Language Client to send code changes and reimport the latest changes, + // clearing errors and warning about missing fields. const [code, newId] = getActiveCode(); const newState = getOrCreateEditorState(newId, code); if ( @@ -116,22 +123,15 @@ const EnhancedEditor = (props: any) => { editor.current.restoreViewState(newState.viewState); editor.current.focus(); - setTimeout(() => { - newState.model.setValue(code); - editor.current.setModel(newState.model); - editor.current.restoreViewState(newState.viewState); - editor.current.focus(); - }, 120); + editor.current.layout(); } - editorOnChange.current = editor.current.onDidChangeModelContent(onChange); + editorOnChange.current = + editor.current.onDidChangeModelContent(debouncedModelChange); } }, [project.active, project.project.accounts]); - const destroyEditor = () => { - editor.current.dispose(); - }; - + // "initEditor" will create new instance of Monaco Editor and set it up const initEditor = async () => { const container = document.getElementById(MONACO_CONTAINER_ID); editor.current = monaco.editor.create(container, { @@ -142,14 +142,8 @@ const EnhancedEditor = (props: any) => { }, }); - // Setup even listener when code is updated - // editorOnChange.current = editor.current.onDidChangeModelContent(onChange); - const [code] = getActiveCode(); - const model = monaco.editor.createModel( - code, - CADENCE_LANGUAGE_ID, - ); + const model = monaco.editor.createModel(code, CADENCE_LANGUAGE_ID); const state: EditorState = { model, viewState: null, @@ -157,25 +151,30 @@ const EnhancedEditor = (props: any) => { editor.current.setModel(state.model); editor.current.restoreViewState(state.viewState); editor.current.focus(); + editor.current.layout(); window.addEventListener('resize', () => { - console.log('Resize Editor Layout'); editor && editor.current.layout(); }); }; + // "destroyEditor" is used to dispose of Monaco Editor instance, when the component is unmounted (for any reasons) + const destroyEditor = () => { + editor.current.dispose(); + }; + // Do it once, when CadenceEditor component is instantiated useEffect(() => { - // Drop returned Promise - initEditor().then(); - + initEditor().then(); // drop returned Promise as we not going to use it return () => { - if (editor) { - destroyEditor(); - } + editor && destroyEditor(); }; }, []); - return ; + return ( + + + + ); }; -export default EnhancedEditor; +export default CadenceEditor; diff --git a/src/components/CadenceEditor.tsx b/src/components/CadenceEditorOld.tsx similarity index 100% rename from src/components/CadenceEditor.tsx rename to src/components/CadenceEditorOld.tsx diff --git a/src/containers/Editor/components.tsx b/src/containers/Editor/components.tsx index 1fbc484e..21ade213 100644 --- a/src/containers/Editor/components.tsx +++ b/src/containers/Editor/components.tsx @@ -38,7 +38,7 @@ import { } from './layout-components' import { decodeText } from "util/readme"; -import EnhancedEditor from "components/MonacoEditor" +import CadenceEditor from "components/CadenceEditor" export interface WithShowProps { show: boolean; @@ -327,7 +327,6 @@ const EditorContainer: React.FC = ({ setDescriptionOverflow(overflow) setReadme(readme); if(!overflow){ - console.log({title, description, readme}) updateProject(title, description, readme); } }} @@ -345,7 +344,7 @@ const EditorContainer: React.FC = ({ onChange={(code: string, _: any) => onEditorChange(code)} show={!isReadmeEditor} />*/} - + From e0d59ccf10430fd2542bf0598874a4023ea73298 Mon Sep 17 00:00:00 2001 From: Max Daunarovich Date: Tue, 5 Apr 2022 18:43:21 +0300 Subject: [PATCH 24/49] More ControlPanel refactoring --- .../CadenceEditor/ControlPanel/index.tsx | 398 ++++++++++++++++-- .../CadenceEditor/ControlPanel/types.ts | 37 +- .../CadenceEditor/ControlPanel/utils.tsx | 114 +++++ src/components/CadenceEditor/index.tsx | 4 +- 4 files changed, 525 insertions(+), 28 deletions(-) create mode 100644 src/components/CadenceEditor/ControlPanel/utils.tsx diff --git a/src/components/CadenceEditor/ControlPanel/index.tsx b/src/components/CadenceEditor/ControlPanel/index.tsx index c094091f..633e57f6 100644 --- a/src/components/CadenceEditor/ControlPanel/index.tsx +++ b/src/components/CadenceEditor/ControlPanel/index.tsx @@ -1,41 +1,300 @@ -import React, { useRef, useState } from 'react'; +import React, { useContext, useEffect, useRef, useState } from 'react'; +import { FaRegCheckCircle, FaRegTimesCircle, FaSpinner } from 'react-icons/fa'; +import { AiFillCloseCircle } from 'react-icons/ai'; +import { motion, AnimatePresence } from 'framer-motion'; +import { ExecuteCommandRequest } from 'monaco-languageclient'; +import { useThemeUI, Box, Text, Flex } from 'theme-ui'; + +import { + ResultType, + useSetExecutionResultsMutation, +} from 'api/apollo/generated/graphql'; + +import { EntityType } from 'providers/Project'; +import { useProject } from 'providers/Project/projectHooks'; +import { RemoveToastButton } from 'layout/RemoveToastButton'; + import { ActionButton, ArgumentsList, ArgumentsTitle, + ErrorsList, + Hints, Signers, -} from 'components/Arguments/components'; -import { EntityType } from 'providers/Project'; -import { useProject } from 'providers/Project/projectHooks'; +} from '../../Arguments/components'; -import { IValue } from './types'; -import { MotionBox, HoverPanel } from './components'; -import { ControlContainer, StatusMessage } from 'components/Arguments/styles'; -import { FaRegCheckCircle, FaRegTimesCircle, FaSpinner } from 'react-icons/fa'; -import { ExecuteCommandRequest } from 'monaco-languageclient'; -import { ResultType } from 'api/apollo/generated/graphql'; +import { + ControlContainer, + ToastContainer, + HoverPanel, + StatusMessage, +} from '../../Arguments/styles'; + +import { getLabel, validateByType, useTemplateType } from './utils'; +import { ControlPanelProps, IValue } from './types'; +import { Highlight } from 'util/language-syntax-errors'; +import * as monaco from 'monaco-editor'; +import { extractSigners } from 'util/parser'; +import { CadenceCheckerContext } from 'providers/CadenceChecker'; + +const hover = + (editor: any) => + (highlight: Highlight): void => { + const { startLine, startColumn, endLine, endColumn, color } = highlight; + const model = editor.getModel(); -const ControlPanel = (props) => { - const { project, active, isSavingCode, lastSigners } = useProject(); + const selection = model.getAllDecorations().find((item: any) => { + return ( + item.range.startLineNumber === startLine && + item.range.startColumn === startColumn + ); + }); + const selectionEndLine = selection + ? selection.range.endLineNumber + : endLine; + const selectionEndColumn = selection + ? selection.range.endColumn + : endColumn; + + const highlightLine = [ + { + range: new monaco.Range(startLine, startColumn, endLine, endColumn), + options: { + isWholeLine: true, + className: `playground-syntax-${color}-hover`, + }, + }, + { + range: new monaco.Range( + startLine, + startColumn, + selectionEndLine, + selectionEndColumn, + ), + options: { + isWholeLine: false, + className: `playground-syntax-${color}-hover-selection`, + }, + }, + ]; + editor.getModel().deltaDecorations([], highlightLine); + editor.revealLineInCenter(startLine); + }; + +const ControlPanel: React.FC = (props) => { + // Hooks + const { languageClient } = useContext(CadenceCheckerContext); + const { + project, + active, + isSavingCode, + lastSigners, + // updateAccountDeployedCode + } = useProject(); + const { theme } = useThemeUI(); + + // Destructuring const { type } = active; - const list = []; + const { editor } = props; + + // Collect problems with the code + const [problems, setProblems] = useState({ + error: [], + warning: [], + hint: [], + info: [], + }); + + const [list, setList] = useState([]); + + const code = editor.model.getValue(); + const signers = extractSigners(code).length; + + const validCode = problems.error.length === 0; + const needSigners = type == EntityType.TransactionTemplate && signers > 0; const [selected, updateSelectedAccounts] = useState([]); const [errors, setErrors] = useState({}); const [expanded, setExpanded] = useState(true); const [values, setValue] = useState({}); + const constraintsRef = useRef(); + + const removeNotification = (set: any, id: number) => { + set((prev: any[]) => { + delete prev[id]; + return { + ...prev, + }; + }); + }; + + const numberOfErrors = Object.keys(errors).length; + const notEnoughSigners = needSigners && selected.length < signers; + const haveErrors = numberOfErrors > 0 || notEnoughSigners; + + const validate = (list: any, values: any) => { + const errors = list.reduce((acc: any, item: any) => { + const { name, type } = item; + const value = values[name]; + if (value) { + const error = validateByType(value, type); + if (error) { + acc[name] = error; + } + } else { + if (type !== 'String') { + acc[name] = "Value can't be empty"; + } + } + return acc; + }, {}); + + console.log({ errors }); + setErrors(errors); + }; + + useEffect(() => { + validate(list, values); + }, [list, values]); + const [processingStatus, setProcessingStatus] = useState(false); - const numberOfErrors = 0; - const constraintsRef = useRef(); - const validCode = true; - const signers = 5; + const [setResult] = useSetExecutionResultsMutation(); + const { scriptFactory, transactionFactory, contractDeployment } = + useTemplateType(); + + const [notifications, setNotifications] = useState<{ + [identifier: string]: string[]; + }>({}); + + // compare 'state' field for each account, set 'notifications' state for new data + // @ts-ignore: <- this state is only used to compare and render notifications + /* + const [_, setProjectAccts] = useState(project.accounts); + const [counter, setCounter] = useState(0); + useEffect(() => { + setProjectAccts((prevAccounts) => { + const latestAccounts = project.accounts; + const updatedAccounts = latestAccounts.filter( + (latestAccount, index) => + latestAccount.state !== prevAccounts[index].state, + ); + + if (updatedAccounts.length > 0) { + setNotifications((prev) => { + return { + ...prev, + [counter]: updatedAccounts, + }; + }); + setTimeout(() => removeNotification(setNotifications, counter), 5000); + setCounter((prev) => prev + 1); + } + return project.accounts; + }); + }, [project]); + */ + const { accounts } = project; + + const signersAccounts = selected.map((i) => accounts[i]); + + const send = async () => { + if (!processingStatus) { + setProcessingStatus(true); + } + + // TODO: implement algorithm for drilling down dictionaries + + const fixed = list.map((arg) => { + const { name, type } = arg; + let value = values[name]; + + if (type === `String`) { + value = `${value}`; + } + + // We probably better fix this on server side... + if (type === 'UFix64') { + if (value.indexOf('.') < 0) { + value = `${value}.0`; + } + } + + // Language server throws "input is not literal" without quotes + if (type === `String`) { + value = `\"${value.replace(/"/g, '\\"')}\"`; + } + + return value; + }); + + let formatted: any; + try { + formatted = await languageClient.sendRequest(ExecuteCommandRequest.type, { + command: 'cadence.server.parseEntryPointArguments', + arguments: [editor.getModel().uri.toString(), fixed], + }); + } catch (e) { + console.log(e); + } + + // Map values to strings that will be passed to backend + const args: any = list.map((_, index) => JSON.stringify(formatted[index])); + + let rawResult, resultType; + try { + switch (type) { + case EntityType.ScriptTemplate: { + resultType = ResultType.Script; + rawResult = await scriptFactory(args); + break; + } + + case EntityType.TransactionTemplate: { + resultType = ResultType.Transaction; + rawResult = await transactionFactory(signersAccounts, args); + break; + } + + case EntityType.Account: { + // Ask if user wants to redeploy the contract + if (accounts[active.index] && accounts[active.index].deployedCode) { + const choiceMessage = + 'Redeploying will clear the state of all accounts. Proceed?'; + if (!confirm(choiceMessage)) { + setProcessingStatus(false); + return; + } + } + resultType = ResultType.Contract; + rawResult = await contractDeployment(); + break; + } + default: + break; + } + } catch (e) { + console.error(e); + rawResult = e.toString(); + } + + setProcessingStatus(false); + + // Display result in the bottom area + setResult({ + variables: { + label: getLabel(resultType, project, active.index), + resultType, + rawResult, + }, + }); + }; - const haveErrors = false; const isOk = !haveErrors && validCode !== undefined && !!validCode; let statusIcon = isOk ? : ; let statusMessage = isOk ? 'Ready' : 'Fix errors'; + const progress = isSavingCode || processingStatus; if (progress) { @@ -43,14 +302,20 @@ const ControlPanel = (props) => { statusMessage = 'Please, wait...'; } - const send = () => { - console.log('send'); + const actions = { + goTo: () => {}, + hideDecorations: () => {}, + hover: hover(editor), }; - return ( <> -
- +
+ {validCode && ( <> @@ -74,9 +339,19 @@ const ControlPanel = (props) => { /> )} + {needSigners && ( + + )} )} + + + {statusIcon} @@ -85,7 +360,82 @@ const ControlPanel = (props) => { - + + +
    + + {Object.keys(notifications).map((id) => { + const updatedAccounts = notifications[id]; + + let updatedStorageAccts: string[] = []; + updatedAccounts.map((acct: any) => { + const addr = acct.address; + const acctNum = addr.charAt(addr.length - 1); + const acctHex = `0x0${acctNum}`; + updatedStorageAccts.push(acctHex); + }); + + // render a new list item for each new id in 'notifications' state + return ( + lastSigners && + updatedStorageAccts && ( + + + + removeNotification(setNotifications, parseInt(id)) + } + > + + + + + + {`Account${lastSigners?.length > 1 ? 's' : ''} + ${lastSigners.join(', ')} + updated the storage in + account${updatedStorageAccts?.length > 1 ? 's' : ''} + ${updatedStorageAccts.join(', ')}.`} + + + + ) + ); + })} + +
+
); }; diff --git a/src/components/CadenceEditor/ControlPanel/types.ts b/src/components/CadenceEditor/ControlPanel/types.ts index 505d4a2c..098ec569 100644 --- a/src/components/CadenceEditor/ControlPanel/types.ts +++ b/src/components/CadenceEditor/ControlPanel/types.ts @@ -1,3 +1,36 @@ +import { EntityType } from 'providers/Project'; +import { Highlight, ProblemsList } from 'util/language-syntax-errors'; +import * as monaco from 'monaco-editor'; +import { MonacoLanguageClient } from 'monaco-languageclient'; +import { Argument } from 'components/Arguments/types'; +import {Account} from "api/apollo/generated/graphql"; + export interface IValue { - [key: string]: string; -} \ No newline at end of file + [key: string]: string; +} + +export type ControlPanelProps = { + editor: any; +/* type: EntityType; + list: Argument[]; + signers: number; + problems: ProblemsList; + goTo: (position: monaco.IPosition) => void; + hover: (highlight: Highlight) => void; + hideDecorations: () => void; + languageClient: MonacoLanguageClient;*/ +}; + +export type ScriptExecution = (args?: string[]) => Promise; +export type TransactionExecution = ( + signingAccounts: Account[], + args?: string[], +) => Promise; +export type DeployExecution = () => Promise; + +export type ProcessingArgs = { + disabled: boolean; + scriptFactory?: ScriptExecution; + transactionFactory?: TransactionExecution; + contractDeployment?: DeployExecution; +}; diff --git a/src/components/CadenceEditor/ControlPanel/utils.tsx b/src/components/CadenceEditor/ControlPanel/utils.tsx new file mode 100644 index 00000000..07dfea34 --- /dev/null +++ b/src/components/CadenceEditor/ControlPanel/utils.tsx @@ -0,0 +1,114 @@ +import { ResultType } from 'api/apollo/generated/graphql'; +import { useProject } from 'providers/Project/projectHooks'; +import { ProcessingArgs } from './types'; + +const isDictionary = (type: string) => type.includes('{'); +const isArray = (type: string) => type.includes('['); +const isImportedType = (type: string) => type.includes('.'); +const isComplexType = (type: string) => + isDictionary(type) || isArray(type) || isImportedType(type); + +export const startsWith = (value: string, prefix: string) => { + return value.startsWith(prefix) || value.startsWith('U' + prefix); +}; + +export const checkJSON = (value: any, type: string) => { + try { + JSON.parse(value); + return null; + } catch (e) { + return `Not a valid argument of type ${type}`; + } +}; + +export const validateByType = (value: any, type: string) => { + if (value.length === 0) { + return "Value can't be empty"; + } + + switch (true) { + // Strings + case type === 'String': { + return null; // no need to validate String for now + } + + // Integers + case startsWith(type, 'Int'): { + if (isNaN(value) || value === '') { + return 'Should be a valid Integer number'; + } + return null; + } + + // Words + case startsWith(type, 'Word'): { + if (isNaN(value) || value === '') { + return 'Should be a valid Word number'; + } + return null; + } + + // Fixed Point + case startsWith(type, 'Fix'): { + if (isNaN(value) || value === '') { + return 'Should be a valid fixed point number'; + } + return null; + } + + case isComplexType(type): { + // This case it to catch complex arguments like Dictionaries + return checkJSON(value, type); + } + + // Address + case type === 'Address': { + if (!value.match(/(^0x[\w\d]{16})|(^0x[\w\d]{1,4})/)) { + return 'Not a valid Address'; + } + return null; + } + + // Booleans + case type === 'Bool': { + if (value !== 'true' && value !== 'false') { + return 'Boolean values can be either true or false'; + } + return null; + } + + default: { + return null; + } + } +}; + +export const getLabel = ( + resultType: ResultType, + project: any, + index: number, +): string => { + return resultType === ResultType.Contract + ? 'Deployment' + : resultType === ResultType.Script + ? project.scriptTemplates[index].title + : resultType === ResultType.Transaction + ? project.transactionTemplates[index].title + : 'Interaction'; +}; + +export const useTemplateType = (): ProcessingArgs => { + const { isSavingCode } = useProject(); + const { + createScriptExecution, + createTransactionExecution, + updateAccountDeployedCode, + } = useProject(); + + return { + disabled: isSavingCode, + scriptFactory: createScriptExecution, + transactionFactory: createTransactionExecution, + contractDeployment: updateAccountDeployedCode, + }; +}; diff --git a/src/components/CadenceEditor/index.tsx b/src/components/CadenceEditor/index.tsx index 7a5f5cd7..64d839fc 100644 --- a/src/components/CadenceEditor/index.tsx +++ b/src/components/CadenceEditor/index.tsx @@ -164,7 +164,7 @@ const CadenceEditor = (props: any) => { // Do it once, when CadenceEditor component is instantiated useEffect(() => { - initEditor().then(); // drop returned Promise as we not going to use it + initEditor().then(); // drop returned Promise as we are not going to use it return () => { editor && destroyEditor(); }; @@ -172,7 +172,7 @@ const CadenceEditor = (props: any) => { return ( - + ); }; From 2063e295c45a95e32cc437e97a1694d287695d65 Mon Sep 17 00:00:00 2001 From: Max Daunarovich Date: Tue, 5 Apr 2022 19:51:49 +0300 Subject: [PATCH 25/49] Working layout for control Panel. Assembly process --- .../CadenceEditor/ControlPanel/components.tsx | 8 +- .../CadenceEditor/ControlPanel/index.tsx | 98 +++++++++++++++---- .../ControlPanel/{types.ts => types.tsx} | 8 +- src/components/CadenceEditor/components.tsx | 8 +- src/components/CadenceEditor/index.tsx | 46 ++++----- 5 files changed, 116 insertions(+), 52 deletions(-) rename src/components/CadenceEditor/ControlPanel/{types.ts => types.tsx} (88%) diff --git a/src/components/CadenceEditor/ControlPanel/components.tsx b/src/components/CadenceEditor/ControlPanel/components.tsx index 788ddd1b..6d47668b 100644 --- a/src/components/CadenceEditor/ControlPanel/components.tsx +++ b/src/components/CadenceEditor/ControlPanel/components.tsx @@ -3,19 +3,19 @@ import { motion } from 'framer-motion'; import styled from 'styled-components'; import theme from '../../../theme'; -export const MotionBox = forwardRef((props: any, ref: any) => { - const { children } = props; +export const MotionBox = (props: any) => { + const { children, dragConstraints } = props; return ( {children} ); -}); +}; interface HoverPanelProps { width?: string; diff --git a/src/components/CadenceEditor/ControlPanel/index.tsx b/src/components/CadenceEditor/ControlPanel/index.tsx index 633e57f6..b69908ec 100644 --- a/src/components/CadenceEditor/ControlPanel/index.tsx +++ b/src/components/CadenceEditor/ControlPanel/index.tsx @@ -4,15 +4,19 @@ import { AiFillCloseCircle } from 'react-icons/ai'; import { motion, AnimatePresence } from 'framer-motion'; import { ExecuteCommandRequest } from 'monaco-languageclient'; import { useThemeUI, Box, Text, Flex } from 'theme-ui'; +import { IPosition, Range } from 'monaco-editor/esm/vs/editor/editor.api'; import { ResultType, useSetExecutionResultsMutation, } from 'api/apollo/generated/graphql'; +import { CadenceCheckerContext } from 'providers/CadenceChecker'; import { EntityType } from 'providers/Project'; import { useProject } from 'providers/Project/projectHooks'; import { RemoveToastButton } from 'layout/RemoveToastButton'; +import { goTo, Highlight } from 'util/language-syntax-errors'; +import { extractSigners } from 'util/parser'; import { ActionButton, @@ -32,10 +36,8 @@ import { import { getLabel, validateByType, useTemplateType } from './utils'; import { ControlPanelProps, IValue } from './types'; -import { Highlight } from 'util/language-syntax-errors'; -import * as monaco from 'monaco-editor'; -import { extractSigners } from 'util/parser'; -import { CadenceCheckerContext } from 'providers/CadenceChecker'; +import { MotionBox } from 'components/CadenceEditor/ControlPanel/components'; +import { CadenceCheckCompleted } from 'util/language-server'; const hover = (editor: any) => @@ -59,14 +61,14 @@ const hover = const highlightLine = [ { - range: new monaco.Range(startLine, startColumn, endLine, endColumn), + range: new Range(startLine, startColumn, endLine, endColumn), options: { isWholeLine: true, className: `playground-syntax-${color}-hover`, }, }, { - range: new monaco.Range( + range: new Range( startLine, startColumn, selectionEndLine, @@ -83,6 +85,13 @@ const hover = }; const ControlPanel: React.FC = (props) => { + // Props + const { editor } = props; + + if (!editor) { + return null; + } + // Hooks const { languageClient } = useContext(CadenceCheckerContext); const { @@ -96,7 +105,6 @@ const ControlPanel: React.FC = (props) => { // Destructuring const { type } = active; - const { editor } = props; // Collect problems with the code const [problems, setProblems] = useState({ @@ -108,7 +116,7 @@ const ControlPanel: React.FC = (props) => { const [list, setList] = useState([]); - const code = editor.model.getValue(); + const code = ''; const signers = extractSigners(code).length; const validCode = problems.error.length === 0; @@ -196,7 +204,6 @@ const ControlPanel: React.FC = (props) => { }, [project]); */ const { accounts } = project; - const signersAccounts = selected.map((i) => accounts[i]); const send = async () => { @@ -303,19 +310,74 @@ const ControlPanel: React.FC = (props) => { } const actions = { - goTo: () => {}, + goTo: (position: IPosition) => goTo(editor, position), hideDecorations: () => {}, - hover: hover(editor), + hover: () => {}, }; + + // =========================================================================== + // Connect LanguageClient to ControlPanel + // HOOKS ------------------------------------------------------------------- + const clientOnNotification = useRef(null); + const [executionArguments, setExecutionArguments] = useState({}); + + // METHODS ------------------------------------------------------------------ + const getParameters = () => { + if (!languageClient) { + return []; + } + try { + const args = await languageClient.sendRequest( + ExecuteCommandRequest.type, + { + command: 'cadence.server.getEntryPointParameters', + arguments: [editor.getModel().uri.toString()], + }, + ); + return args || []; + } catch (error) { + console.error(error); + return []; + } + }; + + const processMarkers = () => { + console.log('%c NOT IMPLEMENTED: Process Markers', { color: 'orange' }); + }; + + // EFFECTS ------------------------------------------------------------------ + useEffect(() => { + if (languageClient) { + if (clientOnNotification.current) { + clientOnNotification.current.dispose(); + } + + clientOnNotification.current = languageClient.onNotification( + CadenceCheckCompleted.methodName, + async (result: CadenceCheckCompleted.Params) => { + if (result.valid) { + const params = await getParameters(); + // TODO: Get id of active editor from project + const key = `${active.type}-${active.index}`; + + // Update state + setExecutionArguments({ + ...executionArguments, + [key]: params, + }); + } + processMarkers(); + }, + ); + } + }, [languageClient]); + + // =========================================================================== + // RENDER return ( <>
- + {validCode && ( <> @@ -360,7 +422,7 @@ const ControlPanel: React.FC = (props) => { - +
    diff --git a/src/components/CadenceEditor/ControlPanel/types.ts b/src/components/CadenceEditor/ControlPanel/types.tsx similarity index 88% rename from src/components/CadenceEditor/ControlPanel/types.ts rename to src/components/CadenceEditor/ControlPanel/types.tsx index 098ec569..8bc83194 100644 --- a/src/components/CadenceEditor/ControlPanel/types.ts +++ b/src/components/CadenceEditor/ControlPanel/types.tsx @@ -3,7 +3,7 @@ import { Highlight, ProblemsList } from 'util/language-syntax-errors'; import * as monaco from 'monaco-editor'; import { MonacoLanguageClient } from 'monaco-languageclient'; import { Argument } from 'components/Arguments/types'; -import {Account} from "api/apollo/generated/graphql"; +import { Account } from 'api/apollo/generated/graphql'; export interface IValue { [key: string]: string; @@ -11,7 +11,7 @@ export interface IValue { export type ControlPanelProps = { editor: any; -/* type: EntityType; + /* type: EntityType; list: Argument[]; signers: number; problems: ProblemsList; @@ -23,8 +23,8 @@ export type ControlPanelProps = { export type ScriptExecution = (args?: string[]) => Promise; export type TransactionExecution = ( - signingAccounts: Account[], - args?: string[], + signingAccounts: Account[], + args?: string[], ) => Promise; export type DeployExecution = () => Promise; diff --git a/src/components/CadenceEditor/components.tsx b/src/components/CadenceEditor/components.tsx index 086dee88..376c40a7 100644 --- a/src/components/CadenceEditor/components.tsx +++ b/src/components/CadenceEditor/components.tsx @@ -7,16 +7,16 @@ const blink = keyframes` } `; -interface EditorContainerProps{ - show: boolean; +interface EditorContainerProps { + show: boolean; } export const EditorContainer = styled.div` width: 100%; height: 100%; position: relative; - - display: ${({show = true}) => show ? "block" : "none"}; + + display: ${({ show = true }) => (show ? 'block' : 'none')}; .drag-box { width: fit-content; diff --git a/src/components/CadenceEditor/index.tsx b/src/components/CadenceEditor/index.tsx index 64d839fc..39bc2632 100644 --- a/src/components/CadenceEditor/index.tsx +++ b/src/components/CadenceEditor/index.tsx @@ -13,10 +13,8 @@ type EditorState = { model: any; viewState: any }; const CadenceEditor = (props: any) => { const project = useProject(); - const cadenceInitiated = useRef(false); - const editor = useRef(null); + const [editor, setEditor] = useState(null); const editorOnChange = useRef(null); - // We will specify type as index as non-existent numbers to prevent collision with existing enums const lastEdit = useRef({ type: 8, @@ -84,15 +82,15 @@ const CadenceEditor = (props: any) => { const debouncedModelChange = debounce((event) => { // we will ignore text line, cause onChange is different for readme and other scripts // @ts-ignore - project.active.onChange(editor.current.getValue()); + project.active.onChange(editor.getValue()); }, 150); useEffect(configureCadence, []); useEffect(() => { // TODO: save/restore view state with: // - use ref to track current active id - // - const oldState = editor.current.saveViewState(); - if (editor.current) { + // - const oldState = editor.saveViewState(); + if (editor) { // Remove tracking of model updates to prevent re-rendering if (editorOnChange.current) { editorOnChange.current.dispose(); @@ -107,9 +105,9 @@ const CadenceEditor = (props: any) => { lastEdit.current.type == project.active.type && lastEdit.current.index == project.active.index ) { - editor.current.setModel(newState.model); - editor.current.restoreViewState(newState.viewState); - editor.current.focus(); + editor.setModel(newState.model); + editor.restoreViewState(newState.viewState); + editor.focus(); } else { // - Add new line at the end of the model newState.model.setValue(code + '\n'); @@ -119,22 +117,22 @@ const CadenceEditor = (props: any) => { }; // - Mark last edited as type, index, edited = true - editor.current.setModel(newState.model); - editor.current.restoreViewState(newState.viewState); - editor.current.focus(); + editor.setModel(newState.model); + editor.restoreViewState(newState.viewState); + editor.focus(); - editor.current.layout(); + editor.layout(); } editorOnChange.current = - editor.current.onDidChangeModelContent(debouncedModelChange); + editor.onDidChangeModelContent(debouncedModelChange); } }, [project.active, project.project.accounts]); // "initEditor" will create new instance of Monaco Editor and set it up const initEditor = async () => { const container = document.getElementById(MONACO_CONTAINER_ID); - editor.current = monaco.editor.create(container, { + const editor = monaco.editor.create(container, { theme: 'vs-light', language: CADENCE_LANGUAGE_ID, minimap: { @@ -148,18 +146,22 @@ const CadenceEditor = (props: any) => { model, viewState: null, }; - editor.current.setModel(state.model); - editor.current.restoreViewState(state.viewState); - editor.current.focus(); - editor.current.layout(); + editor.setModel(state.model); + editor.restoreViewState(state.viewState); + editor.focus(); + editor.layout(); window.addEventListener('resize', () => { - editor && editor.current.layout(); + editor && editor.layout(); }); + + // Save editor in component state + setEditor(editor); }; + // "destroyEditor" is used to dispose of Monaco Editor instance, when the component is unmounted (for any reasons) const destroyEditor = () => { - editor.current.dispose(); + editor.dispose(); }; // Do it once, when CadenceEditor component is instantiated @@ -172,7 +174,7 @@ const CadenceEditor = (props: any) => { return ( - + ); }; From e39ca7484eff0feb6e6c6fb2cdcf09885fed0a7b Mon Sep 17 00:00:00 2001 From: Max Daunarovich Date: Tue, 5 Apr 2022 20:48:27 +0300 Subject: [PATCH 26/49] Fix yield error --- src/components/CadenceEditor/ControlPanel/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/CadenceEditor/ControlPanel/index.tsx b/src/components/CadenceEditor/ControlPanel/index.tsx index b69908ec..99b5d309 100644 --- a/src/components/CadenceEditor/ControlPanel/index.tsx +++ b/src/components/CadenceEditor/ControlPanel/index.tsx @@ -322,7 +322,7 @@ const ControlPanel: React.FC = (props) => { const [executionArguments, setExecutionArguments] = useState({}); // METHODS ------------------------------------------------------------------ - const getParameters = () => { + const getParameters = async () => { if (!languageClient) { return []; } From c35b9290837af1896ad3f934be545498e5e242be Mon Sep 17 00:00:00 2001 From: Max Daunarovich Date: Wed, 6 Apr 2022 19:35:59 +0300 Subject: [PATCH 27/49] More refactoring around error messages --- .../CadenceEditor/ControlPanel/index.tsx | 144 ++++++++++-------- 1 file changed, 82 insertions(+), 62 deletions(-) diff --git a/src/components/CadenceEditor/ControlPanel/index.tsx b/src/components/CadenceEditor/ControlPanel/index.tsx index 99b5d309..fb6cf968 100644 --- a/src/components/CadenceEditor/ControlPanel/index.tsx +++ b/src/components/CadenceEditor/ControlPanel/index.tsx @@ -4,7 +4,11 @@ import { AiFillCloseCircle } from 'react-icons/ai'; import { motion, AnimatePresence } from 'framer-motion'; import { ExecuteCommandRequest } from 'monaco-languageclient'; import { useThemeUI, Box, Text, Flex } from 'theme-ui'; -import { IPosition, Range } from 'monaco-editor/esm/vs/editor/editor.api'; +import { + IPosition, + Range, + editor as monacoEditor, +} from 'monaco-editor/esm/vs/editor/editor.api'; import { ResultType, @@ -15,7 +19,13 @@ import { CadenceCheckerContext } from 'providers/CadenceChecker'; import { EntityType } from 'providers/Project'; import { useProject } from 'providers/Project/projectHooks'; import { RemoveToastButton } from 'layout/RemoveToastButton'; -import { goTo, Highlight } from 'util/language-syntax-errors'; +import { + CadenceProblem, + formatMarker, + goTo, + Highlight, + ProblemsList, +} from 'util/language-syntax-errors'; import { extractSigners } from 'util/parser'; import { @@ -103,22 +113,32 @@ const ControlPanel: React.FC = (props) => { } = useProject(); const { theme } = useThemeUI(); + const getActiveKey = () => `${active.type}-${active.index}`; + // Destructuring const { type } = active; // Collect problems with the code - const [problems, setProblems] = useState({ - error: [], - warning: [], - hint: [], - info: [], - }); + const [problemsList, setProblemsList] = useState({}); + + const getProblems = (): ProblemsList => { + const key = getActiveKey(); + return ( + problemsList[key] || { + error: [], + warning: [], + hint: [], + info: [], + } + ); + }; const [list, setList] = useState([]); const code = ''; const signers = extractSigners(code).length; + const problems = getProblems(); const validCode = problems.error.length === 0; const needSigners = type == EntityType.TransactionTemplate && signers > 0; @@ -162,10 +182,6 @@ const ControlPanel: React.FC = (props) => { setErrors(errors); }; - useEffect(() => { - validate(list, values); - }, [list, values]); - const [processingStatus, setProcessingStatus] = useState(false); const [setResult] = useSetExecutionResultsMutation(); @@ -176,33 +192,6 @@ const ControlPanel: React.FC = (props) => { [identifier: string]: string[]; }>({}); - // compare 'state' field for each account, set 'notifications' state for new data - // @ts-ignore: <- this state is only used to compare and render notifications - /* - const [_, setProjectAccts] = useState(project.accounts); - const [counter, setCounter] = useState(0); - useEffect(() => { - setProjectAccts((prevAccounts) => { - const latestAccounts = project.accounts; - const updatedAccounts = latestAccounts.filter( - (latestAccount, index) => - latestAccount.state !== prevAccounts[index].state, - ); - - if (updatedAccounts.length > 0) { - setNotifications((prev) => { - return { - ...prev, - [counter]: updatedAccounts, - }; - }); - setTimeout(() => removeNotification(setNotifications, counter), 5000); - setCounter((prev) => prev + 1); - } - return project.accounts; - }); - }, [project]); - */ const { accounts } = project; const signersAccounts = selected.map((i) => accounts[i]); @@ -341,36 +330,67 @@ const ControlPanel: React.FC = (props) => { } }; + // Pay attention, that we are passing "processMarkers" into the callback function of + // language server. This will create closure around methods - like "getActiveKey" + // and their returned values, which would be able to pick up changes in component state. const processMarkers = () => { - console.log('%c NOT IMPLEMENTED: Process Markers', { color: 'orange' }); + const model = editor.getModel(); + const modelMarkers = monacoEditor.getModelMarkers({ resource: model.uri }); + const errors = modelMarkers.reduce( + (acc: { [key: string]: CadenceProblem[] }, marker) => { + const mappedMarker: CadenceProblem = formatMarker(marker); + acc[mappedMarker.type].push(mappedMarker); + return acc; + }, + { + error: [], + warning: [], + info: [], + hint: [], + }, + ); + + const key = getActiveKey(); // <- this value will be from static closure + + setProblemsList({ + ...problemsList, + [key]: errors, + }); + }; + + const setupLanguageClientListener = () => { + if (clientOnNotification.current) { + clientOnNotification.current.dispose(); + } + clientOnNotification.current = languageClient.onNotification( + CadenceCheckCompleted.methodName, + async (result: CadenceCheckCompleted.Params) => { + console.log('%cCheck completed', { color: 'green' }); + if (result.valid) { + const params = await getParameters(); + const key = getActiveKey(); + + // Update state + setExecutionArguments({ + ...executionArguments, + [key]: params, + }); + } + processMarkers(); + }, + ); }; // EFFECTS ------------------------------------------------------------------ useEffect(() => { if (languageClient) { - if (clientOnNotification.current) { - clientOnNotification.current.dispose(); - } - - clientOnNotification.current = languageClient.onNotification( - CadenceCheckCompleted.methodName, - async (result: CadenceCheckCompleted.Params) => { - if (result.valid) { - const params = await getParameters(); - // TODO: Get id of active editor from project - const key = `${active.type}-${active.index}`; - - // Update state - setExecutionArguments({ - ...executionArguments, - [key]: params, - }); - } - processMarkers(); - }, - ); + setupLanguageClientListener(); } - }, [languageClient]); + }, [languageClient, active]); + + useEffect(() => { + validate(list, values); + }, [list, values]); // =========================================================================== // RENDER From c70335a78f38880d44c1bfe22fb2305daf3d9e9e Mon Sep 17 00:00:00 2001 From: Max Daunarovich Date: Thu, 7 Apr 2022 17:21:08 +0300 Subject: [PATCH 28/49] Move hover in --- src/util/language-syntax-errors.ts | 135 +++++++++++++++++++++-------- 1 file changed, 98 insertions(+), 37 deletions(-) diff --git a/src/util/language-syntax-errors.ts b/src/util/language-syntax-errors.ts index 3f3af0d1..32a2ef42 100644 --- a/src/util/language-syntax-errors.ts +++ b/src/util/language-syntax-errors.ts @@ -1,52 +1,55 @@ // import { FaUnlink, FaRandom } from 'react-icons/fa'; import { FaExclamationTriangle } from 'react-icons/fa'; -import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; +import { + Range, + IPosition, + editor as monacoEditor, +} from 'monaco-editor/esm/vs/editor/editor.api'; export enum ProblemType { Error = 'error', Warning = 'warning', Info = 'info', Hint = 'hint', - Unknown = "unknown" + Unknown = 'unknown', } export type Highlight = { - color: string, - startLine: number, - startColumn: number, - endLine: number, - endColumn: number -} + color: string; + startLine: number; + startColumn: number; + endLine: number; + endColumn: number; +}; export type CadenceProblem = { - message: string, - type: string, - position: monaco.IPosition, - icon: any, - highlight: Highlight, -} + message: string; + type: string; + position: IPosition; + icon: any; + highlight: Highlight; +}; export type ProblemsList = { - [key: string]: CadenceProblem[] -} + [key: string]: CadenceProblem[]; +}; const getMarkerType = (severity: number): ProblemType => { switch (true) { - case (severity === 8): - return ProblemType.Error - case (severity === 4): - return ProblemType.Warning - case (severity === 2): - return ProblemType.Info - case (severity === 1): - return ProblemType.Hint + case severity === 8: + return ProblemType.Error; + case severity === 4: + return ProblemType.Warning; + case severity === 2: + return ProblemType.Info; + case severity === 1: + return ProblemType.Hint; default: - return ProblemType.Unknown + return ProblemType.Unknown; } -} - -export const formatMarker = (marker: monaco.editor.IMarker): CadenceProblem => { +}; +export const formatMarker = (marker: monacoEditor.IMarker): CadenceProblem => { const markerType = getMarkerType(marker.severity); return { @@ -55,7 +58,7 @@ export const formatMarker = (marker: monaco.editor.IMarker): CadenceProblem => { icon: FaExclamationTriangle, position: { lineNumber: marker.startLineNumber, - column: marker.startColumn + column: marker.startColumn, }, highlight: { color: markerType, @@ -63,13 +66,16 @@ export const formatMarker = (marker: monaco.editor.IMarker): CadenceProblem => { endLine: marker.endLineNumber, startColumn: marker.startColumn, endColumn: marker.endColumn, - } - } + }, + }; }; -export const hasErrors = (problemList: any[]):boolean => { - return problemList.filter(problem => problem.type === ProblemType.Error).length === 0 -} +export const hasErrors = (problemList: any[]): boolean => { + return ( + problemList.filter((problem) => problem.type === ProblemType.Error) + .length === 0 + ); +}; export const getIconByErrorType = (errorType: SyntaxError): any => { switch (errorType) { @@ -85,10 +91,65 @@ export const getIconByErrorType = (errorType: SyntaxError): any => { }; export const goTo = ( - editor: monaco.editor.ICodeEditor, - position: monaco.IPosition, -) => { + editor: monacoEditor.ICodeEditor, + position: IPosition, +): void => { editor.revealLineInCenter(position.lineNumber); editor.setPosition(position); editor.focus(); }; + +export const hover = ( + editor: monacoEditor.ICodeEditor, + highlight: Highlight, +): void => { + const { startLine, startColumn, endLine, endColumn, color } = highlight; + const model = editor.getModel(); + + const selection = model.getAllDecorations().find((item: any) => { + return ( + item.range.startLineNumber === startLine && + item.range.startColumn === startColumn + ); + }); + + const selectionEndLine = selection ? selection.range.endLineNumber : endLine; + const selectionEndColumn = selection ? selection.range.endColumn : endColumn; + + const highlightLine = [ + { + range: new Range(startLine, startColumn, endLine, endColumn), + options: { + isWholeLine: true, + className: `playground-syntax-${color}-hover`, + }, + }, + { + range: new Range( + startLine, + startColumn, + selectionEndLine, + selectionEndColumn, + ), + options: { + isWholeLine: false, + className: `playground-syntax-${color}-hover-selection`, + }, + }, + ]; + editor.getModel().deltaDecorations([], highlightLine); + editor.revealLineInCenter(startLine); +}; + +export const hideDecorations = (editor: monacoEditor.ICodeEditor): void => { + const model = editor.getModel(); + let current = model + .getAllDecorations() + .filter((item) => { + const { className } = item.options; + return className?.includes('playground-syntax'); + }) + .map((item) => item.id); + + model.deltaDecorations(current, []); +}; From 79509915caf2450430b7633b9e243b10a221fb0f Mon Sep 17 00:00:00 2001 From: Max Daunarovich Date: Thu, 7 Apr 2022 17:21:30 +0300 Subject: [PATCH 29/49] Use deployedCode for code checks --- src/hooks/useLanguageServer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks/useLanguageServer.ts b/src/hooks/useLanguageServer.ts index 5a4d5b9a..9db6171f 100644 --- a/src/hooks/useLanguageServer.ts +++ b/src/hooks/useLanguageServer.ts @@ -83,7 +83,7 @@ export default function useLanguageServer() { if (index < 0 || index >= accounts.length) { return; } - let code = accounts[index].draftCode; + let code = accounts[index].deployedCode; return code; }; From f6e9a6eea1d822f8875fc2319cdc6942694c57c5 Mon Sep 17 00:00:00 2001 From: Max Daunarovich Date: Thu, 7 Apr 2022 17:21:55 +0300 Subject: [PATCH 30/49] Refactor actions props --- src/components/Arguments/components.tsx | 7 ++++--- src/components/Arguments/index.tsx | 4 ++-- src/components/Arguments/types.tsx | 12 +++++++----- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/components/Arguments/components.tsx b/src/components/Arguments/components.tsx index 1ff88e56..504c6761 100644 --- a/src/components/Arguments/components.tsx +++ b/src/components/Arguments/components.tsx @@ -134,7 +134,8 @@ const renderMessage = (message: string) => { }; export const ErrorsList: React.FC = (props) => { - const { list, goTo, hideDecorations, hover } = props; + const { list, actions } = props; + const {goTo, hideDecorations, hover} = actions if (list.length === 0) { return null; } @@ -167,8 +168,8 @@ export const ErrorsList: React.FC = (props) => { export const Hints: React.FC = (props: HintsProps) => { const [ expanded, setExpanded ] = useState(true); - const { problems, goTo, hideDecorations, hover } = props; - + const { problems, actions } = props; + const { goTo, hideDecorations, hover } = actions const toggle = () => { setExpanded(!expanded); }; diff --git a/src/components/Arguments/index.tsx b/src/components/Arguments/index.tsx index 0819364a..4f2d3442 100644 --- a/src/components/Arguments/index.tsx +++ b/src/components/Arguments/index.tsx @@ -405,8 +405,8 @@ const Arguments: React.FC = (props) => { )} - - + + diff --git a/src/components/Arguments/types.tsx b/src/components/Arguments/types.tsx index 24095555..6dcce08e 100644 --- a/src/components/Arguments/types.tsx +++ b/src/components/Arguments/types.tsx @@ -44,18 +44,20 @@ export type ArgumentsListProps = { errors: any; }; -export type ErrorListProps = { - list: CadenceProblem[]; +export type Actions = { goTo: (position: monaco.IPosition) => void; hover: (highlight: Highlight) => void; hideDecorations: () => void; +} + +export type ErrorListProps = { + list: CadenceProblem[]; + actions: Actions }; export type HintsProps = { problems: ProblemsList; - goTo: (position: monaco.IPosition) => void; - hover: (highlight: Highlight) => void; - hideDecorations: () => void; + actions: Actions }; export type HintsState = { From 869ece1c76a95e20223e046d7a3a9d3630813b2a Mon Sep 17 00:00:00 2001 From: Max Daunarovich Date: Thu, 7 Apr 2022 17:22:11 +0300 Subject: [PATCH 31/49] Define editor type. Clear unnecessary props --- src/components/CadenceEditor/ControlPanel/types.tsx | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/components/CadenceEditor/ControlPanel/types.tsx b/src/components/CadenceEditor/ControlPanel/types.tsx index 8bc83194..c04c44e4 100644 --- a/src/components/CadenceEditor/ControlPanel/types.tsx +++ b/src/components/CadenceEditor/ControlPanel/types.tsx @@ -1,24 +1,16 @@ import { EntityType } from 'providers/Project'; import { Highlight, ProblemsList } from 'util/language-syntax-errors'; -import * as monaco from 'monaco-editor'; import { MonacoLanguageClient } from 'monaco-languageclient'; import { Argument } from 'components/Arguments/types'; import { Account } from 'api/apollo/generated/graphql'; +import { editor as monacoEditor } from 'monaco-editor/esm/vs/editor/editor.api'; export interface IValue { [key: string]: string; } export type ControlPanelProps = { - editor: any; - /* type: EntityType; - list: Argument[]; - signers: number; - problems: ProblemsList; - goTo: (position: monaco.IPosition) => void; - hover: (highlight: Highlight) => void; - hideDecorations: () => void; - languageClient: MonacoLanguageClient;*/ + editor: monacoEditor.ICodeEditor; }; export type ScriptExecution = (args?: string[]) => Promise; From 6c2a70092bd18c4842e7719fd4dc6a554c68c354 Mon Sep 17 00:00:00 2001 From: Max Daunarovich Date: Thu, 7 Apr 2022 17:22:59 +0300 Subject: [PATCH 32/49] Use deployedCode --- src/components/CadenceEditor/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/CadenceEditor/index.tsx b/src/components/CadenceEditor/index.tsx index 39bc2632..43f511dc 100644 --- a/src/components/CadenceEditor/index.tsx +++ b/src/components/CadenceEditor/index.tsx @@ -60,7 +60,7 @@ const CadenceEditor = (props: any) => { let code, id; switch (type) { case EntityType.Account: - code = accounts[index].draftCode; // TODO: replace this to ".deployedCode", when UI is in place + code = accounts[index].deployedCode; id = accounts[index].id; break; case EntityType.TransactionTemplate: From c20fb0f70805b8e30a2c29ef12f0da5b2ffec2ca Mon Sep 17 00:00:00 2001 From: Max Daunarovich Date: Thu, 7 Apr 2022 17:23:28 +0300 Subject: [PATCH 33/49] Refactor methods, hooks and components. Sprinkle with documentation for methods. Add more meaningful comments on how it works --- .../CadenceEditor/ControlPanel/index.tsx | 375 +++++++++--------- 1 file changed, 181 insertions(+), 194 deletions(-) diff --git a/src/components/CadenceEditor/ControlPanel/index.tsx b/src/components/CadenceEditor/ControlPanel/index.tsx index fb6cf968..bb0a47bc 100644 --- a/src/components/CadenceEditor/ControlPanel/index.tsx +++ b/src/components/CadenceEditor/ControlPanel/index.tsx @@ -1,3 +1,4 @@ +// External Modules import React, { useContext, useEffect, useRef, useState } from 'react'; import { FaRegCheckCircle, FaRegTimesCircle, FaSpinner } from 'react-icons/fa'; import { AiFillCloseCircle } from 'react-icons/ai'; @@ -6,15 +7,10 @@ import { ExecuteCommandRequest } from 'monaco-languageclient'; import { useThemeUI, Box, Text, Flex } from 'theme-ui'; import { IPosition, - Range, editor as monacoEditor, } from 'monaco-editor/esm/vs/editor/editor.api'; -import { - ResultType, - useSetExecutionResultsMutation, -} from 'api/apollo/generated/graphql'; - +// Project Modules import { CadenceCheckerContext } from 'providers/CadenceChecker'; import { EntityType } from 'providers/Project'; import { useProject } from 'providers/Project/projectHooks'; @@ -23,11 +19,26 @@ import { CadenceProblem, formatMarker, goTo, + hideDecorations, Highlight, + hover, ProblemsList, } from 'util/language-syntax-errors'; import { extractSigners } from 'util/parser'; +import { CadenceCheckCompleted } from 'util/language-server'; +// Local Generated Modules +import { + ResultType, + useSetExecutionResultsMutation, +} from 'api/apollo/generated/graphql'; + +// Component Scoped Files +import { getLabel, validateByType, useTemplateType } from './utils'; +import { ControlPanelProps, IValue } from './types'; +import { MotionBox } from './components'; + +// Other import { ActionButton, ArgumentsList, @@ -44,83 +55,64 @@ import { StatusMessage, } from '../../Arguments/styles'; -import { getLabel, validateByType, useTemplateType } from './utils'; -import { ControlPanelProps, IValue } from './types'; -import { MotionBox } from 'components/CadenceEditor/ControlPanel/components'; -import { CadenceCheckCompleted } from 'util/language-server'; - -const hover = - (editor: any) => - (highlight: Highlight): void => { - const { startLine, startColumn, endLine, endColumn, color } = highlight; - const model = editor.getModel(); +const ControlPanel: React.FC = (props) => { + // We should not render this component if editor is non existent + if (!props.editor) { + return null; + } - const selection = model.getAllDecorations().find((item: any) => { - return ( - item.range.startLineNumber === startLine && - item.range.startColumn === startColumn - ); - }); + // ============================================================================ + // NOTIFICATION BITS ----------------------------------------------------------- + // TODO: Refactor this one + const clientOnNotification = useRef(null); - const selectionEndLine = selection - ? selection.range.endLineNumber - : endLine; - const selectionEndColumn = selection - ? selection.range.endColumn - : endColumn; + const [notifications, setNotifications] = useState<{ + [identifier: string]: string[]; + }>({}); - const highlightLine = [ - { - range: new Range(startLine, startColumn, endLine, endColumn), - options: { - isWholeLine: true, - className: `playground-syntax-${color}-hover`, - }, - }, - { - range: new Range( - startLine, - startColumn, - selectionEndLine, - selectionEndColumn, - ), - options: { - isWholeLine: false, - className: `playground-syntax-${color}-hover-selection`, - }, - }, - ]; - editor.getModel().deltaDecorations([], highlightLine); - editor.revealLineInCenter(startLine); + const removeNotification = (set: any, id: number) => { + set((prev: any[]) => { + delete prev[id]; + return { + ...prev, + }; + }); }; -const ControlPanel: React.FC = (props) => { - // Props - const { editor } = props; - - if (!editor) { - return null; - } - - // Hooks + // =========================================================================== + // GLOBAL HOOKS const { languageClient } = useContext(CadenceCheckerContext); - const { - project, - active, - isSavingCode, - lastSigners, - // updateAccountDeployedCode - } = useProject(); + const { project, active, isSavingCode, lastSigners } = useProject(); const { theme } = useThemeUI(); - const getActiveKey = () => `${active.type}-${active.index}`; - - // Destructuring - const { type } = active; + // HOOKS ------------------------------------------------------------------- + const [executionArguments, setExecutionArguments] = useState({}); + const [processingStatus, setProcessingStatus] = useState(false); + const [setResult] = useSetExecutionResultsMutation(); + const { scriptFactory, transactionFactory, contractDeployment } = + useTemplateType(); + const [selected, updateSelectedAccounts] = useState([]); + const [expanded, setExpanded] = useState(true); - // Collect problems with the code + const [values, setValue] = useState({}); + // Handles errors with arguments + const [errors, setErrors] = useState({}); + // Handles problems, hints and info for checked code const [problemsList, setProblemsList] = useState({}); + // Holds reference to constraining div for floating window + const constraintsRef = useRef(); + + // =========================================================================== + // METHODS ------------------------------------------------------------------ + /** + * Make active key out of active project item type and index + */ + const getActiveKey = () => `${active.type}-${active.index}`; + + /** + * Returns a list of problems, hints and info for active code + */ const getProblems = (): ProblemsList => { const key = getActiveKey(); return ( @@ -133,34 +125,100 @@ const ControlPanel: React.FC = (props) => { ); }; - const [list, setList] = useState([]); - - const code = ''; - const signers = extractSigners(code).length; + /** + * Sends request to langugeClient to get entry point parameters. + * @return Promise which resolved to a list of arguments + */ + const getParameters = async (): Promise<[any?]> => { + if (!languageClient) { + return []; + } + try { + const args = await languageClient.sendRequest( + ExecuteCommandRequest.type, + { + command: 'cadence.server.getEntryPointParameters', + arguments: [editor.getModel().uri.toString()], + }, + ); + return args || []; + } catch (error) { + console.error(error); + return []; + } + }; - const problems = getProblems(); - const validCode = problems.error.length === 0; + /** + * Process model markers and collect them into respective groups for rendering + * Pay attention, that we are passing "processMarkers" into the callback function of + * language server. This will create closure around methods - like "getActiveKey" + * and their returned values, which would be able to pick up changes in component state. + */ + const processMarkers = () => { + const model = editor.getModel(); + const modelMarkers = monacoEditor.getModelMarkers({ resource: model.uri }); + const errors = modelMarkers.reduce( + (acc: { [key: string]: CadenceProblem[] }, marker) => { + const mappedMarker: CadenceProblem = formatMarker(marker); + acc[mappedMarker.type].push(mappedMarker); + return acc; + }, + { + error: [], + warning: [], + info: [], + hint: [], + }, + ); - const needSigners = type == EntityType.TransactionTemplate && signers > 0; - const [selected, updateSelectedAccounts] = useState([]); - const [errors, setErrors] = useState({}); - const [expanded, setExpanded] = useState(true); - const [values, setValue] = useState({}); - const constraintsRef = useRef(); + const key = getActiveKey(); // <- this value will be from static closure - const removeNotification = (set: any, id: number) => { - set((prev: any[]) => { - delete prev[id]; - return { - ...prev, - }; + setProblemsList({ + ...problemsList, + [key]: errors, }); }; - const numberOfErrors = Object.keys(errors).length; - const notEnoughSigners = needSigners && selected.length < signers; - const haveErrors = numberOfErrors > 0 || notEnoughSigners; + /** + * Get list of arguments from this component's state + */ + const getArguments = (): any => { + const key = getActiveKey(); + return executionArguments[key] || []; + }; + + /** + * Disposes old languageClient callback and attached new one to create proper closure for all local methods. + * Otherwise, they will refer to old value of "project" prop and provide de-synced values + */ + const setupLanguageClientListener = () => { + if (clientOnNotification.current) { + clientOnNotification.current.dispose(); + } + clientOnNotification.current = languageClient.onNotification( + CadenceCheckCompleted.methodName, + async (result: CadenceCheckCompleted.Params) => { + console.log('%cCheck completed', { color: 'green' }); + if (result.valid) { + const params = await getParameters(); + const key = getActiveKey(); + // Update state + setExecutionArguments({ + ...executionArguments, + [key]: params, + }); + } + processMarkers(); + }, + ); + }; + + /** + * Validates that list of arguments conforms to their respective types + * @param list - list of argument types + * @param values - list of argument values + */ const validate = (list: any, values: any) => { const errors = list.reduce((acc: any, item: any) => { const { name, type } = item; @@ -178,23 +236,12 @@ const ControlPanel: React.FC = (props) => { return acc; }, {}); - console.log({ errors }); setErrors(errors); }; - const [processingStatus, setProcessingStatus] = useState(false); - - const [setResult] = useSetExecutionResultsMutation(); - const { scriptFactory, transactionFactory, contractDeployment } = - useTemplateType(); - - const [notifications, setNotifications] = useState<{ - [identifier: string]: string[]; - }>({}); - - const { accounts } = project; - const signersAccounts = selected.map((i) => accounts[i]); - + /** + * Processes arguments and send scripts and transaction for execution or contracts for deployment + */ const send = async () => { if (!processingStatus) { setProcessingStatus(true); @@ -284,7 +331,31 @@ const ControlPanel: React.FC = (props) => { resultType, rawResult, }, - }); + }).then(); + }; + + // VARIABLES AND CONSTANTS ------------------------------------------------- + const { editor } = props; + const { type } = active; + const code = editor.getModel().getValue(); + const problems = getProblems(); + const validCode = problems.error.length === 0; + + const signers = extractSigners(code).length; + const needSigners = type == EntityType.TransactionTemplate && signers > 0; + + const numberOfErrors = Object.keys(errors).length; + const notEnoughSigners = needSigners && selected.length < signers; + const haveErrors = numberOfErrors > 0 || notEnoughSigners; + + const { accounts } = project; + const signersAccounts = selected.map((i) => accounts[i]); + + const list = getArguments(); + const actions = { + goTo: (position: IPosition) => goTo(editor, position), + hideDecorations: () => hideDecorations(editor), + hover: (highlight: Highlight) => hover(editor, highlight), }; const isOk = !haveErrors && validCode !== undefined && !!validCode; @@ -292,95 +363,11 @@ const ControlPanel: React.FC = (props) => { let statusMessage = isOk ? 'Ready' : 'Fix errors'; const progress = isSavingCode || processingStatus; - if (progress) { statusIcon = ; statusMessage = 'Please, wait...'; } - const actions = { - goTo: (position: IPosition) => goTo(editor, position), - hideDecorations: () => {}, - hover: () => {}, - }; - - // =========================================================================== - // Connect LanguageClient to ControlPanel - // HOOKS ------------------------------------------------------------------- - const clientOnNotification = useRef(null); - const [executionArguments, setExecutionArguments] = useState({}); - - // METHODS ------------------------------------------------------------------ - const getParameters = async () => { - if (!languageClient) { - return []; - } - try { - const args = await languageClient.sendRequest( - ExecuteCommandRequest.type, - { - command: 'cadence.server.getEntryPointParameters', - arguments: [editor.getModel().uri.toString()], - }, - ); - return args || []; - } catch (error) { - console.error(error); - return []; - } - }; - - // Pay attention, that we are passing "processMarkers" into the callback function of - // language server. This will create closure around methods - like "getActiveKey" - // and their returned values, which would be able to pick up changes in component state. - const processMarkers = () => { - const model = editor.getModel(); - const modelMarkers = monacoEditor.getModelMarkers({ resource: model.uri }); - const errors = modelMarkers.reduce( - (acc: { [key: string]: CadenceProblem[] }, marker) => { - const mappedMarker: CadenceProblem = formatMarker(marker); - acc[mappedMarker.type].push(mappedMarker); - return acc; - }, - { - error: [], - warning: [], - info: [], - hint: [], - }, - ); - - const key = getActiveKey(); // <- this value will be from static closure - - setProblemsList({ - ...problemsList, - [key]: errors, - }); - }; - - const setupLanguageClientListener = () => { - if (clientOnNotification.current) { - clientOnNotification.current.dispose(); - } - clientOnNotification.current = languageClient.onNotification( - CadenceCheckCompleted.methodName, - async (result: CadenceCheckCompleted.Params) => { - console.log('%cCheck completed', { color: 'green' }); - if (result.valid) { - const params = await getParameters(); - const key = getActiveKey(); - - // Update state - setExecutionArguments({ - ...executionArguments, - [key]: params, - }); - } - processMarkers(); - }, - ); - }; - // EFFECTS ------------------------------------------------------------------ useEffect(() => { if (languageClient) { @@ -431,8 +418,8 @@ const ControlPanel: React.FC = (props) => { )} - - + + From d3b883ce5594b2aaaa2aa51318700c36bf55ac5d Mon Sep 17 00:00:00 2001 From: Max Daunarovich Date: Thu, 7 Apr 2022 17:26:19 +0300 Subject: [PATCH 34/49] Revert back to draftCode as it should be --- src/components/CadenceEditor/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/CadenceEditor/index.tsx b/src/components/CadenceEditor/index.tsx index 43f511dc..39e7908e 100644 --- a/src/components/CadenceEditor/index.tsx +++ b/src/components/CadenceEditor/index.tsx @@ -60,7 +60,7 @@ const CadenceEditor = (props: any) => { let code, id; switch (type) { case EntityType.Account: - code = accounts[index].deployedCode; + code = accounts[index].draftCode; id = accounts[index].id; break; case EntityType.TransactionTemplate: From c2d049a06354262230d693fa949f4d558319b773 Mon Sep 17 00:00:00 2001 From: Max Daunarovich Date: Fri, 8 Apr 2022 13:03:10 +0300 Subject: [PATCH 35/49] Move EditorState into types file --- src/components/CadenceEditor/types.tsx | 1 + 1 file changed, 1 insertion(+) create mode 100644 src/components/CadenceEditor/types.tsx diff --git a/src/components/CadenceEditor/types.tsx b/src/components/CadenceEditor/types.tsx new file mode 100644 index 00000000..74105854 --- /dev/null +++ b/src/components/CadenceEditor/types.tsx @@ -0,0 +1 @@ +export type EditorState = { model: any; viewState: any }; \ No newline at end of file From 8985d697a863c24726ddcf3ee68ac66b503dc47d Mon Sep 17 00:00:00 2001 From: Max Daunarovich Date: Fri, 8 Apr 2022 13:03:25 +0300 Subject: [PATCH 36/49] Create Notifications component --- .../Notifications/components.tsx | 76 +++++++++++ .../CadenceEditor/Notifications/index.tsx | 119 ++++++++++++++++++ 2 files changed, 195 insertions(+) create mode 100644 src/components/CadenceEditor/Notifications/components.tsx create mode 100644 src/components/CadenceEditor/Notifications/index.tsx diff --git a/src/components/CadenceEditor/Notifications/components.tsx b/src/components/CadenceEditor/Notifications/components.tsx new file mode 100644 index 00000000..3926788f --- /dev/null +++ b/src/components/CadenceEditor/Notifications/components.tsx @@ -0,0 +1,76 @@ +import React from 'react'; +import styled from 'styled-components'; +import { motion } from 'framer-motion'; +import { Box, Flex, Text, useThemeUI } from 'theme-ui'; + +import theme from '../../../theme'; + +export const ToastContainer = styled.div` + z-index: 1000; + position: fixed; + bottom: 40px; + left: 80px; + display: flex; + align-items: center; + justify-content: space-between; + color: ${theme.colors.darkPrimary}; +`; + +export const RemoveToastButton = styled.button` + border: none; + background: transparent; + transform: translate(25%, 50%); + color: ${theme.colors.grey}; + &:hover { + color: ${theme.colors.heading}; + } +`; + +export const ButtonContainer = ({ children }) => { + return ( + + {children} + + ); +}; + +export const ContentBox = ({ children }) => { + const sx = { + marginTop: '0.0rem', + padding: '0.8rem 0.5rem', + alignItems: 'center', + border: `1px solid ${theme.colors.borderDark}`, + backgroundColor: theme.colors.background, + borderRadius: '8px', + maxWidth: '500px', + boxShadow: '10px 10px 20px #c9c9c9, -10px -10px 20px #ffffff', + }; + return ( + + {children} + + ); +}; + +export const Content = ({ children }) => { + const sx = { + padding: '0.75rem', + }; + return {children}; +}; + +export const SingleToast = (props: any) => { + const { id, children } = props; + const toastProps = { + key: id, + layout: true, + initial: { opacity: 0, y: 50, scale: 0.3 }, + animate: { opacity: 1, y: 0, scale: 1 }, + exit: { opacity: 0, scale: 0.5, transition: { duration: 0.2 } }, + }; + return {children}; +}; diff --git a/src/components/CadenceEditor/Notifications/index.tsx b/src/components/CadenceEditor/Notifications/index.tsx new file mode 100644 index 00000000..caa31630 --- /dev/null +++ b/src/components/CadenceEditor/Notifications/index.tsx @@ -0,0 +1,119 @@ +import React, { useEffect, useState } from 'react'; +import { useProject } from 'providers/Project/projectHooks'; +import { useThemeUI } from 'theme-ui'; + +import { + SingleToast, + ToastContainer, + RemoveToastButton, + ButtonContainer, + ContentBox, + Content, +} from './components'; +import { AiFillCloseCircle } from 'react-icons/ai'; + +const Notifications = (props: any) => { + // =========================================================================== + // GLOBAL HOOKS + const { project, lastSigners } = useProject(); + const { theme } = useThemeUI(); + + // HOOKS ------------------------------------------------------------------- + const [_, setProjectAccts] = useState(project.accounts); + const [counter, setCounter] = useState(0); + const [notifications, setNotifications] = useState<{ + [identifier: string]: string[]; + }>({}); + + // METHODS ------------------------------------------------------------------- + const removeNotification = (set: any, id: number) => { + set((prev: any[]) => { + delete prev[id]; + return { + ...prev, + }; + }); + }; + + // EFFECTS ------------------------------------------------------------------- + useEffect(() => { + setProjectAccts((prevAccounts) => { + const latestAccounts = project.accounts; + const updatedAccounts = latestAccounts.filter( + (latestAccount, index) => + latestAccount.state !== prevAccounts[index].state, + ); + + if (updatedAccounts.length > 0) { + setNotifications((prev) => { + return { + ...prev, + [counter]: updatedAccounts, + }; + }); + setTimeout(() => removeNotification(setNotifications, counter), 5000); + setCounter((prev) => prev + 1); + } + return project.accounts; + }); + }, [project]); + + // VARIABLES AND CONSTANTS --------------------------------------------------- + const toasts = Object.keys(notifications).map((id) => { + const updatedAccounts = notifications[id]; + let updatedStorageAccounts: string[] = []; + + updatedAccounts.forEach((acct: any) => { + const { address } = acct; + const accountIndex = address.charAt(address.length - 1); + const accountHex = `0x0${accountIndex}`; + updatedStorageAccounts.push(accountHex); + }); + + const shallRender = lastSigners && updatedStorageAccounts; + if (!shallRender) { + return null; + } + + const pluralSigners = lastSigners?.length > 1 ? 'Accounts' : 'Account'; + const pluralUpdated = + updatedStorageAccounts?.length > 1 ? 'accounts' : 'account'; + const signers = lastSigners.join(', '); + const updated = updatedStorageAccounts.join(', '); + const toastText = `${pluralSigners} ${signers} updated the storage in ${pluralUpdated} ${updated}.`; + + const onClick = () => removeNotification(setNotifications, parseInt(id)); + + return { + id, + toastText, + onClick, + }; + }); + + // RENDER + return ( + +
      + {toasts.map((toast) => { + const { id, toastText, onClick } = toast; + + return ( + + + + + + + + {toastText} + + + ); + })} +
    +
    + ); +}; + +export default Notifications; From f928e2fb68873db72a4717ebda27190c075836d6 Mon Sep 17 00:00:00 2001 From: Max Daunarovich Date: Fri, 8 Apr 2022 13:03:47 +0300 Subject: [PATCH 37/49] Clean up Notifications bits from ControlPanel --- .../CadenceEditor/ControlPanel/index.tsx | 97 +------------------ 1 file changed, 3 insertions(+), 94 deletions(-) diff --git a/src/components/CadenceEditor/ControlPanel/index.tsx b/src/components/CadenceEditor/ControlPanel/index.tsx index bb0a47bc..8a3e8006 100644 --- a/src/components/CadenceEditor/ControlPanel/index.tsx +++ b/src/components/CadenceEditor/ControlPanel/index.tsx @@ -61,29 +61,10 @@ const ControlPanel: React.FC = (props) => { return null; } - // ============================================================================ - // NOTIFICATION BITS ----------------------------------------------------------- - // TODO: Refactor this one - const clientOnNotification = useRef(null); - - const [notifications, setNotifications] = useState<{ - [identifier: string]: string[]; - }>({}); - - const removeNotification = (set: any, id: number) => { - set((prev: any[]) => { - delete prev[id]; - return { - ...prev, - }; - }); - }; - // =========================================================================== // GLOBAL HOOKS const { languageClient } = useContext(CadenceCheckerContext); const { project, active, isSavingCode, lastSigners } = useProject(); - const { theme } = useThemeUI(); // HOOKS ------------------------------------------------------------------- const [executionArguments, setExecutionArguments] = useState({}); @@ -100,8 +81,11 @@ const ControlPanel: React.FC = (props) => { // Handles problems, hints and info for checked code const [problemsList, setProblemsList] = useState({}); + // REFS ------------------------------------------------------------------- // Holds reference to constraining div for floating window const constraintsRef = useRef(); + // Holds reference to Disposable callback for languageClient + const clientOnNotification = useRef(null); // =========================================================================== // METHODS ------------------------------------------------------------------ @@ -430,81 +414,6 @@ const ControlPanel: React.FC = (props) => {
    - -
      - - {Object.keys(notifications).map((id) => { - const updatedAccounts = notifications[id]; - - let updatedStorageAccts: string[] = []; - updatedAccounts.map((acct: any) => { - const addr = acct.address; - const acctNum = addr.charAt(addr.length - 1); - const acctHex = `0x0${acctNum}`; - updatedStorageAccts.push(acctHex); - }); - - // render a new list item for each new id in 'notifications' state - return ( - lastSigners && - updatedStorageAccts && ( - - - - removeNotification(setNotifications, parseInt(id)) - } - > - - - - - - {`Account${lastSigners?.length > 1 ? 's' : ''} - ${lastSigners.join(', ')} - updated the storage in - account${updatedStorageAccts?.length > 1 ? 's' : ''} - ${updatedStorageAccts.join(', ')}.`} - - - - ) - ); - })} - -
    -
    ); }; From 4eefa06b38291e1508cd6ee4e001b18ea0ad3e38 Mon Sep 17 00:00:00 2001 From: Max Daunarovich Date: Fri, 8 Apr 2022 13:03:57 +0300 Subject: [PATCH 38/49] Update CadenceEditor --- src/components/CadenceEditor/index.tsx | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/components/CadenceEditor/index.tsx b/src/components/CadenceEditor/index.tsx index 39e7908e..6332f671 100644 --- a/src/components/CadenceEditor/index.tsx +++ b/src/components/CadenceEditor/index.tsx @@ -1,15 +1,18 @@ import React, { useEffect, useRef, useState } from 'react'; import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; -import configureCadence, { CADENCE_LANGUAGE_ID } from 'util/cadence'; -import { EditorContainer } from './components'; + import { useProject } from 'providers/Project/projectHooks'; -import debounce from 'util/debounce'; import { EntityType } from 'providers/Project'; +import debounce from 'util/debounce'; +import configureCadence, { CADENCE_LANGUAGE_ID } from 'util/cadence'; + +import Notifications from './Notifications'; import ControlPanel from './ControlPanel'; +import { EditorContainer } from './components'; -const MONACO_CONTAINER_ID = 'monaco-container'; +import { EditorState } from './types'; -type EditorState = { model: any; viewState: any }; +const MONACO_CONTAINER_ID = 'monaco-container'; const CadenceEditor = (props: any) => { const project = useProject(); @@ -175,6 +178,7 @@ const CadenceEditor = (props: any) => { return ( + ); }; From c31d186c391ac1e49435b743de21ecf8398c9e85 Mon Sep 17 00:00:00 2001 From: Max Daunarovich Date: Fri, 8 Apr 2022 13:47:30 +0300 Subject: [PATCH 39/49] Clean up --- src/App.tsx | 41 +- .../CadenceEditor/ControlPanel/components.tsx | 2 +- .../CadenceEditor/ControlPanel/index.tsx | 9 +- .../CadenceEditor/ControlPanel/types.tsx | 4 - .../Notifications/components.tsx | 2 +- .../CadenceEditor/Notifications/index.tsx | 8 +- src/components/CadenceEditor/index.tsx | 5 +- src/components/CadenceEditorOld.tsx | 458 ------------------ src/containers/Editor/components.tsx | 93 ++-- src/hooks/useLanguageServer.ts | 10 +- src/providers/Project/index.tsx | 2 +- tsconfig.json | 4 +- 12 files changed, 74 insertions(+), 564 deletions(-) delete mode 100644 src/components/CadenceEditorOld.tsx diff --git a/src/App.tsx b/src/App.tsx index 8261fb7c..b0f61321 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,18 +1,20 @@ import React, { useEffect } from 'react'; import { Global } from '@emotion/core'; -import { ThemeProvider, Text } from 'theme-ui'; +import { ThemeProvider } from 'theme-ui'; import { Router, globalHistory } from '@reach/router'; import { ApolloProvider } from '@apollo/react-hooks'; -import AppMobileWrapper from 'containers/AppMobileWrapper'; -import BrowserDetector from 'components/BrowserDetector'; +import 'reset-css'; + import * as GoogleAnalytics from 'util/google-analytics'; import client from 'api/apollo/client'; -import globalStyles from './globalStyles'; -import theme from './theme'; -import 'reset-css'; import Playground from 'containers/Editor'; +import AppMobileWrapper from 'containers/AppMobileWrapper'; +import BrowserDetector from 'components/BrowserDetector'; + import FourOhFour from './pages/404'; +import globalStyles from './globalStyles'; +import theme from './theme'; GoogleAnalytics.initialize(process.env.GA_TRACKING_CODE); @@ -20,19 +22,6 @@ const Base = (props: any) => { return
    {props.children}
    ; }; -const version = ( - - v0.3.5 - -); - const App: React.FC = () => { useEffect(() => { // record initial pageview @@ -50,15 +39,15 @@ const App: React.FC = () => { -{/* */} + - - - - - + + + + + -{/* */} + diff --git a/src/components/CadenceEditor/ControlPanel/components.tsx b/src/components/CadenceEditor/ControlPanel/components.tsx index 6d47668b..9ffabec2 100644 --- a/src/components/CadenceEditor/ControlPanel/components.tsx +++ b/src/components/CadenceEditor/ControlPanel/components.tsx @@ -1,4 +1,4 @@ -import React, { forwardRef } from 'react'; +import React from 'react'; import { motion } from 'framer-motion'; import styled from 'styled-components'; import theme from '../../../theme'; diff --git a/src/components/CadenceEditor/ControlPanel/index.tsx b/src/components/CadenceEditor/ControlPanel/index.tsx index 8a3e8006..920dda04 100644 --- a/src/components/CadenceEditor/ControlPanel/index.tsx +++ b/src/components/CadenceEditor/ControlPanel/index.tsx @@ -1,10 +1,7 @@ // External Modules import React, { useContext, useEffect, useRef, useState } from 'react'; import { FaRegCheckCircle, FaRegTimesCircle, FaSpinner } from 'react-icons/fa'; -import { AiFillCloseCircle } from 'react-icons/ai'; -import { motion, AnimatePresence } from 'framer-motion'; import { ExecuteCommandRequest } from 'monaco-languageclient'; -import { useThemeUI, Box, Text, Flex } from 'theme-ui'; import { IPosition, editor as monacoEditor, @@ -14,7 +11,6 @@ import { import { CadenceCheckerContext } from 'providers/CadenceChecker'; import { EntityType } from 'providers/Project'; import { useProject } from 'providers/Project/projectHooks'; -import { RemoveToastButton } from 'layout/RemoveToastButton'; import { CadenceProblem, formatMarker, @@ -50,13 +46,12 @@ import { import { ControlContainer, - ToastContainer, HoverPanel, StatusMessage, } from '../../Arguments/styles'; const ControlPanel: React.FC = (props) => { - // We should not render this component if editor is non existent + // We should not render this component if editor is non-existent if (!props.editor) { return null; } @@ -64,7 +59,7 @@ const ControlPanel: React.FC = (props) => { // =========================================================================== // GLOBAL HOOKS const { languageClient } = useContext(CadenceCheckerContext); - const { project, active, isSavingCode, lastSigners } = useProject(); + const { project, active, isSavingCode } = useProject(); // HOOKS ------------------------------------------------------------------- const [executionArguments, setExecutionArguments] = useState({}); diff --git a/src/components/CadenceEditor/ControlPanel/types.tsx b/src/components/CadenceEditor/ControlPanel/types.tsx index c04c44e4..0f958302 100644 --- a/src/components/CadenceEditor/ControlPanel/types.tsx +++ b/src/components/CadenceEditor/ControlPanel/types.tsx @@ -1,7 +1,3 @@ -import { EntityType } from 'providers/Project'; -import { Highlight, ProblemsList } from 'util/language-syntax-errors'; -import { MonacoLanguageClient } from 'monaco-languageclient'; -import { Argument } from 'components/Arguments/types'; import { Account } from 'api/apollo/generated/graphql'; import { editor as monacoEditor } from 'monaco-editor/esm/vs/editor/editor.api'; diff --git a/src/components/CadenceEditor/Notifications/components.tsx b/src/components/CadenceEditor/Notifications/components.tsx index 3926788f..ce1a2705 100644 --- a/src/components/CadenceEditor/Notifications/components.tsx +++ b/src/components/CadenceEditor/Notifications/components.tsx @@ -1,7 +1,7 @@ import React from 'react'; import styled from 'styled-components'; import { motion } from 'framer-motion'; -import { Box, Flex, Text, useThemeUI } from 'theme-ui'; +import { Box, Flex, Text } from 'theme-ui'; import theme from '../../../theme'; diff --git a/src/components/CadenceEditor/Notifications/index.tsx b/src/components/CadenceEditor/Notifications/index.tsx index caa31630..cd1ab873 100644 --- a/src/components/CadenceEditor/Notifications/index.tsx +++ b/src/components/CadenceEditor/Notifications/index.tsx @@ -1,6 +1,5 @@ import React, { useEffect, useState } from 'react'; import { useProject } from 'providers/Project/projectHooks'; -import { useThemeUI } from 'theme-ui'; import { SingleToast, @@ -12,14 +11,13 @@ import { } from './components'; import { AiFillCloseCircle } from 'react-icons/ai'; -const Notifications = (props: any) => { +const Notifications = () => { // =========================================================================== // GLOBAL HOOKS const { project, lastSigners } = useProject(); - const { theme } = useThemeUI(); // HOOKS ------------------------------------------------------------------- - const [_, setProjectAccts] = useState(project.accounts); + const [_, setProjectAccounts] = useState(project.accounts); const [counter, setCounter] = useState(0); const [notifications, setNotifications] = useState<{ [identifier: string]: string[]; @@ -37,7 +35,7 @@ const Notifications = (props: any) => { // EFFECTS ------------------------------------------------------------------- useEffect(() => { - setProjectAccts((prevAccounts) => { + setProjectAccounts((prevAccounts) => { const latestAccounts = project.accounts; const updatedAccounts = latestAccounts.filter( (latestAccount, index) => diff --git a/src/components/CadenceEditor/index.tsx b/src/components/CadenceEditor/index.tsx index 6332f671..c576358b 100644 --- a/src/components/CadenceEditor/index.tsx +++ b/src/components/CadenceEditor/index.tsx @@ -26,12 +26,15 @@ const CadenceEditor = (props: any) => { const [editorStates, setEditorStates] = useState({}); + // TODO: restore view state in next implementation + /* const saveEditorState = (id: string, viewState: any) => { setEditorStates({ ...editorStates, [id]: viewState, }); }; + */ // This method is used to retrieve previous MonacoEditor state const getOrCreateEditorState = (id: string, code: string): EditorState => { @@ -82,7 +85,7 @@ const CadenceEditor = (props: any) => { }; // Method to use, when model was changed - const debouncedModelChange = debounce((event) => { + const debouncedModelChange = debounce(() => { // we will ignore text line, cause onChange is different for readme and other scripts // @ts-ignore project.active.onChange(editor.getValue()); diff --git a/src/components/CadenceEditorOld.tsx b/src/components/CadenceEditorOld.tsx deleted file mode 100644 index fe0a281b..00000000 --- a/src/components/CadenceEditorOld.tsx +++ /dev/null @@ -1,458 +0,0 @@ -import React from 'react'; -import styled from '@emotion/styled'; -import { keyframes } from '@emotion/core'; -import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; -import { extractSigners } from "util/parser"; - - -import configureCadence, { CADENCE_LANGUAGE_ID } from 'util/cadence'; -import { - CadenceCheckCompleted, - CadenceLanguageServer, - Callbacks, -} from 'util/language-server'; -import { createCadenceLanguageClient } from 'util/language-client'; -import { EntityType } from 'providers/Project'; -import Arguments from 'components/Arguments'; -import { Argument } from 'components/Arguments/types'; -import { - CadenceProblem, - formatMarker, - goTo, - Highlight, - ProblemsList, -} from 'util/language-syntax-errors'; -import { - MonacoLanguageClient, - ExecuteCommandRequest, -} from 'monaco-languageclient'; -import { WithShowProps } from "containers/Editor/components"; - -const blink = keyframes` - 50% { - opacity: 0.5; - } -`; - -const EditorContainer = styled.div` - width: 100%; - height: 100%; - position: relative; - - display: ${({ show }) => (show ? 'block' : 'none')}; - - .drag-box { - width: fit-content; - height: fit-content; - position: absolute; - right: 30px; - top: 0; - z-index: 12; - } - - .constraints { - width: 96vw; - height: 90vh; - position: fixed; - left: 2vw; - right: 2vw; - top: 2vw; - bottom: 2vw; - pointer-events: none; - } - - .playground-syntax-error-hover { - background-color: rgba(238, 67, 30, 0.1); - } - - .playground-syntax-error-hover-selection { - background-color: rgba(238, 67, 30, 0.3); - border-radius: 3px; - animation: ${blink} 1s ease-in-out infinite; - } - - .playground-syntax-warning-hover { - background-color: rgb(238, 169, 30, 0.1); - } - - .playground-syntax-warning-hover-selection { - background-color: rgb(238, 169, 30, 0.3); - border-radius: 3px; - animation: ${blink} 1s ease-in-out infinite; - } - - .playground-syntax-info-hover { - background-color: rgb(85, 238, 30, 0.1); - } - - .playground-syntax-info-hover-selection { - background-color: rgb(85, 238, 30, 0.3); - border-radius: 3px; - animation: ${blink} 1s ease-in-out infinite; - } - - .playground-syntax-hint-hover, - .playground-syntax-unknown-hover { - background-color: rgb(160, 160, 160, 0.1); - } - - .playground-syntax-hint-hover-selection, - .playground-syntax-unknown-hover-selection { - background-color: rgb(160, 160, 160, 0.3); - border-radius: 3px; - animation: ${blink} 1s ease-in-out infinite; - } -`; - -type EditorState = { - model: any; - viewState: any; -}; - -type CadenceEditorProps = { - type: EntityType; - code: string; - mount: string; - show: boolean; - onChange: any; - activeId: string; -}; - -type CadenceEditorState = { - args: { [key: string]: Argument[] }; - problems: { [key: string]: ProblemsList }; -}; - -class CadenceEditor extends React.Component< - CadenceEditorProps, - CadenceEditorState -> { - editor: monaco.editor.ICodeEditor; - languageClient?: MonacoLanguageClient; - _subscription: any; - editorStates: { [key: string]: EditorState }; - clients: { [key: string]: MonacoLanguageClient } - private callbacks: Callbacks; - - constructor(props: { - code: string; - mount: string; - show: boolean; - onChange: any; - activeId: string; - type: EntityType; - languageServer: any; - callbacks: Callbacks; - serverReady: boolean; - }) { - super(props); - - this.editorStates = {}; - this.clients = {}; - this.handleResize = this.handleResize.bind(this); - window.addEventListener('resize', this.handleResize); - configureCadence(); - - this.state = { - args: {}, - problems: {}, - }; - } - - handleResize() { - this.editor && this.editor.layout(); - } - - async componentDidMount() { - await this.initEditor() - - // TODO: refactor -/* if (this.props.serverReady) { - await this.loadLanguageClient() - }*/ - } - - async initEditor() { - this.editor = monaco.editor.create( - document.getElementById(this.props.mount), - { - theme: 'vs-light', - language: CADENCE_LANGUAGE_ID, - minimap: { - enabled: false, - }, - }, - ); - this._subscription = this.editor.onDidChangeModelContent((event: any) => { - this.props.onChange(this.editor.getValue(), event); - }); - - const state = this.getOrCreateEditorState( - this.props.activeId, - this.props.code, - ); - this.editor.setModel(state.model); - this.editor.focus(); - } - -/* private async loadLanguageClient() { - this.callbacks = this.props.callbacks; - const clientId = this.props.activeId; - if(!this.clients[clientId]){ - this.languageClient = createCadenceLanguageClient(this.callbacks); - this.languageClient.start(); - await this.languageClient.onReady() - this.languageClient.onNotification( - CadenceCheckCompleted.methodName, - async (result: CadenceCheckCompleted.Params) => { - if (result.valid) { - const params = await this.getParameters(); - this.setExecutionArguments(params); - } - this.processMarkers(); - }, - ); - this.clients[clientId] = this.languageClient; - } else { - this.languageClient = this.clients[clientId] - } - }*/ - - private async getParameters() { - await this.languageClient.onReady(); - - try { - const args = await this.languageClient.sendRequest( - ExecuteCommandRequest.type, - { - command: 'cadence.server.getEntryPointParameters', - arguments: [this.editor.getModel().uri.toString()], - }, - ); - return args || []; - } catch (error) { - console.error(error); - } - } - - processMarkers() { - const model = this.editor.getModel(); - const modelMarkers = monaco.editor.getModelMarkers({ resource: model.uri }); - const errors = modelMarkers.reduce( - (acc: { [key: string]: CadenceProblem[] }, marker) => { - const mappedMarker: CadenceProblem = formatMarker(marker); - acc[mappedMarker.type].push(mappedMarker); - return acc; - }, - { - error: [], - warning: [], - info: [], - hint: [], - }, - ); - - const { activeId } = this.props; - - this.setState({ - problems: { - [activeId]: errors, - }, - }); - } - - setExecutionArguments(args: Argument[]) { - const { activeId } = this.props; - this.setState({ - args: { - [activeId]: args, - }, - }); - } - - getOrCreateEditorState(id: string, code: string): EditorState { - const existingState = this.editorStates[id]; - - if (existingState !== undefined) { - return existingState; - } - - const model = monaco.editor.createModel(code, CADENCE_LANGUAGE_ID); - - const state: EditorState = { - model, - viewState: null, - }; - - this.editorStates[id] = state; - - return state; - } - - saveEditorState(id: string, viewState: any) { - this.editorStates[id].viewState = viewState; - } - - switchEditor(prevId: string, newId: string) { - const newState = this.getOrCreateEditorState(newId, this.props.code); - - const currentViewState = this.editor.saveViewState(); - - this.saveEditorState(prevId, currentViewState); - - this.editor.setModel(newState.model); - this.editor.restoreViewState(newState.viewState); - this.editor.focus(); - } - - componentWillUnmount() { - this.destroyMonaco(); - window.removeEventListener('resize', this.handleResize); - if (this.callbacks && this.callbacks.onClientClose) { - this.callbacks.onClientClose(); - } - } - - async componentDidUpdate(prevProps: any) { - if (this.props.activeId !== prevProps.activeId) { - await this.swapMonacoEditor(prevProps.activeId, this.props.activeId) - } - - - // TODO: refactor - /* - const serverStatusChanged = this.props.serverReady !== prevProps.serverReady - const activeIdChanged = this.props.activeId !== prevProps.activeId - const typeChanged = this.props.type !== prevProps.type - - if (serverStatusChanged || activeIdChanged || typeChanged) { - if (this.props.callbacks.toServer !== null) { - await this.loadLanguageClient() - } - }*/ - } - - async swapMonacoEditor(prev: any, current: any) { - await this.destroyMonaco(); - await this.initEditor(); - this.switchEditor(prev, current); - } - - destroyMonaco() { - if (this.editor) { - this.editor.dispose(); - const model = this.editor.getModel(); - if (model) { - model.dispose(); - } - } - if (this._subscription) { - this._subscription.dispose(); - } - } - - extract(code: string, keyWord: string): string[] { - const target = code - .split(/\r\n|\n|\r/) - .find((line) => line.includes(keyWord)); - - if (target) { - const match = target.match(/(?:\()(.*)(?:\))/); - if (match) { - return match[1].split(',').map((item) => item.replace(/\s*/g, '')); - } - } - return []; - } - - hover(highlight: Highlight): void { - const { startLine, startColumn, endLine, endColumn, color } = highlight; - const model = this.editor.getModel(); - - const selection = model.getAllDecorations().find((item: any) => { - return ( - item.range.startLineNumber === startLine && - item.range.startColumn === startColumn - ); - }); - - const selectionEndLine = selection - ? selection.range.endLineNumber - : endLine; - const selectionEndColumn = selection - ? selection.range.endColumn - : endColumn; - - const highlightLine = [ - { - range: new monaco.Range(startLine, startColumn, endLine, endColumn), - options: { - isWholeLine: true, - className: `playground-syntax-${color}-hover`, - }, - }, - { - range: new monaco.Range( - startLine, - startColumn, - selectionEndLine, - selectionEndColumn, - ), - options: { - isWholeLine: false, - className: `playground-syntax-${color}-hover-selection`, - }, - }, - ]; - this.editor.getModel().deltaDecorations([], highlightLine); - this.editor.revealLineInCenter(startLine); - } - - hideDecorations(): void { - const model = this.editor.getModel(); - let current = model - .getAllDecorations() - .filter((item) => { - const { className } = item.options; - return className?.includes('playground-syntax'); - }) - .map((item) => item.id); - - model.deltaDecorations(current, []); - } - - render() { - const { type, code, show } = this.props; - - /// Get a list of args from language server - const { activeId } = this.props; - const { args, problems } = this.state; - const list = args[activeId] || []; - - /// Extract number of signers from code - const signers = extractSigners(code).length - const problemsList: ProblemsList = problems[activeId] || { - error: [], - warning: [], - hint: [], - info: [], - }; - return ( - - this.hover(highlight)} - hideDecorations={() => this.hideDecorations()} - goTo={(position: monaco.IPosition) => goTo(this.editor, position)} - editor={this.editor} - languageClient={this.languageClient} - /> - - ); - } -} - -export default CadenceEditor; diff --git a/src/containers/Editor/components.tsx b/src/containers/Editor/components.tsx index 21ade213..485b2915 100644 --- a/src/containers/Editor/components.tsx +++ b/src/containers/Editor/components.tsx @@ -10,8 +10,11 @@ import { Editor as EditorRoot } from 'layout/Editor'; import { Heading } from 'layout/Heading'; import { EntityType, ActiveEditor } from 'providers/Project'; import { useProject } from 'providers/Project/projectHooks'; -import { PLACEHOLDER_DESCRIPTION, PLACEHOLDER_TITLE } from "providers/Project/projectDefault"; -import {Account, Project} from 'api/apollo/generated/graphql' +import { + PLACEHOLDER_DESCRIPTION, + PLACEHOLDER_TITLE, +} from 'providers/Project/projectDefault'; +import { Account, Project } from 'api/apollo/generated/graphql'; import debounce from 'util/debounce'; import Mixpanel from 'util/mixpanel'; @@ -34,11 +37,11 @@ import { ProjectInfoContainer, ProjectDescription, ProjectHeading, - ReadmeHtmlContainer -} from './layout-components' + ReadmeHtmlContainer, +} from './layout-components'; -import { decodeText } from "util/readme"; -import CadenceEditor from "components/CadenceEditor" +import { decodeText } from 'util/readme'; +import CadenceEditor from 'components/CadenceEditor'; export interface WithShowProps { show: boolean; @@ -168,30 +171,29 @@ function getActiveId(project: Project, active: ActiveEditor): string { } } - const usePrevious = (value: any) => { const ref = useRef(); useEffect(() => { ref.current = value; //assign the value of ref to the argument - },[value]); //this code will run when the value of 'value' changes + }, [value]); //this code will run when the value of 'value' changes return ref.current; //in the end, return the current ref value. -} +}; // This method const compareContracts = (prev: Account[], current: Account[]) => { for (let i = 0; i < prev.length; i++) { - if (prev[i].deployedCode !== current[i].deployedCode){ - return false + if (prev[i].deployedCode !== current[i].deployedCode) { + return false; } } - return true -} + return true; +}; -const MAX_DESCRIPTION_SIZE = Math.pow(1024, 2) // 1mb of storage can be saved into readme field +const MAX_DESCRIPTION_SIZE = Math.pow(1024, 2); // 1mb of storage can be saved into readme field const calculateSize = (readme: string) => { - const { size } = new Blob([readme]) - return size >= MAX_DESCRIPTION_SIZE -} + const { size } = new Blob([readme]); + return size >= MAX_DESCRIPTION_SIZE; +}; const EditorContainer: React.FC = ({ isLoading, @@ -199,19 +201,23 @@ const EditorContainer: React.FC = ({ active, }) => { const [title, setTitle] = useState( - decodeText(project.title) + decodeText(project.title), ); const [description, setDescription] = useState( - decodeText(project.description) + decodeText(project.description), ); const [readme, setReadme] = useState(project.readme); + //@ts-ignore const [code, setCode] = useState(''); + //@ts-ignore const [activeId, setActiveId] = useState(null); - const projectAccess = useProject() + const projectAccess = useProject(); - const [descriptionOverflow, setDescriptionOverflow] = useState(calculateSize(project.readme)) + const [descriptionOverflow, setDescriptionOverflow] = useState( + calculateSize(project.readme), + ); useEffect(() => { if (isLoading) { @@ -228,34 +234,20 @@ const EditorContainer: React.FC = ({ } }, [isLoading, active, projectAccess.project]); - const getCode = (project: any) => (address: string) =>{ - const number = parseInt(address, 16); - if (!number) { - return; - } - - const index = number - 1 - if (index < 0 || index >= project.accounts.length) { - return; - } - let code = project.accounts[index].deployedCode; - return code - } - - const previousProjectState = usePrevious(project) + const previousProjectState = usePrevious(project); // This hook will listen for project updates and if one of the contracts has been changed, // it will reload language server - useEffect(()=>{ - if (previousProjectState !== undefined){ + useEffect(() => { + if (previousProjectState !== undefined) { // @ts-ignore - const previousAccounts = previousProjectState.accounts || [] - const equal = compareContracts(previousAccounts, project.accounts) - if (!equal){ + const previousAccounts = previousProjectState.accounts || []; + const equal = compareContracts(previousAccounts, project.accounts); + if (!equal) { // reloadServer() } } - }, [project]) + }, [project]); const onEditorChange = debounce(active.onChange); const updateProject = ( @@ -270,10 +262,9 @@ const EditorContainer: React.FC = ({ }; const isReadmeEditor = active.type === 4; - const readmeLabel = `README.md${descriptionOverflow - ? " - Content can't be more than 1Mb in size" - : "" - }` + const readmeLabel = `README.md${ + descriptionOverflow ? " - Content can't be more than 1Mb in size" : '' + }`; return ( @@ -323,10 +314,10 @@ const EditorContainer: React.FC = ({ { - const overflow = calculateSize(readme) - setDescriptionOverflow(overflow) + const overflow = calculateSize(readme); + setDescriptionOverflow(overflow); setReadme(readme); - if(!overflow){ + if (!overflow) { updateProject(title, description, readme); } }} @@ -336,7 +327,7 @@ const EditorContainer: React.FC = ({ )} {/* This is Cadence Editor */} -{/* = ({ onChange={(code: string, _: any) => onEditorChange(code)} show={!isReadmeEditor} />*/} - + diff --git a/src/hooks/useLanguageServer.ts b/src/hooks/useLanguageServer.ts index 9db6171f..fd58a2bc 100644 --- a/src/hooks/useLanguageServer.ts +++ b/src/hooks/useLanguageServer.ts @@ -1,4 +1,4 @@ -import {useEffect, useState, useRef, useMemo} from 'react'; +import {useEffect, useState, useMemo} from 'react'; import { CadenceLanguageServer, Callbacks } from 'util/language-server'; import { MonacoServices } from 'monaco-languageclient/lib/monaco-services'; import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; @@ -11,17 +11,15 @@ let monacoServicesInstalled = false; async function startLanguageServer(callbacks: any, getCode: any, ops) { const { setLanguageServer, setCallbacks } = ops; const server = await CadenceLanguageServer.create(callbacks); - new Promise((resolve, reject) => { + new Promise(() => { let checkInterval = setInterval(() => { // .toServer() method is populated by language server // if it was not properly started or in progress it will be "null" if (callbacks.toServer !== null) { -// console.log(callbacks.toServer); clearInterval(checkInterval); callbacks.getAddressCode = getCode; setCallbacks(callbacks); setLanguageServer(server); - console.log(server) console.log("%c LS: Is Up!",'color: #00FF00') } }, 100); @@ -43,8 +41,6 @@ const launchLanguageClient = async ( export default function useLanguageServer() { const project = useProject(); - window.project = project - const accountUpdates = useRef(1); // Language Server Callbacks let initialCallbacks: Callbacks = { @@ -108,7 +104,7 @@ export default function useLanguageServer() { },[project.project.accounts]) // TODO: Disable this, once the cadence language server package is updated - // useEffect(debouncedServerRestart, [project.project.accounts, project.active]); + useEffect(debouncedServerRestart, [project.project.accounts, project.active]); useEffect(() => { diff --git a/src/providers/Project/index.tsx b/src/providers/Project/index.tsx index 4ddb7a5a..03414d3e 100644 --- a/src/providers/Project/index.tsx +++ b/src/providers/Project/index.tsx @@ -1,4 +1,4 @@ -import React, {createContext, useState, useRef, useMemo} from 'react'; +import React, {createContext, useState, useMemo} from 'react'; import { useApolloClient, useQuery } from '@apollo/react-hooks'; import { navigate, useLocation, Redirect } from '@reach/router'; import ProjectMutator from './projectMutator'; diff --git a/tsconfig.json b/tsconfig.json index 6b98cb35..aef42d62 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,10 +7,10 @@ "allowJs": false, "moduleResolution": "node", "allowSyntheticDefaultImports": true, -/* - "noImplicitAny": false, "noUnusedLocals": true, "noUnusedParameters": true, +/* + "noImplicitAny": false, */ "removeComments": false, "preserveConstEnums": true, From 9114d6bbb94c73e291c4d88a8efa323ce6119c37 Mon Sep 17 00:00:00 2001 From: Max Daunarovich Date: Fri, 8 Apr 2022 13:55:02 +0300 Subject: [PATCH 40/49] Clear decorations, when errors are fixed --- src/components/Arguments/components.tsx | 59 +++++++++++-------------- 1 file changed, 27 insertions(+), 32 deletions(-) diff --git a/src/components/Arguments/components.tsx b/src/components/Arguments/components.tsx index 504c6761..f9e207c1 100644 --- a/src/components/Arguments/components.tsx +++ b/src/components/Arguments/components.tsx @@ -1,5 +1,10 @@ import React, { useState } from 'react'; -import { FaArrowCircleRight, FaExclamationTriangle, FaCaretSquareUp, FaCaretSquareDown } from 'react-icons/fa'; +import { + FaArrowCircleRight, + FaExclamationTriangle, + FaCaretSquareUp, + FaCaretSquareDown, +} from 'react-icons/fa'; import { EntityType } from 'providers/Project'; import Button from 'components/Button'; import { useProject } from 'providers/Project/projectHooks'; @@ -46,19 +51,11 @@ export const ArgumentsTitle: React.FC = (props) => { {errors} )} - {expanded ? - - : - - } + {expanded ? ( + + ) : ( + + )} ); @@ -104,7 +101,7 @@ const renderMessage = (message: string) => { let spanClass = getSpanClass(message); const { items } = message.split(' ').reduce( - (acc, item,i) => { + (acc, item, i) => { let current = acc.items[acc.items.length - 1]; if (acc.startNew) { acc.startNew = false; @@ -115,7 +112,9 @@ const renderMessage = (message: string) => { if (item.startsWith('`')) { acc.startNew = true; const span = ( - {item.replace(/`/g, '')} + + {item.replace(/`/g, '')} + ); acc.items.push(span); acc.startNew = true; @@ -135,10 +134,12 @@ const renderMessage = (message: string) => { export const ErrorsList: React.FC = (props) => { const { list, actions } = props; - const {goTo, hideDecorations, hover} = actions + const { goTo, hideDecorations, hover } = actions; if (list.length === 0) { + hideDecorations() return null; } + return ( @@ -167,9 +168,9 @@ export const ErrorsList: React.FC = (props) => { }; export const Hints: React.FC = (props: HintsProps) => { - const [ expanded, setExpanded ] = useState(true); + const [expanded, setExpanded] = useState(true); const { problems, actions } = props; - const { goTo, hideDecorations, hover } = actions + const { goTo, hideDecorations, hover } = actions; const toggle = () => { setExpanded(!expanded); }; @@ -178,7 +179,7 @@ export const Hints: React.FC = (props: HintsProps) => { return null; } const fullList = [...problems.warning, ...problems.info]; - const hintsAmount = fullList.length + const hintsAmount = fullList.length; return ( @@ -189,17 +190,11 @@ export const Hints: React.FC = (props: HintsProps) => { {hintsAmount} )} - {expanded ? - - : - - } + {expanded ? ( + + ) : ( + + )} {expanded && ( @@ -230,7 +225,7 @@ export const Hints: React.FC = (props: HintsProps) => { const getLabel = (type: EntityType) => { const { project, active } = useProject(); const { accounts } = project; - + switch (true) { case type === EntityType.Account: return accounts[active.index].deployedCode ? 'Redeploy' : 'Deploy'; From 80331ad2fe87a2bcbc97157f1d5ff56c837172d1 Mon Sep 17 00:00:00 2001 From: Max Daunarovich Date: Fri, 8 Apr 2022 15:01:10 +0300 Subject: [PATCH 41/49] Memoize list of arguments --- src/components/CadenceEditor/ControlPanel/index.tsx | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/components/CadenceEditor/ControlPanel/index.tsx b/src/components/CadenceEditor/ControlPanel/index.tsx index 920dda04..e02ee6b1 100644 --- a/src/components/CadenceEditor/ControlPanel/index.tsx +++ b/src/components/CadenceEditor/ControlPanel/index.tsx @@ -1,5 +1,5 @@ // External Modules -import React, { useContext, useEffect, useRef, useState } from 'react'; +import React, { useContext, useEffect, useMemo, useRef, useState } from 'react'; import { FaRegCheckCircle, FaRegTimesCircle, FaSpinner } from 'react-icons/fa'; import { ExecuteCommandRequest } from 'monaco-languageclient'; import { @@ -177,7 +177,6 @@ const ControlPanel: React.FC = (props) => { clientOnNotification.current = languageClient.onNotification( CadenceCheckCompleted.methodName, async (result: CadenceCheckCompleted.Params) => { - console.log('%cCheck completed', { color: 'green' }); if (result.valid) { const params = await getParameters(); const key = getActiveKey(); @@ -226,8 +225,6 @@ const ControlPanel: React.FC = (props) => { setProcessingStatus(true); } - // TODO: implement algorithm for drilling down dictionaries - const fixed = list.map((arg) => { const { name, type } = arg; let value = values[name]; @@ -313,6 +310,11 @@ const ControlPanel: React.FC = (props) => { }).then(); }; + // MEMOIZED ----------------------------------------------------------------- + // we need to wrap it in useMemo, cause otherwise it might push component into infinite rerender + // as "getArguments" will return new empty array on each render + const list = useMemo(getArguments, [active]); + // VARIABLES AND CONSTANTS ------------------------------------------------- const { editor } = props; const { type } = active; @@ -330,7 +332,6 @@ const ControlPanel: React.FC = (props) => { const { accounts } = project; const signersAccounts = selected.map((i) => accounts[i]); - const list = getArguments(); const actions = { goTo: (position: IPosition) => goTo(editor, position), hideDecorations: () => hideDecorations(editor), @@ -355,6 +356,7 @@ const ControlPanel: React.FC = (props) => { }, [languageClient, active]); useEffect(() => { + console.log('Call again!'); validate(list, values); }, [list, values]); From 28b447462ce0564231077671a3f8313e9c6213f2 Mon Sep 17 00:00:00 2001 From: Max Daunarovich Date: Fri, 8 Apr 2022 15:01:27 +0300 Subject: [PATCH 42/49] Speed up refresh for debounce model change --- src/components/CadenceEditor/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/CadenceEditor/index.tsx b/src/components/CadenceEditor/index.tsx index c576358b..a19aab02 100644 --- a/src/components/CadenceEditor/index.tsx +++ b/src/components/CadenceEditor/index.tsx @@ -89,7 +89,7 @@ const CadenceEditor = (props: any) => { // we will ignore text line, cause onChange is different for readme and other scripts // @ts-ignore project.active.onChange(editor.getValue()); - }, 150); + }, 50); useEffect(configureCadence, []); useEffect(() => { From ab1c6c55e9583b0fd2f952f0077d94f4cbc5295f Mon Sep 17 00:00:00 2001 From: Max Daunarovich Date: Fri, 8 Apr 2022 15:01:41 +0300 Subject: [PATCH 43/49] Pass key proper way --- src/components/CadenceEditor/Notifications/components.tsx | 3 +-- src/components/CadenceEditor/Notifications/index.tsx | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/CadenceEditor/Notifications/components.tsx b/src/components/CadenceEditor/Notifications/components.tsx index ce1a2705..5193be92 100644 --- a/src/components/CadenceEditor/Notifications/components.tsx +++ b/src/components/CadenceEditor/Notifications/components.tsx @@ -64,9 +64,8 @@ export const Content = ({ children }) => { }; export const SingleToast = (props: any) => { - const { id, children } = props; + const { children } = props; const toastProps = { - key: id, layout: true, initial: { opacity: 0, y: 50, scale: 0.3 }, animate: { opacity: 1, y: 0, scale: 1 }, diff --git a/src/components/CadenceEditor/Notifications/index.tsx b/src/components/CadenceEditor/Notifications/index.tsx index cd1ab873..72264547 100644 --- a/src/components/CadenceEditor/Notifications/index.tsx +++ b/src/components/CadenceEditor/Notifications/index.tsx @@ -97,7 +97,7 @@ const Notifications = () => { const { id, toastText, onClick } = toast; return ( - + From 51b2ed5611269df3f6c1047796126ce3fadb6446 Mon Sep 17 00:00:00 2001 From: Max Daunarovich Date: Tue, 12 Apr 2022 19:43:35 +0300 Subject: [PATCH 44/49] Refactor LSP status --- src/components/CadenceVersion.tsx | 50 ++++++++++++++++++++++++++----- 1 file changed, 42 insertions(+), 8 deletions(-) diff --git a/src/components/CadenceVersion.tsx b/src/components/CadenceVersion.tsx index 452c1ff4..d8181b80 100644 --- a/src/components/CadenceVersion.tsx +++ b/src/components/CadenceVersion.tsx @@ -1,5 +1,36 @@ import React, { useState, useEffect, useContext } from 'react'; import { CadenceCheckerContext } from 'providers/CadenceChecker'; +import styled from 'styled-components'; + +export const StatusContainer = styled.div` + display: grid; + justify-content: flex-end; + grid-gap: 10px; + grid-template-columns: repeat(2, auto); + align-items: center; +`; + +export const DotBox = styled.div` + display: flex; + justify-content: flex-end; + align-items: baseline; + flex-direction: row; + gap: 3px; +` + +interface DotType { + active: string; +} +export const Dot = styled.div` + --size: 8px; + display: block; + width: var(--size); + height: var(--size); + border-radius: var(--size); + background-color: ${({ active = false }) => { + return active === 'OFF' ? '#ff006f' : '#00ff76'; + }}; +`; const API = process.env.PLAYGROUND_API; @@ -8,24 +39,27 @@ export const Version = () => { const { languageClient, languageServer } = useContext(CadenceCheckerContext); useEffect(() => { - getCadenceVerion().then() + getCadenceVersion().then(); }, []); const url = `${API}/utils/version`; - const getCadenceVerion = async () => { + const getCadenceVersion = async () => { const response = await fetch(url); const { version } = await response.json(); setVersion(version); }; - const lsStatus = languageServer ? 'ON' : "OFF"; - const lcStatus = languageClient ? 'ON' : "OFF"; + const lsStatus = languageServer ? 'ON' : 'OFF'; + const lcStatus = languageClient ? 'ON' : 'OFF'; return ( - <> - LS: {lsStatus} - LC: {lcStatus} + + + LSP: + + + Cadence: {version} - + ); }; From 7dd9de745ab33d157398e4a51bb6ec7bda7a0ed5 Mon Sep 17 00:00:00 2001 From: Max Daunarovich Date: Tue, 12 Apr 2022 21:19:24 +0300 Subject: [PATCH 45/49] Update argument extraction --- src/components/CadenceEditor/ControlPanel/index.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/CadenceEditor/ControlPanel/index.tsx b/src/components/CadenceEditor/ControlPanel/index.tsx index e02ee6b1..079309c1 100644 --- a/src/components/CadenceEditor/ControlPanel/index.tsx +++ b/src/components/CadenceEditor/ControlPanel/index.tsx @@ -313,7 +313,7 @@ const ControlPanel: React.FC = (props) => { // MEMOIZED ----------------------------------------------------------------- // we need to wrap it in useMemo, cause otherwise it might push component into infinite rerender // as "getArguments" will return new empty array on each render - const list = useMemo(getArguments, [active]); + const list = useMemo(getArguments, [active, executionArguments]); // VARIABLES AND CONSTANTS ------------------------------------------------- const { editor } = props; @@ -356,7 +356,6 @@ const ControlPanel: React.FC = (props) => { }, [languageClient, active]); useEffect(() => { - console.log('Call again!'); validate(list, values); }, [list, values]); From 5a7bbf91433851a726281b091164b92985b7ed47 Mon Sep 17 00:00:00 2001 From: Max Daunarovich Date: Tue, 12 Apr 2022 21:25:40 +0300 Subject: [PATCH 46/49] Fixed editor change catchup --- src/components/CadenceEditor/index.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/components/CadenceEditor/index.tsx b/src/components/CadenceEditor/index.tsx index a19aab02..253d3d8e 100644 --- a/src/components/CadenceEditor/index.tsx +++ b/src/components/CadenceEditor/index.tsx @@ -128,6 +128,11 @@ const CadenceEditor = (props: any) => { editor.focus(); editor.layout(); + + setTimeout(()=>{ + console.log("Restore back!") + newState.model.setValue(code); + }, 100) } editorOnChange.current = From 26e134e28a0c667fc59c9af01413351e85509efd Mon Sep 17 00:00:00 2001 From: Max Daunarovich Date: Tue, 12 Apr 2022 21:50:58 +0300 Subject: [PATCH 47/49] Add Hidable component to prevent resetting of argument values --- src/components/Arguments/styles.tsx | 7 ++ .../CadenceEditor/ControlPanel/index.tsx | 66 ++++++++++--------- 2 files changed, 41 insertions(+), 32 deletions(-) diff --git a/src/components/Arguments/styles.tsx b/src/components/Arguments/styles.tsx index 2cf0e375..22fd214e 100644 --- a/src/components/Arguments/styles.tsx +++ b/src/components/Arguments/styles.tsx @@ -14,6 +14,13 @@ export const HoverPanel = styled.div` box-shadow: 10px 10px 20px #c9c9c9, -10px -10px 20px #ffffff; `; +interface HidableProps { + hidden: Boolean +} +export const Hidable = styled.div` + display: ${({hidden = false}) => hidden ? "none" : "block"}; +` + export const Heading = styled.div` display: flex; align-items: center; diff --git a/src/components/CadenceEditor/ControlPanel/index.tsx b/src/components/CadenceEditor/ControlPanel/index.tsx index 079309c1..38200b2b 100644 --- a/src/components/CadenceEditor/ControlPanel/index.tsx +++ b/src/components/CadenceEditor/ControlPanel/index.tsx @@ -47,6 +47,7 @@ import { import { ControlContainer, HoverPanel, + Hidable, StatusMessage, } from '../../Arguments/styles'; @@ -65,8 +66,11 @@ const ControlPanel: React.FC = (props) => { const [executionArguments, setExecutionArguments] = useState({}); const [processingStatus, setProcessingStatus] = useState(false); const [setResult] = useSetExecutionResultsMutation(); - const { scriptFactory, transactionFactory, contractDeployment } = - useTemplateType(); + const { + scriptFactory, + transactionFactory, + contractDeployment, + } = useTemplateType(); const [selected, updateSelectedAccounts] = useState([]); const [expanded, setExpanded] = useState(true); @@ -366,37 +370,35 @@ const ControlPanel: React.FC = (props) => {
    - {validCode && ( - <> - {list.length > 0 && ( - <> - -