Skip to content

Commit 0d3a3be

Browse files
committed
improve code quality and user experience
1 parent a2dacc5 commit 0d3a3be

File tree

8 files changed

+371
-17
lines changed

8 files changed

+371
-17
lines changed

bin/builders/BaseBuilder.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@ export default abstract class BaseBuilder {
173173
`cd "${npmDirectory}" && ${this.getBuildCommand(packageManager)}`,
174174
this.getBuildTimeout(),
175175
buildEnv,
176+
this.options.debug,
176177
);
177178

178179
// Copy app

bin/helpers/merge.ts

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@ import { generateSafeFilename, generateIdentifierSafeName } from '@/utils/name';
77
import { PakeAppOptions, PlatformMap } from '@/types';
88
import { tauriConfigDirectory, npmDirectory } from '@/utils/dir';
99

10+
/**
11+
* Helper function to generate safe lowercase app name for file paths
12+
*/
13+
function getSafeAppName(name: string): string {
14+
return generateSafeFilename(name).toLowerCase();
15+
}
16+
1017
export async function mergeConfig(
1118
url: string,
1219
options: PakeAppOptions,
@@ -153,7 +160,7 @@ export async function mergeConfig(
153160
delete tauriConf.bundle.linux.deb.files;
154161

155162
// Generate correct desktop file configuration
156-
const appNameSafe = generateSafeFilename(name).toLowerCase();
163+
const appNameSafe = getSafeAppName(name);
157164
const identifier = `com.pake.${appNameSafe}`;
158165
const desktopFileName = `${identifier}.desktop`;
159166

@@ -212,22 +219,23 @@ StartupNotify=true
212219
}
213220

214221
// Set icon.
222+
const safeAppName = getSafeAppName(name);
215223
const platformIconMap: PlatformMap = {
216224
win32: {
217225
fileExt: '.ico',
218-
path: `png/${generateSafeFilename(name).toLowerCase()}_256.ico`,
226+
path: `png/${safeAppName}_256.ico`,
219227
defaultIcon: 'png/icon_256.ico',
220228
message: 'Windows icon must be .ico and 256x256px.',
221229
},
222230
linux: {
223231
fileExt: '.png',
224-
path: `png/${generateSafeFilename(name).toLowerCase()}_512.png`,
232+
path: `png/${safeAppName}_512.png`,
225233
defaultIcon: 'png/icon_512.png',
226234
message: 'Linux icon must be .png and 512x512px.',
227235
},
228236
darwin: {
229237
fileExt: '.icns',
230-
path: `icons/${generateSafeFilename(name).toLowerCase()}.icns`,
238+
path: `icons/${safeAppName}.icns`,
231239
defaultIcon: 'icons/icon.icns',
232240
message: 'macOS icon must be .icns type.',
233241
},
@@ -278,9 +286,9 @@ StartupNotify=true
278286
if (iconExt == '.png' || iconExt == '.ico') {
279287
const trayIcoPath = path.join(
280288
npmDirectory,
281-
`src-tauri/png/${generateSafeFilename(name).toLowerCase()}${iconExt}`,
289+
`src-tauri/png/${safeAppName}${iconExt}`,
282290
);
283-
trayIconPath = `png/${generateSafeFilename(name).toLowerCase()}${iconExt}`;
291+
trayIconPath = `png/${safeAppName}${iconExt}`;
284292
await fsExtra.copy(systemTrayIcon, trayIcoPath);
285293
} else {
286294
logger.warn(

dist/cli.js

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,12 @@ function generateIdentifierSafeName(name) {
422422
return cleaned;
423423
}
424424

425+
/**
426+
* Helper function to generate safe lowercase app name for file paths
427+
*/
428+
function getSafeAppName(name) {
429+
return generateSafeFilename(name).toLowerCase();
430+
}
425431
async function mergeConfig(url, options, tauriConf) {
426432
// Ensure .pake directory exists and copy source templates if needed
427433
const srcTauriDir = path.join(npmDirectory, 'src-tauri');
@@ -512,7 +518,7 @@ async function mergeConfig(url, options, tauriConf) {
512518
// Remove hardcoded desktop files and regenerate with correct app name
513519
delete tauriConf.bundle.linux.deb.files;
514520
// Generate correct desktop file configuration
515-
const appNameSafe = generateSafeFilename(name).toLowerCase();
521+
const appNameSafe = getSafeAppName(name);
516522
const identifier = `com.pake.${appNameSafe}`;
517523
const desktopFileName = `${identifier}.desktop`;
518524
// Create desktop file content
@@ -563,22 +569,23 @@ StartupNotify=true
563569
}
564570
}
565571
// Set icon.
572+
const safeAppName = getSafeAppName(name);
566573
const platformIconMap = {
567574
win32: {
568575
fileExt: '.ico',
569-
path: `png/${generateSafeFilename(name).toLowerCase()}_256.ico`,
576+
path: `png/${safeAppName}_256.ico`,
570577
defaultIcon: 'png/icon_256.ico',
571578
message: 'Windows icon must be .ico and 256x256px.',
572579
},
573580
linux: {
574581
fileExt: '.png',
575-
path: `png/${generateSafeFilename(name).toLowerCase()}_512.png`,
582+
path: `png/${safeAppName}_512.png`,
576583
defaultIcon: 'png/icon_512.png',
577584
message: 'Linux icon must be .png and 512x512px.',
578585
},
579586
darwin: {
580587
fileExt: '.icns',
581-
path: `icons/${generateSafeFilename(name).toLowerCase()}.icns`,
588+
path: `icons/${safeAppName}.icns`,
582589
defaultIcon: 'icons/icon.icns',
583590
message: 'macOS icon must be .icns type.',
584591
},
@@ -622,8 +629,8 @@ StartupNotify=true
622629
// 需要判断图标格式,默认只支持ico和png两种
623630
let iconExt = path.extname(systemTrayIcon).toLowerCase();
624631
if (iconExt == '.png' || iconExt == '.ico') {
625-
const trayIcoPath = path.join(npmDirectory, `src-tauri/png/${generateSafeFilename(name).toLowerCase()}${iconExt}`);
626-
trayIconPath = `png/${generateSafeFilename(name).toLowerCase()}${iconExt}`;
632+
const trayIcoPath = path.join(npmDirectory, `src-tauri/png/${safeAppName}${iconExt}`);
633+
trayIconPath = `png/${safeAppName}${iconExt}`;
627634
await fsExtra.copy(systemTrayIcon, trayIcoPath);
628635
}
629636
else {
@@ -799,7 +806,7 @@ class BaseBuilder {
799806
...this.getBuildEnvironment(),
800807
...(process.env.NO_STRIP && { NO_STRIP: process.env.NO_STRIP }),
801808
};
802-
await shellExec(`cd "${npmDirectory}" && ${this.getBuildCommand(packageManager)}`, this.getBuildTimeout(), buildEnv);
809+
await shellExec(`cd "${npmDirectory}" && ${this.getBuildCommand(packageManager)}`, this.getBuildTimeout(), buildEnv, this.options.debug);
803810
// Copy app
804811
const fileName = this.getFileName();
805812
const fileType = this.getFileType(target);

docs/advanced-usage.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,44 @@ document.addEventListener("keydown", (e) => {
3838
});
3939
```
4040

41+
## Built-in Features
42+
43+
### Download Error Notifications
44+
45+
Pake automatically provides user-friendly download error notifications:
46+
47+
**Features:**
48+
49+
- **Bilingual Support**: Automatically detects browser language (Chinese/English)
50+
- **System Notifications**: Uses native OS notifications when permission is granted
51+
- **Graceful Fallback**: Falls back to console logging if notifications are unavailable
52+
- **Comprehensive Coverage**: Handles all download types (HTTP, Data URI, Blob)
53+
54+
**User Experience:**
55+
56+
When a download fails, users will see a notification:
57+
58+
- English: "Download Error - Download failed: filename.pdf"
59+
- Chinese: "下载错误 - 下载失败: filename.pdf"
60+
61+
**Requesting Notification Permission:**
62+
63+
To enable notifications, add this to your injected JavaScript:
64+
65+
```javascript
66+
// Request notification permission on app start
67+
if (window.Notification && Notification.permission === "default") {
68+
Notification.requestPermission();
69+
}
70+
```
71+
72+
The download system automatically handles:
73+
74+
- Regular HTTP(S) downloads
75+
- Data URI downloads (base64 encoded files)
76+
- Blob URL downloads (dynamically generated files)
77+
- Context menu initiated downloads
78+
4179
## Container Communication
4280

4381
Send messages between web content and Pake container.

docs/advanced-usage_CN.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,41 @@
4040
});
4141
```
4242
43+
## 内置功能
44+
45+
### 下载错误通知
46+
47+
Pake 自动提供用户友好的下载错误通知:
48+
49+
**功能特性:**
50+
- **双语支持**:自动检测浏览器语言(中文/英文)
51+
- **系统通知**:在授予权限后使用原生操作系统通知
52+
- **优雅降级**:如果通知不可用则降级到控制台日志
53+
- **全面覆盖**:处理所有下载类型(HTTP、Data URI、Blob)
54+
55+
**用户体验:**
56+
57+
当下载失败时,用户将看到通知:
58+
- 英文:"Download Error - Download failed: filename.pdf"
59+
- 中文:"下载错误 - 下载失败: filename.pdf"
60+
61+
**请求通知权限:**
62+
63+
要启用通知,请在注入的 JavaScript 中添加:
64+
65+
```javascript
66+
// 在应用启动时请求通知权限
67+
if (window.Notification && Notification.permission === "default") {
68+
Notification.requestPermission();
69+
}
70+
```
71+
72+
下载系统自动处理:
73+
- 常规 HTTP(S) 下载
74+
- Data URI 下载(base64 编码文件)
75+
- Blob URL 下载(动态生成的文件)
76+
- 右键菜单发起的下载
77+
4378
## 容器通信
4479
4580
在网页内容和 Pake 容器之间发送消息。

src-tauri/src/inject/event.js

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,22 @@ function isChineseLanguage(language = getUserLanguage()) {
148148
);
149149
}
150150

151+
// User notification helper
152+
function showDownloadError(filename) {
153+
const isChinese = isChineseLanguage();
154+
const message = isChinese
155+
? `下载失败: ${filename}`
156+
: `Download failed: ${filename}`;
157+
158+
if (window.Notification && Notification.permission === "granted") {
159+
new Notification(isChinese ? "下载错误" : "Download Error", {
160+
body: message,
161+
});
162+
} else {
163+
console.error(message);
164+
}
165+
}
166+
151167
// Unified file detection - replaces both isDownloadLink and isFileLink
152168
function isDownloadableFile(url) {
153169
try {
@@ -251,7 +267,7 @@ document.addEventListener("DOMContentLoaded", () => {
251267
}
252268

253269
// write the ArrayBuffer to a binary, and you're done
254-
const userLanguage = navigator.language || navigator.userLanguage;
270+
const userLanguage = getUserLanguage();
255271
invoke("download_file_by_binary", {
256272
params: {
257273
filename,
@@ -260,16 +276,18 @@ document.addEventListener("DOMContentLoaded", () => {
260276
},
261277
}).catch((error) => {
262278
console.error("Failed to download data URI file:", filename, error);
279+
showDownloadError(filename);
263280
});
264281
} catch (error) {
265282
console.error("Failed to process data URI:", dataURI, error);
283+
showDownloadError(filename || "file");
266284
}
267285
}
268286

269287
function downloadFromBlobUrl(blobUrl, filename) {
270288
convertBlobUrlToBinary(blobUrl)
271289
.then((binary) => {
272-
const userLanguage = navigator.language || navigator.userLanguage;
290+
const userLanguage = getUserLanguage();
273291
invoke("download_file_by_binary", {
274292
params: {
275293
filename,
@@ -278,10 +296,12 @@ document.addEventListener("DOMContentLoaded", () => {
278296
},
279297
}).catch((error) => {
280298
console.error("Failed to download blob file:", filename, error);
299+
showDownloadError(filename);
281300
});
282301
})
283302
.catch((error) => {
284303
console.error("Failed to convert blob to binary:", blobUrl, error);
304+
showDownloadError(filename);
285305
});
286306
}
287307

@@ -659,13 +679,16 @@ document.addEventListener("DOMContentLoaded", () => {
659679
}
660680
} else {
661681
// Regular HTTP(S) image
662-
const userLanguage = navigator.language || navigator.userLanguage;
682+
const userLanguage = getUserLanguage();
663683
invoke("download_file", {
664684
params: {
665685
url: imageUrl,
666686
filename: filename,
667687
language: userLanguage,
668688
},
689+
}).catch((error) => {
690+
console.error("Failed to download image:", filename, error);
691+
showDownloadError(filename);
669692
});
670693
}
671694
}
@@ -713,7 +736,7 @@ document.addEventListener("DOMContentLoaded", () => {
713736

714737
// Simplified menu builder
715738
function buildMenuItems(type, data) {
716-
const userLanguage = navigator.language || navigator.userLanguage;
739+
const userLanguage = getUserLanguage();
717740
const items = [];
718741

719742
switch (type) {
@@ -740,6 +763,9 @@ document.addEventListener("DOMContentLoaded", () => {
740763
const filename = getFilenameFromUrl(data.url);
741764
invoke("download_file", {
742765
params: { url: data.url, filename, language: userLanguage },
766+
}).catch((error) => {
767+
console.error("Failed to download file:", filename, error);
768+
showDownloadError(filename);
743769
});
744770
}),
745771
);

tests/index.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import fs from "fs";
1212
import path from "path";
1313
import ora from "ora";
1414
import config, { TIMEOUTS, TEST_URLS } from "./config.js";
15+
import { runHelperTests } from "./unit/helpers.test.js";
1516

1617
class PakeTestRunner {
1718
constructor() {
@@ -42,6 +43,12 @@ class PakeTestRunner {
4243
console.log("📋 Running Unit Tests...");
4344
await this.runUnitTests();
4445
testCount++;
46+
47+
// Run helper function tests
48+
const helperTestsPassed = await runHelperTests();
49+
if (!helperTestsPassed) {
50+
console.log("⚠️ Some helper tests failed");
51+
}
4552
}
4653

4754
if (integration && !quick) {

0 commit comments

Comments
 (0)