Extract QueryInput to its own component.

Add larger modal text editor.
Include JSON representation in JS filtering.
This commit is contained in:
Sage Vaillancourt 2022-08-22 22:59:28 -04:00
parent bd0967df0e
commit 368da3360a
4 changed files with 141 additions and 59 deletions

View File

@ -1,9 +1,10 @@
<script>
export let level = ''
export let title
export let onCancel = () => {}
export let onConfirm = () => {}
export let onClickOut = () => {}
export let confirm = 'Confirm'
export let level = ''
export let title
export let onCancel = () => {}
export let onConfirm = () => {}
export let onClickOut = () => {}
</script>
<div class="modal" on:click|self={() => onClickOut && onClickOut()}>
@ -15,8 +16,8 @@
<slot></slot>
</div>
<div class="modal-button-row">
<button on:click={onCancel}>Cancel</button>
<button class={level} on:click={onConfirm}>Confirm</button>
<button on:click={onCancel} style={onCancel ? '' : 'visibility: hidden;'}>Cancel</button>
<button class={level} on:click={onConfirm}>{confirm}</button>
</div>
</div>
</div>
@ -40,7 +41,7 @@
/* Modal Content/Box */
.modal-content {
background-color: #fefefe;
margin: 15% auto; /* 15% from the top and centered */
margin: /*15%*/ auto; /* 15% from the top and centered */
padding: 2em;
border: 1px solid #888;
border-radius: 10px;

62
src/lib/QueryInput.svelte Normal file
View File

@ -0,0 +1,62 @@
<script>
export let big
export let jsFilter
export let mutableObjects
export let queryCode
</script>
<div class="query-input-header">
<div on:click={() => jsFilter = !jsFilter}>
Use JavaScript to filter messages
<input type=checkbox bind:checked={jsFilter}>
</div>
</div>
{#if jsFilter}
<div class="query-input-display">
<div title="If enabled, mutations made by the below code will be displayed in the result data.">
Allow JavaScript to mutate objects
<input type=checkbox bind:checked={mutableObjects}>
</div>
<textarea class={"query-input" + (big ? ' big' : '')} bind:value={queryCode}></textarea>
</div>
{/if}
<style>
.query-input-header {
display: flex;
flex-direction: row;
justify-content: space-between;
}
.query-input-display {
margin-top: 1em;
display: flex;
flex-direction: column;
}
.query-input {
min-height: 5vw;
resize: vertical;
}
.query-input.big {
height: 60vh;
width: 60vw;
}
textarea {
margin-top: 1em;
margin-bottom: 0;
overflow: hidden;
z-index: 1;
margin-left: 0;
background-color: #222222;
color: #d0dde9;
border: none;
font-size: 14px;
font-family: var(--font-mono);
padding: 1em;
overflow-x: auto;
min-width: 20vw;
}
</style>

View File

@ -39,6 +39,7 @@ export const testMode = true
export const state = writable({
items: [],
itemCount: undefined,
matchCount: undefined,
error: 'Connecting to WebSocket...',
})
@ -50,7 +51,7 @@ const updateClearError = updater => state.update(s => {
let ws;
let itemLimit = Infinity
let disconnected = false
let filterFunc = (_message, _value) => true
let filterFunc = (_message, _value, _json) => true
let runTestQuery = true
const testTimeout = 200
@ -60,9 +61,13 @@ const testQuery = (mode) => {
if (mode === queryMode.REAL_TIME) {
const addItem = () => {
const item = getRandomFromArray(mockItems)
if (filterFunc(item, item.value)) {
if (filterFunc(item, item.value, JSON.stringify(item))) {
item.timestamp = new Date().getTime()
state.update(s => ({ ...s, items: [item, ...s.items].slice(0, itemLimit), itemCount: (s.itemCount || 0) + 1 }))
state.update(s => ({
...s,
items: [item, ...s.items].slice(0, itemLimit),
itemCount: (s.itemCount || 0) + 1
}))
}
if (runTestQuery) {
setTimeout(addItem, testTimeout)
@ -70,7 +75,11 @@ const testQuery = (mode) => {
}
setTimeout(addItem, testTimeout)
} else {
state.update(s => ({ ...s, items: mockItems.filter(item => filterFunc(item, item.value)), itemCount: (s.itemCount || 0) + 1 }))
state.update(s => ({
...s,
items: mockItems.filter(item => filterFunc(item, item.value, JSON.stringify(item))),
itemCount: (s.itemCount || 0) + 1
}))
}
} catch (e) {
console.log('Caught an error:', e.toString())
@ -89,7 +98,7 @@ export const killQuery = async ({ }) => {
// noinspection JSUnusedGlobalSymbols
export const query = async ({ cluster, topic, mode, jsFilter, queryCode, maxItems, mutableObjects }) => {
if (jsFilter) {
filterFunc = new Function('message', 'value', queryCode)
filterFunc = new Function('message', 'value', 'json', queryCode)
} else {
filterFunc = () => true
}
@ -153,7 +162,7 @@ export const connect = () => {
}))
break;
case 'message':
if (filterFunc(data.message)) {
if (filterFunc(data.message, data.message.value, message.data)) {
updateClearError(s => ({
...s,
items: console.log('new item', data.message) || [data.message, ...s.items].slice(0, itemLimit),

View File

@ -1,9 +1,13 @@
<script>
import { onMount } from 'svelte'
import { JsonView } from '@zerodevx/svelte-json-view'
import QueryInput from '$lib/QueryInput.svelte'
import { state, connect, query, killQuery } from '$lib/state'
import { queryMode } from "../lib/constants.js"
import { apiFetch } from "../utils.js"
import Modal from "../lib/Modal.svelte";
let showQueryModal = false
let expandAll = true
let showMetadata = true
@ -16,8 +20,10 @@
maxItems: 20,
mutableObjects: false,
queryCode:
`// The message object contains all metadata
`// \`message\` contains all metadata
// \`value\` is shorthand for message.value
// it contains the actual produced data
// \`json\` is a JSON representation of \`message\`
return message.value.eventType === "Television";`
}
@ -123,14 +129,24 @@ return message.value.eventType === "Television";`
<br/>
<div class="settings-option">
<div class="query-input-header"><div on:click={() => querySettings.jsFilter = !querySettings.jsFilter}>Use JavaScript to filter messages</div> <input type=checkbox bind:checked={querySettings.jsFilter}></div>
<QueryInput
bind:jsFilter={querySettings.jsFilter}
bind:mutableObjects={querySettings.mutableObjects}
bind:queryCode={querySettings.queryCode} />
{#if querySettings.jsFilter}
<div class="query-input-display">
<div title="If enabled, mutations made by the below code will be displayed in the result data.">
Allow JavaScript to mutate objects <input type=checkbox bind:checked={querySettings.mutableObjects}>
</div>
<textarea class="query-input" bind:value={querySettings.queryCode}></textarea>
</div>
<button class="query-input-button" on:click={() => showQueryModal = true}>Open Large Editor</button>
{/if}
{#if showQueryModal}
<Modal onClickOut={() => showQueryModal = false}
onCancel={null}
onConfirm={() => showQueryModal = false}
confirm="Close"
title={'JS Filter'}>
<QueryInput big
bind:jsFilter={querySettings.jsFilter}
bind:mutableObjects={querySettings.mutableObjects}
bind:queryCode={querySettings.queryCode} />
</Modal>
{/if}
</div>
@ -167,21 +183,25 @@ return message.value.eventType === "Television";`
<div class="data-view-container">
<div class="data-view-bar">
<div>
<div class="border-between">
<button class={expandAll ? 'selected' : ''} on:click={() => expandAll = !expandAll}>
Expand All Objects
</button>
<button class={showMetadata ? 'selected' : ''} on:click={() => showMetadata = !showMetadata}>
Show Metadata
</button>
<button on:click={fontUp}>+</button>
<button on:click={fontDown}>-</button>
</div>
{#if $state.itemCount >= 0}
<div class="data-view-results">
{$state.itemCount} Messages
<div style="display: flex; align-items: center">
{#if $state.itemCount >= 0}
<div class="data-view-results">
{$state.itemCount} Messages
</div>
{/if}
<div class="border-between">
<button on:click={fontDown} style="border-color: #aaa;">-</button>
<button on:click={fontUp}>+</button>
</div>
{/if}
</div>
</div>
{#if $state.items?.length > 0}
<div class="data-view" style={`font-size: ${fontSizes[dataViewFontSize]}%`}>
@ -212,10 +232,21 @@ return message.value.eventType === "Television";`
justify-content: space-between;
align-items: center;
}
.data-view-bar button {
.border-between {
border-width: 1px;
border-style: none;
display: flex;
}
.border-between button:last-child {
border-right: none;
}
.data-view-bar button {
border-color: white;
border-style: none;
border-right: solid;
border-width: 1px;
border-radius: 0;
margin-right: 1px;
margin: 0;
}
button.selected {
background-color: #4732a5;
@ -243,20 +274,14 @@ return message.value.eventType === "Television";`
width: 100%;
}
textarea {
overflow: hidden;
z-index: 1;
margin-left: 0;
background-color: #222222;
.query-input-button {
border-style: none;
border-radius: 0;
background-color: #313131;
color: #d0dde9;
border: none;
font-size: 14px;
font-family: var(--font-mono);
padding: 1em;
overflow-x: auto;
min-width: 20vw;
}
.query-input-button:hover {
background-color: #575757;
}
.query-settings {
@ -269,21 +294,6 @@ return message.value.eventType === "Television";`
height: 83vh;
}
.query-input-header {
display: flex;
flex-direction: row;
justify-content: space-between;
}
.query-input-display {
margin-top: 1em;
display: flex;
flex-direction: column;
}
.query-input {
min-height: 4vw;
resize: vertical;
}
.query-type {
display: flex;
flex-direction: row;