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.
- package/.vercel/project.json +1 -0
- package/README.md +105 -0
- package/api/cli.ts +42 -3
- package/deploy/mjpic-start.desktop +20 -0
- package/deploy/node_install.sh +111 -0
- package/deploy//344/275/277/347/224/250/350/257/264/346/230/216.txt +17 -0
- package/dist/cli/cli.js +36 -2
- package/dist/client/assets/{index-BQfYCBRX.js → index-C4AVPLLP.js} +25 -25
- package/dist/client/index.html +1 -1
- package/package.json +4 -3
- package/scripts/get-random-port.js +40 -0
- package/src/components/dialogs/SaveDialog.tsx +1 -1
- package/src/components/layout/Header.tsx +43 -18
- package/src/components/layout/RightPanel.tsx +0 -21
- package/vite.config.ts +3 -1
- package/tmp/guangxi.jpg +0 -0
- 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
- 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
- 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
- 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
package/dist/client/index.html
CHANGED
|
@@ -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-
|
|
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.
|
|
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
|
|
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
|
-
//
|
|
43
|
-
const
|
|
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 (!
|
|
48
|
-
console.error('No image
|
|
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
|
-
//
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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:
|
|
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
|