chenbhao Claude Opus 4.6 commited on
Commit
6d1207a
·
1 Parent(s): 0afdca0

refactor: extract UI from ImageShowTool into separate component

Browse files

Split ImageShowTool.ts into utility functions (ImageShowTool.ts) and
React component (UI.tsx), enabling reuse of load/calculate logic.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

bun.lock CHANGED
@@ -117,6 +117,8 @@
117
  },
118
  "devDependencies": {
119
  "@types/bun": "^1.3.11",
 
 
120
  "typescript": "^6.0.2",
121
  "vitest": "^4.1.10",
122
  },
@@ -612,6 +614,8 @@
612
 
613
  "@types/react": ["@types/react@19.2.17", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw=="],
614
 
 
 
615
  "@types/wrap-ansi": ["@types/wrap-ansi@3.0.0", "", {}, "sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g=="],
616
 
617
  "@typespec/ts-http-runtime": ["@typespec/ts-http-runtime@0.3.6", "", { "dependencies": { "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.0", "tslib": "^2.6.2" } }, "sha512-jIXhD0eWQ1JA6ln/5Dltyx22UxWNrw0hZmhy2rlv6m6KgF7kplHx3g0fzi09lNmTJQRR91OlemYp3xFnvDK9og=="],
 
117
  },
118
  "devDependencies": {
119
  "@types/bun": "^1.3.11",
120
+ "@types/react": "^19.2.17",
121
+ "@types/react-dom": "^19.2.3",
122
  "typescript": "^6.0.2",
123
  "vitest": "^4.1.10",
124
  },
 
614
 
615
  "@types/react": ["@types/react@19.2.17", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw=="],
616
 
617
+ "@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="],
618
+
619
  "@types/wrap-ansi": ["@types/wrap-ansi@3.0.0", "", {}, "sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g=="],
620
 
621
  "@typespec/ts-http-runtime": ["@typespec/ts-http-runtime@0.3.6", "", { "dependencies": { "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.0", "tslib": "^2.6.2" } }, "sha512-jIXhD0eWQ1JA6ln/5Dltyx22UxWNrw0hZmhy2rlv6m6KgF7kplHx3g0fzi09lNmTJQRR91OlemYp3xFnvDK9og=="],
package.json CHANGED
@@ -134,6 +134,8 @@
134
  },
135
  "devDependencies": {
136
  "@types/bun": "^1.3.11",
 
 
137
  "typescript": "^6.0.2",
138
  "vitest": "^4.1.10"
139
  },
 
134
  },
135
  "devDependencies": {
136
  "@types/bun": "^1.3.11",
137
+ "@types/react": "^19.2.17",
138
+ "@types/react-dom": "^19.2.3",
139
  "typescript": "^6.0.2",
140
  "vitest": "^4.1.10"
141
  },
src/ink-picture/__tests__/LocalPicture.test.tsx CHANGED
@@ -11,7 +11,7 @@
11
  */
12
 
13
  import { Jimp } from "jimp";
14
- import React, { useEffect, useState } from "react";
15
  import { render, Box, Text, useApp } from "ink";
16
  import Image, { InkPictureProvider } from "../index.ts";
17
  import { loadImageFromUrl } from "../utils/jimpURL.ts";
