Skip to content

Commit

Permalink
fix: improve websockets handling for flow editor
Browse files Browse the repository at this point in the history
  • Loading branch information
rubenfiszel committed Jun 2, 2023
1 parent 711b47c commit ce94426
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 81 deletions.
56 changes: 36 additions & 20 deletions frontend/src/lib/components/Editor.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -80,19 +80,27 @@
}
export let shouldBindKey: boolean = true
export let fixedOverflowWidgets = true
export let path: string = randomHash()
export let path: string | undefined = undefined
export let yContent: Text | undefined = undefined
export let awareness: any | undefined = undefined
if (path == '' || path == undefined || path.startsWith('/')) {
path = randomHash()
const rHash = randomHash()
$: filePath = computePath(path)
function computePath(path: string | undefined): string {
if (path == '' || path == undefined || path.startsWith('/')) {
return rHash
} else {
return path as string
}
}
let initialPath: string = path
let initialPath: string | undefined = path
$: path != initialPath && lang == 'typescript' && handlePathChange()
let websockets: [MonacoLanguageClient, WebSocket][] = []
let websockets: WebSocket[] = []
let languageClients: MonacoLanguageClient[] = []
let websocketInterval: NodeJS.Timer | undefined
let lastWsAttempt: Date = new Date()
let nbWsAttempt = 0
Expand All @@ -101,7 +109,7 @@
const uri =
lang == 'typescript'
? `file:///${path}.${langToExt(lang)}`
? `file:///${filePath}.${langToExt(lang)}`
: `file:///tmp/monaco/${randomHash()}.${langToExt(lang)}`
buildWorkerDefinition('../../../workers', import.meta.url, false)
Expand Down Expand Up @@ -271,7 +279,7 @@
) {
try {
const webSocket = new WebSocket(url)
websockets.push(webSocket)
webSocket.onopen = async () => {
const socket = toSocket(webSocket)
const reader = new WebSocketMessageReader(socket)
Expand All @@ -285,7 +293,7 @@
// if (middlewareOptions != undefined) {
// languageClient.registerNotUsedFeatures()
// }
websockets.push([languageClient, webSocket])
languageClients.push(languageClient)
// HACK ALERT: for some reasons, the client need to be restarted to take into account the 'go get <dep>' command
// the only way I could figure out to listen for this event is this. I'm sure there is a better way to do this
Expand Down Expand Up @@ -355,7 +363,7 @@
let encodedImportMap = ''
if (lang == 'typescript') {
if (path && path.split('/').length > 2) {
if (filePath && filePath.split('/').length > 2) {
let expiration = new Date()
expiration.setHours(expiration.getHours() + 2)
const token = await UserService.createToken({
Expand All @@ -367,7 +375,7 @@
'file:///': root + '/'
}
}
let path_splitted = path.split('/')
let path_splitted = filePath.split('/')
for (let c = 0; c < path_splitted.length; c++) {
let key = 'file://./'
for (let i = 0; i < c; i++) {
Expand Down Expand Up @@ -551,28 +559,35 @@
let pathTimeout: NodeJS.Timeout | undefined = undefined
function handlePathChange() {
console.log('path changed, reloading language server', initialPath, path)
initialPath = path
pathTimeout && clearTimeout(pathTimeout)
pathTimeout = setTimeout(reloadWebsocket, 3000)
pathTimeout = setTimeout(reloadWebsocket, 1000)
}
async function closeWebsockets() {
command && command.dispose()
command = undefined
console.debug(`disposing ${websockets.length} language clients and closing websockets`)
for (const x of languageClients) {
try {
await x.dispose()
} catch (err) {
console.debug('error disposing language client', err)
}
}
languageClients = []
for (const x of websockets) {
try {
await x[0].dispose()
x[1].close()
await x.close()
} catch (err) {
console.log('error disposing language client, closing websocket', err)
try {
x[1].close()
} catch (err) {
console.log('error disposing websocket, closin', err)
}
console.debug('error closing websocket', err)
}
}
console.log('disposed language client and closed websocket')
console.debug('done closing websockets')
websockets = []
websocketInterval && clearInterval(websocketInterval)
}
Expand Down Expand Up @@ -642,6 +657,7 @@
!websocketAlive.go &&
!websocketInterval
) {
console.log('reconnecting to language servers on focus')
reloadWebsocket()
}
})
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/lib/components/InputTransformForm.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@
</span>
{/if}
{#if isStaticTemplate(inputCat) && propertyType == 'static' && !noDynamicToggle}
<div class="mt-2 min-h-[28px] rounded border border-1 border-gray-500">
<div class="mt-2 min-h-[28px] rounded border border-1 border-gray-400">
{#if arg}
<TemplateEditor
bind:this={monacoTemplate}
Expand Down
6 changes: 5 additions & 1 deletion frontend/src/lib/components/InputTransformSchemaForm.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@
args = {}
}
export function setArgs(nargs: Record<string, InputTransform | any>) {
args = nargs
}
function removeExtraKey() {
const nargs = {}
Object.keys(args ?? {}).forEach((key) => {
Expand Down Expand Up @@ -57,7 +61,7 @@
{#if keys.length > 0}
{#each keys as argName (argName)}
{#if (!filter || filter.includes(argName)) && Object.keys(schema.properties ?? {}).includes(argName)}
<div class="z-10">
<div class="z-10 pt-1.5">
<InputTransformForm
{previousModuleId}
bind:arg={args[argName]}
Expand Down
110 changes: 51 additions & 59 deletions frontend/src/lib/components/flows/content/FlowModuleComponent.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@
import ModulePreview from '$lib/components/ModulePreview.svelte'
import { createScriptFromInlineScript, fork } from '$lib/components/flows/flowStateUtils'
import { RawScript, type FlowModule, type PathFlow, type PathScript } from '$lib/gen'
import { RawScript, type FlowModule } from '$lib/gen'
import FlowCard from '../common/FlowCard.svelte'
import FlowModuleHeader from './FlowModuleHeader.svelte'
import { getLatestHashForScript, scriptLangToEditorLang } from '$lib/scripts'
import PropPickerWrapper from '../propPicker/PropPickerWrapper.svelte'
import { afterUpdate, getContext } from 'svelte'
import { afterUpdate, getContext, tick } from 'svelte'
import type { FlowEditorContext } from '../types'
import { loadSchemaFromModule } from '../utils'
import FlowModuleScript from './FlowModuleScript.svelte'
Expand All @@ -38,9 +38,6 @@
export let parentModule: FlowModule | undefined = undefined
export let previousModule: FlowModule | undefined
let value = flowModule.value as PathFlow | RawScript | PathScript
$: value = flowModule.value as PathFlow | RawScript | PathScript
let editor: Editor
let modulePreview: ModulePreview
let websocketAlive = {
Expand All @@ -59,10 +56,6 @@
let validCode = true
let width = 1200
let inputTransforms: Record<string, any> = value.input_transforms
$: value.input_transforms = inputTransforms
$: stepPropPicker = failureModule
? {
pickableProperties: {
Expand Down Expand Up @@ -91,16 +84,15 @@
}
}
let inputTransformSchemaForm: InputTransformSchemaForm | undefined = undefined
async function reload(flowModule: FlowModule) {
try {
const { input_transforms, schema } = await loadSchemaFromModule(flowModule)
validCode = true
setTimeout(() => {
if (!deepEqual(value.input_transforms, input_transforms)) {
inputTransforms = input_transforms
}
})
inputTransformSchemaForm?.setArgs(input_transforms)
await tick()
if (!deepEqual(schema, $flowStateStore[flowModule.id]?.schema)) {
if (!$flowStateStore[flowModule.id]) {
$flowStateStore[flowModule.id] = { schema }
Expand All @@ -127,15 +119,12 @@
totalTopGap = panesTop - wrapperTop
})
let isScript = true
$: isScript != (value.type === 'script') && (isScript = value.type === 'script')
let forceReload = 0
</script>

<svelte:window on:keydown={onKeyDown} />

{#if value}
{#if flowModule.value}
<div class="h-full" bind:this={wrapper} bind:clientWidth={width}>
<FlowCard bind:flowModule>
<svelte:fragment slot="header">
Expand Down Expand Up @@ -173,12 +162,12 @@
/>
</svelte:fragment>

{#if value.type === 'rawscript'}
{#if flowModule.value.type === 'rawscript'}
<div class="border-b-2 shadow-sm px-1">
<EditorBar
{validCode}
{editor}
lang={value['language'] ?? 'deno'}
lang={flowModule.value['language'] ?? 'deno'}
{websocketAlive}
iconOnly={width < 850}
/>
Expand All @@ -191,65 +180,68 @@
style="max-height: calc(100% - {totalTopGap}px) !important;"
>
<Splitpanes horizontal>
<Pane size={isScript ? 30 : 50} minSize={20}>
{#if value.type === 'rawscript'}
<Editor
path={value['path']}
bind:websocketAlive
bind:this={editor}
class="h-full relative"
bind:code={value.content}
deno={value.language === RawScript.language.DENO}
lang={scriptLangToEditorLang(value.language)}
automaticLayout={true}
cmdEnterAction={async () => {
selected = 'test'
if ($selectedId == flowModule.id) {
if (value.type === 'rawscript') {
value.content = editor.getCode()
<Pane size={flowModule.value.type == 'script' ? 30 : 50} minSize={20}>
{#if flowModule.value.type === 'rawscript'}
{#key flowModule.id}
<Editor
path={flowModule.value.path}
bind:websocketAlive
bind:this={editor}
class="h-full relative"
bind:code={flowModule.value.content}
deno={flowModule.value.language === RawScript.language.DENO}
lang={scriptLangToEditorLang(flowModule.value.language)}
automaticLayout={true}
cmdEnterAction={async () => {
selected = 'test'
if ($selectedId == flowModule.id) {
if (flowModule.value.type === 'rawscript') {
flowModule.value.content = editor.getCode()
}
await reload(flowModule)
modulePreview?.runTestWithStepArgs()
}
}}
on:change={async (event) => {
if (flowModule.value.type === 'rawscript') {
flowModule.value.content = event.detail
}
await reload(flowModule)
modulePreview?.runTestWithStepArgs()
}
}}
on:change={async (event) => {
if (flowModule.value.type === 'rawscript') {
flowModule.value.content = event.detail
}
await reload(flowModule)
}}
formatAction={() => {
reload(flowModule)
saveDraft()
}}
fixedOverflowWidgets={true}
/>
{:else if value.type === 'script'}
}}
formatAction={() => {
reload(flowModule)
saveDraft()
}}
fixedOverflowWidgets={true}
/>
{/key}
{:else if flowModule.value.type === 'script'}
{#key forceReload}
<FlowModuleScript path={value.path} hash={value.hash} />
<FlowModuleScript path={flowModule.value.path} hash={flowModule.value.hash} />
{/key}
{:else if value.type === 'flow'}
<FlowPathViewer path={value.path} />
{:else if flowModule.value.type === 'flow'}
<FlowPathViewer path={flowModule.value.path} />
{/if}
</Pane>
<Pane size={isScript ? 70 : 50} minSize={20}>
<Pane size={flowModule.value.type == 'script' ? 70 : 50} minSize={20}>
<Tabs bind:selected>
<Tab value="inputs"><span class="font-semibold">Step Input</span></Tab>
<Tab value="test"><span class="font-semibold text-md">Test this step</span></Tab>
<Tab value="advanced">Advanced</Tab>
</Tabs>
<div class="h-[calc(100%-32px)]">
{#if selected === 'inputs'}
{#if selected === 'inputs' && (flowModule.value.type == 'rawscript' || flowModule.value.type == 'script' || flowModule.value.type == 'flow')}
<div class="h-full overflow-auto">
<PropPickerWrapper
pickableProperties={stepPropPicker.pickableProperties}
error={failureModule}
>
<InputTransformSchemaForm
bind:this={inputTransformSchemaForm}
schema={$flowStateStore[$selectedId]?.schema ?? {}}
previousModuleId={previousModule?.id}
bind:args={value.input_transforms}
bind:extraLib={stepPropPicker.extraLib}
bind:args={flowModule.value.input_transforms}
extraLib={stepPropPicker.extraLib}
/>
</PropPickerWrapper>
</div>
Expand Down
1 change: 1 addition & 0 deletions react-sdk/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
<title>Windmill SDK example</title>
</head>
<body>
<img src="/assets/windmill.svg" alt="Windmill" class="w-32 h-32 mx-auto" />
<div id="app"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
Expand Down

0 comments on commit ce94426

Please sign in to comment.