mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-21 00:02:23 +00:00
feat(frontend): templatable editor with autocompletion
This commit is contained in:
@@ -39,6 +39,10 @@
|
||||
.monaco-editor textarea:focus {
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.monaco-editor span.mtk20 {
|
||||
color: black !important;
|
||||
}
|
||||
}
|
||||
|
||||
@layer components {
|
||||
|
||||
@@ -14,6 +14,9 @@
|
||||
import type VariableEditor from './VariableEditor.svelte'
|
||||
import type ItemPicker from './ItemPicker.svelte'
|
||||
import type { InputTransform } from '$lib/gen'
|
||||
import TemplateEditor from './TemplateEditor.svelte'
|
||||
import Tooltip from './Tooltip.svelte'
|
||||
import { escape } from 'svelte/internal'
|
||||
|
||||
export let schema: Schema
|
||||
export let arg: InputTransform | any
|
||||
@@ -99,6 +102,10 @@
|
||||
}
|
||||
|
||||
const { focusProp, propPickerConfig } = getContext<PropPickerWrapperContext>('PropPickerWrapper')
|
||||
|
||||
$: isStaticTemplate(inputCat) && propertyType == 'static' && setPropertyType(arg.value)
|
||||
const openBracket = '${'
|
||||
const closeBracket = '}'
|
||||
</script>
|
||||
|
||||
{#if arg != undefined}
|
||||
@@ -174,7 +181,11 @@
|
||||
>
|
||||
{#if isStaticTemplate(inputCat)}
|
||||
<ToggleButton light position="left" value="static" size="xs">
|
||||
{'${} '}Templatable</ToggleButton
|
||||
{'${} '}Templatable <Tooltip
|
||||
>Write javascript expressions between "{openBracket}" and "{closeBracket}". You may
|
||||
refer to contextual objects like 'flow_input', or 'result' or functions like
|
||||
'resource' and 'variable'
|
||||
</Tooltip></ToggleButton
|
||||
>
|
||||
{:else}
|
||||
<ToggleButton light position="left" value="static" size="xs">Static</ToggleButton>
|
||||
@@ -207,7 +218,11 @@
|
||||
Connect input →
|
||||
</span>
|
||||
{/if}
|
||||
{#if propertyType === undefined || propertyType == 'static'}
|
||||
{#if isStaticTemplate(inputCat) && propertyType == 'static'}
|
||||
<div class="py-1">
|
||||
<TemplateEditor {extraLib} on:focus={onFocus} bind:code={arg.value} />
|
||||
</div>
|
||||
{:else if propertyType === undefined || propertyType == 'static'}
|
||||
<ArgInput
|
||||
noMargin
|
||||
compact
|
||||
@@ -229,11 +244,6 @@
|
||||
properties={schema.properties[argName].properties}
|
||||
displayHeader={false}
|
||||
bind:inputCat
|
||||
on:input={(e) => {
|
||||
if (isStaticTemplate(inputCat)) {
|
||||
setPropertyType(e.detail.rawValue)
|
||||
}
|
||||
}}
|
||||
{variableEditor}
|
||||
{itemPicker}
|
||||
bind:pickForField
|
||||
|
||||
@@ -299,7 +299,7 @@
|
||||
} else if (kind === 'group') {
|
||||
meta.owner = 'all'
|
||||
} else {
|
||||
meta.owner = $userStore?.username ?? ''
|
||||
meta.owner = $userStore?.username?.split('@')[0] ?? ''
|
||||
}
|
||||
}
|
||||
}}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts" context="module">
|
||||
import * as monaco from 'monaco-editor'
|
||||
import libStdContent from '$lib/es5.d.ts.txt?raw'
|
||||
|
||||
monaco.languages.typescript.javascriptDefaults.setCompilerOptions({
|
||||
target: monaco.languages.typescript.ScriptTarget.Latest,
|
||||
@@ -156,15 +157,19 @@
|
||||
dispatch('blur')
|
||||
})
|
||||
|
||||
if (lang == 'javascript' && extraLib != '') {
|
||||
monaco.languages.typescript.javascriptDefaults.setExtraLibs([
|
||||
{
|
||||
content: extraLib,
|
||||
filePath: 'windmill.d.ts'
|
||||
}
|
||||
])
|
||||
} else {
|
||||
monaco.languages.typescript.javascriptDefaults.setExtraLibs([])
|
||||
if (lang == 'javascript') {
|
||||
const stdLib = { content: libStdContent, filePath: 'es5.d.ts' }
|
||||
if (extraLib != '') {
|
||||
monaco.languages.typescript.javascriptDefaults.setExtraLibs([
|
||||
{
|
||||
content: extraLib,
|
||||
filePath: 'windmill.d.ts'
|
||||
},
|
||||
stdLib
|
||||
])
|
||||
} else {
|
||||
monaco.languages.typescript.javascriptDefaults.setExtraLibs([stdLib])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,577 @@
|
||||
<script lang="ts" context="module">
|
||||
import * as monaco from 'monaco-editor'
|
||||
import libStdContent from '$lib/es5.d.ts.txt?raw'
|
||||
|
||||
monaco.languages.typescript.javascriptDefaults.setCompilerOptions({
|
||||
target: monaco.languages.typescript.ScriptTarget.Latest,
|
||||
allowNonTsExtensions: true,
|
||||
noLib: true
|
||||
})
|
||||
|
||||
monaco.languages.register({ id: 'template' })
|
||||
|
||||
export const conf = {
|
||||
wordPattern:
|
||||
/(-?\d*\.\d\w*)|([^\`\~\!\@\#\%\^\&\*\(\)\-\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\?\s]+)/g,
|
||||
|
||||
comments: {
|
||||
lineComment: '//',
|
||||
blockComment: ['/*', '*/'] as [string, string]
|
||||
},
|
||||
|
||||
brackets: [
|
||||
['{', '}'],
|
||||
['[', ']'],
|
||||
['(', ')']
|
||||
] as [string, string][],
|
||||
|
||||
onEnterRules: [],
|
||||
autoClosingPairs: [
|
||||
{ open: '{', close: '}' },
|
||||
{ open: '[', close: ']' },
|
||||
{ open: '(', close: ')' },
|
||||
{ open: '"', close: '"', notIn: ['string'] },
|
||||
{ open: "'", close: "'", notIn: ['string', 'comment'] },
|
||||
{ open: '`', close: '`', notIn: ['string', 'comment'] }
|
||||
],
|
||||
|
||||
folding: {
|
||||
markers: {
|
||||
start: new RegExp('^\\s*//\\s*#?region\\b'),
|
||||
end: new RegExp('^\\s*//\\s*#?endregion\\b')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const language = {
|
||||
// Set defaultToken to invalid to see what you do not tokenize yet
|
||||
defaultToken: 'invalid',
|
||||
tokenPostfix: '.ts',
|
||||
|
||||
keywords: [
|
||||
// Should match the keys of textToKeywordObj in
|
||||
// https://github.com/microsoft/TypeScript/blob/master/src/compiler/scanner.ts
|
||||
'abstract',
|
||||
'any',
|
||||
'as',
|
||||
'asserts',
|
||||
'bigint',
|
||||
'boolean',
|
||||
'break',
|
||||
'case',
|
||||
'catch',
|
||||
'class',
|
||||
'continue',
|
||||
'const',
|
||||
'constructor',
|
||||
'debugger',
|
||||
'declare',
|
||||
'default',
|
||||
'delete',
|
||||
'do',
|
||||
'else',
|
||||
'enum',
|
||||
'export',
|
||||
'extends',
|
||||
'false',
|
||||
'finally',
|
||||
'for',
|
||||
'from',
|
||||
'function',
|
||||
'get',
|
||||
'if',
|
||||
'implements',
|
||||
'import',
|
||||
'in',
|
||||
'infer',
|
||||
'instanceof',
|
||||
'interface',
|
||||
'is',
|
||||
'keyof',
|
||||
'let',
|
||||
'module',
|
||||
'namespace',
|
||||
'never',
|
||||
'new',
|
||||
'null',
|
||||
'number',
|
||||
'object',
|
||||
'out',
|
||||
'package',
|
||||
'private',
|
||||
'protected',
|
||||
'public',
|
||||
'override',
|
||||
'readonly',
|
||||
'require',
|
||||
'global',
|
||||
'return',
|
||||
'set',
|
||||
'static',
|
||||
'string',
|
||||
'super',
|
||||
'switch',
|
||||
'symbol',
|
||||
'this',
|
||||
'throw',
|
||||
'true',
|
||||
'try',
|
||||
'type',
|
||||
'typeof',
|
||||
'undefined',
|
||||
'unique',
|
||||
'unknown',
|
||||
'var',
|
||||
'void',
|
||||
'while',
|
||||
'with',
|
||||
'yield',
|
||||
'async',
|
||||
'await',
|
||||
'of'
|
||||
],
|
||||
|
||||
operators: [
|
||||
'<=',
|
||||
'>=',
|
||||
'==',
|
||||
'!=',
|
||||
'===',
|
||||
'!==',
|
||||
'=>',
|
||||
'+',
|
||||
'-',
|
||||
'**',
|
||||
'*',
|
||||
'/',
|
||||
'%',
|
||||
'++',
|
||||
'--',
|
||||
'<<',
|
||||
'</',
|
||||
'>>',
|
||||
'>>>',
|
||||
'&',
|
||||
'|',
|
||||
'^',
|
||||
'!',
|
||||
'~',
|
||||
'&&',
|
||||
'||',
|
||||
'??',
|
||||
'?',
|
||||
':',
|
||||
'=',
|
||||
'+=',
|
||||
'-=',
|
||||
'*=',
|
||||
'**=',
|
||||
'/=',
|
||||
'%=',
|
||||
'<<=',
|
||||
'>>=',
|
||||
'>>>=',
|
||||
'&=',
|
||||
'|=',
|
||||
'^=',
|
||||
'@'
|
||||
],
|
||||
|
||||
// we include these common regular expressions
|
||||
symbols: /[=><!~?:&|+\-*\/\^%]+/,
|
||||
escapes: /\\(?:[abfnrtv\\"']|x[0-9A-Fa-f]{1,4}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})/,
|
||||
digits: /\d+(_+\d+)*/,
|
||||
octaldigits: /[0-7]+(_+[0-7]+)*/,
|
||||
binarydigits: /[0-1]+(_+[0-1]+)*/,
|
||||
hexdigits: /[[0-9a-fA-F]+(_+[0-9a-fA-F]+)*/,
|
||||
|
||||
regexpctl: /[(){}\[\]\$\^|\-*+?\.]/,
|
||||
regexpesc: /\\(?:[bBdDfnrstvwWn0\\\/]|@regexpctl|c[A-Z]|x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4})/,
|
||||
|
||||
// The main tokenizer for our languages
|
||||
tokenizer: {
|
||||
root: [
|
||||
[/\$\{/, { token: 'delimiter.bracket', next: '@bracketCounting' }],
|
||||
[/[^\\`$]+/, 'string'],
|
||||
[/@escapes/, 'string.escape'],
|
||||
[/\\./, 'string.escape.invalid']
|
||||
],
|
||||
|
||||
common: [
|
||||
// identifiers and keywords
|
||||
[
|
||||
/[a-z_$][\w$]*/,
|
||||
{
|
||||
cases: {
|
||||
'@keywords': 'keyword',
|
||||
'@default': 'identifier'
|
||||
}
|
||||
}
|
||||
],
|
||||
[/[A-Z][\w\$]*/, 'type.identifier'], // to show class names nicely
|
||||
// [/[A-Z][\w\$]*/, 'identifier'],
|
||||
|
||||
// whitespace
|
||||
{ include: '@whitespace' },
|
||||
|
||||
// regular expression: ensure it is terminated before beginning (otherwise it is an opeator)
|
||||
[
|
||||
/\/(?=([^\\\/]|\\.)+\/([dgimsuy]*)(\s*)(\.|;|,|\)|\]|\}|$))/,
|
||||
{ token: 'regexp', bracket: '@open', next: '@regexp' }
|
||||
],
|
||||
|
||||
// delimiters and operators
|
||||
[/[()\[\]]/, '@brackets'],
|
||||
[/[<>](?!@symbols)/, '@brackets'],
|
||||
[/!(?=([^=]|$))/, 'delimiter'],
|
||||
[
|
||||
/@symbols/,
|
||||
{
|
||||
cases: {
|
||||
'@operators': 'delimiter',
|
||||
'@default': ''
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
// numbers
|
||||
[/(@digits)[eE]([\-+]?(@digits))?/, 'number.float'],
|
||||
[/(@digits)\.(@digits)([eE][\-+]?(@digits))?/, 'number.float'],
|
||||
[/0[xX](@hexdigits)n?/, 'number.hex'],
|
||||
[/0[oO]?(@octaldigits)n?/, 'number.octal'],
|
||||
[/0[bB](@binarydigits)n?/, 'number.binary'],
|
||||
[/(@digits)n?/, 'number'],
|
||||
|
||||
// delimiter: after number because of .\d floats
|
||||
[/[;,.]/, 'delimiter'],
|
||||
|
||||
// strings
|
||||
[/"([^"\\]|\\.)*$/, 'string.invalid'], // non-teminated string
|
||||
[/'([^'\\]|\\.)*$/, 'string.invalid'], // non-teminated string
|
||||
[/"/, 'string', '@string_double'],
|
||||
[/'/, 'string', '@string_single'],
|
||||
[/`/, 'string', '@string_backtick']
|
||||
],
|
||||
|
||||
whitespace: [
|
||||
[/[ \t\r\n]+/, ''],
|
||||
[/\/\*\*(?!\/)/, 'comment.doc', '@jsdoc'],
|
||||
[/\/\*/, 'comment', '@comment'],
|
||||
[/\/\/.*$/, 'comment']
|
||||
],
|
||||
|
||||
comment: [
|
||||
[/[^\/*]+/, 'comment'],
|
||||
[/\*\//, 'comment', '@pop'],
|
||||
[/[\/*]/, 'comment']
|
||||
],
|
||||
|
||||
jsdoc: [
|
||||
[/[^\/*]+/, 'comment.doc'],
|
||||
[/\*\//, 'comment.doc', '@pop'],
|
||||
[/[\/*]/, 'comment.doc']
|
||||
],
|
||||
|
||||
// We match regular expression quite precisely
|
||||
regexp: [
|
||||
[
|
||||
/(\{)(\d+(?:,\d*)?)(\})/,
|
||||
['regexp.escape.control', 'regexp.escape.control', 'regexp.escape.control']
|
||||
],
|
||||
[
|
||||
/(\[)(\^?)(?=(?:[^\]\\\/]|\\.)+)/,
|
||||
['regexp.escape.control', { token: 'regexp.escape.control', next: '@regexrange' }]
|
||||
],
|
||||
[/(\()(\?:|\?=|\?!)/, ['regexp.escape.control', 'regexp.escape.control']],
|
||||
[/[()]/, 'regexp.escape.control'],
|
||||
[/@regexpctl/, 'regexp.escape.control'],
|
||||
[/[^\\\/]/, 'regexp'],
|
||||
[/@regexpesc/, 'regexp.escape'],
|
||||
[/\\\./, 'regexp.invalid'],
|
||||
[
|
||||
/(\/)([dgimsuy]*)/,
|
||||
[{ token: 'regexp', bracket: '@close', next: '@pop' }, 'keyword.other']
|
||||
]
|
||||
],
|
||||
|
||||
regexrange: [
|
||||
[/-/, 'regexp.escape.control'],
|
||||
[/\^/, 'regexp.invalid'],
|
||||
[/@regexpesc/, 'regexp.escape'],
|
||||
[/[^\]]/, 'regexp'],
|
||||
[
|
||||
/\]/,
|
||||
{
|
||||
token: 'regexp.escape.control',
|
||||
next: '@pop',
|
||||
bracket: '@close'
|
||||
}
|
||||
]
|
||||
],
|
||||
|
||||
string_double: [
|
||||
[/[^\\"]+/, 'string'],
|
||||
[/@escapes/, 'string.escape'],
|
||||
[/\\./, 'string.escape.invalid'],
|
||||
[/"/, 'string', '@pop']
|
||||
],
|
||||
|
||||
string_single: [
|
||||
[/[^\\']+/, 'string'],
|
||||
[/@escapes/, 'string.escape'],
|
||||
[/\\./, 'string.escape.invalid'],
|
||||
[/'/, 'string', '@pop']
|
||||
],
|
||||
|
||||
string_backtick: [
|
||||
[/\$\{/, { token: 'delimiter.bracket', next: '@bracketCounting' }],
|
||||
[/[^\\`$]+/, 'string'],
|
||||
[/@escapes/, 'string.escape'],
|
||||
[/\\./, 'string.escape.invalid'],
|
||||
[/`/, 'string', '@pop']
|
||||
],
|
||||
|
||||
bracketCounting: [
|
||||
[/\{/, 'delimiter.bracket', '@bracketCounting'],
|
||||
[/\}/, 'delimiter.bracket', '@pop'],
|
||||
{ include: 'common' }
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
// Register a tokens provider for the language
|
||||
monaco.languages.registerTokensProviderFactory('template', {
|
||||
create: () => language as monaco.languages.IMonarchLanguage
|
||||
})
|
||||
|
||||
monaco.languages.setLanguageConfiguration('template', conf)
|
||||
|
||||
// monaco.languages.typescript.getTypeScriptWorker()
|
||||
|
||||
// Register a completion item provider for the new language
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { browser, dev } from '$app/env'
|
||||
|
||||
import tsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker'
|
||||
|
||||
import { buildWorkerDefinition } from 'monaco-editor-workers'
|
||||
|
||||
import { createEventDispatcher, onDestroy, onMount } from 'svelte'
|
||||
import {
|
||||
convertKind,
|
||||
createDocumentationString,
|
||||
createHash,
|
||||
displayPartsToString,
|
||||
editorConfig,
|
||||
updateOptions
|
||||
} from '$lib/editorUtils'
|
||||
|
||||
let divEl: HTMLDivElement | null = null
|
||||
let editor: monaco.editor.IStandaloneCodeEditor
|
||||
let model: monaco.editor.ITextModel
|
||||
|
||||
export let code: string = ''
|
||||
export let hash: string = createHash()
|
||||
export let automaticLayout = true
|
||||
export let extraLib: string = ''
|
||||
export let autoHeight = true
|
||||
export let fixedOverflowWidgets = true
|
||||
|
||||
const lang = 'template'
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
const uri = `file:///${hash}.ts`
|
||||
|
||||
if (browser) {
|
||||
if (dev) {
|
||||
buildWorkerDefinition(
|
||||
'../../../node_modules/monaco-editor-workers/dist/workers',
|
||||
import.meta.url,
|
||||
false
|
||||
)
|
||||
} else {
|
||||
// @ts-ignore
|
||||
self.MonacoEnvironment = {
|
||||
getWorker: function (_moduleId: any, label: string) {
|
||||
return new tsWorker()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function getCode(): string {
|
||||
return editor?.getValue() ?? ''
|
||||
}
|
||||
|
||||
let cip
|
||||
let extraModel
|
||||
|
||||
let width = 0
|
||||
async function loadMonaco() {
|
||||
model = monaco.editor.createModel(code, lang, monaco.Uri.parse(uri))
|
||||
|
||||
model.updateOptions(updateOptions)
|
||||
|
||||
editor = monaco.editor.create(divEl as HTMLDivElement, {
|
||||
...editorConfig(model, code, lang, automaticLayout, fixedOverflowWidgets),
|
||||
lineNumbers: 'off',
|
||||
fontSize: 16,
|
||||
suggestOnTriggerCharacters: true
|
||||
})
|
||||
|
||||
const stdLib = { content: libStdContent, filePath: 'es5.d.ts' }
|
||||
if (extraLib != '') {
|
||||
monaco.languages.typescript.javascriptDefaults.setExtraLibs([
|
||||
{
|
||||
content: extraLib,
|
||||
filePath: 'windmill.d.ts'
|
||||
},
|
||||
stdLib
|
||||
])
|
||||
} else {
|
||||
monaco.languages.typescript.javascriptDefaults.setExtraLibs([stdLib])
|
||||
}
|
||||
|
||||
extraModel = monaco.editor.createModel('`' + model.getValue() + '`', 'javascript')
|
||||
const worker = await monaco.languages.typescript.getJavaScriptWorker()
|
||||
const client = await worker(extraModel.uri)
|
||||
|
||||
cip = monaco.languages.registerCompletionItemProvider('template', {
|
||||
triggerCharacters: ['.'],
|
||||
|
||||
provideCompletionItems: async (model, position) => {
|
||||
extraModel.setValue('`' + model.getValue() + '`')
|
||||
|
||||
const offset = model.getOffsetAt(position) + 1
|
||||
const info = await client.getCompletionsAtPosition(extraModel.uri.toString(), offset)
|
||||
if (!info) {
|
||||
return { suggestions: [] }
|
||||
}
|
||||
const wordInfo = model.getWordUntilPosition(position)
|
||||
const wordRange = new monaco.Range(
|
||||
position.lineNumber,
|
||||
wordInfo.startColumn,
|
||||
position.lineNumber,
|
||||
wordInfo.endColumn
|
||||
)
|
||||
|
||||
const suggestions = info.entries
|
||||
.filter((x) => x.kind != 'keyword' && x.kind != 'var')
|
||||
.map((entry) => {
|
||||
let range = wordRange
|
||||
if (entry.replacementSpan) {
|
||||
const p1 = model.getPositionAt(entry.replacementSpan.start)
|
||||
const p2 = model.getPositionAt(
|
||||
entry.replacementSpan.start + entry.replacementSpan.length
|
||||
)
|
||||
range = new monaco.Range(p1.lineNumber, p1.column, p2.lineNumber, p2.column)
|
||||
}
|
||||
|
||||
const tags: monaco.languages.CompletionItemTag[] = []
|
||||
if (entry.kindModifiers?.indexOf('deprecated') !== -1) {
|
||||
tags.push(monaco.languages.CompletionItemTag.Deprecated)
|
||||
}
|
||||
return {
|
||||
uri: model.uri,
|
||||
position: position,
|
||||
offset: offset,
|
||||
range: range,
|
||||
label: entry.name,
|
||||
insertText: entry.name,
|
||||
sortText: entry.sortText,
|
||||
kind: convertKind(entry.kind),
|
||||
tags
|
||||
}
|
||||
})
|
||||
return { suggestions }
|
||||
},
|
||||
resolveCompletionItem: async (item: monaco.languages.CompletionItem, token: any) => {
|
||||
extraModel.setValue('`' + model.getValue() + '`')
|
||||
|
||||
const myItem = <any>item
|
||||
const position = myItem.position
|
||||
const offset = myItem.offset
|
||||
|
||||
const details = await client.getCompletionEntryDetails(
|
||||
extraModel.uri.toString(),
|
||||
offset,
|
||||
myItem.label
|
||||
)
|
||||
if (!details) {
|
||||
return myItem
|
||||
}
|
||||
return <any>{
|
||||
uri: model.uri,
|
||||
position: position,
|
||||
label: details.name,
|
||||
kind: convertKind(details.kind),
|
||||
detail: displayPartsToString(details.displayParts),
|
||||
documentation: {
|
||||
value: createDocumentationString(details)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
let timeoutModel: NodeJS.Timeout | undefined = undefined
|
||||
editor.onDidChangeModelContent((event) => {
|
||||
timeoutModel && clearTimeout(timeoutModel)
|
||||
timeoutModel = setTimeout(() => {
|
||||
code = getCode()
|
||||
dispatch('change', { code })
|
||||
}, 200)
|
||||
})
|
||||
|
||||
if (autoHeight) {
|
||||
let ignoreEvent = false
|
||||
const updateHeight = () => {
|
||||
const contentHeight = Math.min(1000, editor.getContentHeight())
|
||||
if (divEl) {
|
||||
divEl.style.height = `${contentHeight}px`
|
||||
}
|
||||
try {
|
||||
ignoreEvent = true
|
||||
editor.layout({ width, height: contentHeight })
|
||||
} finally {
|
||||
ignoreEvent = false
|
||||
}
|
||||
}
|
||||
editor.onDidContentSizeChange(updateHeight)
|
||||
updateHeight()
|
||||
}
|
||||
|
||||
editor.onDidFocusEditorText(() => {
|
||||
dispatch('focus')
|
||||
})
|
||||
|
||||
editor.onDidBlurEditorText(() => {
|
||||
code = getCode()
|
||||
dispatch('blur')
|
||||
})
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
if (browser) {
|
||||
loadMonaco()
|
||||
}
|
||||
})
|
||||
|
||||
onDestroy(() => {
|
||||
try {
|
||||
model && model.dispose()
|
||||
editor && editor.dispose()
|
||||
cip && cip.dispose()
|
||||
extraModel && extraModel.dispose()
|
||||
} catch (err) {}
|
||||
})
|
||||
</script>
|
||||
|
||||
<div bind:this={divEl} class="{$$props.class} editor" bind:clientWidth={width} />
|
||||
|
||||
<style>
|
||||
.editor {
|
||||
@apply rounded-lg mx-0.5;
|
||||
}
|
||||
</style>
|
||||
@@ -26,9 +26,9 @@
|
||||
let runnableComponent: RunnableComponent
|
||||
</script>
|
||||
|
||||
<InputValue input={configuration.label} bind:value={labelValue} />
|
||||
<InputValue input={configuration.color} bind:value={color} />
|
||||
<InputValue input={configuration.size} bind:value={size} />
|
||||
<InputValue {id} input={configuration.label} bind:value={labelValue} />
|
||||
<InputValue {id} input={configuration.color} bind:value={color} />
|
||||
<InputValue {id} input={configuration.size} bind:value={size} />
|
||||
|
||||
<RunnableWrapper
|
||||
bind:runnableComponent
|
||||
|
||||
@@ -64,8 +64,8 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<InputValue input={configuration.theme} bind:value={theme} />
|
||||
<InputValue input={configuration.labels} bind:value={labels} />
|
||||
<InputValue {id} input={configuration.theme} bind:value={theme} />
|
||||
<InputValue {id} input={configuration.labels} bind:value={labels} />
|
||||
|
||||
<RunnableWrapper bind:componentInput {id} bind:result>
|
||||
{#if result}
|
||||
|
||||
@@ -63,8 +63,8 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<InputValue input={configuration.theme} bind:value={theme} />
|
||||
<InputValue input={configuration.labels} bind:value={labels} />
|
||||
<InputValue {id} input={configuration.theme} bind:value={theme} />
|
||||
<InputValue {id} input={configuration.labels} bind:value={labels} />
|
||||
|
||||
<RunnableWrapper bind:componentInput {id} bind:result>
|
||||
{#if result}
|
||||
|
||||
@@ -27,9 +27,9 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<InputValue input={configuration.label} bind:value={labelValue} />
|
||||
<InputValue input={configuration.minDate} bind:value={minValue} />
|
||||
<InputValue input={configuration.maxDate} bind:value={maxValue} />
|
||||
<InputValue {id} input={configuration.label} bind:value={labelValue} />
|
||||
<InputValue {id} input={configuration.minDate} bind:value={minValue} />
|
||||
<InputValue {id} input={configuration.maxDate} bind:value={maxValue} />
|
||||
|
||||
<AlignWrapper {verticalAlignment}>
|
||||
<input
|
||||
|
||||
@@ -25,9 +25,9 @@
|
||||
let runnableComponent: RunnableComponent
|
||||
</script>
|
||||
|
||||
<InputValue input={configuration.label} bind:value={labelValue} />
|
||||
<InputValue input={configuration.color} bind:value={color} />
|
||||
<InputValue input={configuration.size} bind:value={size} />
|
||||
<InputValue {id} input={configuration.label} bind:value={labelValue} />
|
||||
<InputValue {id} input={configuration.color} bind:value={color} />
|
||||
<InputValue {id} input={configuration.size} bind:value={size} />
|
||||
|
||||
<RunnableWrapper
|
||||
bind:runnableComponent
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { isCodeInjection } from '$lib/components/flows/utils'
|
||||
import { getContext } from 'svelte'
|
||||
import type { AppInput } from '../../inputType'
|
||||
import type { AppEditorContext } from '../../types'
|
||||
@@ -8,25 +9,79 @@
|
||||
|
||||
export let input: AppInput
|
||||
export let value: T
|
||||
export let id: string | undefined = undefined
|
||||
|
||||
const { worldStore } = getContext<AppEditorContext>('AppEditorContext')
|
||||
$: state = $worldStore?.state
|
||||
|
||||
$: input && $worldStore && handleConnection()
|
||||
|
||||
$: input && $state && input.type == 'template' && setValue()
|
||||
|
||||
function handleConnection() {
|
||||
if (input.type === 'connected') {
|
||||
$worldStore?.connect<any>(input, onValueChange)
|
||||
} else if (input.type === 'static') {
|
||||
$worldStore?.connect<any>(input, onValueChange, value)
|
||||
} else if (input.type === 'static' || input.type == 'template') {
|
||||
setValue()
|
||||
} else {
|
||||
value = undefined
|
||||
}
|
||||
}
|
||||
|
||||
function setValue() {
|
||||
if (input.type === 'static') {
|
||||
value = input.value
|
||||
function computeGlobalContext() {
|
||||
Object.prototype.toString = function () {
|
||||
return JSON.stringify(this)
|
||||
}
|
||||
return Object.fromEntries(
|
||||
Object.entries($worldStore?.outputsById ?? {})
|
||||
.filter(([k, _]) => k != id)
|
||||
.map(([key, value]) => {
|
||||
return [
|
||||
key,
|
||||
Object.fromEntries(Object.entries(value ?? {}).map((x) => [x[0], x[1].peak()]))
|
||||
]
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
function setValue() {
|
||||
console.log(computeGlobalContext())
|
||||
if (input.type === 'template' && isCodeInjection(input.eval)) {
|
||||
try {
|
||||
value = eval_like('`' + input.eval + '`', computeGlobalContext())
|
||||
} catch (e) {
|
||||
value = e.message
|
||||
}
|
||||
} else if (input.type === 'static') {
|
||||
value = input.value
|
||||
} else if (input.type === 'template') {
|
||||
value = input.eval
|
||||
}
|
||||
}
|
||||
|
||||
function create_context_function_template(eval_string, context) {
|
||||
return `
|
||||
return function (context) {
|
||||
"use strict";
|
||||
${
|
||||
Object.keys(context).length > 0
|
||||
? `let ${Object.keys(context).map((key) => ` ${key} = context['${key}']`)};`
|
||||
: ``
|
||||
}
|
||||
return ${eval_string};
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
function make_context_evaluator(eval_string, context) {
|
||||
let template = create_context_function_template(eval_string, context)
|
||||
let functor = Function(template)
|
||||
return functor()
|
||||
}
|
||||
|
||||
function eval_like(text, context = {}) {
|
||||
let evaluator = make_context_evaluator(text, context)
|
||||
return evaluator(context)
|
||||
}
|
||||
|
||||
function onValueChange(newValue: any): void {
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
</script>
|
||||
|
||||
{#if componentInput.type !== 'runnable'}
|
||||
<InputValue input={componentInput} bind:value={result} />
|
||||
<InputValue {id} input={componentInput} bind:value={result} />
|
||||
{/if}
|
||||
|
||||
<slot />
|
||||
|
||||
@@ -199,7 +199,7 @@
|
||||
</script>
|
||||
|
||||
{#each Object.keys(inputs ?? {}) as key}
|
||||
<InputValue input={inputs[key]} bind:value={runnableInputValues[key]} />
|
||||
<InputValue {id} input={inputs[key]} bind:value={runnableInputValues[key]} />
|
||||
{/each}
|
||||
|
||||
<TestJobLoader
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import type { AppInput } from '../../inputType'
|
||||
import type { Output } from '../../rx'
|
||||
import type { AppEditorContext } from '../../types'
|
||||
import DebouncedInput from '../helpers/DebouncedInput.svelte'
|
||||
import AlignWrapper from '../helpers/AlignWrapper.svelte'
|
||||
import InputValue from '../helpers/InputValue.svelte'
|
||||
|
||||
@@ -29,7 +28,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<InputValue input={configuration.label} bind:value={labelValue} />
|
||||
<InputValue {id} input={configuration.label} bind:value={labelValue} />
|
||||
|
||||
<AlignWrapper {verticalAlignment}>
|
||||
<input
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<InputValue input={configuration.label} bind:value={labelValue} />
|
||||
<InputValue {id} input={configuration.label} bind:value={labelValue} />
|
||||
|
||||
<AlignWrapper {horizontalAlignment} {verticalAlignment}>
|
||||
<Toggle
|
||||
|
||||
@@ -27,9 +27,9 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<InputValue input={configuration.label} bind:value={label} />
|
||||
<InputValue input={configuration.items} bind:value={items} />
|
||||
<InputValue input={configuration.itemKey} bind:value={itemKey} />
|
||||
<InputValue {id} input={configuration.label} bind:value={label} />
|
||||
<InputValue {id} input={configuration.items} bind:value={items} />
|
||||
<InputValue {id} input={configuration.itemKey} bind:value={itemKey} />
|
||||
|
||||
<AlignWrapper {horizontalAlignment} {verticalAlignment}>
|
||||
<Select on:clear={onChange} on:change={onChange} {items} placeholder="Select an item" />
|
||||
|
||||
@@ -100,8 +100,8 @@
|
||||
$: result && rerender()
|
||||
</script>
|
||||
|
||||
<InputValue input={configuration.search} bind:value={search} />
|
||||
<InputValue input={configuration.pagination} bind:value={pagination} />
|
||||
<InputValue {id} input={configuration.search} bind:value={search} />
|
||||
<InputValue {id} input={configuration.pagination} bind:value={pagination} />
|
||||
|
||||
<RunnableWrapper bind:componentInput {id} bind:result {extraQueryParams}>
|
||||
{#if Array.isArray(result) && result.every(isObject)}
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<InputValue input={configuration.label} bind:value={labelValue} />
|
||||
<InputValue {id} input={configuration.label} bind:value={labelValue} />
|
||||
|
||||
<AlignWrapper {verticalAlignment}>
|
||||
<input
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
mounted = true
|
||||
})
|
||||
|
||||
$: mounted && ($worldStore = buildWorld($staticOutputs))
|
||||
$: mounted && ($worldStore = buildWorld($staticOutputs, $worldStore))
|
||||
$: previewing = $mode === 'preview'
|
||||
$: width = $breakpoint === 'sm' ? 'w-[640px]' : 'min-w-[1080px] w-full'
|
||||
|
||||
@@ -89,7 +89,7 @@
|
||||
<Pane size={20}>
|
||||
<ContextPanel />
|
||||
</Pane>
|
||||
<Pane size={60}>
|
||||
<Pane size={55}>
|
||||
<SplitPanesWrapper horizontal>
|
||||
<Pane size={70}>
|
||||
<div class={classNames('bg-gray-100 mx-auto relative min-h-full', width)}>
|
||||
@@ -110,7 +110,7 @@
|
||||
</Pane>
|
||||
</SplitPanesWrapper>
|
||||
</Pane>
|
||||
<Pane size={20} minSize={20} maxSize={20}>
|
||||
<Pane size={25} minSize={20} maxSize={33}>
|
||||
<Tabs bind:selected={selectedTab}>
|
||||
<Tab value="insert" size="xs">
|
||||
<div class="m-1 flex flex-row gap-2">
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
mounted = true
|
||||
})
|
||||
|
||||
$: mounted && ($worldStore = buildWorld($staticOutputs))
|
||||
$: mounted && ($worldStore = buildWorld($staticOutputs, undefined))
|
||||
$: width = $breakpoint === 'sm' ? 'w-[640px]' : 'w-full '
|
||||
</script>
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
</script>
|
||||
|
||||
<PanelSection title="Outputs">
|
||||
{#each Object.entries($staticOutputs) as [componentId, outputs], index}
|
||||
{#each Object.entries($staticOutputs) as [componentId, outputs] (componentId)}
|
||||
{#if outputs.length > 0 && $worldStore?.outputsById[componentId]}
|
||||
<div class="flex flex-row justify-between w-full -mb-2 ">
|
||||
<button
|
||||
|
||||
+23
-9
@@ -5,20 +5,34 @@
|
||||
|
||||
export let componentInput: AppInput
|
||||
export let disableStatic: boolean = false
|
||||
|
||||
$: if (componentInput.fieldType == 'textarea' && componentInput.type == 'static') {
|
||||
//@ts-ignore
|
||||
componentInput.type = 'template'
|
||||
}
|
||||
|
||||
const brackets = '${}'
|
||||
</script>
|
||||
|
||||
{#if componentInput.fieldType !== 'any'}
|
||||
<div class="w-full">
|
||||
<ToggleButtonGroup bind:selected={componentInput.type}>
|
||||
<ToggleButton
|
||||
position="left"
|
||||
value="static"
|
||||
startIcon={{ icon: faPen }}
|
||||
size="xs"
|
||||
disable={disableStatic}
|
||||
>
|
||||
Static
|
||||
</ToggleButton>
|
||||
{#if componentInput.fieldType === 'textarea'}
|
||||
<ToggleButton position="left" value="template" size="xs" disable={disableStatic}>
|
||||
{brackets} Templatable
|
||||
</ToggleButton>
|
||||
{:else}
|
||||
<ToggleButton
|
||||
position="left"
|
||||
value="static"
|
||||
startIcon={{ icon: faPen }}
|
||||
size="xs"
|
||||
disable={disableStatic}
|
||||
>
|
||||
Static
|
||||
</ToggleButton>
|
||||
{/if}
|
||||
|
||||
<ToggleButton
|
||||
value="connected"
|
||||
position="center"
|
||||
|
||||
@@ -12,17 +12,17 @@
|
||||
import { capitalize } from '$lib/utils'
|
||||
import { fieldTypeToTsType } from '../../utils'
|
||||
import Recompute from './Recompute.svelte'
|
||||
import gridHelp from 'svelte-grid/build/helper/index.mjs'
|
||||
import { gridColumns } from '../../gridUtils'
|
||||
import Tooltip from '$lib/components/Tooltip.svelte'
|
||||
import ComponentInputTypeEditor from './ComponentInputTypeEditor.svelte'
|
||||
import AlignmentEditor from './AlignmentEditor.svelte'
|
||||
import RunnableInputEditor from './inputEditor/RunnableInputEditor.svelte'
|
||||
import TemplateEditor from '$lib/components/TemplateEditor.svelte'
|
||||
import type { Output } from '../../rx'
|
||||
|
||||
export let component: AppComponent | undefined
|
||||
export let onDelete: (() => void) | undefined = undefined
|
||||
|
||||
const { app, staticOutputs, runnableComponents } =
|
||||
const { app, staticOutputs, runnableComponents, worldStore } =
|
||||
getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
function removeGridElement() {
|
||||
@@ -48,6 +48,25 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function buildExtraLib(components: Record<string, Record<string, Output<any>>>): string {
|
||||
return Object.entries(components)
|
||||
.filter(([k, v]) => k != component?.id)
|
||||
.map(([k, v]) => [k, Object.fromEntries(Object.entries(v).map(([k, v]) => [k, v.peak()]))])
|
||||
.map(
|
||||
([k, v]) => `
|
||||
|
||||
declare const ${k} = ${JSON.stringify(v)};
|
||||
|
||||
`
|
||||
)
|
||||
.join('\n')
|
||||
}
|
||||
|
||||
$: extraLib =
|
||||
component?.componentInput?.type === 'template' && $worldStore
|
||||
? buildExtraLib($worldStore?.outputsById ?? {})
|
||||
: undefined
|
||||
</script>
|
||||
|
||||
{#if component}
|
||||
@@ -71,6 +90,8 @@
|
||||
<div class="flex flex-col w-full gap-2 my-2">
|
||||
{#if component.componentInput.type === 'static'}
|
||||
<StaticInputEditor bind:componentInput={component.componentInput} />
|
||||
{:else if component.componentInput.type === 'template' && component.componentInput !== undefined}
|
||||
<TemplateEditor bind:code={component.componentInput.eval} {extraLib} />
|
||||
{:else if component.componentInput.type === 'connected' && component.componentInput !== undefined}
|
||||
<ConnectedInputEditor bind:componentInput={component.componentInput} />
|
||||
{:else if component.componentInput?.type === 'runnable' && component.componentInput !== undefined}
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@
|
||||
{#if componentInput.fieldType === 'number'}
|
||||
<input type="number" bind:value={componentInput.value} />
|
||||
{:else if componentInput.fieldType === 'textarea'}
|
||||
<textarea bind:value={componentInput.value} />
|
||||
<textarea type="text" bind:value={componentInput.value} />
|
||||
{:else if componentInput.fieldType === 'boolean'}
|
||||
<Toggle bind:checked={componentInput.value} />
|
||||
{:else if componentInput.fieldType === 'select'}
|
||||
|
||||
@@ -40,6 +40,11 @@ export type StaticInput<U> = {
|
||||
visible?: boolean | undefined
|
||||
}
|
||||
|
||||
export type TemplateInput = {
|
||||
eval: string
|
||||
type: 'template'
|
||||
}
|
||||
|
||||
type RunnableByPath = {
|
||||
path: string
|
||||
runType: 'script' | 'flow' | 'hubscript'
|
||||
@@ -66,6 +71,7 @@ type AppInputSpec<T extends InputType, U, V extends InputType = never> = (
|
||||
| ConnectedInput
|
||||
| UserInput<U>
|
||||
| ResultInput
|
||||
| TemplateInput
|
||||
) &
|
||||
InputConfiguration<T, U, V>
|
||||
|
||||
@@ -86,11 +92,11 @@ export type AppInput =
|
||||
| AppInputSpec<'any', any>
|
||||
| AppInputSpec<'object', Record<string | number, any>>
|
||||
| (AppInputSpec<'select', string> & {
|
||||
/**
|
||||
* One of the keys of `staticValues` from `lib/components/apps/editor/componentsPanel/componentStaticValues`
|
||||
*/
|
||||
optionValuesKey: keyof typeof staticValues
|
||||
})
|
||||
/**
|
||||
* One of the keys of `staticValues` from `lib/components/apps/editor/componentsPanel/componentStaticValues`
|
||||
*/
|
||||
optionValuesKey: keyof typeof staticValues
|
||||
})
|
||||
| AppInputSpec<'array', string[], 'text'>
|
||||
| AppInputSpec<'array', string[], 'textarea'>
|
||||
| AppInputSpec<'array', number[], 'number'>
|
||||
@@ -100,8 +106,8 @@ export type AppInput =
|
||||
| AppInputSpec<'array', string[], 'datetime'>
|
||||
| AppInputSpec<'array', object[], 'object'>
|
||||
| (AppInputSpec<'array', string[], 'select'> & {
|
||||
optionValuesKey: keyof typeof staticValues
|
||||
})
|
||||
optionValuesKey: keyof typeof staticValues
|
||||
})
|
||||
|
||||
export type StaticAppInput = Extract<AppInput, { type: 'static' }>
|
||||
export type ConnectedAppInput = Extract<AppInput, { type: 'connected' }>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { AppInput } from './inputType'
|
||||
import { writable, type Writable } from 'svelte/store'
|
||||
|
||||
export interface Subscriber<T> {
|
||||
next(v: T)
|
||||
@@ -9,40 +10,44 @@ export interface Observable<T> {
|
||||
}
|
||||
export interface Output<T> extends Observable<T> {
|
||||
set(x: T, force?: boolean): void
|
||||
peak(): T | any | undefined
|
||||
}
|
||||
|
||||
export interface Input<T> extends Subscriber<T> {
|
||||
peak(): T | any | undefined
|
||||
}
|
||||
|
||||
|
||||
export type World = {
|
||||
outputsById: Record<string, Record<string, Output<any>>>
|
||||
connect: <T>(inputSpec: AppInput, next: (x: T) => void) => Input<T>
|
||||
connect: <T>(inputSpec: AppInput, next: (x: T) => void, previousValue: T) => Input<T>
|
||||
state: Writable<number>
|
||||
}
|
||||
|
||||
export function buildWorld(components: Record<string, string[]>) {
|
||||
export function buildWorld(components: Record<string, string[]>, previousWorld: World | undefined): World {
|
||||
const newWorld = buildObservableWorld()
|
||||
const outputsById: Record<string, Record<string, Output<any>>> = {}
|
||||
|
||||
const state = writable(0)
|
||||
for (const [k, outputs] of Object.entries(components)) {
|
||||
outputsById[k] = {}
|
||||
|
||||
for (const o of outputs) {
|
||||
outputsById[k][o] = newWorld.newOutput(k, o)
|
||||
outputsById[k][o] = newWorld.newOutput(k, o, state, previousWorld?.outputsById[k]?.[o].peak())
|
||||
}
|
||||
}
|
||||
state.update((x) => x + 1)
|
||||
|
||||
return { outputsById, connect: newWorld.connect }
|
||||
return { outputsById, connect: newWorld.connect, state }
|
||||
}
|
||||
|
||||
export function buildObservableWorld() {
|
||||
const observables: Record<string, Output<any>> = {}
|
||||
|
||||
function connect<T>(inputSpec: AppInput, next: (x: T) => void): Input<T> {
|
||||
function connect<T>(inputSpec: AppInput, next: (x: T) => void, previousValue: T): Input<T> {
|
||||
if (inputSpec.type === 'static') {
|
||||
return {
|
||||
peak: () => inputSpec.value,
|
||||
next: () => {}
|
||||
next: () => { }
|
||||
}
|
||||
} else if (inputSpec.type === 'connected') {
|
||||
const input = cachedInput(next)
|
||||
@@ -52,7 +57,7 @@ export function buildObservableWorld() {
|
||||
if (!connection) {
|
||||
return {
|
||||
peak: () => undefined,
|
||||
next: () => {}
|
||||
next: () => { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,7 +71,7 @@ export function buildObservableWorld() {
|
||||
console.warn('Observable at ' + componentId + '.' + p + ' not found')
|
||||
return {
|
||||
peak: () => undefined,
|
||||
next: () => {}
|
||||
next: () => { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,15 +80,16 @@ export function buildObservableWorld() {
|
||||
} else if (inputSpec.type === 'user') {
|
||||
return {
|
||||
peak: () => inputSpec.value,
|
||||
next: () => {}
|
||||
next: () => { }
|
||||
}
|
||||
} else {
|
||||
throw Error('Unknown input type ' + inputSpec)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function newOutput<T>(id: string, name: string): Output<T> {
|
||||
const output = settableOutput<T>()
|
||||
function newOutput<T>(id: string, name: string, state: Writable<number>, previousValue: T): Output<T> {
|
||||
const output = settableOutput<T>(state, previousValue)
|
||||
observables[`${id}.${name}`] = output
|
||||
return output
|
||||
}
|
||||
@@ -111,12 +117,13 @@ export function cachedInput<T>(nextParan: (x: T) => void): Input<T> {
|
||||
}
|
||||
}
|
||||
|
||||
export function settableOutput<T>(): Output<T> {
|
||||
let value: T | undefined = undefined
|
||||
export function settableOutput<T>(state: Writable<number>, previousValue: T): Output<T> {
|
||||
let value: T | undefined = previousValue
|
||||
const subscribers: Subscriber<T>[] = []
|
||||
|
||||
function subscribe(x: Subscriber<T>) {
|
||||
if (!subscribers.includes(x)) {
|
||||
|
||||
subscribers.push(x)
|
||||
|
||||
// Send the current value to the new subscriber if it already exists
|
||||
@@ -128,14 +135,21 @@ export function settableOutput<T>(): Output<T> {
|
||||
|
||||
function set(x: T, force: boolean = false) {
|
||||
if (value != x || force) {
|
||||
state.update((x) => x + 1)
|
||||
|
||||
value = x
|
||||
|
||||
subscribers.forEach((x) => x.next(value!))
|
||||
}
|
||||
}
|
||||
|
||||
function peak(): T | undefined {
|
||||
return value
|
||||
}
|
||||
|
||||
return {
|
||||
subscribe,
|
||||
set
|
||||
set,
|
||||
peak
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,18 @@
|
||||
<Icon data={faPlus} scale={0.8} />
|
||||
</button>
|
||||
<div class="divide-y divide-gray-100 text-xs w-40">
|
||||
<button
|
||||
class="w-full text-left p-2 hover:bg-gray-100"
|
||||
on:click={() => {
|
||||
close()
|
||||
dispatch('new', 'script')
|
||||
}}
|
||||
role="menuitem"
|
||||
tabindex="-1"
|
||||
>
|
||||
<Icon data={faCode} scale={0.8} class="mr-1" />
|
||||
Action (Script)
|
||||
</button>
|
||||
{#if trigger}
|
||||
<button
|
||||
class="w-full text-left p-2 hover:bg-gray-100"
|
||||
@@ -39,18 +51,6 @@
|
||||
Trigger (Script)
|
||||
</button>
|
||||
{/if}
|
||||
<button
|
||||
class="w-full text-left p-2 hover:bg-gray-100"
|
||||
on:click={() => {
|
||||
close()
|
||||
dispatch('new', 'script')
|
||||
}}
|
||||
role="menuitem"
|
||||
tabindex="-1"
|
||||
>
|
||||
<Icon data={faCode} scale={0.8} class="mr-1" />
|
||||
Action (Script)
|
||||
</button>
|
||||
<button
|
||||
class="w-full text-left p-2 hover:bg-gray-100"
|
||||
on:click={() => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
|
||||
|
||||
import { languages } from 'monaco-editor/esm/vs/editor/editor.api'
|
||||
export function editorConfig(model: any, code: string, lang: string, automaticLayout: boolean, fixedOverflowWidgets: boolean) {
|
||||
|
||||
return {
|
||||
model,
|
||||
value: code,
|
||||
@@ -10,10 +10,9 @@ export function editorConfig(model: any, code: string, lang: string, automaticLa
|
||||
fixedOverflowWidgets,
|
||||
autoDetectHighContrast: true,
|
||||
//lineNumbers: 'off',
|
||||
//lineDecorationsWidth: 0,
|
||||
lineNumbersMinChars: 4,
|
||||
lineDecorationsWidth: 15,
|
||||
lineNumbersMinChars: 2,
|
||||
scrollbar: { alwaysConsumeMouseWheel: false },
|
||||
lineNumbers: (ln) => '<span class="pr-4 text-gray-400">' + ln + '</span>',
|
||||
folding: false,
|
||||
scrollBeyondLastLine: false,
|
||||
minimap: {
|
||||
@@ -22,6 +21,9 @@ export function editorConfig(model: any, code: string, lang: string, automaticLa
|
||||
lightbulb: {
|
||||
enabled: true
|
||||
},
|
||||
suggest: {
|
||||
showKeywords: false,
|
||||
},
|
||||
'bracketPairColorization.enabled': true,
|
||||
matchBrackets: 'always' as 'always',
|
||||
}
|
||||
@@ -52,4 +54,100 @@ export function langToExt(lang: string): string {
|
||||
}
|
||||
}
|
||||
|
||||
export const updateOptions = { tabSize: 2, insertSpaces: true }
|
||||
export const updateOptions = { tabSize: 2, insertSpaces: true }
|
||||
|
||||
export function convertKind(kind: string): any {
|
||||
switch (kind) {
|
||||
case Kind.primitiveType:
|
||||
case Kind.keyword:
|
||||
return languages.CompletionItemKind.Keyword;
|
||||
case Kind.variable:
|
||||
case Kind.localVariable:
|
||||
return languages.CompletionItemKind.Variable;
|
||||
case Kind.memberVariable:
|
||||
case Kind.memberGetAccessor:
|
||||
case Kind.memberSetAccessor:
|
||||
return languages.CompletionItemKind.Field;
|
||||
case Kind.function:
|
||||
case Kind.memberFunction:
|
||||
case Kind.constructSignature:
|
||||
case Kind.callSignature:
|
||||
case Kind.indexSignature:
|
||||
return languages.CompletionItemKind.Function;
|
||||
case Kind.enum:
|
||||
return languages.CompletionItemKind.Enum;
|
||||
case Kind.module:
|
||||
return languages.CompletionItemKind.Module;
|
||||
case Kind.class:
|
||||
return languages.CompletionItemKind.Class;
|
||||
case Kind.interface:
|
||||
return languages.CompletionItemKind.Interface;
|
||||
case Kind.warning:
|
||||
return languages.CompletionItemKind.File;
|
||||
}
|
||||
|
||||
return languages.CompletionItemKind.Property;
|
||||
}
|
||||
|
||||
class Kind {
|
||||
public static unknown: string = '';
|
||||
public static keyword: string = 'keyword';
|
||||
public static script: string = 'script';
|
||||
public static module: string = 'module';
|
||||
public static class: string = 'class';
|
||||
public static interface: string = 'interface';
|
||||
public static type: string = 'type';
|
||||
public static enum: string = 'enum';
|
||||
public static variable: string = 'var';
|
||||
public static localVariable: string = 'local var';
|
||||
public static function: string = 'function';
|
||||
public static localFunction: string = 'local function';
|
||||
public static memberFunction: string = 'method';
|
||||
public static memberGetAccessor: string = 'getter';
|
||||
public static memberSetAccessor: string = 'setter';
|
||||
public static memberVariable: string = 'property';
|
||||
public static constructorImplementation: string = 'constructor';
|
||||
public static callSignature: string = 'call';
|
||||
public static indexSignature: string = 'index';
|
||||
public static constructSignature: string = 'construct';
|
||||
public static parameter: string = 'parameter';
|
||||
public static typeParameter: string = 'type parameter';
|
||||
public static primitiveType: string = 'primitive type';
|
||||
public static label: string = 'label';
|
||||
public static alias: string = 'alias';
|
||||
public static const: string = 'const';
|
||||
public static let: string = 'let';
|
||||
public static warning: string = 'warning';
|
||||
}
|
||||
|
||||
export function createDocumentationString(details: any): string {
|
||||
let documentationString = displayPartsToString(details.documentation);
|
||||
if (details.tags) {
|
||||
for (const tag of details.tags) {
|
||||
documentationString += `\n\n${tagToString(tag)}`;
|
||||
}
|
||||
}
|
||||
return documentationString;
|
||||
}
|
||||
|
||||
|
||||
function tagToString(tag: any): string {
|
||||
let tagLabel = `*@${tag.name}*`;
|
||||
if (tag.name === 'param' && tag.text) {
|
||||
const [paramName, ...rest] = tag.text;
|
||||
tagLabel += `\`${paramName.text}\``;
|
||||
if (rest.length > 0) tagLabel += ` — ${rest.map((r) => r.text).join(' ')}`;
|
||||
} else if (Array.isArray(tag.text)) {
|
||||
tagLabel += ` — ${tag.text.map((r) => r.text).join(' ')}`;
|
||||
} else if (tag.text) {
|
||||
tagLabel += ` — ${tag.text}`;
|
||||
}
|
||||
return tagLabel;
|
||||
}
|
||||
|
||||
export function displayPartsToString(displayParts: any | undefined): string {
|
||||
if (displayParts) {
|
||||
return displayParts.map((displayPart) => displayPart.text).join('');
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user