Give a connector node an icon that is not a code icon
A device read over a proprietary protocol was showing the fallback used for a function node. A connector's type cannot be in the built-in icon map — that is the point of it being a connector — so they share a plug. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011LF61rxW1FG5YCD2J9YqjY
This commit is contained in:
@@ -0,0 +1,41 @@
|
|||||||
|
/** Screenshot the dashboard and the flow canvas as a user will find them. */
|
||||||
|
import { mkdir } from "node:fs/promises"
|
||||||
|
import { chromium } from "@playwright/test"
|
||||||
|
|
||||||
|
const APP_URL = process.env.APP_URL || "http://app.localhost"
|
||||||
|
const OUT = process.env.SCREENSHOT_DIR || "screenshots"
|
||||||
|
const browser = await chromium.launch()
|
||||||
|
|
||||||
|
for (const theme of ["light", "dark"]) {
|
||||||
|
const dir = `${OUT}/${theme}`
|
||||||
|
await mkdir(dir, { recursive: true })
|
||||||
|
const context = await browser.newContext({
|
||||||
|
viewport: { width: 1440, height: 900 },
|
||||||
|
colorScheme: theme,
|
||||||
|
})
|
||||||
|
await context.addInitScript((t) => {
|
||||||
|
localStorage.setItem("fluksio-ui-theme", t)
|
||||||
|
}, theme)
|
||||||
|
const page = await context.newPage()
|
||||||
|
|
||||||
|
await page.goto(`${APP_URL}/login`, { waitUntil: "networkidle" })
|
||||||
|
await page.getByTestId("email-input").fill(process.env.FIRST_SUPERUSER)
|
||||||
|
await page
|
||||||
|
.getByTestId("password-input")
|
||||||
|
.fill(process.env.FIRST_SUPERUSER_PASSWORD)
|
||||||
|
await page.getByRole("button", { name: /log in/i }).click()
|
||||||
|
await page.waitForURL(`${APP_URL}/`, { timeout: 15000 })
|
||||||
|
|
||||||
|
await page.goto(`${APP_URL}/dashboards/aircon`, { waitUntil: "networkidle" })
|
||||||
|
await page.waitForTimeout(2500)
|
||||||
|
await page.screenshot({ path: `${dir}/live-dashboard.png` })
|
||||||
|
|
||||||
|
await page.goto(`${APP_URL}/flows/aircon`, { waitUntil: "networkidle" })
|
||||||
|
await page.keyboard.press("Escape")
|
||||||
|
await page.waitForTimeout(2500)
|
||||||
|
await page.screenshot({ path: `${dir}/live-flow.png` })
|
||||||
|
|
||||||
|
console.log(` ${theme}: done`)
|
||||||
|
await context.close()
|
||||||
|
}
|
||||||
|
await browser.close()
|
||||||
@@ -303,6 +303,18 @@ function FlowEditorInner({
|
|||||||
[nodeTypeInfo],
|
[nodeTypeInfo],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Which types came from an installed connector rather than the engine. They
|
||||||
|
// cannot be in the icon map, so they share one.
|
||||||
|
const pluginTypes = useMemo(
|
||||||
|
() =>
|
||||||
|
new Set(
|
||||||
|
(nodeTypeInfo ?? [])
|
||||||
|
.filter((info) => info.plugin)
|
||||||
|
.map((info) => info.type),
|
||||||
|
),
|
||||||
|
[nodeTypeInfo],
|
||||||
|
)
|
||||||
|
|
||||||
const issuesByNode = useMemo(() => {
|
const issuesByNode = useMemo(() => {
|
||||||
const map = new Map<string, string[]>()
|
const map = new Map<string, string[]>()
|
||||||
for (const issue of issues) {
|
for (const issue of issues) {
|
||||||
@@ -328,11 +340,20 @@ function FlowEditorInner({
|
|||||||
flow: flowName,
|
flow: flowName,
|
||||||
typeLabel:
|
typeLabel:
|
||||||
typeLabels.get(definition?.type ?? "") ?? definition?.type ?? "",
|
typeLabels.get(definition?.type ?? "") ?? definition?.type ?? "",
|
||||||
|
isPlugin: pluginTypes.has(definition?.type ?? ""),
|
||||||
issueText: nodeIssues.join("\n"),
|
issueText: nodeIssues.join("\n"),
|
||||||
} satisfies FlowNodeData,
|
} satisfies FlowNodeData,
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
[canvasNodes, definitions, flowName, issuesByNode, selectedId, typeLabels],
|
[
|
||||||
|
canvasNodes,
|
||||||
|
definitions,
|
||||||
|
flowName,
|
||||||
|
issuesByNode,
|
||||||
|
pluginTypes,
|
||||||
|
selectedId,
|
||||||
|
typeLabels,
|
||||||
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
// A cheap fingerprint of the wiring: it changes when a name does, but not
|
// A cheap fingerprint of the wiring: it changes when a name does, but not
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
Globe,
|
Globe,
|
||||||
Merge,
|
Merge,
|
||||||
Play,
|
Play,
|
||||||
|
Plug,
|
||||||
Radio,
|
Radio,
|
||||||
Shuffle,
|
Shuffle,
|
||||||
Split,
|
Split,
|
||||||
@@ -58,6 +59,7 @@ export type FlowNodeData = {
|
|||||||
definition: NodeDef_Input
|
definition: NodeDef_Input
|
||||||
flow: string
|
flow: string
|
||||||
typeLabel: string
|
typeLabel: string
|
||||||
|
isPlugin?: boolean
|
||||||
issueText: string
|
issueText: string
|
||||||
[key: string]: unknown
|
[key: string]: unknown
|
||||||
}
|
}
|
||||||
@@ -101,10 +103,15 @@ function PortHandles({
|
|||||||
}
|
}
|
||||||
|
|
||||||
function FlowNodeComponent({ data, selected }: NodeProps) {
|
function FlowNodeComponent({ data, selected }: NodeProps) {
|
||||||
const { definition, flow, typeLabel, issueText } = data as FlowNodeData
|
const { definition, flow, typeLabel, isPlugin, issueText } =
|
||||||
|
data as FlowNodeData
|
||||||
const live = useNodeStatus(`${flow}.${definition.id}`)
|
const live = useNodeStatus(`${flow}.${definition.id}`)
|
||||||
const emits = useNodeEmits(`${flow}.${definition.id}`)
|
const emits = useNodeEmits(`${flow}.${definition.id}`)
|
||||||
const Icon = NODE_ICONS[definition.type as keyof typeof NODE_ICONS] ?? Code2
|
const Icon =
|
||||||
|
NODE_ICONS[definition.type as keyof typeof NODE_ICONS] ??
|
||||||
|
// A connector's own type cannot be in the map above, and a device is
|
||||||
|
// not a piece of code.
|
||||||
|
(isPlugin ? Plug : Code2)
|
||||||
|
|
||||||
// Whatever is wrong — it failed to load, it failed to run, or the graph
|
// Whatever is wrong — it failed to load, it failed to run, or the graph
|
||||||
// around it does not add up — is the same red dot with the same explanation.
|
// around it does not add up — is the same red dot with the same explanation.
|
||||||
|
|||||||
Reference in New Issue
Block a user