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

fix: content api for frontend events from backend, ui updates and reset command #101

Merged
merged 9 commits into from
May 8, 2024
5 changes: 3 additions & 2 deletions src/main/framework/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@ import short from 'short-uuid'
import { runInNewContext } from 'node:vm'
import * as tryRequire from 'try-require'
import { compile } from '../vendor/webpack'
import { ExecuteContext } from './runtime'
import { Settings } from './settings'
import { ExecuteContext } from './execute-context'

export type FrontendHook = () => Promise<void> | void
export class Commands {
public lib: object = {}
public state: Map<string, object> = new Map<string, object>()

public handlers: Map<string, FrontendHook> = new Map<string, FrontendHook>()
constructor(
private workingDirectory: string,
private templateDirectory: string
Expand Down
154 changes: 154 additions & 0 deletions src/main/framework/execute-context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import { Workspace } from './workspace'
import { Command, ResultContentEvent, ResultStreamEvent, Runtime } from './runtime'
import { WebContents } from 'electron'
import { readFile } from 'fs-extra'
import short from 'short-uuid'
import { ResultStream } from './result-stream'

export interface RuntimeContentHandle {
id: string
update(html: string): void
on(event: string, handler: RuntimeContentEventCallback): void
}

export interface RuntimeContentEvent {
event: string
}

export type RuntimeContentEventCallback = (event: RuntimeContentEvent) => Promise<void> | void

export class ExecuteContext {
public readonly start: number = Date.now()
public readonly id: string = short.generate()

public readonly events: ResultContentEvent[] = []
private readonly eventHandlers = new Map<string, RuntimeContentEventCallback>()

constructor(
public readonly platform: string,
public readonly sender: WebContents,
public readonly workspace: Workspace,
public readonly runtime: Runtime,
public readonly command: Command,
public readonly profile: string,
public readonly history: {
enabled: boolean
results: boolean
max: number
}
) {}

out(text: string, error: boolean = false): ResultStream | null {
const isFinished = this.command.aborted || this.command.complete
if (isFinished) {
if (!this.command.result.edit) {
return null
}
}
const streamEntry = new ResultStream(text, error)

this.command.result.stream.push(streamEntry)

const streamEvent: ResultStreamEvent = {
entry: streamEntry,
runtime: this.runtime.id,
command: this.command.id
}

if (!this.sender.isDestroyed()) {
this.sender.send('runtime.commandEvent', streamEvent)
}

return streamEntry
}

content(html: string): RuntimeContentHandle {
const contextId = this.id
const runtimeId = this.runtime.id
const commandId = this.command.id

const command = this.command

const id = short.generate()
const events = this.events
const container = (html: string): string => `<span id="${id}">${html}</span>`

const R = this.out(container(html), false)
const sender = this.sender
const eventHandlers = this.eventHandlers
return {
id,
update(newHTML: string): void {
if (R !== null && !command.aborted && !command.complete) {
R.setText(container(newHTML))

sender.send('runtime.commandEvent')
}
},
on(event: string, callback: RuntimeContentEventCallback): void {
const eventHandlerId = short.generate()

eventHandlers.set(eventHandlerId, callback)

events.push({
event,
commandId,
contextId,
runtimeId,
contentId: id,
handlerId: eventHandlerId
})

sender.send('runtime.observe', {
contextId,
contentId: id,
eventHandlerId,
event
})

sender.send('runtime.commandEvent')
}
}
}

async fireEvent(eventName: string, handlerId: string): Promise<void> {
const event = this.eventHandlers.get(handlerId)
if (this.command.aborted || this.command.complete) {
return
}
if (!event) {
return
}

await event({
event: eventName
})
}

async edit(path: string, callback: (text: string) => void): Promise<void> {
const file = await readFile(path)

this.command.result.edit = {
path,
modified: false,
content: file.toString(),
callback
}

this.sender.send('runtime.commandEvent')
}

finish(code: number): void {
if (this.command.aborted || this.command.complete) {
return
}

this.command.result.code = code
this.command.complete = true
this.command.error = this.command.result.code !== 0

if (this.history.enabled) {
this.workspace.history.append(this.command, this.start, this.profile, this.history.results)
}
}
}
18 changes: 18 additions & 0 deletions src/main/framework/result-stream.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { sanitizeOutput } from '../util'

export class ResultStream {
public text: string
constructor(
public raw: string,
public error: boolean = false
) {
this.raw = this.raw.toString()
this.text = sanitizeOutput(raw)
}

setText(raw: string, error: boolean = this.error): void {
this.raw = raw
this.text = sanitizeOutput(raw)
this.error = error
}
}
Loading
Loading