system tray
parent
5a0769e91b
commit
c91f67944d
|
|
@ -16,7 +16,7 @@ crate-type = ["staticlib", "cdylib", "rlib"]
|
||||||
tauri-build = { version = "2.0.0-rc", features = [] }
|
tauri-build = { version = "2.0.0-rc", features = [] }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tauri = { version = "2.0.0-rc", features = ["unstable"] }
|
tauri = { version = "2.0.0-rc", features = ["unstable", "tray-icon"] }
|
||||||
tauri-plugin-shell = "2.0.0-rc"
|
tauri-plugin-shell = "2.0.0-rc"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
|
|
|
||||||
|
|
@ -1,127 +1,175 @@
|
||||||
mod setup;
|
|
||||||
mod reader;
|
mod reader;
|
||||||
|
mod setup;
|
||||||
|
|
||||||
use std::{fs, path};
|
|
||||||
use serde_json::{self,json};
|
|
||||||
use std::path::Path;
|
|
||||||
use tauri::ipc::Response;
|
|
||||||
use serde::{Deserialize,Serialize};
|
|
||||||
use chrono::Local;
|
use chrono::Local;
|
||||||
use libloading::{Library,Symbol};
|
use libloading::{Library, Symbol};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_json::{self, json};
|
||||||
use std::ffi::CString;
|
use std::ffi::CString;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::{fs, path};
|
||||||
|
use tauri::ipc::Response;
|
||||||
|
|
||||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||||
pub fn run() {
|
pub fn run() {
|
||||||
tauri::Builder::default()
|
tauri::Builder::default()
|
||||||
.setup(setup::init)
|
.setup(setup::init)
|
||||||
.plugin(tauri_plugin_fs::init())
|
.plugin(tauri_plugin_fs::init())
|
||||||
.plugin(tauri_plugin_dialog::init())
|
.plugin(tauri_plugin_dialog::init())
|
||||||
.invoke_handler(tauri::generate_handler![read_resource_dir,get_file,new_wallpaper,set_wallpaper])
|
.invoke_handler(tauri::generate_handler![
|
||||||
.run(tauri::generate_context!())
|
read_resource_dir,
|
||||||
.expect("error while running tauri application");
|
get_file,
|
||||||
|
new_wallpaper,
|
||||||
|
set_wallpaper,
|
||||||
|
del_folder
|
||||||
|
])
|
||||||
|
.run(tauri::generate_context!())
|
||||||
|
.expect("error while running tauri application");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn read_resource_dir() -> String {
|
async fn read_resource_dir() -> String {
|
||||||
let mut file_map = reader::FileMap::new();
|
let mut file_map = reader::FileMap::new();
|
||||||
let path = Path::new("./resource");
|
let path = Path::new(".\\resource");
|
||||||
if let Ok(false) = fs::exists(path){
|
if let Ok(false) = fs::exists(path) {
|
||||||
fs::create_dir(path).expect("Can't create dir");
|
fs::create_dir(path).expect("Can't create dir");
|
||||||
}
|
}
|
||||||
file_map.read_resourse_directory(path).expect("Can't read dir");
|
file_map
|
||||||
serde_json::to_string(&file_map).unwrap()
|
.read_resourse_directory(path)
|
||||||
|
.expect("Can't read dir");
|
||||||
|
serde_json::to_string(&file_map).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
// remember to call `.manage(MyState::default())`
|
// remember to call `.manage(MyState::default())`
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn get_file(path:String) -> Response{
|
async fn get_file(path: String) -> Response {
|
||||||
let p = path::Path::new(&path);
|
let p = Path::new(&path);
|
||||||
if let Ok(true) = fs::exists(p){
|
if p.starts_with(".\\") {
|
||||||
let data: Vec<u8> = fs::read(p).unwrap();
|
if let Ok(true) = fs::exists(p) {
|
||||||
return tauri::ipc::Response::new(data);
|
let data: Vec<u8> = fs::read(p).unwrap();
|
||||||
}
|
return tauri::ipc::Response::new(data);
|
||||||
tauri::ipc::Response::new(String::from(""))
|
}
|
||||||
|
}
|
||||||
|
tauri::ipc::Response::new(String::from(""))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn del_folder(path: String) -> bool {
|
||||||
|
let p = Path::new(&path);
|
||||||
|
if p.starts_with(".\\") {
|
||||||
|
if p.is_dir() {
|
||||||
|
if let Ok(true) = fs::exists(p) {
|
||||||
|
if fs::remove_dir_all(p).is_ok() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct AddInfo {
|
struct AddInfo {
|
||||||
name: String,
|
name: String,
|
||||||
preview: String,
|
preview: String,
|
||||||
media: String,
|
media: String,
|
||||||
description: String
|
description: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
struct Info{
|
struct Info {
|
||||||
media_type:String,
|
media_type: String,
|
||||||
description:String,
|
description: String,
|
||||||
created:i64,
|
created: i64,
|
||||||
entry_point:String
|
entry_point: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
struct Opt{
|
struct Opt {
|
||||||
mute:bool
|
mute: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
struct Config{
|
struct Config {
|
||||||
name:String,
|
name: String,
|
||||||
info:Info,
|
info: Info,
|
||||||
option:Opt
|
option: Opt,
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
async fn new_wallpaper(info:AddInfo) -> String{
|
|
||||||
let base_url = String::from("./resource");
|
|
||||||
let current_time:i64 = Local::now().timestamp();
|
|
||||||
let folder = format!("{}/{}",base_url,current_time);
|
|
||||||
if fs::create_dir_all(Path::new(&folder)).is_err(){
|
|
||||||
return String::from("Error creating folder.");
|
|
||||||
}
|
|
||||||
if let Ok(false) = fs::exists(Path::new(&info.preview)){
|
|
||||||
return String::from("Source Image doesn't exist.");
|
|
||||||
}
|
|
||||||
if fs::copy(Path::new(&info.preview), Path::new(&format!("{}/preview.jpg",folder))).is_err(){
|
|
||||||
return String::from("Error copy image.");
|
|
||||||
}
|
|
||||||
if fs::create_dir(Path::new(&format!("{}/res",folder))).is_err(){
|
|
||||||
return String::from("Error creating res folder.");
|
|
||||||
}
|
|
||||||
let media = Path::new(&info.media);
|
|
||||||
if let Some(filename) = media.file_name().and_then(|f| f.to_str()){
|
|
||||||
if fs::copy(Path::new(&info.media), Path::new(&format!("{}/res/{}",folder,filename))).is_err(){
|
|
||||||
return String::from("Error copy media.");
|
|
||||||
}
|
|
||||||
let config =json!( Config{
|
|
||||||
name:info.name,
|
|
||||||
info:Info { media_type: String::from("video"), description: info.description, created: current_time,entry_point:String::from(filename)},
|
|
||||||
option:Opt{mute:true}
|
|
||||||
});
|
|
||||||
if fs::write(Path::new(&format!("{}/config.json",folder)), config.to_string()).is_err(){
|
|
||||||
return String::from("Error write config.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
return String::from("Invalid media path.");
|
|
||||||
}
|
|
||||||
String::from("Success")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn set_wallpaper(title:String)->bool{
|
async fn new_wallpaper(info: AddInfo) -> String {
|
||||||
let lib = unsafe {
|
let base_url = String::from("./resource");
|
||||||
Library::new("wallitor-core.dll").unwrap()
|
let current_time: i64 = Local::now().timestamp();
|
||||||
};
|
let folder = format!("{}/{}", base_url, current_time);
|
||||||
type SetFn = unsafe extern "C" fn(*const i8)->i8;
|
if fs::create_dir_all(Path::new(&folder)).is_err() {
|
||||||
let set:Symbol<SetFn> = unsafe {
|
return String::from("Error creating folder.");
|
||||||
lib.get(b"set_wallpaper\0").unwrap()
|
}
|
||||||
};
|
if !info.preview.is_empty() {
|
||||||
let title = CString::new(title.to_string()).unwrap();
|
if let Ok(false) = fs::exists(Path::new(&info.preview)) {
|
||||||
unsafe {
|
return String::from("Source Image doesn't exist.");
|
||||||
let res = set(title.as_ptr());
|
}
|
||||||
if res == 0 {return false;};
|
let preview = Path::new(&info.preview);
|
||||||
}
|
if let Some(ext) = preview.extension().and_then(|f| f.to_str()) {
|
||||||
return true;
|
if fs::copy(
|
||||||
}
|
Path::new(&info.preview),
|
||||||
|
Path::new(&format!("{}/preview.{}", folder, ext)),
|
||||||
|
)
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
|
return String::from("Error copy image.");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return String::from("Invalid Image Path.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if fs::create_dir(Path::new(&format!("{}/res", folder))).is_err() {
|
||||||
|
return String::from("Error creating res folder.");
|
||||||
|
}
|
||||||
|
let media = Path::new(&info.media);
|
||||||
|
if let Some(filename) = media.file_name().and_then(|f| f.to_str()) {
|
||||||
|
if fs::copy(
|
||||||
|
Path::new(&info.media),
|
||||||
|
Path::new(&format!("{}/res/{}", folder, filename)),
|
||||||
|
)
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
|
return String::from("Error copy media.");
|
||||||
|
}
|
||||||
|
let config = json!(Config {
|
||||||
|
name: info.name,
|
||||||
|
info: Info {
|
||||||
|
media_type: String::from("video"),
|
||||||
|
description: info.description,
|
||||||
|
created: current_time,
|
||||||
|
entry_point: String::from(filename)
|
||||||
|
},
|
||||||
|
option: Opt { mute: true }
|
||||||
|
});
|
||||||
|
if fs::write(
|
||||||
|
Path::new(&format!("{}/config.json", folder)),
|
||||||
|
config.to_string(),
|
||||||
|
)
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
|
return String::from("Error write config.");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return String::from("Invalid media path.");
|
||||||
|
}
|
||||||
|
String::from("Success")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn set_wallpaper(title: String) -> bool {
|
||||||
|
let lib = unsafe { Library::new("wallitor-core.dll").unwrap() };
|
||||||
|
type SetFn = unsafe extern "C" fn(*const i8) -> i8;
|
||||||
|
let set: Symbol<SetFn> = unsafe { lib.get(b"set_wallpaper\0").unwrap() };
|
||||||
|
let title = CString::new(title.to_string()).unwrap();
|
||||||
|
unsafe {
|
||||||
|
let res = set(title.as_ptr());
|
||||||
|
if res == 0 {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,56 @@
|
||||||
|
use tauri::{
|
||||||
|
menu::{Menu, MenuItem},
|
||||||
|
tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent},
|
||||||
|
};
|
||||||
use tauri::{App, Manager};
|
use tauri::{App, Manager};
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
use window_vibrancy;
|
use window_vibrancy;
|
||||||
|
|
||||||
|
|
||||||
/// setup
|
/// setup
|
||||||
pub fn init(app: &mut App) -> std::result::Result<(), Box<dyn std::error::Error>> {
|
pub fn init(app: &mut App) -> std::result::Result<(), Box<dyn std::error::Error>> {
|
||||||
let win = app.get_webview_window("main").unwrap();
|
let win = app.get_webview_window("main").unwrap();
|
||||||
// 仅在 windows 下执行
|
// 仅在 windows 下执行
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
if let Err(_) = window_vibrancy::apply_mica(&win, None){
|
if let Err(_) = window_vibrancy::apply_mica(&win, None) {
|
||||||
window_vibrancy::apply_acrylic(&win, Some((10,10,10,210))).expect("Unsupport Blur Effect!")
|
window_vibrancy::apply_acrylic(&win, Some((10, 10, 10, 210)))
|
||||||
|
.expect("Unsupport Blur Effect!")
|
||||||
}
|
}
|
||||||
|
let quit_i = MenuItem::with_id(app, "quit", "Quit", true, None::<&str>)?;
|
||||||
|
let menu = Menu::with_items(app, &[&quit_i])?;
|
||||||
|
TrayIconBuilder::new()
|
||||||
|
.icon(app.default_window_icon().unwrap().clone())
|
||||||
|
.menu(&menu)
|
||||||
|
.on_menu_event(|app, event| match event.id.as_ref() {
|
||||||
|
"quit" => {
|
||||||
|
app.exit(0);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
})
|
||||||
|
.on_tray_icon_event(move |tray, event| match event {
|
||||||
|
TrayIconEvent::Click {
|
||||||
|
button: MouseButton::Left,
|
||||||
|
button_state: MouseButtonState::Up,
|
||||||
|
..
|
||||||
|
}
|
||||||
|
| TrayIconEvent::DoubleClick {
|
||||||
|
button: MouseButton::Left,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
let app = tray.app_handle();
|
||||||
|
if let Some(window) = app.get_webview_window("main") {
|
||||||
|
let _ = window.show();
|
||||||
|
let _ = window.set_focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TrayIconEvent::Click {
|
||||||
|
button: MouseButton::Right,
|
||||||
|
button_state: MouseButtonState::Up,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
let _ = menu.app_handle().show_menu();
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
})
|
||||||
|
.build(app)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1728872416760" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3377" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M874.666667 241.066667h-202.666667V170.666667c0-40.533333-34.133333-74.666667-74.666667-74.666667h-170.666666c-40.533333 0-74.666667 34.133333-74.666667 74.666667v70.4H149.333333c-17.066667 0-32 14.933333-32 32s14.933333 32 32 32h53.333334V853.333333c0 40.533333 34.133333 74.666667 74.666666 74.666667h469.333334c40.533333 0 74.666667-34.133333 74.666666-74.666667V305.066667H874.666667c17.066667 0 32-14.933333 32-32s-14.933333-32-32-32zM416 170.666667c0-6.4 4.266667-10.666667 10.666667-10.666667h170.666666c6.4 0 10.666667 4.266667 10.666667 10.666667v70.4h-192V170.666667z m341.333333 682.666666c0 6.4-4.266667 10.666667-10.666666 10.666667H277.333333c-6.4 0-10.666667-4.266667-10.666666-10.666667V309.333333h490.666666V853.333333z" fill="#currentColor" p-id="3378"></path><path d="M426.666667 736c17.066667 0 32-14.933333 32-32V490.666667c0-17.066667-14.933333-32-32-32s-32 14.933333-32 32v213.333333c0 17.066667 14.933333 32 32 32zM597.333333 736c17.066667 0 32-14.933333 32-32V490.666667c0-17.066667-14.933333-32-32-32s-32 14.933333-32 32v213.333333c0 17.066667 14.933333 32 32 32z" fill="currentColor" p-id="3379"></path></svg>
|
||||||
|
After Width: | Height: | Size: 1.4 KiB |
|
|
@ -82,6 +82,13 @@ const addInfo = ref<AddInfo>({
|
||||||
})
|
})
|
||||||
const image_src = ref("");
|
const image_src = ref("");
|
||||||
const support_ext = [".mp4", ".mkv", ".flv", ".ts"];
|
const support_ext = [".mp4", ".mkv", ".flv", ".ts"];
|
||||||
|
const support_img_ext = [".jpg", ".png", ".gif", ".webp"];
|
||||||
|
const support_img_ext_map: { [key in (typeof support_img_ext)[number]]: string } = {
|
||||||
|
'.jpg': 'image/jpeg',
|
||||||
|
'.png': 'image/png',
|
||||||
|
'.gif': 'image/gif',
|
||||||
|
'.webp': 'image/webp'
|
||||||
|
}
|
||||||
|
|
||||||
defineExpose({ open })
|
defineExpose({ open })
|
||||||
|
|
||||||
|
|
@ -99,7 +106,10 @@ function selectMedia() {
|
||||||
let ext = file.substring(file.lastIndexOf("."));
|
let ext = file.substring(file.lastIndexOf("."));
|
||||||
if (support_ext.includes(ext)) {
|
if (support_ext.includes(ext)) {
|
||||||
addInfo.value.media = file;
|
addInfo.value.media = file;
|
||||||
}
|
} else ElMessage({
|
||||||
|
type: "error",
|
||||||
|
message: "文件格式不受支持"
|
||||||
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -107,14 +117,20 @@ function selectMedia() {
|
||||||
function selectPreview() {
|
function selectPreview() {
|
||||||
handleFileOpen().then((file) => {
|
handleFileOpen().then((file) => {
|
||||||
if (file) {
|
if (file) {
|
||||||
addInfo.value.preview = file;
|
let ext = file.substring(file.lastIndexOf("."));
|
||||||
invoke("get_file", {
|
if (support_img_ext.includes(ext)) {
|
||||||
path: file
|
addInfo.value.preview = file;
|
||||||
}).then((res) => {
|
invoke("get_file", {
|
||||||
let binary_data_arr = new Uint8Array(res as number[]);
|
path: file
|
||||||
const blob = new Blob([binary_data_arr], { type: 'image/jpeg' });
|
}).then((res) => {
|
||||||
const imageUrl = URL.createObjectURL(blob);
|
let binary_data_arr = new Uint8Array(res as number[]);
|
||||||
image_src.value = imageUrl;
|
const blob = new Blob([binary_data_arr], { type: support_img_ext_map[ext] });
|
||||||
|
const imageUrl = URL.createObjectURL(blob);
|
||||||
|
image_src.value = imageUrl;
|
||||||
|
})
|
||||||
|
} else ElMessage({
|
||||||
|
type: "error",
|
||||||
|
message: "文件格式不受支持"
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
@ -124,8 +140,13 @@ function toggleVisible() {
|
||||||
visible.value = !visible.value
|
visible.value = !visible.value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function checkInfo(info: AddInfo) {
|
||||||
|
if (!info.name || !info.media) return false
|
||||||
|
else return true;
|
||||||
|
}
|
||||||
|
|
||||||
function handleAdd() {
|
function handleAdd() {
|
||||||
invoke("new_wallpaper", {
|
if (checkInfo(addInfo.value)) invoke("new_wallpaper", {
|
||||||
info: addInfo.value
|
info: addInfo.value
|
||||||
}).then((res) => {
|
}).then((res) => {
|
||||||
if (res as string == "Success") {
|
if (res as string == "Success") {
|
||||||
|
|
@ -135,6 +156,7 @@ function handleAdd() {
|
||||||
media: "",
|
media: "",
|
||||||
description: ""
|
description: ""
|
||||||
}
|
}
|
||||||
|
image_src.value = "";
|
||||||
store.commit("getWpList");
|
store.commit("getWpList");
|
||||||
toggleVisible();
|
toggleVisible();
|
||||||
ElMessage({
|
ElMessage({
|
||||||
|
|
@ -147,6 +169,10 @@ function handleAdd() {
|
||||||
message: `新建失败 ${res}`
|
message: `新建失败 ${res}`
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
else ElMessage({
|
||||||
|
type: "error",
|
||||||
|
message: "请填写名称并选择媒体文件"
|
||||||
|
})
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -170,7 +170,7 @@ function apply() {
|
||||||
}
|
}
|
||||||
|
|
||||||
.apply-bar-mask {
|
.apply-bar-mask {
|
||||||
z-index: 500;
|
z-index: 200;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ export default defineComponent({
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
#mask {
|
#mask {
|
||||||
z-index: 500;
|
z-index: 200;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,85 @@
|
||||||
|
<template>
|
||||||
|
<div class="cui-rmenu-mask" @click.self="handleClose" @contextmenu.prevent="handleClose" v-if="visible">
|
||||||
|
<div ref="bg" class="cui-rmenu-bg">
|
||||||
|
<div class="cui-rmenu-content">
|
||||||
|
<slot name="content"></slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { defineProps, ref, defineEmits, defineExpose } from 'vue';
|
||||||
|
|
||||||
|
defineProps({
|
||||||
|
visible: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const emit = defineEmits(["update:visible"])
|
||||||
|
defineExpose({ handleOpen });
|
||||||
|
const bg = ref<HTMLDivElement | null>(null);
|
||||||
|
function handleClose() {
|
||||||
|
if (bg.value) bg.value.style.animation = "cui-rmenu-disappear .15s ease-in";
|
||||||
|
setTimeout(() => emit("update:visible", false), 150);
|
||||||
|
}
|
||||||
|
function handleOpen(x: number, y: number) {
|
||||||
|
setTimeout(() => {
|
||||||
|
if (bg.value) {
|
||||||
|
bg.value.style.top = `${y}px`;
|
||||||
|
bg.value.style.left = `${x}px`;
|
||||||
|
}
|
||||||
|
}, 0)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
@keyframes cui-rmenu-appear {
|
||||||
|
0% {
|
||||||
|
opacity: 0%;
|
||||||
|
transform: translate(-25%, -25%) scale(0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
opacity: 100%;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes cui-rmenu-disappear {
|
||||||
|
0% {
|
||||||
|
opacity: 100%;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
opacity: 0%;
|
||||||
|
transform: translate(-25%, -25%) scale(0.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.cui-rmenu-mask {
|
||||||
|
z-index: 2007;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cui-rmenu-bg {
|
||||||
|
position: absolute;
|
||||||
|
width: auto;
|
||||||
|
height: auto;
|
||||||
|
max-width: 80%;
|
||||||
|
border: solid var(--bd-color) 1px;
|
||||||
|
background-color: var(--bg-color-solid);
|
||||||
|
border-radius: 8px;
|
||||||
|
animation: cui-rmenu-appear .25s cubic-bezier(0, 0, 0.36, 1.29);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cui-rmenu-content {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
<template>
|
||||||
|
<div class="cui-rmenu-cell-wrapper">
|
||||||
|
<div class="cui-rmenu-cell-icon">
|
||||||
|
<slot name="icon"></slot>
|
||||||
|
</div>
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.cui-rmenu-cell-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: row;
|
||||||
|
width: auto;
|
||||||
|
height: 30px;
|
||||||
|
margin: 5px;
|
||||||
|
border-radius: 3px;
|
||||||
|
transition: .5s;
|
||||||
|
background-color: var(--bg-color-solid);
|
||||||
|
font-size: 14px;
|
||||||
|
padding: 2px 7px 2px 2px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cui-rmenu-cell-wrapper:hover {
|
||||||
|
background-color: var(--bg-color-alter);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cui-rmenu-cell-icon {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="item-card" :style="{
|
<div :style="{
|
||||||
backgroundImage: `url(${cell.img})`
|
backgroundImage: cell.img ? `url(${cell.img})` : 'linear-gradient(135deg, #00000020 0%, #FFFFFF20 100%)'
|
||||||
}">
|
}" class="item-card">
|
||||||
<!-- <div class="item-card-main">
|
<!-- <div class="item-card-main">
|
||||||
<img :src="config.img" />
|
<img :src="config.img" />
|
||||||
</div> -->
|
</div> -->
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,7 @@ function toggleMaximize() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function close() {
|
function close() {
|
||||||
appWindow.close()
|
appWindow.hide();
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
@ -124,6 +124,7 @@ function close() {
|
||||||
backdrop-filter: blur(10px) saturate(180%);
|
backdrop-filter: blur(10px) saturate(180%);
|
||||||
box-shadow: var(--shadow-edge-glow), var(--shadow);
|
box-shadow: var(--shadow-edge-glow), var(--shadow);
|
||||||
background-color: var(--bg-color-alpha);
|
background-color: var(--bg-color-alpha);
|
||||||
|
z-index: 300;
|
||||||
}
|
}
|
||||||
|
|
||||||
.titlebar-button {
|
.titlebar-button {
|
||||||
|
|
|
||||||
|
|
@ -1,46 +1,74 @@
|
||||||
import { createStore } from 'vuex'
|
import { createStore } from 'vuex'
|
||||||
import { invoke } from '@tauri-apps/api/core';
|
import { invoke } from '@tauri-apps/api/core'
|
||||||
import type {ResourceDir,wpConfig} from '@/ts/types'
|
import type { ResourceDir, wpConfig, Cell } from '@/ts/types'
|
||||||
|
|
||||||
function arrayBufferToString(buffer: ArrayBuffer): string {
|
function arrayBufferToString(buffer: ArrayBuffer): string {
|
||||||
const decoder = new TextDecoder('utf-8');
|
const decoder = new TextDecoder('utf-8')
|
||||||
return decoder.decode(buffer);
|
return decoder.decode(buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
const support_ext = ['.jpg', '.png', '.gif', '.webp']
|
||||||
|
const support_ext_map: { [key in (typeof support_ext)[number]]: string } = {
|
||||||
|
'.jpg': 'image/jpeg',
|
||||||
|
'.png': 'image/png',
|
||||||
|
'.gif': 'image/gif',
|
||||||
|
'.webp': 'image/webp'
|
||||||
}
|
}
|
||||||
|
|
||||||
export const store = createStore({
|
export const store = createStore({
|
||||||
state() {
|
state() {
|
||||||
return {
|
return {
|
||||||
wpList:[]
|
wpList: [] as Cell[]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mutations: {
|
mutations: {
|
||||||
getWpList(state){
|
getWpList(state) {
|
||||||
state.wpList = [];
|
state.wpList = []
|
||||||
invoke("read_resource_dir", {}).then((res) => {
|
invoke('read_resource_dir', {}).then((res) => {
|
||||||
const resource = JSON.parse(res as string) as ResourceDir;
|
const resource = JSON.parse(res as string) as ResourceDir
|
||||||
for (let id of Object.keys(resource.files)) {
|
for (const id of Object.keys(resource.files)) {
|
||||||
let dir = resource.files[id]
|
const dir = resource.files[id]
|
||||||
if ("preview.jpg" in dir) {
|
if ('config.json' in dir)
|
||||||
invoke("get_file", {
|
invoke('get_file', {
|
||||||
path: dir["preview.jpg"]
|
path: `${id}\\config.json`
|
||||||
}).then((res) => {
|
}).then((cfg) => {
|
||||||
let binary_data_arr = new Uint8Array(res as number[]);
|
const config: wpConfig = JSON.parse(arrayBufferToString(cfg as ArrayBuffer))
|
||||||
const blob = new Blob([binary_data_arr], { type: 'image/jpeg' });
|
let hasPreview = false
|
||||||
const imageUrl = URL.createObjectURL(blob);
|
for (const ext of support_ext) {
|
||||||
invoke("get_file", {
|
const filename = 'preview' + ext
|
||||||
path: `${id}\\config.json`
|
if (filename in dir) {
|
||||||
}).then((cfg) => {
|
hasPreview = true
|
||||||
let config: wpConfig = JSON.parse(arrayBufferToString(cfg as ArrayBuffer));
|
invoke('get_file', {
|
||||||
state.wpList.push({
|
path: dir[filename]
|
||||||
path: id,
|
}).then((res) => {
|
||||||
img: imageUrl,
|
const binary_data_arr = new Uint8Array(res as number[])
|
||||||
config: config
|
const blob = new Blob([binary_data_arr], {
|
||||||
})
|
type: support_ext_map[ext]
|
||||||
|
})
|
||||||
|
const imageUrl = URL.createObjectURL(blob)
|
||||||
|
invoke('get_file', {
|
||||||
|
path: `${id}\\config.json`
|
||||||
|
}).then((cfg) => {
|
||||||
|
const config: wpConfig = JSON.parse(arrayBufferToString(cfg as ArrayBuffer))
|
||||||
|
state.wpList.push({
|
||||||
|
path: id,
|
||||||
|
img: imageUrl,
|
||||||
|
config: config
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
if (!hasPreview) {
|
||||||
|
state.wpList.push({
|
||||||
|
path: id,
|
||||||
|
img: '',
|
||||||
|
config: config
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,21 @@
|
||||||
import type { Store } from 'vuex'
|
import type { Store } from 'vuex'
|
||||||
import type { Cell } from '@/ts/types'
|
import type { Cell } from '@/ts/types'
|
||||||
|
|
||||||
|
declare module 'vuex' {
|
||||||
|
export * from 'vuex/types/index.d.ts'
|
||||||
|
export * from 'vuex/types/helpers.d.ts'
|
||||||
|
export * from 'vuex/types/logger.d.ts'
|
||||||
|
export * from 'vuex/types/vue.d.ts'
|
||||||
|
}
|
||||||
|
|
||||||
declare module 'vue' {
|
declare module 'vue' {
|
||||||
// 声明自己的 store state
|
// 声明自己的 store state
|
||||||
interface State {
|
interface State {
|
||||||
wpList:Cell[]
|
wpList: Cell[]
|
||||||
}
|
}
|
||||||
|
|
||||||
// 为 `this.$store` 提供类型声明
|
// 为 `this.$store` 提供类型声明
|
||||||
interface ComponentCustomProperties {
|
interface ComponentCustomProperties {
|
||||||
$store: Store<State>
|
$store: Store<State>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,37 +2,82 @@
|
||||||
import ItemCard from '@/components/ItemCard.vue';
|
import ItemCard from '@/components/ItemCard.vue';
|
||||||
import ApplyBar from '@/components/ApplyBar.vue';
|
import ApplyBar from '@/components/ApplyBar.vue';
|
||||||
import AddItem from '@/components/AddItem.vue';
|
import AddItem from '@/components/AddItem.vue';
|
||||||
import { ref, onMounted, computed } from 'vue';
|
import { ref, onMounted, computed, nextTick } from 'vue';
|
||||||
import { entry } from '@/ts/entry';
|
import CRMenu from '@/components/CRMenu.vue';
|
||||||
|
import CRMenuCell from '@/components/CRMenuCell.vue';
|
||||||
|
import SvgIcon from '@/components/SvgIcon.vue';
|
||||||
import type { Cell } from '@/ts/types'
|
import type { Cell } from '@/ts/types'
|
||||||
import { useStore } from 'vuex';
|
import { useStore } from 'vuex';
|
||||||
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
|
import { ElMessage } from 'element-plus';
|
||||||
|
|
||||||
const store = useStore();
|
const store = useStore();
|
||||||
const items = computed<Cell[]>(()=>store.state.wpList);
|
const items = computed<Cell[]>(() => store.state.wpList);
|
||||||
const apply_bar_visible = ref(false);
|
const apply_bar_visible = ref(false);
|
||||||
const applyBar = ref<InstanceType<typeof ApplyBar> | null>(null);
|
const applyBar = ref<InstanceType<typeof ApplyBar> | null>(null);
|
||||||
const item_add_visible = ref(false);
|
const item_add_visible = ref(false);
|
||||||
|
const r_display = ref(false);
|
||||||
|
const r_data = ref<Cell>();
|
||||||
|
const menu = ref<InstanceType<typeof CRMenu> | null>(null);
|
||||||
|
const options = ref<{ name: string, icon: string, handler: (data: Cell) => void }[]>([{
|
||||||
|
name: "删除",
|
||||||
|
icon: "delete",
|
||||||
|
handler: del_wallpaper
|
||||||
|
}])
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
const main = document.querySelector(".home-main") as HTMLElement;
|
store.commit("getWpList");
|
||||||
setTimeout(() => {
|
|
||||||
entry("up", main, 20);
|
|
||||||
})
|
|
||||||
store.commit("getWpList");
|
|
||||||
})
|
})
|
||||||
|
|
||||||
function openCard(config: Cell) {
|
function openCard(config: Cell) {
|
||||||
console.log(applyBar.value)
|
console.log(applyBar.value)
|
||||||
if (applyBar.value) applyBar.value.open(config);
|
if (applyBar.value) applyBar.value.open(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleRightClick(event: MouseEvent, data: Cell) {
|
||||||
|
r_data.value = data;
|
||||||
|
r_display.value = true;
|
||||||
|
nextTick(() => {
|
||||||
|
if (menu.value) menu.value.handleOpen(event.x, event.y);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function del_wallpaper(data: Cell) {
|
||||||
|
invoke("del_folder", {
|
||||||
|
path: data.path
|
||||||
|
}).then((res) => {
|
||||||
|
if (res) {
|
||||||
|
store.commit("getWpList");
|
||||||
|
ElMessage({
|
||||||
|
type: "success",
|
||||||
|
message: `已删除 ${data.path}`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else ElMessage({
|
||||||
|
type: "error",
|
||||||
|
message: `删除失败`
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<main class="colbox home-main">
|
<main class="colbox home-main">
|
||||||
<ItemCard v-for="(item, index) in items" :key="index" :cell="item" @click="openCard(item)"></ItemCard>
|
<ItemCard v-for="(item, index) in items" :key="index" :cell="item" @click="openCard(item)"
|
||||||
|
@contextmenu.prevent="(e) => handleRightClick(e, item)"></ItemCard>
|
||||||
</main>
|
</main>
|
||||||
<ApplyBar v-model="apply_bar_visible" ref="applyBar"></ApplyBar>
|
<ApplyBar v-model="apply_bar_visible" ref="applyBar"></ApplyBar>
|
||||||
<AddItem v-model="item_add_visible"></AddItem>
|
<AddItem v-model="item_add_visible"></AddItem>
|
||||||
|
<CRMenu ref="menu" v-model:visible="r_display">
|
||||||
|
<template #content>
|
||||||
|
<CRMenuCell v-for="option in options" :key="option.name" @click="option.handler(r_data); r_display = false;">
|
||||||
|
<template #icon>
|
||||||
|
<SvgIcon size="20px" :name="option.icon" style="color: var(--text-color);"></SvgIcon>
|
||||||
|
</template>
|
||||||
|
{{ option.name }}
|
||||||
|
</CRMenuCell>
|
||||||
|
</template>
|
||||||
|
</CRMenu>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
|
||||||
|
|
@ -7,5 +7,10 @@
|
||||||
{
|
{
|
||||||
"path": "./tsconfig.app.json"
|
"path": "./tsconfig.app.json"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"compilerOptions": {
|
||||||
|
"paths": {
|
||||||
|
"vuex": ["./node_modules/vuex/types"]
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue