Skip to content

Commit 1dc869d

Browse files
author
Greg Harris
committed
Merge branch 'master' into rgl-action-to-get-component
2 parents 8aa04f6 + 3446711 commit 1dc869d

6 files changed

Lines changed: 225 additions & 50 deletions

File tree

src/mui-material.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ declare module "@mui/material/styles" {
132132
interface Theme {
133133
customName?: string;
134134
borders: {
135+
[key: string]: BorderDef;
135136
default: BorderDef;
136137
dynamictabs: BorderDef;
137138
linearmeter: BorderDef;
@@ -145,6 +146,7 @@ declare module "@mui/material/styles" {
145146
interface ThemeOptions {
146147
customName?: string;
147148
borders?: {
149+
[key: string]: Partial<BorderDef>;
148150
default?: Partial<BorderDef>;
149151
dynamictabs?: Partial<BorderDef>;
150152
linearmeter?: Partial<BorderDef>;

src/phoebusTheme.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,12 @@ export const phoebusTheme = createTheme({
149149
borderColor: "lightgrey",
150150
borderRadius: "0"
151151
},
152+
input: {
153+
borderStyle: "solid",
154+
borderWidth: "0px",
155+
borderColor: "#000000",
156+
borderRadius: 0
157+
},
152158
linearmeter: {
153159
borderStyle: "solid",
154160
borderWidth: "1px",

src/ui/hooks/useClassFile.test.tsx

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,29 @@ import { contextRender, createRootStoreState } from "../../testResources";
33
import { vi } from "vitest";
44
import { act, screen } from "@testing-library/react";
55
import { ensureWidgetsRegistered } from "../widgets";
6-
import { useClassFile } from "./useClassFile";
6+
import { extractThemeProps, useClassFile } from "./useClassFile";
77
import { CsWebLibConfig } from "../../redux";
88
import { phoebusTheme } from "../../phoebusTheme";
99
import { createTheme } from "@mui/material";
1010
import { CsState } from "../../redux/csState";
11+
import { WidgetDescription } from "../widgets/createComponent";
12+
import { newAbsolutePosition } from "../../types/position";
13+
import { newFont } from "../../types/font";
14+
import { newColor } from "../../types/color";
1115

1216
ensureWidgetsRegistered();
1317

18+
const CLASS_WIDGET: WidgetDescription = {
19+
type: "label",
20+
id: "123",
21+
fileId: "AShapeFilePath",
22+
name: "MY_CLASS",
23+
font: newFont(20),
24+
position: newAbsolutePosition("0", "0", "0", "0"),
25+
backgroundColor: newColor("rgba(56,206,56,1)"),
26+
foregroundColor: newColor("rgba(29,41,69,1)")
27+
};
28+
1429
function getFileState(): CsState {
1530
return {
1631
valueCache: {},
@@ -84,6 +99,15 @@ describe("useClassFile", (): void => {
8499
</color>
85100
</background_color>
86101
<tooltip>$(actions)</tooltip>
102+
<font use_class="true">
103+
<font family="Montserrat" style="REGULAR" size="8.0">
104+
</font>
105+
</font>
106+
<border_width use_class="true">3</border_width>
107+
<border_color use_class="true">
108+
<color name="INVALID" red="255" green="0" blue="255">
109+
</color>
110+
</border_color>
87111
</widget>
88112
</display>`;
89113
const mockJsonPromise = Promise.resolve(mockSuccessResponse);
@@ -124,3 +148,60 @@ describe("useClassFile", (): void => {
124148
).toBeInTheDocument();
125149
});
126150
});
151+
152+
describe("extractThemeProps", (): void => {
153+
it("returns nothing if no props in list match", (): void => {
154+
const matches = extractThemeProps(
155+
CLASS_WIDGET,
156+
new Set(["offColor", "onColor"]),
157+
value => value.colorString
158+
);
159+
expect(matches).toEqual({});
160+
});
161+
it("returns nothing if the map function is wrong", (): void => {
162+
const matches = extractThemeProps(
163+
CLASS_WIDGET,
164+
new Set(["font"]),
165+
value => value.colorString
166+
);
167+
expect(matches).toEqual({});
168+
});
169+
it("filters out undefined values", (): void => {
170+
const newClassWidget = {
171+
...CLASS_WIDGET,
172+
backgroundColor: { colorString: undefined }
173+
};
174+
const matches = extractThemeProps(
175+
newClassWidget,
176+
new Set(["backgroundColor", "foregroundColor"]),
177+
value => value.colorString
178+
);
179+
expect(matches).toEqual({ contrastText: "rgba(29,41,69,1)" });
180+
});
181+
it("returns a list of matches that are correctly mapped", (): void => {
182+
const matches = extractThemeProps(
183+
CLASS_WIDGET,
184+
new Set(["backgroundColor", "foregroundColor"]),
185+
value => value.colorString
186+
);
187+
expect(matches).toEqual({
188+
contrastText: "rgba(29,41,69,1)",
189+
main: "rgba(56,206,56,1)"
190+
});
191+
});
192+
it("returns a list of matches that don't need mapping", (): void => {
193+
const matches = extractThemeProps(
194+
CLASS_WIDGET,
195+
new Set(["font"]),
196+
value => value
197+
);
198+
expect(matches).toEqual({
199+
font: {
200+
name: undefined,
201+
size: 20,
202+
style: "Regular",
203+
typeface: "Liberation sans"
204+
}
205+
});
206+
});
207+
});

src/ui/hooks/useClassFile.tsx

Lines changed: 59 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,16 @@ import { fetchAndConvert } from "./useFile";
55
import { WidgetDescription } from "../widgets/createComponent";
66
import { selectClassFile } from "../../redux/slices/configurationSlice";
77
import { phoebusTheme } from "../../phoebusTheme";
8+
import { borderToCss } from "../../types/border";
9+
import { fontToCss } from "../../types/font";
810

911
// Map widget props to MUI theme props
1012
const keyMap: Record<string, string> = {
1113
backgroundColor: "main",
1214
foregroundColor: "contrastText"
1315
};
1416

15-
const CLASS_PROPS = new Set([
17+
const CLASS_COLOR_PROPS = new Set([
1618
"offColor",
1719
"onColor",
1820
"foregroundColor",
@@ -24,8 +26,15 @@ const CLASS_PROPS = new Set([
2426
"fillColor",
2527
"needleColor",
2628
"selectedColor",
27-
"deselectedColor",
28-
"borderColor"
29+
"deselectedColor"
30+
]);
31+
32+
const CLASS_FONT_PROPS = new Set([
33+
"font",
34+
"scaleFont",
35+
"titleFont",
36+
"labelFont",
37+
"legendFont"
2938
]);
3039

3140
export function useClassFile(userTheme?: Theme): Theme {
@@ -39,7 +48,7 @@ export function useClassFile(userTheme?: Theme): Theme {
3948
"ca",
4049
{}
4150
);
42-
setTheme(createClassPalettes(widgetDescription));
51+
setTheme(createClassTheme(widgetDescription));
4352
};
4453

4554
if (classFile !== undefined) {
@@ -50,39 +59,69 @@ export function useClassFile(userTheme?: Theme): Theme {
5059
return theme;
5160
}
5261

53-
export function createClassPalettes(classFile: WidgetDescription): Theme {
62+
/**
63+
* Convert individual widget props into an object
64+
* containing all class props on the widget, for a given group
65+
* e.g. all color classes
66+
* @param widget
67+
* @param allowedProps set of props for given group
68+
* @param mapper func that returns correct value for group
69+
* @returns
70+
*/
71+
export function extractThemeProps(
72+
widget: WidgetDescription,
73+
allowedProps: Set<string>,
74+
mapper: (value: any) => any
75+
): Record<string, any> {
76+
return Object.fromEntries(
77+
Object.entries(widget)
78+
.filter(([key]) => allowedProps.has(key))
79+
.map(([key, value]) => [keyMap[key] ?? key, mapper(value) ?? undefined])
80+
.filter(([, v]) => v !== undefined)
81+
);
82+
}
83+
84+
export function createClassTheme(classFile: WidgetDescription): Theme {
5485
// If classfile is empty, do nothing
5586
if (!classFile.children) return phoebusTheme;
5687

5788
const palette: { [key: string]: any } = {};
89+
const typography: { [key: string]: any } = {};
90+
const borders: { [key: string]: any } = {};
5891
classFile.children?.forEach((child: WidgetDescription) => {
5992
const widgetType: string = child.type;
6093
// Construct palette name from widget type and classname
6194
const paletteName = `${child.name}${widgetType}`;
6295

63-
// Only colors go in the theme palette
64-
const matches = Object.entries(child)
65-
.filter(([key]) => CLASS_PROPS.has(key))
66-
.map(([key, value]) => ({ key, value }));
96+
const colours = extractThemeProps(
97+
child,
98+
CLASS_COLOR_PROPS,
99+
value => value?.colorString
100+
);
101+
if (Object.keys(colours).length) palette[paletteName] = colours;
67102

68-
// Assign colors to palette
69-
palette[paletteName] = {
70-
// Put Phoebus theme defaults, overwrite with class props
71-
...phoebusTheme.palette[widgetType],
72-
...Object.fromEntries(
73-
matches.map(({ key, value }) => [
74-
keyMap[key] ?? key,
75-
value?.colorString ?? undefined
76-
])
77-
)
78-
};
103+
const fonts = extractThemeProps(child, CLASS_FONT_PROPS, value =>
104+
fontToCss(value)
105+
);
106+
if (Object.keys(fonts).length) typography[paletteName] = fonts;
107+
108+
borders[paletteName] = borderToCss(child.border);
79109
});
110+
80111
// Create Theme
81112
const classTheme = createTheme({
82113
customName: "class",
83114
palette: {
84115
...phoebusTheme.palette,
85116
...palette
117+
},
118+
typography: {
119+
...phoebusTheme.typography,
120+
...typography
121+
},
122+
borders: {
123+
...phoebusTheme.borders,
124+
...borders
86125
}
87126
});
88127
return classTheme;

src/ui/hooks/useStyle.test.ts

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,21 @@ const mockTheme = {
3838
borderWidth: 2,
3939
borderColor: "red",
4040
borderRadius: 4
41+
},
42+
MY_CLASSwidgetA: {
43+
borderStyle: "solid",
44+
borderWidth: 3,
45+
borderColor: "green",
46+
borderRadius: 5
4147
}
4248
},
43-
typography: { fontSize: 14 }
49+
typography: {
50+
fontSize: 14,
51+
MY_CLASSwidgetA: {
52+
fontSize: 10,
53+
fontFamily: "Montserrat"
54+
}
55+
}
4456
};
4557

4658
beforeEach(() => {
@@ -105,6 +117,17 @@ describe("useStyle", () => {
105117
});
106118
});
107119

120+
it("uses class border when provided", () => {
121+
const { result } = renderHook(() => useStyle({}, "widgetA", "MY_CLASS"));
122+
123+
expect(result.current.border).toEqual({
124+
borderStyle: "solid",
125+
borderWidth: 3,
126+
borderColor: "green",
127+
borderRadius: 5
128+
});
129+
});
130+
108131
it("returns customColors, overriding only matching keys", () => {
109132
const { result } = renderHook(() =>
110133
useStyle(
@@ -149,6 +172,15 @@ describe("useStyle", () => {
149172
expect(result.current.font).toEqual(mockTheme.typography);
150173
});
151174

175+
it("uses class font when class exists", () => {
176+
const { result } = renderHook(() => useStyle({}, "widgetA", "MY_CLASS"));
177+
178+
expect(result.current.font).toEqual({
179+
fontFamily: "Montserrat",
180+
fontSize: 10
181+
});
182+
});
183+
152184
it("cursor is pointer when actions exist", () => {
153185
const { result } = renderHook(() =>
154186
useStyle({

0 commit comments

Comments
 (0)