@zthun/romulator-web 1.15.0 → 1.17.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.
@@ -0,0 +1,109 @@
1
+ import {
2
+ useCss,
3
+ ZCard,
4
+ ZIconFontAwesome,
5
+ ZImageSource,
6
+ ZStack,
7
+ } from "@zthun/fashion-boutique";
8
+ import { ZSizeFixed, ZSizeVaried } from "@zthun/fashion-tailor";
9
+ import { css, cssJoinDefined, ZOrientation } from "@zthun/helpful-fn";
10
+ import {
11
+ ZRomulatorGameMediaType,
12
+ ZRomulatorSystemMediaType,
13
+ type ZRomulatorMediaType,
14
+ } from "@zthun/romulator-client";
15
+ import { ZRomulatorEnvironmentBuilder } from "../environment/environment.mjs";
16
+
17
+ // TODO: Localization
18
+ const ZRomulatorMediaTypeName: Record<ZRomulatorMediaType, string> = {
19
+ [ZRomulatorGameMediaType.BackCover]: "Back Cover",
20
+ [ZRomulatorGameMediaType.Box3d]: "3D Box",
21
+ [ZRomulatorGameMediaType.Cover]: "Cover",
22
+ [ZRomulatorGameMediaType.FanArt]: "Fan Art",
23
+ [ZRomulatorGameMediaType.Manual]: "Manual",
24
+ [ZRomulatorGameMediaType.Marquee]: "Marquee",
25
+ [ZRomulatorGameMediaType.PhysicalMedia]: "Physical Media",
26
+ [ZRomulatorGameMediaType.Screenshot]: "Screenshot",
27
+ [ZRomulatorGameMediaType.Title]: "Title",
28
+ [ZRomulatorGameMediaType.Video]: "Video",
29
+ [ZRomulatorSystemMediaType.Controller]: "Controller",
30
+ [ZRomulatorSystemMediaType.Picture]: "System Picture",
31
+ [ZRomulatorSystemMediaType.Wheel]: "Wheel Logo",
32
+ };
33
+
34
+ // TODO: Localization
35
+ const ZRomulatorMediaTypeDescription: Record<ZRomulatorMediaType, string> = {
36
+ [ZRomulatorGameMediaType.BackCover]: "Rear packaging artwork.",
37
+ [ZRomulatorGameMediaType.Box3d]: "3D box render.",
38
+ [ZRomulatorGameMediaType.Cover]: "Front cover art.",
39
+ [ZRomulatorGameMediaType.FanArt]: "Community artwork.",
40
+ [ZRomulatorGameMediaType.Manual]: "Digital version of the game manual.",
41
+ [ZRomulatorGameMediaType.Marquee]: "Game Logo.",
42
+ [ZRomulatorGameMediaType.PhysicalMedia]: "Disc or cartridge.",
43
+ [ZRomulatorGameMediaType.Screenshot]: "In-game action.",
44
+ [ZRomulatorGameMediaType.Title]: "Screen upon load.",
45
+ [ZRomulatorGameMediaType.Video]: "Gameplay or trailer video preview.",
46
+ [ZRomulatorSystemMediaType.Controller]: "Primary controller image.",
47
+ [ZRomulatorSystemMediaType.Picture]: "Image of system hardware.",
48
+ [ZRomulatorSystemMediaType.Wheel]: "Logo for the system.",
49
+ };
50
+
51
+ /**
52
+ * Props for the media card.
53
+ */
54
+ export interface IZRomulatorMediaCard {
55
+ /**
56
+ * The id of the game or system.
57
+ */
58
+ identifier: string;
59
+ /**
60
+ * The type of media to retrieve.
61
+ */
62
+ type: ZRomulatorMediaType;
63
+ }
64
+
65
+ export function ZRomulatorMediaCard(props: IZRomulatorMediaCard) {
66
+ const { identifier, type } = props;
67
+ const { api } = new ZRomulatorEnvironmentBuilder().build();
68
+ const id = `${identifier}-${type}`;
69
+ const media = `${api}/media/${id}`;
70
+
71
+ const _className = useCss(css`
72
+ & .ZRomulatorMediaCard-image-container {
73
+ height: 20rem;
74
+ }
75
+
76
+ & .ZRomulatorMediaCard-image-container img {
77
+ object-fit: scale-down;
78
+ }
79
+ `);
80
+
81
+ return (
82
+ <ZCard
83
+ className={cssJoinDefined("ZRomulatorMediaCard-root", _className)}
84
+ name={type}
85
+ TitleProps={{
86
+ avatar: <ZIconFontAwesome name="image" width={ZSizeFixed.Medium} />,
87
+ heading: ZRomulatorMediaTypeName[type],
88
+ subHeading: ZRomulatorMediaTypeDescription[type],
89
+ }}
90
+ data-type={type}
91
+ data-identifier={identifier}
92
+ >
93
+ <ZStack
94
+ className="ZRomulatorMediaCard-image-container"
95
+ orientation={ZOrientation.Horizontal}
96
+ width={ZSizeVaried.Full}
97
+ height={ZSizeVaried.Full}
98
+ align={{ items: "center" }}
99
+ justify={{ content: "center" }}
100
+ >
101
+ <ZImageSource
102
+ src={media}
103
+ width={ZSizeVaried.Full}
104
+ height={ZSizeVaried.Full}
105
+ />
106
+ </ZStack>
107
+ </ZCard>
108
+ );
109
+ }
@@ -6,7 +6,12 @@ import {
6
6
  ZGridViewComponentModel,
7
7
  ZSuspenseComponentModel,
8
8
  } from "@zthun/fashion-boutique";
