Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(expressions): add router-playground-modal [KM-299] #1497

Merged
merged 1 commit into from
Aug 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 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,12 @@ 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
sumimakito marked this conversation as resolved.
Show resolved Hide resolved

app.component('VueFormGenerator', VueFormGenerator) // 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 +82,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: `''`

2eha0 marked this conversation as resolved.
Show resolved Hide resolved
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"
2eha0 marked this conversation as resolved.
Show resolved Hide resolved
}
}
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
Loading