🔀 merge dev
This commit is contained in:
16
.github/workflows/pake-cli.yaml
vendored
16
.github/workflows/pake-cli.yaml
vendored
@@ -83,7 +83,7 @@ jobs:
|
||||
profile: minimal
|
||||
override: true
|
||||
target: x86_64-unknown-linux-musl
|
||||
|
||||
|
||||
- name: Install Rust for windows-latest
|
||||
if: inputs.platform == 'windows-latest'
|
||||
uses: actions-rs/toolchain@v1
|
||||
@@ -92,7 +92,7 @@ jobs:
|
||||
profile: minimal
|
||||
override: true
|
||||
target: x86_64-pc-windows-msvc
|
||||
|
||||
|
||||
- name: Install Rust for macos-latest
|
||||
if: inputs.platform == 'macos-latest'
|
||||
uses: actions-rs/toolchain@v1
|
||||
@@ -101,20 +101,20 @@ jobs:
|
||||
profile: minimal
|
||||
override: true
|
||||
target: x86_64-apple-darwin
|
||||
|
||||
|
||||
- name: install dependencies (ubuntu only)
|
||||
if: inputs.platform == 'ubuntu-20.04'
|
||||
uses: awalsh128/cache-apt-pkgs-action@latest
|
||||
with:
|
||||
packages: libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libayatana-appindicator3-dev librsvg2-dev gnome-video-effects gnome-video-effects-extra
|
||||
version: 1.1
|
||||
|
||||
|
||||
- name: install pake-cli local
|
||||
shell: bash
|
||||
run: |
|
||||
run: |
|
||||
echo "install pake on local"
|
||||
npm install pake-cli
|
||||
|
||||
|
||||
- name: rust cache restore
|
||||
uses: actions/cache/restore@v3
|
||||
with:
|
||||
@@ -128,7 +128,7 @@ jobs:
|
||||
|
||||
- name: build with pake-cli
|
||||
shell: pwsh
|
||||
run: |
|
||||
run: |
|
||||
pwsh ./script/build_with_pake_cli.ps1
|
||||
env:
|
||||
URL: ${{ inputs.url }}
|
||||
@@ -148,7 +148,7 @@ jobs:
|
||||
name: output-${{ inputs.platform }}.zip
|
||||
path: node_modules/pake-cli/output/*
|
||||
retention-days: 3
|
||||
|
||||
|
||||
- name: rust cache store
|
||||
uses: actions/cache/save@v3
|
||||
with:
|
||||
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -30,7 +30,9 @@ output
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
pnpm-lock.yaml
|
||||
dist/
|
||||
dist
|
||||
!dist/about_pake.html
|
||||
!dist/cli.js
|
||||
!dist/.gitkeep
|
||||
.next/
|
||||
src-tauri/.cargo/config
|
||||
.next
|
||||
|
||||
70
bin/README.md
vendored
70
bin/README.md
vendored
@@ -48,7 +48,7 @@ pake url [options]
|
||||
|
||||
### url
|
||||
|
||||
url 为你需要打包的网页链接 🔗,必须提供。
|
||||
url 为你需要打包的网页链接 🔗 或者本地 html 文件,必须提供。
|
||||
|
||||
### [options]
|
||||
|
||||
@@ -100,32 +100,66 @@ url 为你需要打包的网页链接 🔗,必须提供。
|
||||
|
||||
#### [transparent]
|
||||
|
||||
是否开启沉浸式头部,默认为 `false` 不开启。使用下面的命令来开启该功能。
|
||||
是否开启沉浸式头部,默认为 `false` 不开启,输入下面的命令则开启沉浸式,推荐 MacOS 用户开启。
|
||||
|
||||
```shell
|
||||
--transparent
|
||||
# 或者
|
||||
-t
|
||||
```
|
||||
|
||||
#### [fullscreen]
|
||||
|
||||
打开应用后是否开启全屏,默认为 `false`,使用下面的命令开启该功能。
|
||||
|
||||
```shell
|
||||
--fullscreen
|
||||
# 或者
|
||||
-f
|
||||
```
|
||||
|
||||
#### [resize]
|
||||
|
||||
是否可以拖动大小,默认为 `true` 可拖动。使用下面的命令来关闭该功能。
|
||||
是否可以拖动大小,默认为 `true` 可拖动,输入下面的命令则不能对窗口大小进行拉伸。
|
||||
|
||||
```shell
|
||||
--no-resizable
|
||||
# 或者
|
||||
-r
|
||||
```
|
||||
|
||||
#### [fullscreen]
|
||||
|
||||
打开应用后是否开启全屏,默认为 `false`,输入下面的命令则会自动全屏。
|
||||
|
||||
```shell
|
||||
--fullscreen
|
||||
```
|
||||
|
||||
#### [user-agent]
|
||||
|
||||
自定义浏览器请求头, 默认为空。
|
||||
|
||||
```shell
|
||||
--user-agent <value>
|
||||
```
|
||||
|
||||
#### [show-menu]
|
||||
|
||||
显示菜单栏, 默认不显示,输入下面的命令则会显示,推荐 MacOS 用户开启。
|
||||
|
||||
```shell
|
||||
--show-menu
|
||||
```
|
||||
|
||||
#### [show-system-tray]
|
||||
|
||||
显示通知栏托盘, 默认不显示,输入下面的命令则会显示。
|
||||
|
||||
```shell
|
||||
--show-system-tray
|
||||
```
|
||||
|
||||
#### [system-tray-icon]
|
||||
|
||||
通知栏托盘图标,仅当显示通知栏托盘时有效, 图标必须为.ico 或者.png 格式的,512\*512 像素的图片。
|
||||
|
||||
```shell
|
||||
--system-tray-icon <value>
|
||||
```
|
||||
|
||||
#### [copy-iter-file]
|
||||
|
||||
递归拷贝,当 url 为本地文件路径时候,若开启该选项,则将 url 路径文件所在文件夹以及所有子文件都拷贝到 pake 静态文件夹,默认不开启
|
||||
|
||||
```shell
|
||||
--copy-iter-file
|
||||
```
|
||||
|
||||
#### [multi-arch]
|
||||
@@ -157,7 +191,7 @@ rustup target add x86_64-apple-darwin
|
||||
|
||||
#### [targets]
|
||||
|
||||
选择输出的包格式,支持deb/appimage/all,如果选择all,则同时打包deb和appimage,该选项仅支持Linux,默认为`all`。
|
||||
选择输出的包格式,支持 deb/appimage/all,如果选择 all,则同时打包 deb 和 appimage,该选项仅支持 Linux,默认为`all`。
|
||||
|
||||
```shell
|
||||
--targets xxx
|
||||
|
||||
40
bin/README_EN.md
vendored
40
bin/README_EN.md
vendored
@@ -130,6 +130,46 @@ Use the command below to disable this feature.
|
||||
-r
|
||||
```
|
||||
|
||||
#### [user-agent]
|
||||
|
||||
Custom browser user agent, default is empty.
|
||||
|
||||
```shell
|
||||
--user-agent <value>
|
||||
```
|
||||
|
||||
#### [show-menu]
|
||||
|
||||
Display the menu bar, not display it by default, enter the following command and it will be displayed. MacOS users are recommended to enable.
|
||||
|
||||
```shell
|
||||
--show-menu
|
||||
```
|
||||
|
||||
#### [show-system-tray]
|
||||
|
||||
Display the notification tray, not display it by default, entering the following command will display.
|
||||
|
||||
```shell
|
||||
--show-system-tray <value>
|
||||
```
|
||||
|
||||
#### [system-tray-icon]
|
||||
|
||||
The notification tray icon is only valid when the notification tray is displayed. The icon must be a 512\*512 pixel image in .ico or .png format.
|
||||
|
||||
```shell
|
||||
--system-tray-icon <value>
|
||||
```
|
||||
|
||||
#### [copy-iter-file]
|
||||
|
||||
Recursive copy, when the url is a local file path, if this option is enabled, the folder where the url path file is located and all sub-files are copied to the pake static folder, which is not enabled by default
|
||||
|
||||
```shell
|
||||
--copy-iter-file
|
||||
```
|
||||
|
||||
#### [multi-arch]
|
||||
|
||||
Package results support both Intel and m1 chips, only for MacOS. The default is `false`.
|
||||
|
||||
17
bin/builders/LinuxBuilder.ts
vendored
17
bin/builders/LinuxBuilder.ts
vendored
@@ -5,6 +5,7 @@ import { checkRustInstalled, installRust } from '@/helpers/rust.js';
|
||||
import { PakeAppOptions } from '@/types.js';
|
||||
import { IBuilder } from './base.js';
|
||||
import { shellExec } from '@/utils/shell.js';
|
||||
import {isChinaDomain} from '@/utils/ip_addr.js';
|
||||
// @ts-expect-error 加上resolveJsonModule rollup会打包报错
|
||||
// import tauriConf from '../../src-tauri/tauri.windows.conf.json';
|
||||
import tauriConf from './tauriConf.js';
|
||||
@@ -44,10 +45,22 @@ export default class LinuxBuilder implements IBuilder {
|
||||
async build(url: string, options: PakeAppOptions) {
|
||||
logger.debug('PakeAppOptions', options);
|
||||
const { name } = options;
|
||||
|
||||
await mergeTauriConfig(url, options, tauriConf);
|
||||
await shellExec(`cd "${npmDirectory}" && npm install --verbose && npm run build`);
|
||||
const isChina = isChinaDomain("www.npmjs.com")
|
||||
|
||||
if (isChina) {
|
||||
// crates.io也顺便换源
|
||||
const rust_project_dir = path.join(npmDirectory, 'src-tauri', ".cargo");
|
||||
const project_cn_conf = path.join(rust_project_dir, "cn_config.bak");
|
||||
const project_conf = path.join(rust_project_dir, "config");
|
||||
fs.copyFile(project_cn_conf, project_conf);
|
||||
|
||||
const _ = await shellExec(
|
||||
`cd ${npmDirectory} && npm install --registry=https://registry.npmmirror.com && npm run build`
|
||||
);
|
||||
} else {
|
||||
const _ = await shellExec(`cd ${npmDirectory} && npm install && npm run build`);
|
||||
}
|
||||
let arch: string;
|
||||
if (process.arch === "x64") {
|
||||
arch = "amd64";
|
||||
|
||||
25
bin/builders/MacBuilder.ts
vendored
25
bin/builders/MacBuilder.ts
vendored
@@ -11,6 +11,7 @@ import tauriConf from './tauriConf.js';
|
||||
import log from 'loglevel';
|
||||
import { mergeTauriConfig } from './common.js';
|
||||
import { npmDirectory } from '@/utils/dir.js';
|
||||
import {isChinaDomain} from '@/utils/ip_addr.js';
|
||||
import logger from '@/options/logger.js';
|
||||
|
||||
export default class MacBuilder implements IBuilder {
|
||||
@@ -41,10 +42,30 @@ export default class MacBuilder implements IBuilder {
|
||||
await mergeTauriConfig(url, options, tauriConf);
|
||||
let dmgName: string;
|
||||
if (options.multiArch) {
|
||||
await shellExec(`cd "${npmDirectory}" && npm install --verbose && npm run build:mac`);
|
||||
const isChina = isChinaDomain("www.npmjs.com")
|
||||
if (isChina) {
|
||||
// crates.io也顺便换源
|
||||
const rust_project_dir = path.join(npmDirectory, 'src-tauri', ".cargo");
|
||||
const project_cn_conf = path.join(rust_project_dir, "cn_config.bak");
|
||||
const project_conf = path.join(rust_project_dir, "config");
|
||||
fs.copyFile(project_cn_conf, project_conf);
|
||||
|
||||
const _ = await shellExec(
|
||||
`cd ${npmDirectory} && npm install --registry=https://registry.npmmirror.com && npm run build:mac`
|
||||
);
|
||||
} else {
|
||||
const _ = await shellExec(`cd ${npmDirectory} && npm install && npm run build:mac`);
|
||||
}
|
||||
dmgName = `${name}_${tauriConf.package.version}_universal.dmg`;
|
||||
} else {
|
||||
await shellExec(`cd "${npmDirectory}" && npm install --verbose && npm run build`);
|
||||
const isChina = isChinaDomain("www.npmjs.com")
|
||||
if (isChina) {
|
||||
const _ = await shellExec(
|
||||
`cd ${npmDirectory} && npm install --registry=https://registry.npmmirror.com && npm run build`
|
||||
);
|
||||
} else {
|
||||
const _ = await shellExec(`cd ${npmDirectory} && npm install && npm run build`);
|
||||
}
|
||||
let arch = "x64";
|
||||
if (process.arch === "arm64") {
|
||||
arch = "aarch64";
|
||||
|
||||
16
bin/builders/WinBulider.ts
vendored
16
bin/builders/WinBulider.ts
vendored
@@ -11,6 +11,7 @@ import tauriConf from './tauriConf.js';
|
||||
import logger from '@/options/logger.js';
|
||||
import { mergeTauriConfig } from './common.js';
|
||||
import { npmDirectory } from '@/utils/dir.js';
|
||||
import {isChinaDomain} from '@/utils/ip_addr.js';
|
||||
|
||||
export default class WinBuilder implements IBuilder {
|
||||
async prepare() {
|
||||
@@ -45,7 +46,20 @@ export default class WinBuilder implements IBuilder {
|
||||
|
||||
await mergeTauriConfig(url, options, tauriConf);
|
||||
|
||||
await shellExec(`cd "${npmDirectory}" && npm install --verbose && npm run build`);
|
||||
const isChina = isChinaDomain("www.npmjs.com")
|
||||
if (isChina) {
|
||||
// crates.io也顺便换源
|
||||
const rust_project_dir = path.join(npmDirectory, 'src-tauri', ".cargo");
|
||||
const project_cn_conf = path.join(rust_project_dir, "cn_config.bak");
|
||||
const project_conf = path.join(rust_project_dir, "config");
|
||||
fs.copyFile(project_cn_conf, project_conf);
|
||||
|
||||
const _ = await shellExec(
|
||||
`cd ${npmDirectory} && npm install --registry=https://registry.npmmirror.com && npm run build`
|
||||
);
|
||||
} else {
|
||||
const _ = await shellExec(`cd ${npmDirectory} && npm install && npm run build`);
|
||||
}
|
||||
const language = tauriConf.tauri.bundle.windows.wix.language[0];
|
||||
const arch = process.arch;
|
||||
const msiName = `${name}_${tauriConf.package.version}_${arch}_${language}.msi`;
|
||||
|
||||
179
bin/builders/common.ts
vendored
179
bin/builders/common.ts
vendored
@@ -1,10 +1,12 @@
|
||||
import { PakeAppOptions } from '@/types.js';
|
||||
import prompts from 'prompts';
|
||||
import prompts, { override } from 'prompts';
|
||||
import path from 'path';
|
||||
import fs from 'fs/promises';
|
||||
import fs2 from 'fs-extra';
|
||||
import { npmDirectory } from '@/utils/dir.js';
|
||||
import logger from '@/options/logger.js';
|
||||
|
||||
|
||||
export async function promptText(message: string, initial?: string) {
|
||||
const response = await prompts({
|
||||
type: 'text',
|
||||
@@ -15,6 +17,7 @@ export async function promptText(message: string, initial?: string) {
|
||||
return response.content;
|
||||
}
|
||||
|
||||
|
||||
export async function mergeTauriConfig(
|
||||
url: string,
|
||||
options: PakeAppOptions,
|
||||
@@ -26,6 +29,11 @@ export async function mergeTauriConfig(
|
||||
fullscreen,
|
||||
transparent,
|
||||
resizable,
|
||||
userAgent,
|
||||
showMenu,
|
||||
showSystemTray,
|
||||
systemTrayIcon,
|
||||
iterCopyFile,
|
||||
identifier,
|
||||
name,
|
||||
} = options;
|
||||
@@ -56,10 +64,132 @@ export async function mergeTauriConfig(
|
||||
}
|
||||
}
|
||||
|
||||
// logger.warn(JSON.stringify(tauriConf.pake.windows, null, 4));
|
||||
Object.assign(tauriConf.pake.windows[0], { url, ...tauriConfWindowOptions });
|
||||
// 判断一下url类型,是文件还是网站
|
||||
// 如果是文件,并且开启了递归拷贝功能,则需要将该文件以及所在文件夹下的所有文件拷贝到src目录下,否则只拷贝单个文件。
|
||||
|
||||
const url_exists = await fs.stat(url)
|
||||
.then(() => true)
|
||||
.catch(() => false);
|
||||
if (url_exists) {
|
||||
logger.warn("you input may a local file");
|
||||
tauriConf.pake.windows[0].url_type = "local";
|
||||
const file_name = path.basename(url);
|
||||
const dir_name = path.dirname(url);
|
||||
if (!iterCopyFile) {
|
||||
const url_path = path.join(npmDirectory,"dist/", file_name);
|
||||
await fs.copyFile(url, url_path);
|
||||
} else {
|
||||
const old_dir = path.join(npmDirectory,"dist/");
|
||||
const new_dir = path.join(npmDirectory,"dist_bak/");
|
||||
fs2.moveSync(old_dir, new_dir, {"overwrite": true});
|
||||
fs2.copySync(dir_name, old_dir, {"overwrite": true});
|
||||
// logger.warn("dir name", dir_name);
|
||||
// 将dist_bak里面的cli.js和about_pake.html拷贝回去
|
||||
const cli_path = path.join(new_dir, "cli.js")
|
||||
const cli_path_target = path.join(old_dir, "cli.js")
|
||||
const about_pake_path = path.join(new_dir, "about_pake.html");
|
||||
const about_patk_path_target = path.join(old_dir, "about_pake.html")
|
||||
fs.copyFile(cli_path, cli_path_target);
|
||||
fs.copyFile(about_pake_path, about_patk_path_target);
|
||||
}
|
||||
tauriConf.pake.windows[0].url = file_name;
|
||||
tauriConf.pake.windows[0].url_type = "local";
|
||||
} else {
|
||||
tauriConf.pake.windows[0].url_type = "web";
|
||||
}
|
||||
|
||||
// 处理user-agent
|
||||
logger.warn(userAgent);
|
||||
if (userAgent.length > 0) {
|
||||
if (process.platform === "win32") {
|
||||
tauriConf.pake.user_agent.windows = userAgent;
|
||||
}
|
||||
|
||||
if (process.platform === "linux") {
|
||||
tauriConf.pake.user_agent.linux = userAgent;
|
||||
}
|
||||
|
||||
if (process.platform === "darwin") {
|
||||
tauriConf.pake.user_agent.macos = userAgent;
|
||||
}
|
||||
}
|
||||
|
||||
// 处理菜单栏
|
||||
if (showMenu) {
|
||||
if (process.platform === "win32") {
|
||||
tauriConf.pake.menu.windows = true;
|
||||
}
|
||||
|
||||
if (process.platform === "linux") {
|
||||
tauriConf.pake.menu.linux = true;
|
||||
}
|
||||
|
||||
if (process.platform === "darwin") {
|
||||
tauriConf.pake.user_agent.macos = true;
|
||||
}
|
||||
} else {
|
||||
if (process.platform === "win32") {
|
||||
tauriConf.pake.menu.windows = false;
|
||||
}
|
||||
|
||||
if (process.platform === "linux") {
|
||||
tauriConf.pake.menu.linux = false;
|
||||
}
|
||||
|
||||
if (process.platform === "darwin") {
|
||||
tauriConf.pake.user_agent.macos = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 处理托盘
|
||||
if (showSystemTray) {
|
||||
if (process.platform === "win32") {
|
||||
tauriConf.pake.system_tray.windows = true;
|
||||
}
|
||||
|
||||
if (process.platform === "linux") {
|
||||
tauriConf.pake.system_tray.linux = true;
|
||||
}
|
||||
|
||||
if (process.platform === "darwin") {
|
||||
tauriConf.pake.system_tray.macos = true;
|
||||
}
|
||||
} else {
|
||||
if (process.platform === "win32") {
|
||||
tauriConf.pake.system_tray.windows = false;
|
||||
}
|
||||
|
||||
if (process.platform === "linux") {
|
||||
tauriConf.pake.system_tray.linux = false;
|
||||
}
|
||||
|
||||
if (process.platform === "darwin") {
|
||||
tauriConf.pake.system_tray.macos = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 处理targets 暂时只对linux开放
|
||||
if (process.platform === "linux") {
|
||||
if (options.targets.length > 0) {
|
||||
if (options.targets === "deb" || options.targets === "appimage" || options.targets === "all") {
|
||||
tauriConf.tauri.bundle.targets = [options.targets];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tauriConf.tauri.bundle.targets = ["deb"];
|
||||
}
|
||||
|
||||
Object.assign(tauriConf.tauri.windows[0], { url, ...tauriConfWindowOptions });
|
||||
tauriConf.package.productName = name;
|
||||
tauriConf.tauri.bundle.identifier = identifier;
|
||||
|
||||
// 删除映射关系
|
||||
if (process.platform === "linux") {
|
||||
delete tauriConf.tauri.bundle.deb.files;
|
||||
}
|
||||
|
||||
// 处理应用图标
|
||||
const exists = await fs.stat(options.icon)
|
||||
.then(() => true)
|
||||
.catch(() => false);
|
||||
@@ -83,18 +213,21 @@ export async function mergeTauriConfig(
|
||||
} else {
|
||||
updateIconPath = false;
|
||||
logger.warn(`icon file in Windows must be 256 * 256 pix with .ico type, but you give ${customIconExt}`);
|
||||
tauriConf.tauri.bundle.icon = ["png/icon_256.ico"];
|
||||
}
|
||||
}
|
||||
if (process.platform === "linux") {
|
||||
if (customIconExt != ".png") {
|
||||
updateIconPath = false;
|
||||
logger.warn(`icon file in Linux must be 512 * 512 pix with .png type, but you give ${customIconExt}`);
|
||||
tauriConf.tauri.bundle.icon = ["png/icon_512.png"];
|
||||
}
|
||||
}
|
||||
|
||||
if (process.platform === "darwin" && customIconExt !== ".icns") {
|
||||
updateIconPath = false;
|
||||
logger.warn(`icon file in MacOS must be .icns type, but you give ${customIconExt}`);
|
||||
tauriConf.tauri.bundle.icon = ["icons/icon.icns"];
|
||||
}
|
||||
if (updateIconPath) {
|
||||
tauriConf.tauri.bundle.icon = [options.icon];
|
||||
@@ -120,7 +253,40 @@ export async function mergeTauriConfig(
|
||||
}
|
||||
}
|
||||
|
||||
// 处理托盘自定义图标
|
||||
let useDefaultIcon = true; // 是否使用默认托盘图标
|
||||
if (systemTrayIcon.length > 0) {
|
||||
const icon_exists = await fs.stat(systemTrayIcon)
|
||||
.then(() => true)
|
||||
.catch(() => false);
|
||||
if (icon_exists) {
|
||||
// 需要判断图标格式,默认只支持ico和png两种
|
||||
let iconExt = path.extname(systemTrayIcon).toLowerCase();
|
||||
if (iconExt == ".png" || iconExt == ".icon") {
|
||||
useDefaultIcon = false;
|
||||
const trayIcoPath = path.join(npmDirectory, `src-tauri/png/${name.toLowerCase()}${iconExt}`);
|
||||
tauriConf.tauri.systemTray.iconPath = `png/${name.toLowerCase()}${iconExt}`;
|
||||
await fs.copyFile(systemTrayIcon, trayIcoPath);
|
||||
} else {
|
||||
logger.warn(`file type for system tray icon mut be .ico or .png , but you give ${iconExt}`);
|
||||
logger.warn(`system tray icon file will not change with default.`);
|
||||
}
|
||||
} else {
|
||||
logger.warn(`${systemTrayIcon} not exists!`)
|
||||
logger.warn(`system tray icon file will not change with default.`);
|
||||
}
|
||||
}
|
||||
|
||||
// 处理托盘默认图标
|
||||
if (useDefaultIcon) {
|
||||
if (process.platform === "linux" || process.platform === "win32") {
|
||||
tauriConf.tauri.systemTray.iconPath = tauriConf.tauri.bundle.icon[0];
|
||||
} else {
|
||||
tauriConf.tauri.systemTray.iconPath = "png/icon_512.png";
|
||||
}
|
||||
}
|
||||
|
||||
// 保存配置文件
|
||||
let configPath = "";
|
||||
switch (process.platform) {
|
||||
case "win32": {
|
||||
@@ -143,6 +309,15 @@ export async function mergeTauriConfig(
|
||||
Buffer.from(JSON.stringify(bundleConf, null, '\t'), 'utf-8')
|
||||
);
|
||||
|
||||
const pakeConfigPath = path.join(npmDirectory, 'src-tauri/pake.json')
|
||||
await fs.writeFile(
|
||||
pakeConfigPath,
|
||||
Buffer.from(JSON.stringify(tauriConf.pake, null, 4), 'utf-8')
|
||||
);
|
||||
|
||||
let tauriConf2 = JSON.parse(JSON.stringify(tauriConf));
|
||||
delete tauriConf2.pake;
|
||||
delete tauriConf2.tauri.bundle;
|
||||
|
||||
const configJsonPath = path.join(npmDirectory, 'src-tauri/tauri.conf.json')
|
||||
await fs.writeFile(
|
||||
|
||||
4
bin/builders/tauriConf.js
vendored
4
bin/builders/tauriConf.js
vendored
@@ -1,11 +1,13 @@
|
||||
import CommonConf from '../../src-tauri/tauri.conf.json';
|
||||
import pakeConf from '../../src-tauri/pake.json';
|
||||
import WinConf from '../../src-tauri/tauri.windows.conf.json';
|
||||
import MacConf from '../../src-tauri/tauri.macos.conf.json';
|
||||
import LinuxConf from '../../src-tauri/tauri.linux.conf.json';
|
||||
|
||||
let tauriConf = {
|
||||
package: CommonConf.package,
|
||||
tauri: CommonConf.tauri
|
||||
tauri: CommonConf.tauri,
|
||||
pake: pakeConf
|
||||
}
|
||||
switch (process.platform) {
|
||||
case "win32": {
|
||||
|
||||
43
bin/cli.ts
vendored
43
bin/cli.ts
vendored
@@ -13,17 +13,32 @@ program.version(packageJson.version).description('A command-line tool that can q
|
||||
|
||||
program
|
||||
.showHelpAfterError()
|
||||
.argument('[url]', 'the web URL you want to package', validateUrlInput)
|
||||
.option('-n, --name <string>', 'application name')
|
||||
.option('-i, --icon <string>', 'application icon', DEFAULT_PAKE_OPTIONS.icon)
|
||||
.option('-w, --width <number>', 'window width', validateNumberInput, DEFAULT_PAKE_OPTIONS.width)
|
||||
.option('-h, --height <number>', 'window height', validateNumberInput, DEFAULT_PAKE_OPTIONS.height)
|
||||
.option('-f, --fullscreen', 'start in full screen mode', DEFAULT_PAKE_OPTIONS.fullscreen)
|
||||
.option('-t, --transparent', 'transparent title bar', DEFAULT_PAKE_OPTIONS.transparent)
|
||||
.option('-r, --no-resizable', 'whether the window can be resizable', DEFAULT_PAKE_OPTIONS.resizable)
|
||||
.option('-d, --debug', 'debug', DEFAULT_PAKE_OPTIONS.debug)
|
||||
.option('-m, --multi-arch', "available for Mac only, and supports both Intel and M1", DEFAULT_PAKE_OPTIONS.multiArch)
|
||||
.option('--targets <string>', "Select the output package format, support deb/appimage/all, only for Linux", DEFAULT_PAKE_OPTIONS.targets)
|
||||
.argument('[url]', 'the web url you want to package', validateUrlInput)
|
||||
.option('--name <string>', 'application name')
|
||||
.option('--icon <string>', 'application icon', DEFAULT_PAKE_OPTIONS.icon)
|
||||
.option('--height <number>', 'window height', validateNumberInput, DEFAULT_PAKE_OPTIONS.height)
|
||||
.option('--width <number>', 'window width', validateNumberInput, DEFAULT_PAKE_OPTIONS.width)
|
||||
.option('--no-resizable', 'whether the window can be resizable', DEFAULT_PAKE_OPTIONS.resizable)
|
||||
.option('--fullscreen', 'makes the packaged app start in full screen', DEFAULT_PAKE_OPTIONS.fullscreen)
|
||||
.option('--transparent', 'transparent title bar', DEFAULT_PAKE_OPTIONS.transparent)
|
||||
.option('--user-agent <string>', 'custom user agent', DEFAULT_PAKE_OPTIONS.userAgent)
|
||||
.option('--show-menu', 'show menu in app', DEFAULT_PAKE_OPTIONS.showMenu)
|
||||
.option('--show-system-tray', 'show system tray in app', DEFAULT_PAKE_OPTIONS.showSystemTray)
|
||||
.option('--system-tray-icon <string>', 'custom system tray icon', DEFAULT_PAKE_OPTIONS.systemTrayIcon)
|
||||
.option('--iter-copy-file',
|
||||
'copy all static file to pake app when url is a local file',
|
||||
DEFAULT_PAKE_OPTIONS.iterCopyFile)
|
||||
.option(
|
||||
'-m, --multi-arch',
|
||||
"available for Mac only, and supports both Intel and M1",
|
||||
DEFAULT_PAKE_OPTIONS.multiArch
|
||||
)
|
||||
.option(
|
||||
'--targets <string>',
|
||||
'only for linux, default is "deb", option "appaimge" or "all"(deb & appimage)',
|
||||
DEFAULT_PAKE_OPTIONS.targets
|
||||
)
|
||||
.option('--debug', 'debug', DEFAULT_PAKE_OPTIONS.transparent)
|
||||
.action(async (url: string, options: PakeCliOptions) => {
|
||||
|
||||
await checkUpdateTips();
|
||||
@@ -40,10 +55,10 @@ program
|
||||
|
||||
const builder = BuilderFactory.create();
|
||||
await builder.prepare();
|
||||
|
||||
// logger.warn("you input url is ", url);
|
||||
const appOptions = await handleInputOptions(options, url);
|
||||
|
||||
await builder.build(url, appOptions);
|
||||
// logger.info(JSON.stringify(appOptions, null, 4));
|
||||
builder.build(url, appOptions);
|
||||
});
|
||||
|
||||
program.parse();
|
||||
|
||||
7
bin/defaults.ts
vendored
7
bin/defaults.ts
vendored
@@ -7,6 +7,13 @@ export const DEFAULT_PAKE_OPTIONS: PakeCliOptions = {
|
||||
fullscreen: false,
|
||||
resizable: true,
|
||||
transparent: false,
|
||||
userAgent: '',
|
||||
showMenu: false,
|
||||
showSystemTray: false,
|
||||
multiArch: false,
|
||||
targets: 'deb',
|
||||
iterCopyFile: false,
|
||||
systemTrayIcon: '',
|
||||
debug: false,
|
||||
multiArch: false,
|
||||
targets: "all",
|
||||
|
||||
12
bin/helpers/rust.ts
vendored
12
bin/helpers/rust.ts
vendored
@@ -2,9 +2,17 @@ import { IS_WIN } from '@/utils/platform.js';
|
||||
import ora from 'ora';
|
||||
import shelljs from 'shelljs';
|
||||
import { shellExec } from '../utils/shell.js';
|
||||
import {isChinaDomain} from '@/utils/ip_addr.js'
|
||||
|
||||
const RustInstallScriptFocMac =
|
||||
"curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y";
|
||||
const is_china = isChinaDomain("sh.rustup.rs");
|
||||
let RustInstallScriptFocMac = "";
|
||||
if (is_china) {
|
||||
RustInstallScriptFocMac =
|
||||
'export RUSTUP_DIST_SERVER="https://rsproxy.cn" && export RUSTUP_UPDATE_ROOT="https://rsproxy.cn/rustup" && curl --proto "=https" --tlsv1.2 -sSf https://rsproxy.cn/rustup-init.sh | sh';
|
||||
} else {
|
||||
RustInstallScriptFocMac =
|
||||
"curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y";
|
||||
}
|
||||
const RustInstallScriptForWin = 'winget install --id Rustlang.Rustup';
|
||||
|
||||
export async function installRust() {
|
||||
|
||||
11
bin/options/index.ts
vendored
11
bin/options/index.ts
vendored
@@ -3,15 +3,22 @@ import { getDomain } from '@/utils/url.js';
|
||||
import { getIdentifier } from '../helpers/tauriConfig.js';
|
||||
import { PakeAppOptions, PakeCliOptions } from '../types.js';
|
||||
import { handleIcon } from './icon.js';
|
||||
import fs from 'fs/promises';
|
||||
|
||||
export default async function handleOptions(options: PakeCliOptions, url: string): Promise<PakeAppOptions> {
|
||||
const appOptions: PakeAppOptions = {
|
||||
...options,
|
||||
identifier: '',
|
||||
};
|
||||
|
||||
const url_exists = await fs.stat(url)
|
||||
.then(() => true)
|
||||
.catch(() => false);
|
||||
if (!appOptions.name) {
|
||||
appOptions.name = await promptText('Please enter the name of your application.', getDomain(url));
|
||||
if (!url_exists) {
|
||||
appOptions.name = await promptText('please input your application name', getDomain(url));
|
||||
} else {
|
||||
appOptions.name = await promptText('please input your application name', "");
|
||||
}
|
||||
}
|
||||
|
||||
appOptions.identifier = getIdentifier(appOptions.name, url);
|
||||
|
||||
21
bin/types.ts
vendored
21
bin/types.ts
vendored
@@ -20,6 +20,27 @@ export interface PakeCliOptions {
|
||||
/** 是否开启沉浸式头部,默认为 false 不开启 ƒ*/
|
||||
transparent: boolean;
|
||||
|
||||
/** 自定义UA,默认为不开启 ƒ*/
|
||||
userAgent: string;
|
||||
|
||||
/** 开启菜单栏,MacOS默认开启,Windows,Linux默认不开启 ƒ*/
|
||||
showMenu: boolean;
|
||||
|
||||
/** 开启系统托盘,MacOS默认不开启,Windows,Linux默认开启 ƒ*/
|
||||
showSystemTray: boolean;
|
||||
|
||||
/** 托盘图标, Windows、Linux默认和应用图标共用一样的,MacOS需要提别提供, 格式为png或者ico */
|
||||
systemTrayIcon: string;
|
||||
|
||||
// /** 递归拷贝,当url为本地文件路径时候,若开启该选项,则将url路径文件所在文件夹以及所有子文件都拷贝到pake静态文件夹,默认不开启 */
|
||||
iterCopyFile: false;
|
||||
|
||||
/** mutli arch, Supports both Intel and m1 chips, only for Mac */
|
||||
multiArch: boolean;
|
||||
|
||||
// 包输出产物,对linux用户有效,默认为deb,可选appimage, 或者all(即同时输出deb和all);
|
||||
targets: string;
|
||||
|
||||
/** 调试模式,会输出更多日志 */
|
||||
debug: boolean;
|
||||
|
||||
|
||||
35
bin/utils/ip_addr.ts
vendored
Normal file
35
bin/utils/ip_addr.ts
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
import { exec } from 'child_process';
|
||||
import { promisify } from 'util';
|
||||
import dns from 'dns';
|
||||
|
||||
const resolve = promisify(dns.resolve);
|
||||
|
||||
async function isChinaDomain(domain: string): Promise<boolean> {
|
||||
try {
|
||||
// 解析域名为IP地址
|
||||
const [ip] = await resolve(domain);
|
||||
return await isChinaIP(ip);
|
||||
} catch (error) {
|
||||
// 域名无法解析,返回false
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function isChinaIP(ip: string): Promise<boolean> {
|
||||
return new Promise((resolve, reject) => {
|
||||
exec(`ping -c 1 -w 1 ${ip}`, (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
// 命令执行出错,返回false
|
||||
resolve(false);
|
||||
} else {
|
||||
// 解析输出信息,提取延迟值
|
||||
const match = stdout.match(/time=(\d+\.\d+) ms/);
|
||||
const latency = match ? parseFloat(match[1]) : 0;
|
||||
// 判断延迟是否超过100ms
|
||||
resolve(latency > 100);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export { isChinaDomain, isChinaIP };
|
||||
13
bin/utils/validate.ts
vendored
13
bin/utils/validate.ts
vendored
@@ -1,5 +1,6 @@
|
||||
import * as Commander from 'commander';
|
||||
import { normalizeUrl } from './url.js';
|
||||
import fs from 'fs';
|
||||
|
||||
export function validateNumberInput(value: string) {
|
||||
const parsedValue = Number(value);
|
||||
@@ -10,9 +11,13 @@ export function validateNumberInput(value: string) {
|
||||
}
|
||||
|
||||
export function validateUrlInput(url: string) {
|
||||
try {
|
||||
return normalizeUrl(url);
|
||||
} catch (error) {
|
||||
throw new Commander.InvalidArgumentError(error.message);
|
||||
if(!fs.existsSync(url)) {
|
||||
try {
|
||||
return normalizeUrl(url)
|
||||
} catch (error) {
|
||||
throw new Commander.InvalidArgumentError(error.message);
|
||||
}
|
||||
} else {
|
||||
return url;
|
||||
}
|
||||
}
|
||||
|
||||
16
dist/about_pake.html
vendored
Normal file
16
dist/about_pake.html
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Document</title>
|
||||
</head>
|
||||
<body>
|
||||
<h5>Welcome from Pake!</h5>
|
||||
<p>version: 1.0.9</p>
|
||||
<a href="https://github.com/tw93/Pake">Project link</a><br>
|
||||
<a href="https://github.com/tw93/Pake/discussions">Discussions</a><br>
|
||||
<a href="https://github.com/tw93/Pake/issues" >Issues</a><br>
|
||||
<p>LICENSE: MIT</p>
|
||||
</body>
|
||||
</html>
|
||||
453
dist/cli.js
vendored
453
dist/cli.js
vendored
@@ -3,9 +3,11 @@ import { program } from 'commander';
|
||||
import log from 'loglevel';
|
||||
import url, { fileURLToPath } from 'url';
|
||||
import isurl from 'is-url';
|
||||
import fs from 'fs';
|
||||
import prompts from 'prompts';
|
||||
import path from 'path';
|
||||
import fs from 'fs/promises';
|
||||
import fs$1 from 'fs/promises';
|
||||
import fs2 from 'fs-extra';
|
||||
import chalk from 'chalk';
|
||||
import crypto from 'crypto';
|
||||
import axios from 'axios';
|
||||
@@ -13,31 +15,34 @@ import { fileTypeFromBuffer } from 'file-type';
|
||||
import { dir } from 'tmp-promise';
|
||||
import ora from 'ora';
|
||||
import shelljs from 'shelljs';
|
||||
import { exec } from 'child_process';
|
||||
import { promisify } from 'util';
|
||||
import dns from 'dns';
|
||||
import updateNotifier from 'update-notifier';
|
||||
|
||||
/******************************************************************************
|
||||
Copyright (c) Microsoft Corporation.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
PERFORMANCE OF THIS SOFTWARE.
|
||||
***************************************************************************** */
|
||||
|
||||
function __awaiter(thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
/******************************************************************************
|
||||
Copyright (c) Microsoft Corporation.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
PERFORMANCE OF THIS SOFTWARE.
|
||||
***************************************************************************** */
|
||||
|
||||
function __awaiter(thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
}
|
||||
|
||||
const DEFAULT_PAKE_OPTIONS = {
|
||||
@@ -47,6 +52,13 @@ const DEFAULT_PAKE_OPTIONS = {
|
||||
fullscreen: false,
|
||||
resizable: true,
|
||||
transparent: false,
|
||||
userAgent: '',
|
||||
showMenu: false,
|
||||
showSystemTray: false,
|
||||
multiArch: false,
|
||||
targets: 'deb',
|
||||
iterCopyFile: false,
|
||||
systemTrayIcon: '',
|
||||
debug: false,
|
||||
multiArch: false,
|
||||
targets: "all",
|
||||
@@ -1585,11 +1597,16 @@ function validateNumberInput(value) {
|
||||
return parsedValue;
|
||||
}
|
||||
function validateUrlInput(url) {
|
||||
try {
|
||||
return normalizeUrl(url);
|
||||
if (!fs.existsSync(url)) {
|
||||
try {
|
||||
return normalizeUrl(url);
|
||||
}
|
||||
catch (error) {
|
||||
throw new Commander.InvalidArgumentError(error.message);
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
throw new Commander.InvalidArgumentError(error.message);
|
||||
else {
|
||||
return url;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1626,7 +1643,7 @@ function promptText(message, initial) {
|
||||
}
|
||||
function mergeTauriConfig(url, options, tauriConf) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const { width, height, fullscreen, transparent, resizable, identifier, name, } = options;
|
||||
const { width, height, fullscreen, transparent, resizable, userAgent, showMenu, showSystemTray, systemTrayIcon, iterCopyFile, identifier, name, } = options;
|
||||
const tauriConfWindowOptions = {
|
||||
width,
|
||||
height,
|
||||
@@ -1652,10 +1669,120 @@ function mergeTauriConfig(url, options, tauriConf) {
|
||||
process.exit();
|
||||
}
|
||||
}
|
||||
Object.assign(tauriConf.tauri.windows[0], Object.assign({ url }, tauriConfWindowOptions));
|
||||
// logger.warn(JSON.stringify(tauriConf.pake.windows, null, 4));
|
||||
Object.assign(tauriConf.pake.windows[0], Object.assign({ url }, tauriConfWindowOptions));
|
||||
// 判断一下url类型,是文件还是网站
|
||||
// 如果是文件,并且开启了递归拷贝功能,则需要将该文件以及所在文件夹下的所有文件拷贝到src目录下,否则只拷贝单个文件。
|
||||
const url_exists = yield fs$1.stat(url)
|
||||
.then(() => true)
|
||||
.catch(() => false);
|
||||
if (url_exists) {
|
||||
logger.warn("you input may a local file");
|
||||
tauriConf.pake.windows[0].url_type = "local";
|
||||
const file_name = path.basename(url);
|
||||
const dir_name = path.dirname(url);
|
||||
if (!iterCopyFile) {
|
||||
const url_path = path.join(npmDirectory, "dist/", file_name);
|
||||
yield fs$1.copyFile(url, url_path);
|
||||
}
|
||||
else {
|
||||
const old_dir = path.join(npmDirectory, "dist/");
|
||||
const new_dir = path.join(npmDirectory, "dist_bak/");
|
||||
fs2.moveSync(old_dir, new_dir, { "overwrite": true });
|
||||
fs2.copySync(dir_name, old_dir, { "overwrite": true });
|
||||
// logger.warn("dir name", dir_name);
|
||||
// 将dist_bak里面的cli.js和about_pake.html拷贝回去
|
||||
const cli_path = path.join(new_dir, "cli.js");
|
||||
const cli_path_target = path.join(old_dir, "cli.js");
|
||||
const about_pake_path = path.join(new_dir, "about_pake.html");
|
||||
const about_patk_path_target = path.join(old_dir, "about_pake.html");
|
||||
fs$1.copyFile(cli_path, cli_path_target);
|
||||
fs$1.copyFile(about_pake_path, about_patk_path_target);
|
||||
}
|
||||
tauriConf.pake.windows[0].url = file_name;
|
||||
tauriConf.pake.windows[0].url_type = "local";
|
||||
}
|
||||
else {
|
||||
tauriConf.pake.windows[0].url_type = "web";
|
||||
}
|
||||
// 处理user-agent
|
||||
logger.warn(userAgent);
|
||||
if (userAgent.length > 0) {
|
||||
if (process.platform === "win32") {
|
||||
tauriConf.pake.user_agent.windows = userAgent;
|
||||
}
|
||||
if (process.platform === "linux") {
|
||||
tauriConf.pake.user_agent.linux = userAgent;
|
||||
}
|
||||
if (process.platform === "darwin") {
|
||||
tauriConf.pake.user_agent.macos = userAgent;
|
||||
}
|
||||
}
|
||||
// 处理菜单栏
|
||||
if (showMenu) {
|
||||
if (process.platform === "win32") {
|
||||
tauriConf.pake.menu.windows = true;
|
||||
}
|
||||
if (process.platform === "linux") {
|
||||
tauriConf.pake.menu.linux = true;
|
||||
}
|
||||
if (process.platform === "darwin") {
|
||||
tauriConf.pake.user_agent.macos = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (process.platform === "win32") {
|
||||
tauriConf.pake.menu.windows = false;
|
||||
}
|
||||
if (process.platform === "linux") {
|
||||
tauriConf.pake.menu.linux = false;
|
||||
}
|
||||
if (process.platform === "darwin") {
|
||||
tauriConf.pake.user_agent.macos = false;
|
||||
}
|
||||
}
|
||||
// 处理托盘
|
||||
if (showSystemTray) {
|
||||
if (process.platform === "win32") {
|
||||
tauriConf.pake.system_tray.windows = true;
|
||||
}
|
||||
if (process.platform === "linux") {
|
||||
tauriConf.pake.system_tray.linux = true;
|
||||
}
|
||||
if (process.platform === "darwin") {
|
||||
tauriConf.pake.system_tray.macos = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (process.platform === "win32") {
|
||||
tauriConf.pake.system_tray.windows = false;
|
||||
}
|
||||
if (process.platform === "linux") {
|
||||
tauriConf.pake.system_tray.linux = false;
|
||||
}
|
||||
if (process.platform === "darwin") {
|
||||
tauriConf.pake.system_tray.macos = false;
|
||||
}
|
||||
}
|
||||
// 处理targets 暂时只对linux开放
|
||||
if (process.platform === "linux") {
|
||||
if (options.targets.length > 0) {
|
||||
if (options.targets === "deb" || options.targets === "appimage" || options.targets === "all") {
|
||||
tauriConf.tauri.bundle.targets = [options.targets];
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
tauriConf.tauri.bundle.targets = ["deb"];
|
||||
}
|
||||
tauriConf.package.productName = name;
|
||||
tauriConf.tauri.bundle.identifier = identifier;
|
||||
const exists = yield fs.stat(options.icon)
|
||||
// 删除映射关系
|
||||
if (process.platform === "linux") {
|
||||
delete tauriConf.tauri.bundle.deb.files;
|
||||
}
|
||||
// 处理应用图标
|
||||
const exists = yield fs$1.stat(options.icon)
|
||||
.then(() => true)
|
||||
.catch(() => false);
|
||||
if (process.platform === "linux") {
|
||||
@@ -1674,22 +1801,25 @@ function mergeTauriConfig(url, options, tauriConf) {
|
||||
if (customIconExt === ".ico") {
|
||||
const ico_path = path.join(npmDirectory, `src-tauri/png/${name.toLowerCase()}_32.ico`);
|
||||
tauriConf.tauri.bundle.resources = [`png/${name.toLowerCase()}_32.ico`];
|
||||
yield fs.copyFile(options.icon, ico_path);
|
||||
yield fs$1.copyFile(options.icon, ico_path);
|
||||
}
|
||||
else {
|
||||
updateIconPath = false;
|
||||
logger.warn(`icon file in Windows must be 256 * 256 pix with .ico type, but you give ${customIconExt}`);
|
||||
tauriConf.tauri.bundle.icon = ["png/icon_256.ico"];
|
||||
}
|
||||
}
|
||||
if (process.platform === "linux") {
|
||||
if (customIconExt != ".png") {
|
||||
updateIconPath = false;
|
||||
logger.warn(`icon file in Linux must be 512 * 512 pix with .png type, but you give ${customIconExt}`);
|
||||
tauriConf.tauri.bundle.icon = ["png/icon_512.png"];
|
||||
}
|
||||
}
|
||||
if (process.platform === "darwin" && customIconExt !== ".icns") {
|
||||
updateIconPath = false;
|
||||
logger.warn(`icon file in MacOS must be .icns type, but you give ${customIconExt}`);
|
||||
tauriConf.tauri.bundle.icon = ["icons/icon.icns"];
|
||||
}
|
||||
if (updateIconPath) {
|
||||
tauriConf.tauri.bundle.icon = [options.icon];
|
||||
@@ -1716,6 +1846,41 @@ function mergeTauriConfig(url, options, tauriConf) {
|
||||
}
|
||||
}
|
||||
}
|
||||
// 处理托盘自定义图标
|
||||
let useDefaultIcon = true; // 是否使用默认托盘图标
|
||||
if (systemTrayIcon.length > 0) {
|
||||
const icon_exists = yield fs$1.stat(systemTrayIcon)
|
||||
.then(() => true)
|
||||
.catch(() => false);
|
||||
if (icon_exists) {
|
||||
// 需要判断图标格式,默认只支持ico和png两种
|
||||
let iconExt = path.extname(systemTrayIcon).toLowerCase();
|
||||
if (iconExt == ".png" || iconExt == ".icon") {
|
||||
useDefaultIcon = false;
|
||||
const trayIcoPath = path.join(npmDirectory, `src-tauri/png/${name.toLowerCase()}${iconExt}`);
|
||||
tauriConf.tauri.systemTray.iconPath = `png/${name.toLowerCase()}${iconExt}`;
|
||||
yield fs$1.copyFile(systemTrayIcon, trayIcoPath);
|
||||
}
|
||||
else {
|
||||
logger.warn(`file type for system tray icon mut be .ico or .png , but you give ${iconExt}`);
|
||||
logger.warn(`system tray icon file will not change with default.`);
|
||||
}
|
||||
}
|
||||
else {
|
||||
logger.warn(`${systemTrayIcon} not exists!`);
|
||||
logger.warn(`system tray icon file will not change with default.`);
|
||||
}
|
||||
}
|
||||
// 处理托盘默认图标
|
||||
if (useDefaultIcon) {
|
||||
if (process.platform === "linux" || process.platform === "win32") {
|
||||
tauriConf.tauri.systemTray.iconPath = tauriConf.tauri.bundle.icon[0];
|
||||
}
|
||||
else {
|
||||
tauriConf.tauri.systemTray.iconPath = "png/icon_512.png";
|
||||
}
|
||||
}
|
||||
// 保存配置文件
|
||||
let configPath = "";
|
||||
switch (process.platform) {
|
||||
case "win32": {
|
||||
@@ -1732,9 +1897,14 @@ function mergeTauriConfig(url, options, tauriConf) {
|
||||
}
|
||||
}
|
||||
let bundleConf = { tauri: { bundle: tauriConf.tauri.bundle } };
|
||||
yield fs.writeFile(configPath, Buffer.from(JSON.stringify(bundleConf, null, '\t'), 'utf-8'));
|
||||
yield fs$1.writeFile(configPath, Buffer.from(JSON.stringify(bundleConf, null, 4), 'utf-8'));
|
||||
const pakeConfigPath = path.join(npmDirectory, 'src-tauri/pake.json');
|
||||
yield fs$1.writeFile(pakeConfigPath, Buffer.from(JSON.stringify(tauriConf.pake, null, 4), 'utf-8'));
|
||||
let tauriConf2 = JSON.parse(JSON.stringify(tauriConf));
|
||||
delete tauriConf2.pake;
|
||||
delete tauriConf2.tauri.bundle;
|
||||
const configJsonPath = path.join(npmDirectory, 'src-tauri/tauri.conf.json');
|
||||
yield fs.writeFile(configJsonPath, Buffer.from(JSON.stringify(tauriConf, null, '\t'), 'utf-8'));
|
||||
yield fs$1.writeFile(configJsonPath, Buffer.from(JSON.stringify(tauriConf2, null, 4), 'utf-8'));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1801,7 +1971,7 @@ function downloadIcon(iconUrl) {
|
||||
}
|
||||
const { path } = yield dir();
|
||||
const iconPath = `${path}/icon.${fileDetails.ext}`;
|
||||
yield fs.writeFile(iconPath, iconData);
|
||||
yield fs$1.writeFile(iconPath, iconData);
|
||||
return iconPath;
|
||||
});
|
||||
}
|
||||
@@ -1809,8 +1979,16 @@ function downloadIcon(iconUrl) {
|
||||
function handleOptions(options, url) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const appOptions = Object.assign(Object.assign({}, options), { identifier: '' });
|
||||
const url_exists = yield fs$1.stat(url)
|
||||
.then(() => true)
|
||||
.catch(() => false);
|
||||
if (!appOptions.name) {
|
||||
appOptions.name = yield promptText('Please enter the name of your application.', getDomain(url));
|
||||
if (!url_exists) {
|
||||
appOptions.name = yield promptText('please input your application name', getDomain(url));
|
||||
}
|
||||
else {
|
||||
appOptions.name = yield promptText('please input your application name', "");
|
||||
}
|
||||
}
|
||||
appOptions.identifier = getIdentifier(appOptions.name, url);
|
||||
appOptions.icon = yield handleIcon(appOptions);
|
||||
@@ -1831,7 +2009,50 @@ function shellExec(command) {
|
||||
});
|
||||
}
|
||||
|
||||
const RustInstallScriptFocMac = "curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y";
|
||||
const resolve = promisify(dns.resolve);
|
||||
function isChinaDomain(domain) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
try {
|
||||
// 解析域名为IP地址
|
||||
const [ip] = yield resolve(domain);
|
||||
return yield isChinaIP(ip);
|
||||
}
|
||||
catch (error) {
|
||||
// 域名无法解析,返回false
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
function isChinaIP(ip) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
return new Promise((resolve, reject) => {
|
||||
exec(`ping -c 1 -w 1 ${ip}`, (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
// 命令执行出错,返回false
|
||||
resolve(false);
|
||||
}
|
||||
else {
|
||||
// 解析输出信息,提取延迟值
|
||||
const match = stdout.match(/time=(\d+\.\d+) ms/);
|
||||
const latency = match ? parseFloat(match[1]) : 0;
|
||||
// 判断延迟是否超过100ms
|
||||
resolve(latency > 100);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const is_china = isChinaDomain("sh.rustup.rs");
|
||||
let RustInstallScriptFocMac = "";
|
||||
if (is_china) {
|
||||
RustInstallScriptFocMac =
|
||||
'export RUSTUP_DIST_SERVER="https://rsproxy.cn" && export RUSTUP_UPDATE_ROOT="https://rsproxy.cn/rustup" && curl --proto "=https" --tlsv1.2 -sSf https://rsproxy.cn/rustup-init.sh | sh';
|
||||
}
|
||||
else {
|
||||
RustInstallScriptFocMac =
|
||||
"curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y";
|
||||
}
|
||||
const RustInstallScriptForWin = 'winget install --id Rustlang.Rustup';
|
||||
function installRust() {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
@@ -1852,24 +2073,22 @@ function checkRustInstalled() {
|
||||
}
|
||||
|
||||
var tauri$3 = {
|
||||
windows: [
|
||||
{
|
||||
url: "https://weread.qq.com/",
|
||||
transparent: true,
|
||||
fullscreen: false,
|
||||
width: 1200,
|
||||
height: 780,
|
||||
resizable: true
|
||||
}
|
||||
],
|
||||
security: {
|
||||
csp: null
|
||||
},
|
||||
updater: {
|
||||
active: false
|
||||
},
|
||||
systemTray: {
|
||||
iconPath: "png/weread_512.png",
|
||||
iconAsTemplate: true
|
||||
},
|
||||
allowlist: {
|
||||
all: true
|
||||
}
|
||||
};
|
||||
var build = {
|
||||
withGlobalTauri: true,
|
||||
devPath: "../dist",
|
||||
distDir: "../dist",
|
||||
beforeBuildCommand: "",
|
||||
@@ -1884,6 +2103,39 @@ var CommonConf = {
|
||||
build: build
|
||||
};
|
||||
|
||||
var windows = [
|
||||
{
|
||||
url: "https://weread.qq.com/",
|
||||
transparent: true,
|
||||
fullscreen: false,
|
||||
width: 1200,
|
||||
height: 780,
|
||||
resizable: true,
|
||||
url_type: "web"
|
||||
}
|
||||
];
|
||||
var user_agent = {
|
||||
macos: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.1 Safari/605.1.15",
|
||||
linux: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36",
|
||||
windows: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36"
|
||||
};
|
||||
var menu = {
|
||||
macos: true,
|
||||
linux: false,
|
||||
windows: false
|
||||
};
|
||||
var system_tray = {
|
||||
macos: false,
|
||||
linux: true,
|
||||
windows: true
|
||||
};
|
||||
var pakeConf = {
|
||||
windows: windows,
|
||||
user_agent: user_agent,
|
||||
menu: menu,
|
||||
system_tray: system_tray
|
||||
};
|
||||
|
||||
var tauri$2 = {
|
||||
bundle: {
|
||||
icon: [
|
||||
@@ -1964,16 +2216,8 @@ var tauri = {
|
||||
copyright: "",
|
||||
deb: {
|
||||
depends: [
|
||||
"libwebkit2gtk-4.0-dev",
|
||||
"build-essential",
|
||||
"curl",
|
||||
"wget",
|
||||
"libssl-dev",
|
||||
"libgtk-3-dev",
|
||||
"libayatana-appindicator3-dev",
|
||||
"librsvg2-dev",
|
||||
"gnome-video-effects",
|
||||
"gnome-video-effects-extra"
|
||||
"wget"
|
||||
],
|
||||
files: {
|
||||
"/usr/share/applications/com-tw93-weread.desktop": "assets/com-tw93-weread.desktop"
|
||||
@@ -1997,7 +2241,8 @@ var LinuxConf = {
|
||||
|
||||
let tauriConf = {
|
||||
package: CommonConf.package,
|
||||
tauri: CommonConf.tauri
|
||||
tauri: CommonConf.tauri,
|
||||
pake: pakeConf
|
||||
};
|
||||
switch (process.platform) {
|
||||
case "win32": {
|
||||
@@ -2042,11 +2287,28 @@ class MacBuilder {
|
||||
yield mergeTauriConfig(url, options, tauriConf);
|
||||
let dmgName;
|
||||
if (options.multiArch) {
|
||||
yield shellExec(`cd "${npmDirectory}" && npm install --verbose && npm run build:mac`);
|
||||
const isChina = isChinaDomain("www.npmjs.com");
|
||||
if (isChina) {
|
||||
// crates.io也顺便换源
|
||||
const rust_project_dir = path.join(npmDirectory, 'src-tauri', ".cargo");
|
||||
const project_cn_conf = path.join(rust_project_dir, "cn_config.bak");
|
||||
const project_conf = path.join(rust_project_dir, "config");
|
||||
fs$1.copyFile(project_cn_conf, project_conf);
|
||||
yield shellExec(`cd ${npmDirectory} && npm install --registry=https://registry.npmmirror.com && npm run build:mac`);
|
||||
}
|
||||
else {
|
||||
yield shellExec(`cd ${npmDirectory} && npm install && npm run build:mac`);
|
||||
}
|
||||
dmgName = `${name}_${tauriConf.package.version}_universal.dmg`;
|
||||
}
|
||||
else {
|
||||
yield shellExec(`cd "${npmDirectory}" && npm install --verbose && npm run build`);
|
||||
const isChina = isChinaDomain("www.npmjs.com");
|
||||
if (isChina) {
|
||||
yield shellExec(`cd ${npmDirectory} && npm install --registry=https://registry.npmmirror.com && npm run build`);
|
||||
}
|
||||
else {
|
||||
yield shellExec(`cd ${npmDirectory} && npm install && npm run build`);
|
||||
}
|
||||
let arch = "x64";
|
||||
if (process.arch === "arm64") {
|
||||
arch = "aarch64";
|
||||
@@ -2058,8 +2320,8 @@ class MacBuilder {
|
||||
}
|
||||
const appPath = this.getBuildAppPath(npmDirectory, dmgName, options.multiArch);
|
||||
const distPath = path.resolve(`${name}.dmg`);
|
||||
yield fs.copyFile(appPath, distPath);
|
||||
yield fs.unlink(appPath);
|
||||
yield fs$1.copyFile(appPath, distPath);
|
||||
yield fs$1.unlink(appPath);
|
||||
logger.success('Build success!');
|
||||
logger.success('You can find the app installer in', distPath);
|
||||
});
|
||||
@@ -2104,14 +2366,25 @@ class WinBuilder {
|
||||
logger.debug('PakeAppOptions', options);
|
||||
const { name } = options;
|
||||
yield mergeTauriConfig(url, options, tauriConf);
|
||||
yield shellExec(`cd "${npmDirectory}" && npm install --verbose && npm run build`);
|
||||
const isChina = isChinaDomain("www.npmjs.com");
|
||||
if (isChina) {
|
||||
// crates.io也顺便换源
|
||||
const rust_project_dir = path.join(npmDirectory, 'src-tauri', ".cargo");
|
||||
const project_cn_conf = path.join(rust_project_dir, "cn_config.bak");
|
||||
const project_conf = path.join(rust_project_dir, "config");
|
||||
fs$1.copyFile(project_cn_conf, project_conf);
|
||||
yield shellExec(`cd ${npmDirectory} && npm install --registry=https://registry.npmmirror.com && npm run build`);
|
||||
}
|
||||
else {
|
||||
yield shellExec(`cd ${npmDirectory} && npm install && npm run build`);
|
||||
}
|
||||
const language = tauriConf.tauri.bundle.windows.wix.language[0];
|
||||
const arch = process.arch;
|
||||
const msiName = `${name}_${tauriConf.package.version}_${arch}_${language}.msi`;
|
||||
const appPath = this.getBuildAppPath(npmDirectory, msiName);
|
||||
const distPath = path.resolve(`${name}.msi`);
|
||||
yield fs.copyFile(appPath, distPath);
|
||||
yield fs.unlink(appPath);
|
||||
yield fs$1.copyFile(appPath, distPath);
|
||||
yield fs$1.unlink(appPath);
|
||||
logger.success('Build success!');
|
||||
logger.success('You can find the app installer in', distPath);
|
||||
});
|
||||
@@ -2149,7 +2422,18 @@ class LinuxBuilder {
|
||||
logger.debug('PakeAppOptions', options);
|
||||
const { name } = options;
|
||||
yield mergeTauriConfig(url, options, tauriConf);
|
||||
yield shellExec(`cd "${npmDirectory}" && npm install --verbose && npm run build`);
|
||||
const isChina = isChinaDomain("www.npmjs.com");
|
||||
if (isChina) {
|
||||
// crates.io也顺便换源
|
||||
const rust_project_dir = path.join(npmDirectory, 'src-tauri', ".cargo");
|
||||
const project_cn_conf = path.join(rust_project_dir, "cn_config.bak");
|
||||
const project_conf = path.join(rust_project_dir, "config");
|
||||
fs$1.copyFile(project_cn_conf, project_conf);
|
||||
yield shellExec(`cd ${npmDirectory} && npm install --registry=https://registry.npmmirror.com && npm run build`);
|
||||
}
|
||||
else {
|
||||
yield shellExec(`cd ${npmDirectory} && npm install && npm run build`);
|
||||
}
|
||||
let arch;
|
||||
if (process.arch === "x64") {
|
||||
arch = "amd64";
|
||||
@@ -2161,8 +2445,8 @@ class LinuxBuilder {
|
||||
const debName = `${name}_${tauriConf.package.version}_${arch}.deb`;
|
||||
const appPath = this.getBuildAppPath(npmDirectory, "deb", debName);
|
||||
const distPath = path.resolve(`${name}.deb`);
|
||||
yield fs.copyFile(appPath, distPath);
|
||||
yield fs.unlink(appPath);
|
||||
yield fs$1.copyFile(appPath, distPath);
|
||||
yield fs$1.unlink(appPath);
|
||||
logger.success('Build Deb success!');
|
||||
logger.success('You can find the deb app installer in', distPath);
|
||||
}
|
||||
@@ -2170,8 +2454,8 @@ class LinuxBuilder {
|
||||
const appImageName = `${name}_${tauriConf.package.version}_${arch}.AppImage`;
|
||||
const appImagePath = this.getBuildAppPath(npmDirectory, "appimage", appImageName);
|
||||
const distAppPath = path.resolve(`${name}.AppImage`);
|
||||
yield fs.copyFile(appImagePath, distAppPath);
|
||||
yield fs.unlink(appImagePath);
|
||||
yield fs$1.copyFile(appImagePath, distAppPath);
|
||||
yield fs$1.unlink(appImagePath);
|
||||
logger.success('Build AppImage success!');
|
||||
logger.success('You can find the AppImage app installer in', distAppPath);
|
||||
}
|
||||
@@ -2250,6 +2534,7 @@ var dependencies = {
|
||||
chalk: "^5.1.2",
|
||||
commander: "^9.4.1",
|
||||
"file-type": "^18.0.0",
|
||||
"fs-extra": "^11.1.0",
|
||||
"is-url": "^1.2.4",
|
||||
loglevel: "^1.8.1",
|
||||
ora: "^6.1.2",
|
||||
@@ -2264,6 +2549,7 @@ var devDependencies = {
|
||||
"@rollup/plugin-json": "^5.0.1",
|
||||
"@rollup/plugin-terser": "^0.1.0",
|
||||
"@rollup/plugin-typescript": "^9.0.2",
|
||||
"@types/fs-extra": "^9.0.13",
|
||||
"@types/is-url": "^1.2.30",
|
||||
"@types/page-icon": "^0.3.4",
|
||||
"@types/prompts": "^2.4.1",
|
||||
@@ -2304,17 +2590,22 @@ function checkUpdateTips() {
|
||||
program.version(packageJson.version).description('A command-line tool that can quickly convert a webpage into a desktop application.');
|
||||
program
|
||||
.showHelpAfterError()
|
||||
.argument('[url]', 'the web URL you want to package', validateUrlInput)
|
||||
.option('-n, --name <string>', 'application name')
|
||||
.option('-i, --icon <string>', 'application icon', DEFAULT_PAKE_OPTIONS.icon)
|
||||
.option('-w, --width <number>', 'window width', validateNumberInput, DEFAULT_PAKE_OPTIONS.width)
|
||||
.option('-h, --height <number>', 'window height', validateNumberInput, DEFAULT_PAKE_OPTIONS.height)
|
||||
.option('-f, --fullscreen', 'start in full screen mode', DEFAULT_PAKE_OPTIONS.fullscreen)
|
||||
.option('-t, --transparent', 'transparent title bar', DEFAULT_PAKE_OPTIONS.transparent)
|
||||
.option('-r, --no-resizable', 'whether the window can be resizable', DEFAULT_PAKE_OPTIONS.resizable)
|
||||
.option('-d, --debug', 'debug', DEFAULT_PAKE_OPTIONS.debug)
|
||||
.argument('[url]', 'the web url you want to package', validateUrlInput)
|
||||
.option('--name <string>', 'application name')
|
||||
.option('--icon <string>', 'application icon', DEFAULT_PAKE_OPTIONS.icon)
|
||||
.option('--height <number>', 'window height', validateNumberInput, DEFAULT_PAKE_OPTIONS.height)
|
||||
.option('--width <number>', 'window width', validateNumberInput, DEFAULT_PAKE_OPTIONS.width)
|
||||
.option('--no-resizable', 'whether the window can be resizable', DEFAULT_PAKE_OPTIONS.resizable)
|
||||
.option('--fullscreen', 'makes the packaged app start in full screen', DEFAULT_PAKE_OPTIONS.fullscreen)
|
||||
.option('--transparent', 'transparent title bar', DEFAULT_PAKE_OPTIONS.transparent)
|
||||
.option('--user-agent <string>', 'custom user agent', DEFAULT_PAKE_OPTIONS.userAgent)
|
||||
.option('--show-menu', 'show menu in app', DEFAULT_PAKE_OPTIONS.showMenu)
|
||||
.option('--show-system-tray', 'show system tray in app', DEFAULT_PAKE_OPTIONS.showSystemTray)
|
||||
.option('--system-tray-icon <string>', 'custom system tray icon', DEFAULT_PAKE_OPTIONS.systemTrayIcon)
|
||||
.option('--iter-copy-file', 'copy all static file to pake app when url is a local file', DEFAULT_PAKE_OPTIONS.iterCopyFile)
|
||||
.option('-m, --multi-arch', "available for Mac only, and supports both Intel and M1", DEFAULT_PAKE_OPTIONS.multiArch)
|
||||
.option('--targets <string>', "Select the output package format, support deb/appimage/all, only for Linux", DEFAULT_PAKE_OPTIONS.targets)
|
||||
.option('--targets <string>', 'only for linux, default is "deb", option "appaimge" or "all"(deb & appimage)', DEFAULT_PAKE_OPTIONS.targets)
|
||||
.option('--debug', 'debug', DEFAULT_PAKE_OPTIONS.transparent)
|
||||
.action((url, options) => __awaiter(void 0, void 0, void 0, function* () {
|
||||
yield checkUpdateTips();
|
||||
if (!url) {
|
||||
@@ -2327,7 +2618,9 @@ program
|
||||
}
|
||||
const builder = BuilderFactory.create();
|
||||
yield builder.prepare();
|
||||
// logger.warn("you input url is ", url);
|
||||
const appOptions = yield handleOptions(options, url);
|
||||
yield builder.build(url, appOptions);
|
||||
// logger.info(JSON.stringify(appOptions, null, 4));
|
||||
builder.build(url, appOptions);
|
||||
}));
|
||||
program.parse();
|
||||
|
||||
@@ -52,6 +52,7 @@
|
||||
"chalk": "^5.1.2",
|
||||
"commander": "^9.4.1",
|
||||
"file-type": "^18.0.0",
|
||||
"fs-extra": "^11.1.0",
|
||||
"is-url": "^1.2.4",
|
||||
"loglevel": "^1.8.1",
|
||||
"ora": "^6.1.2",
|
||||
@@ -66,6 +67,7 @@
|
||||
"@rollup/plugin-json": "^5.0.1",
|
||||
"@rollup/plugin-terser": "^0.1.0",
|
||||
"@rollup/plugin-typescript": "^9.0.2",
|
||||
"@types/fs-extra": "^9.0.13",
|
||||
"@types/is-url": "^1.2.30",
|
||||
"@types/page-icon": "^0.3.4",
|
||||
"@types/prompts": "^2.4.1",
|
||||
|
||||
99
script/build.bat
vendored
Normal file
99
script/build.bat
vendored
Normal file
@@ -0,0 +1,99 @@
|
||||
@echo off
|
||||
chcp 65001
|
||||
|
||||
if not exist node_modules (
|
||||
call npm i
|
||||
)
|
||||
|
||||
if not exist output (
|
||||
mkdir output
|
||||
)
|
||||
|
||||
|
||||
if not exist output\windows (
|
||||
mkdir output\windows
|
||||
)
|
||||
|
||||
echo.
|
||||
echo =======================
|
||||
echo "build for windows"
|
||||
echo =======================
|
||||
echo.
|
||||
|
||||
:: total package number
|
||||
set /A index=1
|
||||
for /f %%a in (' find /c /v "" ^<"app.csv" ') do set /A total=%%a
|
||||
:: ignore first header line
|
||||
set /A total=total-1
|
||||
|
||||
set old_name=weread
|
||||
set old_title=WeRead
|
||||
set old_zh_name=微信阅读
|
||||
set old_url=https://weread.qq.com/
|
||||
|
||||
:: set init name, we will recovery code to init when build finish.
|
||||
set init_name=%old_name%
|
||||
set init_title=%old_title%
|
||||
set init_zh_name=%old_zh_name%
|
||||
set init_url=%old_url%
|
||||
|
||||
:: for windows, we need replace package name to title
|
||||
:: .\script\sd.exe "\"productName\": \"weread\"" "\"productName\": \"WeRead\"" src-tauri\tauri.conf.json
|
||||
|
||||
for /f "skip=1 tokens=1-4 delims=," %%i in (app.csv) do (
|
||||
setlocal enabledelayedexpansion
|
||||
set name=%%i
|
||||
set title=%%j
|
||||
set name_zh=%%k
|
||||
set url=%%l
|
||||
@echo on
|
||||
|
||||
::echo name is !name! !name_zh! !url!
|
||||
:: replace url
|
||||
.\script\sd.exe -s !old_url! !url! src-tauri\pake.json
|
||||
::replace pacakge name
|
||||
.\script\sd.exe !old_title! !title! src-tauri\tauri.conf.json
|
||||
.\script\sd.exe !old_name! !name! src-tauri\tauri.conf.json
|
||||
.\script\sd.exe !old_name! !name! src-tauri\tauri.windows.conf.json
|
||||
|
||||
echo.
|
||||
::update package info
|
||||
set old_zh_name=!name_zh!
|
||||
set old_name=!name!
|
||||
set old_title=!title!
|
||||
set old_url=!url!
|
||||
::build package
|
||||
echo building package !index!/!total!
|
||||
echo package name is !name! !name_zh!
|
||||
echo npm run build:windows
|
||||
@echo off
|
||||
call npm run tauri build -- --target x86_64-pc-windows-msvc
|
||||
move src-tauri\target\x86_64-pc-windows-msvc\release\bundle\msi\*.msi output\windows\!title!_x64.msi
|
||||
::rm cache
|
||||
del /q /f /s src-tauri\target\x86_64-pc-windows-msvc\release\*.exe
|
||||
del /q /f /s src-tauri\target\x86_64-pc-windows-msvc\release\resources\*.ico
|
||||
del /q /f /s src-tauri\target\x86_64-pc-windows-msvc\release\png\*.ico
|
||||
del /q /f /s src-tauri\target\x86_64-pc-windows-msvc\release\wix\*.*
|
||||
del /q /f /s src-tauri\target\x86_64-pc-windows-msvc\release\app.*
|
||||
rd /s /q src-tauri\target\x86_64-pc-windows-msvc\release\resources
|
||||
rd /s /q src-tauri\target\x86_64-pc-windows-msvc\release\png
|
||||
rd /s /q src-tauri\target\x86_64-pc-windows-msvc\release\wix
|
||||
@echo on
|
||||
echo package build success!
|
||||
echo.
|
||||
echo.
|
||||
|
||||
set /A index=index+1
|
||||
@echo off
|
||||
|
||||
)
|
||||
|
||||
:: for windows, we need replace package name to lower again
|
||||
:: .\script\sd.exe "\"productName\": \"WeRead\"" "\"productName\": \"weread\"" src-tauri\tauri.conf.json
|
||||
echo "output dir is output\windows"
|
||||
|
||||
::recovery code
|
||||
.\script\sd.exe %url% %init_url% src-tauri\pake.json
|
||||
.\script\sd.exe %title% %init_title% src-tauri\tauri.conf.json
|
||||
.\script\sd.exe %name% %init_name% src-tauri\tauri.conf.json
|
||||
.\script\sd.exe %name% %init_name% src-tauri\tauri.windows.conf.json
|
||||
20
script/build.ps1
vendored
20
script/build.ps1
vendored
@@ -29,6 +29,7 @@ $identifier_prefix = "com.tw93"
|
||||
# total package number
|
||||
$index = 1
|
||||
$total = (Get-Content ./app.csv | Measure-Object -Line).Lines
|
||||
$pake_conf_path = "src-tauri/pake.json"
|
||||
$common_conf_path = "src-tauri/tauri.conf.json"
|
||||
$windows_conf_path = "src-tauri/tauri.windows.conf.json"
|
||||
|
||||
@@ -47,20 +48,27 @@ ForEach ($line in (Get-Content -Path .\app.csv | Select-Object -Skip 1)) {
|
||||
Write-Host "name_zh = ${name_zh}"
|
||||
Write-Host "url = ${url}"
|
||||
Write-Host "=========================="
|
||||
# -- replace url --
|
||||
# -- replace url -- #
|
||||
# clear url with regex
|
||||
(Get-Content -Path $common_conf_path -Raw) -replace '"url":\s*"[^"]*"', '"url": ""' | Set-Content -Path $common_conf_path
|
||||
(Get-Content -Path $pake_conf_path -Raw) -replace '"url":\s*"[^"]*"', '"url": ""' | Set-Content -Path $pake_conf_path
|
||||
# replace url with no regex
|
||||
(Get-Content -Path $common_conf_path -Raw) | ForEach-Object { $_.Replace('"url": ""', "`"url`": `"${url}`"") } | Set-Content $common_conf_path
|
||||
(Get-Content -Path $pake_conf_path -Raw) | ForEach-Object { $_.Replace('"url": ""', "`"url`": `"${url}`"") } | Set-Content $pake_conf_path
|
||||
|
||||
|
||||
# replace package name
|
||||
# -- replace package name -- #
|
||||
# clear package_name with regex
|
||||
(Get-Content -Path $common_conf_path -Raw) -replace '"productName":\s*"[^"]*"', '"productName": ""' | Set-Content -Path $common_conf_path
|
||||
# replace package_name with no regex
|
||||
(Get-Content -Path $common_conf_path -Raw) | ForEach-Object { $_.Replace('"productName": ""', "`"productName`": `"${title}`"") } | Set-Content $common_conf_path
|
||||
|
||||
# -- replace icon --
|
||||
|
||||
# -- replace systemTray iconPath -- #
|
||||
# clear systemTray iconPath with regex
|
||||
(Get-Content -Path $common_conf_path -Raw) -replace '"iconPath":\s*"[^"]*"', '"iconPath": ""' | Set-Content -Path $common_conf_path
|
||||
# replace systemTray iconPath with no regex
|
||||
(Get-Content -Path $common_conf_path -Raw) | ForEach-Object { $_.Replace('"iconPath": ""', "`"iconPath`": `"png/${name}_32.ico`"") } | Set-Content $common_conf_path
|
||||
|
||||
# -- replace icon --
|
||||
# clear icon path with regex
|
||||
(Get-Content -Path $windows_conf_path -Raw) -replace '(?s)"icon":\s*\[[^\]]*\]', '"icon": []' | Set-Content -Path $windows_conf_path
|
||||
# replace icon path with no regex
|
||||
@@ -85,7 +93,7 @@ ForEach ($line in (Get-Content -Path .\app.csv | Select-Object -Skip 1)) {
|
||||
if (-not (Test-Path "src-tauri\png\${name}_256.ico")) {
|
||||
Copy-Item "src-tauri\png\icon_256.ico" "src-tauri\png\${name}_256.ico"
|
||||
}
|
||||
|
||||
|
||||
# build package
|
||||
Write-Host "npm run build:windows"
|
||||
npm run tauri build -- --target x86_64-pc-windows-msvc
|
||||
|
||||
23
script/build.sh
vendored
23
script/build.sh
vendored
@@ -61,12 +61,12 @@ do
|
||||
package_title=${arr[1]}
|
||||
package_zh_name=${arr[2]}
|
||||
url=${arr[3]}
|
||||
|
||||
|
||||
# replace package info
|
||||
# clear url with regex
|
||||
$sd "\"url\": \"(.*?)\"," "\"url\": \"\"," src-tauri/tauri.conf.json
|
||||
$sd "\"url\": \"(.*?)\"," "\"url\": \"\"," src-tauri/pake.json
|
||||
# replace url with no regex
|
||||
$sd -s "\"url\": \"\"," "\"url\": \"${url}\"," src-tauri/tauri.conf.json
|
||||
$sd -s "\"url\": \"\"," "\"url\": \"${url}\"," src-tauri/pake.json
|
||||
|
||||
# for apple, need replace title
|
||||
if [[ "$OSTYPE" =~ ^darwin ]]; then
|
||||
@@ -106,14 +106,25 @@ do
|
||||
echo "warning"
|
||||
cp "src-tauri/png/icon_512.png" "src-tauri/png/${package_name}_512.png"
|
||||
fi
|
||||
# -- replace package name -- #
|
||||
# clear package_name with regex
|
||||
$sd "\"productName\": \"(.*?)\"," "\"productName\": \"\"," src-tauri/tauri.conf.json
|
||||
# replace package_name with no regex
|
||||
$sd -s "\"productName\": \"\"," "\"productName\": \"${package_prefix}-${package_name}\"," src-tauri/tauri.conf.json
|
||||
|
||||
# -- replace systemTray iconPath -- #
|
||||
# clear systemTray iconPath with regex
|
||||
$sd "\"iconPath\": \"(.*?)\"," "\"iconPath\": \"\"," src-tauri/tauri.conf.json
|
||||
# replace systemTray iconPath with no regex
|
||||
$sd -s "\"iconPath\": \"\"," "\"iconPath\": \"png/${package_name}_512.png\"," src-tauri/tauri.conf.json
|
||||
|
||||
# -- replace icon -- #
|
||||
# clear icon path with regex
|
||||
$sd "\"icon\": \[\"(.*?)\"\]," "\"icon\": [\"\"]," src-tauri/tauri.linux.conf.json
|
||||
# replace icon path with no regex
|
||||
$sd -s "\"icon\": [\"\"]," "\"icon\": [\"png/${package_name}_512.png\"]," src-tauri/tauri.linux.conf.json
|
||||
|
||||
# -- replace identifier -- #
|
||||
# clear identifier with regex
|
||||
$sd "\"identifier\": \"(.*?)\"," "\"identifier\": \"\"," src-tauri/tauri.linux.conf.json
|
||||
# replace identifier with not regex
|
||||
@@ -122,10 +133,10 @@ do
|
||||
|
||||
new_desktop="${PROJECT_FOLDER}/src-tauri/assets/${package_prefix}-${package_name}.desktop"
|
||||
new_desktop_map_path="/usr/share/applications/${package_prefix}-${package_name}.desktop"
|
||||
for file in `ls ${PROJECT_FOLDER}/src-tauri/assets/`
|
||||
for file in `ls ${PROJECT_FOLDER}/src-tauri/assets/*.desktop`
|
||||
do
|
||||
mv "${PROJECT_FOLDER}/src-tauri/assets/${file}" "${new_desktop}"
|
||||
echo mv "${PROJECT_FOLDER}/src-tauri/assets/${file}" "${new_desktop}"
|
||||
mv "${file}" "${new_desktop}"
|
||||
echo mv "${file}" "${new_desktop}"
|
||||
done
|
||||
# clear desktop file with regex
|
||||
$sd "\"files\": \{\"(.*)\"\}" "\"files\": \{\"\"\}" src-tauri/tauri.linux.conf.json
|
||||
|
||||
BIN
script/sd.exe
vendored
BIN
script/sd.exe
vendored
Binary file not shown.
14
src-tauri/.cargo/cn_config.bak
Normal file
14
src-tauri/.cargo/cn_config.bak
Normal file
@@ -0,0 +1,14 @@
|
||||
[source.crates-io]
|
||||
# To use sparse index, change 'rsproxy' to 'rsproxy-sparse'
|
||||
replace-with = 'rsproxy'
|
||||
|
||||
[source.rsproxy]
|
||||
registry = "https://rsproxy.cn/crates.io-index"
|
||||
[source.rsproxy-sparse]
|
||||
registry = "sparse+https://rsproxy.cn/index/"
|
||||
|
||||
[registries.rsproxy]
|
||||
index = "https://rsproxy.cn/crates.io-index"
|
||||
|
||||
[net]
|
||||
git-fetch-with-cli = true
|
||||
1957
src-tauri/Cargo.lock
generated
1957
src-tauri/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,10 +1,10 @@
|
||||
[package]
|
||||
name = "app"
|
||||
version = "0.1.0"
|
||||
description = "Pake 打包工具"
|
||||
description = "🤱🏻 Turn any webpage into a desktop app with Rust."
|
||||
authors = ["Tw93"]
|
||||
license = ""
|
||||
repository = ""
|
||||
license = "MIT"
|
||||
repository = "https://github.com/tw93/Pake"
|
||||
default-run = "app"
|
||||
edition = "2021"
|
||||
rust-version = "1.63.0"
|
||||
@@ -15,15 +15,15 @@ rust-version = "1.63.0"
|
||||
tauri-build = { version = "1.2.1", features = [] }
|
||||
|
||||
[dependencies]
|
||||
serde_json = "1.0.91"
|
||||
serde = { version = "1.0.152", features = ["derive"] }
|
||||
tauri = { version = "1.2.4", features = [] }
|
||||
serde_json = "1.0.89"
|
||||
serde = { version = "1.0.150", features = ["derive"] }
|
||||
tauri = { version = "1.2.1", features = ["api-all", "devtools", "system-tray"] }
|
||||
image = "0.24.5"
|
||||
home = "0.5.4"
|
||||
tauri-utils = "1.2.1"
|
||||
webbrowser = "0.8.7"
|
||||
wry = "0.23.4"
|
||||
dirs = "4.0"
|
||||
home = "0.5"
|
||||
dirs = "5.0"
|
||||
libc = "0.2"
|
||||
download_rs = { version = "0.2.0", features = ["sync_download"] }
|
||||
tauri-plugin-window-state = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "dev" }
|
||||
|
||||
[features]
|
||||
# by default Tauri runs in production mode
|
||||
@@ -32,5 +32,3 @@ default = ["custom-protocol"]
|
||||
# this feature is used used for production builds where `devPath` points to the filesystem
|
||||
# DO NOT remove this
|
||||
custom-protocol = ["tauri/custom-protocol"]
|
||||
# Enable DevTools for debugging.
|
||||
devtools = []
|
||||
|
||||
28
src-tauri/pake.json
Normal file
28
src-tauri/pake.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"windows": [
|
||||
{
|
||||
"url": "https://weread.qq.com/",
|
||||
"transparent": true,
|
||||
"fullscreen": false,
|
||||
"width": 1200,
|
||||
"height": 780,
|
||||
"resizable": true,
|
||||
"url_type": "web"
|
||||
}
|
||||
],
|
||||
"user_agent": {
|
||||
"macos": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.1 Safari/605.1.15",
|
||||
"linux": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36",
|
||||
"windows": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36"
|
||||
},
|
||||
"menu": {
|
||||
"macos": true,
|
||||
"linux": false,
|
||||
"windows": false
|
||||
},
|
||||
"system_tray": {
|
||||
"macos": false,
|
||||
"linux": true,
|
||||
"windows": true
|
||||
}
|
||||
}
|
||||
63
src-tauri/src/app/config.rs
Normal file
63
src-tauri/src/app/config.rs
Normal file
@@ -0,0 +1,63 @@
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct WindowConfig {
|
||||
pub url: String,
|
||||
pub transparent: bool,
|
||||
pub fullscreen: bool,
|
||||
pub width: f64,
|
||||
pub height: f64,
|
||||
pub resizable: bool,
|
||||
pub url_type: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct PlatformSpecific<T> {
|
||||
pub macos: T,
|
||||
pub linux: T,
|
||||
pub windows: T,
|
||||
}
|
||||
|
||||
impl<T> PlatformSpecific<T> {
|
||||
pub const fn get(&self) -> &T {
|
||||
#[cfg(target_os = "macos")]
|
||||
let platform = &self.macos;
|
||||
#[cfg(target_os = "linux")]
|
||||
let platform = &self.linux;
|
||||
#[cfg(target_os = "windows")]
|
||||
let platform = &self.windows;
|
||||
|
||||
platform
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> PlatformSpecific<T>
|
||||
where
|
||||
T: Copy,
|
||||
{
|
||||
pub const fn copied(&self) -> T {
|
||||
*self.get()
|
||||
}
|
||||
}
|
||||
|
||||
pub type UserAgent = PlatformSpecific<String>;
|
||||
pub type FunctionON = PlatformSpecific<bool>;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct PakeConfig {
|
||||
pub windows: Vec<WindowConfig>,
|
||||
pub user_agent: UserAgent,
|
||||
pub menu: FunctionON,
|
||||
pub system_tray: FunctionON,
|
||||
}
|
||||
|
||||
impl PakeConfig {
|
||||
pub fn show_menu(&self) -> bool {
|
||||
self.menu.copied()
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
pub fn show_system_tray(&self) -> bool {
|
||||
self.system_tray.copied()
|
||||
}
|
||||
}
|
||||
47
src-tauri/src/app/invoke.rs
Normal file
47
src-tauri/src/app/invoke.rs
Normal file
@@ -0,0 +1,47 @@
|
||||
use crate::util::{check_file_or_append, get_download_message, show_toast};
|
||||
use download_rs::sync_download::Download;
|
||||
use tauri::{api, command, AppHandle, Manager, Window};
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
pub struct DownloadFileParams {
|
||||
url: String,
|
||||
filename: String,
|
||||
}
|
||||
|
||||
#[command]
|
||||
pub fn drag_window(app: AppHandle) {
|
||||
app.get_window("pake").unwrap().start_dragging().unwrap();
|
||||
}
|
||||
|
||||
#[command]
|
||||
pub fn fullscreen(app: AppHandle) {
|
||||
let win = app.get_window("pake").unwrap();
|
||||
if win.is_fullscreen().unwrap() {
|
||||
win.set_fullscreen(false).unwrap();
|
||||
} else {
|
||||
win.set_fullscreen(true).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn open_browser(app: AppHandle, url: String) {
|
||||
api::shell::open(&app.shell_scope(), url, None).unwrap();
|
||||
}
|
||||
|
||||
#[command]
|
||||
pub async fn download_file(app: AppHandle, params: DownloadFileParams) -> Result<(), String> {
|
||||
let window: Window = app.get_window("pake").unwrap();
|
||||
let output_path = api::path::download_dir().unwrap().join(params.filename);
|
||||
let file_path = check_file_or_append(output_path.to_str().unwrap());
|
||||
let download = Download::new(¶ms.url, Some(&file_path), None);
|
||||
match download.download() {
|
||||
Ok(_) => {
|
||||
show_toast(&window, &get_download_message());
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => {
|
||||
show_toast(&window, &e.to_string());
|
||||
Err(e.to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
106
src-tauri/src/app/menu.rs
Normal file
106
src-tauri/src/app/menu.rs
Normal file
@@ -0,0 +1,106 @@
|
||||
use tauri::MenuItem;
|
||||
|
||||
use tauri::{CustomMenuItem, Menu, Submenu, WindowMenuEvent};
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
||||
use tauri::{Manager, SystemTray, SystemTrayEvent, SystemTrayMenu, WindowBuilder, WindowUrl};
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
||||
use tauri_plugin_window_state::{AppHandleExt, StateFlags};
|
||||
|
||||
pub fn get_menu() -> Menu {
|
||||
let close = CustomMenuItem::new("close".to_string(), "Close Window").accelerator("CmdOrCtrl+W");
|
||||
let first_menu = Menu::new()
|
||||
.add_native_item(MenuItem::Copy)
|
||||
.add_native_item(MenuItem::Cut)
|
||||
.add_native_item(MenuItem::Paste)
|
||||
.add_native_item(MenuItem::Undo)
|
||||
.add_native_item(MenuItem::Redo)
|
||||
.add_native_item(MenuItem::SelectAll)
|
||||
.add_native_item(MenuItem::Separator)
|
||||
.add_native_item(MenuItem::EnterFullScreen)
|
||||
.add_native_item(MenuItem::Minimize)
|
||||
.add_native_item(MenuItem::Hide)
|
||||
.add_native_item(MenuItem::HideOthers)
|
||||
.add_native_item(MenuItem::ShowAll)
|
||||
.add_native_item(MenuItem::Separator)
|
||||
.add_item(close)
|
||||
.add_native_item(MenuItem::Quit);
|
||||
|
||||
let app_menu = Submenu::new("File", first_menu);
|
||||
Menu::new().add_submenu(app_menu)
|
||||
}
|
||||
|
||||
pub fn menu_event_handle(event: WindowMenuEvent) {
|
||||
if event.menu_item_id() == "close" {
|
||||
event.window().minimize().expect("can't minimize window");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
||||
pub fn get_system_tray(show_menu: bool) -> SystemTray {
|
||||
let hide_app = CustomMenuItem::new("hide_app".to_string(), "Hide App");
|
||||
let show_app = CustomMenuItem::new("show_app".to_string(), "Show App");
|
||||
let quit = CustomMenuItem::new("quit".to_string(), "Quit");
|
||||
let about = CustomMenuItem::new("about".to_string(), "About");
|
||||
let tray_menu = SystemTrayMenu::new().add_item(hide_app).add_item(show_app);
|
||||
if show_menu {
|
||||
let hide_menu = CustomMenuItem::new("hide_menu".to_string(), "Hide Menu");
|
||||
let show_menu = CustomMenuItem::new("show_menu".to_string(), "Show Menu");
|
||||
let tray_menu = tray_menu
|
||||
.add_item(hide_menu)
|
||||
.add_item(show_menu)
|
||||
.add_item(quit)
|
||||
.add_item(about);
|
||||
SystemTray::new().with_menu(tray_menu)
|
||||
} else {
|
||||
let tray_menu = tray_menu.add_item(quit).add_item(about);
|
||||
SystemTray::new().with_menu(tray_menu)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
||||
pub fn system_tray_handle(app: &tauri::AppHandle, event: SystemTrayEvent) {
|
||||
if let SystemTrayEvent::MenuItemClick { tray_id: _, id, .. } = event {
|
||||
match id.as_str() {
|
||||
"hide_app" => {
|
||||
app.get_window("pake").unwrap().hide().unwrap();
|
||||
}
|
||||
"show_app" => {
|
||||
app.get_window("pake").unwrap().show().unwrap();
|
||||
}
|
||||
"hide_menu" => {
|
||||
app.get_window("pake")
|
||||
.unwrap()
|
||||
.menu_handle()
|
||||
.hide()
|
||||
.unwrap();
|
||||
}
|
||||
"show_menu" => {
|
||||
app.get_window("pake")
|
||||
.unwrap()
|
||||
.menu_handle()
|
||||
.show()
|
||||
.unwrap();
|
||||
}
|
||||
"quit" => {
|
||||
let _res = app.save_window_state(StateFlags::all());
|
||||
// println!("save windows state result {:?}", _res);
|
||||
std::process::exit(0);
|
||||
}
|
||||
"about" => {
|
||||
let _about_window = WindowBuilder::new(
|
||||
app,
|
||||
"about",
|
||||
WindowUrl::App(std::path::PathBuf::from("about_pake.html")),
|
||||
)
|
||||
.resizable(true)
|
||||
.title("About")
|
||||
.inner_size(600.0, 400.0)
|
||||
.build()
|
||||
.expect("can't open about!");
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
};
|
||||
}
|
||||
4
src-tauri/src/app/mod.rs
Normal file
4
src-tauri/src/app/mod.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
pub mod config;
|
||||
pub mod invoke;
|
||||
pub mod menu;
|
||||
pub mod window;
|
||||
48
src-tauri/src/app/window.rs
Normal file
48
src-tauri/src/app/window.rs
Normal file
@@ -0,0 +1,48 @@
|
||||
use crate::app::config::PakeConfig;
|
||||
use std::path::PathBuf;
|
||||
use tauri::{App, Window, WindowBuilder, WindowUrl};
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
use tauri::TitleBarStyle;
|
||||
|
||||
pub fn get_window(app: &mut App, config: PakeConfig, _data_dir: PathBuf) -> Window {
|
||||
let window_config = config
|
||||
.windows
|
||||
.first()
|
||||
.expect("At least one window configuration is required");
|
||||
|
||||
let user_agent = config.user_agent.get();
|
||||
|
||||
let url = match window_config.url_type.as_str() {
|
||||
"web" => WindowUrl::App(window_config.url.parse().unwrap()),
|
||||
"local" => WindowUrl::App(PathBuf::from(&window_config.url)),
|
||||
_ => panic!("url type can only be web or local"),
|
||||
};
|
||||
|
||||
let mut window_builder = WindowBuilder::new(app, "pake", url)
|
||||
.title("")
|
||||
.user_agent(user_agent)
|
||||
.resizable(window_config.resizable)
|
||||
.fullscreen(window_config.fullscreen)
|
||||
.inner_size(window_config.width, window_config.height)
|
||||
.visible(false) // Prevent initial shaking
|
||||
.initialization_script(include_str!("../inject/style.js"))
|
||||
.initialization_script(include_str!("../inject/index.js"));
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
let title_bar_style = if window_config.transparent {
|
||||
TitleBarStyle::Overlay
|
||||
} else {
|
||||
TitleBarStyle::Visible
|
||||
};
|
||||
window_builder = window_builder.title_bar_style(title_bar_style)
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
{
|
||||
window_builder = window_builder.data_directory(_data_dir);
|
||||
}
|
||||
|
||||
window_builder.build().unwrap()
|
||||
}
|
||||
170
src-tauri/src/inject/index.js
Normal file
170
src-tauri/src/inject/index.js
Normal file
@@ -0,0 +1,170 @@
|
||||
const shortcuts = {
|
||||
ArrowUp: () => scrollTo(0, 0),
|
||||
ArrowDown: () => scrollTo(0, document.body.scrollHeight),
|
||||
ArrowLeft: () => window.history.back(),
|
||||
ArrowRight: () => window.history.forward(),
|
||||
'[': () => window.history.back(),
|
||||
']': () => window.history.forward(),
|
||||
r: () => window.location.reload(),
|
||||
'-': () => zoomOut(),
|
||||
'=': () => zoomIn(),
|
||||
'+': () => zoomIn(),
|
||||
0: () => setZoom('100%'),
|
||||
};
|
||||
|
||||
function setZoom(zoom) {
|
||||
const html = document.getElementsByTagName('html')[0];
|
||||
html.style.zoom = zoom;
|
||||
window.localStorage.setItem('htmlZoom', zoom);
|
||||
}
|
||||
|
||||
function zoomCommon(zoomChange) {
|
||||
const currentZoom = window.localStorage.getItem('htmlZoom') || '100%';
|
||||
setZoom(zoomChange(currentZoom));
|
||||
}
|
||||
|
||||
function zoomIn() {
|
||||
zoomCommon((currentZoom) => `${Math.min(parseInt(currentZoom) + 10, 200)}%`);
|
||||
}
|
||||
|
||||
function zoomOut() {
|
||||
zoomCommon((currentZoom) => `${Math.max(parseInt(currentZoom) - 10, 30)}%`);
|
||||
}
|
||||
|
||||
function handleShortcut(event) {
|
||||
if (shortcuts[event.key]) {
|
||||
event.preventDefault();
|
||||
shortcuts[event.key]();
|
||||
}
|
||||
}
|
||||
|
||||
//这里参考 ChatGPT 的代码
|
||||
const uid = () => window.crypto.getRandomValues(new Uint32Array(1))[0];
|
||||
function transformCallback(callback = () => {}, once = false) {
|
||||
const identifier = uid();
|
||||
const prop = `_${identifier}`;
|
||||
Object.defineProperty(window, prop, {
|
||||
value: (result) => {
|
||||
if (once) {
|
||||
Reflect.deleteProperty(window, prop);
|
||||
}
|
||||
return callback(result);
|
||||
},
|
||||
writable: false,
|
||||
configurable: true,
|
||||
});
|
||||
return identifier;
|
||||
}
|
||||
async function invoke(cmd, args) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!window.__TAURI_POST_MESSAGE__)
|
||||
reject('__TAURI_POST_MESSAGE__ does not exist~');
|
||||
const callback = transformCallback((e) => {
|
||||
resolve(e);
|
||||
Reflect.deleteProperty(window, `_${error}`);
|
||||
}, true);
|
||||
const error = transformCallback((e) => {
|
||||
reject(e);
|
||||
Reflect.deleteProperty(window, `_${callback}`);
|
||||
}, true);
|
||||
window.__TAURI_POST_MESSAGE__({
|
||||
cmd,
|
||||
callback,
|
||||
error,
|
||||
...args,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const topDom = document.createElement('div');
|
||||
topDom.id = 'pack-top-dom';
|
||||
document.body.appendChild(topDom);
|
||||
const domEl = document.getElementById('pack-top-dom');
|
||||
|
||||
domEl.addEventListener('mousedown', (e) => {
|
||||
e.preventDefault();
|
||||
if (e.buttons === 1 && e.detail !== 2) {
|
||||
invoke('drag_window');
|
||||
}
|
||||
});
|
||||
|
||||
domEl.addEventListener('touchstart', () => {
|
||||
invoke('drag_window');
|
||||
});
|
||||
|
||||
domEl.addEventListener('dblclick', () => {
|
||||
invoke('fullscreen');
|
||||
});
|
||||
|
||||
document.addEventListener('keyup', (event) => {
|
||||
if (/windows|linux/i.test(navigator.userAgent) && event.ctrlKey) {
|
||||
handleShortcut(event);
|
||||
}
|
||||
if (/macintosh|mac os x/i.test(navigator.userAgent) && event.metaKey) {
|
||||
handleShortcut(event);
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('click', (e) => {
|
||||
const anchorElement = e.target.closest('a');
|
||||
|
||||
if (anchorElement && anchorElement.href) {
|
||||
const target = anchorElement.target;
|
||||
anchorElement.target = '_self';
|
||||
const hrefUrl = new URL(anchorElement.href);
|
||||
const absoluteUrl = hrefUrl.href;
|
||||
|
||||
// 处理外部链接跳转
|
||||
if (window.location.host !== hrefUrl.host && target === '_blank') {
|
||||
e.preventDefault();
|
||||
invoke('open_browser', { url: absoluteUrl });
|
||||
return;
|
||||
}
|
||||
|
||||
// 处理下载链接让Rust处理
|
||||
if (/\.[a-zA-Z0-9]+$/i.test(absoluteUrl)) {
|
||||
e.preventDefault();
|
||||
// invoke('open_browser', { url: absoluteUrl });
|
||||
invoke('download_file', {
|
||||
params: {
|
||||
url: absoluteUrl,
|
||||
filename: getFilenameFromUrl(absoluteUrl),
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
setDefaultZoom();
|
||||
});
|
||||
|
||||
function setDefaultZoom() {
|
||||
const htmlZoom = window.localStorage.getItem('htmlZoom');
|
||||
if (htmlZoom) {
|
||||
setZoom(htmlZoom);
|
||||
}
|
||||
}
|
||||
|
||||
function getFilenameFromUrl(url) {
|
||||
const urlPath = new URL(url).pathname;
|
||||
const filename = urlPath.substring(urlPath.lastIndexOf('/') + 1);
|
||||
return filename;
|
||||
}
|
||||
|
||||
function pakeToast(msg) {
|
||||
const m = document.createElement('div');
|
||||
m.innerHTML = msg;
|
||||
m.style.cssText =
|
||||
'max-width:60%;min-width: 80px;padding:0 12px;height: 32px;color: rgb(255, 255, 255);line-height: 32px;text-align: center;border-radius: 8px;position: fixed; bottom:24px;right: 28px;z-index: 999999;background: rgba(0, 0, 0,.8);font-size: 13px;';
|
||||
document.body.appendChild(m);
|
||||
setTimeout(function () {
|
||||
const d = 0.5;
|
||||
m.style.transition =
|
||||
'transform ' + d + 's ease-in, opacity ' + d + 's ease-in';
|
||||
m.style.opacity = '0';
|
||||
setTimeout(function () {
|
||||
document.body.removeChild(m);
|
||||
}, d * 1000);
|
||||
}, 3000);
|
||||
}
|
||||
@@ -1,39 +1,5 @@
|
||||
/**
|
||||
* @typedef {string} KeyboardKey `event.key` 的代号,
|
||||
* 见 <https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values>
|
||||
* @typedef {() => void} OnKeyDown 使用者按下 [CtrlKey] 或者 ⌘ [KeyboardKey]时应该执行的行为
|
||||
* 以 Ctrl键或者Meta 键 (⌘) 为首的快捷键清单。
|
||||
* 每个写在这里的 shortcuts 都会运行 {@link Event.preventDefault}.
|
||||
* @type {Record<KeyboardKey, OnKeyDown>}
|
||||
*/
|
||||
|
||||
const metaKeyShortcuts = {
|
||||
ArrowUp: () => scrollTo(0, 0),
|
||||
ArrowDown: () => scrollTo(0, document.body.scrollHeight),
|
||||
"[": () => window.history.back(),
|
||||
"]": () => window.history.forward(),
|
||||
r: () => window.location.reload(),
|
||||
"-": () => zoomOut(),
|
||||
"=": () => zoomIn(),
|
||||
"+": () => zoomIn(),
|
||||
0: () => zoomCommon(() => "100%"),
|
||||
};
|
||||
|
||||
const ctrlKeyShortcuts = {
|
||||
ArrowUp: () => scrollTo(0, 0),
|
||||
ArrowDown: () => scrollTo(0, document.body.scrollHeight),
|
||||
ArrowLeft: () => window.history.back(),
|
||||
ArrowRight: () => window.history.forward(),
|
||||
r: () => window.location.reload(),
|
||||
"-": () => zoomOut(),
|
||||
"=": () => zoomIn(),
|
||||
"+": () => zoomIn(),
|
||||
0: () => zoomCommon(() => "100%"),
|
||||
};
|
||||
|
||||
window.addEventListener("DOMContentLoaded", (_event) => {
|
||||
const style = document.createElement("style");
|
||||
style.innerHTML = `
|
||||
window.addEventListener('DOMContentLoaded', (_event) => {
|
||||
const css = `
|
||||
#page #footer-wrapper,
|
||||
.drawing-board .toolbar .toolbar-action,
|
||||
.c-swiper-container,
|
||||
@@ -49,6 +15,8 @@ window.addEventListener("DOMContentLoaded", (_event) => {
|
||||
#masthead-ad,
|
||||
#app > header > div > div.menu,
|
||||
#root > div > div.fixed.top-0.left-0.w-64.h-screen.p-10.pb-0.flex.flex-col.justify-between > div > div.space-y-4 > a:nth-child(3),
|
||||
#app > div.layout > div.main-container > div.side-bar > div,
|
||||
#app > div.layout > div.main-container > div.side-bar > li.divider,
|
||||
#Rightbar > div:nth-child(6) > div.sidebar_compliance {
|
||||
display: none !important;
|
||||
}
|
||||
@@ -130,7 +98,8 @@ window.addEventListener("DOMContentLoaded", (_event) => {
|
||||
top: 30px;
|
||||
}
|
||||
|
||||
.geist-page nav.dashboard_nav__PRmJv {
|
||||
.geist-page nav.dashboard_nav__PRmJv,
|
||||
#app > div.layout > div.header-container.showSearchBoxOrHeaderFixed > header > a {
|
||||
padding-top:10px;
|
||||
}
|
||||
|
||||
@@ -1,278 +1,73 @@
|
||||
// at the top of main.rs - that will prevent the console from showing
|
||||
#![windows_subsystem = "windows"]
|
||||
extern crate image;
|
||||
#![cfg_attr(
|
||||
all(not(debug_assertions), target_os = "windows"),
|
||||
windows_subsystem = "windows"
|
||||
)]
|
||||
|
||||
use dirs::download_dir;
|
||||
use std::path::PathBuf;
|
||||
use tauri_utils::config::{Config, WindowConfig};
|
||||
use wry::{
|
||||
application::{
|
||||
event::{Event, StartCause, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
menu::MenuType,
|
||||
window::{Fullscreen, Window, WindowBuilder},
|
||||
},
|
||||
webview::WebViewBuilder,
|
||||
};
|
||||
mod app;
|
||||
mod util;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
use wry::application::{
|
||||
accelerator::{Accelerator, SysMods},
|
||||
keyboard::KeyCode,
|
||||
menu::{MenuBar as Menu, MenuItem, MenuItemAttributes},
|
||||
platform::macos::WindowBuilderExtMacOS,
|
||||
};
|
||||
use app::{invoke, menu, window};
|
||||
use invoke::{download_file, drag_window, fullscreen, open_browser};
|
||||
use menu::{get_menu, menu_event_handle};
|
||||
use tauri_plugin_window_state::{Builder as windowStatePlugin, StateFlags, WindowExt};
|
||||
use util::{get_data_dir, get_pake_config};
|
||||
use window::get_window;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
use wry::application::window::Icon;
|
||||
pub fn run_app() {
|
||||
let (pake_config, tauri_config) = get_pake_config();
|
||||
let show_menu = pake_config.show_menu();
|
||||
let menu = get_menu();
|
||||
let data_dir = get_data_dir(tauri_config);
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
||||
use wry::webview::WebContext;
|
||||
let mut tauri_app = tauri::Builder::default();
|
||||
|
||||
enum UserEvent {
|
||||
DownloadStarted(String, String),
|
||||
DownloadComplete(Option<PathBuf>, bool),
|
||||
}
|
||||
|
||||
fn main() -> wry::Result<()> {
|
||||
#[cfg(target_os = "macos")]
|
||||
let (menu_bar_menu, close_item) = {
|
||||
let mut menu_bar_menu = Menu::new();
|
||||
let mut first_menu = Menu::new();
|
||||
first_menu.add_native_item(MenuItem::Hide);
|
||||
first_menu.add_native_item(MenuItem::EnterFullScreen);
|
||||
first_menu.add_native_item(MenuItem::Minimize);
|
||||
first_menu.add_native_item(MenuItem::Separator);
|
||||
first_menu.add_native_item(MenuItem::Copy);
|
||||
first_menu.add_native_item(MenuItem::Cut);
|
||||
first_menu.add_native_item(MenuItem::Paste);
|
||||
first_menu.add_native_item(MenuItem::Undo);
|
||||
first_menu.add_native_item(MenuItem::Redo);
|
||||
first_menu.add_native_item(MenuItem::SelectAll);
|
||||
first_menu.add_native_item(MenuItem::Separator);
|
||||
let close_item = first_menu.add_item(
|
||||
MenuItemAttributes::new("CloseWindow")
|
||||
.with_accelerators(&Accelerator::new(SysMods::Cmd, KeyCode::KeyW)),
|
||||
);
|
||||
first_menu.add_native_item(MenuItem::Quit);
|
||||
menu_bar_menu.add_submenu("App", true, first_menu);
|
||||
(menu_bar_menu, close_item)
|
||||
};
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
||||
let (
|
||||
package_name,
|
||||
WindowConfig {
|
||||
url,
|
||||
width,
|
||||
height,
|
||||
resizable,
|
||||
fullscreen,
|
||||
..
|
||||
},
|
||||
) = {
|
||||
let (package_name, windows_config) = get_windows_config();
|
||||
(
|
||||
package_name
|
||||
.expect("can't get package name in config file")
|
||||
.to_lowercase(),
|
||||
windows_config.unwrap_or_default(),
|
||||
)
|
||||
};
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
let WindowConfig {
|
||||
url,
|
||||
width,
|
||||
height,
|
||||
resizable,
|
||||
transparent,
|
||||
fullscreen,
|
||||
..
|
||||
} = get_windows_config().1.unwrap_or_default();
|
||||
|
||||
let event_loop: EventLoop<UserEvent> = EventLoop::with_user_event();
|
||||
let proxy = event_loop.create_proxy();
|
||||
let common_window = WindowBuilder::new()
|
||||
.with_title("")
|
||||
.with_resizable(resizable)
|
||||
.with_fullscreen(if fullscreen {
|
||||
Some(Fullscreen::Borderless(None))
|
||||
} else {
|
||||
None
|
||||
})
|
||||
.with_inner_size(wry::application::dpi::LogicalSize::new(width, height));
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
let window = {
|
||||
let mut icon_path = format!("png/{}_32.ico", package_name);
|
||||
// If there is no setting, use the default one.
|
||||
if !std::path::Path::new(&icon_path).exists() {
|
||||
icon_path = "png/icon_32.ico".to_string();
|
||||
}
|
||||
let icon = load_icon(std::path::Path::new(&icon_path));
|
||||
common_window
|
||||
.with_decorations(true)
|
||||
.with_window_icon(Some(icon))
|
||||
.build(&event_loop)
|
||||
.unwrap()
|
||||
};
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
let window = common_window.build(&event_loop).unwrap();
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
let window = common_window
|
||||
.with_fullsize_content_view(true)
|
||||
.with_titlebar_buttons_hidden(false)
|
||||
.with_titlebar_transparent(transparent)
|
||||
.with_title_hidden(true)
|
||||
.with_menu(menu_bar_menu)
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
// Handling events of JS -> Rust
|
||||
let handler = move |window: &Window, req: String| {
|
||||
if req == "drag_window" {
|
||||
let _ = window.drag_window();
|
||||
} else if req == "fullscreen" {
|
||||
let is_maximized = window.is_maximized();
|
||||
window.set_maximized(!is_maximized);
|
||||
} else if req.starts_with("open_browser") {
|
||||
let href = req.replace("open_browser:", "");
|
||||
webbrowser::open(&href).expect("no browser");
|
||||
}
|
||||
};
|
||||
|
||||
let download_started = {
|
||||
let proxy = proxy.clone();
|
||||
move |uri: String, default_path: &mut PathBuf| {
|
||||
let path = download_dir()
|
||||
.unwrap()
|
||||
.join(default_path.display().to_string())
|
||||
.as_path()
|
||||
.to_path_buf();
|
||||
*default_path = path.clone();
|
||||
let submitted = proxy
|
||||
.send_event(UserEvent::DownloadStarted(uri, path.display().to_string()))
|
||||
.is_ok();
|
||||
submitted
|
||||
}
|
||||
};
|
||||
|
||||
let download_completed = {
|
||||
move |_uri, path, success| {
|
||||
let _ = proxy.send_event(UserEvent::DownloadComplete(path, success));
|
||||
}
|
||||
};
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
let webview = {
|
||||
let user_agent_string = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.1 Safari/605.1.15";
|
||||
WebViewBuilder::new(window)?
|
||||
.with_user_agent(user_agent_string)
|
||||
.with_url(&url.to_string())?
|
||||
.with_devtools(cfg!(feature = "devtools"))
|
||||
.with_initialization_script(include_str!("pake.js"))
|
||||
.with_ipc_handler(handler)
|
||||
.with_back_forward_navigation_gestures(true)
|
||||
.with_download_started_handler(download_started)
|
||||
.with_download_completed_handler(download_completed)
|
||||
.build()?
|
||||
};
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
||||
let webview = {
|
||||
let home_dir = match home::home_dir() {
|
||||
Some(path1) => path1,
|
||||
None => panic!("Error, can't found you home dir!!"),
|
||||
};
|
||||
#[cfg(target_os = "windows")]
|
||||
let data_dir = home_dir.join("AppData").join("Roaming").join(package_name);
|
||||
#[cfg(target_os = "linux")]
|
||||
let data_dir = home_dir.join(".config").join(package_name);
|
||||
if !data_dir.exists() {
|
||||
std::fs::create_dir(&data_dir)
|
||||
.unwrap_or_else(|_| panic!("can't create dir {}", data_dir.display()));
|
||||
}
|
||||
let mut web_content = WebContext::new(Some(data_dir));
|
||||
#[cfg(target_os = "windows")]
|
||||
let user_agent_string = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36";
|
||||
#[cfg(target_os = "linux")]
|
||||
let user_agent_string = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36";
|
||||
WebViewBuilder::new(window)?
|
||||
.with_user_agent(user_agent_string)
|
||||
.with_url(&url.to_string())?
|
||||
.with_devtools(cfg!(feature = "devtools"))
|
||||
.with_initialization_script(include_str!("pake.js"))
|
||||
.with_ipc_handler(handler)
|
||||
.with_web_context(&mut web_content)
|
||||
.with_download_started_handler(download_started)
|
||||
.with_download_completed_handler(download_completed)
|
||||
.build()?
|
||||
};
|
||||
#[cfg(feature = "devtools")]
|
||||
{
|
||||
webview.open_devtools();
|
||||
if show_menu {
|
||||
tauri_app = tauri_app.menu(menu).on_menu_event(menu_event_handle);
|
||||
}
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
{
|
||||
use menu::{get_system_tray, system_tray_handle};
|
||||
|
||||
match event {
|
||||
Event::NewEvents(StartCause::Init) => println!("Wry has started!"),
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => *control_flow = ControlFlow::Exit,
|
||||
Event::MenuEvent {
|
||||
menu_id,
|
||||
origin: MenuType::MenuBar,
|
||||
..
|
||||
} => {
|
||||
#[cfg(target_os = "macos")]
|
||||
if menu_id == close_item.clone().id() {
|
||||
webview.window().set_minimized(true);
|
||||
}
|
||||
println!("Clicked on {menu_id:?}");
|
||||
}
|
||||
Event::UserEvent(UserEvent::DownloadStarted(uri, temp_dir)) => {
|
||||
println!("Download: {uri}");
|
||||
println!("Will write to: {temp_dir:?}");
|
||||
}
|
||||
Event::UserEvent(UserEvent::DownloadComplete(_, success)) => {
|
||||
println!("Succeeded: {success}");
|
||||
if success {
|
||||
let _ = webview
|
||||
.evaluate_script("window.pakeToast('Downloaded to download folder~')");
|
||||
} else {
|
||||
println!("No output path")
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
let show_system_tray = pake_config.show_system_tray();
|
||||
let system_tray = get_system_tray(show_menu);
|
||||
|
||||
if show_system_tray {
|
||||
tauri_app = tauri_app
|
||||
.system_tray(system_tray)
|
||||
.on_system_tray_event(system_tray_handle);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
tauri_app
|
||||
.plugin(windowStatePlugin::default().build())
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
drag_window,
|
||||
fullscreen,
|
||||
open_browser,
|
||||
download_file
|
||||
])
|
||||
.setup(|app| {
|
||||
let _window = get_window(app, pake_config, data_dir);
|
||||
// Prevent initial shaking
|
||||
_window.restore_state(StateFlags::all()).unwrap();
|
||||
_window.show().unwrap();
|
||||
#[cfg(feature = "devtools")]
|
||||
{
|
||||
_window.open_devtools();
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.on_window_event(|event| {
|
||||
if let tauri::WindowEvent::CloseRequested { api, .. } = event.event() {
|
||||
event.window().minimize().unwrap();
|
||||
api.prevent_close();
|
||||
}
|
||||
})
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
|
||||
fn get_windows_config() -> (Option<String>, Option<WindowConfig>) {
|
||||
let config_file = include_str!("../tauri.conf.json");
|
||||
let config: Config = serde_json::from_str(config_file).expect("failed to parse windows config");
|
||||
(
|
||||
config.package.product_name.clone(),
|
||||
config.tauri.windows.first().cloned(),
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn load_icon(path: &std::path::Path) -> Icon {
|
||||
let (icon_rgba, icon_width, icon_height) = {
|
||||
// alternatively, you can embed the icon in the binary through `include_bytes!` macro and use `image::load_from_memory`
|
||||
let image = image::open(path)
|
||||
.expect("Failed to open icon path")
|
||||
.into_rgba8();
|
||||
let (width, height) = image.dimensions();
|
||||
let rgba = image.into_raw();
|
||||
(rgba, width, height)
|
||||
};
|
||||
Icon::from_rgba(icon_rgba, icon_width, icon_height).expect("Failed to open icon")
|
||||
fn main() {
|
||||
run_app()
|
||||
}
|
||||
|
||||
60
src-tauri/src/util.rs
Normal file
60
src-tauri/src/util.rs
Normal file
@@ -0,0 +1,60 @@
|
||||
use crate::app::config::PakeConfig;
|
||||
use dirs::config_dir;
|
||||
use libc::getenv;
|
||||
use std::ffi::CStr;
|
||||
use std::path::PathBuf;
|
||||
use tauri::{Config, Window};
|
||||
|
||||
pub fn get_pake_config() -> (PakeConfig, Config) {
|
||||
let pake_config: PakeConfig =
|
||||
serde_json::from_str(include_str!("../pake.json")).expect("Failed to parse pake config");
|
||||
|
||||
let tauri_config: Config = serde_json::from_str(include_str!("../tauri.conf.json"))
|
||||
.expect("Failed to parse tauri config");
|
||||
|
||||
(pake_config, tauri_config)
|
||||
}
|
||||
|
||||
pub fn get_data_dir(_tauri_config: Config) -> PathBuf {
|
||||
{
|
||||
let package_name = _tauri_config.package.product_name.unwrap();
|
||||
let data_dir = config_dir()
|
||||
.expect("Failed to get data dirname")
|
||||
.join(package_name);
|
||||
|
||||
if !data_dir.exists() {
|
||||
std::fs::create_dir(&data_dir)
|
||||
.unwrap_or_else(|_| panic!("Can't create dir {}", data_dir.display()));
|
||||
}
|
||||
data_dir
|
||||
}
|
||||
}
|
||||
|
||||
pub fn show_toast(window: &Window, message: &str) {
|
||||
let script = format!(r#"pakeToast("{}");"#, message);
|
||||
window.eval(&script).unwrap();
|
||||
}
|
||||
|
||||
pub fn get_download_message() -> String {
|
||||
let lang_env_var = unsafe { CStr::from_ptr(getenv(b"LANG\0".as_ptr() as *const i8)) };
|
||||
let lang_str = lang_env_var.to_string_lossy().to_lowercase();
|
||||
if lang_str.starts_with("zh") {
|
||||
"下载成功,已保存到下载目录~".to_string()
|
||||
} else {
|
||||
"Download successful, saved to download directory~".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the file exists, if it exists, add a number to file name
|
||||
pub fn check_file_or_append(file_path: &str) -> String {
|
||||
let mut new_path = PathBuf::from(file_path);
|
||||
let mut counter = 1;
|
||||
while new_path.exists() {
|
||||
let file_stem = new_path.file_stem().unwrap().to_string_lossy().to_string();
|
||||
let extension = new_path.extension().unwrap().to_string_lossy().to_string();
|
||||
let parent_dir = new_path.parent().unwrap();
|
||||
new_path = parent_dir.join(format!("{}-{}.{}", file_stem, counter, extension));
|
||||
counter += 1;
|
||||
}
|
||||
new_path.to_string_lossy().into_owned()
|
||||
}
|
||||
@@ -1,31 +1,28 @@
|
||||
{
|
||||
"package": {
|
||||
"productName": "WeRead",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"tauri": {
|
||||
"windows": [
|
||||
{
|
||||
"url": "https://weread.qq.com/",
|
||||
"transparent": true,
|
||||
"fullscreen": false,
|
||||
"width": 1200,
|
||||
"height": 780,
|
||||
"resizable": true
|
||||
}
|
||||
],
|
||||
"security": {
|
||||
"csp": null
|
||||
},
|
||||
"updater": {
|
||||
"active": false
|
||||
}
|
||||
},
|
||||
"build": {
|
||||
"devPath": "../dist",
|
||||
"distDir": "../dist",
|
||||
"beforeBuildCommand": "",
|
||||
"beforeDevCommand": ""
|
||||
}
|
||||
"package": {
|
||||
"productName": "WeRead",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"tauri": {
|
||||
"security": {
|
||||
"csp": null
|
||||
},
|
||||
"updater": {
|
||||
"active": false
|
||||
},
|
||||
"systemTray": {
|
||||
"iconPath": "png/weread_512.png",
|
||||
"iconAsTemplate": true
|
||||
},
|
||||
"allowlist": {
|
||||
"all": true
|
||||
}
|
||||
},
|
||||
"build": {
|
||||
"withGlobalTauri": true,
|
||||
"devPath": "../dist",
|
||||
"distDir": "../dist",
|
||||
"beforeBuildCommand": "",
|
||||
"beforeDevCommand": ""
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,32 +1,33 @@
|
||||
{
|
||||
"tauri": {
|
||||
"bundle": {
|
||||
"icon": ["png/weread_512.png"],
|
||||
"identifier": "com.tw93.weread",
|
||||
"active": true,
|
||||
"category": "DeveloperTool",
|
||||
"copyright": "",
|
||||
"deb": {
|
||||
"depends": [
|
||||
"libwebkit2gtk-4.0-dev",
|
||||
"build-essential",
|
||||
"curl",
|
||||
"wget",
|
||||
"libssl-dev",
|
||||
"libgtk-3-dev",
|
||||
"libayatana-appindicator3-dev",
|
||||
"librsvg2-dev",
|
||||
"gnome-video-effects",
|
||||
"gnome-video-effects-extra"
|
||||
],
|
||||
"files": {"/usr/share/applications/com-tw93-weread.desktop": "assets/com-tw93-weread.desktop"}
|
||||
},
|
||||
"externalBin": [],
|
||||
"longDescription": "",
|
||||
"resources": [],
|
||||
"shortDescription": "",
|
||||
"targets": ["deb", "appimage"]
|
||||
}
|
||||
}
|
||||
"tauri": {
|
||||
"bundle": {
|
||||
"icon": ["png/weread_512.png"],
|
||||
"identifier": "com.tw93.weread",
|
||||
"active": true,
|
||||
"category": "DeveloperTool",
|
||||
"copyright": "",
|
||||
"deb": {
|
||||
"depends": [
|
||||
"libwebkit2gtk-4.0-dev",
|
||||
"build-essential",
|
||||
"curl",
|
||||
"wget",
|
||||
"libssl-dev",
|
||||
"libgtk-3-dev",
|
||||
"libayatana-appindicator3-dev",
|
||||
"librsvg2-dev",
|
||||
"gnome-video-effects",
|
||||
"gnome-video-effects-extra"
|
||||
],
|
||||
"files": {
|
||||
"/usr/share/applications/com-tw93-weread.desktop": "assets/com-tw93-weread.desktop"
|
||||
}
|
||||
},
|
||||
"externalBin": [],
|
||||
"longDescription": "",
|
||||
"resources": [],
|
||||
"shortDescription": "",
|
||||
"targets": ["deb", "appimage"]
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user