🔀 merge dev
This commit is contained in:
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()
|
||||
}
|
||||
Reference in New Issue
Block a user