Skip to content

Commit f92b341

Browse files
Merge pull request #93 from Digital-Alchemy-TS/client/header
Client/header
2 parents dc9467f + df393c1 commit f92b341

43 files changed

Lines changed: 751 additions & 404 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Dockerfile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ FROM node:22-bookworm-slim
44
ENV CI=1
55
ENV EXPO_NO_TELEMETRY=1
66
ENV PORT=3789
7+
# Set UTF-8 locale to support emoji and special characters in filenames
8+
ENV LANG=en_US.UTF-8
9+
ENV LC_ALL=en_US.UTF-8
710
# Don't set NODE_ENV=production yet - we need devDependencies for build
811

912
# Install system dependencies needed for building native modules

apps/client/app.config.ts

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,4 @@
1-
import {
2-
type GenericParserBuilder,
3-
parseAsInteger,
4-
parseAsString,
5-
type UseQueryStateOptions,
6-
} from "nuqs"
1+
import { type GenericParserBuilder, parseAsString } from "nuqs"
72

83
import { codeGlueLight } from "./src/design/editorThemes"
94

@@ -41,11 +36,12 @@ export const appConfig = {
4136
| StringLiteralUnion<BundledLanguage, string>
4237
)[],
4338
},
39+
logs: {
40+
defaultFontSize: 14,
41+
font: "Monaspace Krypton",
42+
},
4443
routes: {
45-
home: { title: "Home" },
46-
logs: {
47-
title: "Logs",
48-
},
44+
home: { title: "Logs" }, // home is a special route name that will show without params in the URL
4945
automations: {
5046
title: "Automations",
5147
queryStrings: {

apps/client/public/headerLogo.png

12.1 KB
Loading

apps/client/src/components/AutomationDetail/Footer.tsx

Lines changed: 0 additions & 15 deletions
This file was deleted.

apps/client/src/components/AutomationLogs/index.tsx

Lines changed: 0 additions & 96 deletions
This file was deleted.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { Row, useTheme } from "@code-glue/paradigm"
2+
3+
export const Header = ({ children }: { children: React.ReactNode }) => {
4+
const theme = useTheme()
5+
return (
6+
<Row
7+
noShrink
8+
align="center"
9+
justify="space-between"
10+
color={theme.cardStock}
11+
borderBottomStyle="solid"
12+
borderBottomColor={theme.uiStroke}
13+
borderBottomWidth="$thinStroke"
14+
height="$headerHeight"
15+
px="$edgeInset"
16+
>
17+
{children}
18+
</Row>
19+
)
20+
}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import { useCallback, useEffect, useRef } from "react"
2+
3+
import { ScrollView, type ScrollViewRef, Text } from "@code-glue/paradigm"
4+
import { formatAutomationContext } from "@code-glue/server/utils/helpers/format.mts"
5+
import {
6+
LogLevel,
7+
type LogLine,
8+
useAutomationLogs,
9+
} from "@/hooks/useAutomationLogs"
10+
import { appConfig } from "../../app.config"
11+
import { useAutomation } from "../hooks/useAutomation"
12+
13+
import type React from "react"
14+
import type { ScrollView as RNScrollView } from "react-native"
15+
16+
type LogsProps = {
17+
/**
18+
* filter to just show logs for a given automation ID
19+
*/
20+
automationId?: string
21+
/**
22+
* level to filter logs by.
23+
* Defaults to LogLevel.trace
24+
*/
25+
level?: LogLevel
26+
27+
/** Optional ref for parent access to ScrollView methods */
28+
ref: React.Ref<ScrollViewRef>
29+
}
30+
31+
const logKey = (log: LogLine) =>
32+
`${log.timestamp}-${log.level}-${log.context}-${log.msg}`
33+
34+
const LONGEST_LEVEL_STRING = Math.max(
35+
...Object.values(LogLevel).map((level) => level.length),
36+
)
37+
38+
const getLogLevelPadding = (level: LogLevel) =>
39+
" ".repeat(Math.max(0, LONGEST_LEVEL_STRING - level.length))
40+
41+
export const Logs = ({
42+
automationId,
43+
level = LogLevel.trace,
44+
ref,
45+
}: LogsProps) => {
46+
const { automationSnapshot: automation } = useAutomation(automationId)
47+
48+
const loggerContext = automationId
49+
? formatAutomationContext(automation.title)
50+
: undefined
51+
52+
const options: Parameters<typeof useAutomationLogs>[0] = {
53+
enabled: true,
54+
level,
55+
}
56+
57+
if (loggerContext) {
58+
options.context = loggerContext
59+
}
60+
61+
const { logs, isLoading, error } = useAutomationLogs(options)
62+
63+
useEffect(() => {
64+
if (!isLoading && logs.length > 0 && ref?.current) {
65+
// Ensure layout/paint has occurred before scrolling
66+
requestAnimationFrame(() => {
67+
ref.current?.scrollToEnd?.({ animated: true })
68+
})
69+
}
70+
}, [logs, isLoading, ref?.current])
71+
72+
if (isLoading) {
73+
return <div>Loading logs...</div>
74+
}
75+
76+
if (error) {
77+
return <div>Error: {error.message}</div>
78+
}
79+
80+
return (
81+
<ScrollView ref={ref} scrollBehavior="smooth">
82+
{logs.map((log) => (
83+
<Text
84+
style={Text.style.footnote}
85+
key={logKey(log)}
86+
_style={{ fontFamily: appConfig.logs.font }}
87+
color={log.isHistorical ? "$colorDisabled" : "$color"}
88+
>
89+
<Text
90+
color={!log.isHistorical && getLevelColor(log.level)}
91+
letterCase={Text.letterCase.upper}
92+
>
93+
{getLogLevelPadding(log.level)}[{log.level}]
94+
</Text>
95+
<Text>{new Date(log.timestamp).toLocaleTimeString()}</Text>
96+
<Text> {log.msg}</Text>
97+
</Text>
98+
))}
99+
</ScrollView>
100+
)
101+
}
102+
103+
function getLevelColor(level: string): string {
104+
switch (level) {
105+
case "error":
106+
case "fatal":
107+
return "red"
108+
case "warn":
109+
return "orange"
110+
case "info":
111+
return "blue"
112+
case "debug":
113+
return "gray"
114+
case "trace":
115+
return "lightgray"
116+
default:
117+
return "black"
118+
}
119+
}

apps/client/src/hooks/useAutomation.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,21 @@ import { proxy, useSnapshot } from "valtio"
33
import { emptyAutomation, store } from "@/store"
44
import { useRouter } from "./useRouter"
55

6-
export const useCurrentAutomation = () => {
7-
const [{ automationId }, navigateTo] = useRouter()
8-
9-
const currentAutomation = automationId
6+
export const useAutomation = (automationId?: string | null) => {
7+
const automation = automationId
108
? store.automations.get(automationId)
119
: undefined
1210

13-
const automationSnapshot = useSnapshot(
14-
currentAutomation || proxy(emptyAutomation),
15-
)
11+
const automationSnapshot = useSnapshot(automation || proxy(emptyAutomation))
12+
13+
return { automation, automationSnapshot }
14+
}
15+
16+
export const useCurrentAutomation = () => {
17+
const [{ automationId }, navigateTo] = useRouter()
18+
19+
const { automation: currentAutomation, automationSnapshot } =
20+
useAutomation(automationId)
1621

1722
// If the given ID isn't found on the server, fallback to the index page
1823
if (automationId && currentAutomation === undefined) navigateTo("home")

0 commit comments

Comments
 (0)