@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/CHANGELOG.md +9 -0
- package/dist/assets/index-Dtk2L-Bc.js +787 -0
- package/dist/index.html +1 -1
- package/package.json +4 -3
- package/src/systems/system-page.cm.mts +16 -0
- package/src/systems/system-page.spec.tsx +68 -0
- package/src/systems/system-page.tsx +102 -14
- package/vite.config.mts +2 -0
- package/dist/assets/index-Bvc6l8vJ.js +0 -39810
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-
|
|
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.
|
|
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.
|
|
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": "
|
|
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
|
|
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
|
-
<
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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=
|
|
180
|
+
<ZStack gap={ZSizeFixed.Medium} className="ZRomulatorSystemPage-root">
|
|
93
181
|
<ZBreadcrumbsLocation />
|
|
94
182
|
|
|
95
|
-
{
|
|
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);
|