@zthun/romulator-web 1.11.0 → 1.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.html CHANGED
@@ -4,7 +4,7 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>Romulator: Organize your Games</title>
7
- <script type="module" crossorigin src="/assets/index-Bvc6l8vJ.js"></script>
7
+ <script type="module" crossorigin src="/assets/index-Dtk2L-Bc.js"></script>
8
8
  </head>
9
9
  <body>
10
10
  <div id="zthunworks-romulator"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zthun/romulator-web",
3
- "version": "1.11.0",
3
+ "version": "1.12.0",
4
4
  "description": "Romulator frontend",
5
5
  "author": "Anthony Bonta",
6
6
  "license": "MIT",
@@ -28,7 +28,7 @@
28
28
  "@zthun/helpful-query": "^9.9.0",
29
29
  "@zthun/helpful-react": "^9.9.0",
30
30
  "@zthun/janitor-build-config": "^19.4.1",
31
- "@zthun/romulator-client": "^1.11.0",
31
+ "@zthun/romulator-client": "^1.12.0",
32
32
  "@zthun/webigail-http": "^5.0.0",
33
33
  "@zthun/webigail-rest": "^5.0.0",
34
34
  "@zthun/webigail-url": "^5.0.0",
@@ -39,8 +39,9 @@
39
39
  "tsconfig-paths": "^4.2.0",
40
40
  "typescript": "^5.9.3",
41
41
  "vite": "^7.1.12",
42
+ "vite-plugin-node-polyfills": "^0.24.0",
42
43
  "vitest": "^4.0.3",
43
44
  "vitest-mock-extended": "^3.1.0"
44
45
  },
45
- "gitHead": "0bdf3fa128737f52824cd317345514d07a12260a"
46
+ "gitHead": "bb53a8480f83f487fa99d291d18636cb6db6587c"
46
47
  }
@@ -6,6 +6,7 @@ import {
6
6
  ZGridViewComponentModel,
7
7
  ZSuspenseComponentModel,
8
8
  } from "@zthun/fashion-boutique";
9
+ import { kebabCase } from "lodash-es";
9
10
 
