Skip to content

Commit

Permalink
feat(expressions,entities-roues): add router playground [KM-299]
Browse files Browse the repository at this point in the history
  • Loading branch information
2eha0 committed Aug 5, 2024
1 parent 4f8f66b commit a4081f2
Show file tree
Hide file tree
Showing 35 changed files with 3,779 additions and 1,662 deletions.
8 changes: 6 additions & 2 deletions packages/core/expressions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Reusable components to support [Kong's expressions language](https://docs.konghq
- `vue` must be initialized in the host application
- [`monaco-editor`](https://www.npmjs.com/package/monaco-editor) is required as a dependency in the host application
- [`vite-plugin-monaco-editor`](https://www.npmjs.com/package/vite-plugin-monaco-editor) is a required Vite plugin to bundle the Monaco Editor and its web workers
- [`@kong-ui-public/forms`](https://www.npmjs.com/package/@kong-ui-public/forms) is an optional dependency required for the `RouterPlaygroundModal` component

## Usage

Expand All @@ -27,6 +28,7 @@ Install required `dependencies` in your host application:

```sh
yarn add monaco-editor
yarn add @kong-ui-public/forms # optional: required for `RouterPlaygroundModal` component
```

Install required `devDependencies` in your host application:
Expand Down Expand Up @@ -58,9 +60,10 @@ Import the component(s) in your host application as well as the package styles:
```ts
import { asyncInit, ExpressionsEditor } from '@kong-ui-public/expressions'
import '@kong-ui-public/expressions/dist/style.css'
import '@kong-ui-public/forms/dist/style.css' // optional: required for `RouterPlaygroundModal` component
```
This package utilizes [vite-plugin-top-level-await](https://github.com/Menci/vite-plugin-top-level-await) to transform code in order to use top-level await on older browsers. To load the WASM correctly, you must use `await` or `Promise.then` to wait the imported `asyncInit` before using any other imported values.
This package utilizes [vite-plugin-top-level-await](https://github.com/Menci/vite-plugin-top-level-await) to transform code in order to use top-level await on older browsers. To load the WASM correctly, you must use `await` or `Promise.then` to wait the imported `asyncInit` before using any other imported values.
For example:
Expand All @@ -77,4 +80,5 @@ You can also make use of Vue's experimental [Suspense](https://vuejs.org/guide/b
## Individual component documentation
- [`<ExpressionsEditor.vue />`](docs/expressions-editor.md)
- [`<ExpressionsEditor />`](docs/expressions-editor.md)
- [`<RouterPlaygroundModal />`](docs/router-playground-modal.md)
2 changes: 1 addition & 1 deletion packages/core/expressions/docs/expressions-editor.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# ExpressionsEditor.vue
# ExpressionsEditor

A Monaco-based editor with autocomplete and syntax highlighting support for the expressions language.

Expand Down
86 changes: 86 additions & 0 deletions packages/core/expressions/docs/router-playground-modal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# RouterPlaygroundModal

The `RouterPlaygroundModal` component is a modal that allows the user to edit a route expression and see the result of the expression evaluation.

- [Requirements](#requirements)
- [Usage](#usage)
- [Install](#install)
- [Props](#props)
- [Events](#events)
- [Usage example](#usage-example)
- [TypeScript definitions](#typescript-definitions)

## Requirements

[See requirements for the `@kong-ui-public/expressions` package.](../README.md#requirements)

## Usage

### Install

[See instructions for installing the `@kong-ui-public/expressions` package.](../README.md#install)

### Props

#### `isVisible`

- type: `boolean`
- required: `true`

Controls whether the modal is visible or not.

#### `localstorageKey`

- type: `String`
- required: `false`
- default: `kong-manager-router-playground-requests`

The key to use for storing the playground requests in the local storage.

#### `hideEditorActions`

- type: `boolean`
- required: `false`
- default: `false`

Controls whether the editor actions should be hidden or not.

#### `initialExpression`

- type: `string`
- required: `false`
- default: `''`

The initial expression to be displayed in the editor.

### Events

#### change

A `change` event is emitted when the expression has been updated.

#### commit

A `commit` event is emitted when the expression has been committed.

#### cancel

A `cancel` event is emitted when the modal's cancel button has been clicked.

#### notify

A `notify` event is emitted when a Toast is triggered. The event payload is an object with the following properties:
- `message`:
- type: `string`
- The message to display in the Toast.
- `type`:
- type: `'success' | 'error' | 'warning' | 'info'`
- The type of Toast to display.

### Usage example

Please refer to the [sandbox](../sandbox/App.vue).

## TypeScript definitions

TypeScript definitions are bundled with the package and can be directly imported into your host application.
8 changes: 7 additions & 1 deletion packages/core/expressions/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,11 @@
"test:unit:open": "cross-env FORCE_COLOR=1 vitest --ui"
},
"devDependencies": {
"@kong-ui-public/forms": "workspace:^",
"@kong/atc-router": "1.6.0-rc.1",
"@kong/design-tokens": "1.15.3",
"@kong/kongponents": "9.1.7",
"@types/uuid": "^9.0.8",
"monaco-editor": "0.21.3",
"vite-plugin-monaco-editor": "^1.1.0",
"vite-plugin-top-level-await": "^1.4.1",
Expand All @@ -66,10 +68,14 @@
"peerDependencies": {
"@kong/atc-router": "^1.6.0-rc.1",
"@kong/kongponents": "^9.1.7",
"@kong-ui-public/forms": "workspace:^",
"monaco-editor": "0.21.3",
"vue": "^3.4.31"
},
"dependencies": {
"@kong-ui-public/core": "workspace:^"
"@kong-ui-public/core": "workspace:^",
"@kong-ui-public/i18n": "workspace:^",
"@kong/icons": "^1.14.2",
"uuid": "^9.0.1"
}
}
36 changes: 26 additions & 10 deletions packages/core/expressions/sandbox/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,25 @@
@parse-result-update="onParseResultUpdate"
/>

<a
href="#"
style="color: #0030cc; font-weight: bold;"
@click.prevent="isVisible = true"
>Test with Router Playground</a>

<RouterPlaygroundModal
:hide-editor-actions="false"
:initial-expression="expression"
:is-visible="isVisible"
@cancel="isVisible = false"
@commit="handleCommit"
@notify="console.log"
>
<template #page-header>
<p>A playground where you can test out the Kong router Expressions.</p>
</template>
</RouterPlaygroundModal>

<div>
<p>ParseResult:</p>
<pre>{{ parseResult }}</pre>
Expand All @@ -40,7 +59,7 @@
<script setup lang="ts">
import { ref, watch } from 'vue'
import type { SchemaDefinition } from '../src'
import { ExpressionsEditor, HTTP_SCHEMA_DEFINITION, STREAM_SCHEMA_DEFINITION } from '../src'
import { ExpressionsEditor, HTTP_SCHEMA_DEFINITION, STREAM_SCHEMA_DEFINITION, RouterPlaygroundModal } from '../src'
type NamedSchemaDefinition = { name: string; definition: SchemaDefinition }
Expand Down Expand Up @@ -68,21 +87,18 @@ const btoa = (s: string) => window.btoa(s)
const expression = ref(expressionPresets[0])
const schemaDefinition = ref<NamedSchemaDefinition>(schemaPresets[0])
const parseResult = ref('')
const isVisible = ref(false)
const onParseResultUpdate = (result: any) => {
parseResult.value = JSON.stringify(result, null, 2)
}
const handleCommit = (exp: string) => {
expression.value = exp
isVisible.value = false
}
watch(schemaDefinition, (newSchemaDefinition) => {
schemaDefinition.value = newSchemaDefinition
})
</script>

<style lang="scss" scoped>
.presets {
display: flex;
flex-direction: row;
align-items: center;
gap: 0.5rem;
}
</style>
3 changes: 3 additions & 0 deletions packages/core/expressions/sandbox/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import '@kong/kongponents/dist/style.css'
import { createApp } from 'vue'
import App from './App.vue'
import Kongponents from '@kong/kongponents'

const app = createApp(App)

app.use(Kongponents)
app.mount('#app')
83 changes: 83 additions & 0 deletions packages/core/expressions/src/components/MonacoEditor.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
<template>
<div ref="editorRoot" />
</template>

<script setup lang="ts">
import { ref, watch, onMounted, onBeforeUnmount, defineEmits, defineProps } from 'vue'
import * as monaco from 'monaco-editor'
const props = defineProps({
modelValue: {
type: String,
required: true,
},
theme: {
type: String,
default: 'vs',
},
language: {
type: String,
default: 'html',
},
options: {
type: Object,
default: () => ({}),
},
})
const emits = defineEmits<{
(e: 'update:modelValue', value: string, event: monaco.editor.IModelContentChangedEvent): void
}>()
const editorRoot = ref<HTMLElement | null>(null)
let editor: monaco.editor.IStandaloneCodeEditor
watch(() => props.options, (newOptions) => {
if (editor) {
editor.updateOptions(newOptions)
}
}, { deep: true })
watch(() => props.modelValue, (newValue) => {
if (editor && newValue !== editor.getValue()) {
editor.setValue(newValue)
}
})
watch(() => props.language, (newVal) => {
if (editor) {
monaco.editor.setModelLanguage(editor.getModel()!, newVal)
}
})
watch(() => props.theme, (newVal) => {
if (editor) {
monaco.editor.setTheme(newVal)
}
})
onMounted(() => {
const options = Object.assign(
{
value: props.modelValue,
theme: props.theme,
language: props.language,
},
props.options,
)
editor = monaco.editor.create(editorRoot.value as HTMLElement, options)
editor.onDidChangeModelContent((event) => {
const value = editor!.getValue()
if (props.modelValue !== value) {
emits('update:modelValue', value, event)
}
})
})
onBeforeUnmount(() => {
editor?.dispose()
})
</script>
77 changes: 77 additions & 0 deletions packages/core/expressions/src/components/PageHeader.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<template>
<header
class="page-header"
>
<div class="row">
<component
:is="`h${size}`"
:class="{ 'title-no-margin': noMargin }"
>
<span
v-if="!hideTitle"
class="title"
>{{ title }}</span>
<slot name="title-logo" />
</component>
<nav class="operations">
<slot />
</nav>
</div>
<slot name="below-title" />
</header>
</template>

<script setup lang="ts">
import { defineProps } from 'vue'
defineProps<{
title: string,
size: number,
hideTitle?: boolean,
noMargin?: boolean,
}>()
</script>

<style lang="scss" scoped>
.page-header h1, h2, h3, h4, h5, h6 {
color: $kui-color-text;
font-size: $kui-font-size-70;
font-weight: $kui-font-weight-bold;
.title {
word-break: break-all;
}
}
.row {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
margin-left: -$kui-space-60;
margin-right: -$kui-space-60;
padding: $kui-space-0 $kui-space-60;
}
.col {
line-height: $kui-line-height-70;
}
h1, h2, h3, h4, h5, h6, nav {
margin-bottom: $kui-space-70;
margin-top: $kui-space-0;
}
.title-no-margin {
margin: $kui-space-0 !important;
}
.operations {
align-items: center;
display: inline-flex;
flex-grow: 0;
justify-content: flex-end;
margin-left: $kui-space-auto;
text-align: right;
white-space: nowrap;
}
</style>
Loading

0 comments on commit a4081f2

Please sign in to comment.