9
+ import { firstDefined } from "@zthun/helpful-fn";
10
+ import { ZRomulatorSystemMediaType } from "@zthun/romulator-client";
9
11
  import { kebabCase } from "lodash-es";
12
+ import { ZRomulatorMediaCardComponentModel } from "../media/media-card.cm.mjs";
13
+
14
+ const { Controller, Picture, Wheel } = ZRomulatorSystemMediaType;
10
15
 
11
16
  export class ZRomulatorSystemPageComponentModel extends ZCircusComponentModel {
12
17
  public static readonly Selector = ".ZRomulatorSystemPage-root";
@@ -31,28 +36,39 @@ export class ZRomulatorSystemPageComponentModel extends ZCircusComponentModel {
31
36
  return ZCircusBy.optional(this.driver, ZAlertComponentModel);
32
37
  }
33
38
 
34
- public system(): Promise<ZCardComponentModel | null> {
35
- return ZCircusBy.optional(this.driver, ZCardComponentModel, "system-info");
39
+ public info(): Promise<ZCardComponentModel | null> {
40
+ return ZCircusBy.optional(this.driver, ZCardComponentModel, "info");
36
41
  }
37
42
 
38
- public media(): Promise<ZCardComponentModel | null> {
39
- return ZCircusBy.optional(this.driver, ZCardComponentModel, "system-media");
43
+ private media(
44
+ type: ZRomulatorSystemMediaType,
45
+ ): Promise<ZRomulatorMediaCardComponentModel> {
46
+ return ZCircusBy.first(
47
+ this.driver,
48
+ ZRomulatorMediaCardComponentModel,
49
+ type,
50
+ );
40
51
  }
41
52
 
42
- private async fieldValue(key: string): Promise<string> {
43
- const klass = `.ZRomulatorSystemPage-${kebabCase(key)}`;
44
- const element = await this.driver.select(klass);
53
+ public controller = this.media.bind(this, Controller);
54
+ public picture = this.media.bind(this, Picture);
55
+ public wheel = this.media.bind(this, Wheel);
45
56
 
46
- return element.attribute("data-value", "");
57
+ private async field(key: string): Promise<string> {
58
+ const info = await this.info();
59
+ const klass = `.ZRomulatorSystemPage-${kebabCase(key)}`;
60
+ const element = await info?.driver.select(klass);
61
+ const value = await element?.attribute("data-value", "");
62
+ return firstDefined("", value);
47
63
  }
48
64
 
49
- public name = this.fieldValue.bind(this, "Name");
50
- public company = this.fieldValue.bind(this, "Company");
51
- public hardwareType = this.fieldValue.bind(this, "Hardware Type");
52
- public mediaFormat = this.fieldValue.bind(this, "Media Format");
53
- public contentType = this.fieldValue.bind(this, "Content Type");
54
- public productionStart = this.fieldValue.bind(this, "Production Start");
55
- public productionEnd = this.fieldValue.bind(this, "Production End");
65
+ public name = this.field.bind(this, "Name");
66
+ public company = this.field.bind(this, "Company");
67
+ public hardwareType = this.field.bind(this, "Hardware Type");
68
+ public mediaFormat = this.field.bind(this, "Media Format");
69
+ public contentType = this.field.bind(this, "Content Type");
70
+ public productionStart = this.field.bind(this, "Production Start");
71
+ public productionEnd = this.field.bind(this, "Production End");
56
72
 
57
73
  public games(): Promise<ZGridViewComponentModel | null> {
58
74
  return ZCircusBy.optional(this.driver, ZGridViewComponentModel);
@@ -21,6 +21,7 @@ import {
21
21
  ZRomulatorSystemHardwareType,
22
22
  ZRomulatorSystemId,
23
23
  ZRomulatorSystemMediaFormat,
24
+ ZRomulatorSystemMediaType,
24
25
  } from "@zthun/romulator-client";
25
26
  import type { History } from "history";
26
27
  import { createMemoryHistory } from "history";
@@ -159,44 +160,6 @@ describe("SystemPage", () => {
159
160
  });
160
161
  });
161
162
 
162
- describe("System", () => {
163
- it("should render the system information card", async () => {
164
- // Arrange.
165
- const target = await createTestTarget();
166
- await target.load();
167
-
168
- // Act.
169
- const actual = await target.system();
170
-
171
- // Assert.
172
- expect(actual).toBeTruthy();
173
- });
174
-
175
- it("should render the system media card", async () => {
176
- // Arrange.
177
- const target = await createTestTarget();
178
- await target.load();
179
-
180
- // Act.
181
- const actual = await target.media();
182
-
183
- // Assert.
184
- expect(actual).toBeTruthy();
185
- });
186
-
187
- it("should render the games list", async () => {
188
- // Arrange.
189
- const target = await createTestTarget();
190
- await target.load();
191
-
192
- // Act.
193
- const actual = await target.games();
194
-
195
- // Assert.
196
- expect(actual).toBeTruthy();
197
- });
198
- });
199
-
200
163
  describe("Information", () => {
201
164
  async function shouldRenderInformation(
202
165
  expected: string,
@@ -292,4 +255,48 @@ describe("SystemPage", () => {
292
255
  expect(history.location.pathname).toEqual(`/games/${batman.id}`);
293
256
  });
294
257
  });
258
+
259
+ describe("Media", () => {
260
+ it("should render the controller", async () => {
261
+ // Arrange.
262
+ const target = await createTestTarget();
263
+
264
+ // Act.
265
+ const image = await target.controller();
266
+ const identifier = await image.identifier();
267
+ const actual = await image.type();
268
+
269
+ // Assert.
270
+ expect(identifier).toEqual(nes.id);
271
+ expect(actual).toEqual(ZRomulatorSystemMediaType.Controller);
272
+ });
273
+
274
+ it("should render the picture", async () => {
275
+ // Arrange.
276
+ const target = await createTestTarget();
277
+
278
+ // Act.
279
+ const image = await target.picture();
280
+ const identifier = await image.identifier();
281
+ const actual = await image.type();
282
+
283
+ // Assert.
284
+ expect(identifier).toEqual(nes.id);
285
+ expect(actual).toEqual(ZRomulatorSystemMediaType.Picture);
286
+ });
287
+
288
+ it("should render the wheel", async () => {
289
+ // Arrange.
290
+ const target = await createTestTarget();
291
+
292
+ // Act.
293
+ const image = await target.wheel();
294
+ const identifier = await image.identifier();
295
+ const actual = await image.type();
296
+
297
+ // Assert.
298
+ expect(identifier).toEqual(nes.id);
299
+ expect(actual).toEqual(ZRomulatorSystemMediaType.Wheel);
300
+ });
301
+ });
295
302
  });
@@ -3,19 +3,16 @@ import {
3
3
  useParams,
4
4
  ZAlert,
5
5
  ZBreadcrumbsLocation,
6
- ZBubble,
7
6
  ZCaption,
8
7
  ZCard,
9
- ZCarousel,
10
8
  ZGrid,
11
9
  ZIconFontAwesome,
12
- ZImageSource,
13
10
  ZLabel,
14
11
  ZStack,
15
12
  ZSuspenseProgress,
16
13
  } from "@zthun/fashion-boutique";
17
14
  import { ZSizeFixed, ZSizeVaried } from "@zthun/fashion-tailor";
18
- import { firstDefined, ZOrientation } from "@zthun/helpful-fn";
15
+ import { firstDefined } from "@zthun/helpful-fn";
19
16
  import {
20
17
  ZDataRequestBuilder,
21
18
  ZFilterBinaryBuilder,
@@ -25,10 +22,7 @@ import {
25
22
  isStateLoading,
26
23
  useSyncState,
27
24
  } from "@zthun/helpful-react";
28
- import type {
29
- IZRomulatorSystem,
30
- ZRomulatorMediaType,
31
- } from "@zthun/romulator-client";
25
+ import type { IZRomulatorSystem } from "@zthun/romulator-client";
32
26
  import {
33
27
  ZRomulatorSystemContentType,
34
28
  ZRomulatorSystemHardwareType,
@@ -36,9 +30,9 @@ import {
36
30
  ZRomulatorSystemMediaType,
37
31
  } from "@zthun/romulator-client";
38
32
  import { kebabCase, startCase } from "lodash-es";
39
- import { useMemo, useState } from "react";
40
- import { ZRomulatorEnvironmentBuilder } from "../environment/environment.mjs";
33
+ import { useMemo } from "react";
41
34
  import { ZRomulatorGamesList } from "../games/games-list.js";
35
+ import { ZRomulatorMediaCard } from "../media/media-card.js";
42
36
  import { useSystem } from "./systems-service.mjs";
43
37
 
44
38
  // TODO: These enum headers should be done in i18n. Keeping them here for now is fine.
@@ -75,7 +69,6 @@ export function ZRomulatorSystemPage() {
75
69
  const { id } = useParams();
76
70
  const { error } = useFashionTheme();
77
71
  const [system] = useSystem(firstDefined("", id));
78
- const [mediaIndex, setMediaIndex] = useState(0);
79
72
  const gameFilter = useMemo(
80
73
  () =>
81
74
  new ZFilterBinaryBuilder().subject("system").equal().value(id).build(),
@@ -88,6 +81,7 @@ export function ZRomulatorSystemPage() {
88
81
  );
89
82
 
90
83
  const [userRequest, setGameRequest] = useSyncState(baseGameRequest);
84
+ const media = useMemo(() => Object.values(ZRomulatorSystemMediaType), []);
91
85
 
92
86
  const renderSystemInfoField = (
93
87
  label: string,
@@ -117,7 +111,7 @@ export function ZRomulatorSystemPage() {
117
111
 
118
112
  return (
119
113
  <ZCard
120
- name="system-info"
114
+ name="info"
121
115
  TitleProps={{
122
116
  avatar: (
123
117
  <ZIconFontAwesome name="puzzle-piece" width={ZSizeFixed.Medium} />
@@ -168,56 +162,10 @@ export function ZRomulatorSystemPage() {
168
162
  );
169
163
  };
170
164
 
171
- const renderSystemMedia = (
172
- type: ZRomulatorMediaType,
173
- system: IZRomulatorSystem,
174
- ) => {
175
- const { api } = new ZRomulatorEnvironmentBuilder().build();
176
- const id = `${system.id}-${type}`;
177
- const media = `${api}/media/${id}`;
178
-
179
- return (
180
- <ZBubble width={ZSizeFixed.ExtraLarge}>
181
- <ZImageSource
182
- className="ZRomulatorSystemsPage-wheel"
183
- src={media}
184
- width={ZSizeVaried.Full}
185
- />
186
- </ZBubble>
187
- );
188
- };
189
-
190
- const renderSystemMediaCard = (system: IZRomulatorSystem) => {
191
- const media = [
192
- ZRomulatorSystemMediaType.Picture,
193
- ZRomulatorSystemMediaType.Controller,
194
- ZRomulatorSystemMediaType.Wheel,
195
- ];
196
-
197
- return (
198
- <ZCard
199
- name="system-media"
200
- TitleProps={{
201
- avatar: <ZIconFontAwesome name="image" width={ZSizeFixed.Medium} />,
202
- heading: "Media",
203
- subHeading: startCase(media[mediaIndex]),
204
- }}
205
- >
206
- <ZStack
207
- orientation={ZOrientation.Horizontal}
208
- width={ZSizeVaried.Full}
209
- align={{ items: "center" }}
210
- justify={{ content: "center" }}
211
- >
212
- <ZCarousel
213
- count={media.length}
214
- renderAtIndex={(i) => renderSystemMedia(media[i], system)}
215
- value={mediaIndex}
216
- onValueChange={setMediaIndex}
217
- />
218
- </ZStack>
219
- </ZCard>
220
- );
165
+ const renderSystemMedia = (system: IZRomulatorSystem) => {
166
+ return media.map((type) => (
167
+ <ZRomulatorMediaCard key={type} identifier={system.id} type={type} />
168
+ ));
221
169
  };
222
170
 
223
171
  const renderPageContent = () => {
@@ -238,12 +186,16 @@ export function ZRomulatorSystemPage() {
238
186
  }
239
187
 
240
188
  return (
241
- <ZGrid columns={{ xl: "1fr auto", md: "1fr" }} gap={ZSizeFixed.Medium}>
189
+ <ZGrid
190
+ columns={{ xl: "1fr auto", md: "1fr" }}
191
+ gap={ZSizeFixed.Medium}
192
+ align={{ items: "start" }}
193
+ >
242
194
  {renderSystemGameListCard(system)}
243
195
 
244
196
  <ZStack gap={ZSizeFixed.Medium}>
245
197
  {renderSystemInfoCard(system)}
246
- {renderSystemMediaCard(system)}
198
+ {renderSystemMedia(system)}
247
199
  </ZStack>
248
200
  </ZGrid>
249
201
  );