From fd73cc778088a9a1a2011e6c0f580f8d5241d1e5 Mon Sep 17 00:00:00 2001 From: x1unix Date: Mon, 7 Feb 2022 11:04:02 +0200 Subject: [PATCH 01/12] make: autopopulate GOROOT and GOPATH if not defined --- Makefile | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index a9a7a44f..0f1d1471 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,7 @@ +GO ?= go +GOROOT ?= $(shell go env GOROOT) +GOPATH ?= $(shell go env GOPATH) + PKG := ./cmd/playground UI := ./web TARGET := ./target @@ -13,7 +17,7 @@ include docker.mk .PHONY:run run: - @go run $(PKG) -f ./data/packages.json -debug=$(DEBUG) -addr $(LISTEN_ADDR) + @GOROOT=$(GOROOT) $(GO) run $(PKG) -f ./data/packages.json -debug=$(DEBUG) -addr $(LISTEN_ADDR) .PHONY:ui ui: From f3c6228e984f6e46b5fa68346c83e0f12eae6a50 Mon Sep 17 00:00:00 2001 From: x1unix Date: Mon, 7 Feb 2022 12:13:17 +0200 Subject: [PATCH 02/12] web: add editorconfig --- web/.editorconfig | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 web/.editorconfig diff --git a/web/.editorconfig b/web/.editorconfig new file mode 100644 index 00000000..7d35b2a5 --- /dev/null +++ b/web/.editorconfig @@ -0,0 +1,18 @@ +# https://editorconfig.org +root = true + +[*.{js,ts,css,less,scss}] +charset = utf-8 +end_of_line = lf +indent_size = 2 +indent_style = space +insert_final_newline = true +max_line_length = 80 +trim_trailing_whitespace = true + +[*.md] +max_line_length = 0 +trim_trailing_whitespace = false + +[COMMIT_EDITMSG] +max_line_length = 0 \ No newline at end of file From 3af891b00cd87824f14271024fcde3bb34fc8747 Mon Sep 17 00:00:00 2001 From: x1unix Date: Mon, 7 Feb 2022 12:13:41 +0200 Subject: [PATCH 03/12] web: add typescript aliases support --- web/craco.config.js | 19 +++++++++++++++++-- web/package.json | 2 ++ web/tsconfig.json | 5 +++-- web/tsconfig.paths.json | 16 ++++++++++++++++ web/yarn.lock | 17 +++++++++++++++++ 5 files changed, 55 insertions(+), 4 deletions(-) create mode 100644 web/tsconfig.paths.json diff --git a/web/craco.config.js b/web/craco.config.js index 99bc1068..4eb78cae 100644 --- a/web/craco.config.js +++ b/web/craco.config.js @@ -1,5 +1,6 @@ const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin'); const CircularDependencyPlugin = require('circular-dependency-plugin'); +const CracoAlias = require("craco-alias"); module.exports = { webpack: { @@ -18,8 +19,22 @@ module.exports = { allowAsyncCycles: false, // set the current working directory for displaying module paths cwd: process.cwd(), - }) + }), ] } - } + }, + plugins: [ + { + plugin: CracoAlias, + options: { + source: "tsconfig", + // baseUrl SHOULD be specified + // plugin does not take it from tsconfig + baseUrl: "./src", + /* tsConfigPath should point to the file where "baseUrl" and "paths" + are specified*/ + tsConfigPath: "./tsconfig.paths.json" + } + } + ] }; diff --git a/web/package.json b/web/package.json index ec84f6bc..d0c29261 100644 --- a/web/package.json +++ b/web/package.json @@ -13,10 +13,12 @@ "axios": "^0.21.1", "circular-dependency-plugin": "^5.2.2", "connected-react-router": "^6.9.2", + "craco-alias": "^3.0.1", "file-saver": "^2.0.5", "monaco-editor": "^0.32.1", "monaco-editor-webpack-plugin": "^7.0.1", "office-ui-fabric-react": "^7.82.1", + "re-resizable": "^6.9.1", "react": "^17.0.2", "react-dom": "^17.0.2", "react-monaco-editor": "^0.47.0", diff --git a/web/tsconfig.json b/web/tsconfig.json index 550797a1..e04deaa8 100644 --- a/web/tsconfig.json +++ b/web/tsconfig.json @@ -1,4 +1,5 @@ { + "extends": "./tsconfig.paths.json", "compilerOptions": { "target": "es5", "lib": [ @@ -20,9 +21,9 @@ "jsx": "react", "noImplicitAny": false, "experimentalDecorators": true, - "noFallthroughCasesInSwitch": true + "noFallthroughCasesInSwitch": true, }, "include": [ "src" ] -} +} \ No newline at end of file diff --git a/web/tsconfig.paths.json b/web/tsconfig.paths.json new file mode 100644 index 00000000..435ffd6d --- /dev/null +++ b/web/tsconfig.paths.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "baseUrl": "./src", + "paths": { + "@components/*": [ + "./components/*" + ], + "@services/*": [ + "./services/*" + ], + "@store/*": [ + "./store/*" + ] + } + } +} \ No newline at end of file diff --git a/web/yarn.lock b/web/yarn.lock index 08583633..5cea5afd 100644 --- a/web/yarn.lock +++ b/web/yarn.lock @@ -3241,6 +3241,11 @@ cosmiconfig@^7, cosmiconfig@^7.0.0, cosmiconfig@^7.0.1: path-type "^4.0.0" yaml "^1.10.0" +craco-alias@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/craco-alias/-/craco-alias-3.0.1.tgz#45e5cb338b222a7f62d17e398b54aff7cf1572af" + integrity sha512-N+Qaf/Gr/f3o5ZH2TQjMu5NhR9PnT1ZYsfejpNvZPpB0ujdrhsSr4Ct6GVjnV5ostCVquhTKJpIVBKyL9qDQYA== + create-require@^1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" @@ -4283,6 +4288,11 @@ fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6: resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= +fast-memoize@^2.5.1: + version "2.5.2" + resolved "https://registry.yarnpkg.com/fast-memoize/-/fast-memoize-2.5.2.tgz#79e3bb6a4ec867ea40ba0e7146816f6cdce9b57e" + integrity sha512-Ue0LwpDYErFbmNnZSF0UH6eImUwDmogUO1jyE+JbN2gsQz/jICm1Ve7t9QT0rNSsfJt+Hs4/S3GnsDVjL4HVrw== + fastq@^1.6.0: version "1.13.0" resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.13.0.tgz#616760f88a7526bdfc596b7cab8c18938c36b98c" @@ -7253,6 +7263,13 @@ raw-body@2.4.2: iconv-lite "0.4.24" unpipe "1.0.0" +re-resizable@^6.9.1: + version "6.9.1" + resolved "https://registry.yarnpkg.com/re-resizable/-/re-resizable-6.9.1.tgz#6be082b55d02364ca4bfee139e04feebdf52441c" + integrity sha512-KRYAgr9/j1PJ3K+t+MBhlQ+qkkoLDJ1rs0z1heIWvYbCW/9Vq4djDU+QumJ3hQbwwtzXF6OInla6rOx6hhgRhQ== + dependencies: + fast-memoize "^2.5.1" + react-app-polyfill@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/react-app-polyfill/-/react-app-polyfill-3.0.0.tgz#95221e0a9bd259e5ca6b177c7bb1cb6768f68fd7" From 4175ed9aa0b5be22eab7ebd50904415ca3ffc6ac Mon Sep 17 00:00:00 2001 From: x1unix Date: Mon, 7 Feb 2022 12:16:33 +0200 Subject: [PATCH 04/12] web: add "util" --- web/package.json | 1 + web/yarn.lock | 56 ++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/web/package.json b/web/package.json index d0c29261..6e3138b3 100644 --- a/web/package.json +++ b/web/package.json @@ -28,6 +28,7 @@ "redux": "^4.1.2", "redux-thunk": "^2.4.1", "typescript": "^4.5.5", + "util": "^0.12.4", "uuid": "^3.4.0", "web-vitals": "^2.1.4" }, diff --git a/web/yarn.lock b/web/yarn.lock index 5cea5afd..42fcbdef 100644 --- a/web/yarn.lock +++ b/web/yarn.lock @@ -2575,6 +2575,11 @@ autoprefixer@^10.4.2: picocolors "^1.0.0" postcss-value-parser "^4.2.0" +available-typed-arrays@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7" + integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw== + axe-core@^4.3.5: version "4.4.1" resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.4.1.tgz#7dbdc25989298f9ad006645cd396782443757413" @@ -3851,7 +3856,7 @@ error-stack-parser@^2.0.6: dependencies: stackframe "^1.1.1" -es-abstract@^1.17.2, es-abstract@^1.19.0, es-abstract@^1.19.1: +es-abstract@^1.17.2, es-abstract@^1.18.5, es-abstract@^1.19.0, es-abstract@^1.19.1: version "1.19.1" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.19.1.tgz#d4885796876916959de78edaa0df456627115ec3" integrity sha512-2vJ6tjA/UfqLm2MPs7jxVybLoB8i1t1Jd9R3kISld20sIxPcTbLuggQOUxeWeAvIUkduv/CfMjuh4WmiXr2v9w== @@ -4423,6 +4428,11 @@ follow-redirects@^1.0.0, follow-redirects@^1.10.0: resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.7.tgz#2004c02eb9436eee9a21446a6477debf17e81685" integrity sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ== +foreach@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" + integrity sha1-C+4AUBiusmDQo6865ljdATbsG5k= + fork-ts-checker-webpack-plugin@^6.5.0: version "6.5.0" resolved "https://registry.yarnpkg.com/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.5.0.tgz#0282b335fa495a97e167f69018f566ea7d2a2b5e" @@ -5047,6 +5057,13 @@ is-generator-fn@^2.0.0: resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== +is-generator-function@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.10.tgz#f1558baf1ac17e0deea7c0415c438351ff2b3c72" + integrity sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A== + dependencies: + has-tostringtag "^1.0.0" + is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: version "4.0.3" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" @@ -5143,6 +5160,17 @@ is-symbol@^1.0.2, is-symbol@^1.0.3: dependencies: has-symbols "^1.0.2" +is-typed-array@^1.1.3, is-typed-array@^1.1.7: + version "1.1.8" + resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.8.tgz#cbaa6585dc7db43318bc5b89523ea384a6f65e79" + integrity sha512-HqH41TNZq2fgtGT8WHVFVJhBVGuY3AnP3Q36K8JKXUxSxRgk/d+7NjmwG2vo2mYmXK8UYZKu0qH8bVP5gEisjA== + dependencies: + available-typed-arrays "^1.0.5" + call-bind "^1.0.2" + es-abstract "^1.18.5" + foreach "^2.0.5" + has-tostringtag "^1.0.0" + is-typedarray@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" @@ -7716,7 +7744,7 @@ safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@~5.2.0: +safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.2, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== @@ -8645,6 +8673,18 @@ util.promisify@~1.0.0: has-symbols "^1.0.1" object.getownpropertydescriptors "^2.1.0" +util@^0.12.4: + version "0.12.4" + resolved "https://registry.yarnpkg.com/util/-/util-0.12.4.tgz#66121a31420df8f01ca0c464be15dfa1d1850253" + integrity sha512-bxZ9qtSlGUWSOy9Qa9Xgk11kSslpuZwaxCg4sNIDj6FLucDab2JxnHwyNTCpHMtK1MjoQiWQ6DiUMZYbSrO+Sw== + dependencies: + inherits "^2.0.3" + is-arguments "^1.0.4" + is-generator-function "^1.0.7" + is-typed-array "^1.1.3" + safe-buffer "^5.1.2" + which-typed-array "^1.1.2" + utila@~0.4: version "0.4.0" resolved "https://registry.yarnpkg.com/utila/-/utila-0.4.0.tgz#8a16a05d445657a3aea5eecc5b12a4fa5379772c" @@ -8918,6 +8958,18 @@ which-boxed-primitive@^1.0.2: is-string "^1.0.5" is-symbol "^1.0.3" +which-typed-array@^1.1.2: + version "1.1.7" + resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.7.tgz#2761799b9a22d4b8660b3c1b40abaa7739691793" + integrity sha512-vjxaB4nfDqwKI0ws7wZpxIlde1XrLX5uB0ZjpfshgmapJMD7jJWhZI+yToJTqaFByF0eNBcYxbjmCzoRP7CfEw== + dependencies: + available-typed-arrays "^1.0.5" + call-bind "^1.0.2" + es-abstract "^1.18.5" + foreach "^2.0.5" + has-tostringtag "^1.0.0" + is-typed-array "^1.1.7" + which@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" From d211a52ccfb6dec3e4ba56bd42f1723468ec12d5 Mon Sep 17 00:00:00 2001 From: x1unix Date: Mon, 7 Feb 2022 12:36:05 +0200 Subject: [PATCH 05/12] web: use relative imports --- web/src/App.tsx | 34 +++---- web/src/Playground.tsx | 21 ---- web/src/{ => components/core}/Header.css | 0 web/src/{ => components/core}/Header.tsx | 64 ++++++------- .../{ => components}/editor/CodeEditor.css | 0 .../{ => components}/editor/CodeEditor.tsx | 8 +- web/src/components/editor/props.ts | 36 +++++++ web/src/components/editor/provider.ts | 95 +++++++++++++++++++ web/src/{ => components}/editor/snippets.ts | 0 .../{ => components/modals}/AboutModal.tsx | 12 +-- .../modals}/ChangeLogModal.css | 0 .../modals}/ChangeLogModal.tsx | 10 +- web/src/{ => components/pages}/Playground.css | 0 web/src/components/pages/Playground.tsx | 21 ++++ .../preview}/EvalEventView.css | 0 .../preview}/EvalEventView.tsx | 0 web/src/{ => components/preview}/Preview.css | 8 +- web/src/{ => components/preview}/Preview.tsx | 22 ++--- web/src/editor/props.ts | 36 ------- web/src/editor/provider.ts | 95 ------------------- web/src/index.tsx | 2 +- web/src/settings/SettingsModal.tsx | 48 +++++----- web/src/store/dispatch.ts | 2 +- web/tsconfig.paths.json | 3 + 24 files changed, 259 insertions(+), 258 deletions(-) delete mode 100644 web/src/Playground.tsx rename web/src/{ => components/core}/Header.css (100%) rename web/src/{ => components/core}/Header.tsx (78%) rename web/src/{ => components}/editor/CodeEditor.css (100%) rename web/src/{ => components}/editor/CodeEditor.tsx (94%) create mode 100644 web/src/components/editor/props.ts create mode 100644 web/src/components/editor/provider.ts rename web/src/{ => components}/editor/snippets.ts (100%) rename web/src/{ => components/modals}/AboutModal.tsx (84%) rename web/src/{ => components/modals}/ChangeLogModal.css (100%) rename web/src/{ => components/modals}/ChangeLogModal.tsx (88%) rename web/src/{ => components/pages}/Playground.css (100%) create mode 100644 web/src/components/pages/Playground.tsx rename web/src/{ => components/preview}/EvalEventView.css (100%) rename web/src/{ => components/preview}/EvalEventView.tsx (100%) rename web/src/{ => components/preview}/Preview.css (87%) rename web/src/{ => components/preview}/Preview.tsx (76%) delete mode 100644 web/src/editor/props.ts delete mode 100644 web/src/editor/provider.ts diff --git a/web/src/App.tsx b/web/src/App.tsx index 81aaa012..59258ae5 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -1,13 +1,13 @@ import React from 'react'; import { Provider } from 'react-redux'; -import {Fabric} from 'office-ui-fabric-react/lib/Fabric'; +import { Fabric } from 'office-ui-fabric-react/lib/Fabric'; import { ConnectedRouter } from 'connected-react-router'; -import {Switch, Route} from "react-router-dom"; +import { Switch, Route } from "react-router-dom"; import { configureStore, createGoConsoleAdapter } from './store'; import { history } from './store/configure'; import { bootstrapGo } from './services/go'; -import Playground from './Playground'; +import Playground from '~/components/pages/Playground'; import './App.css'; import config from './services/config' @@ -19,20 +19,20 @@ config.sync(); bootstrapGo(createGoConsoleAdapter(a => store.dispatch(a))); function App() { - return ( - - - - - - - - - - ); + return ( + + + + + + + + + + ); } export default App; diff --git a/web/src/Playground.tsx b/web/src/Playground.tsx deleted file mode 100644 index e9ab80cd..00000000 --- a/web/src/Playground.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import React from 'react'; -import { Header } from './Header'; -import CodeEditor from './editor/CodeEditor'; -import './Playground.css'; -import Preview from './Preview'; -import { useParams } from "react-router-dom"; -import {newSnippetLoadDispatcher} from "./store"; -import { connect } from 'react-redux'; - -const Playground = connect()(function (props: any) { - const {snippetID} = useParams(); - props.dispatch(newSnippetLoadDispatcher(snippetID)); - - return
-
- - -
; -}); - -export default Playground; \ No newline at end of file diff --git a/web/src/Header.css b/web/src/components/core/Header.css similarity index 100% rename from web/src/Header.css rename to web/src/components/core/Header.css diff --git a/web/src/Header.tsx b/web/src/components/core/Header.tsx similarity index 78% rename from web/src/Header.tsx rename to web/src/components/core/Header.tsx index 9119e1dd..03f5e3b9 100644 --- a/web/src/Header.tsx +++ b/web/src/components/core/Header.tsx @@ -1,14 +1,13 @@ import React from 'react'; -import './Header.css' import { MessageBarButton } from 'office-ui-fabric-react'; -import {CommandBar, ICommandBarItemProps} from 'office-ui-fabric-react/lib/CommandBar'; +import { CommandBar, ICommandBarItemProps } from 'office-ui-fabric-react/lib/CommandBar'; import { MessageBar, MessageBarType } from 'office-ui-fabric-react/lib/MessageBar'; -import {getTheme} from '@uifabric/styling'; -import SettingsModal, {SettingsChanges} from './settings/SettingsModal'; -import AboutModal from './AboutModal'; -import config from './services/config'; -import api from './services/api'; -import { getSnippetsMenuItems, SnippetMenuItem } from './utils/headerutils'; +import { getTheme } from '@uifabric/styling'; +import SettingsModal, { SettingsChanges } from '~/settings/SettingsModal'; +import AboutModal from '~/components/modals/AboutModal'; +import config from '~/services/config'; +import api from '~/services/api'; +import { getSnippetsMenuItems, SnippetMenuItem } from '~/utils/headerutils'; import { Connect, dispatchToggleTheme, @@ -19,8 +18,9 @@ import { runFileDispatcher, saveFileDispatcher, shareSnippetDispatcher -} from './store'; -import ChangeLogModal from "./ChangeLogModal"; +} from '~/store'; +import ChangeLogModal from '@components/modals/ChangeLogModal'; +import './Header.css'; interface HeaderState { showSettings: boolean @@ -30,7 +30,7 @@ interface HeaderState { showUpdateBanner: boolean } -@Connect(s => ({darkMode: s.settings.darkMode, loading: s.status?.loading})) +@Connect(s => ({ darkMode: s.settings.darkMode, loading: s.status?.loading })) export class Header extends React.Component { private fileInput?: HTMLInputElement; private snippetMenuItems = getSnippetsMenuItems(i => this.onSnippetMenuItemClick(i)); @@ -55,9 +55,9 @@ export class Header extends React.Component { // show update popover api.getVersion().then(r => { - const {version} = r; + const { version } = r; if (!version) return; - this.setState({showUpdateBanner: version !== config.appVersion}); + this.setState({ showUpdateBanner: version !== config.appVersion }); }).catch(err => console.warn('failed to check server API version: ', err)); } @@ -81,7 +81,7 @@ export class Header extends React.Component { key: 'openFile', text: 'Open', split: true, - iconProps: {iconName: 'OpenFile'}, + iconProps: { iconName: 'OpenFile' }, disabled: this.props.loading, onClick: () => this.fileInput?.click(), subMenuProps: { @@ -93,7 +93,7 @@ export class Header extends React.Component { text: 'Run', ariaLabel: 'Run program (Ctrl+Enter)', title: 'Run program (Ctrl+Enter)', - iconProps: {iconName: 'Play'}, + iconProps: { iconName: 'Play' }, disabled: this.props.loading, onClick: () => { this.props.dispatch(runFileDispatcher); @@ -102,7 +102,7 @@ export class Header extends React.Component { { key: 'share', text: 'Share', - iconProps: {iconName: 'Share'}, + iconProps: { iconName: 'Share' }, disabled: this.props.loading, onClick: () => { this.props.dispatch(shareSnippetDispatcher); @@ -111,7 +111,7 @@ export class Header extends React.Component { { key: 'download', text: 'Download', - iconProps: {iconName: 'Download'}, + iconProps: { iconName: 'Download' }, disabled: this.props.loading, onClick: () => { this.props.dispatch(saveFileDispatcher); @@ -121,10 +121,10 @@ export class Header extends React.Component { key: 'settings', text: 'Settings', ariaLabel: 'Settings', - iconProps: {iconName: 'Settings'}, + iconProps: { iconName: 'Settings' }, disabled: this.props.loading, onClick: () => { - this.setState({showSettings: true}); + this.setState({ showSettings: true }); } } ]; @@ -137,9 +137,9 @@ export class Header extends React.Component { text: 'What\'s new', ariaLabel: 'Changelog', disabled: this.props.loading, - iconProps: {iconName: 'Giftbox'}, + iconProps: { iconName: 'Giftbox' }, onClick: () => { - this.setState({showChangelog: true}); + this.setState({ showChangelog: true }); } }, { @@ -148,7 +148,7 @@ export class Header extends React.Component { ariaLabel: 'Format Code (Ctrl+Shift+F)', iconOnly: true, disabled: this.props.loading, - iconProps: {iconName: 'Code'}, + iconProps: { iconName: 'Code' }, onClick: () => { this.props.dispatch(formatFileDispatcher); } @@ -158,7 +158,7 @@ export class Header extends React.Component { text: 'Toggle Dark Mode', ariaLabel: 'Toggle Dark Mode', iconOnly: true, - iconProps: {iconName: this.props.darkMode ? 'Brightness' : 'ClearNight'}, + iconProps: { iconName: this.props.darkMode ? 'Brightness' : 'ClearNight' }, onClick: () => { this.props.dispatch(dispatchToggleTheme) }, @@ -172,23 +172,23 @@ export class Header extends React.Component { key: 'new-issue', text: 'Submit Issue', ariaLabel: 'Submit Issue', - iconProps: {iconName: 'Bug'}, + iconProps: { iconName: 'Bug' }, onClick: () => window.open(config.issueUrl, '_blank') }, { key: 'donate', text: 'Donate', ariaLabel: 'Donate', - iconProps: {iconName: 'Heart'}, + iconProps: { iconName: 'Heart' }, onClick: () => window.open(config.donateUrl, '_blank') }, { key: 'about', text: 'About', ariaLabel: 'About', - iconProps: {iconName: 'Info'}, + iconProps: { iconName: 'Info' }, onClick: () => { - this.setState({showAbout: true}); + this.setState({ showAbout: true }); } } ] @@ -214,7 +214,7 @@ export class Header extends React.Component { this.props.dispatch(newBuildParamsChangeDispatcher(runtime, autoFormat)); } - this.setState({showSettings: false}); + this.setState({ showSettings: false }); } render() { @@ -222,13 +222,13 @@ export class Header extends React.Component { this.setState({showUpdateBanner: false})} + onDismiss={() => this.setState({ showUpdateBanner: false })} dismissButtonAriaLabel="Close" isMultiline={false} actions={
{ - this.setState({showUpdateBanner: false}); + this.setState({ showUpdateBanner: false }); config.forceRefreshPage(); }}>Action
@@ -249,8 +249,8 @@ export class Header extends React.Component { ariaLabel='CodeEditor menu' /> this.onSettingsClose(args)} isOpen={this.state.showSettings} /> - this.setState({showAbout: false})} isOpen={this.state.showAbout} /> - this.setState({showChangelog: false})} isOpen={this.state.showChangelog} /> + this.setState({ showAbout: false })} isOpen={this.state.showAbout} /> + this.setState({ showChangelog: false })} isOpen={this.state.showChangelog} /> ; } } diff --git a/web/src/editor/CodeEditor.css b/web/src/components/editor/CodeEditor.css similarity index 100% rename from web/src/editor/CodeEditor.css rename to web/src/components/editor/CodeEditor.css diff --git a/web/src/editor/CodeEditor.tsx b/web/src/components/editor/CodeEditor.tsx similarity index 94% rename from web/src/editor/CodeEditor.tsx rename to web/src/components/editor/CodeEditor.tsx index c648e84b..2166bcd4 100644 --- a/web/src/editor/CodeEditor.tsx +++ b/web/src/components/editor/CodeEditor.tsx @@ -1,9 +1,9 @@ import React from 'react'; import MonacoEditor from 'react-monaco-editor'; -import {editor} from 'monaco-editor'; +import { editor } from 'monaco-editor'; import * as monaco from 'monaco-editor'; -import {Connect, formatFileDispatcher, newFileChangeAction, runFileDispatcher} from '../store'; -import { Analyzer } from '../services/analyzer'; +import { Connect, formatFileDispatcher, newFileChangeAction, runFileDispatcher } from '~/store'; +import { Analyzer } from '~/services/analyzer'; import { LANGUAGE_GOLANG, stateToOptions } from './props'; @@ -11,7 +11,7 @@ const ANALYZE_DEBOUNCE_TIME = 500; interface CodeEditorState { code?: string - loading?:boolean + loading?: boolean } @Connect(s => ({ diff --git a/web/src/components/editor/props.ts b/web/src/components/editor/props.ts new file mode 100644 index 00000000..e4e901e7 --- /dev/null +++ b/web/src/components/editor/props.ts @@ -0,0 +1,36 @@ +import * as monaco from 'monaco-editor'; +import { MonacoSettings } from '~/services/config'; +import { getFontFamily, getDefaultFontFamily } from '~/services/fonts'; + +export const LANGUAGE_GOLANG = 'go'; + +export const DEMO_CODE = [ + 'package main\n', + 'import (', + '\t"fmt"', + ')\n', + 'func main() {', + '\tfmt.Println("Hello World")', + '}\n' +].join('\n'); + +// stateToOptions converts MonacoState to IEditorOptions +export const stateToOptions = (state: MonacoSettings): monaco.editor.IEditorOptions => { + const { + selectOnLineNumbers, + mouseWheelZoom, + smoothScrolling, + cursorBlinking, + fontLigatures, + cursorStyle, + contextMenu + } = state; + return { + selectOnLineNumbers, mouseWheelZoom, smoothScrolling, cursorBlinking, cursorStyle, fontLigatures, + fontFamily: state.fontFamily ? getFontFamily(state.fontFamily) : getDefaultFontFamily(), + showUnused: true, + automaticLayout: true, + minimap: { enabled: state.minimap }, + contextmenu: contextMenu, + }; +}; diff --git a/web/src/components/editor/provider.ts b/web/src/components/editor/provider.ts new file mode 100644 index 00000000..a260e952 --- /dev/null +++ b/web/src/components/editor/provider.ts @@ -0,0 +1,95 @@ +import * as monaco from 'monaco-editor'; +import { IAPIClient } from '~/services/api'; +import snippets from './snippets'; + +// Import aliases +type CompletionList = monaco.languages.CompletionList; +type CompletionContext = monaco.languages.CompletionContext; +type ITextModel = monaco.editor.ITextModel; +type Position = monaco.Position; +type CancellationToken = monaco.CancellationToken; + +let alreadyRegistered = false; + +// Matches package (and method name) +const COMPL_REGEXP = /([a-zA-Z0-9_]+)(\.([A-Za-z0-9_]+))?$/; +const R_GROUP_PKG = 1; +const R_GROUP_METHOD = 3; + +const parseExpression = (expr: string) => { + COMPL_REGEXP.lastIndex = 0; // Reset regex state + const m = COMPL_REGEXP.exec(expr); + if (!m) { + return null; + } + + const varName = m[R_GROUP_PKG]; + const propValue = m[R_GROUP_METHOD]; + + if (!propValue) { + return { value: varName }; + } + + return { + packageName: varName, + value: propValue + }; +}; + +class GoCompletionItemProvider implements monaco.languages.CompletionItemProvider { + constructor(private client: IAPIClient) { } + + async provideCompletionItems(model: ITextModel, position: Position, context: CompletionContext, token: CancellationToken): Promise { + const val = model.getValueInRange({ + startLineNumber: position.lineNumber, + startColumn: 0, + endLineNumber: position.lineNumber, + endColumn: position.column, + }).trim(); + + const query = parseExpression(val); + if (!query) { + return Promise.resolve({ suggestions: [] }); + } + + let word = model.getWordUntilPosition(position); + let range = { + startLineNumber: position.lineNumber, + endLineNumber: position.lineNumber, + startColumn: word.startColumn, + endColumn: word.endColumn + }; + + // filter snippets by prefix. + // usually monaco does that but not always in right way + const relatedSnippets = snippets + .filter(s => s.label.toString().startsWith(query.value)) + .map(s => ({ ...s, range })); + + try { + const { suggestions } = await this.client.getSuggestions(query); + if (!suggestions) { + return { + suggestions: relatedSnippets + } + } + + return { + suggestions: relatedSnippets.concat(suggestions.map(s => ({ ...s, range }))) + } + } catch (err: any) { + console.error(`Failed to get code completion from server: ${err.message}`); + return { suggestions: relatedSnippets }; + } + } +} + +export const registerGoLanguageProvider = (client: IAPIClient) => { + if (alreadyRegistered) { + console.warn('Go Language provider was already registered'); + return; + } + + alreadyRegistered = true; + return monaco.languages.registerCompletionItemProvider('go', new GoCompletionItemProvider(client)); +}; diff --git a/web/src/editor/snippets.ts b/web/src/components/editor/snippets.ts similarity index 100% rename from web/src/editor/snippets.ts rename to web/src/components/editor/snippets.ts diff --git a/web/src/AboutModal.tsx b/web/src/components/modals/AboutModal.tsx similarity index 84% rename from web/src/AboutModal.tsx rename to web/src/components/modals/AboutModal.tsx index 4170787d..19652256 100644 --- a/web/src/AboutModal.tsx +++ b/web/src/components/modals/AboutModal.tsx @@ -1,9 +1,9 @@ import React from 'react'; import { Modal } from 'office-ui-fabric-react/lib/Modal'; import { Link } from 'office-ui-fabric-react/lib/Link'; -import {getTheme, IconButton, FontWeights, FontSizes, mergeStyleSets} from 'office-ui-fabric-react'; -import {getContentStyles, getIconButtonStyles} from './styles/modal'; -import config from './services/config'; +import { getTheme, IconButton, FontWeights, FontSizes, mergeStyleSets } from 'office-ui-fabric-react'; +import { getContentStyles, getIconButtonStyles } from '~/styles/modal'; +import config from '~/services/config'; const TITLE_ID = 'AboutTitle'; const SUB_TITLE_ID = 'AboutSubtitle'; @@ -31,7 +31,7 @@ export default function AboutModal(props: AboutModalProps) { const theme = getTheme(); const contentStyles = getContentStyles(theme); const iconButtonStyles = getIconButtonStyles(theme); - + return ( -
+
Better Go Playground
@@ -61,4 +61,4 @@ export default function AboutModal(props: AboutModalProps) { ) } -AboutModal.defaultProps = {isOpen: false}; \ No newline at end of file +AboutModal.defaultProps = { isOpen: false }; \ No newline at end of file diff --git a/web/src/ChangeLogModal.css b/web/src/components/modals/ChangeLogModal.css similarity index 100% rename from web/src/ChangeLogModal.css rename to web/src/components/modals/ChangeLogModal.css diff --git a/web/src/ChangeLogModal.tsx b/web/src/components/modals/ChangeLogModal.tsx similarity index 88% rename from web/src/ChangeLogModal.tsx rename to web/src/components/modals/ChangeLogModal.tsx index 910c1d1c..a409f2fe 100644 --- a/web/src/ChangeLogModal.tsx +++ b/web/src/components/modals/ChangeLogModal.tsx @@ -1,9 +1,9 @@ import React from 'react'; import { Modal } from 'office-ui-fabric-react/lib/Modal'; import { Link } from 'office-ui-fabric-react/lib/Link'; -import {getTheme, IconButton } from 'office-ui-fabric-react'; -import {getContentStyles, getIconButtonStyles} from './styles/modal'; -import config from './services/config'; +import { getTheme, IconButton } from 'office-ui-fabric-react'; +import { getContentStyles, getIconButtonStyles } from '~/styles/modal'; +import config from '~/services/config'; import './ChangeLogModal.css'; @@ -37,7 +37,7 @@ export default function ChangeLogModal(props: ChangeLogModalProps) { onClick={props.onClose as any} />
-
+
Interface - Editor
  • @@ -64,4 +64,4 @@ export default function ChangeLogModal(props: ChangeLogModalProps) { ) } -ChangeLogModal.defaultProps = {isOpen: false}; \ No newline at end of file +ChangeLogModal.defaultProps = { isOpen: false }; \ No newline at end of file diff --git a/web/src/Playground.css b/web/src/components/pages/Playground.css similarity index 100% rename from web/src/Playground.css rename to web/src/components/pages/Playground.css diff --git a/web/src/components/pages/Playground.tsx b/web/src/components/pages/Playground.tsx new file mode 100644 index 00000000..059ed62b --- /dev/null +++ b/web/src/components/pages/Playground.tsx @@ -0,0 +1,21 @@ +import React, { useState } from 'react'; +import { useParams } from "react-router-dom"; +import { connect } from 'react-redux'; +import { newSnippetLoadDispatcher } from "~/store"; +import { Header } from '~/components/core/Header'; +import CodeEditor from '~/components/editor/CodeEditor'; +import Preview from '~/components/preview/Preview'; +import './Playground.css'; + +const Playground = connect()(function (props: any) { + const { snippetID } = useParams(); + props.dispatch(newSnippetLoadDispatcher(snippetID)); + + return
    +
    + + +
    ; +}); + +export default Playground; \ No newline at end of file diff --git a/web/src/EvalEventView.css b/web/src/components/preview/EvalEventView.css similarity index 100% rename from web/src/EvalEventView.css rename to web/src/components/preview/EvalEventView.css diff --git a/web/src/EvalEventView.tsx b/web/src/components/preview/EvalEventView.tsx similarity index 100% rename from web/src/EvalEventView.tsx rename to web/src/components/preview/EvalEventView.tsx diff --git a/web/src/Preview.css b/web/src/components/preview/Preview.css similarity index 87% rename from web/src/Preview.css rename to web/src/components/preview/Preview.css index eee56fca..eea69de1 100644 --- a/web/src/Preview.css +++ b/web/src/components/preview/Preview.css @@ -1,8 +1,6 @@ .app-preview { - /*background: #222;*/ - /*color: #ccc;*/ - max-height: 640px; - height: 50%; + /* max-height: 640px; + height: 50%; */ box-sizing: border-box; overflow: auto; } @@ -45,4 +43,4 @@ .app-preview__progress--hidden { display: none; -} \ No newline at end of file +} diff --git a/web/src/Preview.tsx b/web/src/components/preview/Preview.tsx similarity index 76% rename from web/src/Preview.tsx rename to web/src/components/preview/Preview.tsx index fe2c59ab..72067800 100644 --- a/web/src/Preview.tsx +++ b/web/src/components/preview/Preview.tsx @@ -1,22 +1,22 @@ import React from 'react'; -import './Preview.css'; -import {getDefaultFontFamily} from './services/fonts'; -import {Connect} from './store'; -import { RuntimeType } from './services/config'; -import {EvalEvent} from './services/api'; +import { getTheme } from '@uifabric/styling'; +import { MessageBar, MessageBarType } from 'office-ui-fabric-react'; +import { ProgressIndicator } from 'office-ui-fabric-react/lib/ProgressIndicator'; +import { getDefaultFontFamily } from '~/services/fonts'; +import { Connect } from '~/store'; +import { RuntimeType } from '~/services/config'; +import { EvalEvent } from '~/services/api'; import EvalEventView from './EvalEventView'; -import {getTheme} from '@uifabric/styling'; -import {MessageBar, MessageBarType} from 'office-ui-fabric-react'; -import {ProgressIndicator} from 'office-ui-fabric-react/lib/ProgressIndicator'; +import './Preview.css'; interface PreviewProps { - lastError?:string | null; + lastError?: string | null; events?: EvalEvent[] loading?: boolean runtime?: RuntimeType } -@Connect(s => ({darkMode: s.settings.darkMode, runtime: s.settings.runtime, ...s.status})) +@Connect(s => ({ darkMode: s.settings.darkMode, runtime: s.settings.runtime, ...s.status })) export default class Preview extends React.Component { get styles() { const { palette } = getTheme(); @@ -59,7 +59,7 @@ export default class Preview extends React.Component { } return
    - +
    {content}
    diff --git a/web/src/editor/props.ts b/web/src/editor/props.ts deleted file mode 100644 index b0107a88..00000000 --- a/web/src/editor/props.ts +++ /dev/null @@ -1,36 +0,0 @@ -import * as monaco from 'monaco-editor'; -import {MonacoSettings} from '../services/config'; -import { getFontFamily, getDefaultFontFamily } from '../services/fonts'; - -export const LANGUAGE_GOLANG = 'go'; - -export const DEMO_CODE = [ - 'package main\n', - 'import (', - '\t"fmt"', - ')\n', - 'func main() {', - '\tfmt.Println("Hello World")', - '}\n' -].join('\n'); - -// stateToOptions converts MonacoState to IEditorOptions -export const stateToOptions = (state: MonacoSettings): monaco.editor.IEditorOptions => { - const { - selectOnLineNumbers, - mouseWheelZoom, - smoothScrolling, - cursorBlinking, - fontLigatures, - cursorStyle, - contextMenu - } = state; - return { - selectOnLineNumbers, mouseWheelZoom, smoothScrolling, cursorBlinking, cursorStyle, fontLigatures, - fontFamily: state.fontFamily ? getFontFamily(state.fontFamily) : getDefaultFontFamily(), - showUnused: true, - automaticLayout: true, - minimap: {enabled: state.minimap}, - contextmenu: contextMenu, - }; -}; diff --git a/web/src/editor/provider.ts b/web/src/editor/provider.ts deleted file mode 100644 index 13863f23..00000000 --- a/web/src/editor/provider.ts +++ /dev/null @@ -1,95 +0,0 @@ -import * as monaco from 'monaco-editor'; -import {IAPIClient} from '../services/api'; -import snippets from './snippets' - -// Import aliases -type CompletionList = monaco.languages.CompletionList; -type CompletionContext = monaco.languages.CompletionContext; -type ITextModel = monaco.editor.ITextModel; -type Position = monaco.Position; -type CancellationToken = monaco.CancellationToken; - -let alreadyRegistered = false; - -// Matches package (and method name) -const COMPL_REGEXP = /([a-zA-Z0-9_]+)(\.([A-Za-z0-9_]+))?$/; -const R_GROUP_PKG = 1; -const R_GROUP_METHOD = 3; - -const parseExpression = (expr: string) => { - COMPL_REGEXP.lastIndex = 0; // Reset regex state - const m = COMPL_REGEXP.exec(expr); - if (!m) { - return null; - } - - const varName = m[R_GROUP_PKG]; - const propValue = m[R_GROUP_METHOD]; - - if (!propValue) { - return {value: varName}; - } - - return { - packageName: varName, - value: propValue - }; -}; - -class GoCompletionItemProvider implements monaco.languages.CompletionItemProvider { - constructor(private client: IAPIClient) {} - - async provideCompletionItems(model: ITextModel, position: Position, context: CompletionContext, token: CancellationToken): Promise { - const val = model.getValueInRange({ - startLineNumber: position.lineNumber, - startColumn: 0, - endLineNumber: position.lineNumber, - endColumn: position.column, - }).trim(); - - const query = parseExpression(val); - if (!query) { - return Promise.resolve({suggestions: []}); - } - - let word = model.getWordUntilPosition(position); - let range = { - startLineNumber: position.lineNumber, - endLineNumber: position.lineNumber, - startColumn: word.startColumn, - endColumn: word.endColumn - }; - - // filter snippets by prefix. - // usually monaco does that but not always in right way - const relatedSnippets = snippets - .filter(s => s.label.toString().startsWith(query.value)) - .map(s => ({...s, range})); - - try { - const {suggestions} = await this.client.getSuggestions(query); - if (!suggestions) { - return { - suggestions: relatedSnippets - } - } - - return { - suggestions: relatedSnippets.concat(suggestions.map(s => ({...s, range}))) - } - } catch (err: any) { - console.error(`Failed to get code completion from server: ${err.message}`); - return {suggestions: relatedSnippets}; - } - } -} - -export const registerGoLanguageProvider = (client: IAPIClient) => { - if (alreadyRegistered) { - console.warn('Go Language provider was already registered'); - return; - } - - alreadyRegistered = true; - return monaco.languages.registerCompletionItemProvider('go', new GoCompletionItemProvider(client)); -}; diff --git a/web/src/index.tsx b/web/src/index.tsx index e3e882e1..6686ada4 100644 --- a/web/src/index.tsx +++ b/web/src/index.tsx @@ -4,7 +4,7 @@ import './index.css'; import App from './App'; import { initializeIcons } from '@uifabric/icons'; import * as serviceWorker from './serviceWorker'; -import {registerGoLanguageProvider} from './editor/provider'; +import {registerGoLanguageProvider} from './components/editor/provider'; import apiClient from './services/api'; diff --git a/web/src/settings/SettingsModal.tsx b/web/src/settings/SettingsModal.tsx index 2eaf4874..7c301fb4 100644 --- a/web/src/settings/SettingsModal.tsx +++ b/web/src/settings/SettingsModal.tsx @@ -1,13 +1,13 @@ import React from 'react'; -import {Checkbox, Dropdown, getTheme, IconButton, IDropdownOption, Modal} from 'office-ui-fabric-react'; -import {Pivot, PivotItem} from 'office-ui-fabric-react/lib/Pivot'; -import {MessageBar, MessageBarType} from 'office-ui-fabric-react/lib/MessageBar'; -import {Link} from 'office-ui-fabric-react/lib/Link'; -import {getContentStyles, getIconButtonStyles} from '../styles/modal'; +import { Checkbox, Dropdown, getTheme, IconButton, IDropdownOption, Modal } from 'office-ui-fabric-react'; +import { Pivot, PivotItem } from 'office-ui-fabric-react/lib/Pivot'; +import { MessageBar, MessageBarType } from 'office-ui-fabric-react/lib/MessageBar'; +import { Link } from 'office-ui-fabric-react/lib/Link'; +import { getContentStyles, getIconButtonStyles } from '../styles/modal'; import SettingsProperty from './SettingsProperty'; -import {MonacoSettings, RuntimeType} from '../services/config'; +import { MonacoSettings, RuntimeType } from '../services/config'; import { DEFAULT_FONT, getAvailableFonts } from '../services/fonts'; -import {BuildParamsArgs, Connect, MonacoParamsChanges, SettingsState} from '../store'; +import { BuildParamsArgs, Connect, MonacoParamsChanges, SettingsState } from '~/store'; const WASM_SUPPORTED = 'WebAssembly' in window; @@ -21,24 +21,24 @@ const COMPILER_OPTIONS: IDropdownOption[] = [ ]; const CURSOR_BLINK_STYLE_OPTS: IDropdownOption[] = [ - {key: 'blink', text: 'Blink (default)'}, - {key: 'smooth', text: 'Smooth'}, - {key: 'phase', text: 'Phase'}, - {key: 'expand', text: 'Expand'}, - {key: 'solid', text: 'Solid'}, + { key: 'blink', text: 'Blink (default)' }, + { key: 'smooth', text: 'Smooth' }, + { key: 'phase', text: 'Phase' }, + { key: 'expand', text: 'Expand' }, + { key: 'solid', text: 'Solid' }, ]; const CURSOR_LINE_OPTS: IDropdownOption[] = [ - {key: 'line', text: 'Line (default)'}, - {key: 'block', text: 'Block'}, - {key: 'underline', text: 'Underline'}, - {key: 'line-thin', text: 'Line thin'}, - {key: 'block-outline', text: 'Block outline'}, - {key: 'underline-thin', text: 'Underline thin'}, + { key: 'line', text: 'Line (default)' }, + { key: 'block', text: 'Block' }, + { key: 'underline', text: 'Underline' }, + { key: 'line-thin', text: 'Line thin' }, + { key: 'block-outline', text: 'Block outline' }, + { key: 'underline-thin', text: 'Underline thin' }, ]; const FONT_OPTS: IDropdownOption[] = [ - {key: DEFAULT_FONT, text: 'System default'}, + { key: DEFAULT_FONT, text: 'System default' }, ...getAvailableFonts().map(f => ({ key: f.family, text: f.label, @@ -81,7 +81,7 @@ export default class SettingsModal extends React.Component this.onClose()} />
    -
    +
    } /> - + } /> -
    +
    WebAssembly is a modern runtime that gives you additional features like possibility to interact with web browser but is unstable. diff --git a/web/src/store/dispatch.ts b/web/src/store/dispatch.ts index 07b2ce33..f62edd34 100644 --- a/web/src/store/dispatch.ts +++ b/web/src/store/dispatch.ts @@ -13,7 +13,7 @@ import { import {State} from "./state"; import client, {EvalEventKind, instantiateStreaming} from '../services/api'; import config, {RuntimeType} from '../services/config'; -import {DEMO_CODE} from '../editor/props'; +import {DEMO_CODE} from '../components/editor/props'; import {getImportObject, goRun} from '../services/go'; export type StateProvider = () => State diff --git a/web/tsconfig.paths.json b/web/tsconfig.paths.json index 435ffd6d..dca6dcb8 100644 --- a/web/tsconfig.paths.json +++ b/web/tsconfig.paths.json @@ -10,6 +10,9 @@ ], "@store/*": [ "./store/*" + ], + "~/*": [ + "./*" ] } } From e0988d2c0dd2c8ef69789e8eb9cecf823c7553e3 Mon Sep 17 00:00:00 2001 From: x1unix Date: Mon, 7 Feb 2022 12:36:23 +0200 Subject: [PATCH 06/12] web: add resizable preview panel --- web/src/components/editor/FlexContainer.tsx | 13 +++++++++++++ web/src/components/pages/Playground.tsx | 16 ++++++++++++++-- 2 files changed, 27 insertions(+), 2 deletions(-) create mode 100644 web/src/components/editor/FlexContainer.tsx diff --git a/web/src/components/editor/FlexContainer.tsx b/web/src/components/editor/FlexContainer.tsx new file mode 100644 index 00000000..9b24aa73 --- /dev/null +++ b/web/src/components/editor/FlexContainer.tsx @@ -0,0 +1,13 @@ +import React, { FC, PropsWithChildren } from 'react'; + +const FlexContainer: FC> = ({children}) => ( +
    + {children} +
    +); + +export default FlexContainer; diff --git a/web/src/components/pages/Playground.tsx b/web/src/components/pages/Playground.tsx index 059ed62b..587f5a48 100644 --- a/web/src/components/pages/Playground.tsx +++ b/web/src/components/pages/Playground.tsx @@ -1,20 +1,32 @@ import React, { useState } from 'react'; import { useParams } from "react-router-dom"; import { connect } from 'react-redux'; +import { Resizable } from 're-resizable'; import { newSnippetLoadDispatcher } from "~/store"; import { Header } from '~/components/core/Header'; import CodeEditor from '~/components/editor/CodeEditor'; import Preview from '~/components/preview/Preview'; +import FlexContainer from '~/components/editor/FlexContainer'; import './Playground.css'; const Playground = connect()(function (props: any) { const { snippetID } = useParams(); props.dispatch(newSnippetLoadDispatcher(snippetID)); + const [height, setHeight] = useState(300); + const onResize = (e, direction, ref, d) => { + setHeight(height + d.height); + console.log('onResize', d); + }; + return
    - - + + + + + +
    ; }); From ec8f8ab46281f0209cc303e0e89277dbd98a5e2c Mon Sep 17 00:00:00 2001 From: x1unix Date: Mon, 7 Feb 2022 15:22:57 +0200 Subject: [PATCH 07/12] web: enable .editorconfig for .tsx --- .editorconfig | 2 +- web/.editorconfig | 18 ------------------ 2 files changed, 1 insertion(+), 19 deletions(-) delete mode 100644 web/.editorconfig diff --git a/.editorconfig b/.editorconfig index 7d35b2a5..75fb6065 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,7 +1,7 @@ # https://editorconfig.org root = true -[*.{js,ts,css,less,scss}] +[*.{js,ts,tsx,css,less,scss}] charset = utf-8 end_of_line = lf indent_size = 2 diff --git a/web/.editorconfig b/web/.editorconfig deleted file mode 100644 index 7d35b2a5..00000000 --- a/web/.editorconfig +++ /dev/null @@ -1,18 +0,0 @@ -# https://editorconfig.org -root = true - -[*.{js,ts,css,less,scss}] -charset = utf-8 -end_of_line = lf -indent_size = 2 -indent_style = space -insert_final_newline = true -max_line_length = 80 -trim_trailing_whitespace = true - -[*.md] -max_line_length = 0 -trim_trailing_whitespace = false - -[COMMIT_EDITMSG] -max_line_length = 0 \ No newline at end of file From 9e763dc90e7a89691a900910f5307357d171fdbd Mon Sep 17 00:00:00 2001 From: x1unix Date: Mon, 7 Feb 2022 15:30:30 +0200 Subject: [PATCH 08/12] web: make preview panel resizable --- web/src/components/pages/Playground.tsx | 18 ++++-------- web/src/components/preview/Preview.tsx | 2 +- .../components/preview/ResizablePreview.css | 9 ++++++ .../components/preview/ResizablePreview.tsx | 29 +++++++++++++++++++ 4 files changed, 44 insertions(+), 14 deletions(-) create mode 100644 web/src/components/preview/ResizablePreview.css create mode 100644 web/src/components/preview/ResizablePreview.tsx diff --git a/web/src/components/pages/Playground.tsx b/web/src/components/pages/Playground.tsx index 587f5a48..eb35a691 100644 --- a/web/src/components/pages/Playground.tsx +++ b/web/src/components/pages/Playground.tsx @@ -1,33 +1,25 @@ -import React, { useState } from 'react'; +import React from 'react'; import { useParams } from "react-router-dom"; import { connect } from 'react-redux'; -import { Resizable } from 're-resizable'; import { newSnippetLoadDispatcher } from "~/store"; import { Header } from '~/components/core/Header'; import CodeEditor from '~/components/editor/CodeEditor'; -import Preview from '~/components/preview/Preview'; import FlexContainer from '~/components/editor/FlexContainer'; +import ResizablePreview from '@components/preview/ResizablePreview'; + import './Playground.css'; const Playground = connect()(function (props: any) { const { snippetID } = useParams(); props.dispatch(newSnippetLoadDispatcher(snippetID)); - const [height, setHeight] = useState(300); - const onResize = (e, direction, ref, d) => { - setHeight(height + d.height); - console.log('onResize', d); - }; - return
    - - - +
    ; }); -export default Playground; \ No newline at end of file +export default Playground; diff --git a/web/src/components/preview/Preview.tsx b/web/src/components/preview/Preview.tsx index 72067800..2b0d1112 100644 --- a/web/src/components/preview/Preview.tsx +++ b/web/src/components/preview/Preview.tsx @@ -9,7 +9,7 @@ import { EvalEvent } from '~/services/api'; import EvalEventView from './EvalEventView'; import './Preview.css'; -interface PreviewProps { +export interface PreviewProps { lastError?: string | null; events?: EvalEvent[] loading?: boolean diff --git a/web/src/components/preview/ResizablePreview.css b/web/src/components/preview/ResizablePreview.css new file mode 100644 index 00000000..57d4faeb --- /dev/null +++ b/web/src/components/preview/ResizablePreview.css @@ -0,0 +1,9 @@ +.ResizablePreview { + overflow: hidden; + display: flex; + flex-direction: column; +} + +.ResizablePreview .app-preview { + flex: 1 1 auto; +} diff --git a/web/src/components/preview/ResizablePreview.tsx b/web/src/components/preview/ResizablePreview.tsx new file mode 100644 index 00000000..1bb70806 --- /dev/null +++ b/web/src/components/preview/ResizablePreview.tsx @@ -0,0 +1,29 @@ +import React, { useState, useCallback } from 'react'; +import { Resizable } from 're-resizable'; +import Preview from './Preview'; + +import './ResizablePreview.css'; + +const DEFAULT_HEIGHT_PX = 300; +const DEFAULT_WIDTH = '100%'; + +interface Props { } + +const ResizablePreview: React.FC = () => { + const [height, setHeight] = useState(DEFAULT_HEIGHT_PX); + const onResize = useCallback((e, direction, ref, d) => { + setHeight(height + d.height); + }, [setHeight, height]); + + return ( + + + + ); +}; + +export default ResizablePreview; From 5d71ee633c462eef85584316ae980d8971b5b3c8 Mon Sep 17 00:00:00 2001 From: x1unix Date: Mon, 7 Feb 2022 17:58:33 +0200 Subject: [PATCH 09/12] web: add styles for resizer --- .../components/preview/ResizablePreview.css | 49 ++++++++++++++++++- .../components/preview/ResizablePreview.tsx | 23 +++++++++ 2 files changed, 71 insertions(+), 1 deletion(-) diff --git a/web/src/components/preview/ResizablePreview.css b/web/src/components/preview/ResizablePreview.css index 57d4faeb..1dfd1025 100644 --- a/web/src/components/preview/ResizablePreview.css +++ b/web/src/components/preview/ResizablePreview.css @@ -1,5 +1,4 @@ .ResizablePreview { - overflow: hidden; display: flex; flex-direction: column; } @@ -7,3 +6,51 @@ .ResizablePreview .app-preview { flex: 1 1 auto; } + +.ResizablePreview__handle--top:before { + content: ''; + position: absolute; + background: var(--pg-handle-default-color); + transition: background-color 0.1s ease-in 0s; + top: 5px; + left: 0; + right: 0; + height: 1px; +} + +.ResizablePreview__handle--top { + transition: background-color 0.1s ease-in 0s; +} + +.ResizablePreview__handle--top:hover:before, +.ResizablePreview__handle--top:active:before { + background: var(--pg-handle-active-color); + height: 3px; +} + +@media (hover: none), (pointer:coarse) { + .ResizablePreview__handle--top { + background: black; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + } + + .ResizablePreview__handle--top:hover, + .ResizablePreview__handle--top:active { + background: var(--pg-handle-active-color); + } + + .ResizablePreview__handle--top:before { + display: none; + } + + .ResizablePreview__handle--top:after { + content: ''; + background: #d3d3d3; + width: 25%; + height: 1px; + align-self: center; + } +} diff --git a/web/src/components/preview/ResizablePreview.tsx b/web/src/components/preview/ResizablePreview.tsx index 1bb70806..0a2b23ba 100644 --- a/web/src/components/preview/ResizablePreview.tsx +++ b/web/src/components/preview/ResizablePreview.tsx @@ -1,4 +1,5 @@ import React, { useState, useCallback } from 'react'; +import { getTheme } from '@uifabric/styling'; import { Resizable } from 're-resizable'; import Preview from './Preview'; @@ -6,10 +7,26 @@ import './ResizablePreview.css'; const DEFAULT_HEIGHT_PX = 300; const DEFAULT_WIDTH = '100%'; +const handleClasses = { + top: 'ResizablePreview__handle--top' +} + +// re-resizable requires to implicitly mark disabled corners +const enabledCorners = { + top: true, + right: false, + bottom: false, + left: false, + topRight: false, + bottomRight: false, + bottomLeft: false, + topLeft: false +} interface Props { } const ResizablePreview: React.FC = () => { + const { palette: { accent }, semanticColors: { buttonBorder } } = getTheme(); const [height, setHeight] = useState(DEFAULT_HEIGHT_PX); const onResize = useCallback((e, direction, ref, d) => { setHeight(height + d.height); @@ -18,8 +35,14 @@ const ResizablePreview: React.FC = () => { return ( From a6034e95d53533b7b1cca8fb61ae98d3e942e8ee Mon Sep 17 00:00:00 2001 From: x1unix Date: Mon, 7 Feb 2022 18:02:15 +0200 Subject: [PATCH 10/12] web: fix scale for mobile devices --- web/public/index.html | 82 ++++++++++++++++++------------------------- 1 file changed, 35 insertions(+), 47 deletions(-) diff --git a/web/public/index.html b/web/public/index.html index 3d95f26b..1581d411 100644 --- a/web/public/index.html +++ b/web/public/index.html @@ -1,51 +1,39 @@ - - - - - - - - - - - - Better Go Playground - - - -
    - - - + + +
    + + + + \ No newline at end of file From ed48736cdae957db94548360c135c01e1127734d Mon Sep 17 00:00:00 2001 From: x1unix Date: Mon, 7 Feb 2022 18:02:31 +0200 Subject: [PATCH 11/12] web: fix app chrome styles --- web/src/App.css | 8 +------- web/src/App.tsx | 30 +++++++++++++++--------------- 2 files changed, 16 insertions(+), 22 deletions(-) diff --git a/web/src/App.css b/web/src/App.css index f6c2872b..425fe9c9 100644 --- a/web/src/App.css +++ b/web/src/App.css @@ -1,10 +1,4 @@ .App { - position: absolute; - top: 0; - left: 0; - bottom: 0; - right: 0; display: flex; flex-direction: column; - /*background: #ccc;*/ -} \ No newline at end of file +} diff --git a/web/src/App.tsx b/web/src/App.tsx index 59258ae5..70148676 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -8,8 +8,8 @@ import { configureStore, createGoConsoleAdapter } from './store'; import { history } from './store/configure'; import { bootstrapGo } from './services/go'; import Playground from '~/components/pages/Playground'; +import config from './services/config'; import './App.css'; -import config from './services/config' // Configure store and import config from localStorage const store = configureStore(); @@ -19,20 +19,20 @@ config.sync(); bootstrapGo(createGoConsoleAdapter(a => store.dispatch(a))); function App() { - return ( - - - - - - - - - - ); + return ( + + + + + + + + + + ); } export default App; From 2b6570df7eb687ffb4ccefcf8312db4f903ba380 Mon Sep 17 00:00:00 2001 From: x1unix Date: Mon, 7 Feb 2022 18:08:34 +0200 Subject: [PATCH 12/12] web: fix header overflow on mobile --- web/src/components/core/Header.css | 9 +- web/src/components/core/Header.tsx | 438 ++++++++++++++--------------- 2 files changed, 227 insertions(+), 220 deletions(-) diff --git a/web/src/components/core/Header.css b/web/src/components/core/Header.css index 8234eb1c..b9e86b69 100644 --- a/web/src/components/core/Header.css +++ b/web/src/components/core/Header.css @@ -33,4 +33,11 @@ .header__commandBar { flex: 1 1 auto; -} \ No newline at end of file +} + +@media only screen and (max-width: 600px) { + .header__logo { + height: 1em; + margin-left: 0; + } +} diff --git a/web/src/components/core/Header.tsx b/web/src/components/core/Header.tsx index 03f5e3b9..1e954354 100644 --- a/web/src/components/core/Header.tsx +++ b/web/src/components/core/Header.tsx @@ -8,250 +8,250 @@ import AboutModal from '~/components/modals/AboutModal'; import config from '~/services/config'; import api from '~/services/api'; import { getSnippetsMenuItems, SnippetMenuItem } from '~/utils/headerutils'; +import ChangeLogModal from '@components/modals/ChangeLogModal'; import { - Connect, - dispatchToggleTheme, - formatFileDispatcher, - newBuildParamsChangeDispatcher, newCodeImportDispatcher, - newImportFileDispatcher, - newMonacoParamsChangeDispatcher, newSnippetLoadDispatcher, - runFileDispatcher, - saveFileDispatcher, - shareSnippetDispatcher + Connect, + dispatchToggleTheme, + formatFileDispatcher, + newBuildParamsChangeDispatcher, newCodeImportDispatcher, + newImportFileDispatcher, + newMonacoParamsChangeDispatcher, newSnippetLoadDispatcher, + runFileDispatcher, + saveFileDispatcher, + shareSnippetDispatcher } from '~/store'; -import ChangeLogModal from '@components/modals/ChangeLogModal'; import './Header.css'; interface HeaderState { - showSettings: boolean - showAbout: boolean - showChangelog: boolean - loading: boolean - showUpdateBanner: boolean + showSettings: boolean + showAbout: boolean + showChangelog: boolean + loading: boolean + showUpdateBanner: boolean } @Connect(s => ({ darkMode: s.settings.darkMode, loading: s.status?.loading })) export class Header extends React.Component { - private fileInput?: HTMLInputElement; - private snippetMenuItems = getSnippetsMenuItems(i => this.onSnippetMenuItemClick(i)); - - constructor(props) { - super(props); - this.state = { - showSettings: false, - showAbout: false, - showChangelog: false, - loading: false, - showUpdateBanner: false - }; - } + private fileInput?: HTMLInputElement; + private snippetMenuItems = getSnippetsMenuItems(i => this.onSnippetMenuItemClick(i)); - componentDidMount(): void { - const fileElement = document.createElement('input') as HTMLInputElement; - fileElement.type = 'file'; - fileElement.accept = '.go'; - fileElement.addEventListener('change', () => this.onItemSelect(), false); - this.fileInput = fileElement; + constructor(props) { + super(props); + this.state = { + showSettings: false, + showAbout: false, + showChangelog: false, + loading: false, + showUpdateBanner: false + }; + } - // show update popover - api.getVersion().then(r => { - const { version } = r; - if (!version) return; - this.setState({ showUpdateBanner: version !== config.appVersion }); - }).catch(err => console.warn('failed to check server API version: ', err)); - } + componentDidMount(): void { + const fileElement = document.createElement('input') as HTMLInputElement; + fileElement.type = 'file'; + fileElement.accept = '.go'; + fileElement.addEventListener('change', () => this.onItemSelect(), false); + this.fileInput = fileElement; - onItemSelect() { - const file = this.fileInput?.files?.item(0); - if (!file) { - return; - } + // show update popover + api.getVersion().then(r => { + const { version } = r; + if (!version) return; + this.setState({ showUpdateBanner: version !== config.appVersion }); + }).catch(err => console.warn('failed to check server API version: ', err)); + } - this.props.dispatch(newImportFileDispatcher(file)); + onItemSelect() { + const file = this.fileInput?.files?.item(0); + if (!file) { + return; } - onSnippetMenuItemClick(item: SnippetMenuItem) { - const dispatcher = item.snippet ? newSnippetLoadDispatcher(item.snippet) : newCodeImportDispatcher(item.label, item.text as string); - this.props.dispatch(dispatcher); - } + this.props.dispatch(newImportFileDispatcher(file)); + } - get menuItems(): ICommandBarItemProps[] { - return [ - { - key: 'openFile', - text: 'Open', - split: true, - iconProps: { iconName: 'OpenFile' }, - disabled: this.props.loading, - onClick: () => this.fileInput?.click(), - subMenuProps: { - items: this.snippetMenuItems, - }, - }, - { - key: 'run', - text: 'Run', - ariaLabel: 'Run program (Ctrl+Enter)', - title: 'Run program (Ctrl+Enter)', - iconProps: { iconName: 'Play' }, - disabled: this.props.loading, - onClick: () => { - this.props.dispatch(runFileDispatcher); - } - }, - { - key: 'share', - text: 'Share', - iconProps: { iconName: 'Share' }, - disabled: this.props.loading, - onClick: () => { - this.props.dispatch(shareSnippetDispatcher); - } - }, - { - key: 'download', - text: 'Download', - iconProps: { iconName: 'Download' }, - disabled: this.props.loading, - onClick: () => { - this.props.dispatch(saveFileDispatcher); - }, - }, - { - key: 'settings', - text: 'Settings', - ariaLabel: 'Settings', - iconProps: { iconName: 'Settings' }, - disabled: this.props.loading, - onClick: () => { - this.setState({ showSettings: true }); - } - } - ]; - } + onSnippetMenuItemClick(item: SnippetMenuItem) { + const dispatcher = item.snippet ? newSnippetLoadDispatcher(item.snippet) : newCodeImportDispatcher(item.label, item.text as string); + this.props.dispatch(dispatcher); + } - get asideItems(): ICommandBarItemProps[] { - return [ - { - key: 'changelog', - text: 'What\'s new', - ariaLabel: 'Changelog', - disabled: this.props.loading, - iconProps: { iconName: 'Giftbox' }, - onClick: () => { - this.setState({ showChangelog: true }); - } - }, - { - key: 'format', - text: 'Format Code', - ariaLabel: 'Format Code (Ctrl+Shift+F)', - iconOnly: true, - disabled: this.props.loading, - iconProps: { iconName: 'Code' }, - onClick: () => { - this.props.dispatch(formatFileDispatcher); - } - }, - { - key: 'toggleTheme', - text: 'Toggle Dark Mode', - ariaLabel: 'Toggle Dark Mode', - iconOnly: true, - iconProps: { iconName: this.props.darkMode ? 'Brightness' : 'ClearNight' }, - onClick: () => { - this.props.dispatch(dispatchToggleTheme) - }, - } - ]; - } - - get overflowItems(): ICommandBarItemProps[] { - return [ - { - key: 'new-issue', - text: 'Submit Issue', - ariaLabel: 'Submit Issue', - iconProps: { iconName: 'Bug' }, - onClick: () => window.open(config.issueUrl, '_blank') - }, - { - key: 'donate', - text: 'Donate', - ariaLabel: 'Donate', - iconProps: { iconName: 'Heart' }, - onClick: () => window.open(config.donateUrl, '_blank') - }, - { - key: 'about', - text: 'About', - ariaLabel: 'About', - iconProps: { iconName: 'Info' }, - onClick: () => { - this.setState({ showAbout: true }); - } - } - ] - } - - get styles() { - // Apply the same colors as rest of Fabric components - const theme = getTheme(); - return { - backgroundColor: theme.palette.white + get menuItems(): ICommandBarItemProps[] { + return [ + { + key: 'openFile', + text: 'Open', + split: true, + iconProps: { iconName: 'OpenFile' }, + disabled: this.props.loading, + onClick: () => this.fileInput?.click(), + subMenuProps: { + items: this.snippetMenuItems, + }, + }, + { + key: 'run', + text: 'Run', + ariaLabel: 'Run program (Ctrl+Enter)', + title: 'Run program (Ctrl+Enter)', + iconProps: { iconName: 'Play' }, + disabled: this.props.loading, + onClick: () => { + this.props.dispatch(runFileDispatcher); } - } + }, + { + key: 'share', + text: 'Share', + iconProps: { iconName: 'Share' }, + disabled: this.props.loading, + onClick: () => { + this.props.dispatch(shareSnippetDispatcher); + } + }, + { + key: 'download', + text: 'Download', + iconProps: { iconName: 'Download' }, + disabled: this.props.loading, + onClick: () => { + this.props.dispatch(saveFileDispatcher); + }, + }, + { + key: 'settings', + text: 'Settings', + ariaLabel: 'Settings', + iconProps: { iconName: 'Settings' }, + disabled: this.props.loading, + onClick: () => { + this.setState({ showSettings: true }); + } + } + ]; + } - private onSettingsClose(changes: SettingsChanges) { - if (changes.monaco) { - // Update monaco state if some of it's settings were changed - this.props.dispatch(newMonacoParamsChangeDispatcher(changes.monaco)); + get asideItems(): ICommandBarItemProps[] { + return [ + { + key: 'changelog', + text: 'What\'s new', + ariaLabel: 'Changelog', + disabled: this.props.loading, + iconProps: { iconName: 'Giftbox' }, + onClick: () => { + this.setState({ showChangelog: true }); + } + }, + { + key: 'format', + text: 'Format Code', + ariaLabel: 'Format Code (Ctrl+Shift+F)', + iconOnly: true, + disabled: this.props.loading, + iconProps: { iconName: 'Code' }, + onClick: () => { + this.props.dispatch(formatFileDispatcher); } + }, + { + key: 'toggleTheme', + text: 'Toggle Dark Mode', + ariaLabel: 'Toggle Dark Mode', + iconOnly: true, + iconProps: { iconName: this.props.darkMode ? 'Brightness' : 'ClearNight' }, + onClick: () => { + this.props.dispatch(dispatchToggleTheme) + }, + } + ]; + } - if (changes.args) { - // Save runtime settings - const { runtime, autoFormat } = changes.args; - this.props.dispatch(newBuildParamsChangeDispatcher(runtime, autoFormat)); + get overflowItems(): ICommandBarItemProps[] { + return [ + { + key: 'new-issue', + text: 'Submit Issue', + ariaLabel: 'Submit Issue', + iconProps: { iconName: 'Bug' }, + onClick: () => window.open(config.issueUrl, '_blank') + }, + { + key: 'donate', + text: 'Donate', + ariaLabel: 'Donate', + iconProps: { iconName: 'Heart' }, + onClick: () => window.open(config.donateUrl, '_blank') + }, + { + key: 'about', + text: 'About', + ariaLabel: 'About', + iconProps: { iconName: 'Info' }, + onClick: () => { + this.setState({ showAbout: true }); } + } + ] + } - this.setState({ showSettings: false }); + get styles() { + // Apply the same colors as rest of Fabric components + const theme = getTheme(); + return { + backgroundColor: theme.palette.white } + } - render() { - return
    - this.setState({ showUpdateBanner: false })} - dismissButtonAriaLabel="Close" - isMultiline={false} - actions={ -
    - { - this.setState({ showUpdateBanner: false }); - config.forceRefreshPage(); - }}>Action -
    - } - > - Web application was updated, click Reload to apply changes -
    - Golang Logo - - this.onSettingsClose(args)} isOpen={this.state.showSettings} /> - this.setState({ showAbout: false })} isOpen={this.state.showAbout} /> - this.setState({ showChangelog: false })} isOpen={this.state.showChangelog} /> -
    ; + private onSettingsClose(changes: SettingsChanges) { + if (changes.monaco) { + // Update monaco state if some of it's settings were changed + this.props.dispatch(newMonacoParamsChangeDispatcher(changes.monaco)); } + + if (changes.args) { + // Save runtime settings + const { runtime, autoFormat } = changes.args; + this.props.dispatch(newBuildParamsChangeDispatcher(runtime, autoFormat)); + } + + this.setState({ showSettings: false }); + } + + render() { + return
    + this.setState({ showUpdateBanner: false })} + dismissButtonAriaLabel="Close" + isMultiline={false} + actions={ +
    + { + this.setState({ showUpdateBanner: false }); + config.forceRefreshPage(); + }}>Action +
    + } + > + Web application was updated, click Reload to apply changes +
    + Golang Logo + + this.onSettingsClose(args)} isOpen={this.state.showSettings} /> + this.setState({ showAbout: false })} isOpen={this.state.showAbout} /> + this.setState({ showChangelog: false })} isOpen={this.state.showChangelog} /> +
    ; + } }