@@ -78,7 +78,6 @@ function App() {
78
  useEffect(() => {
79
  const handleSigint = () => exit();
80
  process.on("SIGINT", handleSigint);
81
- return () => process.off("SIGINT", handleSigint);
82
  }, [exit]);
83
 
84
  if (err) {
 
11
  */
12
 
13
  import { Jimp } from "jimp";
14
+ import { useEffect, useState } from "react";
15
  import { render, Box, Text, useApp } from "ink";
16
  import Image, { InkPictureProvider } from "../index.ts";
17
  import { loadImageFromUrl } from "../utils/jimpURL.ts";
 
78
  useEffect(() => {
79
  const handleSigint = () => exit();
80
  process.on("SIGINT", handleSigint);
 
81
  }, [exit]);
82
 
83
  if (err) {
src/tools/ImageShowTool/ImageShowTool.ts CHANGED
@@ -11,10 +11,10 @@
11
  */
12
 
13
  import { Jimp } from "jimp";
14
- import React, { useEffect, useState } from "react";
15
  import { render, Box, Text, useApp } from "ink";
16
- import Image, { InkPictureProvider } from "../../ink-picture/index.ts";
17
- import { loadImageFromUrl } from "../../ink-picture/utils/jimpURL.ts";
18
 
19
  // 终端字符尺寸(像素)
20
  const CELL_WIDTH = 8;
@@ -78,7 +78,6 @@ function App() {
78
  useEffect(() => {
79
  const handleSigint = () => exit();
80
  process.on("SIGINT", handleSigint);
81
- return () => process.off("SIGINT", handleSigint);
82
  }, [exit]);
83
 
84
  if (err) {
 
11
  */
12
 
13
  import { Jimp } from "jimp";
14
+ import { useEffect, useState } from "react";
15
  import { render, Box, Text, useApp } from "ink";
16
+ import Image, { InkPictureProvider } from "../index.ts";
17
+ import { loadImageFromUrl } from "../utils/jimpURL.ts";
18
 
19
  // 终端字符尺寸(像素)
20
  const CELL_WIDTH = 8;
 
78
  useEffect(() => {
79
  const handleSigint = () => exit();
80
  process.on("SIGINT", handleSigint);
 
81
  }, [exit]);
82
 
83
  if (err) {
src/tools/ImageShowTool/UI.tsx CHANGED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * Visual test: display a local image or URL using ink-picture + Ink.
4
+ */
5
+
6
+ import { useEffect, useState } from "react";
7
+ import { render, Box, Text, useApp } from "ink";
8
+ import Image, { InkPictureProvider } from "../../ink-picture/index.ts";
9
+ import {
10
+ getImagePath,
11
+ isUrl,
12
+ loadImage,
13
+ calculateDimensions,
14
+ type ImageDimensions,
15
+ } from "./ImageShowTool.ts";
16
+
17
+ const args = process.argv.slice(2);
18
+ const IMAGE_PATH = getImagePath(args);
19
+
20
+ function App() {
21
+ const { exit } = useApp();
22
+ const [dimensions, setDimensions] = useState<ImageDimensions | null>(null);
23
+ const [err, setErr] = useState(false);
24
+
25
+ useEffect(() => {
26
+ (async () => {
27
+ try {
28
+ const image = await loadImage(IMAGE_PATH);
29
+ const dims = calculateDimensions(
30
+ image.bitmap.width,
31
+ image.bitmap.height,
32
+ process.stdout.columns ?? 80
33
+ );
34
+ setDimensions(dims);
35
+ } catch (e) {
36
+ console.error(e);
37
+ setErr(true);
38
+ exit();
39
+ }
40
+ })();
41
+ }, [exit]);
42
+
43
+ useEffect(() => {
44
+ const handleSigint = () => exit();
45
+ process.on("SIGINT", handleSigint);
46
+ return () => {
47
+ process.off("SIGINT", handleSigint);
48
+ };
49
+ }, [exit]);
50
+
51
+ if (err) {
52
+ return <Text color="red">Failed to fetch: {IMAGE_PATH}</Text>;
53
+ }
54
+
55
+ if (!dimensions) {
56
+ return <Text>Loading...</Text>;
57
+ }
58
+
59
+ return (
60
+ <Box flexDirection="column">
61
+ <InkPictureProvider>
62
+ <Image
63
+ src={IMAGE_PATH}
64
+ width={dimensions.width}
65
+ height={dimensions.height}
66
+ pixelWidth={dimensions.pixelWidth}
67
+ pixelHeight={dimensions.pixelHeight}
68
+ alt={isUrl(IMAGE_PATH) ? "url-image" : "local-image"}
69
+ />
70
+ </InkPictureProvider>
71
+ </Box>
72
+ );
73
+ }
74
+
75
+ const { waitUntilExit } = render(<App />);
76
+ await waitUntilExit();