Sure! Pl
|
|
@ -0,0 +1,26 @@
|
|||
# Dependencies
|
||||
node_modules/
|
||||
package-lock.json
|
||||
|
||||
# Rust
|
||||
src-tauri/target/
|
||||
src-tauri/Cargo.lock
|
||||
|
||||
# Tauri
|
||||
src-tauri/gen/
|
||||
|
||||
# IDE
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Build outputs
|
||||
dist/
|
||||
build/
|
||||
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
# 프로젝트 구조 가이드
|
||||
|
||||
## 현재 구조 (표준 Tauri 구조)
|
||||
|
||||
```
|
||||
ndd-drive/
|
||||
├── src/ # 프론트엔드 소스 코드
|
||||
│ └── index.html # 메인 HTML 파일
|
||||
│
|
||||
├── src-tauri/ # Rust 백엔드 (Tauri)
|
||||
│ ├── src/ # Rust 소스 파일
|
||||
│ │ ├── main.rs # 진입점
|
||||
│ │ └── lib.rs # 라이브러리 진입점
|
||||
│ ├── Cargo.toml # Rust 의존성 관리
|
||||
│ ├── tauri.conf.json # Tauri 설정
|
||||
│ ├── build.rs # 빌드 스크립트
|
||||
│ ├── icons/ # 앱 아이콘
|
||||
│ ├── capabilities/ # Tauri 권한 설정
|
||||
│ ├── gen/ # 자동 생성 파일 (gitignore)
|
||||
│ └── target/ # 빌드 결과물 (gitignore)
|
||||
│
|
||||
├── package.json # Node.js 의존성 및 스크립트
|
||||
├── node_modules/ # Node.js 패키지 (gitignore)
|
||||
└── package-lock.json # 패키지 잠금 파일
|
||||
```
|
||||
|
||||
## 개선된 구조 제안
|
||||
|
||||
### 옵션 1: 현재 구조 유지 + 모듈화 (추천)
|
||||
|
||||
```
|
||||
ndd-drive/
|
||||
├── src/ # 프론트엔드
|
||||
│ ├── index.html
|
||||
│ ├── css/
|
||||
│ │ ├── main.css
|
||||
│ │ └── components/
|
||||
│ ├── js/
|
||||
│ │ ├── main.js
|
||||
│ │ ├── components/
|
||||
│ │ └── utils/
|
||||
│ ├── assets/
|
||||
│ │ ├── images/
|
||||
│ │ └── fonts/
|
||||
│ └── components/ # 재사용 가능한 컴포넌트
|
||||
│
|
||||
├── src-tauri/ # Rust 백엔드
|
||||
│ ├── src/
|
||||
│ │ ├── main.rs
|
||||
│ │ ├── lib.rs
|
||||
│ │ ├── commands/ # Tauri 커맨드 (프론트엔드에서 호출)
|
||||
│ │ │ └── mod.rs
|
||||
│ │ ├── utils/ # 유틸리티 함수
|
||||
│ │ │ └── mod.rs
|
||||
│ │ └── models/ # 데이터 모델
|
||||
│ │ └── mod.rs
|
||||
│ ├── Cargo.toml
|
||||
│ ├── tauri.conf.json
|
||||
│ ├── build.rs
|
||||
│ ├── icons/
|
||||
│ └── capabilities/
|
||||
│
|
||||
├── package.json
|
||||
└── README.md
|
||||
```
|
||||
|
||||
### 옵션 2: 프론트엔드 프레임워크 사용 시 (React/Vue 등)
|
||||
|
||||
```
|
||||
ndd-drive/
|
||||
├── src/ # 프론트엔드 (프레임워크)
|
||||
│ ├── index.html
|
||||
│ ├── main.jsx / main.tsx
|
||||
│ ├── App.jsx / App.tsx
|
||||
│ ├── components/
|
||||
│ ├── pages/
|
||||
│ ├── hooks/ # React hooks 또는 Vue composables
|
||||
│ ├── utils/
|
||||
│ ├── styles/
|
||||
│ └── assets/
|
||||
│
|
||||
├── src-tauri/ # Rust 백엔드 (동일)
|
||||
│ └── ...
|
||||
│
|
||||
├── public/ # 정적 파일
|
||||
├── package.json
|
||||
└── vite.config.js / webpack.config.js
|
||||
```
|
||||
|
||||
## 각 디렉토리 설명
|
||||
|
||||
### `/src` - 프론트엔드
|
||||
- **index.html**: 앱의 진입점 HTML
|
||||
- **css/**: 스타일시트 파일
|
||||
- **js/**: JavaScript 파일
|
||||
- **assets/**: 이미지, 폰트 등 정적 리소스
|
||||
- **components/**: 재사용 가능한 UI 컴포넌트
|
||||
|
||||
### `/src-tauri` - Rust 백엔드
|
||||
- **src/main.rs**: Rust 프로그램 진입점
|
||||
- **src/lib.rs**: 라이브러리 진입점 (Tauri 앱 설정)
|
||||
- **src/commands/**: Tauri 커맨드 (프론트엔드에서 호출 가능한 Rust 함수)
|
||||
- **src/utils/**: 공통 유틸리티 함수
|
||||
- **src/models/**: 데이터 구조 및 모델
|
||||
- **tauri.conf.json**: Tauri 앱 설정 (윈도우 크기, 권한 등)
|
||||
- **capabilities/**: 보안 권한 설정
|
||||
|
||||
## 권장 사항
|
||||
|
||||
1. **프론트엔드 모듈화**: CSS와 JS를 분리하여 관리
|
||||
2. **Rust 코드 모듈화**: 기능별로 파일 분리 (commands, utils 등)
|
||||
3. **.gitignore**: target/, node_modules/, gen/ 등 빌드 결과물 제외
|
||||
4. **문서화**: README.md에 프로젝트 설명 및 실행 방법 작성
|
||||
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
# 웹에서 실행하기
|
||||
|
||||
## 방법 1: npm 스크립트 사용 (추천)
|
||||
|
||||
```bash
|
||||
npm run web:dev
|
||||
```
|
||||
|
||||
또는
|
||||
|
||||
```bash
|
||||
npm run web:serve
|
||||
```
|
||||
|
||||
브라우저에서 `http://localhost:8080`으로 접속
|
||||
|
||||
## 방법 2: Python 간단 서버
|
||||
|
||||
```bash
|
||||
# Python 3
|
||||
cd src
|
||||
python -m http.server 8080
|
||||
```
|
||||
|
||||
## 방법 3: Node.js http-server
|
||||
|
||||
```bash
|
||||
npx http-server src -p 8080 -o
|
||||
```
|
||||
|
||||
## 방법 4: VS Code Live Server
|
||||
|
||||
VS Code의 "Live Server" 확장 프로그램 사용
|
||||
|
||||
## 주의사항
|
||||
|
||||
- 웹에서는 Tauri API가 작동하지 않습니다
|
||||
- 트레이 기능은 데스크탑 앱에서만 동작합니다
|
||||
- 웹에서는 UI만 확인 가능합니다
|
||||
- 버튼 클릭 시 상태 텍스트는 변경되지만, 실제 트레이는 업데이트되지 않습니다
|
||||
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
# 시스템 트레이 구현 가이드
|
||||
|
||||
## 개요
|
||||
|
||||
Proton Drive처럼 시스템 트레이 아이콘을 표시하고, 로그인 상태에 따라 아이콘 색상과 툴팁을 변경하는 기능을 구현하는 방법입니다.
|
||||
|
||||
## 구조 설명
|
||||
|
||||
### 1. 파일 구조
|
||||
|
||||
```
|
||||
src-tauri/src/
|
||||
├── lib.rs # 메인 앱 설정 (트레이 초기화)
|
||||
├── tray.rs # 트레이 관련 로직 (별도 모듈)
|
||||
└── commands/
|
||||
└── tray_commands.rs # 프론트엔드에서 호출할 트레이 커맨드
|
||||
```
|
||||
|
||||
### 2. 모듈 분리 이유
|
||||
|
||||
**`tray.rs` (별도 파일로 분리)**
|
||||
- ✅ **유지보수성**: 트레이 관련 코드가 한 곳에 모여있어 수정이 쉬움
|
||||
- ✅ **확장성**: 새로운 트레이 기능 추가 시 해당 파일만 수정
|
||||
- ✅ **가독성**: 메인 코드(`lib.rs`)가 간결해짐
|
||||
- ✅ **테스트 용이**: 트레이 로직을 독립적으로 테스트 가능
|
||||
|
||||
### 3. 구현 방법
|
||||
|
||||
#### Tauri 2.x에서 시스템 트레이 사용
|
||||
|
||||
Tauri 2.x에서는 시스템 트레이 기능이 변경되었을 수 있습니다. 다음 중 하나의 방법을 사용해야 합니다:
|
||||
|
||||
**방법 1: Tauri 플러그인 사용 (권장)**
|
||||
```toml
|
||||
# Cargo.toml에 추가
|
||||
[dependencies]
|
||||
tauri-plugin-system-tray = "2.0"
|
||||
```
|
||||
|
||||
**방법 2: 직접 구현**
|
||||
- `tray-icon` 크레이트 사용
|
||||
- 또는 Tauri 2.x의 새로운 API 사용
|
||||
|
||||
### 4. 현재 구현된 구조
|
||||
|
||||
#### `src-tauri/src/tray.rs`
|
||||
- `TrayStatus` 열거형: 로그인 상태 정의
|
||||
- `create_system_tray()`: 트레이 생성
|
||||
- `handle_tray_event()`: 트레이 이벤트 처리
|
||||
- `update_tray_status()`: 상태에 따른 아이콘/툴팁 업데이트
|
||||
|
||||
#### `src-tauri/src/commands/tray_commands.rs`
|
||||
- `update_tray_icon()`: 프론트엔드에서 호출 가능한 커맨드
|
||||
|
||||
### 5. 사용 예시
|
||||
|
||||
**프론트엔드에서 호출:**
|
||||
```javascript
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
|
||||
// 로그인 상태로 변경
|
||||
await invoke('update_tray_icon', { status: 'logged_in' });
|
||||
|
||||
// 동기화 중 상태로 변경
|
||||
await invoke('update_tray_icon', { status: 'syncing' });
|
||||
```
|
||||
|
||||
### 6. 아이콘 색상 변경 방법
|
||||
|
||||
Proton Drive처럼 아이콘 색상을 변경하려면:
|
||||
|
||||
**옵션 1: 상태별 아이콘 파일 준비**
|
||||
```
|
||||
src-tauri/icons/
|
||||
├── icon_logged_out.png (회색)
|
||||
├── icon_logged_in.png (녹색)
|
||||
├── icon_syncing.png (파란색)
|
||||
└── icon_error.png (빨간색)
|
||||
```
|
||||
|
||||
**옵션 2: 런타임 아이콘 생성**
|
||||
- `image` 크레이트를 사용하여 런타임에 아이콘 색상 변경
|
||||
- Cargo.toml에 추가: `image = "0.24"`
|
||||
|
||||
### 7. 다음 단계
|
||||
|
||||
1. **Tauri 2.x API 확인**: 정확한 시스템 트레이 API 확인 필요
|
||||
2. **플러그인 설치**: `tauri-plugin-system-tray` 플러그인 사용 여부 확인
|
||||
3. **아이콘 준비**: 상태별 아이콘 파일 생성
|
||||
4. **테스트**: 각 상태에서 트레이 동작 확인
|
||||
|
||||
## 참고사항
|
||||
|
||||
- Tauri 2.x의 시스템 트레이 API는 1.x와 다를 수 있습니다
|
||||
- 실제 구현 전에 Tauri 2.x 공식 문서를 확인하세요
|
||||
- OS별로 트레이 동작이 다를 수 있습니다 (Windows, macOS, Linux)
|
||||
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"scripts": {
|
||||
"tauri": "tauri",
|
||||
"tauri:dev": "tauri dev",
|
||||
"tauri:build": "tauri build",
|
||||
"web:dev": "npx http-server src -p 8080 -o",
|
||||
"web:serve": "npx serve src -l 8080"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^2.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tauri-apps/cli": "^2.9.4"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
/target/
|
||||
/gen/schemas
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
[package]
|
||||
name = "app"
|
||||
version = "0.1.0"
|
||||
description = "A Tauri App"
|
||||
authors = ["you"]
|
||||
license = ""
|
||||
repository = ""
|
||||
edition = "2021"
|
||||
rust-version = "1.77.2"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[lib]
|
||||
name = "app_lib"
|
||||
crate-type = ["staticlib", "cdylib", "rlib"]
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "2.5.1", features = [] }
|
||||
|
||||
[dependencies]
|
||||
serde_json = "1.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
log = "0.4"
|
||||
tauri = { version = "2.9.2", features = ["tray-icon"] }
|
||||
tauri-plugin-log = "2"
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
fn main() {
|
||||
tauri_build::build()
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"$schema": "../gen/schemas/desktop-schema.json",
|
||||
"identifier": "default",
|
||||
"description": "enables the default permissions",
|
||||
"windows": [
|
||||
"main"
|
||||
],
|
||||
"permissions": [
|
||||
"core:default"
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 9.0 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 5.9 KiB |
|
After Width: | Height: | Size: 7.4 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 49 KiB |
|
|
@ -0,0 +1,22 @@
|
|||
// Tauri 커맨드 모듈
|
||||
// 프론트엔드에서 호출할 수 있는 Rust 함수들을 여기에 정의
|
||||
|
||||
mod tray_commands;
|
||||
|
||||
pub use tray_commands::update_tray_icon;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct GreetResponse {
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
/// 예시: 프론트엔드에서 호출할 수 있는 커맨드
|
||||
#[tauri::command]
|
||||
pub fn greet(name: &str) -> GreetResponse {
|
||||
GreetResponse {
|
||||
message: format!("Hello, {}! You've been greeted from Rust!", name),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
// 트레이 관련 Tauri 커맨드
|
||||
// 프론트엔드에서 트레이 상태를 업데이트할 수 있는 커맨드
|
||||
|
||||
use crate::tray::{TrayStatus, update_tray_status};
|
||||
use tauri::{AppHandle, Manager};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
/// 프론트엔드에서 호출: 트레이 상태 업데이트
|
||||
#[tauri::command]
|
||||
pub fn update_tray_icon(app: AppHandle, status: String) -> Result<(), String> {
|
||||
let tray_status = match status.as_str() {
|
||||
"logged_out" => TrayStatus::LoggedOut,
|
||||
"logged_in" => TrayStatus::LoggedIn,
|
||||
"syncing" => TrayStatus::Syncing,
|
||||
"error" => TrayStatus::Error,
|
||||
_ => return Err(format!("Unknown status: {}", status)),
|
||||
};
|
||||
|
||||
// 앱 상태에서 트레이 가져오기
|
||||
if let Some(tray_state) = app.try_state::<Arc<Mutex<Option<tauri::tray::TrayIcon>>>>() {
|
||||
if let Ok(tray_mutex) = tray_state.lock() {
|
||||
if let Some(ref tray) = *tray_mutex {
|
||||
update_tray_status(&app, tray, tray_status)
|
||||
.map_err(|e| format!("Failed to update tray: {}", e))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
// 모듈 선언
|
||||
mod commands;
|
||||
mod tray;
|
||||
|
||||
use commands::{greet, update_tray_icon};
|
||||
use tray::{create_system_tray, update_tray_status, TrayStatus};
|
||||
use tauri::Manager;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
tauri::Builder::default()
|
||||
.invoke_handler(tauri::generate_handler![greet, update_tray_icon])
|
||||
.setup(|app| {
|
||||
// 시스템 트레이 생성
|
||||
let app_handle = app.handle().clone();
|
||||
let tray = create_system_tray(&app_handle)?;
|
||||
|
||||
// 트레이를 앱 상태에 저장 (나중에 업데이트하기 위해)
|
||||
app.manage(Arc::new(Mutex::new(Some(tray))));
|
||||
|
||||
// 초기 트레이 상태 설정 (로그아웃 상태)
|
||||
if let Some(tray_state) = app.try_state::<Arc<Mutex<Option<tauri::tray::TrayIcon>>>>() {
|
||||
if let Ok(tray_mutex) = tray_state.lock() {
|
||||
if let Some(ref tray) = *tray_mutex {
|
||||
update_tray_status(&app_handle, tray, TrayStatus::LoggedOut)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// X 버튼 클릭 시 종료하지 않고 숨기기
|
||||
if let Some(window) = app.get_webview_window("main") {
|
||||
let window_clone = window.clone();
|
||||
window.on_window_event(move |event| {
|
||||
if let tauri::WindowEvent::CloseRequested { api, .. } = event {
|
||||
// 종료 대신 숨기기
|
||||
api.prevent_close();
|
||||
let _ = window_clone.hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if cfg!(debug_assertions) {
|
||||
app.handle().plugin(
|
||||
tauri_plugin_log::Builder::default()
|
||||
.level(log::LevelFilter::Info)
|
||||
.build(),
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||
|
||||
fn main() {
|
||||
app_lib::run();
|
||||
}
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
// 시스템 트레이 모듈
|
||||
// Proton Drive처럼 로그인 상태에 따라 아이콘과 툴팁을 변경하는 기능
|
||||
|
||||
use tauri::{AppHandle, Manager};
|
||||
use tauri::tray::{TrayIconBuilder, MouseButton, MouseButtonState};
|
||||
use tauri::menu::{Menu, MenuItem};
|
||||
use tauri::tray::TrayIconEvent;
|
||||
|
||||
/// 트레이 상태 열거형
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum TrayStatus {
|
||||
LoggedOut, // 로그아웃 상태
|
||||
LoggedIn, // 로그인 상태
|
||||
Syncing, // 동기화 중
|
||||
Error, // 오류 상태
|
||||
}
|
||||
|
||||
/// 시스템 트레이 생성 및 설정
|
||||
pub fn create_system_tray(app: &AppHandle) -> Result<tauri::tray::TrayIcon, Box<dyn std::error::Error>> {
|
||||
// 트레이 메뉴 생성
|
||||
let show_item = MenuItem::with_id(app, "show", "Show Window", true, None::<&str>)?;
|
||||
let quit_item = MenuItem::with_id(app, "quit", "Quit", true, None::<&str>)?;
|
||||
|
||||
let menu = Menu::new(app)?;
|
||||
menu.append(&show_item)?;
|
||||
// separator는 Tauri 2.x에서 다른 방식으로 구현해야 할 수 있음
|
||||
menu.append(&quit_item)?;
|
||||
|
||||
// 트레이 아이콘 생성 (기본 아이콘 사용)
|
||||
// 아이콘은 tauri.conf.json에서 설정된 기본 아이콘 사용
|
||||
// 런타임에 아이콘을 변경하려면 별도로 로드해야 함
|
||||
|
||||
let tray = TrayIconBuilder::new()
|
||||
.tooltip("NaDoDev Drive")
|
||||
.menu(&menu)
|
||||
.on_menu_event(|app, event| {
|
||||
match event.id.as_ref() {
|
||||
"show" => {
|
||||
if let Some(window) = app.get_webview_window("main") {
|
||||
let _ = window.show();
|
||||
let _ = window.set_focus();
|
||||
}
|
||||
}
|
||||
"quit" => {
|
||||
app.exit(0);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
})
|
||||
.on_tray_icon_event(|tray, event| {
|
||||
if let TrayIconEvent::Click {
|
||||
button: MouseButton::Left,
|
||||
button_state: MouseButtonState::Up,
|
||||
..
|
||||
} = event
|
||||
{
|
||||
if let Some(window) = tray.app_handle().get_webview_window("main") {
|
||||
if window.is_visible().unwrap_or(false) {
|
||||
let _ = window.hide();
|
||||
} else {
|
||||
let _ = window.show();
|
||||
let _ = window.set_focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.build(app)?;
|
||||
|
||||
Ok(tray)
|
||||
}
|
||||
|
||||
// 트레이 이벤트는 create_system_tray 내부에서 처리됨
|
||||
|
||||
/// 트레이 아이콘 및 툴팁 업데이트
|
||||
///
|
||||
/// 참고: Proton Drive처럼 상태별로 다른 아이콘을 사용하려면
|
||||
/// icons/ 디렉토리에 상태별 아이콘 파일을 준비해야 합니다:
|
||||
/// - icon_logged_out.png (로그아웃)
|
||||
/// - icon_logged_in.png (로그인 - 녹색)
|
||||
/// - icon_syncing.png (동기화 중 - 파란색)
|
||||
/// - icon_error.png (오류 - 빨간색)
|
||||
pub fn update_tray_status(_app: &AppHandle, tray: &tauri::tray::TrayIcon, status: TrayStatus) -> Result<(), Box<dyn std::error::Error>> {
|
||||
// 툴팁 텍스트 업데이트
|
||||
let tooltip = match status {
|
||||
TrayStatus::LoggedOut => "NaDoDev Drive - Not logged in",
|
||||
TrayStatus::LoggedIn => "NaDoDev Drive - Logged in",
|
||||
TrayStatus::Syncing => "NaDoDev Drive - Syncing...",
|
||||
TrayStatus::Error => "NaDoDev Drive - Error",
|
||||
};
|
||||
|
||||
tray.set_tooltip(Some(tooltip))?;
|
||||
|
||||
// TODO: 아이콘 색상 변경을 위해서는:
|
||||
// 1. 상태별 아이콘 파일을 준비하고 런타임에 로드하거나
|
||||
// 2. image 크레이트를 사용하여 런타임에 아이콘을 동적으로 생성
|
||||
// 현재는 툴팁만 업데이트합니다.
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
|
||||
"productName": "NaDoDev Drive",
|
||||
"version": "0.1.0",
|
||||
"identifier": "com.tauri.dev",
|
||||
"build": {
|
||||
"frontendDist": "../src",
|
||||
"beforeDevCommand": "",
|
||||
"beforeBuildCommand": "npm run build"
|
||||
},
|
||||
"app": {
|
||||
"windows": [
|
||||
{
|
||||
"title": "NaDoDev Drive",
|
||||
"width": 800,
|
||||
"height": 600,
|
||||
"resizable": true,
|
||||
"fullscreen": false
|
||||
}
|
||||
],
|
||||
"security": {
|
||||
"csp": null
|
||||
}
|
||||
},
|
||||
"bundle": {
|
||||
"active": true,
|
||||
"targets": "all",
|
||||
"icon": [
|
||||
"icons/32x32.png",
|
||||
"icons/128x128.png",
|
||||
"icons/128x128@2x.png",
|
||||
"icons/icon.icns",
|
||||
"icons/icon.ico"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.container {
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 3rem;
|
||||
margin-bottom: 1rem;
|
||||
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 1.2rem;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.logo {
|
||||
font-size: 5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.tray-controls {
|
||||
margin-top: 3rem;
|
||||
padding: 2rem;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 1rem;
|
||||
backdrop-filter: blur(10px);
|
||||
max-width: 500px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.tray-controls h2 {
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.status-text {
|
||||
font-size: 1rem;
|
||||
margin-bottom: 1.5rem;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
#current-status {
|
||||
font-weight: bold;
|
||||
color: #ffd700;
|
||||
}
|
||||
|
||||
.button-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.tray-btn {
|
||||
padding: 0.75rem 1.5rem;
|
||||
font-size: 1rem;
|
||||
border: none;
|
||||
border-radius: 0.5rem;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.tray-btn:hover {
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.tray-btn:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.hint {
|
||||
font-size: 0.9rem;
|
||||
opacity: 0.8;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>NaDoDev Drive</title>
|
||||
<link rel="stylesheet" href="css/main.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="logo">🚀</div>
|
||||
<h1>NaDoDev Drive</h1>
|
||||
<p>Tauri 데스크탑 애플리케이션</p>
|
||||
|
||||
<div class="tray-controls">
|
||||
<h2>시스템 트레이 테스트</h2>
|
||||
<p class="status-text">현재 상태: <span id="current-status">로그아웃</span></p>
|
||||
|
||||
<div class="button-group">
|
||||
<button id="btn-logged-out" class="tray-btn">로그아웃 상태</button>
|
||||
<button id="btn-logged-in" class="tray-btn">로그인 상태</button>
|
||||
<button id="btn-syncing" class="tray-btn">동기화 중</button>
|
||||
<button id="btn-error" class="tray-btn">오류 상태</button>
|
||||
</div>
|
||||
|
||||
<p class="hint">💡 시스템 트레이 아이콘을 확인하세요!</p>
|
||||
</div>
|
||||
</div>
|
||||
<script type="module" src="js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
// 메인 JavaScript 파일
|
||||
console.log('NaDoDev Drive 앱이 시작되었습니다.');
|
||||
|
||||
// Tauri API 체크 및 import
|
||||
let invoke = null;
|
||||
let isTauri = false;
|
||||
|
||||
// Tauri 환경 초기화 함수
|
||||
async function initTauri() {
|
||||
try {
|
||||
// Tauri 환경 체크
|
||||
if (window.__TAURI__) {
|
||||
isTauri = true;
|
||||
const tauriApi = await import('@tauri-apps/api/core');
|
||||
invoke = tauriApi.invoke;
|
||||
console.log('Tauri 환경에서 실행 중');
|
||||
return true;
|
||||
} else {
|
||||
console.log('웹 브라우저에서 실행 중 (Tauri API 사용 불가)');
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('웹 브라우저에서 실행 중 (Tauri API 사용 불가)', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 트레이 상태 업데이트 함수
|
||||
async function updateTrayStatus(status, displayName) {
|
||||
// UI 업데이트 (항상 실행)
|
||||
const statusElement = document.getElementById('current-status');
|
||||
if (statusElement) {
|
||||
statusElement.textContent = displayName;
|
||||
}
|
||||
|
||||
if (isTauri && invoke) {
|
||||
// Tauri 환경: 실제 트레이 업데이트
|
||||
try {
|
||||
await invoke('update_tray_icon', { status });
|
||||
console.log(`트레이 상태가 "${displayName}"로 변경되었습니다.`);
|
||||
} catch (error) {
|
||||
console.error('트레이 상태 업데이트 실패:', error);
|
||||
alert(`오류 발생: ${error}`);
|
||||
}
|
||||
} else {
|
||||
// 웹 환경: UI만 업데이트 (트레이 기능 없음)
|
||||
console.log(`웹 모드: 상태 표시만 "${displayName}"로 변경됨 (트레이 기능 없음)`);
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
console.log('DOM이 로드되었습니다.');
|
||||
|
||||
// Tauri 초기화
|
||||
await initTauri();
|
||||
|
||||
// 버튼 이벤트 리스너 설정
|
||||
const btnLoggedOut = document.getElementById('btn-logged-out');
|
||||
const btnLoggedIn = document.getElementById('btn-logged-in');
|
||||
const btnSyncing = document.getElementById('btn-syncing');
|
||||
const btnError = document.getElementById('btn-error');
|
||||
|
||||
if (btnLoggedOut) {
|
||||
btnLoggedOut.addEventListener('click', () => {
|
||||
updateTrayStatus('logged_out', '로그아웃');
|
||||
});
|
||||
}
|
||||
|
||||
if (btnLoggedIn) {
|
||||
btnLoggedIn.addEventListener('click', () => {
|
||||
updateTrayStatus('logged_in', '로그인');
|
||||
});
|
||||
}
|
||||
|
||||
if (btnSyncing) {
|
||||
btnSyncing.addEventListener('click', () => {
|
||||
updateTrayStatus('syncing', '동기화 중');
|
||||
});
|
||||
}
|
||||
|
||||
if (btnError) {
|
||||
btnError.addEventListener('click', () => {
|
||||
updateTrayStatus('error', '오류');
|
||||
});
|
||||
}
|
||||
|
||||
console.log('트레이 테스트 UI가 준비되었습니다.');
|
||||
});
|
||||
|
||||