xw-devtool-cli 1.0.35 → 1.0.37

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.35",
3
+ "version": "1.0.37",
4
4
  "type": "module",
5
5
  "description": "基于node的开发者助手cli",
6
6
  "main": "index.js",
@@ -145,45 +145,35 @@ export async function colorPickHandler() {
145
145
  console.log(i18next.t('colorPick.notSupported'));
146
146
  return;
147
147
  }
148
- let overlay = null;
149
- let sampler = null;
150
- const readOneLine = (timeoutMs = 800) => new Promise((resolve) => {
151
- let settled = false;
152
- const onData = (buf) => {
153
- if (settled) return;
154
- settled = true;
155
- resolve(buf.toString('utf8').trim());
156
- };
157
- sampler.stdout.once('data', onData);
158
- if (timeoutMs > 0) {
159
- setTimeout(() => {
160
- if (settled) return;
161
- settled = true;
162
- try { sampler.stdout.removeListener('data', onData); } catch {}
163
- resolve(null);
164
- }, timeoutMs);
165
- }
166
- });
167
- try {
168
- const overlayScript = `
148
+ const overlayScript = `
169
149
  Add-Type -AssemblyName System.Windows.Forms; Add-Type -AssemblyName System.Drawing;
170
150
  Add-Type -TypeDefinition @'
171
151
  using System;
172
152
  using System.Drawing;
173
- using System.Windows.Forms;
174
153
  using System.Runtime.InteropServices;
154
+ using System.Windows.Forms;
175
155
  public class OverlayForm : Form {
176
156
  const int WS_EX_TRANSPARENT=0x20;
177
157
  const int WS_EX_LAYERED=0x80000;
178
158
  const int GWL_EXSTYLE=-20;
179
159
  [DllImport("user32.dll")] static extern int GetWindowLong(IntPtr hWnd, int nIndex);
180
160
  [DllImport("user32.dll")] static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
161
+ [DllImport("user32.dll")] static extern IntPtr GetDC(IntPtr hWnd);
162
+ [DllImport("user32.dll")] static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);
163
+ [DllImport("gdi32.dll")] static extern int GetPixel(IntPtr hdc, int x, int y);
181
164
  Timer t;
165
+ DateTime copyUntil = DateTime.MinValue;
166
+ string lastHex = "#000000";
167
+ string lastRgb = "RGB(0,0,0)";
182
168
  public OverlayForm(){
183
169
  this.FormBorderStyle=FormBorderStyle.None;
184
170
  this.TopMost=true;
185
171
  this.ShowInTaskbar=false;
186
- this.WindowState=FormWindowState.Maximized;
172
+ this.KeyPreview = true;
173
+ Rectangle totalBounds = Rectangle.Empty;
174
+ foreach(Screen s in Screen.AllScreens){ totalBounds = Rectangle.Union(totalBounds, s.Bounds); }
175
+ this.StartPosition = FormStartPosition.Manual;
176
+ this.Bounds = totalBounds;
187
177
  this.BackColor=Color.Fuchsia;
188
178
  this.TransparencyKey=this.BackColor;
189
179
  this.DoubleBuffered=true;
@@ -192,20 +182,55 @@ public class OverlayForm : Form {
192
182
  base.OnShown(e);
193
183
  int ex=GetWindowLong(this.Handle,GWL_EXSTYLE);
194
184
  SetWindowLong(this.Handle,GWL_EXSTYLE,ex|WS_EX_LAYERED|WS_EX_TRANSPARENT);
185
+ this.Focus();
186
+ this.Activate();
195
187
  t=new Timer();
196
188
  t.Interval=33;
197
189
  t.Tick+=(s,ev)=>{ this.Invalidate(); };
198
190
  t.Start();
199
191
  }
192
+ protected override void OnKeyDown(KeyEventArgs e){
193
+ if(e.KeyCode==Keys.Escape){ Application.Exit(); }
194
+ if(e.KeyCode==Keys.Enter){ Clipboard.SetText(lastHex); copyUntil = DateTime.Now.AddSeconds(2); }
195
+ }
200
196
  protected override void OnPaint(PaintEventArgs e){
201
- var p=Cursor.Position;
202
197
  var g=e.Graphics;
198
+ var p=this.PointToClient(Cursor.Position);
203
199
  int gap=16;
204
200
  using(var pen=new Pen(Color.Red,1)){
205
201
  g.DrawLine(pen,0,p.Y,Math.Max(0,p.X-gap),p.Y);
206
- g.DrawLine(pen,Math.Min(this.Width,p.X+gap),p.Y,this.Width,p.Y);
202
+ g.DrawLine(pen,Math.Min(this.ClientSize.Width,p.X+gap),p.Y,this.ClientSize.Width,p.Y);
207
203
  g.DrawLine(pen,p.X,0,p.X,Math.Max(0,p.Y-gap));
208
- g.DrawLine(pen,p.X,Math.Min(this.Height,p.Y+gap),p.X,this.Height);
204
+ g.DrawLine(pen,p.X,Math.Min(this.ClientSize.Height,p.Y+gap),p.X,this.ClientSize.Height);
205
+ }
206
+ IntPtr hdc = GetDC(IntPtr.Zero);
207
+ int color = GetPixel(hdc, Cursor.Position.X, Cursor.Position.Y);
208
+ ReleaseDC(IntPtr.Zero, hdc);
209
+ if(color!=-1){
210
+ int r = color & 0xFF;
211
+ int gC = (color >> 8) & 0xFF;
212
+ int b = (color >> 16) & 0xFF;
213
+ lastHex = String.Format("#{0:X2}{1:X2}{2:X2}", r, gC, b);
214
+ lastRgb = String.Format("RGB({0},{1},{2})", r, gC, b);
215
+ SizeF szHex = g.MeasureString(lastHex, new Font("Segoe UI", 12, FontStyle.Bold));
216
+ SizeF szRgb = g.MeasureString(lastRgb, new Font("Segoe UI", 10, FontStyle.Regular));
217
+ float pad = 12;
218
+ float boxW = Math.Max(szHex.Width, szRgb.Width) + pad*2 + 40;
219
+ float boxH = szHex.Height + szRgb.Height + pad*3;
220
+ float x = this.ClientSize.Width - boxW - 16;
221
+ float y = this.ClientSize.Height - boxH - 16;
222
+ using(var bg = new SolidBrush(Color.FromArgb(180,0,0,0))){ g.FillRectangle(bg, x, y, boxW, boxH); }
223
+ using(var preview = new SolidBrush(Color.FromArgb(r,gC,b))){ g.FillRectangle(preview, x+pad, y+pad, 36, boxH - pad*2); }
224
+ g.DrawString(lastHex, new Font("Segoe UI", 12, FontStyle.Bold), Brushes.White, x+pad+40, y+pad);
225
+ g.DrawString(lastRgb, new Font("Segoe UI", 10), Brushes.White, x+pad+40, y+pad+szHex.Height+6);
226
+ if(DateTime.Now < copyUntil){
227
+ string copied = "Copied: " + lastHex;
228
+ SizeF szC = g.MeasureString(copied, new Font("Segoe UI", 10));
229
+ float cx = x + pad + 40;
230
+ float cy = y + boxH - szC.Height - pad;
231
+ using(var bg2 = new SolidBrush(Color.FromArgb(160,0,128,0))){ g.FillRectangle(bg2, cx-6, cy-4, szC.Width+12, szC.Height+8); }
232
+ g.DrawString(copied, new Font("Segoe UI", 10), Brushes.White, cx, cy);
233
+ }
209
234
  }
210
235
  }
211
236
  }
@@ -215,75 +240,17 @@ public static class Entry {
215
240
  '@ -ReferencedAssemblies System.Windows.Forms,System.Drawing;
216
241
  [Entry]::Main()
217
242
  `;
218
- overlay = spawn('powershell', ['-NoProfile', '-Command', overlayScript], { windowsHide: true });
219
- const samplerScript = `
220
- Add-Type -TypeDefinition @'
221
- using System;
222
- using System.Runtime.InteropServices;
223
- public static class ColorSampler {
224
- [StructLayout(LayoutKind.Sequential)]
225
- public struct POINT { public int X; public int Y; }
226
- [DllImport("user32.dll")] static extern bool GetCursorPos(out POINT lpPoint);
227
- [DllImport("user32.dll")] static extern IntPtr GetDC(IntPtr hWnd);
228
- [DllImport("user32.dll")] static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);
229
- [DllImport("gdi32.dll")] static extern int GetPixel(IntPtr hdc, int x, int y);
230
- public static string Sample() {
231
- POINT p;
232
- if(!GetCursorPos(out p)) return "";
233
- IntPtr hdc = GetDC(IntPtr.Zero);
234
- if (hdc == IntPtr.Zero) return "";
235
- int color = GetPixel(hdc, p.X, p.Y);
236
- ReleaseDC(IntPtr.Zero, hdc);
237
- if (color == -1) return "";
238
- int r = color & 0xFF;
239
- int g = (color >> 8) & 0xFF;
240
- int b = (color >> 16) & 0xFF;
241
- return $"{r},{g},{b},255";
242
- }
243
- }
244
- '@
245
- function Sample {
246
- $s=[ColorSampler]::Sample();
247
- if([string]::IsNullOrEmpty($s)){ [Console]::Out.WriteLine(""); } else { [Console]::Out.WriteLine($s) }
248
- }
249
- while($true){
250
- $line = [Console]::In.ReadLine();
251
- if($line -eq $null){ break }
252
- if($line -eq 'SAMPLE'){ Sample }
253
- elseif($line -eq 'EXIT'){ break }
254
- }
255
- `;
256
- sampler = spawn('powershell', ['-NoProfile', '-Command', samplerScript], { windowsHide: true, stdio: ['pipe', 'pipe', 'ignore'] });
257
- try { sampler.stdout.setEncoding('utf8'); } catch {}
258
- } catch {}
259
- console.log(i18next.t('colorPick.moveMousePrompt'));
260
- let last = null;
261
- const sampleOnce = async () => {
243
+ const overlay = spawn('powershell', ['-NoProfile', '-Command', overlayScript], { windowsHide: true });
244
+ const sampleCmd = `powershell -NoProfile -Command "Add-Type -AssemblyName System.Windows.Forms; Add-Type -AssemblyName System.Drawing; $p=[System.Windows.Forms.Cursor]::Position; $bmp=New-Object System.Drawing.Bitmap 1,1; $g=[System.Drawing.Graphics]::FromImage($bmp); $g.CopyFromScreen($p.X,$p.Y,0,0,[System.Drawing.Size]::new(1,1)); $c=$bmp.GetPixel(0,0); Write-Output (\\"$($c.R),$($c.G),$($c.B),255\\"); $g.Dispose(); $bmp.Dispose();"`;
245
+ const sampleNow = () => {
262
246
  try {
263
- let out = null;
264
- if (sampler && sampler.stdin && !sampler.killed) {
265
- sampler.stdin.write('SAMPLE\n');
266
- out = await readOneLine(800);
267
- }
268
- if (!out) {
269
- const cmd = `powershell -NoProfile -Command "Add-Type -AssemblyName System.Windows.Forms; Add-Type -AssemblyName System.Drawing; $p=[System.Windows.Forms.Cursor]::Position; $bmp=New-Object System.Drawing.Bitmap 1,1; $g=[System.Drawing.Graphics]::FromImage($bmp); $g.CopyFromScreen($p.X,$p.Y,0,0,[System.Drawing.Size]::new(1,1)); $c=$bmp.GetPixel(0,0); Write-Output (\\"$($c.R),$($c.G),$($c.B),255\\"); $g.Dispose(); $bmp.Dispose();"`;
270
- out = execSync(cmd, { stdio: ['pipe', 'pipe', 'ignore'] }).toString('utf8').trim();
271
- }
247
+ const out = execSync(sampleCmd, { stdio: ['pipe', 'pipe', 'ignore'] }).toString('utf8').trim();
272
248
  const parts = out.split(',').map(n => parseInt(n, 10));
273
249
  const r = parts[0], g = parts[1], b = parts[2];
274
- last = { r, g, b, a: 1 };
275
- const tc = tinycolor({ r: last.r, g: last.g, b: last.b, a: last.a });
276
- const hex = last.a < 1 ? tc.toHex8String().toUpperCase() : tc.toHexString().toUpperCase();
277
- const rgb = tc.toRgbString();
278
- const hsl = tc.toHslString();
279
- console.log('\n=== ' + i18next.t('colorPick.result') + ' ===');
280
- console.log(`Hex: ${hex}`);
281
- console.log(`RGB: ${rgb}`);
282
- console.log(`HSL: ${hsl}`);
283
- console.log('==========================\n');
284
- const label = `${i18next.t('colorPreview.preview')} ${hex} ${rgb} ${hsl}`;
285
- printColorBar({ r: last.r, g: last.g, b: last.b }, 50, 2, label, last.a);
250
+ const tc = tinycolor({ r, g, b, a: 1 });
251
+ const hex = tc.toHexString().toUpperCase();
286
252
  copy(hex);
253
+ console.log(i18next.t('colorPreview.copied', { value: hex }));
287
254
  } catch {}
288
255
  };
289
256
  await new Promise((resolve) => {
@@ -292,24 +259,25 @@ while($true){
292
259
  stdin.resume();
293
260
  const onData = (data) => {
294
261
  const s = data.toString('utf8');
295
- if (s === ' ') {
296
- sampleOnce();
297
- } else if (s === '\r' || s === '\n') {
262
+ if (s === '\x1b') {
298
263
  stdin.removeListener('data', onData);
299
264
  try { stdin.setRawMode(false); } catch {}
300
265
  stdin.pause();
266
+ try { overlay.kill(); } catch {}
301
267
  resolve();
268
+ } else if (s === '\r' || s === '\n') {
269
+ sampleNow();
302
270
  }
303
271
  };
304
272
  stdin.on('data', onData);
273
+ overlay.on('close', () => {
274
+ stdin.removeListener('data', onData);
275
+ try { stdin.setRawMode(false); } catch {}
276
+ stdin.pause();
277
+ resolve();
278
+ });
305
279
  });
306
- try { if (sampler) { sampler.stdin.write('EXIT\n'); sampler.kill(); } } catch {}
307
- try { if (overlay) { overlay.kill(); } } catch {}
308
- if (!last) {
309
- console.log(i18next.t('colorPick.pickFail'));
310
- return;
311
- }
312
- picked = { r: last.r, g: last.g, b: last.b, a: last.a, region: null };
280
+ return;
313
281
  } else {
314
282
  const inputPath = await pickInputFile();
315
283
  if (inputPath === '__BACK__') {
@@ -124,8 +124,8 @@ async function pickPreserveStructure() {
124
124
  return choice;
125
125
  }
126
126
 
127
- async function pickTimestampSuffix() {
128
- const choice = await selectFromMenu(i18next.t('imgCompress.timestampPrompt'), [
127
+ async function pickSuffixOption() {
128
+ const choice = await selectFromMenu(i18next.t('imgCompress.suffixPrompt'), [
129
129
  { name: i18next.t('imgCompress.yes'), value: true },
130
130
  { name: i18next.t('imgCompress.no'), value: false }
131
131
  ], true, i18next.t('common.back'));
@@ -133,20 +133,7 @@ async function pickTimestampSuffix() {
133
133
  return choice;
134
134
  }
135
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) {
136
+ async function compressOne(inputPath, quality, outDir, baseDir, preserveStructure, addSuffix) {
150
137
  const ext = path.extname(inputPath).toLowerCase();
151
138
  const name = path.basename(inputPath, ext);
152
139
  const ts = dayjs().format('YYYYMMDD_HHmmss');
@@ -156,7 +143,7 @@ async function compressOne(inputPath, quality, outDir, baseDir, preserveStructur
156
143
  targetDir = path.join(outDir, rel);
157
144
  fs.mkdirSync(targetDir, { recursive: true });
158
145
  }
159
- let outPath = addTimestamp ? path.join(targetDir, `${name}_compressed_${ts}${ext}`) : uniqueOutPath(targetDir, `${name}_compressed${ext}`);
146
+ const outPath = addSuffix ? path.join(targetDir, `${name}_compressed_${ts}${ext}`) : path.join(targetDir, `${name}${ext}`);
160
147
  const image = sharp(inputPath, { failOnError: false });
161
148
  if (ext === '.jpg' || ext === '.jpeg') {
162
149
  await image.jpeg({ quality, mozjpeg: true }).toFile(outPath);
@@ -200,19 +187,19 @@ export async function imgCompressHandler() {
200
187
  const outDir = path.resolve(baseDir, `compressed_${dayjs().format('YYYYMMDD_HHmmss')}`);
201
188
  fs.mkdirSync(outDir, { recursive: true });
202
189
  let preserveStructure = false;
203
- let addTimestamp = true;
190
+ let addSuffix = input.type === 'folder';
204
191
  if (input.type === 'folder') {
205
192
  const preserveChoice = await pickPreserveStructure();
206
193
  if (preserveChoice === '__BACK__') return;
207
194
  preserveStructure = preserveChoice;
208
- const tsChoice = await pickTimestampSuffix();
209
- if (tsChoice === '__BACK__') return;
210
- addTimestamp = tsChoice;
195
+ const suffixChoice = await pickSuffixOption();
196
+ if (suffixChoice === '__BACK__') return;
197
+ addSuffix = suffixChoice;
211
198
  }
212
199
  const results = [];
213
200
  for (const f of files) {
214
201
  try {
215
- const out = await compressOne(f, quality, outDir, baseDir, preserveStructure, addTimestamp);
202
+ const out = await compressOne(f, quality, outDir, baseDir, preserveStructure, addSuffix);
216
203
  if (out) results.push(out);
217
204
  } catch (e) {
218
205
  console.error(`${i18next.t('imgCompress.fail')}: ${f} -> ${e.message}`);
package/src/locales/en.js CHANGED
@@ -156,6 +156,6 @@ export default {
156
156
  opened: 'Opened output directory',
157
157
  scanResult: 'Images found',
158
158
  preservePrompt: 'Preserve original directory structure in output?',
159
- timestampPrompt: 'Append timestamp suffix to filename for folder compression?'
159
+ suffixPrompt: 'Append filename suffix (compressed + timestamp)?'
160
160
  }
161
161
  };
package/src/locales/zh.js CHANGED
@@ -155,7 +155,8 @@ export default {
155
155
  openFail: '打开目录失败',
156
156
  opened: '已打开输出目录',
157
157
  scanResult: '扫描到图片数量',
158
- preservePrompt: '递归时是否按原始目录结构输出',
159
- timestampPrompt: '文件夹压缩是否在文件名添加时间戳后缀'
158
+ preservePrompt: '递归时是否按原始目录结构输出'
159
+ ,
160
+ suffixPrompt: '是否在文件名添加后缀(compressed+时间戳)'
160
161
  }
161
162
  };