From 629d64b390eca01af0bfc421209bd769c687cc8e Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Fri, 18 Nov 2022 11:15:25 -0600 Subject: [PATCH 01/21] Add PlatformScript Plugin 1. navigate to http://localhost:3000/platformscript 2. edit libraries in backend/ps/lib/*.yaml 3. can be loaded by http://localhost:7007/api/ps/lib/*.yaml --- package.json | 4 +-- packages/app/package.json | 6 ++++- packages/app/src/App.tsx | 2 ++ .../app/src/components/PlatformScriptPage.tsx | 25 +++++++++++++++++++ packages/backend/ps/lib/hello.yaml | 2 ++ packages/backend/src/index.ts | 7 ++++++ yarn.lock | 7 ++++++ 7 files changed, 50 insertions(+), 3 deletions(-) create mode 100644 packages/app/src/components/PlatformScriptPage.tsx create mode 100644 packages/backend/ps/lib/hello.yaml diff --git a/package.json b/package.json index a212876f79..8e6566f3b0 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "1.0.0", "private": true, "engines": { - "node": "14 || 16" + "node": ">=16" }, "scripts": { "dev": "concurrently \"yarn start\" \"yarn start-backend\"", @@ -61,6 +61,6 @@ ] }, "volta": { - "node": "14.20.0" + "node": "16.18.1" } } diff --git a/packages/app/package.json b/packages/app/package.json index f1021c6f4d..552d90ed3f 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -36,6 +36,7 @@ "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "history": "^5.0.0", + "platformscript": "^1.0.0-alapha.3", "react": "^17.0.2", "react-dom": "^17.0.2", "react-router": "^6.3.0", @@ -80,5 +81,8 @@ }, "files": [ "dist" - ] + ], + "volta": { + "extend": "../../package.json" + } } diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index 01754379e1..bf8e701cb0 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -25,6 +25,7 @@ import { UserSettingsPage } from '@backstage/plugin-user-settings'; import { apis } from './apis'; import { entityPage } from './components/catalog/EntityPage'; import { searchPage } from './components/search/SearchPage'; +import { PlatformScriptPage } from './components/PlatformScriptPage'; import { Root } from './components/Root'; import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; @@ -78,6 +79,7 @@ const routes = ( } /> + } /> } /> createPlatformScript(), []); + + + let result = useAsync(async () => { + let mod = await ps.load("http://localhost:7007/api/ps/lib/hello.yaml"); + + let prog = ps.parse("$greet: Bob"); + let result = await ps.eval(prog, mod.symbols); + return result; + }, [ps]); + + if (result.loading) { + return

...loading

