mjpic 1.0.6 → 1.0.8

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.
Files changed (20) hide show
  1. package/.vercel/project.json +1 -0
  2. package/README.md +105 -0
  3. package/api/cli.ts +42 -3
  4. package/deploy/mjpic-start.desktop +20 -0
  5. package/deploy/node_install.sh +111 -0
  6. package/deploy//344/275/277/347/224/250/350/257/264/346/230/216.txt +17 -0
  7. package/dist/cli/cli.js +36 -2
  8. package/dist/client/assets/{index-BQfYCBRX.js → index-C4AVPLLP.js} +25 -25
  9. package/dist/client/index.html +1 -1
  10. package/package.json +4 -3
  11. package/scripts/get-random-port.js +40 -0
  12. package/src/components/dialogs/SaveDialog.tsx +1 -1
  13. package/src/components/layout/Header.tsx +43 -18
  14. package/src/components/layout/RightPanel.tsx +0 -21
  15. package/vite.config.ts +3 -1
  16. package/tmp/guangxi.jpg +0 -0
  17. package//345/217/202/350/200/203/345/233/276/347/211/207//346/210/252/345/261/2172026-02-18 16.47.45_/345/233/276/347/211/207/345/260/272/345/257/270/350/260/203/350/212/202_/351/242/204/350/256/276/345/260/272/345/257/270.jpg +0 -0
  18. package//345/217/202/350/200/203/345/233/276/347/211/207//346/210/252/345/261/2172026-02-18 16.47.51_/345/233/276/347/211/207/345/260/272/345/257/270/350/260/203/350/212/202_/346/211/213/345/267/245/350/276/223/345/205/245/345/260/272/345/257/270.jpg +0 -0
  19. package//345/217/202/350/200/203/345/233/276/347/211/207//346/210/252/345/261/2172026-02-18 16.54.56_/345/233/276/347/211/207/345/260/272/345/257/270/350/260/203/350/212/202_/346/267/273/345/212/240/345/270/270/347/224/250/345/260/272/345/257/270.jpg +0 -0
  20. package//345/217/202/350/200/203/345/233/276/347/211/207//346/210/252/345/261/2172026-02-18 16.55.11_/345/233/276/347/211/207/345/260/272/345/257/270/350/260/203/350/212/202_/345/210/240/351/231/244/345/270/270/347/224/250/345/260/272/345/257/270.jpg +0 -0
@@ -6,7 +6,7 @@
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
7
  <meta name="description" content="敏捷图片(mjpic)是一个轻量级网页版图片处理工具,设计灵感来源于光影魔术手。" />
8
8
  <title>敏捷图片 (mjpic) - 轻量级网页版图片处理工具</title>
9
- <script type="module" crossorigin src="/assets/index-BQfYCBRX.js"></script>
9
+ <script type="module" crossorigin src="/assets/index-C4AVPLLP.js"></script>
10
10
  <link rel="stylesheet" crossorigin href="/assets/index-BoiS81Ei.css">
11
11
  </head>
12
12
  <body>
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mjpic",
3
3
  "description": "敏捷图片(mjpic)是一个轻量级网页版图片处理工具,设计灵感来源于光影魔术手。",
4
- "version": "1.0.6",
4
+ "version": "1.0.8",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "mjpic": "./dist/cli/cli.js"
@@ -14,8 +14,8 @@
14
14
  "lint": "eslint .",
15
15
  "preview": "vite preview",
16
16
  "check": "tsc --noEmit",
17
- "server:dev": "nodemon",
18
- "dev": "concurrently \"npm run client:dev\" \"npm run server:dev\""
17
+ "server:dev": "PORT=${PORT:-3002} nodemon",
18
+ "dev": "export PORT=$(node scripts/get-random-port.js) && export CLIENT_PORT=$(node scripts/get-random-port.js) && echo \"Starting Backend on $PORT, Frontend on $CLIENT_PORT\" && concurrently \"npm run client:dev\" \"npm run server:dev\""
19
19
  },
