xw-devtool-cli 1.0.34 → 1.0.35
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/package.json +1 -1
- package/src/commands/crosshair.js +47 -39
- package/src/commands/imgCompress.js +29 -4
- package/src/locales/en.js +2 -1
- package/src/locales/zh.js +2 -1
package/package.json
CHANGED
|
@@ -16,40 +16,45 @@ using System;
|
|
|
16
16
|
using System.Drawing;
|
|
17
17
|
using System.Windows.Forms;
|
|
18
18
|
using System.Runtime.InteropServices;
|
|
19
|
-
public class OverlayForm : Form {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
19
|
+
public class OverlayForm : Form {
|
|
20
|
+
const int WS_EX_TRANSPARENT=0x20;
|
|
21
|
+
const int WS_EX_LAYERED=0x80000;
|
|
22
|
+
const int GWL_EXSTYLE=-20;
|
|
23
|
+
[DllImport("user32.dll")] static extern int GetWindowLong(IntPtr hWnd, int nIndex);
|
|
24
|
+
[DllImport("user32.dll")] static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
|
|
25
|
+
Timer t;
|
|
26
|
+
public OverlayForm(){
|
|
27
|
+
this.FormBorderStyle=FormBorderStyle.None;
|
|
28
|
+
this.TopMost=true;
|
|
29
|
+
this.ShowInTaskbar=false;
|
|
30
|
+
this.BackColor=Color.Fuchsia;
|
|
31
|
+
this.TransparencyKey=this.BackColor;
|
|
32
|
+
this.DoubleBuffered=true;
|
|
33
|
+
this.Cursor = Cursors.Default;
|
|
34
|
+
Rectangle totalBounds = Rectangle.Empty;
|
|
35
|
+
foreach(Screen s in Screen.AllScreens) {
|
|
36
|
+
totalBounds = Rectangle.Union(totalBounds, s.Bounds);
|
|
37
|
+
}
|
|
38
|
+
this.StartPosition = FormStartPosition.Manual;
|
|
39
|
+
this.Bounds = totalBounds;
|
|
40
|
+
}
|
|
41
|
+
protected override void OnShown(EventArgs e){
|
|
42
|
+
base.OnShown(e);
|
|
43
|
+
int ex=GetWindowLong(this.Handle,GWL_EXSTYLE);
|
|
44
|
+
SetWindowLong(this.Handle,GWL_EXSTYLE,ex|WS_EX_LAYERED|WS_EX_TRANSPARENT);
|
|
45
|
+
t=new Timer();
|
|
46
|
+
t.Interval=16;
|
|
47
|
+
t.Tick+=(s,ev)=>{ this.Invalidate(); };
|
|
48
|
+
t.Start();
|
|
49
|
+
}
|
|
50
|
+
protected override void OnPaint(PaintEventArgs e){
|
|
51
|
+
var p=this.PointToClient(Cursor.Position);
|
|
47
52
|
var g=e.Graphics;
|
|
48
53
|
using(var pen=new Pen(Color.Red,1)){
|
|
49
|
-
|
|
50
|
-
|
|
54
|
+
g.DrawLine(pen,0,p.Y,this.Width,p.Y);
|
|
55
|
+
g.DrawLine(pen,p.X,0,p.X,this.Height);
|
|
56
|
+
}
|
|
51
57
|
}
|
|
52
|
-
}
|
|
53
58
|
}
|
|
54
59
|
public static class Entry {
|
|
55
60
|
public static void Main(){ Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new OverlayForm()); }
|
|
@@ -81,14 +86,17 @@ public static class Entry {
|
|
|
81
86
|
try { stdin.setRawMode(true); } catch {}
|
|
82
87
|
stdin.resume();
|
|
83
88
|
const onData = (data) => {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
89
|
+
const s = data.toString('utf8');
|
|
90
|
+
if (s === '\x1b') {
|
|
91
|
+
stdin.removeListener('data', onData);
|
|
92
|
+
try { stdin.setRawMode(false); } catch {}
|
|
93
|
+
stdin.pause();
|
|
94
|
+
process.removeListener('exit', exitHandler);
|
|
95
|
+
process.removeListener('SIGINT', exitHandler);
|
|
96
|
+
cleanup();
|
|
97
|
+
resolve();
|
|
98
|
+
}
|
|
91
99
|
};
|
|
92
|
-
stdin.
|
|
100
|
+
stdin.on('data', onData);
|
|
93
101
|
});
|
|
94
102
|
}
|
|
@@ -124,7 +124,29 @@ async function pickPreserveStructure() {
|
|
|
124
124
|
return choice;
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
-
async function
|
|
127
|
+
async function pickTimestampSuffix() {
|
|
128
|
+
const choice = await selectFromMenu(i18next.t('imgCompress.timestampPrompt'), [
|
|
129
|
+
{ name: i18next.t('imgCompress.yes'), value: true },
|
|
130
|
+
{ name: i18next.t('imgCompress.no'), value: false }
|
|
131
|
+
], true, i18next.t('common.back'));
|
|
132
|
+
if (choice === '__BACK__') return '__BACK__';
|
|
133
|
+
return choice;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function uniqueOutPath(dir, baseName) {
|
|
137
|
+
let candidate = path.join(dir, baseName);
|
|
138
|
+
if (!fs.existsSync(candidate)) return candidate;
|
|
139
|
+
let idx = 1;
|
|
140
|
+
const ext = path.extname(baseName);
|
|
141
|
+
const name = path.basename(baseName, ext);
|
|
142
|
+
while (true) {
|
|
143
|
+
candidate = path.join(dir, `${name}_${idx}${ext}`);
|
|
144
|
+
if (!fs.existsSync(candidate)) return candidate;
|
|
145
|
+
idx++;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function compressOne(inputPath, quality, outDir, baseDir, preserveStructure, addTimestamp) {
|
|
128
150
|
const ext = path.extname(inputPath).toLowerCase();
|
|
129
151
|
const name = path.basename(inputPath, ext);
|
|
130
152
|
const ts = dayjs().format('YYYYMMDD_HHmmss');
|
|
@@ -134,12 +156,11 @@ async function compressOne(inputPath, quality, outDir, baseDir, preserveStructur
|
|
|
134
156
|
targetDir = path.join(outDir, rel);
|
|
135
157
|
fs.mkdirSync(targetDir, { recursive: true });
|
|
136
158
|
}
|
|
137
|
-
|
|
159
|
+
let outPath = addTimestamp ? path.join(targetDir, `${name}_compressed_${ts}${ext}`) : uniqueOutPath(targetDir, `${name}_compressed${ext}`);
|
|
138
160
|
const image = sharp(inputPath, { failOnError: false });
|
|
139
161
|
if (ext === '.jpg' || ext === '.jpeg') {
|
|
140
162
|
await image.jpeg({ quality, mozjpeg: true }).toFile(outPath);
|
|
141
163
|
} else if (ext === '.png') {
|
|
142
|
-
// Map quality (1-100) to compressionLevel (0-9) roughly
|
|
143
164
|
const level = Math.max(0, Math.min(9, Math.round((100 - quality) / 10)));
|
|
144
165
|
await image.png({ quality, compressionLevel: level }).toFile(outPath);
|
|
145
166
|
} else if (ext === '.webp') {
|
|
@@ -179,15 +200,19 @@ export async function imgCompressHandler() {
|
|
|
179
200
|
const outDir = path.resolve(baseDir, `compressed_${dayjs().format('YYYYMMDD_HHmmss')}`);
|
|
180
201
|
fs.mkdirSync(outDir, { recursive: true });
|
|
181
202
|
let preserveStructure = false;
|
|
203
|
+
let addTimestamp = true;
|
|
182
204
|
if (input.type === 'folder') {
|
|
183
205
|
const preserveChoice = await pickPreserveStructure();
|
|
184
206
|
if (preserveChoice === '__BACK__') return;
|
|
185
207
|
preserveStructure = preserveChoice;
|
|
208
|
+
const tsChoice = await pickTimestampSuffix();
|
|
209
|
+
if (tsChoice === '__BACK__') return;
|
|
210
|
+
addTimestamp = tsChoice;
|
|
186
211
|
}
|
|
187
212
|
const results = [];
|
|
188
213
|
for (const f of files) {
|
|
189
214
|
try {
|
|
190
|
-
const out = await compressOne(f, quality, outDir, baseDir, preserveStructure);
|
|
215
|
+
const out = await compressOne(f, quality, outDir, baseDir, preserveStructure, addTimestamp);
|
|
191
216
|
if (out) results.push(out);
|
|
192
217
|
} catch (e) {
|
|
193
218
|
console.error(`${i18next.t('imgCompress.fail')}: ${f} -> ${e.message}`);
|
package/src/locales/en.js
CHANGED
|
@@ -155,6 +155,7 @@ export default {
|
|
|
155
155
|
openFail: 'Failed to open directory',
|
|
156
156
|
opened: 'Opened output directory',
|
|
157
157
|
scanResult: 'Images found',
|
|
158
|
-
preservePrompt: 'Preserve original directory structure in output?'
|
|
158
|
+
preservePrompt: 'Preserve original directory structure in output?',
|
|
159
|
+
timestampPrompt: 'Append timestamp suffix to filename for folder compression?'
|
|
159
160
|
}
|
|
160
161
|
};
|
package/src/locales/zh.js
CHANGED