Skip to content

Commit 91a3058

Browse files
Merge pull request #126 from wttech/signals
Signals for global state
2 parents 857b57c + 1e08c01 commit 91a3058

18 files changed

+133
-127
lines changed

ui.frontend/package-lock.json

Lines changed: 28 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ui.frontend/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"dependencies": {
1515
"@adobe/react-spectrum": "^3.40.1",
1616
"@monaco-editor/react": "^4.6.0",
17+
"@preact/signals-react": "^3.2.0",
1718
"@react-spectrum/toast": "^3.0.1",
1819
"@spectrum-icons/illustrations": "^3.6.20",
1920
"@spectrum-icons/workflow": "^4.2.19",

ui.frontend/src/App.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,7 @@ body {
66
input[type='search'] {
77
box-sizing: border-box !important;
88
}
9+
10+
.coral3-Shell-header {
11+
z-index: 1;
12+
}

ui.frontend/src/App.tsx

Lines changed: 21 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,18 @@
11
import { defaultTheme, Flex, Provider, View } from '@adobe/react-spectrum';
22
import { ToastContainer } from '@react-spectrum/toast';
33
import equal from 'fast-deep-equal';
4-
import { useEffect, useRef, useState } from 'react';
4+
import { useEffect, useRef } from 'react';
55
import { Outlet } from 'react-router-dom';
66
import './App.css';
7-
import { AppContext } from './AppContext';
87
import Footer from './components/Footer';
98
import Header from './components/Header';
9+
import { appState } from './hooks/app.ts';
1010
import router from './router';
1111
import { apiRequest } from './utils/api';
12-
import { InstanceRole, InstanceType, State } from './utils/api.types';
12+
import { State } from './utils/api.types';
1313
import { intervalToTimeout } from './utils/spectrum.ts';
1414

1515
function App() {
16-
const [state, setState] = useState<State>({
17-
spaSettings: {
18-
appStateInterval: 3000,
19-
executionPollInterval: 1000,
20-
scriptStatsLimit: 30,
21-
},
22-
healthStatus: {
23-
healthy: true,
24-
issues: [],
25-
},
26-
mockStatus: {
27-
enabled: true,
28-
},
29-
instanceSettings: {
30-
id: 'default',
31-
timezoneId: 'UTC',
32-
role: InstanceRole.AUTHOR,
33-
type: InstanceType.CLOUD_CONTAINER,
34-
},
35-
});
36-
3716
const isFetching = useRef(false);
3817

3918
useEffect(() => {
@@ -48,16 +27,12 @@ function App() {
4827
operation: 'Fetch application state',
4928
url: '/apps/acm/api/state.json',
5029
method: 'get',
51-
timeout: intervalToTimeout(state.spaSettings.appStateInterval),
30+
timeout: intervalToTimeout(appState.value.spaSettings.appStateInterval),
5231
quiet: true,
5332
});
54-
if (!equal(response.data.data, state)) {
55-
setState((prevState) => {
56-
if (!equal(response.data.data, prevState)) {
57-
return response.data.data;
58-
}
59-
return prevState;
60-
});
33+
const stateNew = response.data.data;
34+
if (!equal(stateNew, appState.value)) {
35+
appState.value = stateNew;
6136
}
6237
} catch (error) {
6338
console.warn('Cannot fetch application state:', error);
@@ -67,10 +42,10 @@ function App() {
6742
};
6843

6944
fetchState();
70-
const intervalId = setInterval(fetchState, state.spaSettings.appStateInterval);
45+
const intervalId = setInterval(fetchState, appState.value.spaSettings.appStateInterval);
7146
return () => clearInterval(intervalId);
7247
// eslint-disable-next-line react-hooks/exhaustive-deps
73-
}, [state.spaSettings.appStateInterval]);
48+
}, [appState.value.spaSettings.appStateInterval]);
7449

7550
return (
7651
<Provider
@@ -84,20 +59,18 @@ function App() {
8459
flexDirection: 'column',
8560
}}
8661
>
87-
<AppContext.Provider value={state}>
88-
<Flex direction="column" flex="1">
89-
<View paddingX="size-200" paddingTop="size-200">
90-
<Header />
91-
</View>
92-
<View paddingX="size-200" flex="1" UNSAFE_style={{ display: 'flex' }}>
93-
<Outlet />
94-
</View>
95-
<View paddingX="size-200" paddingBottom="size-200">
96-
<Footer />
97-
</View>
98-
</Flex>
99-
<ToastContainer />
100-
</AppContext.Provider>
62+
<Flex direction="column" flex="1">
63+
<View paddingX="size-200" paddingTop="size-200">
64+
<Header />
65+
</View>
66+
<View paddingX="size-200" flex="1" UNSAFE_style={{ display: 'flex' }}>
67+
<Outlet />
68+
</View>
69+
<View paddingX="size-200" paddingBottom="size-200">
70+
<Footer />
71+
</View>
72+
</Flex>
73+
<ToastContainer />
10174
</Provider>
10275
);
10376
}

