init: GitHub/Gitea contribution heatmap apps
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
26
gitea/src-tauri/Cargo.toml
Normal file
26
gitea/src-tauri/Cargo.toml
Normal file
@@ -0,0 +1,26 @@
|
||||
[package]
|
||||
name = "gitea-heatmap"
|
||||
version = "1.0.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
name = "gitea_heatmap_lib"
|
||||
crate-type = ["lib"]
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "2", features = [] }
|
||||
|
||||
[dependencies]
|
||||
tauri = { version = "2", features = ["tray-icon"] }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
reqwest = { version = "0.12", features = ["json"] }
|
||||
|
||||
[profile.dev]
|
||||
incremental = true
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
lto = true
|
||||
opt-level = "s"
|
||||
panic = "abort"
|
||||
3
gitea/src-tauri/build.rs
Normal file
3
gitea/src-tauri/build.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
tauri_build::build()
|
||||
}
|
||||
23
gitea/src-tauri/capabilities/default.json
Normal file
23
gitea/src-tauri/capabilities/default.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"identifier": "default",
|
||||
"description": "Default capabilities for Gitea HeatMap widget",
|
||||
"windows": ["main"],
|
||||
"permissions": [
|
||||
"core:default",
|
||||
"core:window:allow-close",
|
||||
"core:window:allow-hide",
|
||||
"core:window:allow-show",
|
||||
"core:window:allow-set-focus",
|
||||
"core:window:allow-unminimize",
|
||||
"core:window:allow-set-always-on-top",
|
||||
"core:window:allow-is-always-on-top",
|
||||
"core:window:allow-set-position",
|
||||
"core:window:allow-inner-position",
|
||||
"core:window:allow-outer-position",
|
||||
"core:window:allow-start-dragging",
|
||||
"core:window:allow-set-size",
|
||||
"core:window:allow-inner-size",
|
||||
"core:event:allow-listen",
|
||||
"core:event:allow-unlisten"
|
||||
]
|
||||
}
|
||||
1
gitea/src-tauri/gen/schemas/acl-manifests.json
Normal file
1
gitea/src-tauri/gen/schemas/acl-manifests.json
Normal file
File diff suppressed because one or more lines are too long
1
gitea/src-tauri/gen/schemas/capabilities.json
Normal file
1
gitea/src-tauri/gen/schemas/capabilities.json
Normal file
@@ -0,0 +1 @@
|
||||
{"default":{"identifier":"default","description":"Default capabilities for Gitea HeatMap widget","local":true,"windows":["main"],"permissions":["core:default","core:window:allow-close","core:window:allow-hide","core:window:allow-show","core:window:allow-set-focus","core:window:allow-unminimize","core:window:allow-set-always-on-top","core:window:allow-is-always-on-top","core:window:allow-set-position","core:window:allow-inner-position","core:window:allow-outer-position","core:window:allow-start-dragging","core:window:allow-set-size","core:window:allow-inner-size","core:event:allow-listen","core:event:allow-unlisten"]}}
|
||||
2292
gitea/src-tauri/gen/schemas/desktop-schema.json
Normal file
2292
gitea/src-tauri/gen/schemas/desktop-schema.json
Normal file
File diff suppressed because it is too large
Load Diff
2292
gitea/src-tauri/gen/schemas/windows-schema.json
Normal file
2292
gitea/src-tauri/gen/schemas/windows-schema.json
Normal file
File diff suppressed because it is too large
Load Diff
BIN
gitea/src-tauri/icons/128x128.png
Normal file
BIN
gitea/src-tauri/icons/128x128.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.3 KiB |
BIN
gitea/src-tauri/icons/128x128@2x.png
Normal file
BIN
gitea/src-tauri/icons/128x128@2x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.8 KiB |
BIN
gitea/src-tauri/icons/32x32.png
Normal file
BIN
gitea/src-tauri/icons/32x32.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.2 KiB |
BIN
gitea/src-tauri/icons/icon.ico
Normal file
BIN
gitea/src-tauri/icons/icon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 364 KiB |
BIN
gitea/src-tauri/icons/icon.png
Normal file
BIN
gitea/src-tauri/icons/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.7 KiB |
221
gitea/src-tauri/src/lib.rs
Normal file
221
gitea/src-tauri/src/lib.rs
Normal file
@@ -0,0 +1,221 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use tauri::{
|
||||
menu::{Menu, MenuItem, PredefinedMenuItem},
|
||||
tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent},
|
||||
Emitter, Manager,
|
||||
};
|
||||
|
||||
const TRAY_TOOLTIP: &str = "Gitea 贡献热力图";
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct Config {
|
||||
pub server: String,
|
||||
pub username: String,
|
||||
pub token: String,
|
||||
#[serde(default)]
|
||||
pub window_x: Option<i32>,
|
||||
#[serde(default)]
|
||||
pub window_y: Option<i32>,
|
||||
#[serde(default = "default_theme")]
|
||||
pub theme: String,
|
||||
}
|
||||
|
||||
fn default_theme() -> String {
|
||||
"dark".to_string()
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Config {
|
||||
server: "https://git.shumengya.top".to_string(),
|
||||
username: "shumengya".to_string(),
|
||||
token: String::new(),
|
||||
window_x: None,
|
||||
window_y: None,
|
||||
theme: default_theme(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn data_dir() -> PathBuf {
|
||||
std::env::current_exe()
|
||||
.unwrap_or_default()
|
||||
.parent()
|
||||
.unwrap_or(std::path::Path::new("."))
|
||||
.join("data")
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn load_config() -> Config {
|
||||
let path = data_dir().join("gitea_config.json");
|
||||
fs::read_to_string(&path)
|
||||
.ok()
|
||||
.and_then(|s| serde_json::from_str(&s).ok())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn save_config(config: Config) -> Result<(), String> {
|
||||
let dir = data_dir();
|
||||
fs::create_dir_all(&dir).map_err(|e| e.to_string())?;
|
||||
let content = serde_json::to_string_pretty(&config).map_err(|e| e.to_string())?;
|
||||
fs::write(dir.join("gitea_config.json"), content).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn save_window_pos(x: i32, y: i32) -> Result<(), String> {
|
||||
let path = data_dir().join("gitea_config.json");
|
||||
let mut config: Config = fs::read_to_string(&path)
|
||||
.ok()
|
||||
.and_then(|s| serde_json::from_str(&s).ok())
|
||||
.unwrap_or_default();
|
||||
config.window_x = Some(x);
|
||||
config.window_y = Some(y);
|
||||
let dir = data_dir();
|
||||
fs::create_dir_all(&dir).map_err(|e| e.to_string())?;
|
||||
let content = serde_json::to_string_pretty(&config).map_err(|e| e.to_string())?;
|
||||
fs::write(dir.join("gitea_config.json"), content).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn fetch_gitea_contributions(
|
||||
server: String,
|
||||
username: String,
|
||||
token: String,
|
||||
) -> Result<serde_json::Value, String> {
|
||||
let url = format!(
|
||||
"{}/api/v1/users/{}/heatmap",
|
||||
server.trim_end_matches('/'),
|
||||
username
|
||||
);
|
||||
|
||||
let client = reqwest::Client::builder()
|
||||
.user_agent("GitHeatMap-Widget/1.0")
|
||||
.build()
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
let mut req = client.get(&url).header("Accept", "application/json");
|
||||
|
||||
if !token.is_empty() {
|
||||
req = req.header("Authorization", format!("token {}", token));
|
||||
}
|
||||
|
||||
let resp = req.send().await.map_err(|e| e.to_string())?;
|
||||
|
||||
if !resp.status().is_success() {
|
||||
return Err(format!(
|
||||
"HTTP {}: {}",
|
||||
resp.status(),
|
||||
resp.status().canonical_reason().unwrap_or("Unknown")
|
||||
));
|
||||
}
|
||||
|
||||
resp.json::<serde_json::Value>()
|
||||
.await
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn hide_app(window: tauri::Window) {
|
||||
let _ = window.hide();
|
||||
}
|
||||
|
||||
fn show_main_window(app: &tauri::AppHandle) {
|
||||
if let Some(window) = app.get_webview_window("main") {
|
||||
let _ = window.show();
|
||||
let _ = window.unminimize();
|
||||
let _ = window.set_focus();
|
||||
}
|
||||
}
|
||||
|
||||
fn hide_main_window(app: &tauri::AppHandle) {
|
||||
if let Some(window) = app.get_webview_window("main") {
|
||||
let _ = window.hide();
|
||||
}
|
||||
}
|
||||
|
||||
fn setup_tray(app: &mut tauri::App) -> tauri::Result<()> {
|
||||
let show_i = MenuItem::with_id(app, "show", "显示窗口", true, None::<&str>)?;
|
||||
let refresh_i = MenuItem::with_id(app, "refresh", "刷新", true, None::<&str>)?;
|
||||
let pin_i = MenuItem::with_id(app, "pin", "置顶", true, None::<&str>)?;
|
||||
let theme_i = MenuItem::with_id(app, "theme", "切换主题", true, None::<&str>)?;
|
||||
let settings_i = MenuItem::with_id(app, "settings", "设置", true, None::<&str>)?;
|
||||
let hide_i = MenuItem::with_id(app, "hide", "隐藏窗口", true, None::<&str>)?;
|
||||
let sep = PredefinedMenuItem::separator(app)?;
|
||||
let quit_i = MenuItem::with_id(app, "quit", "退出", true, None::<&str>)?;
|
||||
|
||||
let menu = Menu::with_items(
|
||||
app,
|
||||
&[
|
||||
&show_i,
|
||||
&refresh_i,
|
||||
&pin_i,
|
||||
&theme_i,
|
||||
&settings_i,
|
||||
&hide_i,
|
||||
&sep,
|
||||
&quit_i,
|
||||
],
|
||||
)?;
|
||||
|
||||
let icon = app
|
||||
.default_window_icon()
|
||||
.cloned()
|
||||
.expect("default window icon missing");
|
||||
|
||||
TrayIconBuilder::with_id("main-tray")
|
||||
.icon(icon)
|
||||
.tooltip(TRAY_TOOLTIP)
|
||||
.menu(&menu)
|
||||
.show_menu_on_left_click(false)
|
||||
.on_menu_event(|app, event| match event.id().as_ref() {
|
||||
"show" => show_main_window(app),
|
||||
"hide" => hide_main_window(app),
|
||||
"quit" => app.exit(0),
|
||||
"refresh" | "pin" | "theme" | "settings" => {
|
||||
show_main_window(app);
|
||||
if let Some(window) = app.get_webview_window("main") {
|
||||
let _ = window.emit("tray-action", event.id().as_ref());
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
})
|
||||
.on_tray_icon_event(|tray, event| {
|
||||
if let TrayIconEvent::Click {
|
||||
button: MouseButton::Left,
|
||||
button_state: MouseButtonState::Up,
|
||||
..
|
||||
} = event
|
||||
{
|
||||
show_main_window(tray.app_handle());
|
||||
}
|
||||
})
|
||||
.build(app)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn run() {
|
||||
tauri::Builder::default()
|
||||
.setup(|app| {
|
||||
setup_tray(app)?;
|
||||
Ok(())
|
||||
})
|
||||
.on_window_event(|window, event| {
|
||||
if let tauri::WindowEvent::CloseRequested { api, .. } = event {
|
||||
api.prevent_close();
|
||||
let _ = window.hide();
|
||||
}
|
||||
})
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
load_config,
|
||||
save_config,
|
||||
save_window_pos,
|
||||
fetch_gitea_contributions,
|
||||
hide_app,
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
5
gitea/src-tauri/src/main.rs
Normal file
5
gitea/src-tauri/src/main.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||
|
||||
fn main() {
|
||||
gitea_heatmap_lib::run()
|
||||
}
|
||||
39
gitea/src-tauri/tauri.conf.json
Normal file
39
gitea/src-tauri/tauri.conf.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"productName": "Gitea HeatMap",
|
||||
"version": "1.0.0",
|
||||
"identifier": "top.shumengya.gitea-heatmap",
|
||||
"build": {
|
||||
"frontendDist": "../frontend"
|
||||
},
|
||||
"app": {
|
||||
"withGlobalTauri": true,
|
||||
"windows": [
|
||||
{
|
||||
"label": "main",
|
||||
"title": "Gitea 贡献热力图",
|
||||
"width": 600,
|
||||
"height": 108,
|
||||
"resizable": false,
|
||||
"decorations": false,
|
||||
"transparent": true,
|
||||
"alwaysOnTop": false,
|
||||
"center": true,
|
||||
"skipTaskbar": true
|
||||
}
|
||||
],
|
||||
"security": {
|
||||
"csp": null
|
||||
}
|
||||
},
|
||||
"bundle": {
|
||||
"active": true,
|
||||
"targets": "all",
|
||||
"icon": [
|
||||
"icons/32x32.png",
|
||||
"icons/128x128.png",
|
||||
"icons/128x128@2x.png",
|
||||
"icons/icon.png",
|
||||
"icons/icon.ico"
|
||||
]
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user