+ } else if (result.error) { + return

{result.error.message}

+ } else { + return

{JSON.stringify(result.value?.value)}

+ } +} diff --git a/packages/backend/ps/lib/hello.yaml b/packages/backend/ps/lib/hello.yaml new file mode 100644 index 0000000000..0e89ad3d05 --- /dev/null +++ b/packages/backend/ps/lib/hello.yaml @@ -0,0 +1,2 @@ +greet: + $(thing): "Hello, $thing" diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index 79890a0713..48f4175868 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -81,6 +81,12 @@ function makeCreateEnv(config: Config) { }; } +import { resolvePackagePath } from '@backstage/backend-common'; +import express from 'express'; +import path from 'path'; + +const libps = express.static(path.join(resolvePackagePath('backend'), 'ps', 'lib')) + async function main() { const config = await loadBackendConfig({ argv: process.argv, @@ -111,6 +117,7 @@ async function main() { apiRouter.use('/effection-inspector', await effectionInspector(effectionInspectorEnv)); apiRouter.use('/humanitec', await humanitec(humanitecEnv)); apiRouter.use('/graphql', await graphql(graphqlEnv)); + apiRouter.use('/ps/lib', libps); apiRouter.use(notFoundHandler()); const service = createServiceBuilder(module) diff --git a/yarn.lock b/yarn.lock index 47b80ca011..2c264df4a2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -18826,6 +18826,13 @@ pkginfo@0.4.x, pkginfo@^0.4.1: resolved "https://registry.yarnpkg.com/pkginfo/-/pkginfo-0.4.1.tgz#b5418ef0439de5425fc4995042dced14fb2a84ff" integrity sha512-8xCNE/aT/EXKenuMDZ+xTVwkT8gsoHN2z/Q29l80u0ppGEXVvsKRzNMbtKhg8LS8k1tJLAHHylf6p4VFmP6XUQ== +platformscript@^1.0.0-alapha.3: + version "1.0.0-alpha.3" + resolved "https://registry.yarnpkg.com/platformscript/-/platformscript-1.0.0-alpha.3.tgz#b1765087a74cd0d7c5f06d37536d2b4b15152842" + integrity sha512-/oGpknF1dncZ3MggBuMIh9CAiaTwzmbIPRqIMeVvxfWtCQdsqC83IbgLy9HS5oDuvF/dayhMrHqLw9/OmeEwBQ== + dependencies: + yaml-ast-parser "0.0.43" + pluralize@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1" From 1957f18abef492ef2f8bbf851170751315769750 Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Sun, 20 Nov 2022 16:02:45 -0600 Subject: [PATCH 02/21] Embed react from platformscript --- packages/app/package.json | 2 +- .../app/src/components/PlatformScriptPage.tsx | 20 ++++++++++--------- packages/backend/ps/lib/hello.yaml | 4 ++-- yarn.lock | 8 ++++---- 4 files changed, 18 insertions(+), 16 deletions(-) diff --git a/packages/app/package.json b/packages/app/package.json index 552d90ed3f..2434ff159d 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -36,7 +36,7 @@ "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "history": "^5.0.0", - "platformscript": "^1.0.0-alapha.3", + "platformscript": "^1.0.0-alapha.4", "react": "^17.0.2", "react-dom": "^17.0.2", "react-router": "^6.3.0", diff --git a/packages/app/src/components/PlatformScriptPage.tsx b/packages/app/src/components/PlatformScriptPage.tsx index 73345c4ebd..3242e88d23 100644 --- a/packages/app/src/components/PlatformScriptPage.tsx +++ b/packages/app/src/components/PlatformScriptPage.tsx @@ -1,18 +1,20 @@ import React, { useMemo } from 'react'; import { useAsync } from 'react-use'; -import { createPlatformScript } from 'platformscript'; +import * as ps from 'platformscript'; +import { Button } from '@material-ui/core'; export function PlatformScriptPage() { - let ps = useMemo(() => createPlatformScript(), []); - + let globals = ps.map({ + Button: ps.fn(function*({ arg }) { + return ps.external(); + }) + }); + let platformscript = useMemo(() => ps.createPlatformScript(globals), []); let result = useAsync(async () => { - let mod = await ps.load("http://localhost:7007/api/ps/lib/hello.yaml"); - - let prog = ps.parse("$greet: Bob"); - let result = await ps.eval(prog, mod.symbols); - return result; + let mod = await platformscript.load("http://localhost:7007/api/ps/lib/hello.yaml"); + return mod.value; }, [ps]); if (result.loading) { @@ -20,6 +22,6 @@ export function PlatformScriptPage() { } else if (result.error) { return

{result.error.message}

} else { - return

{JSON.stringify(result.value?.value)}

+ return

{result.value?.value}

} } diff --git a/packages/backend/ps/lib/hello.yaml b/packages/backend/ps/lib/hello.yaml index 0e89ad3d05..bb82342e2e 100644 --- a/packages/backend/ps/lib/hello.yaml +++ b/packages/backend/ps/lib/hello.yaml @@ -1,2 +1,2 @@ -greet: - $(thing): "Hello, $thing" +- + $Button: Press Me diff --git a/yarn.lock b/yarn.lock index 2c264df4a2..dddbf93180 100644 --- a/yarn.lock +++ b/yarn.lock @@ -18826,10 +18826,10 @@ pkginfo@0.4.x, pkginfo@^0.4.1: resolved "https://registry.yarnpkg.com/pkginfo/-/pkginfo-0.4.1.tgz#b5418ef0439de5425fc4995042dced14fb2a84ff" integrity sha512-8xCNE/aT/EXKenuMDZ+xTVwkT8gsoHN2z/Q29l80u0ppGEXVvsKRzNMbtKhg8LS8k1tJLAHHylf6p4VFmP6XUQ== -platformscript@^1.0.0-alapha.3: - version "1.0.0-alpha.3" - resolved "https://registry.yarnpkg.com/platformscript/-/platformscript-1.0.0-alpha.3.tgz#b1765087a74cd0d7c5f06d37536d2b4b15152842" - integrity sha512-/oGpknF1dncZ3MggBuMIh9CAiaTwzmbIPRqIMeVvxfWtCQdsqC83IbgLy9HS5oDuvF/dayhMrHqLw9/OmeEwBQ== +platformscript@^1.0.0-alapha.4: + version "1.0.0-alpha.4" + resolved "https://registry.yarnpkg.com/platformscript/-/platformscript-1.0.0-alpha.4.tgz#828754e925571ae10a0c6cec57e0c66912650fab" + integrity sha512-+UND714EdRzu/l6darBcJ9kI6C3ZiYM3ADNywxnBOX72/hm2vjx54TFeLHixFcF2tZcfcdSAueBom+o8rbaPOQ== dependencies: yaml-ast-parser "0.0.43" From f4696ec70479fc697298bfc747fda9785917d7f1 Mon Sep 17 00:00:00 2001 From: Paul Cowan Date: Mon, 21 Nov 2022 14:59:52 +0000 Subject: [PATCH 03/21] add yaml editor --- packages/app/package.json | 1 + .../app/src/components/PlatformScriptPage.tsx | 59 +++++++++++++++---- .../src/components/yaml-editor/YamlEditor.tsx | 21 +++++++ 3 files changed, 68 insertions(+), 13 deletions(-) create mode 100644 packages/app/src/components/yaml-editor/YamlEditor.tsx diff --git a/packages/app/package.json b/packages/app/package.json index 2434ff159d..edb8f97656 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -35,6 +35,7 @@ "@frontside/backstage-plugin-humanitec": "^0.3.2", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", + "@monaco-editor/react": "^4.4.6", "history": "^5.0.0", "platformscript": "^1.0.0-alapha.4", "react": "^17.0.2", diff --git a/packages/app/src/components/PlatformScriptPage.tsx b/packages/app/src/components/PlatformScriptPage.tsx index 3242e88d23..f2b168e288 100644 --- a/packages/app/src/components/PlatformScriptPage.tsx +++ b/packages/app/src/components/PlatformScriptPage.tsx @@ -1,27 +1,60 @@ -import React, { useMemo } from 'react'; -import { useAsync } from 'react-use'; - +import React, { useCallback, useMemo, useRef, useState } from 'react'; import * as ps from 'platformscript'; import { Button } from '@material-ui/core'; +import { YAMLEditor } from './yaml-editor/YamlEditor'; +import type { MonacoEditor } from './yaml-editor/YamlEditor'; +import { useAsync } from 'react-use'; export function PlatformScriptPage() { - let globals = ps.map({ + const globals = useMemo(() => ps.map({ Button: ps.fn(function*({ arg }) { return ps.external(); - }) - }); - let platformscript = useMemo(() => ps.createPlatformScript(globals), []); + }, + ), + }), []); + + const [yaml, setYaml] = useState(`$Button: "Press Me"`); + + const editorRef = useRef(null); + + const handleEditorMount = useCallback((editor: MonacoEditor) => { + editorRef.current = editor; + }, []); + + function handleEditorChange(value?: string) { + if(!yaml) { + return; + } + + setYaml(value); + }; + + const platformscript = useMemo(() => ps.createPlatformScript(globals), [globals]); + + const result = useAsync(async () => { + const program = platformscript.parse(yaml as string); + + const mod = await platformscript.eval(program); - let result = useAsync(async () => { - let mod = await platformscript.load("http://localhost:7007/api/ps/lib/hello.yaml"); return mod.value; - }, [ps]); + }, [yaml]); if (result.loading) { return

...loading

} else if (result.error) { return

{result.error.message}

- } else { - return

{result.value?.value}

- } + } + + return ( + <> +
+ {result.value} + +
+ + ); } diff --git a/packages/app/src/components/yaml-editor/YamlEditor.tsx b/packages/app/src/components/yaml-editor/YamlEditor.tsx new file mode 100644 index 0000000000..d762d11bb9 --- /dev/null +++ b/packages/app/src/components/yaml-editor/YamlEditor.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import Editor, { OnChange } from "@monaco-editor/react"; +import type { EditorProps, OnMount, BeforeMount } from "@monaco-editor/react"; + +export type YAMLEditorProps = Pick; +export type MonacoEditor = Parameters[0]; +export type OnChangeArgs = Parameters; +export type Monaco = Parameters; + +export function YAMLEditor({ onMount, onChange, beforeMount, defaultValue }: YAMLEditorProps): JSX.Element { + return ( + + ); +} \ No newline at end of file From 2140b71a4938cb05afc43beaf5272d188cc5d3f9 Mon Sep 17 00:00:00 2001 From: Paul Cowan Date: Mon, 21 Nov 2022 17:02:35 +0000 Subject: [PATCH 04/21] add Button props to yaml --- packages/app/.eslintrc.js | 7 +- packages/app/package.json | 2 +- .../app/src/components/PlatformScriptPage.tsx | 65 ++++++++++++------- .../src/components/yaml-editor/YamlEditor.tsx | 11 ++-- yarn.lock | 20 ++++++ 5 files changed, 73 insertions(+), 32 deletions(-) diff --git a/packages/app/.eslintrc.js b/packages/app/.eslintrc.js index e2a53a6ad2..adfbe9a4c9 100644 --- a/packages/app/.eslintrc.js +++ b/packages/app/.eslintrc.js @@ -1 +1,6 @@ -module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname, { + rules: { + 'func-names': 'off', + 'consistent-return': 'off' + } +}); diff --git a/packages/app/package.json b/packages/app/package.json index edb8f97656..9ea4468513 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -35,7 +35,7 @@ "@frontside/backstage-plugin-humanitec": "^0.3.2", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", - "@monaco-editor/react": "^4.4.6", + "@monaco-editor/react": "4.4.5", "history": "^5.0.0", "platformscript": "^1.0.0-alapha.4", "react": "^17.0.2", diff --git a/packages/app/src/components/PlatformScriptPage.tsx b/packages/app/src/components/PlatformScriptPage.tsx index f2b168e288..203cb159f9 100644 --- a/packages/app/src/components/PlatformScriptPage.tsx +++ b/packages/app/src/components/PlatformScriptPage.tsx @@ -1,37 +1,54 @@ import React, { useCallback, useMemo, useRef, useState } from 'react'; import * as ps from 'platformscript'; import { Button } from '@material-ui/core'; -import { YAMLEditor } from './yaml-editor/YamlEditor'; -import type { MonacoEditor } from './yaml-editor/YamlEditor'; +import { MonacoEditor, YAMLEditor } from './yaml-editor/YamlEditor'; import { useAsync } from 'react-use'; +import type { PSMap, PSValue } from 'platformscript'; + +const DefultYaml = `$Button: + text: 'Press Me'`; + +export function lookup(key: string, map: PSMap): PSValue | void { + for (const entry of map.value.entries()) { + const [k, value] = entry; + if (k.value.toString() === key) { + return value; + } + } + return void 0; +} export function PlatformScriptPage() { + const editorRef = useRef(null); const globals = useMemo(() => ps.map({ - Button: ps.fn(function*({ arg }) { - return ps.external(); + Button: ps.fn(function* ({ arg, env }) { + const $arg = yield* env.eval(arg); + let children = ''; + switch($arg.type) { + case 'string': + children = $arg.value; + break; + case 'map': + children = lookup('text', $arg)?.value ?? ''; + break; + default: + children = String($arg.type); + } + + return ps.external(); }, ), }), []); - const [yaml, setYaml] = useState(`$Button: "Press Me"`); + const [yaml, setYaml] = useState(DefultYaml); - const editorRef = useRef(null); - const handleEditorMount = useCallback((editor: MonacoEditor) => { editorRef.current = editor; }, []); - function handleEditorChange(value?: string) { - if(!yaml) { - return; - } - - setYaml(value); - }; - const platformscript = useMemo(() => ps.createPlatformScript(globals), [globals]); - const result = useAsync(async () => { + const result = useAsync(async (): Promise => { const program = platformscript.parse(yaml as string); const mod = await platformscript.eval(program); @@ -39,20 +56,18 @@ export function PlatformScriptPage() { return mod.value; }, [yaml]); - if (result.loading) { - return

...loading

- } else if (result.error) { - return

{result.error.message}

- } - return ( - <> + <>
+ {result.loading && (

...loading

)} + {result.error &&

{result.error.message}

} {result.value} setYaml(value)} + value={yaml} />
diff --git a/packages/app/src/components/yaml-editor/YamlEditor.tsx b/packages/app/src/components/yaml-editor/YamlEditor.tsx index d762d11bb9..76d580c277 100644 --- a/packages/app/src/components/yaml-editor/YamlEditor.tsx +++ b/packages/app/src/components/yaml-editor/YamlEditor.tsx @@ -1,21 +1,22 @@ import React from 'react'; -import Editor, { OnChange } from "@monaco-editor/react"; -import type { EditorProps, OnMount, BeforeMount } from "@monaco-editor/react"; +import Editor from "@monaco-editor/react"; +import type { EditorProps, OnMount, BeforeMount, OnChange } from "@monaco-editor/react"; -export type YAMLEditorProps = Pick; +export type YAMLEditorProps = Pick; export type MonacoEditor = Parameters[0]; export type OnChangeArgs = Parameters; export type Monaco = Parameters; -export function YAMLEditor({ onMount, onChange, beforeMount, defaultValue }: YAMLEditorProps): JSX.Element { +export function YAMLEditor({ onMount, onChange, beforeMount, defaultValue, value }: YAMLEditorProps): JSX.Element { return ( - ); } \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index dddbf93180..e1a8061242 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5655,6 +5655,21 @@ prop-types "^15.7.2" react-is "^16.8.0 || ^17.0.0" +"@monaco-editor/loader@^1.3.2": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@monaco-editor/loader/-/loader-1.3.2.tgz#04effbb87052d19cd7d3c9d81c0635490f9bb6d8" + integrity sha512-BTDbpHl3e47r3AAtpfVFTlAi7WXv4UQ/xZmz8atKl4q7epQV5e7+JbigFDViWF71VBi4IIBdcWP57Hj+OWuc9g== + dependencies: + state-local "^1.0.6" + +"@monaco-editor/react@4.4.5": + version "4.4.5" + resolved "https://registry.yarnpkg.com/@monaco-editor/react/-/react-4.4.5.tgz#beabe491efeb2457441a00d1c7651c653697f65b" + integrity sha512-IImtzU7sRc66OOaQVCG+5PFHkSWnnhrUWGBuH6zNmH2h0YgmAhcjHZQc/6MY9JWEbUtVF1WPBMJ9u1XuFbRrVA== + dependencies: + "@monaco-editor/loader" "^1.3.2" + prop-types "^15.7.2" + "@mswjs/cookies@^0.1.6": version "0.1.7" resolved "https://registry.yarnpkg.com/@mswjs/cookies/-/cookies-0.1.7.tgz#d334081b2c51057a61c1dd7b76ca3cac02251651" @@ -21405,6 +21420,11 @@ start-server-and-test@^1.10.11: ps-tree "1.2.0" wait-on "6.0.0" +state-local@^1.0.6: + version "1.0.7" + resolved "https://registry.yarnpkg.com/state-local/-/state-local-1.0.7.tgz#da50211d07f05748d53009bee46307a37db386d5" + integrity sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w== + statuses@2.0.1, statuses@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" From 0ecfc8daeeb2e77e95428e01a547360771e70505 Mon Sep 17 00:00:00 2001 From: Paul Cowan Date: Tue, 22 Nov 2022 10:21:31 +0000 Subject: [PATCH 05/21] add clickHandler to button --- packages/app/package.json | 4 +- .../app/src/components/PlatformScriptPage.tsx | 43 +++++++++++++++++-- yarn.lock | 8 ++-- 3 files changed, 46 insertions(+), 9 deletions(-) diff --git a/packages/app/package.json b/packages/app/package.json index 9ea4468513..bbce95b37d 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -36,8 +36,10 @@ "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@monaco-editor/react": "4.4.5", + "assert-ts": "^0.3.4", + "effection": "^2.0.6", "history": "^5.0.0", - "platformscript": "^1.0.0-alapha.4", + "platformscript": "^1.0.0-cl-exported-run.0", "react": "^17.0.2", "react-dom": "^17.0.2", "react-router": "^6.3.0", diff --git a/packages/app/src/components/PlatformScriptPage.tsx b/packages/app/src/components/PlatformScriptPage.tsx index 203cb159f9..3a42879f6e 100644 --- a/packages/app/src/components/PlatformScriptPage.tsx +++ b/packages/app/src/components/PlatformScriptPage.tsx @@ -4,9 +4,14 @@ import { Button } from '@material-ui/core'; import { MonacoEditor, YAMLEditor } from './yaml-editor/YamlEditor'; import { useAsync } from 'react-use'; import type { PSMap, PSValue } from 'platformscript'; +import { assert } from 'assert-ts'; const DefultYaml = `$Button: - text: 'Press Me'`; + text: 'Press Me' + onClick: + $(): + $alert: "Pressed!" + `; export function lookup(key: string, map: PSMap): PSValue | void { for (const entry of map.value.entries()) { @@ -21,24 +26,55 @@ export function lookup(key: string, map: PSMap): PSValue | void { export function PlatformScriptPage() { const editorRef = useRef(null); const globals = useMemo(() => ps.map({ + alert: ps.fn(function * ({arg, env}) { + const $arg = yield* env.eval(arg); + + const message = String($arg.value); + + // eslint-disable-next-line no-alert + window.alert(message); + + return ps.boolean(true); + }), Button: ps.fn(function* ({ arg, env }) { const $arg = yield* env.eval(arg); let children = ''; + // TODO: fix return type + let clickHandler = (): any => void 0; switch($arg.type) { case 'string': children = $arg.value; break; - case 'map': + case 'map':{ children = lookup('text', $arg)?.value ?? ''; + + const psClickHandler = lookup('onClick', $arg); + + if(psClickHandler) { + assert(psClickHandler.type === 'fn', `onClick must be a function but is ${psClickHandler.type}`); + + clickHandler = () => { + ps.run(() => env.call(psClickHandler, { + arg: ps.boolean(true), + rest: ps.map({}), + env + })); + } + } + break; + } default: children = String($arg.type); } - return ps.external(); + + return ps.external(); }, ), }), []); + + const platformscript = useMemo(() => ps.createPlatformScript(globals), [globals]); const [yaml, setYaml] = useState(DefultYaml); @@ -46,7 +82,6 @@ export function PlatformScriptPage() { editorRef.current = editor; }, []); - const platformscript = useMemo(() => ps.createPlatformScript(globals), [globals]); const result = useAsync(async (): Promise => { const program = platformscript.parse(yaml as string); diff --git a/yarn.lock b/yarn.lock index e1a8061242..9e13034a3e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -18841,10 +18841,10 @@ pkginfo@0.4.x, pkginfo@^0.4.1: resolved "https://registry.yarnpkg.com/pkginfo/-/pkginfo-0.4.1.tgz#b5418ef0439de5425fc4995042dced14fb2a84ff" integrity sha512-8xCNE/aT/EXKenuMDZ+xTVwkT8gsoHN2z/Q29l80u0ppGEXVvsKRzNMbtKhg8LS8k1tJLAHHylf6p4VFmP6XUQ== -platformscript@^1.0.0-alapha.4: - version "1.0.0-alpha.4" - resolved "https://registry.yarnpkg.com/platformscript/-/platformscript-1.0.0-alpha.4.tgz#828754e925571ae10a0c6cec57e0c66912650fab" - integrity sha512-+UND714EdRzu/l6darBcJ9kI6C3ZiYM3ADNywxnBOX72/hm2vjx54TFeLHixFcF2tZcfcdSAueBom+o8rbaPOQ== +platformscript@^1.0.0-cl-exported-run.0: + version "1.0.0-cl-exported-run.0" + resolved "https://registry.yarnpkg.com/platformscript/-/platformscript-1.0.0-cl-exported-run.0.tgz#097f037f0bbe39c594b976532e34454d402b6500" + integrity sha512-sgOksrHaG5YARa4EK7EfUqOf0VJkxaiaXLshvO2YrotfE3p5H3YPqBsKvWQYd3L6IgHr3AuYXIJmVyQbSY6ITw== dependencies: yaml-ast-parser "0.0.43" From 4a177d9a48db2a5e155bceab8ef3662f32dbd780 Mon Sep 17 00:00:00 2001 From: Paul Cowan Date: Tue, 22 Nov 2022 11:20:55 +0000 Subject: [PATCH 06/21] add some basic yaml configuration to the monaco editor --- .../app/src/components/PlatformScriptPage.tsx | 24 +++++----- .../src/components/yaml-editor/YamlEditor.tsx | 9 ++-- .../yaml-editor/editor-before-mount.ts | 44 +++++++++++++++++++ 3 files changed, 61 insertions(+), 16 deletions(-) create mode 100644 packages/app/src/components/yaml-editor/editor-before-mount.ts diff --git a/packages/app/src/components/PlatformScriptPage.tsx b/packages/app/src/components/PlatformScriptPage.tsx index 3a42879f6e..4a49092936 100644 --- a/packages/app/src/components/PlatformScriptPage.tsx +++ b/packages/app/src/components/PlatformScriptPage.tsx @@ -25,8 +25,9 @@ export function lookup(key: string, map: PSMap): PSValue | void { export function PlatformScriptPage() { const editorRef = useRef(null); + const globals = useMemo(() => ps.map({ - alert: ps.fn(function * ({arg, env}) { + alert: ps.fn(function* ({ arg, env }) { const $arg = yield* env.eval(arg); const message = String($arg.value); @@ -38,19 +39,20 @@ export function PlatformScriptPage() { }), Button: ps.fn(function* ({ arg, env }) { const $arg = yield* env.eval(arg); + let children = ''; - // TODO: fix return type - let clickHandler = (): any => void 0; - switch($arg.type) { + let clickHandler = (): void => void 0; + + switch ($arg.type) { case 'string': children = $arg.value; break; - case 'map':{ + case 'map': { children = lookup('text', $arg)?.value ?? ''; const psClickHandler = lookup('onClick', $arg); - if(psClickHandler) { + if (psClickHandler) { assert(psClickHandler.type === 'fn', `onClick must be a function but is ${psClickHandler.type}`); clickHandler = () => { @@ -68,12 +70,12 @@ export function PlatformScriptPage() { children = String($arg.type); } - + return ps.external(); }, ), }), []); - + const platformscript = useMemo(() => ps.createPlatformScript(globals), [globals]); const [yaml, setYaml] = useState(DefultYaml); @@ -82,7 +84,6 @@ export function PlatformScriptPage() { editorRef.current = editor; }, []); - const result = useAsync(async (): Promise => { const program = platformscript.parse(yaml as string); @@ -95,15 +96,14 @@ export function PlatformScriptPage() { <>
{result.loading && (

...loading

)} - {result.error &&

{result.error.message}

} - {result.value} + {/* {result.error &&

{result.error.message}

} */} setYaml(value)} value={yaml} /> + {result.value}
); diff --git a/packages/app/src/components/yaml-editor/YamlEditor.tsx b/packages/app/src/components/yaml-editor/YamlEditor.tsx index 76d580c277..e38d055101 100644 --- a/packages/app/src/components/yaml-editor/YamlEditor.tsx +++ b/packages/app/src/components/yaml-editor/YamlEditor.tsx @@ -1,21 +1,22 @@ import React from 'react'; import Editor from "@monaco-editor/react"; import type { EditorProps, OnMount, BeforeMount, OnChange } from "@monaco-editor/react"; +import { handleEditorWillMount, LanguageId } from './editor-before-mount'; export type YAMLEditorProps = Pick; export type MonacoEditor = Parameters[0]; export type OnChangeArgs = Parameters; -export type Monaco = Parameters; +export type Monaco = Parameters[0]; -export function YAMLEditor({ onMount, onChange, beforeMount, defaultValue, value }: YAMLEditorProps): JSX.Element { +export function YAMLEditor({ onMount, onChange, defaultValue, value }: YAMLEditorProps): JSX.Element { return ( ); diff --git a/packages/app/src/components/yaml-editor/editor-before-mount.ts b/packages/app/src/components/yaml-editor/editor-before-mount.ts new file mode 100644 index 0000000000..6586d81ec5 --- /dev/null +++ b/packages/app/src/components/yaml-editor/editor-before-mount.ts @@ -0,0 +1,44 @@ +import type { Monaco } from '@monaco-editor/react'; + +export const LanguageId = 'yaml'; + +export function handleEditorWillMount(monaco: Monaco) { + monaco.languages.setLanguageConfiguration(LanguageId, { + comments: { + lineComment: '#', + }, + brackets: [ + ['{', '}'], + ['[', ']'], + ['(', ')'], + ], + autoClosingPairs: [ + { open: '{', close: '}' }, + { open: '[', close: ']' }, + { open: '(', close: ')' }, + { open: '"', close: '"' }, + { open: "'", close: "'" }, + ], + surroundingPairs: [ + { open: '{', close: '}' }, + { open: '[', close: ']' }, + { open: '(', close: ')' }, + { open: '"', close: '"' }, + { open: "'", close: "'" }, + ], + + onEnterRules: [ + { + beforeText: /:\s*$/, + action: { indentAction: monaco.languages.IndentAction.Indent }, + }, + ], + }); + + monaco.languages.register({ + id: LanguageId, + extensions: ['.yaml', '.yml'], + aliases: ['YAML', 'yaml', 'YML', 'yml'], + mimetypes: ['application/x-yaml'], + }); +} From bb248ecfca68a2077a1f8ce7a1d09b345b13a4da Mon Sep 17 00:00:00 2001 From: Paul Cowan Date: Tue, 22 Nov 2022 14:38:10 +0000 Subject: [PATCH 07/21] render Typography and children --- .../app/src/components/PlatformScriptPage.tsx | 52 +++++++++++++++---- 1 file changed, 42 insertions(+), 10 deletions(-) diff --git a/packages/app/src/components/PlatformScriptPage.tsx b/packages/app/src/components/PlatformScriptPage.tsx index 4a49092936..1c1b892a48 100644 --- a/packages/app/src/components/PlatformScriptPage.tsx +++ b/packages/app/src/components/PlatformScriptPage.tsx @@ -1,17 +1,27 @@ + import React, { useCallback, useMemo, useRef, useState } from 'react'; +import type { ComponentType } from 'react'; import * as ps from 'platformscript'; -import { Button } from '@material-ui/core'; +import { Button, Typography } from '@material-ui/core'; import { MonacoEditor, YAMLEditor } from './yaml-editor/YamlEditor'; import { useAsync } from 'react-use'; import type { PSMap, PSValue } from 'platformscript'; import { assert } from 'assert-ts'; -const DefultYaml = `$Button: - text: 'Press Me' - onClick: - $(): - $alert: "Pressed!" - `; +type TypographyProps = typeof Typography extends ComponentType + ? P + : never; + +type Variant = TypographyProps['variant']; + +const DefultYaml = `$Typography: + variant: 'body1' + children: + "No links defined for this entity. You can add links to your entity YAML + as shown in the highlighted example below:" + div: + className: 'whatever' +`; export function lookup(key: string, map: PSMap): PSValue | void { for (const entry of map.value.entries()) { @@ -37,6 +47,29 @@ export function PlatformScriptPage() { return ps.boolean(true); }), + Typography: ps.fn(function* ({ arg, env }) { + const $arg = yield* env.eval(arg); + let variant = ''; + let children: any = ''; + + switch ($arg.type) { + case 'string': + variant = $arg.value; + break; + case 'map': + children = lookup('children', $arg); + + if (children) { + children = yield* env.eval(children); + } + + break; + default: + children = String($arg.type); + } + + return ps.external({children.value}); + },), Button: ps.fn(function* ({ arg, env }) { const $arg = yield* env.eval(arg); @@ -72,8 +105,7 @@ export function PlatformScriptPage() { return ps.external(); - }, - ), + },) }), []); const platformscript = useMemo(() => ps.createPlatformScript(globals), [globals]); @@ -92,11 +124,11 @@ export function PlatformScriptPage() { return mod.value; }, [yaml]); + return ( <>
{result.loading && (

...loading

)} - {/* {result.error &&

{result.error.message}

} */} Date: Tue, 22 Nov 2022 15:18:14 +0000 Subject: [PATCH 08/21] render Fragment and list --- .../app/src/components/PlatformScriptPage.tsx | 32 ++++++++++++------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/packages/app/src/components/PlatformScriptPage.tsx b/packages/app/src/components/PlatformScriptPage.tsx index 1c1b892a48..072b6b3e89 100644 --- a/packages/app/src/components/PlatformScriptPage.tsx +++ b/packages/app/src/components/PlatformScriptPage.tsx @@ -1,4 +1,3 @@ - import React, { useCallback, useMemo, useRef, useState } from 'react'; import type { ComponentType } from 'react'; import * as ps from 'platformscript'; @@ -14,13 +13,11 @@ type TypographyProps = typeof Typography extends ComponentType type Variant = TypographyProps['variant']; -const DefultYaml = `$Typography: - variant: 'body1' - children: - "No links defined for this entity. You can add links to your entity YAML - as shown in the highlighted example below:" - div: - className: 'whatever' +const DefultYaml = `$<>: + - $Typography: + variant: 'body1' + children: + "No links defined for this entity. You can add links to your entity YAML as shown in the highlighted example below:" `; export function lookup(key: string, map: PSMap): PSValue | void { @@ -47,15 +44,22 @@ export function PlatformScriptPage() { return ps.boolean(true); }), + '<>': ps.fn(function* ({ arg, env }) { + const $arg = yield* env.eval(arg); + + assert($arg.type === 'list', `a fragment must contain a list, found ${$arg.type}`); + + + const elements = $arg.value.map(value => value.value); + + return ps.external(<>{elements}); + }), Typography: ps.fn(function* ({ arg, env }) { const $arg = yield* env.eval(arg); let variant = ''; let children: any = ''; switch ($arg.type) { - case 'string': - variant = $arg.value; - break; case 'map': children = lookup('children', $arg); @@ -63,6 +67,8 @@ export function PlatformScriptPage() { children = yield* env.eval(children); } + variant = lookup('variant', $arg)?.value; + break; default: children = String($arg.type); @@ -124,7 +130,9 @@ export function PlatformScriptPage() { return mod.value; }, [yaml]); - + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + result.error && console.log(result.error); + return ( <>
From ad4b7d5c1c341cee007b8cd8a81f55690a435f6e Mon Sep 17 00:00:00 2001 From: Paul Cowan Date: Tue, 22 Nov 2022 16:11:50 +0000 Subject: [PATCH 09/21] add key to fragment children --- packages/app/package.json | 2 +- .../app/src/components/PlatformScriptPage.tsx | 115 ++---------------- packages/app/src/components/globals.tsx | 115 ++++++++++++++++++ yarn.lock | 8 +- 4 files changed, 130 insertions(+), 110 deletions(-) create mode 100644 packages/app/src/components/globals.tsx diff --git a/packages/app/package.json b/packages/app/package.json index bbce95b37d..7fa5d14a31 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -39,7 +39,7 @@ "assert-ts": "^0.3.4", "effection": "^2.0.6", "history": "^5.0.0", - "platformscript": "^1.0.0-cl-exported-run.0", + "platformscript": "1.0.0-alpha.6", "react": "^17.0.2", "react-dom": "^17.0.2", "react-router": "^6.3.0", diff --git a/packages/app/src/components/PlatformScriptPage.tsx b/packages/app/src/components/PlatformScriptPage.tsx index 072b6b3e89..821e51523d 100644 --- a/packages/app/src/components/PlatformScriptPage.tsx +++ b/packages/app/src/components/PlatformScriptPage.tsx @@ -1,120 +1,25 @@ -import React, { useCallback, useMemo, useRef, useState } from 'react'; -import type { ComponentType } from 'react'; +import React, { useCallback, useRef, useState } from 'react'; import * as ps from 'platformscript'; -import { Button, Typography } from '@material-ui/core'; import { MonacoEditor, YAMLEditor } from './yaml-editor/YamlEditor'; import { useAsync } from 'react-use'; -import type { PSMap, PSValue } from 'platformscript'; -import { assert } from 'assert-ts'; - -type TypographyProps = typeof Typography extends ComponentType - ? P - : never; - -type Variant = TypographyProps['variant']; +import type { PSValue } from 'platformscript'; +import { globals } from './globals'; const DefultYaml = `$<>: - $Typography: variant: 'body1' children: "No links defined for this entity. You can add links to your entity YAML as shown in the highlighted example below:" + - $div: + className: 'whatever' + children: + "children" `; -export function lookup(key: string, map: PSMap): PSValue | void { - for (const entry of map.value.entries()) { - const [k, value] = entry; - if (k.value.toString() === key) { - return value; - } - } - return void 0; -} - export function PlatformScriptPage() { const editorRef = useRef(null); - const globals = useMemo(() => ps.map({ - alert: ps.fn(function* ({ arg, env }) { - const $arg = yield* env.eval(arg); - - const message = String($arg.value); - - // eslint-disable-next-line no-alert - window.alert(message); - - return ps.boolean(true); - }), - '<>': ps.fn(function* ({ arg, env }) { - const $arg = yield* env.eval(arg); - - assert($arg.type === 'list', `a fragment must contain a list, found ${$arg.type}`); - - - const elements = $arg.value.map(value => value.value); - - return ps.external(<>{elements}); - }), - Typography: ps.fn(function* ({ arg, env }) { - const $arg = yield* env.eval(arg); - let variant = ''; - let children: any = ''; - - switch ($arg.type) { - case 'map': - children = lookup('children', $arg); - - if (children) { - children = yield* env.eval(children); - } - - variant = lookup('variant', $arg)?.value; - - break; - default: - children = String($arg.type); - } - - return ps.external({children.value}); - },), - Button: ps.fn(function* ({ arg, env }) { - const $arg = yield* env.eval(arg); - - let children = ''; - let clickHandler = (): void => void 0; - - switch ($arg.type) { - case 'string': - children = $arg.value; - break; - case 'map': { - children = lookup('text', $arg)?.value ?? ''; - - const psClickHandler = lookup('onClick', $arg); - - if (psClickHandler) { - assert(psClickHandler.type === 'fn', `onClick must be a function but is ${psClickHandler.type}`); - - clickHandler = () => { - ps.run(() => env.call(psClickHandler, { - arg: ps.boolean(true), - rest: ps.map({}), - env - })); - } - } - - break; - } - default: - children = String($arg.type); - } - - - return ps.external(); - },) - }), []); - - const platformscript = useMemo(() => ps.createPlatformScript(globals), [globals]); + const platformscript = ps.createPlatformScript(globals); const [yaml, setYaml] = useState(DefultYaml); @@ -123,7 +28,7 @@ export function PlatformScriptPage() { }, []); const result = useAsync(async (): Promise => { - const program = platformscript.parse(yaml as string); + const program = ps.parse(yaml as string); const mod = await platformscript.eval(program); @@ -131,7 +36,7 @@ export function PlatformScriptPage() { }, [yaml]); // eslint-disable-next-line @typescript-eslint/no-unused-expressions - result.error && console.log(result.error); + // result.error && console.log(result.error); return ( <> diff --git a/packages/app/src/components/globals.tsx b/packages/app/src/components/globals.tsx new file mode 100644 index 0000000000..3e2e62426f --- /dev/null +++ b/packages/app/src/components/globals.tsx @@ -0,0 +1,115 @@ +import React from 'react'; +import { assert } from 'assert-ts'; +import type { ComponentType } from 'react'; +import type { PlatformScript, PSMap, PSValue } from 'platformscript'; +import * as ps from 'platformscript'; +import { Button, Typography } from '@material-ui/core'; + + +type TypographyProps = typeof Typography extends ComponentType + ? P + : never; + +type Variant = TypographyProps['variant']; + +export function lookup(key: string, map: PSMap): PSValue | void { + for (const entry of map.value.entries()) { + const [k, value] = entry; + if (k.value.toString() === key) { + return value; + } + } + return void 0; +} + + +export function globals(interpreter: PlatformScript) { + return ps.map({ + alert: ps.fn(function* ({ arg, env }) { + const $arg = yield* env.eval(arg); + + const message = String($arg.value); + + // eslint-disable-next-line no-alert + window.alert(message); + + return ps.boolean(true); + }), + '<>': ps.fn(function* ({ arg, env }) { + const $arg = yield* env.eval(arg); + + assert($arg.type === 'list', `a fragment must contain a list, found ${$arg.type}`); + + // TODO: investigate types + // A JSX element has a `key` field which defaults to null + const elements = $arg.value.map((psValue: { value: any }, i: number) => ({ ...psValue.value, key: psValue.value.key !== null ? psValue.value.key : i })); + + return ps.external(<>{elements}); + }), + div: ps.fn(function* ({ arg, env }) { + const $arg = yield* env.eval(arg); + + let children: any = ''; + + switch ($arg.type) { + case 'map': + children = lookup('children', $arg); + + break; + default: + children = String($arg.type); + } + + return ps.external(
{children.value}
) + }), + Typography: ps.fn(function* ({ arg, env }) { + const $arg = yield* env.eval(arg); + let variant = ''; + let children: any = ''; + + switch ($arg.type) { + case 'map': + children = lookup('children', $arg); + + variant = lookup('variant', $arg)?.value; + + break; + default: + children = String($arg.type); + } + + return ps.external({children.value}); + },), + Button: ps.fn(function* ({ arg, env }) { + const $arg = yield* env.eval(arg); + + let children = ''; + let clickHandler = (): void => void 0; + + switch ($arg.type) { + case 'string': + children = $arg.value; + break; + case 'map': { + children = lookup('text', $arg)?.value ?? ''; + + const psClickHandler = lookup('onClick', $arg); + + if (psClickHandler) { + assert(psClickHandler.type === 'fn', `onClick must be a function but is ${psClickHandler.type}`); + + clickHandler = () => { + interpreter.run(() => env.call(psClickHandler, ps.boolean(true))); + }; + } + + break; + } + default: + children = String($arg.type); + } + + return ps.external(); + },) + }) +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 9e13034a3e..650e70d650 100644 --- a/yarn.lock +++ b/yarn.lock @@ -18841,10 +18841,10 @@ pkginfo@0.4.x, pkginfo@^0.4.1: resolved "https://registry.yarnpkg.com/pkginfo/-/pkginfo-0.4.1.tgz#b5418ef0439de5425fc4995042dced14fb2a84ff" integrity sha512-8xCNE/aT/EXKenuMDZ+xTVwkT8gsoHN2z/Q29l80u0ppGEXVvsKRzNMbtKhg8LS8k1tJLAHHylf6p4VFmP6XUQ== -platformscript@^1.0.0-cl-exported-run.0: - version "1.0.0-cl-exported-run.0" - resolved "https://registry.yarnpkg.com/platformscript/-/platformscript-1.0.0-cl-exported-run.0.tgz#097f037f0bbe39c594b976532e34454d402b6500" - integrity sha512-sgOksrHaG5YARa4EK7EfUqOf0VJkxaiaXLshvO2YrotfE3p5H3YPqBsKvWQYd3L6IgHr3AuYXIJmVyQbSY6ITw== +platformscript@1.0.0-alpha.6: + version "1.0.0-alpha.6" + resolved "https://registry.yarnpkg.com/platformscript/-/platformscript-1.0.0-alpha.6.tgz#b995c514b769b587d6879f45ab6b249dc05ae83a" + integrity sha512-QSsY/yTBjj408NYMVyWAL0mDM9Ft3CvVc1O8F7xNzyg5pRl9GE8v7Hl+la2ZC/iYKNcByNc9j1FgHxkFfcpvSw== dependencies: yaml-ast-parser "0.0.43" From d8e62026196fc8042845a577e590ce6b6748ecb2 Mon Sep 17 00:00:00 2001 From: Paul Cowan Date: Tue, 22 Nov 2022 17:21:13 +0000 Subject: [PATCH 10/21] add CodeSnippet --- .../app/src/components/PlatformScriptPage.tsx | 15 ++++++++--- packages/app/src/components/globals.tsx | 27 +++++++++++++++++-- 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/packages/app/src/components/PlatformScriptPage.tsx b/packages/app/src/components/PlatformScriptPage.tsx index 821e51523d..884c955a20 100644 --- a/packages/app/src/components/PlatformScriptPage.tsx +++ b/packages/app/src/components/PlatformScriptPage.tsx @@ -13,7 +13,17 @@ const DefultYaml = `$<>: - $div: className: 'whatever' children: - "children" + $CodeSnippet: + language: 'yaml' + text: >- + metadata: + name: example + links: + - url: https://dashboard.example.com + title: My Dashboard + icon: dashboard + showLineNumbers: true + highlightedNumbers: [3, 4, 5, 6] `; export function PlatformScriptPage() { @@ -34,9 +44,6 @@ export function PlatformScriptPage() { return mod.value; }, [yaml]); - - // eslint-disable-next-line @typescript-eslint/no-unused-expressions - // result.error && console.log(result.error); return ( <> diff --git a/packages/app/src/components/globals.tsx b/packages/app/src/components/globals.tsx index 3e2e62426f..f43fd98c0f 100644 --- a/packages/app/src/components/globals.tsx +++ b/packages/app/src/components/globals.tsx @@ -4,7 +4,7 @@ import type { ComponentType } from 'react'; import type { PlatformScript, PSMap, PSValue } from 'platformscript'; import * as ps from 'platformscript'; import { Button, Typography } from '@material-ui/core'; - +import { CodeSnippet } from '@backstage/core-components'; type TypographyProps = typeof Typography extends ComponentType ? P @@ -22,7 +22,6 @@ export function lookup(key: string, map: PSMap): PSValue | void { return void 0; } - export function globals(interpreter: PlatformScript) { return ps.map({ alert: ps.fn(function* ({ arg, env }) { @@ -62,6 +61,30 @@ export function globals(interpreter: PlatformScript) { return ps.external(
{children.value}
) }), + CodeSnippet: ps.fn(function* ({ arg, env }) { + const $arg = yield* env.eval(arg); + let text = ''; + let showLineNumbers: boolean | undefined = undefined; + let highlightedNumbers: number[] = []; + let language = '' + + if ($arg.type === 'map') { + text = lookup('text', $arg)?.value; + showLineNumbers = lookup('showLineNumbers', $arg)?.value; + highlightedNumbers = lookup('highlightedNumbers', $arg)?.value.map((v: any) => v.value); + language = lookup('language', $arg)?.value; + } + + return ps.external( + + ) + }), Typography: ps.fn(function* ({ arg, env }) { const $arg = yield* env.eval(arg); let variant = ''; From 460527a33f2654de3671f6109c75ff5ada9fc625 Mon Sep 17 00:00:00 2001 From: Paul Cowan Date: Tue, 22 Nov 2022 17:42:18 +0000 Subject: [PATCH 11/21] add final button --- .../app/src/components/PlatformScriptPage.tsx | 8 ++++++- packages/app/src/components/globals.tsx | 23 +++++++++++++++---- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/packages/app/src/components/PlatformScriptPage.tsx b/packages/app/src/components/PlatformScriptPage.tsx index 884c955a20..0c49cc4de5 100644 --- a/packages/app/src/components/PlatformScriptPage.tsx +++ b/packages/app/src/components/PlatformScriptPage.tsx @@ -24,6 +24,12 @@ const DefultYaml = `$<>: icon: dashboard showLineNumbers: true highlightedNumbers: [3, 4, 5, 6] + - $Button: + text: "Read More" + variant: "contained" + color: "primary" + target: "_blank" + href: "https://backstage.io/docs/features/software-catalog/descriptor-format#links-optional" `; export function PlatformScriptPage() { @@ -44,7 +50,7 @@ export function PlatformScriptPage() { return mod.value; }, [yaml]); - + return ( <>
diff --git a/packages/app/src/components/globals.tsx b/packages/app/src/components/globals.tsx index f43fd98c0f..64a07b760f 100644 --- a/packages/app/src/components/globals.tsx +++ b/packages/app/src/components/globals.tsx @@ -6,9 +6,12 @@ import * as ps from 'platformscript'; import { Button, Typography } from '@material-ui/core'; import { CodeSnippet } from '@backstage/core-components'; -type TypographyProps = typeof Typography extends ComponentType - ? P - : never; +type ComponentProps = C extends ComponentType +? P +: never; + +type TypographyProps = ComponentProps; +type ButtonProps = ComponentProps; type Variant = TypographyProps['variant']; @@ -108,6 +111,9 @@ export function globals(interpreter: PlatformScript) { let children = ''; let clickHandler = (): void => void 0; + let variant: Variant = undefined; + let color = ''; + let href: string | undefined = undefined; switch ($arg.type) { case 'string': @@ -115,6 +121,9 @@ export function globals(interpreter: PlatformScript) { break; case 'map': { children = lookup('text', $arg)?.value ?? ''; + variant = lookup('variant', $arg)?.value ?? ''; + color = lookup('color', $arg)?.value ?? ''; + href = lookup('href', $arg)?.value ?? ''; const psClickHandler = lookup('onClick', $arg); @@ -132,7 +141,13 @@ export function globals(interpreter: PlatformScript) { children = String($arg.type); } - return ps.external(); + return ps.external( + ); },) }) } \ No newline at end of file From 75c0cf5e526b796b433d9173b8a8f45494edf031 Mon Sep 17 00:00:00 2001 From: Paul Cowan Date: Tue, 22 Nov 2022 18:26:19 +0000 Subject: [PATCH 12/21] add target to Button --- .../app/src/components/PlatformScriptPage.tsx | 2 +- .../app/src/components/catalog/EntityPage.tsx | 6 +++++- .../src/components/yaml-editor/YamlEditor.tsx | 20 ++++++++++--------- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/packages/app/src/components/PlatformScriptPage.tsx b/packages/app/src/components/PlatformScriptPage.tsx index 0c49cc4de5..ff16d906c9 100644 --- a/packages/app/src/components/PlatformScriptPage.tsx +++ b/packages/app/src/components/PlatformScriptPage.tsx @@ -55,13 +55,13 @@ export function PlatformScriptPage() { <>
{result.loading && (

...loading

)} + {result.value} setYaml(value)} value={yaml} /> - {result.value}
); diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx index 984e450897..cf9a56f029 100644 --- a/packages/app/src/components/catalog/EntityPage.tsx +++ b/packages/app/src/components/catalog/EntityPage.tsx @@ -58,6 +58,8 @@ import { HumanitecCardComponent } from '@frontside/backstage-plugin-humanitec'; import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; +import { PlatformScriptPage } from '../PlatformScriptPage'; +import { InfoCard } from '@backstage/core-components'; const techdocsContent = ( @@ -124,7 +126,9 @@ const overviewContent = ( - + + + diff --git a/packages/app/src/components/yaml-editor/YamlEditor.tsx b/packages/app/src/components/yaml-editor/YamlEditor.tsx index e38d055101..3d6d8ce5d3 100644 --- a/packages/app/src/components/yaml-editor/YamlEditor.tsx +++ b/packages/app/src/components/yaml-editor/YamlEditor.tsx @@ -10,14 +10,16 @@ export type Monaco = Parameters[0]; export function YAMLEditor({ onMount, onChange, defaultValue, value }: YAMLEditorProps): JSX.Element { return ( - +
+ +
); } \ No newline at end of file From ac6b471cd195c8c9b983a8171d5596163529d8bd Mon Sep 17 00:00:00 2001 From: Taras Date: Tue, 22 Nov 2022 18:29:58 -0500 Subject: [PATCH 13/21] Adding components with React.createElement --- catalog-info.yaml | 12 ++++ packages/app/package.json | 5 +- .../app/src/components/PlatformScriptPage.tsx | 27 +++++--- .../app/src/components/catalog/EntityPage.tsx | 65 ++++++++++++------- .../components/catalog/PSOverviewContent.tsx | 18 +++++ packages/app/src/components/globals.tsx | 57 +++++++++++----- yarn.lock | 26 +++++++- 7 files changed, 156 insertions(+), 54 deletions(-) create mode 100644 packages/app/src/components/catalog/PSOverviewContent.tsx diff --git a/catalog-info.yaml b/catalog-info.yaml index 5f5d118000..c0848bd2ea 100644 --- a/catalog-info.yaml +++ b/catalog-info.yaml @@ -10,12 +10,24 @@ metadata: "humanitec.com/orgId": "the-frontside-software-inc" "humanitec.com/appId": "backstage" # backstage.io/techdocs-ref: dir:. + links: + - url: https://dashboard.example.com + title: My Dashboard + icon: dashboard spec: type: website owner: engineering@frontside.com lifecycle: production providesApis: - backstage-graphql-api + overviewContentLayout: + $Grid: + container: true + spacing: 3 + alignItems: stretch + children: + - $div: Hello World + --- apiVersion: backstage.io/v1alpha1 kind: API diff --git a/packages/app/package.json b/packages/app/package.json index 7fa5d14a31..d4e8efbf63 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -27,8 +27,8 @@ "@backstage/plugin-search-react": "^1.2.0", "@backstage/plugin-tech-radar": "^0.5.17", "@backstage/plugin-techdocs": "^1.3.3", - "@backstage/plugin-techdocs-react": "^1.0.5", "@backstage/plugin-techdocs-module-addons-contrib": "^1.0.5", + "@backstage/plugin-techdocs-react": "^1.0.5", "@backstage/plugin-user-settings": "^0.5.0", "@backstage/theme": "^0.2.16", "@frontside/backstage-plugin-effection-inspector": "^0.1.3", @@ -44,7 +44,8 @@ "react-dom": "^17.0.2", "react-router": "^6.3.0", "react-router-dom": "^6.3.0", - "react-use": "^17.2.4" + "react-use": "^17.2.4", + "yaml": "^2.1.3" }, "devDependencies": { "@backstage/test-utils": "^1.2.1", diff --git a/packages/app/src/components/PlatformScriptPage.tsx b/packages/app/src/components/PlatformScriptPage.tsx index ff16d906c9..0a6ee5cba4 100644 --- a/packages/app/src/components/PlatformScriptPage.tsx +++ b/packages/app/src/components/PlatformScriptPage.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useRef, useState } from 'react'; +import React, { useCallback, useMemo, useRef, useState } from 'react'; import * as ps from 'platformscript'; import { MonacoEditor, YAMLEditor } from './yaml-editor/YamlEditor'; import { useAsync } from 'react-use'; @@ -32,24 +32,33 @@ const DefultYaml = `$<>: href: "https://backstage.io/docs/features/software-catalog/descriptor-format#links-optional" `; +export function usePlatformScript(yaml: string) { + const platformscript = useMemo(() => { + return ps.createPlatformScript(globals); + }, []); + + const result = useAsync(async (): Promise => { + const program = ps.parse(yaml as string); + + const mod = await platformscript.eval(program); + + return mod.value; + }, [yaml]); + + return result; +} + export function PlatformScriptPage() { const editorRef = useRef(null); - const platformscript = ps.createPlatformScript(globals); - const [yaml, setYaml] = useState(DefultYaml); const handleEditorMount = useCallback((editor: MonacoEditor) => { editorRef.current = editor; }, []); - const result = useAsync(async (): Promise => { - const program = ps.parse(yaml as string); - const mod = await platformscript.eval(program); - - return mod.value; - }, [yaml]); + const result = usePlatformScript(yaml ?? 'false'); return ( <> diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx index cf9a56f029..e8d5686d53 100644 --- a/packages/app/src/components/catalog/EntityPage.tsx +++ b/packages/app/src/components/catalog/EntityPage.tsx @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; +import React, { useMemo } from 'react'; import { Button, Grid } from '@material-ui/core'; import { EntityApiDefinitionCard, @@ -58,8 +58,9 @@ import { HumanitecCardComponent } from '@frontside/backstage-plugin-humanitec'; import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; -import { PlatformScriptPage } from '../PlatformScriptPage'; -import { InfoCard } from '@backstage/core-components'; +import { useEntity } from '@backstage/plugin-catalog-react'; +import { PSOverviewContent } from './PSOverviewContent'; +import { stringify } from 'yaml'; const techdocsContent = ( @@ -116,30 +117,46 @@ const entityWarningContent = ( ); -const overviewContent = ( - - {entityWarningContent} - - - - - - - - - - - - - +function OverviewContent() { + const { entity } = useEntity(); + + console.log(entity) + + const overviewContentLayout = useMemo(() => { + return stringify(entity?.spec?.overviewContentLayout) + }, [entity?.spec?.overviewContentLayout]) + + if (overviewContentLayout) { + return + } + + return ( + + {entityWarningContent} + + + + + + + + + {/* + + */} + + + + - -); + ) +} + const serviceEntityPage = ( - {overviewContent} + @@ -177,7 +194,7 @@ const serviceEntityPage = ( const websiteEntityPage = ( - {overviewContent} + @@ -211,7 +228,7 @@ const websiteEntityPage = ( const defaultEntityPage = ( - {overviewContent} + diff --git a/packages/app/src/components/catalog/PSOverviewContent.tsx b/packages/app/src/components/catalog/PSOverviewContent.tsx new file mode 100644 index 0000000000..e05e6ca8c3 --- /dev/null +++ b/packages/app/src/components/catalog/PSOverviewContent.tsx @@ -0,0 +1,18 @@ +import React from 'react'; +import { usePlatformScript } from '../PlatformScriptPage'; + +export function PSOverviewContent({ yaml }: { yaml: string; }) { + const result = usePlatformScript(yaml); + + console.log(result) + + if (result.loading) { + return <>Loading...; + } + + if (result.error) { + return <>{result.error.message}; + } + + return <>{result.value}; +} diff --git a/packages/app/src/components/globals.tsx b/packages/app/src/components/globals.tsx index 64a07b760f..888469464d 100644 --- a/packages/app/src/components/globals.tsx +++ b/packages/app/src/components/globals.tsx @@ -3,7 +3,7 @@ import { assert } from 'assert-ts'; import type { ComponentType } from 'react'; import type { PlatformScript, PSMap, PSValue } from 'platformscript'; import * as ps from 'platformscript'; -import { Button, Typography } from '@material-ui/core'; +import { Button, Grid, Typography } from '@material-ui/core'; import { CodeSnippet } from '@backstage/core-components'; type ComponentProps = C extends ComponentType @@ -25,8 +25,46 @@ export function lookup(key: string, map: PSMap): PSValue | void { return void 0; } +function createReactComponent(type: any) { + return ps.fn(function* ({ arg, env, rest }) { + + const $arg: PSValue = yield* env.eval(arg); + const $options = (yield* env.eval(rest)) as PSMap; + + let props = {}; + let children = []; + + switch ($arg.type) { + case "map": { + props = [...$arg.value.entries()] + .reduce((result, [key, value]) => { + return { ...result, [String(key.value)]: value.value } + }, {}); + const _children = lookup('children', $options); + if (!!_children) { + if (_children.type === "list") { + children = _children.value.map(value => value.value) + } else { + children = _children.value; + } + } + } + break; + case "list": + children = $arg.value.map(value => value.value); + break; + default: + children = $arg.value; + + } + + return ps.external(React.createElement(type, props, children)) + }); +}; + export function globals(interpreter: PlatformScript) { return ps.map({ + Grid: createReactComponent(Grid), alert: ps.fn(function* ({ arg, env }) { const $arg = yield* env.eval(arg); @@ -48,22 +86,7 @@ export function globals(interpreter: PlatformScript) { return ps.external(<>{elements}); }), - div: ps.fn(function* ({ arg, env }) { - const $arg = yield* env.eval(arg); - - let children: any = ''; - - switch ($arg.type) { - case 'map': - children = lookup('children', $arg); - - break; - default: - children = String($arg.type); - } - - return ps.external(
{children.value}
) - }), + div: createReactComponent('div'), CodeSnippet: ps.fn(function* ({ arg, env }) { const $arg = yield* env.eval(arg); let text = ''; diff --git a/yarn.lock b/yarn.lock index 650e70d650..21e6a62135 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7455,13 +7455,20 @@ resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== -"@types/react-dom@*", "@types/react-dom@<18.0.0", "@types/react-dom@^17", "@types/react-dom@^18.0.0": +"@types/react-dom@*", "@types/react-dom@<18.0.0": version "17.0.18" resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.18.tgz#8f7af38f5d9b42f79162eea7492e5a1caff70dc2" integrity sha512-rLVtIfbwyur2iFKykP2w0pl/1unw26b5td16d5xMgp7/yjTHomkyxPYChFoCr/FtEX1lN9wY6lFj1qvKdS5kDw== dependencies: "@types/react" "^17" +"@types/react-dom@^18.0.0": + version "18.0.9" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.0.9.tgz#ffee5e4bfc2a2f8774b15496474f8e7fe8d0b504" + integrity sha512-qnVvHxASt/H7i+XG1U1xMiY5t+IHcPGUK7TDMDzom08xa7e86eCeKOiLZezwCKVxJn6NEiiy2ekgX8aQssjIKg== + dependencies: + "@types/react" "*" + "@types/react-redux@^7.1.20": version "7.1.24" resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.24.tgz#6caaff1603aba17b27d20f8ad073e4c077e975c0" @@ -13329,11 +13336,21 @@ graphql-ws@^5.4.1: resolved "https://registry.yarnpkg.com/graphql-ws/-/graphql-ws-5.9.1.tgz#9c0fa48ceb695d61d574ed3ab21b426729e87f2d" integrity sha512-mL/SWGBwIT9Meq0NlfS55yXXTOeWPMbK7bZBEZhFu46bcGk1coTx2Sdtzxdk+9yHWngD+Fk1PZDWaAutQa9tpw== -graphql@*, graphql@16.5.0, "graphql@^15.0.0 || ^16.0.0", graphql@^15.5.1, graphql@^16.0.0, graphql@^16.3.0, graphql@^16.5.0: +graphql@*, "graphql@^15.0.0 || ^16.0.0", graphql@^16.0.0, graphql@^16.3.0, graphql@^16.5.0: version "16.6.0" resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.6.0.tgz#c2dcffa4649db149f6282af726c8c83f1c7c5fdb" integrity sha512-KPIBPDlW7NxrbT/eh4qPXz5FiFdL5UbaA0XUNz2Rp3Z3hqBSkbj0GVjwFDztsWVauZUWsbKHgMg++sk8UX0bkw== +graphql@16.5.0: + version "16.5.0" + resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.5.0.tgz#41b5c1182eaac7f3d47164fb247f61e4dfb69c85" + integrity sha512-qbHgh8Ix+j/qY+a/ZcJnFQ+j8ezakqPiHwPiZhV/3PgGlgf96QMBB5/f2rkiC9sgLoy/xvT6TSiaf2nTHJh5iA== + +graphql@^15.5.1: + version "15.8.0" + resolved "https://registry.yarnpkg.com/graphql/-/graphql-15.8.0.tgz#33410e96b012fa3bdb1091cc99a94769db212b38" + integrity sha512-5gghUc24tP9HRznNpV2+FIoq3xKkj5dTQqf4v0CpdPbFVwFkWoxOM+o+2OC9ZSvjEMTjfmG9QT+gcvggTwW1zw== + gson-conform@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/gson-conform/-/gson-conform-1.0.3.tgz#6f982f98ea84199280bd48b6bfbcd0ae7447f1e2" @@ -23644,6 +23661,11 @@ yaml@^2.0.0: resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.1.1.tgz#1e06fb4ca46e60d9da07e4f786ea370ed3c3cfec" integrity sha512-o96x3OPo8GjWeSLF+wOAbrPfhFOGY0W00GNaxCDv+9hkcDJEnev1yh8S7pgHF0ik6zc8sQLuL8hjHjJULZp8bw== +yaml@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.1.3.tgz#9b3a4c8aff9821b696275c79a8bee8399d945207" + integrity sha512-AacA8nRULjKMX2DvWvOAdBZMOfQlypSFkjcOcu9FalllIDJ1kvlREzcdIZmidQUqqeMv7jorHjq2HlLv/+c2lg== + yargs-parser@20.2.4: version "20.2.4" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54" From 5919b13960db59cc83c6628e391ebaf7cc2349b8 Mon Sep 17 00:00:00 2001 From: Taras Date: Tue, 22 Nov 2022 20:04:40 -0500 Subject: [PATCH 14/21] Rendering EntityAboutCard --- catalog-info.yaml | 9 ++++++++- packages/app/src/components/globals.tsx | 23 +++++++++++++---------- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/catalog-info.yaml b/catalog-info.yaml index c0848bd2ea..cc9e249f40 100644 --- a/catalog-info.yaml +++ b/catalog-info.yaml @@ -26,7 +26,14 @@ spec: spacing: 3 alignItems: stretch children: - - $div: Hello World + - $Grid: + item: true + md: 6 + children: + - $EntityAboutCard: + variant: gridItem + + --- apiVersion: backstage.io/v1alpha1 diff --git a/packages/app/src/components/globals.tsx b/packages/app/src/components/globals.tsx index 888469464d..4c581434be 100644 --- a/packages/app/src/components/globals.tsx +++ b/packages/app/src/components/globals.tsx @@ -5,6 +5,7 @@ import type { PlatformScript, PSMap, PSValue } from 'platformscript'; import * as ps from 'platformscript'; import { Button, Grid, Typography } from '@material-ui/core'; import { CodeSnippet } from '@backstage/core-components'; +import { EntityAboutCard } from '@backstage/plugin-catalog'; type ComponentProps = C extends ComponentType ? P @@ -29,26 +30,27 @@ function createReactComponent(type: any) { return ps.fn(function* ({ arg, env, rest }) { const $arg: PSValue = yield* env.eval(arg); - const $options = (yield* env.eval(rest)) as PSMap; + const $options = yield* env.eval(rest); let props = {}; let children = []; switch ($arg.type) { - case "map": { + case "map": props = [...$arg.value.entries()] .reduce((result, [key, value]) => { return { ...result, [String(key.value)]: value.value } }, {}); - const _children = lookup('children', $options); - if (!!_children) { - if (_children.type === "list") { - children = _children.value.map(value => value.value) - } else { - children = _children.value; + if ($options.type === "map") { + const _children = lookup('children', $options); + if (!!_children) { + if (_children.type === "list") { + children = _children.value.map(value => value.value) + } else { + children = _children.value; + } } } - } break; case "list": children = $arg.value.map(value => value.value); @@ -64,7 +66,9 @@ function createReactComponent(type: any) { export function globals(interpreter: PlatformScript) { return ps.map({ + div: createReactComponent('div'), Grid: createReactComponent(Grid), + EntityAboutCard: createReactComponent(EntityAboutCard), alert: ps.fn(function* ({ arg, env }) { const $arg = yield* env.eval(arg); @@ -86,7 +90,6 @@ export function globals(interpreter: PlatformScript) { return ps.external(<>{elements}); }), - div: createReactComponent('div'), CodeSnippet: ps.fn(function* ({ arg, env }) { const $arg = yield* env.eval(arg); let text = ''; From 98acd433582755abc70148b3f79b59bb31a8445c Mon Sep 17 00:00:00 2001 From: Taras Date: Tue, 22 Nov 2022 22:39:08 -0500 Subject: [PATCH 15/21] Add key to children --- catalog-info.yaml | 12 +++++ .../components/catalog/PSOverviewContent.tsx | 2 - packages/app/src/components/globals.tsx | 44 ++++++++++++++++--- 3 files changed, 50 insertions(+), 8 deletions(-) diff --git a/catalog-info.yaml b/catalog-info.yaml index cc9e249f40..b15ff76cbc 100644 --- a/catalog-info.yaml +++ b/catalog-info.yaml @@ -32,6 +32,18 @@ spec: children: - $EntityAboutCard: variant: gridItem + - $Grid: + item: true + md: 6 + # children: + # - $HumanitecCardComponent + - $Grid: + item: true + md: 4 + xs: 12 + # children: + # - $EntityLinksCard + diff --git a/packages/app/src/components/catalog/PSOverviewContent.tsx b/packages/app/src/components/catalog/PSOverviewContent.tsx index e05e6ca8c3..7464999fea 100644 --- a/packages/app/src/components/catalog/PSOverviewContent.tsx +++ b/packages/app/src/components/catalog/PSOverviewContent.tsx @@ -4,8 +4,6 @@ import { usePlatformScript } from '../PlatformScriptPage'; export function PSOverviewContent({ yaml }: { yaml: string; }) { const result = usePlatformScript(yaml); - console.log(result) - if (result.loading) { return <>Loading...; } diff --git a/packages/app/src/components/globals.tsx b/packages/app/src/components/globals.tsx index 4c581434be..ce2194395a 100644 --- a/packages/app/src/components/globals.tsx +++ b/packages/app/src/components/globals.tsx @@ -5,7 +5,8 @@ import type { PlatformScript, PSMap, PSValue } from 'platformscript'; import * as ps from 'platformscript'; import { Button, Grid, Typography } from '@material-ui/core'; import { CodeSnippet } from '@backstage/core-components'; -import { EntityAboutCard } from '@backstage/plugin-catalog'; +import { EntityAboutCard, EntityLinksCard } from '@backstage/plugin-catalog'; +import { HumanitecCardComponent } from '@frontside/backstage-plugin-humanitec'; type ComponentProps = C extends ComponentType ? P @@ -30,18 +31,47 @@ function createReactComponent(type: any) { return ps.fn(function* ({ arg, env, rest }) { const $arg: PSValue = yield* env.eval(arg); + if (rest.type === "map") { + // add key prop to each item in children array + for (const [key, value] of rest.value.entries()) { + if (key.type === "string" && key.value === "children" && value.type === "list") { + let index = 0; + for (const item of value.value) { + if (item.type === "map") { + let found; + for (const [propKey] of item.value.entries()) { + if (propKey.value === "key") { + found = true; + break; + } + } + if (!found) { + item.value.set( + { type: "string", value: "key" }, + { type: "string", value: String(index) } + ) + } + } + index++; + } + } + } + } const $options = yield* env.eval(rest); - let props = {}; + const props: Record = {}; let children = []; switch ($arg.type) { case "map": - props = [...$arg.value.entries()] - .reduce((result, [key, value]) => { - return { ...result, [String(key.value)]: value.value } - }, {}); + for (const [key, value] of $arg.value.entries()) { + props[String(key.value)] = value.value + } if ($options.type === "map") { + const key = lookup('key', $options); + if (!!key) { + props.key = key.value + } const _children = lookup('children', $options); if (!!_children) { if (_children.type === "list") { @@ -69,6 +99,8 @@ export function globals(interpreter: PlatformScript) { div: createReactComponent('div'), Grid: createReactComponent(Grid), EntityAboutCard: createReactComponent(EntityAboutCard), + HumanitecCardComponent: createReactComponent(HumanitecCardComponent), + EntityLinksCard: createReactComponent(EntityLinksCard), alert: ps.fn(function* ({ arg, env }) { const $arg = yield* env.eval(arg); From cfb57ae0877c69ac92e1ab97b5cde86b9103e5ba Mon Sep 17 00:00:00 2001 From: Taras Date: Tue, 22 Nov 2022 22:49:45 -0500 Subject: [PATCH 16/21] Rendering the entire page --- catalog-info.yaml | 17 +++++++++++++---- packages/app/src/components/globals.tsx | 4 ++-- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/catalog-info.yaml b/catalog-info.yaml index b15ff76cbc..2fd4b029bc 100644 --- a/catalog-info.yaml +++ b/catalog-info.yaml @@ -35,14 +35,23 @@ spec: - $Grid: item: true md: 6 - # children: - # - $HumanitecCardComponent + children: + - $HumanitecCardComponent: + key: "0" - $Grid: item: true md: 4 xs: 12 - # children: - # - $EntityLinksCard + children: + - $EntityLinksCard: + key: "0" + - $Grid: + item: true + md: 8 + xs: 12 + children: + - $EntityHasSubcomponentsCard: + variant: gridItem diff --git a/packages/app/src/components/globals.tsx b/packages/app/src/components/globals.tsx index ce2194395a..6a37df0baa 100644 --- a/packages/app/src/components/globals.tsx +++ b/packages/app/src/components/globals.tsx @@ -5,7 +5,7 @@ import type { PlatformScript, PSMap, PSValue } from 'platformscript'; import * as ps from 'platformscript'; import { Button, Grid, Typography } from '@material-ui/core'; import { CodeSnippet } from '@backstage/core-components'; -import { EntityAboutCard, EntityLinksCard } from '@backstage/plugin-catalog'; +import { EntityAboutCard, EntityHasSubcomponentsCard, EntityLinksCard } from '@backstage/plugin-catalog'; import { HumanitecCardComponent } from '@frontside/backstage-plugin-humanitec'; type ComponentProps = C extends ComponentType @@ -87,7 +87,6 @@ function createReactComponent(type: any) { break; default: children = $arg.value; - } return ps.external(React.createElement(type, props, children)) @@ -101,6 +100,7 @@ export function globals(interpreter: PlatformScript) { EntityAboutCard: createReactComponent(EntityAboutCard), HumanitecCardComponent: createReactComponent(HumanitecCardComponent), EntityLinksCard: createReactComponent(EntityLinksCard), + EntityHasSubcomponentsCard: createReactComponent(EntityHasSubcomponentsCard), alert: ps.fn(function* ({ arg, env }) { const $arg = yield* env.eval(arg); From 03e9189c0448502d7d46967a9470d1c06eda0dbb Mon Sep 17 00:00:00 2001 From: Taras Date: Wed, 23 Nov 2022 09:05:21 -0500 Subject: [PATCH 17/21] Showing content editor --- catalog-info.yaml | 3 - packages/app/src/App.tsx | 4 +- .../app/src/components/PlatformScriptPage.tsx | 10 +-- .../app/src/components/catalog/EntityPage.tsx | 80 ++----------------- .../components/catalog/OverviewContent.tsx | 66 +++++++++++++++ .../src/components/catalog/WebsiteLayout.tsx | 47 +++++++++++ packages/app/src/hooks.ts | 20 +++++ 7 files changed, 146 insertions(+), 84 deletions(-) create mode 100644 packages/app/src/components/catalog/OverviewContent.tsx create mode 100644 packages/app/src/components/catalog/WebsiteLayout.tsx create mode 100644 packages/app/src/hooks.ts diff --git a/catalog-info.yaml b/catalog-info.yaml index 2fd4b029bc..29b55ff7ff 100644 --- a/catalog-info.yaml +++ b/catalog-info.yaml @@ -53,9 +53,6 @@ spec: - $EntityHasSubcomponentsCard: variant: gridItem - - - --- apiVersion: backstage.io/v1alpha1 kind: API diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index bf8e701cb0..eb43a8edd8 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -25,7 +25,7 @@ import { UserSettingsPage } from '@backstage/plugin-user-settings'; import { apis } from './apis'; import { entityPage } from './components/catalog/EntityPage'; import { searchPage } from './components/search/SearchPage'; -import { PlatformScriptPage } from './components/PlatformScriptPage'; +import { PlatformScriptEditor } from './components/PlatformScriptPage'; import { Root } from './components/Root'; import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; @@ -79,7 +79,7 @@ const routes = ( } /> - } /> + } /> } /> : +const defaultYaml = `$<>: - $Typography: variant: 'body1' children: @@ -48,25 +48,23 @@ export function usePlatformScript(yaml: string) { return result; } -export function PlatformScriptPage() { +export function PlatformScriptEditor({ initialYaml }: { initialYaml: string }) { const editorRef = useRef(null); - const [yaml, setYaml] = useState(DefultYaml); + const [yaml, setYaml] = useState(initialYaml); const handleEditorMount = useCallback((editor: MonacoEditor) => { editorRef.current = editor; }, []); - const result = usePlatformScript(yaml ?? 'false'); return ( <>
{result.loading && (

...loading

)} - {result.value} setYaml(value)} value={yaml} diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx index e8d5686d53..11efb67b89 100644 --- a/packages/app/src/components/catalog/EntityPage.tsx +++ b/packages/app/src/components/catalog/EntityPage.tsx @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React, { useMemo } from 'react'; +import React from 'react'; import { Button, Grid } from '@material-ui/core'; import { EntityApiDefinitionCard, @@ -29,7 +29,6 @@ import { EntityDependsOnResourcesCard, EntityHasComponentsCard, EntityHasResourcesCard, - EntityHasSubcomponentsCard, EntityHasSystemsCard, EntityLayout, EntityLinksCard, @@ -54,15 +53,13 @@ import { } from '@backstage/plugin-org'; import { EntityTechdocsContent } from '@backstage/plugin-techdocs'; import { EmptyState } from '@backstage/core-components'; -import { HumanitecCardComponent } from '@frontside/backstage-plugin-humanitec'; import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; -import { useEntity } from '@backstage/plugin-catalog-react'; -import { PSOverviewContent } from './PSOverviewContent'; -import { stringify } from 'yaml'; +import { OverviewContent } from './OverviewContent'; +import { WebsiteLayout } from './WebsiteLayout'; -const techdocsContent = ( +export const techdocsContent = ( @@ -70,7 +67,7 @@ const techdocsContent = ( ); -const cicdContent = ( +export const cicdContent = ( // This is an example of how you can implement your company's logic in entity page. // You can for example enforce that all components of type 'service' should use GitHubActions @@ -97,7 +94,7 @@ const cicdContent = ( ); -const entityWarningContent = ( +export const entityWarningContent = ( <> @@ -117,42 +114,6 @@ const entityWarningContent = ( ); -function OverviewContent() { - const { entity } = useEntity(); - - console.log(entity) - - const overviewContentLayout = useMemo(() => { - return stringify(entity?.spec?.overviewContentLayout) - }, [entity?.spec?.overviewContentLayout]) - - if (overviewContentLayout) { - return - } - - return ( - - {entityWarningContent} - - - - - - - - - {/* - - */} - - - - - - ) -} - - const serviceEntityPage = ( @@ -191,33 +152,6 @@ const serviceEntityPage = ( ); -const websiteEntityPage = ( - - - - - - - {cicdContent} - - - - - - - - - - - - - - - {techdocsContent} - - -); - /** * NOTE: This page is designed to work on small screens such as mobile devices. * This is based on Material UI Grid. If breakpoints are used, each grid item must set the `xs` prop to a column size or to `true`, @@ -244,7 +178,7 @@ const componentPage = ( - {websiteEntityPage} + {defaultEntityPage} diff --git a/packages/app/src/components/catalog/OverviewContent.tsx b/packages/app/src/components/catalog/OverviewContent.tsx new file mode 100644 index 0000000000..aec791ef82 --- /dev/null +++ b/packages/app/src/components/catalog/OverviewContent.tsx @@ -0,0 +1,66 @@ +import React, { useMemo, useState } from 'react'; +import { useEntity } from '@backstage/plugin-catalog-react'; +import { PSOverviewContent } from './PSOverviewContent'; +import { stringify } from 'yaml'; +import { Grid } from '@material-ui/core'; +import { PlatformScriptEditor } from '../PlatformScriptPage'; + +const defaultOverviewContent = ` +$Grid: + container: true + spacing: 3 + alignItems: stretch +children: + - $Grid: + item: true + md: 6 + children: + - $EntityAboutCard: + variant: gridItem + - $Grid: + item: true + md: 6 + children: + - $HumanitecCardComponent: + key: "0" + - $Grid: + item: true + md: 4 + xs: 12 + children: + - $EntityLinksCard: + key: "0" + - $Grid: + item: true + md: 8 + xs: 12 + children: + - $EntityHasSubcomponentsCard: + variant: gridItem +` + +export function OverviewContent({ isEditing }: { isEditing?: boolean }) { + const { entity } = useEntity(); + + const overviewContentLayout = useMemo(() => { + if (entity?.spec?.overviewContentLayout) { + return stringify(entity?.spec?.overviewContentLayout); + } + return defaultOverviewContent; + }, [entity?.spec?.overviewContentLayout]); + + if (isEditing) { + return ( + + + + + + + + + ) + } + + return ; +} diff --git a/packages/app/src/components/catalog/WebsiteLayout.tsx b/packages/app/src/components/catalog/WebsiteLayout.tsx new file mode 100644 index 0000000000..06585c26e6 --- /dev/null +++ b/packages/app/src/components/catalog/WebsiteLayout.tsx @@ -0,0 +1,47 @@ +import React from 'react'; +import { Grid } from '@material-ui/core'; +import { + EntityDependsOnComponentsCard, + EntityDependsOnResourcesCard, EntityLayout +} from '@backstage/plugin-catalog'; +import { OverviewContent } from './OverviewContent'; +import DashboardIcon from '@material-ui/icons/Dashboard'; +import { useToggle } from '../../hooks'; +import { cicdContent, techdocsContent } from './EntityPage'; + +export function WebsiteLayout() { + const [isEditing, toggleIsEditing] = useToggle(); + + return ( + toggleIsEditing() + } + ]}> + + + + + + {cicdContent} + + + + + + + + + + + + + + + {techdocsContent} + + + ); +} diff --git a/packages/app/src/hooks.ts b/packages/app/src/hooks.ts new file mode 100644 index 0000000000..002dae6384 --- /dev/null +++ b/packages/app/src/hooks.ts @@ -0,0 +1,20 @@ +import { useCallback, useState } from "react"; + +/* +// usage +const [isEditing, setIsEditing] = useToggle(); + +return ( + <> + {isEditing & } + + +); +*/ +export function useToggle(initialState = false): (boolean | (() => void))[] { + const [state, setState] = useState(initialState); + + const toggle = useCallback(() => setState((_state) => !_state), []); + + return [state, toggle]; +} \ No newline at end of file From 375fa2572edb55cb1217bfd7e1e23e4b174cb30b Mon Sep 17 00:00:00 2001 From: Taras Date: Wed, 23 Nov 2022 09:21:41 -0500 Subject: [PATCH 18/21] The editor is working --- catalog-info.yaml | 34 +------------------ .../app/src/components/PlatformScriptPage.tsx | 15 +++++--- .../components/catalog/OverviewContent.tsx | 10 ++++-- 3 files changed, 19 insertions(+), 40 deletions(-) diff --git a/catalog-info.yaml b/catalog-info.yaml index 29b55ff7ff..a27676370e 100644 --- a/catalog-info.yaml +++ b/catalog-info.yaml @@ -20,39 +20,7 @@ spec: lifecycle: production providesApis: - backstage-graphql-api - overviewContentLayout: - $Grid: - container: true - spacing: 3 - alignItems: stretch - children: - - $Grid: - item: true - md: 6 - children: - - $EntityAboutCard: - variant: gridItem - - $Grid: - item: true - md: 6 - children: - - $HumanitecCardComponent: - key: "0" - - $Grid: - item: true - md: 4 - xs: 12 - children: - - $EntityLinksCard: - key: "0" - - $Grid: - item: true - md: 8 - xs: 12 - children: - - $EntityHasSubcomponentsCard: - variant: gridItem - +# overviewContentLayout: --- apiVersion: backstage.io/v1alpha1 kind: API diff --git a/packages/app/src/components/PlatformScriptPage.tsx b/packages/app/src/components/PlatformScriptPage.tsx index 7cc132c21a..3eab327ce8 100644 --- a/packages/app/src/components/PlatformScriptPage.tsx +++ b/packages/app/src/components/PlatformScriptPage.tsx @@ -42,22 +42,27 @@ export function usePlatformScript(yaml: string) { const mod = await platformscript.eval(program); - return mod.value; + return mod.value; }, [yaml]); return result; } -export function PlatformScriptEditor({ initialYaml }: { initialYaml: string }) { +interface PlatformScriptOptions { + yaml: string; + initialYaml: string + onChange: (value: string) => void +} + +export function PlatformScriptEditor({ yaml, initialYaml, onChange }: PlatformScriptOptions) { const editorRef = useRef(null); - const [yaml, setYaml] = useState(initialYaml); const handleEditorMount = useCallback((editor: MonacoEditor) => { editorRef.current = editor; }, []); - const result = usePlatformScript(yaml ?? 'false'); + const result = usePlatformScript(yaml); return ( <> @@ -66,7 +71,7 @@ export function PlatformScriptEditor({ initialYaml }: { initialYaml: string }) { setYaml(value)} + onChange={(value = '"false"') => onChange(value)} value={yaml} />
diff --git a/packages/app/src/components/catalog/OverviewContent.tsx b/packages/app/src/components/catalog/OverviewContent.tsx index aec791ef82..0ef7eb6188 100644 --- a/packages/app/src/components/catalog/OverviewContent.tsx +++ b/packages/app/src/components/catalog/OverviewContent.tsx @@ -49,14 +49,20 @@ export function OverviewContent({ isEditing }: { isEditing?: boolean }) { return defaultOverviewContent; }, [entity?.spec?.overviewContentLayout]); + const [yaml, setYaml] = useState(overviewContentLayout); + if (isEditing) { return ( - + - + ) From 80f83fd070900371b25ed319c3eabfbb66ae4461 Mon Sep 17 00:00:00 2001 From: Taras Date: Wed, 23 Nov 2022 09:23:34 -0500 Subject: [PATCH 19/21] Remove unnecessary margin --- .../src/components/yaml-editor/YamlEditor.tsx | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/packages/app/src/components/yaml-editor/YamlEditor.tsx b/packages/app/src/components/yaml-editor/YamlEditor.tsx index 3d6d8ce5d3..e38d055101 100644 --- a/packages/app/src/components/yaml-editor/YamlEditor.tsx +++ b/packages/app/src/components/yaml-editor/YamlEditor.tsx @@ -10,16 +10,14 @@ export type Monaco = Parameters[0]; export function YAMLEditor({ onMount, onChange, defaultValue, value }: YAMLEditorProps): JSX.Element { return ( -
- -
+ ); } \ No newline at end of file From ee86487f9ba0dd8698d4f8fd37b820a17fa1b6a9 Mon Sep 17 00:00:00 2001 From: Taras Date: Wed, 23 Nov 2022 09:30:15 -0500 Subject: [PATCH 20/21] Simplify the yaml document --- .../app/src/components/catalog/OverviewContent.tsx | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/app/src/components/catalog/OverviewContent.tsx b/packages/app/src/components/catalog/OverviewContent.tsx index 0ef7eb6188..f424ff9d58 100644 --- a/packages/app/src/components/catalog/OverviewContent.tsx +++ b/packages/app/src/components/catalog/OverviewContent.tsx @@ -20,23 +20,21 @@ children: - $Grid: item: true md: 6 - children: - - $HumanitecCardComponent: - key: "0" + children: + $HumanitecCardComponent: true - $Grid: item: true md: 4 xs: 12 children: - - $EntityLinksCard: - key: "0" + $EntityLinksCard: true - $Grid: item: true md: 8 xs: 12 children: - - $EntityHasSubcomponentsCard: - variant: gridItem + $EntityHasSubcomponentsCard: + variant: gridItem ` export function OverviewContent({ isEditing }: { isEditing?: boolean }) { From 3bfceaf8da2125fbdbb64597f0636561d11d81ee Mon Sep 17 00:00:00 2001 From: Taras Date: Wed, 23 Nov 2022 09:43:37 -0500 Subject: [PATCH 21/21] Added notes with defining a function --- .../app/src/components/PlatformScriptPage.tsx | 27 ------------------ .../app/src/components/catalog/notes.yaml | 28 +++++++++++++++++++ 2 files changed, 28 insertions(+), 27 deletions(-) create mode 100644 packages/app/src/components/catalog/notes.yaml diff --git a/packages/app/src/components/PlatformScriptPage.tsx b/packages/app/src/components/PlatformScriptPage.tsx index 3eab327ce8..2772d9d0f2 100644 --- a/packages/app/src/components/PlatformScriptPage.tsx +++ b/packages/app/src/components/PlatformScriptPage.tsx @@ -5,33 +5,6 @@ import { useAsync } from 'react-use'; import type { PSValue } from 'platformscript'; import { globals } from './globals'; -const defaultYaml = `$<>: - - $Typography: - variant: 'body1' - children: - "No links defined for this entity. You can add links to your entity YAML as shown in the highlighted example below:" - - $div: - className: 'whatever' - children: - $CodeSnippet: - language: 'yaml' - text: >- - metadata: - name: example - links: - - url: https://dashboard.example.com - title: My Dashboard - icon: dashboard - showLineNumbers: true - highlightedNumbers: [3, 4, 5, 6] - - $Button: - text: "Read More" - variant: "contained" - color: "primary" - target: "_blank" - href: "https://backstage.io/docs/features/software-catalog/descriptor-format#links-optional" -`; - export function usePlatformScript(yaml: string) { const platformscript = useMemo(() => { return ps.createPlatformScript(globals); diff --git a/packages/app/src/components/catalog/notes.yaml b/packages/app/src/components/catalog/notes.yaml new file mode 100644 index 0000000000..1b81fa622d --- /dev/null +++ b/packages/app/src/components/catalog/notes.yaml @@ -0,0 +1,28 @@ +$let: + EntityLinksEmptyState: + $(): + $<>: + - $Typography: + variant: 'body1' + children: + "No links defined for this entity. You can add links to your entity YAML as shown in the highlighted example below:" + - $div: + children: + $CodeSnippet: + language: 'yaml' + text: >- + metadata: + name: example + links: + - url: https://dashboard.example.com + title: My Dashboard + icon: dashboard + showLineNumbers: true + highlightedNumbers: [3, 4, 5, 6] + - $Button: + text: "Read More" + variant: "contained" + color: "primary" + target: "_blank" + href: "https://backstage.io/docs/features/software-catalog/descriptor-format#links-optional" +$do: \ No newline at end of file