Skip to content

Commit

Permalink
Merge pull request #251 from PrefectHQ/convert-classes-to-factories
Browse files Browse the repository at this point in the history
Refactor services into factories and replace mitt with custom events factory
  • Loading branch information
pleek91 authored Oct 18, 2023
2 parents d6413f4 + b156203 commit 9da2bfb
Show file tree
Hide file tree
Showing 30 changed files with 639 additions and 779 deletions.
11 changes: 0 additions & 11 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@
"date-fns": "2.30.0",
"lodash.isequal": "4.5.0",
"lodash.merge": "4.6.2",
"mitt": "3.0.1",
"pixi-viewport": "5.0.2",
"pixi.js": "7.3.1"
}
Expand Down
34 changes: 34 additions & 0 deletions src/factories/box.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { differenceInMilliseconds, millisecondsInSecond } from 'date-fns'
import { Graphics } from 'pixi.js'
import { DEFAULT_TIME_COLUMN_SIZE_PIXELS } from '@/consts'
import { RunGraphNode } from '@/models/RunGraph'
import { waitForConfig } from '@/objects/config'

// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
export async function nodeBoxFactory() {
const config = await waitForConfig()
const box = new Graphics()

async function render(node: RunGraphNode): Promise<Graphics> {
const { background } = config.styles.node(node)

const right = node.start_time
const left = node.end_time ?? new Date()
const seconds = differenceInMilliseconds(left, right) / millisecondsInSecond
const boxWidth = seconds * DEFAULT_TIME_COLUMN_SIZE_PIXELS
const boxHeight = config.styles.nodeHeight - config.styles.nodeMargin * 2

box.clear()
box.lineStyle(1, 0x0, 1, 2)
box.beginFill(background)
box.drawRoundedRect(0, 0, boxWidth, boxHeight, 4)
box.endFill()

return await box
}

return {
box,
render,
}
}
54 changes: 54 additions & 0 deletions src/factories/events.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// need to use any for function arguments
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type Handler<T = any> = (...payload: T[]) => void
type Events = Record<string, unknown>

// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
export function eventsFactory<T extends Events>() {
type Event = keyof T
type Handlers = Set<Handler>
const all = new Map<Event, Handlers>()

function on<E extends Event>(event: E, handler: Handler<T[E]>): () => void {
const existing = all.get(event)

if (existing) {
existing.add(handler)
} else {
all.set(event, new Set([handler]))
}

return () => off(event, handler)
}

function once<E extends Event>(event: E, handler: Handler<T[E]>): void {
const callback: Handler<T[E]> = (args) => {
off(event, callback)
handler(args)
}

on(event, callback)
}

function off<E extends Event>(event: E, handler: Handler<T[E]>): void {
all.get(event)?.delete(handler)
}

function emit<E extends Event>(event: undefined extends T[E] ? E : never): void
function emit<E extends Event>(event: E, payload: T[E]): void
function emit<E extends Event>(event: E, payload?: T[E]): void {
all.get(event)?.forEach(handler => handler(payload))
}

function clear(): void {
all.clear()
}

return {
on,
off,
once,
emit,
clear,
}
}
56 changes: 56 additions & 0 deletions src/factories/flowRun.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { BitmapText, Container, Graphics } from 'pixi.js'
import { DEFAULT_NODE_CONTAINER_NAME } from '@/consts'
import { nodeBoxFactory } from '@/factories/box'
import { nodeLabelFactory } from '@/factories/label'
import { Pixels } from '@/models/layout'
import { RunGraphNode } from '@/models/RunGraph'
import { waitForConfig } from '@/objects/config'

// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
export async function flowRunContainerFactory() {
const container = new Container()
const { label, render: renderLabel } = await nodeLabelFactory()
const { box, render: renderBox } = await nodeBoxFactory()

container.addChild(box)
container.addChild(label)

container.name = DEFAULT_NODE_CONTAINER_NAME
container.eventMode = 'static'
container.cursor = 'pointer'

async function render(node: RunGraphNode): Promise<Container> {
const label = await renderLabel(node)
const box = await renderBox(node)

label.position = await getLabelPosition(label, box)

return container
}

async function getLabelPosition(label: BitmapText, box: Graphics): Promise<Pixels> {
const config = await waitForConfig()

// todo: this should probably be nodePadding
const margin = config.styles.nodeMargin
const inside = box.width > margin + label.width + margin
const y = box.height / 2 - label.height

if (inside) {
return {
x: margin,
y,
}
}

return {
x: box.width + margin,
y,
}
}

return {
container,
render,
}
}
22 changes: 22 additions & 0 deletions src/factories/label.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { BitmapText } from 'pixi.js'
import { RunGraphNode } from '@/models/RunGraph'
import { waitForFonts } from '@/objects/fonts'

// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
export async function nodeLabelFactory() {
const { inter } = await waitForFonts()
const label = inter('', {
fontSize: 12,
})

async function render(node: RunGraphNode): Promise<BitmapText> {
label.text = node.label

return await label
}

return {
label,
render,
}
}
59 changes: 59 additions & 0 deletions src/factories/node.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { Container, Ticker } from 'pixi.js'
import { flowRunContainerFactory } from '@/factories/flowRun'
import { taskRunContainerFactory } from '@/factories/taskRun'
import { RunGraphNode } from '@/models/RunGraph'

export type NodeContainerFactory = Awaited<ReturnType<typeof nodeContainerFactory>>

// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
export async function nodeContainerFactory(node: RunGraphNode) {
const { container, render: renderNode } = await getNodeFactory(node)
const cacheKey: string | null = null

async function render(node: RunGraphNode): Promise<Container> {
const currentCacheKey = getNodeCacheKey(node)

if (currentCacheKey === cacheKey) {
return container
}

await renderNode(node)

if (!node.end_time) {
Ticker.shared.addOnce(() => render(node))
}

return container
}

// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
async function getNodeFactory(node: RunGraphNode) {
const { kind } = node

switch (kind) {
case 'task-run':
return await taskRunContainerFactory()
case 'flow-run':
return await flowRunContainerFactory()
default:
const exhaustive: never = kind
throw new Error(`switch does not have case for value: ${exhaustive}`)
}
}

function getNodeCacheKey(node: RunGraphNode): string {
const keys = Object.keys(node).sort((keyA, keyB) => keyA.localeCompare(keyB)) as (keyof RunGraphNode)[]
const values = keys.map(key => {
const value = node[key] ?? new Date()

return value.toString()
})

return values.join(',')
}

return {
render,
container,
}
}
Loading

0 comments on commit 9da2bfb

Please sign in to comment.