xw-devtool-cli 1.0.33 → 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.33",
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}`);
@@ -25,6 +25,7 @@ public class DistanceForm : Form {
25
25
  private bool p1Set = false;
26
26
  private bool p2Set = false;
27
27
  private Bitmap screenCapture;
28
+ private Timer t;
28
29
 
29
30
  public DistanceForm() {
30
31
  this.FormBorderStyle = FormBorderStyle.None;
@@ -47,6 +48,11 @@ public class DistanceForm : Form {
47
48
  g.CopyFromScreen(totalBounds.Location, Point.Empty, totalBounds.Size);
48
49
  }
49
50
  this.BackgroundImage = screenCapture;
51
+
52
+ t = new Timer();
53
+ t.Interval = 33;
54
+ t.Tick += (s, e) => { this.Invalidate(); };
55
+ t.Start();
50
56
  }
51
57
 
52
58
  private Point GetSnappedPoint(Point current) {
@@ -95,9 +101,7 @@ public class DistanceForm : Form {
95
101
  }
96
102
 
97
103
  protected override void OnMouseMove(MouseEventArgs e) {
98
- if (p1Set && !p2Set) {
99
- this.Invalidate();
100
- }
104
+ this.Invalidate();
101
105
  }
102
106
 
103
107
  protected override void OnPaint(PaintEventArgs e) {
@@ -105,10 +109,18 @@ public class DistanceForm : Form {
105
109
  g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
106
110
 
107
111
  Pen pen = new Pen(Color.Red, 2);
112
+ Pen crossPen = new Pen(Color.Red, 1);
108
113
  Brush brush = Brushes.Red;
109
114
  Font font = new Font("Segoe UI", 12, FontStyle.Bold);
110
115
  Brush textBg = new SolidBrush(Color.FromArgb(180, 0, 0, 0));
111
116
 
117
+ Point cp = this.PointToClient(Cursor.Position);
118
+ int gap = 24;
119
+ g.DrawLine(crossPen, 0, cp.Y, Math.Max(0, cp.X - gap), cp.Y);
120
+ g.DrawLine(crossPen, Math.Min(this.ClientSize.Width, cp.X + gap), cp.Y, this.ClientSize.Width, cp.Y);
121
+ g.DrawLine(crossPen, cp.X, 0, cp.X, Math.Max(0, cp.Y - gap));
122
+ g.DrawLine(crossPen, cp.X, Math.Min(this.ClientSize.Height, cp.Y + gap), cp.X, this.ClientSize.Height);
123
+
112
124
  if (p1Set) {
113
125
  Point target = p2Set ? p2 : GetSnappedPoint(this.PointToClient(Cursor.Position));
114
126
 
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
  };