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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xw-devtool-cli",
3
- "version": "1.0.34",
3
+ "version": "1.0.35",
4
4
  "type": "module",
5
5
  "description": "基于node的开发者助手cli",
6
6
  "main": "index.js",
@@ -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
- 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.WindowState=FormWindowState.Maximized;
31
- this.BackColor=Color.Fuchsia;
32
- this.TransparencyKey=this.BackColor;
33
- this.DoubleBuffered=true;
34
- this.Cursor = Cursors.Default;
35
- }
36
- protected override void OnShown(EventArgs e){
37
- base.OnShown(e);
38
- int ex=GetWindowLong(this.Handle,GWL_EXSTYLE);
39
- SetWindowLong(this.Handle,GWL_EXSTYLE,ex|WS_EX_LAYERED|WS_EX_TRANSPARENT);
40
- t=new Timer();
41
- t.Interval=16;
42
- t.Tick+=(s,ev)=>{ this.Invalidate(); };
43
- t.Start();
44
- }
45
- protected override void OnPaint(PaintEventArgs e){
46
- var p=Cursor.Position;
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
- g.DrawLine(pen,0,p.Y,this.Width,p.Y);
50
- g.DrawLine(pen,p.X,0,p.X,this.Height);
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
- stdin.removeListener('data', onData);
85
- try { stdin.setRawMode(false); } catch {}
86
- stdin.pause();
87
- process.removeListener('exit', exitHandler);
88
- process.removeListener('SIGINT', exitHandler);
89
- cleanup();
90
- resolve();
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.once('data', onData);
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 compressOne(inputPath, quality, outDir, baseDir, preserveStructure) {
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
- const outPath = path.join(targetDir, `${name}_compressed_${ts}${ext}`);
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
@@ -155,6 +155,7 @@ export default {
155
155
  openFail: '打开目录失败',
156
156
  opened: '已打开输出目录',
157
157
  scanResult: '扫描到图片数量',
158
- preservePrompt: '递归时是否按原始目录结构输出'
158
+ preservePrompt: '递归时是否按原始目录结构输出',
159
+ timestampPrompt: '文件夹压缩是否在文件名添加时间戳后缀'
159
160
  }
160
161
  };