20
20
  "dependencies": {
21
21
  "@dnd-kit/core": "^6.3.1",
@@ -30,6 +30,7 @@
30
30
  "i18next-browser-languagedetector": "^8.2.1",
31
31
  "konva": "^9.3.16",
32
32
  "lucide-react": "^0.511.0",
33
+ "mjpic": "^1.0.7",
33
34
  "open": "^11.0.0",
34
35
  "react": "^18.3.1",
35
36
  "react-dom": "^18.3.1",
@@ -0,0 +1,40 @@
1
+ import net from 'net';
2
+
3
+ // Check if a port is available
4
+ const isPortAvailable = (port) => {
5
+ return new Promise((resolve) => {
6
+ const server = net.createServer();
7
+ server.once('error', () => resolve(false));
8
+ server.once('listening', () => {
9
+ server.close();
10
+ resolve(true);
11
+ });
12
+ server.listen(port);
13
+ });
14
+ };
15
+
16
+ // Generate a random port between min and max, excluding common ports
17
+ const getRandomPort = async () => {
18
+ const commonPorts = [3000, 5000, 5173, 8000, 8080, 8888, 4200];
19
+ const min = 10000;
20
+ const max = 65535;
21
+
22
+ // Try up to 50 times to find a port
23
+ for (let i = 0; i < 50; i++) {
24
+ const port = Math.floor(Math.random() * (max - min + 1) + min);
25
+ if (!commonPorts.includes(port) && await isPortAvailable(port)) {
26
+ return port;
27
+ }
28
+ }
29
+
30
+ // Fallback to 0 (let OS choose)
31
+ return 0;
32
+ };
33
+
34
+ // Run if called directly
35
+ const run = async () => {
36
+ const port = await getRandomPort();
37
+ console.log(port);
38
+ };
39
+
40
+ run();
@@ -137,7 +137,7 @@ export const SaveDialog = ({ isOpen, onClose, onConfirm, defaultPath, defaultFil
137
137
  {t('common.cancel')}
138
138
  </button>
139
139
  <button
140
- onClick={() => onConfirm(format, quality / 100, savePath, fileName)}
140
+ onClick={() => onConfirm(format, quality, savePath, fileName)}
141
141
  className="flex-1 px-3 py-2 rounded bg-blue-600 hover:bg-blue-500 text-white text-sm transition-colors"
142
142
  >
143
143
  {t('common.confirm')}
@@ -39,30 +39,55 @@ export const Header = ({ stageRef }: HeaderProps) => {
39
39
  if (!stageRef.current) return;
40
40
 
41
41
  try {
42
- // Find the image node to get current scale
43
- const imageNode = stageRef.current.findOne('Image') as Konva.Image;
44
- // Find the content group (which contains image + border)
45
- const contentGroup = stageRef.current.findOne('#content-group') as Konva.Group;
42
+ // Get the current state from the image store
43
+ const { previewImage, config, originalWidth, originalHeight } = useImageStore.getState();
46
44
 
47
- if (!imageNode || !contentGroup) {
48
- console.error('No image or content group found to save');
45
+ if (!previewImage) {
46
+ console.error('No image found to save');
49
47
  return;
50
48
  }
51
-
52
- // Store current scale to restore later
53
- const currentScaleX = imageNode.scaleX();
54
49
 
55
- // Calculate pixelRatio needed to get original resolution
56
- // The content is scaled down by currentScaleX to fit screen.
57
- // We need to scale it back up by 1/currentScaleX.
58
- const pixelRatio = 1 / currentScaleX;
59
-
60
- const dataUrl = contentGroup.toDataURL({
61
- pixelRatio: pixelRatio,
62
- mimeType: format,
63
- quality: quality
50
+ // Determine the final dimensions based on resize config
51
+ let finalWidth = originalWidth || 800;
52
+ let finalHeight = originalHeight || 600;
53
+
54
+ if (config.resize && config.resize.width > 0 && config.resize.height > 0) {
55
+ finalWidth = config.resize.width;
56
+ finalHeight = config.resize.height;
57
+ }
58
+
59
+ // Create a temporary canvas with the correct dimensions
60
+ const canvas = document.createElement('canvas');
61
+ canvas.width = finalWidth;
62
+ canvas.height = finalHeight;
63
+ const ctx = canvas.getContext('2d');
64
+
65
+ if (!ctx) {
66
+ console.error('Failed to create canvas context');
67
+ return;
68
+ }
69
+
70
+ // Set high quality rendering
71
+ ctx.imageSmoothingEnabled = true;
72
+ ctx.imageSmoothingQuality = 'high';
73
+
74
+ // Load the preview image as the source
75
+ const img = new Image();
76
+ img.crossOrigin = 'anonymous';
77
+
78
+ await new Promise<void>((resolve, reject) => {
79
+ img.onload = () => {
80
+ // Draw the image with the correct dimensions
81
+ ctx.drawImage(img, 0, 0, finalWidth, finalHeight);
82
+ resolve();
83
+ };
84
+ img.onerror = reject;
85
+ img.src = previewImage;
64
86
  });
65
87
 
88
+ // Convert canvas to data URL with correct quality
89
+ const dataUrl = canvas.toDataURL(format, quality / 100);
90
+
66
91
  // Check if we are in CLI mode with an opened file
67
92
  // If savePath is provided (from dialog), use it
68
93
  if (savePath) {
@@ -428,27 +428,6 @@ export const RightPanel = () => {
428
428
  <label htmlFor="maintainAspectRatio" className="text-xs text-zinc-400 cursor-pointer">{t('common.lockAspectRatio')}</label>
429
429
  </div>
430
430
  </div>
431
-
432
- <div className="flex gap-3 mt-2">
433
- <button
434
- onClick={() => {
435
- // 确定按钮的逻辑
436
- // 这里可以添加额外的验证或处理
437
- }}
438
- className="flex-1 bg-blue-600 hover:bg-blue-700 text-white py-2 rounded text-sm font-medium"
439
- >
440
- {t('common.confirm')}
441
- </button>
442
- <button
443
- onClick={() => {
444
- // 取消按钮的逻辑
445
- // 可以重置为之前的尺寸或保持不变
446
- }}
447
- className="flex-1 bg-zinc-700 hover:bg-zinc-600 text-zinc-200 py-2 rounded text-sm"
448
- >
449
- {t('common.cancel')}
450
- </button>
451
- </div>
452
431
  </div>
453
432
  </div>
454
433
  )}
package/vite.config.ts CHANGED
@@ -15,9 +15,11 @@ export default defineConfig({
15
15
  tsconfigPaths(),
16
16
  ],
17
17
  server: {
18
+ port: parseInt(process.env.CLIENT_PORT || '5173'),
19
+ strictPort: true, // Fail if port is busy
18
20
  proxy: {
19
21
  '/api': {
20
- target: 'http://localhost:3002',
22
+ target: `http://localhost:${process.env.PORT || 3002}`,
21
23
  changeOrigin: true,
22
24
  secure: false,
23
25
  configure: (proxy, _options) => {
package/tmp/guangxi.jpg DELETED
Binary file