10
11
  export class ZRomulatorSystemPageComponentModel extends ZCircusComponentModel {
11
12
  public static readonly Selector = ".ZRomulatorSystemPage-root";
@@ -34,6 +35,21 @@ export class ZRomulatorSystemPageComponentModel extends ZCircusComponentModel {
34
35
  return ZCircusBy.optional(this.driver, ZCardComponentModel, "system-info");
35
36
  }
36
37
 
38
+ private async fieldValue(key: string): Promise<string> {
39
+ const klass = `.ZRomulatorSystemPage-${kebabCase(key)}`;
40
+ const element = await this.driver.select(klass);
41
+
42
+ return element.attribute("data-value", "");
43
+ }
44
+
45
+ public name = this.fieldValue.bind(this, "Name");
46
+ public company = this.fieldValue.bind(this, "Company");
47
+ public hardwareType = this.fieldValue.bind(this, "Hardware Type");
48
+ public mediaFormat = this.fieldValue.bind(this, "Media Format");
49
+ public contentType = this.fieldValue.bind(this, "Content Type");
50
+ public productionStart = this.fieldValue.bind(this, "Production Start");
51
+ public productionEnd = this.fieldValue.bind(this, "Production End");
52
+
37
53
  public games(): Promise<ZGridViewComponentModel | null> {
38
54
  return ZCircusBy.optional(this.driver, ZGridViewComponentModel);
39
55
  }
@@ -17,7 +17,10 @@ import type { IZRomulatorGame } from "@zthun/romulator-client";
17
17
  import {
18
18
  ZRomulatorGameBuilder,
19
19
  ZRomulatorSystemBuilder,
20
+ ZRomulatorSystemContentType,
21
+ ZRomulatorSystemHardwareType,
20
22
  ZRomulatorSystemId,
23
+ ZRomulatorSystemMediaFormat,
21
24
  } from "@zthun/romulator-client";
22
25
  import type { History } from "history";
23
26
  import { createMemoryHistory } from "history";
@@ -42,6 +45,11 @@ describe("SystemPage", () => {
42
45
  const nes = new ZRomulatorSystemBuilder()
43
46
  .id(ZRomulatorSystemId.Nintendo)
44
47
  .name("Nintendo Entertainment System")
48
+ .company("Nintendo")
49
+ .hardware(ZRomulatorSystemHardwareType.Console)
50
+ .mediaFormat(ZRomulatorSystemMediaFormat.Cartridge)
51
+ .contentType(ZRomulatorSystemContentType.ReadOnlyMemory)
52
+ .production(1983, 1995)
45
53
  .build();
46
54
 
47
55
  const batman = new ZRomulatorGameBuilder()
@@ -177,6 +185,66 @@ describe("SystemPage", () => {
177
185
  });
178
186
  });
179
187
 
188
+ describe("Information", () => {
189
+ async function shouldRenderInformation(
190
+ expected: string,
191
+ fieldFn: (target: ZRomulatorSystemPageComponentModel) => Promise<string>,
192
+ ) {
193
+ // Arrange.
194
+ const target = await createTestTarget();
195
+ await target.load();
196
+
197
+ // Act.
198
+ const actual = await fieldFn(target);
199
+
200
+ // Assert.
201
+ expect(actual).toEqual(expected);
202
+ }
203
+
204
+ it("should render the system name", async () => {
205
+ await shouldRenderInformation(nes.name, (t) => t.name());
206
+ });
207
+
208
+ it("should render the system company", async () => {
209
+ await shouldRenderInformation(nes.company, (t) => t.company());
210
+ });
211
+
212
+ it("should render the system hardware type", async () => {
213
+ const { classification } = nes;
214
+ const { hardwareType } = classification;
215
+
216
+ await shouldRenderInformation(hardwareType, (t) => t.hardwareType());
217
+ });
218
+
219
+ it("should render the system media format", async () => {
220
+ const { classification } = nes;
221
+ const { mediaFormat } = classification;
222
+
223
+ await shouldRenderInformation(mediaFormat, (t) => t.mediaFormat());
224
+ });
225
+
226
+ it("should render the content type", async () => {
227
+ const { classification } = nes;
228
+ const { contentType } = classification;
229
+
230
+ await shouldRenderInformation(contentType, (t) => t.contentType());
231
+ });
232
+
233
+ it("should render the production start date", async () => {
234
+ const { productionYears } = nes;
235
+ const { start } = productionYears;
236
+
237
+ await shouldRenderInformation(String(start), (t) => t.productionStart());
238
+ });
239
+
240
+ it("should render the production end date", async () => {
241
+ const { productionYears } = nes;
242
+ const { end } = productionYears;
243
+
244
+ await shouldRenderInformation(String(end), (t) => t.productionEnd());
245
+ });
246
+ });
247
+
180
248
  describe("Games", () => {
181
249
  it("should only load games that are assigned to the given system", async () => {
182
250
  // Arrange.
@@ -3,12 +3,15 @@ import {
3
3
  useParams,
4
4
  ZAlert,
5
5
  ZBreadcrumbsLocation,
6
+ ZCaption,
6
7
  ZCard,
8
+ ZGrid,
7
9
  ZIconFontAwesome,
10
+ ZLabel,
8
11
  ZStack,
9
12
  ZSuspenseProgress,
10
13
  } from "@zthun/fashion-boutique";
11
- import { ZSizeFixed } from "@zthun/fashion-tailor";
14
+ import { ZSizeFixed, ZSizeVaried } from "@zthun/fashion-tailor";
12
15
  import { firstDefined } from "@zthun/helpful-fn";
13
16
  import {
14
17
  ZDataRequestBuilder,
@@ -19,10 +22,46 @@ import {
19
22
  isStateLoading,
20
23
  useSyncState,
21
24
  } from "@zthun/helpful-react";
25
+ import {
26
+ ZRomulatorSystemContentType,
27
+ ZRomulatorSystemHardwareType,
28
+ ZRomulatorSystemMediaFormat,
29
+ } from "@zthun/romulator-client";
30
+ import { kebabCase, startCase } from "lodash-es";
22
31
  import { useMemo } from "react";
23
32
  import { ZRomulatorGamesList } from "../games/games-list.js";
24
33
  import { useSystem } from "./systems-service.mjs";
25
34
 
35
+ // TODO: These enum headers should be done in i18n. Keeping them here for now is fine.
36
+ const HardwareTypeDisplay: Record<ZRomulatorSystemHardwareType, string> = {
37
+ [ZRomulatorSystemHardwareType.Accessory]: "Accessory",
38
+ [ZRomulatorSystemHardwareType.Arcade]: "Arcade",
39
+ [ZRomulatorSystemHardwareType.Computer]: "Computer",
40
+ [ZRomulatorSystemHardwareType.Console]: "Console",
41
+ [ZRomulatorSystemHardwareType.Flipper]: "Pinball Machine",
42
+ [ZRomulatorSystemHardwareType.Handheld]: "Handheld",
43
+ [ZRomulatorSystemHardwareType.ScummVm]: "ScummVm",
44
+ [ZRomulatorSystemHardwareType.Smartphone]: "Phone",
45
+ [ZRomulatorSystemHardwareType.Unknown]: "?",
46
+ [ZRomulatorSystemHardwareType.VirtualMachine]: "Virtual Machine",
47
+ };
48
+
49
+ const MediaFormatDisplay: Record<ZRomulatorSystemMediaFormat, string> = {
50
+ [ZRomulatorSystemMediaFormat.Cartridge]: "Cartridge",
51
+ [ZRomulatorSystemMediaFormat.Cd]: "CD",
52
+ [ZRomulatorSystemMediaFormat.FloppyDisk]: "Floppy Disk",
53
+ [ZRomulatorSystemMediaFormat.Pcb]: "PCB",
54
+ [ZRomulatorSystemMediaFormat.Unknown]: "?",
55
+ };
56
+
57
+ const ContentTypeDisplay: Record<ZRomulatorSystemContentType, string> = {
58
+ [ZRomulatorSystemContentType.Disk]: "Disk",
59
+ [ZRomulatorSystemContentType.File]: "File",
60
+ [ZRomulatorSystemContentType.Folder]: "Folder",
61
+ [ZRomulatorSystemContentType.ReadOnlyMemory]: "ROM",
62
+ [ZRomulatorSystemContentType.Unknown]: "?",
63
+ };
64
+
26
65
  export function ZRomulatorSystemPage() {
27
66
  const { id } = useParams();
28
67
  const { error } = useFashionTheme();
@@ -40,7 +79,24 @@ export function ZRomulatorSystemPage() {
40
79
 
41
80
  const [userRequest, setGameRequest] = useSyncState(baseGameRequest);
42
81
 
43
- const renderSystemInformation = () => {
82
+ const renderSystemInfoField = (
83
+ label: string,
84
+ value?: string,
85
+ display?: string,
86
+ ) => (
87
+ <>
88
+ <ZLabel>{label}:</ZLabel>
89
+ <ZCaption
90
+ compact
91
+ className={`ZRomulatorSystemPage-${kebabCase(label)}`}
92
+ data-value={value}
93
+ >
94
+ {firstDefined(value, display)}
95
+ </ZCaption>
96
+ </>
97
+ );
98
+
99
+ const renderPageContent = () => {
44
100
  if (isStateLoading(system)) {
45
101
  return (
46
102
  <ZSuspenseProgress name="system-loading" height={ZSizeFixed.Large} />
@@ -57,18 +113,50 @@ export function ZRomulatorSystemPage() {
57
113
  );
58
114
  }
59
115
 
116
+ const { name, company, classification, productionYears } = system;
117
+ const { hardwareType, mediaFormat, contentType } = classification;
118
+ const { start, end } = productionYears;
119
+
120
+ const _hardware = HardwareTypeDisplay[hardwareType];
121
+ const _media = MediaFormatDisplay[mediaFormat];
122
+ const _content = ContentTypeDisplay[contentType];
123
+
60
124
  return (
61
125
  <ZStack gap={ZSizeFixed.Medium}>
62
- <ZCard
63
- name="system-info"
64
- TitleProps={{
65
- avatar: (
66
- <ZIconFontAwesome name="puzzle-piece" width={ZSizeFixed.Medium} />
67
- ),
68
- heading: system.name,
69
- subHeading: system.company,
70
- }}
71
- />
126
+ <ZGrid columns="auto 1fr">
127
+ <ZCard
128
+ name="system-info"
129
+ TitleProps={{
130
+ avatar: (
131
+ <ZIconFontAwesome
132
+ name="puzzle-piece"
133
+ width={ZSizeFixed.Medium}
134
+ />
135
+ ),
136
+ heading: "Information",
137
+ subHeading: "Details about this system",
138
+ }}
139
+ >
140
+ <ZGrid
141
+ columns="auto auto"
142
+ gap={ZSizeFixed.Medium}
143
+ width={ZSizeVaried.Fit}
144
+ align={{ items: "center" }}
145
+ >
146
+ {renderSystemInfoField("Name", name)}
147
+ {renderSystemInfoField("Company", company)}
148
+ {renderSystemInfoField("Hardware Type", hardwareType, _hardware)}
149
+ {renderSystemInfoField("Media Format", mediaFormat, _media)}
150
+ {renderSystemInfoField("Content Type", contentType, _content)}
151
+ {renderSystemInfoField("Production Start", String(start))}
152
+ {renderSystemInfoField(
153
+ "Production End",
154
+ String(end),
155
+ startCase(String(end)),
156
+ )}
157
+ </ZGrid>
158
+ </ZCard>
159
+ </ZGrid>
72
160
  <ZCard
73
161
  name="game-list"
74
162
  TitleProps={{
@@ -89,10 +177,10 @@ export function ZRomulatorSystemPage() {
89
177
  };
90
178
 
91
179
  return (
92
- <ZStack gap={ZSizeFixed.Medium} className={"ZRomulatorSystemPage-root"}>
180
+ <ZStack gap={ZSizeFixed.Medium} className="ZRomulatorSystemPage-root">
93
181
  <ZBreadcrumbsLocation />
94
182
 
95
- {renderSystemInformation()}
183
+ {renderPageContent()}
96
184
  </ZStack>
97
185
  );
98
186
  }
package/vite.config.mts CHANGED
@@ -4,6 +4,7 @@ import {
4
4
  ZViteTestBuilder,
5
5
  } from "@zthun/janitor-build-config/vite";
6
6
  import { defineConfig } from "vite";
7
+ import { nodePolyfills } from "vite-plugin-node-polyfills";
7
8
 
8
9
  const test = new ZViteTestBuilder().browser().build();
9
10
  const server = new ZViteServerBuilder().dev().build();
@@ -12,6 +13,7 @@ const config = new ZViteConfigBuilder()
12
13
  .server(server)
13
14
  .test(test)
14
15
  .lodash()
16
+ .plugin(nodePolyfills())
15
17
  .build();
16
18
 
17
19
  export default defineConfig(config);