ui.frontend/src/AppContext.tsx

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

ui.frontend/src/components/ImmersiveEditor.tsx renamed to ui.frontend/src/components/CodeEditor.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { debounce } from '../utils/debounce';
99
import { modelStorage } from '../utils/modelStorage';
1010
import { registerGroovyLanguage } from '../utils/monaco/groovy';
1111

12-
type ImmersiveEditorProps<C extends ColorVersion> = editor.IStandaloneEditorConstructionOptions & {
12+
type CodeEditorProps<C extends ColorVersion> = editor.IStandaloneEditorConstructionOptions & {
1313
id: string;
1414
scrollToBottomOnUpdate?: boolean;
1515
initialValue?: string;
@@ -21,7 +21,7 @@ type ImmersiveEditorProps<C extends ColorVersion> = editor.IStandaloneEditorCons
2121
const SaveViewStateDebounce = 1000;
2222
const SuggestWidgetHeight = 480;
2323

24-
const ImmersiveEditor = <C extends ColorVersion>({ containerProps, syntaxError, onChange, id, language, value, initialValue, readOnly, scrollToBottomOnUpdate, ...props }: ImmersiveEditorProps<C>) => {
24+
const CodeEditor = <C extends ColorVersion>({ containerProps, syntaxError, onChange, id, language, value, initialValue, readOnly, scrollToBottomOnUpdate, ...props }: CodeEditorProps<C>) => {
2525
const [isOpen, setIsOpen] = useState(false);
2626
const monacoRef = useMonaco();
2727
const containerRef = useRef<HTMLDivElement>(null);
@@ -169,4 +169,4 @@ const ImmersiveEditor = <C extends ColorVersion>({ containerProps, syntaxError,
169169
);
170170
};
171171

172-
export default ImmersiveEditor;
172+
export default CodeEditor;

ui.frontend/src/components/ExecutionAbortButton.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Button, Text } from '@adobe/react-spectrum';
22
import { ToastQueue } from '@react-spectrum/toast';
33
import Cancel from '@spectrum-icons/workflow/Cancel';
44
import React, { useState } from 'react';
5-
import { useAppState } from '../hooks/app.ts';
5+
import { appState } from '../hooks/app.ts';
66
import { pollExecutionPending } from '../hooks/execution';
77
import { apiRequest } from '../utils/api';
88
import { Execution, ExecutionStatus, isExecutionPending, QueueOutput } from '../utils/api.types';
@@ -14,7 +14,6 @@ interface ExecutionAbortButtonProps {
1414
}
1515

1616
const ExecutionAbortButton: React.FC<ExecutionAbortButtonProps> = ({ execution, onComplete }) => {
17-
const appState = useAppState();
1817
const [isAborting, setIsAborting] = useState(false);
1918

2019
const onAbort = async () => {
@@ -30,7 +29,7 @@ const ExecutionAbortButton: React.FC<ExecutionAbortButtonProps> = ({ execution,
3029
method: 'delete',
3130
});
3231

33-
const queuedExecution = await pollExecutionPending(execution.id, appState.spaSettings.executionPollInterval);
32+
const queuedExecution = await pollExecutionPending(execution.id, appState.value.spaSettings.executionPollInterval);
3433
if (queuedExecution?.status === ExecutionStatus.ABORTED) {
3534
ToastQueue.positive('Code execution aborted successfully!', {
3635
timeout: ToastTimeoutQuick,

ui.frontend/src/components/HealthChecker.tsx

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,10 @@ import Close from '@spectrum-icons/workflow/Close';
55
import Help from '@spectrum-icons/workflow/Help';
66
import Replay from '@spectrum-icons/workflow/Replay';
77
import Settings from '@spectrum-icons/workflow/Settings';
8-
import { useAppState } from '../hooks/app.ts';
8+
import { appState, healthIssues } from '../hooks/app.ts';
99
import { HealthIssueSeverity, instancePrefix, InstanceType } from '../utils/api.types';
1010

1111
const HealthChecker = () => {
12-
const appState = useAppState();
13-
const healthIssues = appState.healthStatus.issues;
14-
1512
const getSeverityVariant = (severity: HealthIssueSeverity): 'negative' | 'yellow' | 'neutral' => {
1613
switch (severity) {
1714
case HealthIssueSeverity.CRITICAL:
@@ -37,15 +34,15 @@ const HealthChecker = () => {
3734
<Flex flex="1" alignItems="center">
3835
<Button
3936
variant="negative"
40-
isDisabled={appState.instanceSettings.type === InstanceType.CLOUD_CONTAINER}
37+
isDisabled={appState.value.instanceSettings.type === InstanceType.CLOUD_CONTAINER}
4138
onPress={() => window.open(`${instancePrefix}/system/console/configMgr/dev.vml.es.acm.core.instance.HealthChecker`, '_blank')}
4239
>
4340
<Settings />
4441
<Text>Configure</Text>
4542
</Button>
4643
</Flex>
4744
<Flex flex="1" justifyContent="center" alignItems="center">
48-
<StatusLight variant={healthIssues.length === 0 ? 'positive' : 'negative'}>{healthIssues.length === 0 ? <>Healthy</> : <>Unhealthy &mdash; {healthIssues.length} issue(s)</>}</StatusLight>
45+
<StatusLight variant={healthIssues.value.length === 0 ? 'positive' : 'negative'}>{healthIssues.value.length === 0 ? <>Healthy</> : <>Unhealthy &mdash; {healthIssues.value.length} issue(s)</>}</StatusLight>
4946
</Flex>
5047
<Flex flex="1" justifyContent="end" alignItems="center">
5148
<DialogTrigger>
@@ -88,7 +85,7 @@ const HealthChecker = () => {
8885
<Column>Message</Column>
8986
</TableHeader>
9087
<TableBody>
91-
{(healthIssues || []).map((issue, index) => (
88+
{(healthIssues.value || []).map((issue, index) => (
9289
<Row key={index}>
9390
<Cell>{index + 1}</Cell>
9491
<Cell>

ui.frontend/src/components/ScriptExecutor.tsx

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import Replay from '@spectrum-icons/workflow/Replay';
1212
import Settings from '@spectrum-icons/workflow/Settings';
1313
import { useEffect, useRef, useState } from 'react';
1414
import { useNavigate } from 'react-router-dom';
15-
import { useAppState } from '../hooks/app.ts';
15+
import { appState } from '../hooks/app.ts';
1616
import { apiRequest } from '../utils/api.ts';
1717
import { ExecutionOutput, ExecutionSummary, InstanceType } from '../utils/api.types.ts';
1818
import { isProduction } from '../utils/node';
@@ -26,8 +26,6 @@ const ScriptExecutor = () => {
2626
const prefix = isProduction() ? '' : 'http://localhost:4502';
2727
const navigate = useNavigate();
2828

29-
const appState = useAppState();
30-
3129
const [executions, setExecutions] = useState<ExecutionSummary[]>([]);
3230
const isFetching = useRef(false);
3331
useEffect(() => {
@@ -41,7 +39,7 @@ const ScriptExecutor = () => {
4139
operation: 'Fetch queued executions',
4240
url: '/apps/acm/api/execution.json?queued=true&format=summary',
4341
method: 'get',
44-
timeout: intervalToTimeout(appState.spaSettings.appStateInterval),
42+
timeout: intervalToTimeout(appState.value.spaSettings.appStateInterval),
4543
quiet: true,
4644
});
4745
setExecutions(response.data.data.list);
@@ -52,9 +50,9 @@ const ScriptExecutor = () => {
5250
}
5351
};
5452
fetchExecutions();
55-
const intervalId = setInterval(fetchExecutions, appState.spaSettings.appStateInterval);
53+
const intervalId = setInterval(fetchExecutions, appState.value.spaSettings.appStateInterval);
5654
return () => clearInterval(intervalId);
57-
}, [appState.spaSettings.appStateInterval]);
55+
}, []);
5856

5957
console.log('executions', executions);
6058

@@ -82,7 +80,7 @@ const ScriptExecutor = () => {
8280
<ButtonGroup>
8381
<ExecutionsAbortButton selectedKeys={selectedIds(selectedKeys)} />
8482
<MenuTrigger>
85-
<Button variant="negative" isDisabled={appState.instanceSettings.type === InstanceType.CLOUD_CONTAINER}>
83+
<Button variant="negative" isDisabled={appState.value.instanceSettings.type === InstanceType.CLOUD_CONTAINER}>
8684
<Settings />
8785
<Text>Configure</Text>
8886
</Button>

ui.frontend/src/components/ScriptListRich.tsx

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import NotFound from '@spectrum-icons/illustrations/NotFound';
44
import Magnify from '@spectrum-icons/workflow/Magnify';
55
import React, { useCallback, useEffect, useState } from 'react';
66
import { useNavigate } from 'react-router-dom';
7-
import { useAppState } from '../hooks/app';
7+
import { appState } from '../hooks/app';
88
import { useFormatter } from '../hooks/formatter';
99
import { toastRequest } from '../utils/api';
1010
import { InstanceRole, isExecutionNegative, ScriptOutput, ScriptType } from '../utils/api.types';
@@ -21,7 +21,6 @@ type ScriptListRichProps = {
2121
};
2222

2323
const ScriptListRich: React.FC<ScriptListRichProps> = ({ type }) => {
24-
const appState = useAppState();
2524
const navigate = useNavigate();
2625
const formatter = useFormatter();
2726

@@ -78,14 +77,14 @@ const ScriptListRich: React.FC<ScriptListRichProps> = ({ type }) => {
7877
{type === ScriptType.ENABLED || type === ScriptType.DISABLED ? (
7978
<>
8079
<ScriptToggleButton type={type} selectedKeys={selectedIds(selectedKeys)} onToggle={loadScripts} />
81-
{appState.instanceSettings.role == InstanceRole.AUTHOR && <ScriptSynchronizeButton selectedKeys={selectedIds(selectedKeys)} onSync={loadScripts} />}
80+
{appState.value.instanceSettings.role == InstanceRole.AUTHOR && <ScriptSynchronizeButton selectedKeys={selectedIds(selectedKeys)} onSync={loadScripts} />}
8281
</>
8382
) : null}
8483
</ButtonGroup>
8584
</Flex>
8685
<Flex flex="1" justifyContent="center" alignItems="center">
87-
<StatusLight variant={appState.healthStatus.healthy ? 'positive' : 'negative'}>
88-
{appState.healthStatus.healthy ? (
86+
<StatusLight variant={appState.value.healthStatus.healthy ? 'positive' : 'negative'}>
87+
{appState.value.healthStatus.healthy ? (
8988
<Text>Executor active</Text>
9089
) : (
9190
<>
@@ -119,14 +118,14 @@ const ScriptListRich: React.FC<ScriptListRichProps> = ({ type }) => {
119118
<Text>Average Duration</Text>
120119
<ContextualHelp variant="info">
121120
<Heading>Explanation</Heading>
122-
<Content>Duration is calculated based on the last {appState.spaSettings.scriptStatsLimit} completed executions (only succeeded or failed).</Content>
121+
<Content>Duration is calculated based on the last {appState.value.spaSettings.scriptStatsLimit} completed executions (only succeeded or failed).</Content>
123122
</ContextualHelp>
124123
</Column>
125124
<Column width="2fr" align="end">
126125
<Text>Success Rate</Text>
127126
<ContextualHelp variant="info">
128127
<Heading>Explanation</Heading>
129-
<Content>Success rate is calculated based on the last {appState.spaSettings.scriptStatsLimit} completed executions (only succeeded or failed).</Content>
128+
<Content>Success rate is calculated based on the last {appState.value.spaSettings.scriptStatsLimit} completed executions (only succeeded or failed).</Content>
130129
</ContextualHelp>
131130
</Column>
132131
</TableHeader>

0 commit comments

Comments
 (0)