-
Notifications
You must be signed in to change notification settings - Fork 586
/
Copy pathindex.tsx
199 lines (181 loc) · 6.67 KB
/
index.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
import {ComponentPropsWithRef, useEffect, useState} from 'react'
import {useNavigate} from 'react-router-dom'
import {map} from 'remeda'
import {toast} from '@/components/ui/toast'
import {useLaunchApp} from '@/hooks/use-launch-app'
import {temperatureDescriptionsKeyed, useTemperatureUnit} from '@/hooks/use-temperature-unit'
import {
DEFAULT_REFRESH_MS,
ExampleWidgetConfig,
RegistryWidget,
WidgetConfig,
WidgetType,
} from '@/modules/widgets/shared/constants'
import {useApps} from '@/providers/apps'
import {trpcReact} from '@/trpc/trpc'
import {celciusToFahrenheit} from '@/utils/temperature'
import {FourStatsWidget} from './four-stats-widget'
import {ListEmojiWidget} from './list-emoji-widget'
import {ListWidget} from './list-widget'
import {WidgetContainer} from './shared/shared'
import {TextWithButtonsWidget} from './text-with-buttons-widget'
import {TextWithProgressWidget} from './text-with-progress-widget'
import {ThreeStatsWidget} from './three-stats-widget'
import {TwoStatsWidget} from './two-stats-with-guage-widget'
export function Widget({appId, config: manifestConfig}: {appId: string; config: RegistryWidget}) {
// TODO: find a way to use `useApp()` to be cleaner
const {userAppsKeyed, systemAppsKeyed, isLoading: isLoadingApps} = useApps()
const app = userAppsKeyed?.[appId]
// const finalEndpointUrl = urlJoin(appToUrlWithAppPath(app), config.endpoint);
const [refetchInterval, setRefetchInterval] = useState(manifestConfig.refresh ?? DEFAULT_REFRESH_MS)
const widgetQ = trpcReact.widget.data.useQuery(
{widgetId: manifestConfig.id},
{
retry: false,
// We do want refetching to happen on a schedule though
refetchInterval,
},
)
// Update the refetch interval based on the widget config, not the manifest config
// This makes the widget refresh interval dynamic
const widgetConfigRefresh = (widgetQ.data as WidgetConfig)?.refresh
useEffect(() => {
if (!widgetConfigRefresh) return
setRefetchInterval(widgetConfigRefresh)
}, [widgetConfigRefresh])
const navigate = useNavigate()
const launchApp = useLaunchApp()
const isLoading = isLoadingApps || widgetQ.isLoading
const handleClick = (link?: string) => {
if (appId === 'live-usage' && systemAppsKeyed['UMBREL_live-usage']) {
navigate(link || '?dialog=live-usage')
} else if (app) {
// Launching directly because it's weird to have credentials show up
// Users will likely open the app by clicking the icon before adding a widget associated with the app
launchApp(appId, {path: link, direct: true})
} else {
toast.error(`App "${appId}" not found.`)
}
}
if (isLoading || widgetQ.isError) return <LoadingWidget type={manifestConfig.type} onClick={handleClick} />
const widget = widgetQ.data as WidgetConfig
switch (manifestConfig.type) {
case 'text-with-buttons': {
const w = widget as WidgetConfig<'text-with-buttons'>
return <TextWithButtonsWidget {...w} onClick={handleClick} />
}
case 'text-with-progress': {
const w = widget as WidgetConfig<'text-with-progress'>
return <TextWithProgressWidget {...w} onClick={handleClick} />
}
case 'two-stats-with-guage': {
const w = widget as WidgetConfig<'two-stats-with-guage'>
return <TwoStatsWidget {...w} onClick={handleClick} />
}
case 'three-stats': {
const w = widget as WidgetConfig<'three-stats'>
// TODO: figure out how to show the user's desired temp unit in a way that isn't brittle
if (manifestConfig.id === 'umbrel:system-statss') {
return <SystemThreeUpWidget {...w} onClick={handleClick} />
}
return <ThreeStatsWidget {...w} onClick={handleClick} />
}
case 'four-stats': {
const w = widget as WidgetConfig<'four-stats'>
return <FourStatsWidget {...w} onClick={handleClick} />
}
case 'list': {
const w = widget as WidgetConfig<'list'>
return <ListWidget {...w} onClick={handleClick} />
}
case 'list-emoji': {
const w = widget as WidgetConfig<'list-emoji'>
return <ListEmojiWidget {...w} onClick={handleClick} />
}
}
}
// Hacky way to get the right temperature unit based on user preferences
export function SystemThreeUpWidget({items, ...props}: ComponentPropsWithRef<typeof ThreeStatsWidget>) {
const [temperatureUnit] = useTemperatureUnit()
if (!items) return <ErrorWidget error='No data.' />
const modifiedItems = map.strict(items, (item) => {
if (!item.text?.includes('℃')) return item
const celciusNumber = parseInt(item.text.replace('℃', ''))
const temperatureNumber = temperatureUnit === 'f' ? celciusToFahrenheit(celciusNumber) : celciusNumber
const temperatureUnitLabel = temperatureDescriptionsKeyed[temperatureUnit].label
const newValue = temperatureNumber + temperatureUnitLabel
return {...item, text: newValue}
})
return <ThreeStatsWidget items={modifiedItems} {...props} />
}
export function ExampleWidget<T extends WidgetType = WidgetType>({
type,
example,
}: {
type: T
example?: ExampleWidgetConfig<T>
}) {
switch (type) {
case 'text-with-buttons': {
const w = example as WidgetConfig<'text-with-buttons'>
const widgetWithButtonLinks = {
...w,
// Link to nowhere
buttons: w.buttons?.map((button) => ({...button, link: ''})),
}
return <TextWithButtonsWidget {...widgetWithButtonLinks} />
}
case 'text-with-progress': {
const w = example as WidgetConfig<'text-with-progress'>
return <TextWithProgressWidget {...w} />
}
case 'two-stats-with-guage': {
const w = example as WidgetConfig<'two-stats-with-guage'>
return <TwoStatsWidget {...w} />
}
case 'three-stats': {
const w = example as WidgetConfig<'three-stats'>
return <ThreeStatsWidget {...w} />
}
case 'four-stats': {
const w = example as WidgetConfig<'four-stats'>
return <FourStatsWidget {...w} />
}
case 'list': {
const w = example as WidgetConfig<'list'>
return <ListWidget {...w} />
}
case 'list-emoji': {
const w = example as WidgetConfig<'list-emoji'>
return <ListEmojiWidget {...w} />
}
}
}
export function LoadingWidget<T extends WidgetType = WidgetType>({type, onClick}: {type: T; onClick?: () => void}) {
switch (type) {
case 'text-with-buttons': {
return <TextWithButtonsWidget onClick={onClick} />
}
case 'text-with-progress': {
return <TextWithProgressWidget onClick={onClick} />
}
case 'two-stats-with-guage': {
return <TwoStatsWidget onClick={onClick} />
}
case 'three-stats': {
return <ThreeStatsWidget onClick={onClick} />
}
case 'four-stats': {
return <FourStatsWidget onClick={onClick} />
}
case 'list': {
return <ListWidget onClick={onClick} />
}
case 'list-emoji': {
return <ListEmojiWidget onClick={onClick} />
}
}
}
export function ErrorWidget({error}: {error: string}) {
return <WidgetContainer className='p-5 text-12 text-destructive2-lightest'>{error}</WidgetContainer>
}