initial hunting-ground
This commit is contained in:
parent
e1326e529c
commit
8c49495865
62 changed files with 5624 additions and 3109 deletions
453
CONTRIBUTING.md
Normal file
453
CONTRIBUTING.md
Normal file
|
|
@ -0,0 +1,453 @@
|
||||||
|
# Contributing to Ziya-Slint
|
||||||
|
|
||||||
|
Thank you for your interest in contributing to Ziya-Slint! This guide outlines our development approach, coding standards, and best practices for the desktop trading application.
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
Ziya-Slint is a desktop trading application built with Rust and Slint UI framework, designed for cryptocurrency trading with real-time market data and portfolio management.
|
||||||
|
|
||||||
|
## Development Philosophy
|
||||||
|
|
||||||
|
We follow these core principles:
|
||||||
|
|
||||||
|
1. **KISS Principle** - Keep code simple and straightforward
|
||||||
|
2. **Explicit Error Handling** - Never use dummy types or unfinished code
|
||||||
|
3. **Real-world Ready** - All code should be production-ready
|
||||||
|
4. **Async-first** - Prefer tokio over std for async operations
|
||||||
|
5. **Type Safety** - Leverage Rust's type system for correctness
|
||||||
|
6. **Desktop UX** - Native desktop app behavior and performance
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- Rust 2024 Edition (see `rust-toolchain.toml`)
|
||||||
|
- Just command runner (for development tasks)
|
||||||
|
- Redis (for data caching)
|
||||||
|
- Git and Git-cliff (for development workflow)
|
||||||
|
|
||||||
|
### Initial Setup
|
||||||
|
|
||||||
|
1. Clone the repository
|
||||||
|
2. Copy configuration files:
|
||||||
|
```bash
|
||||||
|
cp Config.example.toml Config.toml
|
||||||
|
```
|
||||||
|
3. Install development dependencies:
|
||||||
|
```bash
|
||||||
|
just install-deps
|
||||||
|
```
|
||||||
|
4. Verify setup:
|
||||||
|
```bash
|
||||||
|
just test
|
||||||
|
just clippy
|
||||||
|
```
|
||||||
|
|
||||||
|
### Development Workflow
|
||||||
|
|
||||||
|
We use `just` for common development tasks:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# List all available commands
|
||||||
|
just
|
||||||
|
|
||||||
|
# Development with hot reloading
|
||||||
|
just dev
|
||||||
|
|
||||||
|
# Building
|
||||||
|
just build # Build with dev features
|
||||||
|
just build-prod # Build with prod features
|
||||||
|
|
||||||
|
# Quality assurance
|
||||||
|
just test # Run tests
|
||||||
|
just clippy # Run linter
|
||||||
|
just fmt # Format code
|
||||||
|
just check # Check code for errors
|
||||||
|
just clean # Clean build artifacts
|
||||||
|
```
|
||||||
|
|
||||||
|
## Coding Standards
|
||||||
|
|
||||||
|
### Error Handling
|
||||||
|
|
||||||
|
We use strict error handling patterns. **Never** use:
|
||||||
|
- Dummy types or results
|
||||||
|
- Placeholder implementations
|
||||||
|
- TODO comments in production code
|
||||||
|
|
||||||
|
**Always** use our error handling pattern:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use crate::err_with_loc;
|
||||||
|
use crate::error::Result;
|
||||||
|
|
||||||
|
// Import the Result type
|
||||||
|
use crate::error::Result;
|
||||||
|
|
||||||
|
// Use map_err before ? operator
|
||||||
|
let result = some_operation()
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("operation_failed: {}", e);
|
||||||
|
err_with_loc!(AppError::OperationFailed(format!("operation_failed: {}", e)))
|
||||||
|
})?;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Async Programming
|
||||||
|
|
||||||
|
- **Use tokio** instead of std for async operations
|
||||||
|
- Follow the actors pattern from [this guide](https://ryhl.io/blog/actors-with-tokio/)
|
||||||
|
- **Never** use `tokio::spawn` with UI components directly
|
||||||
|
- **Always** use `slint::invoke_from_event_loop` for UI updates from background tasks
|
||||||
|
|
||||||
|
### Code Formatting
|
||||||
|
|
||||||
|
We use `rustfmt` with specific configuration (see `rustfmt.toml`):
|
||||||
|
|
||||||
|
- Max width: 120 characters
|
||||||
|
- Use field init shorthand
|
||||||
|
- Reorder imports and impl items
|
||||||
|
- Group imports by StdExternalCrate
|
||||||
|
- Prefer same line braces
|
||||||
|
|
||||||
|
Run formatting:
|
||||||
|
```bash
|
||||||
|
just fmt
|
||||||
|
```
|
||||||
|
|
||||||
|
## Architecture: Feature-Sliced Design (FSD)
|
||||||
|
|
||||||
|
We follow **Feature-Sliced Design (FSD)** methodology for frontend architecture:
|
||||||
|
|
||||||
|
### FSD Layer Structure
|
||||||
|
```
|
||||||
|
ui/
|
||||||
|
├── app/ # Application layer - root, global setup
|
||||||
|
├── pages/ # Page layer - complete screens
|
||||||
|
├── widgets/ # Widget layer - composite UI blocks
|
||||||
|
├── entities/ # Entity layer - business entities
|
||||||
|
├── features/ # Feature layer - user interactions
|
||||||
|
└── shared/ # Shared layer - reusable resources
|
||||||
|
├── ui/ # UI components
|
||||||
|
├── types/ # Type definitions
|
||||||
|
└── design-system/ # Theme, tokens, etc.
|
||||||
|
```
|
||||||
|
|
||||||
|
### FSD Rules & Guidelines
|
||||||
|
|
||||||
|
1. **Import Rule**: Higher layers can only import from lower layers
|
||||||
|
- ✅ `app/` → `pages/` → `widgets/` → `entities/` → `features/` → `shared/`
|
||||||
|
- ❌ Never import from higher layers
|
||||||
|
|
||||||
|
2. **Layer Responsibilities**:
|
||||||
|
- **App**: Global providers, routing, application setup
|
||||||
|
- **Pages**: Complete screens, page-level logic
|
||||||
|
- **Widgets**: Composite UI blocks (navigation, forms, cards)
|
||||||
|
- **Entities**: Business entities (token, user, market data)
|
||||||
|
- **Features**: User interactions (login, trading, portfolio management)
|
||||||
|
- **Shared**: Reusable resources (UI kit, utilities, constants)
|
||||||
|
|
||||||
|
3. **Slicing by Features**: Each feature should be self-contained
|
||||||
|
```
|
||||||
|
features/
|
||||||
|
├── authentication/
|
||||||
|
│ ├── ui/ # Login components
|
||||||
|
│ ├── model/ # Auth state
|
||||||
|
│ └── api/ # Auth API calls
|
||||||
|
└── trading/
|
||||||
|
├── ui/ # Trading components
|
||||||
|
├── model/ # Trading state
|
||||||
|
└── api/ # Trading API calls
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Component Composition**: Build from bottom-up
|
||||||
|
```slint
|
||||||
|
// shared/ui/button/
|
||||||
|
export component Button { /* base button */ }
|
||||||
|
|
||||||
|
// widgets/navigation/
|
||||||
|
import { Button } from "../../shared/ui/button/";
|
||||||
|
export component NavigationWidget {
|
||||||
|
Button { /* composed navigation */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
// pages/dashboard/
|
||||||
|
import { NavigationWidget } from "../../widgets/navigation/";
|
||||||
|
export component Dashboard {
|
||||||
|
NavigationWidget { /* page composition */ }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **State Management**: Follow unidirectional data flow
|
||||||
|
- Global state in `app/` layer
|
||||||
|
- Feature state in respective `features/` slices
|
||||||
|
- Pass data down, emit events up
|
||||||
|
|
||||||
|
6. **Design System**: Centralized in `shared/design-system/`
|
||||||
|
- Theme tokens and variables
|
||||||
|
- Consistent spacing, colors, typography
|
||||||
|
- Reusable UI components
|
||||||
|
|
||||||
|
## Slint UI Development
|
||||||
|
|
||||||
|
### Threading & State Management
|
||||||
|
|
||||||
|
**Critical Pattern for Slint + Tokio:**
|
||||||
|
```rust
|
||||||
|
// Background work in Rust
|
||||||
|
tokio::spawn(async move {
|
||||||
|
let result = do_background_work().await;
|
||||||
|
|
||||||
|
// Update UI from main thread following FSD data flow
|
||||||
|
let _ = slint::invoke_from_event_loop(move || {
|
||||||
|
if let Some(ui) = ui_weak.upgrade() {
|
||||||
|
// Update flows from app → pages → widgets → shared
|
||||||
|
ui.update_global_state(result);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Component Development Guidelines
|
||||||
|
|
||||||
|
1. **Start with Shared Layer**: Build reusable components first
|
||||||
|
```slint
|
||||||
|
// shared/ui/loading/loading.slint
|
||||||
|
export component LoadingSpinner {
|
||||||
|
// Base loading component
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Compose in Widgets**: Create business-specific blocks
|
||||||
|
```slint
|
||||||
|
// widgets/token-card/index.slint
|
||||||
|
import { LoadingSpinner } from "../../shared/ui/loading/";
|
||||||
|
export component TokenCard {
|
||||||
|
LoadingSpinner { /* token-specific loading */ }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Integrate in Pages**: Assemble complete screens
|
||||||
|
```slint
|
||||||
|
// pages/dashboard/index.slint
|
||||||
|
import { TokenCard } from "../../widgets/token-card/";
|
||||||
|
export component Dashboard {
|
||||||
|
TokenCard { /* dashboard context */ }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Wire in App**: Handle global state and routing
|
||||||
|
```slint
|
||||||
|
// app/index.slint
|
||||||
|
import { Dashboard } from "../pages/dashboard/";
|
||||||
|
export component App {
|
||||||
|
if current-page == "dashboard": Dashboard { }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### File Naming Conventions
|
||||||
|
|
||||||
|
- Use `index.slint` for main component exports
|
||||||
|
- Use kebab-case for file names: `token-card.slint`
|
||||||
|
- Use PascalCase for component names: `TokenCard`
|
||||||
|
|
||||||
|
### Property & Callback Flow
|
||||||
|
|
||||||
|
- Properties flow down: `app → pages → widgets → shared`
|
||||||
|
- Callbacks flow up: `shared → widgets → pages → app`
|
||||||
|
- Keep callback interfaces simple and focused
|
||||||
|
|
||||||
|
## FSD Best Practices
|
||||||
|
|
||||||
|
### ✅ Do:
|
||||||
|
- Keep components focused on single responsibility
|
||||||
|
- Use semantic component names that reflect business domain
|
||||||
|
- Extract common patterns to `shared/` layer
|
||||||
|
- Implement loading and error states consistently
|
||||||
|
- Use TypeScript-like typing through Slint properties
|
||||||
|
- Follow consistent import paths relative to FSD structure
|
||||||
|
|
||||||
|
### ❌ Don't:
|
||||||
|
- Import from higher layers (breaks FSD hierarchy)
|
||||||
|
- Put business logic directly in UI components
|
||||||
|
- Create circular dependencies between features
|
||||||
|
- Hardcode values that should be in design system
|
||||||
|
- Mix concerns (UI logic with business logic)
|
||||||
|
- Create deep nesting beyond FSD layers
|
||||||
|
|
||||||
|
### Common Anti-patterns to Avoid:
|
||||||
|
```slint
|
||||||
|
// ❌ BAD: Widget importing from pages
|
||||||
|
// widgets/header/index.slint
|
||||||
|
import { Dashboard } from "../../pages/dashboard/"; // Wrong!
|
||||||
|
|
||||||
|
// ❌ BAD: Shared component with business logic
|
||||||
|
// shared/ui/button/index.slint
|
||||||
|
export component Button {
|
||||||
|
// Don't put trading logic here!
|
||||||
|
clicked => { execute_trade(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ GOOD: Proper callback delegation
|
||||||
|
// shared/ui/button/index.slint
|
||||||
|
export component Button {
|
||||||
|
callback clicked();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ GOOD: Business logic in appropriate layer
|
||||||
|
// features/trading/ui/trade-button.slint
|
||||||
|
import { Button } from "../../../shared/ui/button/";
|
||||||
|
export component TradeButton {
|
||||||
|
Button {
|
||||||
|
clicked => { handle_trade_click(); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
### Unit Tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
just test
|
||||||
|
```
|
||||||
|
|
||||||
|
### Integration Tests
|
||||||
|
|
||||||
|
- Test UI components with mock data
|
||||||
|
- Test service layer integration
|
||||||
|
- Use feature flags for test environments
|
||||||
|
|
||||||
|
### Feature Testing
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run with specific features
|
||||||
|
cargo test --features dev
|
||||||
|
cargo test --features prod
|
||||||
|
```
|
||||||
|
|
||||||
|
## UI/UX Principles
|
||||||
|
|
||||||
|
We follow comprehensive usability guidelines:
|
||||||
|
|
||||||
|
1. **10 Usability Heuristics** - Nielsen's principles
|
||||||
|
2. **Gestalt Principles** - Visual hierarchy and grouping
|
||||||
|
3. **Accessibility** - Support for different user needs
|
||||||
|
4. **Simplicity** - Minimize cognitive load
|
||||||
|
5. **Desktop Patterns** - Native desktop app behavior
|
||||||
|
|
||||||
|
### Key Requirements:
|
||||||
|
- Responsive design within window constraints (1080x800 - 1920x1080)
|
||||||
|
- Proper error messaging in plain language
|
||||||
|
- Loading states with progress indication
|
||||||
|
- Keyboard navigation support
|
||||||
|
- Native desktop interactions
|
||||||
|
|
||||||
|
## Submission Guidelines
|
||||||
|
|
||||||
|
### Branch Naming
|
||||||
|
|
||||||
|
Use descriptive names:
|
||||||
|
- `feat/shared/button-component` - New shared component
|
||||||
|
- `feat/pages/dashboard-redesign` - Page-level changes
|
||||||
|
- `feat/features/trading-flow` - Feature implementation
|
||||||
|
- `fix/widgets/token-card-loading` - Bug fixes
|
||||||
|
- `refactor/shared/design-system` - Code improvements
|
||||||
|
|
||||||
|
### Commit Messages
|
||||||
|
|
||||||
|
Be descriptive:
|
||||||
|
```
|
||||||
|
feat(shared): add loading spinner component
|
||||||
|
|
||||||
|
- Implement reusable loading spinner in shared/ui
|
||||||
|
- Add animation and theme support
|
||||||
|
- Export from shared layer for use in widgets
|
||||||
|
|
||||||
|
fix(pages): resolve dashboard layout overflow
|
||||||
|
|
||||||
|
- Fix token card grid overflow in dashboard
|
||||||
|
- Improve responsive behavior for small windows
|
||||||
|
- Add proper scrolling for token list
|
||||||
|
```
|
||||||
|
|
||||||
|
### Code Review Checklist
|
||||||
|
|
||||||
|
**General:**
|
||||||
|
- [ ] Follows error handling patterns
|
||||||
|
- [ ] Uses tokio for async operations
|
||||||
|
- [ ] Includes proper logging
|
||||||
|
- [ ] Formatted with rustfmt
|
||||||
|
- [ ] Passes all tests
|
||||||
|
- [ ] Updates documentation if needed
|
||||||
|
|
||||||
|
**FSD-Specific:**
|
||||||
|
- [ ] Components placed in correct FSD layer
|
||||||
|
- [ ] Import dependencies only from lower layers
|
||||||
|
- [ ] Properties flow down, callbacks flow up
|
||||||
|
- [ ] No direct business logic in UI components
|
||||||
|
- [ ] Reusable components in `shared/` layer
|
||||||
|
- [ ] Feature-specific logic in `features/` layer
|
||||||
|
|
||||||
|
**UI/UX:**
|
||||||
|
- [ ] Follows design system patterns
|
||||||
|
- [ ] Implements proper loading states
|
||||||
|
- [ ] Handles error states gracefully
|
||||||
|
- [ ] Supports keyboard navigation
|
||||||
|
- [ ] Responsive within window constraints
|
||||||
|
|
||||||
|
## Development Environment
|
||||||
|
|
||||||
|
### Recommended Tools
|
||||||
|
|
||||||
|
- **IDE**: VS Code with rust-analyzer and Slint extension
|
||||||
|
- **Debugging**: Use tracing for structured logging
|
||||||
|
- **File Watching**: `watchexec` for auto-reload during development
|
||||||
|
- **Hot Reloading**: `just dev` for development workflow
|
||||||
|
|
||||||
|
### Common Issues
|
||||||
|
|
||||||
|
#### Slint Compilation Errors
|
||||||
|
|
||||||
|
If you encounter field offset errors with watchexec:
|
||||||
|
```bash
|
||||||
|
cargo clean
|
||||||
|
# Then rebuild
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Threading Issues
|
||||||
|
|
||||||
|
Remember: Slint UI components are not `Send`. Always use `slint::invoke_from_event_loop` for UI updates from background tasks.
|
||||||
|
|
||||||
|
#### Hot Reloading
|
||||||
|
|
||||||
|
For the best development experience:
|
||||||
|
```bash
|
||||||
|
just dev
|
||||||
|
```
|
||||||
|
|
||||||
|
This watches both `src/` and `ui/` directories and automatically rebuilds.
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
- **Component APIs**: Document with clear examples
|
||||||
|
- **Architecture**: Update this guide when patterns change
|
||||||
|
- **Configuration**: Document all config options
|
||||||
|
- **UI Components**: Maintain design system documentation
|
||||||
|
|
||||||
|
## Getting Help
|
||||||
|
|
||||||
|
1. Check existing documentation
|
||||||
|
2. Search closed issues on GitHub
|
||||||
|
3. Ask in project discussions
|
||||||
|
4. Check Slint documentation for UI issues
|
||||||
|
|
||||||
|
## Code of Conduct
|
||||||
|
|
||||||
|
- Be respectful and inclusive
|
||||||
|
- Focus on constructive feedback
|
||||||
|
- Help others learn and grow
|
||||||
|
- Maintain professional communication
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
By following these guidelines, you help maintain code quality and ensure smooth collaboration on the Ziya-Slint desktop application. Thank you for contributing! 🚀
|
||||||
596
Cargo.lock
generated
596
Cargo.lock
generated
|
|
@ -434,6 +434,19 @@ dependencies = [
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-compat"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7bab94bde396a3f7b4962e396fdad640e241ed797d4d8d77fc8c237d14c58fc0"
|
||||||
|
dependencies = [
|
||||||
|
"futures-core",
|
||||||
|
"futures-io",
|
||||||
|
"once_cell",
|
||||||
|
"pin-project-lite",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-executor"
|
name = "async-executor"
|
||||||
version = "1.13.2"
|
version = "1.13.2"
|
||||||
|
|
@ -678,6 +691,27 @@ version = "0.22.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bb8"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "212d8b8e1a22743d9241575c6ba822cf9c8fef34771c86ab7e477a4fbfd254e5"
|
||||||
|
dependencies = [
|
||||||
|
"futures-util",
|
||||||
|
"parking_lot",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bb8-redis"
|
||||||
|
version = "0.24.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5143936af5e1eea1a881e3e3d21b6777da6315e5e307bc3d0c2301c44fa37da9"
|
||||||
|
dependencies = [
|
||||||
|
"bb8",
|
||||||
|
"redis",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bincode"
|
name = "bincode"
|
||||||
version = "1.3.3"
|
version = "1.3.3"
|
||||||
|
|
@ -1154,7 +1188,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd"
|
checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
|
"futures-core",
|
||||||
"memchr",
|
"memchr",
|
||||||
|
"pin-project-lite",
|
||||||
|
"tokio",
|
||||||
|
"tokio-util",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1556,6 +1594,15 @@ version = "0.3.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5c297a1c74b71ae29df00c3e22dd9534821d60eb9af5a0192823fa2acea70c2a"
|
checksum = "5c297a1c74b71ae29df00c3e22dd9534821d60eb9af5a0192823fa2acea70c2a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "deranged"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e"
|
||||||
|
dependencies = [
|
||||||
|
"powerfmt",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "derivation-path"
|
name = "derivation-path"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
|
|
@ -1768,15 +1815,6 @@ version = "1.15.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "encoding_rs"
|
|
||||||
version = "0.8.35"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "endi"
|
name = "endi"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
|
|
@ -2023,6 +2061,12 @@ version = "0.1.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2551bf44bc5f776c15044b9b94153a00198be06743e262afaaa61f11ac7523a5"
|
checksum = "2551bf44bc5f776c15044b9b94153a00198be06743e262afaaa61f11ac7523a5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fixedbitset"
|
||||||
|
version = "0.5.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "flate2"
|
name = "flate2"
|
||||||
version = "1.1.2"
|
version = "1.1.2"
|
||||||
|
|
@ -2429,25 +2473,6 @@ dependencies = [
|
||||||
"gl_generator",
|
"gl_generator",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "h2"
|
|
||||||
version = "0.4.10"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a9421a676d1b147b16b82c9225157dc629087ef8ec4d5e2960f9437a90dac0a5"
|
|
||||||
dependencies = [
|
|
||||||
"atomic-waker",
|
|
||||||
"bytes",
|
|
||||||
"fnv",
|
|
||||||
"futures-core",
|
|
||||||
"futures-sink",
|
|
||||||
"http",
|
|
||||||
"indexmap",
|
|
||||||
"slab",
|
|
||||||
"tokio",
|
|
||||||
"tokio-util",
|
|
||||||
"tracing",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "half"
|
name = "half"
|
||||||
version = "2.6.0"
|
version = "2.6.0"
|
||||||
|
|
@ -2548,109 +2573,12 @@ dependencies = [
|
||||||
"hmac 0.8.1",
|
"hmac 0.8.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "http"
|
|
||||||
version = "1.3.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565"
|
|
||||||
dependencies = [
|
|
||||||
"bytes",
|
|
||||||
"fnv",
|
|
||||||
"itoa",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "http-body"
|
|
||||||
version = "1.0.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
|
|
||||||
dependencies = [
|
|
||||||
"bytes",
|
|
||||||
"http",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "http-body-util"
|
|
||||||
version = "0.1.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a"
|
|
||||||
dependencies = [
|
|
||||||
"bytes",
|
|
||||||
"futures-core",
|
|
||||||
"http",
|
|
||||||
"http-body",
|
|
||||||
"pin-project-lite",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "httparse"
|
|
||||||
version = "1.10.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "humantime"
|
name = "humantime"
|
||||||
version = "2.2.0"
|
version = "2.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9b112acc8b3adf4b107a8ec20977da0273a8c386765a3ec0229bd500a1443f9f"
|
checksum = "9b112acc8b3adf4b107a8ec20977da0273a8c386765a3ec0229bd500a1443f9f"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "hyper"
|
|
||||||
version = "1.6.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80"
|
|
||||||
dependencies = [
|
|
||||||
"bytes",
|
|
||||||
"futures-channel",
|
|
||||||
"futures-util",
|
|
||||||
"h2",
|
|
||||||
"http",
|
|
||||||
"http-body",
|
|
||||||
"httparse",
|
|
||||||
"itoa",
|
|
||||||
"pin-project-lite",
|
|
||||||
"smallvec",
|
|
||||||
"tokio",
|
|
||||||
"want",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "hyper-tls"
|
|
||||||
version = "0.6.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
|
|
||||||
dependencies = [
|
|
||||||
"bytes",
|
|
||||||
"http-body-util",
|
|
||||||
"hyper",
|
|
||||||
"hyper-util",
|
|
||||||
"native-tls",
|
|
||||||
"tokio",
|
|
||||||
"tokio-native-tls",
|
|
||||||
"tower-service",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "hyper-util"
|
|
||||||
version = "0.1.14"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "dc2fdfdbff08affe55bb779f33b053aa1fe5dd5b54c257343c17edfa55711bdb"
|
|
||||||
dependencies = [
|
|
||||||
"bytes",
|
|
||||||
"futures-channel",
|
|
||||||
"futures-core",
|
|
||||||
"futures-util",
|
|
||||||
"http",
|
|
||||||
"http-body",
|
|
||||||
"hyper",
|
|
||||||
"libc",
|
|
||||||
"pin-project-lite",
|
|
||||||
"socket2",
|
|
||||||
"tokio",
|
|
||||||
"tower-service",
|
|
||||||
"tracing",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "i-slint-backend-linuxkms"
|
name = "i-slint-backend-linuxkms"
|
||||||
version = "1.12.1"
|
version = "1.12.1"
|
||||||
|
|
@ -3152,12 +3080,6 @@ dependencies = [
|
||||||
"windows-sys 0.48.0",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ipnet"
|
|
||||||
version = "2.11.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "is_terminal_polyfill"
|
name = "is_terminal_polyfill"
|
||||||
version = "1.70.1"
|
version = "1.70.1"
|
||||||
|
|
@ -3547,6 +3469,15 @@ dependencies = [
|
||||||
"num-traits",
|
"num-traits",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "matchers"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
|
||||||
|
dependencies = [
|
||||||
|
"regex-automata 0.1.10",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "maybe-rayon"
|
name = "maybe-rayon"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
|
|
@ -3590,12 +3521,6 @@ dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "mime"
|
|
||||||
version = "0.3.17"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "minimal-lexical"
|
name = "minimal-lexical"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
|
|
@ -3642,23 +3567,6 @@ dependencies = [
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "native-tls"
|
|
||||||
version = "0.2.14"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e"
|
|
||||||
dependencies = [
|
|
||||||
"libc",
|
|
||||||
"log",
|
|
||||||
"openssl",
|
|
||||||
"openssl-probe",
|
|
||||||
"openssl-sys",
|
|
||||||
"schannel",
|
|
||||||
"security-framework",
|
|
||||||
"security-framework-sys",
|
|
||||||
"tempfile",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ndk"
|
name = "ndk"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
|
|
@ -3724,6 +3632,16 @@ version = "0.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8"
|
checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nu-ansi-term"
|
||||||
|
version = "0.46.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
|
||||||
|
dependencies = [
|
||||||
|
"overload",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-bigint"
|
name = "num-bigint"
|
||||||
version = "0.4.6"
|
version = "0.4.6"
|
||||||
|
|
@ -3734,6 +3652,12 @@ dependencies = [
|
||||||
"num-traits",
|
"num-traits",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-conv"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-derive"
|
name = "num-derive"
|
||||||
version = "0.4.2"
|
version = "0.4.2"
|
||||||
|
|
@ -4187,12 +4111,6 @@ dependencies = [
|
||||||
"syn 2.0.104",
|
"syn 2.0.104",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "openssl-probe"
|
|
||||||
version = "0.1.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openssl-sys"
|
name = "openssl-sys"
|
||||||
version = "0.9.109"
|
version = "0.9.109"
|
||||||
|
|
@ -4224,6 +4142,12 @@ dependencies = [
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "overload"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "owned_ttf_parser"
|
name = "owned_ttf_parser"
|
||||||
version = "0.25.0"
|
version = "0.25.0"
|
||||||
|
|
@ -4292,6 +4216,19 @@ version = "2.3.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
|
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "petgraph"
|
||||||
|
version = "0.8.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "54acf3a685220b533e437e264e4d932cfbdc4cc7ec0cd232ed73c08d03b8a7ca"
|
||||||
|
dependencies = [
|
||||||
|
"fixedbitset",
|
||||||
|
"hashbrown 0.15.4",
|
||||||
|
"indexmap",
|
||||||
|
"serde",
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "phf"
|
name = "phf"
|
||||||
version = "0.11.3"
|
version = "0.11.3"
|
||||||
|
|
@ -4455,6 +4392,12 @@ dependencies = [
|
||||||
"zerovec",
|
"zerovec",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "powerfmt"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ppv-lite86"
|
name = "ppv-lite86"
|
||||||
version = "0.2.21"
|
version = "0.2.21"
|
||||||
|
|
@ -4748,6 +4691,28 @@ dependencies = [
|
||||||
"crossbeam-utils",
|
"crossbeam-utils",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "redis"
|
||||||
|
version = "0.32.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f2f6fd3fd5bb3a9a48819ae5cbad501968f208c1abd139649e7e2cf5d7f4b40b"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"cfg-if",
|
||||||
|
"combine",
|
||||||
|
"futures-util",
|
||||||
|
"itoa",
|
||||||
|
"num-bigint",
|
||||||
|
"percent-encoding",
|
||||||
|
"pin-project-lite",
|
||||||
|
"ryu",
|
||||||
|
"sha1_smol",
|
||||||
|
"socket2",
|
||||||
|
"tokio",
|
||||||
|
"tokio-util",
|
||||||
|
"url",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redox_syscall"
|
name = "redox_syscall"
|
||||||
version = "0.4.1"
|
version = "0.4.1"
|
||||||
|
|
@ -4774,8 +4739,17 @@ checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"memchr",
|
"memchr",
|
||||||
"regex-automata",
|
"regex-automata 0.4.9",
|
||||||
"regex-syntax",
|
"regex-syntax 0.8.5",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-automata"
|
||||||
|
version = "0.1.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
|
||||||
|
dependencies = [
|
||||||
|
"regex-syntax 0.6.29",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -4786,57 +4760,21 @@ checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"memchr",
|
"memchr",
|
||||||
"regex-syntax",
|
"regex-syntax 0.8.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-syntax"
|
||||||
|
version = "0.6.29"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-syntax"
|
name = "regex-syntax"
|
||||||
version = "0.8.5"
|
version = "0.8.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "reqwest"
|
|
||||||
version = "0.12.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "566cafdd92868e0939d3fb961bd0dc25fcfaaed179291093b3d43e6b3150ea10"
|
|
||||||
dependencies = [
|
|
||||||
"base64 0.22.1",
|
|
||||||
"bytes",
|
|
||||||
"encoding_rs",
|
|
||||||
"futures-core",
|
|
||||||
"futures-util",
|
|
||||||
"h2",
|
|
||||||
"http",
|
|
||||||
"http-body",
|
|
||||||
"http-body-util",
|
|
||||||
"hyper",
|
|
||||||
"hyper-tls",
|
|
||||||
"hyper-util",
|
|
||||||
"ipnet",
|
|
||||||
"js-sys",
|
|
||||||
"log",
|
|
||||||
"mime",
|
|
||||||
"native-tls",
|
|
||||||
"once_cell",
|
|
||||||
"percent-encoding",
|
|
||||||
"pin-project-lite",
|
|
||||||
"rustls-pemfile",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"serde_urlencoded",
|
|
||||||
"sync_wrapper",
|
|
||||||
"system-configuration",
|
|
||||||
"tokio",
|
|
||||||
"tokio-native-tls",
|
|
||||||
"tower-service",
|
|
||||||
"url",
|
|
||||||
"wasm-bindgen",
|
|
||||||
"wasm-bindgen-futures",
|
|
||||||
"web-sys",
|
|
||||||
"winreg",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "resvg"
|
name = "resvg"
|
||||||
version = "0.45.1"
|
version = "0.45.1"
|
||||||
|
|
@ -4934,24 +4872,6 @@ dependencies = [
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rustls-pemfile"
|
|
||||||
version = "2.2.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50"
|
|
||||||
dependencies = [
|
|
||||||
"rustls-pki-types",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rustls-pki-types"
|
|
||||||
version = "1.12.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79"
|
|
||||||
dependencies = [
|
|
||||||
"zeroize",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustversion"
|
name = "rustversion"
|
||||||
version = "1.0.21"
|
version = "1.0.21"
|
||||||
|
|
@ -4991,15 +4911,6 @@ dependencies = [
|
||||||
"winapi-util",
|
"winapi-util",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "schannel"
|
|
||||||
version = "0.1.27"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d"
|
|
||||||
dependencies = [
|
|
||||||
"windows-sys 0.59.0",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "scoped-tls"
|
name = "scoped-tls"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
|
|
@ -5031,29 +4942,6 @@ dependencies = [
|
||||||
"tiny-skia",
|
"tiny-skia",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "security-framework"
|
|
||||||
version = "2.11.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags 2.9.1",
|
|
||||||
"core-foundation 0.9.4",
|
|
||||||
"core-foundation-sys",
|
|
||||||
"libc",
|
|
||||||
"security-framework-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "security-framework-sys"
|
|
||||||
version = "2.14.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32"
|
|
||||||
dependencies = [
|
|
||||||
"core-foundation-sys",
|
|
||||||
"libc",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "semver"
|
name = "semver"
|
||||||
version = "1.0.26"
|
version = "1.0.26"
|
||||||
|
|
@ -5130,18 +5018,6 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde_urlencoded"
|
|
||||||
version = "0.7.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
|
|
||||||
dependencies = [
|
|
||||||
"form_urlencoded",
|
|
||||||
"itoa",
|
|
||||||
"ryu",
|
|
||||||
"serde",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_with"
|
name = "serde_with"
|
||||||
version = "3.13.0"
|
version = "3.13.0"
|
||||||
|
|
@ -5165,6 +5041,12 @@ dependencies = [
|
||||||
"syn 2.0.104",
|
"syn 2.0.104",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sha1_smol"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sha2"
|
name = "sha2"
|
||||||
version = "0.9.9"
|
version = "0.9.9"
|
||||||
|
|
@ -5199,6 +5081,15 @@ dependencies = [
|
||||||
"keccak",
|
"keccak",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sharded-slab"
|
||||||
|
version = "0.1.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
|
||||||
|
dependencies = [
|
||||||
|
"lazy_static",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "shlex"
|
name = "shlex"
|
||||||
version = "1.3.0"
|
version = "1.3.0"
|
||||||
|
|
@ -6969,12 +6860,6 @@ dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "sync_wrapper"
|
|
||||||
version = "0.1.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "synstructure"
|
name = "synstructure"
|
||||||
version = "0.13.2"
|
version = "0.13.2"
|
||||||
|
|
@ -6995,27 +6880,6 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "system-configuration"
|
|
||||||
version = "0.5.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags 1.3.2",
|
|
||||||
"core-foundation 0.9.4",
|
|
||||||
"system-configuration-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "system-configuration-sys"
|
|
||||||
version = "0.5.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9"
|
|
||||||
dependencies = [
|
|
||||||
"core-foundation-sys",
|
|
||||||
"libc",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "system-deps"
|
name = "system-deps"
|
||||||
version = "6.2.2"
|
version = "6.2.2"
|
||||||
|
|
@ -7114,6 +6978,15 @@ dependencies = [
|
||||||
"syn 2.0.104",
|
"syn 2.0.104",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thread_local"
|
||||||
|
version = "1.1.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tiff"
|
name = "tiff"
|
||||||
version = "0.9.1"
|
version = "0.9.1"
|
||||||
|
|
@ -7125,6 +6998,37 @@ dependencies = [
|
||||||
"weezl",
|
"weezl",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "time"
|
||||||
|
version = "0.3.41"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40"
|
||||||
|
dependencies = [
|
||||||
|
"deranged",
|
||||||
|
"itoa",
|
||||||
|
"num-conv",
|
||||||
|
"powerfmt",
|
||||||
|
"serde",
|
||||||
|
"time-core",
|
||||||
|
"time-macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "time-core"
|
||||||
|
version = "0.1.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "time-macros"
|
||||||
|
version = "0.2.22"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49"
|
||||||
|
dependencies = [
|
||||||
|
"num-conv",
|
||||||
|
"time-core",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tiny-skia"
|
name = "tiny-skia"
|
||||||
version = "0.11.4"
|
version = "0.11.4"
|
||||||
|
|
@ -7219,12 +7123,13 @@ dependencies = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-native-tls"
|
name = "tokio-stream"
|
||||||
version = "0.3.1"
|
version = "0.1.17"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
|
checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"native-tls",
|
"futures-core",
|
||||||
|
"pin-project-lite",
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -7291,12 +7196,6 @@ version = "0.1.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
|
checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tower-service"
|
|
||||||
version = "0.3.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tracing"
|
name = "tracing"
|
||||||
version = "0.1.41"
|
version = "0.1.41"
|
||||||
|
|
@ -7309,6 +7208,18 @@ dependencies = [
|
||||||
"tracing-core",
|
"tracing-core",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tracing-appender"
|
||||||
|
version = "0.2.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3566e8ce28cc0a3fe42519fc80e6b4c943cc4c8cef275620eb8dac2d3d4e06cf"
|
||||||
|
dependencies = [
|
||||||
|
"crossbeam-channel",
|
||||||
|
"thiserror 1.0.69",
|
||||||
|
"time",
|
||||||
|
"tracing-subscriber",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tracing-attributes"
|
name = "tracing-attributes"
|
||||||
version = "0.1.30"
|
version = "0.1.30"
|
||||||
|
|
@ -7327,13 +7238,37 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678"
|
checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
|
"valuable",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "try-lock"
|
name = "tracing-log"
|
||||||
version = "0.2.5"
|
version = "0.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
"once_cell",
|
||||||
|
"tracing-core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tracing-subscriber"
|
||||||
|
version = "0.3.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008"
|
||||||
|
dependencies = [
|
||||||
|
"matchers",
|
||||||
|
"nu-ansi-term",
|
||||||
|
"once_cell",
|
||||||
|
"regex",
|
||||||
|
"sharded-slab",
|
||||||
|
"smallvec",
|
||||||
|
"thread_local",
|
||||||
|
"tracing",
|
||||||
|
"tracing-core",
|
||||||
|
"tracing-log",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ttf-parser"
|
name = "ttf-parser"
|
||||||
|
|
@ -7526,6 +7461,12 @@ dependencies = [
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "valuable"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "vcpkg"
|
name = "vcpkg"
|
||||||
version = "0.2.15"
|
version = "0.2.15"
|
||||||
|
|
@ -7577,15 +7518,6 @@ dependencies = [
|
||||||
"winapi-util",
|
"winapi-util",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "want"
|
|
||||||
version = "0.3.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
|
|
||||||
dependencies = [
|
|
||||||
"try-lock",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasi"
|
name = "wasi"
|
||||||
version = "0.9.0+wasi-snapshot-preview1"
|
version = "0.9.0+wasi-snapshot-preview1"
|
||||||
|
|
@ -8303,16 +8235,6 @@ dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "winreg"
|
|
||||||
version = "0.52.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
"windows-sys 0.48.0",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wio"
|
name = "wio"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
|
|
@ -8653,23 +8575,37 @@ dependencies = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ziya-slint"
|
name = "ziya"
|
||||||
version = "0.2.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"async-compat",
|
||||||
|
"bb8",
|
||||||
|
"bb8-redis",
|
||||||
|
"bs58",
|
||||||
"chrono",
|
"chrono",
|
||||||
"chrono-tz",
|
"chrono-tz",
|
||||||
"env_logger 0.11.8",
|
"env_logger 0.11.8",
|
||||||
|
"futures-util",
|
||||||
"i-slint-backend-winit",
|
"i-slint-backend-winit",
|
||||||
"image",
|
|
||||||
"log",
|
"log",
|
||||||
"reqwest",
|
"petgraph",
|
||||||
|
"redis",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"slint",
|
"slint",
|
||||||
"slint-build",
|
"slint-build",
|
||||||
|
"solana-pubkey",
|
||||||
"solana-sdk",
|
"solana-sdk",
|
||||||
|
"thiserror 2.0.12",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"tokio-stream",
|
||||||
|
"tokio-util",
|
||||||
|
"toml 0.8.23",
|
||||||
|
"tracing",
|
||||||
|
"tracing-appender",
|
||||||
|
"tracing-subscriber",
|
||||||
|
"winit",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
||||||
173
Cargo.toml
173
Cargo.toml
|
|
@ -1,25 +1,182 @@
|
||||||
[package]
|
[package]
|
||||||
name = "ziya-slint"
|
name = "ziya"
|
||||||
version = "0.2.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "One stop shop for your trading habit - Slint version"
|
description = "One stop shop for your trading habit - Slint version"
|
||||||
authors = ["rizary"]
|
authors = ["rizary"]
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "ziya"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
slint = "1.12.0"
|
slint = "1.8.0"
|
||||||
i-slint-backend-winit = "1.12.0"
|
i-slint-backend-winit = "1.12.0"
|
||||||
|
winit = "0.30"
|
||||||
tokio = { version = "1.0", features = ["full"] }
|
tokio = { version = "1.0", features = ["full"] }
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
tokio-util = "0.7.13"
|
||||||
serde_json = "1.0"
|
async-compat = "0.2.4"
|
||||||
reqwest = { version = "0.12", features = ["json"] }
|
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
env_logger = "0.11"
|
env_logger = "0.11"
|
||||||
solana-sdk = "2.3.0"
|
tracing = "0.1"
|
||||||
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
|
tracing-appender = "0.2"
|
||||||
chrono = { version = "0.4.39", features = ["serde"] }
|
chrono = { version = "0.4.39", features = ["serde"] }
|
||||||
chrono-tz = "0.10.3"
|
chrono-tz = "0.10.3"
|
||||||
image = { version = "0.25.6", default-features = false, features = [ "ico", "jpeg", "gif", "webp", "tiff" ] }
|
redis = { version = "0.32.2", features = ["aio", "tokio-comp"] }
|
||||||
|
bb8 = "0.9"
|
||||||
|
bb8-redis = "0.24.0"
|
||||||
|
solana-sdk = "2.3.0"
|
||||||
|
solana-pubkey = "2.3.0"
|
||||||
|
thiserror = "2.0.12"
|
||||||
|
futures-util = "0.3"
|
||||||
|
tokio-stream = "0.1"
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
serde_json = "1.0"
|
||||||
|
toml = "0.8"
|
||||||
|
bs58 = "0.5"
|
||||||
|
petgraph = { version = "0.8.1", features = ["serde-1"] }
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
slint-build = "1.12.0"
|
slint-build = "1.12.0"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["prod"]
|
||||||
|
dev = []
|
||||||
|
prod = []
|
||||||
|
deep-trace = []
|
||||||
|
|
||||||
|
# Config for 'git cliff'
|
||||||
|
# Run with `GITHUB_TOKEN=$(gh auth token) git cliff --bump -up CHANGELOG.md`
|
||||||
|
# https://git-cliff.org/docs/configuration
|
||||||
|
[workspace.metadata.git-cliff.bump]
|
||||||
|
features_always_bump_minor = false
|
||||||
|
breaking_always_bump_major = false
|
||||||
|
|
||||||
|
[workspace.metadata.git-cliff.remote.github]
|
||||||
|
owner = "rizilab"
|
||||||
|
repo = "ziya"
|
||||||
|
|
||||||
|
[workspace.metadata.git-cliff.changelog]
|
||||||
|
# changelog header
|
||||||
|
header = """
|
||||||
|
# Changelog\n
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||||
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n
|
||||||
|
"""
|
||||||
|
# template for the changelog body
|
||||||
|
# https://keats.github.io/tera/docs/#introduction
|
||||||
|
body = """
|
||||||
|
{%- macro remote_url() -%}
|
||||||
|
https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }}
|
||||||
|
{%- endmacro -%}
|
||||||
|
|
||||||
|
{% if version -%}
|
||||||
|
## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
|
||||||
|
{% else -%}
|
||||||
|
## [Unreleased]
|
||||||
|
{% endif -%}
|
||||||
|
|
||||||
|
{% for group, commits in commits | group_by(attribute="group") %}
|
||||||
|
### {{ group | striptags | trim | upper_first }}
|
||||||
|
{%- for commit in commits %}
|
||||||
|
- {% if commit.breaking %}**BREAKING** {% endif -%}
|
||||||
|
{% if commit.scope %}*({{ commit.scope }})* {% endif -%}
|
||||||
|
{{ commit.message | trim | upper_first }}\
|
||||||
|
{% if commit.github.username and commit.github.username != "rizary" %} by \
|
||||||
|
[@{{ commit.github.username }}](https://github.com/{{ commit.github.username }})\
|
||||||
|
{%- endif -%}
|
||||||
|
{% if commit.github.pr_number %} in \
|
||||||
|
[#{{ commit.github.pr_number }}]({{ self::remote_url() }}/pull/{{ commit.github.pr_number }})\
|
||||||
|
{%- endif -%}.
|
||||||
|
{%- set fixes = commit.footers | filter(attribute="token", value="Fixes") -%}
|
||||||
|
{%- set closes = commit.footers | filter(attribute="token", value="Closes") -%}
|
||||||
|
{% for footer in fixes | concat(with=closes) -%}
|
||||||
|
{%- set issue_number = footer.value | trim_start_matches(pat="#") %} \
|
||||||
|
([{{ footer.value }}]({{ self::remote_url() }}/issues/{{ issue_number }}))\
|
||||||
|
{%- endfor -%}
|
||||||
|
{% if commit.body %}
|
||||||
|
{%- for section in commit.body | trim | split(pat="\n\n") %}
|
||||||
|
{% raw %} {% endraw %}- {{ section | replace(from="\n", to=" ") }}
|
||||||
|
{%- endfor -%}
|
||||||
|
{%- endif -%}
|
||||||
|
{% endfor %}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{%- if github.contributors | filter(attribute="is_first_time", value=true) | length != 0 %}
|
||||||
|
### New Contributors
|
||||||
|
{%- endif -%}
|
||||||
|
|
||||||
|
{% for contributor in github.contributors | filter(attribute="is_first_time", value=true) %}
|
||||||
|
- @{{ contributor.username }} made their first contribution
|
||||||
|
{%- if contributor.pr_number %} in \
|
||||||
|
[#{{ contributor.pr_number }}]({{ self::remote_url() }}/pull/{{ contributor.pr_number }}) \
|
||||||
|
{%- endif %}
|
||||||
|
{%- endfor %}\n
|
||||||
|
"""
|
||||||
|
# template for the changelog footer
|
||||||
|
footer = """
|
||||||
|
{%- macro remote_url() -%}
|
||||||
|
https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }}
|
||||||
|
{%- endmacro -%}
|
||||||
|
|
||||||
|
{% for release in releases -%}
|
||||||
|
{% if release.version -%}
|
||||||
|
{% if release.previous.version -%}
|
||||||
|
[{{ release.version | trim_start_matches(pat="v") }}]: \
|
||||||
|
{{ self::remote_url() }}/compare/{{ release.previous.version }}...{{ release.version }}
|
||||||
|
{% else -%}
|
||||||
|
{#- compare against the initial commit for the first version -#}
|
||||||
|
[{{ release.version | trim_start_matches(pat="v") }}]: \
|
||||||
|
{{ self::remote_url() }}/compare/{{ release.commit_id }}...{{ release.version }}
|
||||||
|
{% endif -%}
|
||||||
|
{% else -%}
|
||||||
|
[Unreleased]: {{ self::remote_url() }}/compare/{{ release.previous.version }}...HEAD
|
||||||
|
{% endif -%}
|
||||||
|
{%- endfor -%}
|
||||||
|
"""
|
||||||
|
# remove the leading and trailing whitespace from the templates
|
||||||
|
trim = true
|
||||||
|
# postprocessors
|
||||||
|
postprocessors = []
|
||||||
|
|
||||||
|
[workspace.metadata.git-cliff.git]
|
||||||
|
# parse the commits based on https://www.conventionalcommits.org
|
||||||
|
conventional_commits = true
|
||||||
|
# filter out the commits that are not conventional
|
||||||
|
filter_unconventional = true
|
||||||
|
# process each line of a commit as an individual commit
|
||||||
|
split_commits = false
|
||||||
|
# regex for preprocessing the commit messages
|
||||||
|
commit_preprocessors = []
|
||||||
|
# regex for parsing and grouping commits
|
||||||
|
commit_parsers = [
|
||||||
|
{ message = "^feat", group = "<!-- 0 -->Features" },
|
||||||
|
{ body = ".*security", group = "<!-- 1 -->Security" },
|
||||||
|
{ message = "^fix", group = "<!-- 2 -->Bug Fixes" },
|
||||||
|
{ message = "^perf", group = "<!-- 3 -->Performance" },
|
||||||
|
{ message = "^doc", group = "<!-- 4 -->Documentation" },
|
||||||
|
{ message = "^test", group = "<!-- 5 -->Tests" },
|
||||||
|
{ message = "^refactor", group = "<!-- 6 -->Refactor" },
|
||||||
|
{ message = "^style", group = "<!-- 7 -->Style" },
|
||||||
|
{ message = "^chore", group = "<!-- 8 -->Miscellaneous" },
|
||||||
|
{ message = "^ci", default_scope = "ci", group = "<!-- 8 -->Miscellaneous" },
|
||||||
|
{ message = "^release", skip = true },
|
||||||
|
]
|
||||||
|
# protect breaking changes from being skipped due to matching a skipping commit_parser
|
||||||
|
protect_breaking_commits = false
|
||||||
|
# filter out the commits that are not matched by commit parsers
|
||||||
|
filter_commits = false
|
||||||
|
# regex for matching git tags
|
||||||
|
tag_pattern = "v[0-9].*"
|
||||||
|
# regex for skipping tags
|
||||||
|
skip_tags = ""
|
||||||
|
# regex for ignoring tags
|
||||||
|
ignore_tags = ""
|
||||||
|
# sort the tags topologically
|
||||||
|
topo_order = false
|
||||||
|
# sort the commits inside sections by oldest/newest order
|
||||||
|
sort_commits = "oldest"
|
||||||
193
README.md
193
README.md
|
|
@ -7,149 +7,118 @@
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
This is a modern rewrite of the Ziya trading platform using [Slint](https://slint.dev/) for the UI and Rust for the backend logic. Slint provides a declarative UI language that makes it easy to create beautiful, native desktop applications.
|
Ziya is a modern, high-performance trading platform built with [Slint](https://slint.dev/) and Rust. Designed for cryptocurrency traders who demand speed, reliability, and a beautiful user interface.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- **Modern Native UI**: Built with Slint's declarative UI language
|
- **⚡ Lightning Fast**: Native Rust performance with instant startup
|
||||||
- **Fast Performance**: Native Rust performance with efficient rendering
|
- **🎨 Beautiful UI**: Modern interface built with Slint's declarative language
|
||||||
- **Clean Architecture**: Separated UI and business logic
|
- **📊 Real-time Data**: Live token prices and market analysis
|
||||||
- **Async Operations**: Non-blocking token fetching and user management
|
- **🔒 Secure**: Type-safe Rust backend with robust error handling
|
||||||
- **Modular Design**: Well-organized codebase with clear separation of concerns
|
- **📱 Native Feel**: True desktop application, not a web wrapper
|
||||||
|
- **🚀 Lightweight**: ~5MB standalone binary vs ~100MB Electron apps
|
||||||
|
|
||||||
### Application States
|
## Installation
|
||||||
|
|
||||||
- **Loading**: Initial app startup and initialization
|
### Download Pre-built Binaries
|
||||||
- **Login**: User authentication interface
|
|
||||||
- **Dashboard**: Main trading dashboard with token search
|
|
||||||
- **Hunting Ground**: Advanced trading tools (placeholder)
|
|
||||||
- **Profile**: User profile and wallet information
|
|
||||||
|
|
||||||
## Project Structure
|
Visit our [Releases](https://github.com/rizilab/ziya/releases) page to download the latest version for your platform:
|
||||||
|
|
||||||
```
|
- **Windows**: `ziya-x86_64-pc-windows-msvc.zip`
|
||||||
ziya-slint/
|
- **macOS**: `ziya-x86_64-apple-darwin.tar.xz` (Intel) or `ziya-aarch64-apple-darwin.tar.xz` (Apple Silicon)
|
||||||
├── src/
|
- **Linux**: `ziya-x86_64-unknown-linux-gnu.tar.xz`
|
||||||
│ ├── main.rs # Application entry point
|
|
||||||
│ ├── lib.rs # Module organization
|
|
||||||
│ ├── app_state.rs # Application state management
|
|
||||||
│ └── services/ # Business logic services
|
|
||||||
│ ├── mod.rs
|
|
||||||
│ ├── token_service.rs # Token data and search
|
|
||||||
│ └── user_service.rs # User authentication and profiles
|
|
||||||
├── ui/
|
|
||||||
│ └── app-window.slint # Main UI definition
|
|
||||||
├── Cargo.toml # Project dependencies
|
|
||||||
└── build.rs # Build configuration
|
|
||||||
```
|
|
||||||
|
|
||||||
## Getting Started
|
### Build from Source
|
||||||
|
|
||||||
### Prerequisites
|
If you have Rust installed:
|
||||||
|
|
||||||
- Rust 1.70+ (recommended via [rustup](https://rustup.rs/))
|
|
||||||
- Git
|
|
||||||
|
|
||||||
### Installation
|
|
||||||
|
|
||||||
1. Clone the repository:
|
|
||||||
```bash
|
|
||||||
git clone <repository-url>
|
|
||||||
cd ziya-slint
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Build and run:
|
|
||||||
```bash
|
|
||||||
cargo run
|
|
||||||
```
|
|
||||||
|
|
||||||
## Development
|
|
||||||
|
|
||||||
### Running in Development Mode
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Run with debug logging
|
git clone https://github.com/rizilab/ziya.git
|
||||||
RUST_LOG=debug cargo run
|
cd ziya-slint
|
||||||
|
cargo install --path .
|
||||||
```
|
```
|
||||||
|
|
||||||
### Building for Release
|
For detailed build instructions, see [CONTRIBUTING.md](CONTRIBUTING.md).
|
||||||
|
|
||||||
```bash
|
## Quick Start
|
||||||
cargo build --release
|
|
||||||
```
|
|
||||||
|
|
||||||
## Architecture
|
1. **Launch Ziya**: Run the application
|
||||||
|
2. **Login**: Enter your credentials or create a new account
|
||||||
|
3. **Dashboard**: Browse and search tokens
|
||||||
|
4. **Trade**: Access advanced tools in the Hunting Ground
|
||||||
|
|
||||||
### UI Layer (Slint)
|
## Application Features
|
||||||
|
|
||||||
The UI is defined in `ui/app-window.slint` using Slint's declarative language. Key features:
|
### Dashboard
|
||||||
|
- **Token Search**: Find tokens by name, symbol, or address
|
||||||
|
- **Market Overview**: Real-time price data and trends
|
||||||
|
- **Portfolio**: Track your holdings and performance
|
||||||
|
|
||||||
- **State-based rendering**: Different UI states for loading, login, dashboard, etc.
|
### Hunting Ground
|
||||||
- **Reactive properties**: Automatic UI updates when data changes
|
- **Advanced Analysis**: Deep market insights and analytics
|
||||||
- **Component composition**: Reusable UI elements
|
- **Trading Tools**: Professional-grade trading interface
|
||||||
- **Modern styling**: Clean, professional design
|
- **Risk Management**: Built-in tools to manage trading risks
|
||||||
|
|
||||||
### Business Logic (Rust)
|
### Profile Management
|
||||||
|
- **Wallet Integration**: Connect and manage multiple wallets
|
||||||
|
- **Settings**: Customize your trading experience
|
||||||
|
- **Security**: Secure credential management
|
||||||
|
|
||||||
- **Services Pattern**: Separate services for different concerns
|
## Why Slint + Rust?
|
||||||
- **Async/Await**: Non-blocking operations for better UX
|
|
||||||
- **Error Handling**: Proper error propagation with `anyhow`
|
|
||||||
- **Type Safety**: Strong typing throughout the application
|
|
||||||
|
|
||||||
### Key Components
|
| Aspect | Traditional (Electron) | Ziya (Slint + Rust) |
|
||||||
|
|--------|----------------------|-------------------|
|
||||||
|
| **Memory Usage** | ~50MB+ | ~10MB |
|
||||||
|
| **Startup Time** | 2-5 seconds | Instant |
|
||||||
|
| **Bundle Size** | ~100MB | ~5MB |
|
||||||
|
| **Performance** | JavaScript V8 | Native machine code |
|
||||||
|
| **Security** | Web vulnerabilities | Memory-safe Rust |
|
||||||
|
| **Updates** | Large downloads | Efficient delta updates |
|
||||||
|
|
||||||
1. **TokenService**: Handles token data fetching and search
|
## Support
|
||||||
2. **UserService**: Manages user authentication and profiles
|
|
||||||
3. **AppState**: Central application state management
|
|
||||||
4. **Main**: Coordinates UI and business logic
|
|
||||||
|
|
||||||
## Comparison with Vue/Electron Version
|
- **Documentation**: [Full documentation](https://docs.ziya.trading)
|
||||||
|
- **Issues**: [GitHub Issues](https://github.com/rizilab/ziya/issues)
|
||||||
|
- **Discussions**: [GitHub Discussions](https://github.com/rizilab/ziya/discussions)
|
||||||
|
- **Community**: [Discord Server](https://discord.gg/bismillahdao)
|
||||||
|
|
||||||
| Aspect | Vue/Electron | Slint/Rust |
|
## Roadmap
|
||||||
|--------|--------------|------------|
|
|
||||||
| **Performance** | ~50MB memory, slower startup | ~10MB memory, instant startup |
|
### Current Version (v0.2.0)
|
||||||
| **Bundle Size** | ~100MB with Node.js | ~5MB standalone binary |
|
- ✅ Core trading dashboard
|
||||||
| **Development** | Hot reload, web tools | Fast compilation, native debugging |
|
- ✅ Token search and filtering
|
||||||
| **Deployment** | Requires Node.js runtime | Single self-contained binary |
|
- ✅ User authentication
|
||||||
| **UI Development** | HTML/CSS/JS knowledge required | Slint DSL (QML-like, simpler) |
|
- ✅ Basic portfolio tracking
|
||||||
| **Type Safety** | TypeScript (optional) | Full Rust type safety |
|
|
||||||
|
### Next Release (v0.3.0)
|
||||||
|
- 🚧 Real-time WebSocket data feeds
|
||||||
|
- 🚧 Advanced charting with TradingView
|
||||||
|
- 🚧 Multi-exchange support
|
||||||
|
- 🚧 Trading automation tools
|
||||||
|
|
||||||
|
### Future Releases
|
||||||
|
- 📋 Mobile companion app
|
||||||
|
- 📋 DeFi protocol integration
|
||||||
|
- 📋 Social trading features
|
||||||
|
- 📋 Advanced risk analytics
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
This is part of the bismillahDAO ecosystem. When contributing:
|
We welcome contributions! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for:
|
||||||
|
|
||||||
1. Follow Rust conventions and best practices
|
- Development setup
|
||||||
2. Keep UI logic in Slint files, business logic in Rust
|
- Code style guidelines
|
||||||
3. Write tests for business logic
|
- Testing procedures
|
||||||
4. Update documentation for new features
|
- Release process
|
||||||
|
|
||||||
## Migration Status
|
|
||||||
|
|
||||||
### ✅ Completed Features
|
|
||||||
- [x] Basic application structure
|
|
||||||
- [x] Loading, login, and dashboard states
|
|
||||||
- [x] Token list display and search
|
|
||||||
- [x] User profile management
|
|
||||||
- [x] Navigation between views
|
|
||||||
- [x] Mock data services
|
|
||||||
|
|
||||||
### 🚧 In Progress
|
|
||||||
- [ ] Real API integration
|
|
||||||
- [ ] Advanced token analysis
|
|
||||||
- [ ] Wallet integration
|
|
||||||
- [ ] Trading features
|
|
||||||
|
|
||||||
### 📋 Planned Features
|
|
||||||
- [ ] Real-time token price updates
|
|
||||||
- [ ] Advanced charting
|
|
||||||
- [ ] Portfolio tracking
|
|
||||||
- [ ] Trading automation
|
|
||||||
- [ ] Export/import functionality
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
MIT License - see LICENSE file for details.
|
MIT License - see [LICENSE](LICENSE) file for details.
|
||||||
|
|
||||||
## About bismillahDAO
|
## About bismillahDAO
|
||||||
|
|
||||||
This project is created by bismillahDAO, focused on building ethical and innovative trading tools for the cryptocurrency community.
|
Ziya is proudly developed by [bismillahDAO](https://bismillahdao.org), building ethical and innovative tools for the cryptocurrency community.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**⭐ Star this repository if you find Ziya useful!**
|
||||||
|
|
|
||||||
6
build.rs
6
build.rs
|
|
@ -1,8 +1,6 @@
|
||||||
fn main() {
|
fn main() {
|
||||||
// Use fluent-light as default, but we'll support dynamic switching
|
// Use fluent-light as default, but we'll support dynamic switching
|
||||||
let config = slint_build::CompilerConfiguration::new()
|
let config = slint_build::CompilerConfiguration::new().with_style("fluent".to_string());
|
||||||
.with_style("fluent-light".to_string());
|
|
||||||
|
|
||||||
slint_build::compile_with_config("ui/index.slint", config)
|
slint_build::compile_with_config("ui/index.slint", config).expect("Slint build failed");
|
||||||
.expect("Slint build failed");
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
59
justfile
Normal file
59
justfile
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
# Default recipe to display available commands
|
||||||
|
default:
|
||||||
|
@just --list
|
||||||
|
|
||||||
|
# Run the application with dev features
|
||||||
|
run:
|
||||||
|
cargo run --features dev
|
||||||
|
|
||||||
|
# Run the application with prod features
|
||||||
|
run-prod:
|
||||||
|
cargo run --features prod
|
||||||
|
|
||||||
|
# Build the application with dev features
|
||||||
|
build:
|
||||||
|
cargo build --features dev
|
||||||
|
|
||||||
|
# Build the application with prod features
|
||||||
|
build-prod:
|
||||||
|
cargo build --features prod
|
||||||
|
|
||||||
|
# Run tests
|
||||||
|
test:
|
||||||
|
cargo test
|
||||||
|
|
||||||
|
# Run clippy linter
|
||||||
|
clippy:
|
||||||
|
cargo clippy --all-targets --all-features -- -D warnings
|
||||||
|
|
||||||
|
# Format code
|
||||||
|
fmt:
|
||||||
|
cargo fmt
|
||||||
|
|
||||||
|
# Check formatting
|
||||||
|
fmt-check:
|
||||||
|
cargo fmt --check
|
||||||
|
|
||||||
|
# Run development server with hot reloading using watchexec
|
||||||
|
dev:
|
||||||
|
watchexec --clear --stop-timeout=3s -i "./**/target" -w "src" -w "ui" -e "rs,toml,slint" --project-origin "." -r "just run"
|
||||||
|
|
||||||
|
# Generate changelog using git-cliff
|
||||||
|
changelog:
|
||||||
|
git-cliff -o CHANGELOG.md
|
||||||
|
|
||||||
|
# Generate unreleased changelog
|
||||||
|
changelog-unreleased:
|
||||||
|
git-cliff --unreleased --tag unreleased -o CHANGELOG.md
|
||||||
|
|
||||||
|
# Clean build artifacts
|
||||||
|
clean:
|
||||||
|
cargo clean
|
||||||
|
|
||||||
|
# Check for security vulnerabilities
|
||||||
|
audit:
|
||||||
|
cargo audit
|
||||||
|
|
||||||
|
# Install development dependencies
|
||||||
|
install-deps:
|
||||||
|
cargo install watchexec-cli git-cliff cargo-audit
|
||||||
37
src/config.rs
Normal file
37
src/config.rs
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
use crate::err_with_loc;
|
||||||
|
use crate::error::app::AppError;
|
||||||
|
use crate::error::Result;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct StorageRedisConfig {
|
||||||
|
pub host: String,
|
||||||
|
pub port: u16,
|
||||||
|
pub pool_size: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct LoggingConfig {
|
||||||
|
pub directory: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct Config {
|
||||||
|
pub storage_redis: StorageRedisConfig,
|
||||||
|
pub logging: LoggingConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn load_config(path: impl AsRef<Path>) -> Result<Config> {
|
||||||
|
let config_str = std::fs::read_to_string(path).map_err(|e| {
|
||||||
|
err_with_loc!(AppError::Config(format!(
|
||||||
|
"failed_to_read_config_file: {}",
|
||||||
|
e
|
||||||
|
)))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let config: Config = toml::from_str(&config_str)
|
||||||
|
.map_err(|e| err_with_loc!(AppError::Config(format!("failed_to_parse_config: {}", e))))?;
|
||||||
|
|
||||||
|
Ok(config)
|
||||||
|
}
|
||||||
22
src/error/app.rs
Normal file
22
src/error/app.rs
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum AppError {
|
||||||
|
#[error("Redis error: {0}")]
|
||||||
|
Redis(String),
|
||||||
|
|
||||||
|
#[error("Handler error: {0}")]
|
||||||
|
Handler(String),
|
||||||
|
|
||||||
|
#[error("Slint error: {0}")]
|
||||||
|
Slint(String),
|
||||||
|
|
||||||
|
#[error("Serialization error: {0}")]
|
||||||
|
Serialization(String),
|
||||||
|
|
||||||
|
#[error("Configuration error: {0}")]
|
||||||
|
Config(String),
|
||||||
|
|
||||||
|
#[error("UI error: {0}")]
|
||||||
|
UiError(String),
|
||||||
|
}
|
||||||
13
src/error/mod.rs
Normal file
13
src/error/mod.rs
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
pub mod app;
|
||||||
|
|
||||||
|
pub use anyhow::anyhow;
|
||||||
|
pub use anyhow::Context;
|
||||||
|
pub use anyhow::Error;
|
||||||
|
pub use anyhow::Result;
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! err_with_loc {
|
||||||
|
($err:expr) => {
|
||||||
|
anyhow::anyhow!($err).context(format!("at {}:{}", file!(), line!()))
|
||||||
|
};
|
||||||
|
}
|
||||||
19
src/handler/mod.rs
Normal file
19
src/handler/mod.rs
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
pub mod token;
|
||||||
|
pub mod ui;
|
||||||
|
|
||||||
|
use crate::model::token::{MaxDepthReachedData, NewTokenCreatedData, TokenCexUpdatedData};
|
||||||
|
|
||||||
|
// Messages that can be sent to the hunting ground handler
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum TokenHandler {
|
||||||
|
NewToken { data: NewTokenCreatedData },
|
||||||
|
CexUpdated { data: TokenCexUpdatedData },
|
||||||
|
MaxDepthReached { data: MaxDepthReachedData },
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum SlintHandler {
|
||||||
|
ClearNewTokens {},
|
||||||
|
ClearCexTokens {},
|
||||||
|
ClearAnalysisTokens {},
|
||||||
|
}
|
||||||
284
src/handler/token.rs
Normal file
284
src/handler/token.rs
Normal file
|
|
@ -0,0 +1,284 @@
|
||||||
|
use chrono::TimeZone;
|
||||||
|
use chrono::Utc;
|
||||||
|
use chrono_tz::Asia::Jakarta;
|
||||||
|
use std::{collections::VecDeque, sync::Arc};
|
||||||
|
use tokio::sync::{mpsc, oneshot};
|
||||||
|
use tracing::{debug, error, info, warn};
|
||||||
|
|
||||||
|
use super::SlintHandler;
|
||||||
|
use super::TokenHandler;
|
||||||
|
use crate::error::Result;
|
||||||
|
use crate::model::token::{MaxDepthReachedData, NewTokenCreatedData, TokenCexUpdatedData};
|
||||||
|
use crate::slint_ui::{CexUpdatedUiData, MainWindow, MaxDepthReachedUiData, NewTokenUiData};
|
||||||
|
use crate::storage::StorageEngine;
|
||||||
|
use crate::task::shutdown::ShutdownSignal;
|
||||||
|
use slint::{Model, SharedString, Weak};
|
||||||
|
|
||||||
|
// Internal state for the hunting ground handler
|
||||||
|
pub struct TokenMetadataHandler {
|
||||||
|
pub receiver: mpsc::Receiver<TokenHandler>,
|
||||||
|
pub slint_tx: mpsc::Sender<SlintHandler>,
|
||||||
|
pub ui_weak: Weak<MainWindow>,
|
||||||
|
pub shutdown: ShutdownSignal,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TokenMetadataHandler {
|
||||||
|
pub fn new(
|
||||||
|
receiver: mpsc::Receiver<TokenHandler>,
|
||||||
|
slint_tx: mpsc::Sender<SlintHandler>,
|
||||||
|
ui_weak: Weak<MainWindow>,
|
||||||
|
shutdown: ShutdownSignal,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
receiver,
|
||||||
|
slint_tx,
|
||||||
|
ui_weak,
|
||||||
|
shutdown,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn process_new_token_to_slint(&self, token: NewTokenCreatedData) {
|
||||||
|
info!("processing_new_token_to_slint: mint={}", token.mint.clone());
|
||||||
|
let utc_timestamp = Utc.timestamp_millis_opt(token.created_at as i64).unwrap();
|
||||||
|
let jakarta_timestamp = utc_timestamp.with_timezone(&chrono_tz::Asia::Jakarta);
|
||||||
|
let created_at = jakarta_timestamp.format("%Y-%m-%d %H:%M:%S");
|
||||||
|
|
||||||
|
let new_token_ui_data = NewTokenUiData {
|
||||||
|
mint: SharedString::from(token.mint.to_string()),
|
||||||
|
bonding_curve: token
|
||||||
|
.bonding_curve
|
||||||
|
.map(|bc| SharedString::from(bc.to_string()))
|
||||||
|
.unwrap_or(SharedString::from("")),
|
||||||
|
name: SharedString::from(token.name),
|
||||||
|
symbol: SharedString::from(token.symbol),
|
||||||
|
uri: SharedString::from(token.uri),
|
||||||
|
creator: SharedString::from(token.creator.to_string()),
|
||||||
|
created_at: SharedString::from(created_at.to_string()),
|
||||||
|
};
|
||||||
|
|
||||||
|
// ✅ Update UI using proper Slint threading model
|
||||||
|
let ui_weak_clone = self.ui_weak.clone();
|
||||||
|
slint::invoke_from_event_loop(move || {
|
||||||
|
if let Some(ui) = ui_weak_clone.upgrade() {
|
||||||
|
// Convert ModelRc to Vec, add new item, and set back
|
||||||
|
let current_tokens = ui.get_new_tokens();
|
||||||
|
let mut tokens_vec: Vec<NewTokenUiData> = current_tokens.iter().collect();
|
||||||
|
tokens_vec.insert(0, new_token_ui_data);
|
||||||
|
ui.set_new_tokens(tokens_vec.as_slice().into());
|
||||||
|
info!(
|
||||||
|
"process_new_token_to_slint::ui_updated::total_tokens={}",
|
||||||
|
tokens_vec.len()
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
warn!("process_new_token_to_slint::ui_weak_reference_expired");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|e| {
|
||||||
|
error!(
|
||||||
|
"process_new_token_to_slint::failed_to_invoke_from_event_loop: {}",
|
||||||
|
e
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn process_cex_updated_to_slint(&self, token: TokenCexUpdatedData) {
|
||||||
|
info!("processing_cex_updated_to_slint: mint={}", token.mint);
|
||||||
|
|
||||||
|
let utc_created_at = Utc.timestamp_millis_opt(token.created_at as i64).unwrap();
|
||||||
|
let jakarta_created_at = utc_created_at.with_timezone(&chrono_tz::Asia::Jakarta);
|
||||||
|
let created_at = jakarta_created_at.format("%Y-%m-%d %H:%M:%S");
|
||||||
|
|
||||||
|
let utc_updated_at = Utc.timestamp_millis_opt(token.updated_at as i64).unwrap();
|
||||||
|
let jakarta_updated_at = utc_updated_at.with_timezone(&chrono_tz::Asia::Jakarta);
|
||||||
|
let updated_at = jakarta_updated_at.format("%Y-%m-%d %H:%M:%S");
|
||||||
|
|
||||||
|
let cex_updated_ui_data = CexUpdatedUiData {
|
||||||
|
mint: SharedString::from(token.mint),
|
||||||
|
name: SharedString::from(token.name),
|
||||||
|
uri: SharedString::from(token.uri),
|
||||||
|
dev_name: SharedString::from(token.dev_name),
|
||||||
|
creator: SharedString::from(token.creator),
|
||||||
|
cex_name: SharedString::from(token.cex_name),
|
||||||
|
cex_address: SharedString::from(token.cex_address),
|
||||||
|
bonding_curve: SharedString::from(token.bonding_curve),
|
||||||
|
created_at: SharedString::from(created_at.to_string()),
|
||||||
|
updated_at: SharedString::from(updated_at.to_string()),
|
||||||
|
node_count: token.node_count as i32,
|
||||||
|
edge_count: token.edge_count as i32,
|
||||||
|
};
|
||||||
|
|
||||||
|
// ✅ Update UI using proper Slint threading model
|
||||||
|
let ui_weak_clone = self.ui_weak.clone();
|
||||||
|
slint::invoke_from_event_loop(move || {
|
||||||
|
if let Some(ui) = ui_weak_clone.upgrade() {
|
||||||
|
// Convert ModelRc to Vec, add new item, and set back
|
||||||
|
let current_tokens = ui.get_cex_tokens();
|
||||||
|
let mut tokens_vec: Vec<CexUpdatedUiData> = current_tokens.iter().collect();
|
||||||
|
tokens_vec.insert(0, cex_updated_ui_data);
|
||||||
|
ui.set_cex_tokens(tokens_vec.as_slice().into());
|
||||||
|
info!(
|
||||||
|
"process_cex_updated_to_slint::ui_updated::total_tokens={}",
|
||||||
|
tokens_vec.len()
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
warn!("process_cex_updated_to_slint::ui_weak_reference_expired");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|e| {
|
||||||
|
error!(
|
||||||
|
"process_cex_updated_to_slint::failed_to_invoke_from_event_loop: {}",
|
||||||
|
e
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn process_max_depth_reached_to_slint(&self, token: MaxDepthReachedData) {
|
||||||
|
info!("processing_max_depth_reached_to_slint: mint={}", token.mint);
|
||||||
|
|
||||||
|
let utc_created_at = Utc.timestamp_millis_opt(token.created_at as i64).unwrap();
|
||||||
|
let jakarta_created_at = utc_created_at.with_timezone(&chrono_tz::Asia::Jakarta);
|
||||||
|
let created_at = jakarta_created_at.format("%Y-%m-%d %H:%M:%S");
|
||||||
|
|
||||||
|
let utc_updated_at = Utc.timestamp_millis_opt(token.updated_at as i64).unwrap();
|
||||||
|
let jakarta_updated_at = utc_updated_at.with_timezone(&chrono_tz::Asia::Jakarta);
|
||||||
|
let updated_at = jakarta_updated_at.format("%Y-%m-%d %H:%M:%S");
|
||||||
|
|
||||||
|
let max_depth_ui_data = MaxDepthReachedUiData {
|
||||||
|
mint: SharedString::from(token.mint),
|
||||||
|
name: SharedString::from(token.name),
|
||||||
|
uri: SharedString::from(token.uri),
|
||||||
|
dev_name: SharedString::from(token.dev_name),
|
||||||
|
creator: SharedString::from(token.creator),
|
||||||
|
bonding_curve: SharedString::from(token.bonding_curve),
|
||||||
|
created_at: SharedString::from(created_at.to_string()),
|
||||||
|
updated_at: SharedString::from(updated_at.to_string()),
|
||||||
|
node_count: token.node_count as i32,
|
||||||
|
edge_count: token.edge_count as i32,
|
||||||
|
};
|
||||||
|
|
||||||
|
// ✅ Update UI using proper Slint threading model
|
||||||
|
let ui_weak_clone = self.ui_weak.clone();
|
||||||
|
slint::invoke_from_event_loop(move || {
|
||||||
|
if let Some(ui) = ui_weak_clone.upgrade() {
|
||||||
|
// Convert ModelRc to Vec, add new item, and set back
|
||||||
|
let current_tokens = ui.get_analysis_tokens();
|
||||||
|
let mut tokens_vec: Vec<MaxDepthReachedUiData> = current_tokens.iter().collect();
|
||||||
|
tokens_vec.insert(0, max_depth_ui_data);
|
||||||
|
ui.set_analysis_tokens(tokens_vec.as_slice().into());
|
||||||
|
info!(
|
||||||
|
"process_max_depth_reached_to_slint::ui_updated::total_tokens={}",
|
||||||
|
tokens_vec.len()
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
warn!("process_max_depth_reached_to_slint::ui_weak_reference_expired");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|e| {
|
||||||
|
error!(
|
||||||
|
"process_max_depth_reached_to_slint::failed_to_invoke_from_event_loop: {}",
|
||||||
|
e
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run_token_handler(mut token_metadata_handler: TokenMetadataHandler) {
|
||||||
|
debug!("token_handler::started");
|
||||||
|
|
||||||
|
loop {
|
||||||
|
tokio::select! {
|
||||||
|
Some(msg) = token_metadata_handler.receiver.recv() => {
|
||||||
|
match msg {
|
||||||
|
TokenHandler::NewToken { data } => {
|
||||||
|
info!("received_new_token: mint={}", data.mint);
|
||||||
|
token_metadata_handler.process_new_token_to_slint(data).await;
|
||||||
|
},
|
||||||
|
TokenHandler::CexUpdated { data } => {
|
||||||
|
info!("received_cex_updated_token: mint={}", data.mint);
|
||||||
|
token_metadata_handler.process_cex_updated_to_slint(data).await;
|
||||||
|
},
|
||||||
|
TokenHandler::MaxDepthReached { data } => {
|
||||||
|
info!("received_max_depth_reached_token: mint={}", data.mint);
|
||||||
|
token_metadata_handler.process_max_depth_reached_to_slint(data).await;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
else => {
|
||||||
|
// Channel closed, exit gracefully
|
||||||
|
debug!("hunting_ground_handler_token::channel_closed::exiting");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main hunting ground handler following the muhafidh actor pattern
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct TokenMetadataHandlerOperator {
|
||||||
|
pub db: Arc<StorageEngine>,
|
||||||
|
pub sender: mpsc::Sender<TokenHandler>,
|
||||||
|
pub shutdown: ShutdownSignal,
|
||||||
|
pub ui_weak: Weak<MainWindow>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TokenMetadataHandlerOperator {
|
||||||
|
pub fn new(
|
||||||
|
db: Arc<StorageEngine>,
|
||||||
|
shutdown: ShutdownSignal,
|
||||||
|
receiver: mpsc::Receiver<TokenHandler>,
|
||||||
|
sender: mpsc::Sender<TokenHandler>,
|
||||||
|
slint_tx: mpsc::Sender<SlintHandler>,
|
||||||
|
ui_weak: Weak<MainWindow>,
|
||||||
|
) -> Self {
|
||||||
|
let token_metadata_handler =
|
||||||
|
TokenMetadataHandler::new(receiver, slint_tx, ui_weak.clone(), shutdown.clone());
|
||||||
|
|
||||||
|
// Spawn the actor
|
||||||
|
tokio::spawn(run_token_handler(token_metadata_handler));
|
||||||
|
Self {
|
||||||
|
db,
|
||||||
|
sender,
|
||||||
|
shutdown,
|
||||||
|
ui_weak,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn process_new_token(&self, token: NewTokenCreatedData) -> Result<()> {
|
||||||
|
info!("processing_new_token: mint={}", token.mint.clone());
|
||||||
|
if let Err(e) = self.sender.try_send(TokenHandler::NewToken {
|
||||||
|
data: token.clone(),
|
||||||
|
}) {
|
||||||
|
error!(
|
||||||
|
"failed_to_send_token_to_token_handler::mint::{}::error::{}",
|
||||||
|
token.mint, e
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn process_cex_updated(&self, token: TokenCexUpdatedData) -> Result<()> {
|
||||||
|
info!("processing_cex_updated: mint={}", token.mint);
|
||||||
|
if let Err(e) = self.sender.try_send(TokenHandler::CexUpdated {
|
||||||
|
data: token.clone(),
|
||||||
|
}) {
|
||||||
|
error!(
|
||||||
|
"failed_to_send_cex_updated_to_token_handler::mint::{}::error::{}",
|
||||||
|
token.mint, e
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn process_max_depth_reached(&self, token: MaxDepthReachedData) -> Result<()> {
|
||||||
|
info!("processing_max_depth_reached: mint={}", token.mint);
|
||||||
|
if let Err(e) = self.sender.try_send(TokenHandler::MaxDepthReached {
|
||||||
|
data: token.clone(),
|
||||||
|
}) {
|
||||||
|
error!(
|
||||||
|
"failed_to_send_max_depth_reached_to_token_handler::mint::{}::error::{}",
|
||||||
|
token.mint, e
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
99
src/handler/ui.rs
Normal file
99
src/handler/ui.rs
Normal file
|
|
@ -0,0 +1,99 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
use tokio::sync::mpsc;
|
||||||
|
use tracing::{debug, error, info};
|
||||||
|
|
||||||
|
use super::SlintHandler;
|
||||||
|
use super::TokenHandler;
|
||||||
|
use crate::storage::StorageEngine;
|
||||||
|
use crate::task::shutdown::ShutdownSignal;
|
||||||
|
|
||||||
|
// Internal state for the hunting ground handler
|
||||||
|
pub struct SlintHandlerUi {
|
||||||
|
pub receiver: mpsc::Receiver<SlintHandler>,
|
||||||
|
pub token_tx: mpsc::Sender<TokenHandler>,
|
||||||
|
pub shutdown: ShutdownSignal,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SlintHandlerUi {
|
||||||
|
pub fn new(
|
||||||
|
receiver: mpsc::Receiver<SlintHandler>,
|
||||||
|
token_tx: mpsc::Sender<TokenHandler>,
|
||||||
|
shutdown: ShutdownSignal,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
receiver,
|
||||||
|
token_tx,
|
||||||
|
shutdown,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run_slint_handler_ui(mut slint_handler_ui: SlintHandlerUi) {
|
||||||
|
debug!("slint_handler_ui::started");
|
||||||
|
|
||||||
|
loop {
|
||||||
|
tokio::select! {
|
||||||
|
Some(msg) = slint_handler_ui.receiver.recv() => {
|
||||||
|
match msg {
|
||||||
|
SlintHandler::ClearNewTokens { } => {
|
||||||
|
info!("received_clear_new_tokens");
|
||||||
|
},
|
||||||
|
SlintHandler::ClearCexTokens { } => {
|
||||||
|
info!("received_clear_cex_tokens");
|
||||||
|
},
|
||||||
|
SlintHandler::ClearAnalysisTokens { } => {
|
||||||
|
info!("received_clear_analysis_tokens");
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
else => {
|
||||||
|
// Channel closed, exit gracefully
|
||||||
|
debug!("hunting_ground_handler_token::channel_closed::exiting");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Main hunting ground handler following the muhafidh actor pattern
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SlintHandlerUiOperator {
|
||||||
|
pub db: Arc<StorageEngine>,
|
||||||
|
pub sender: mpsc::Sender<SlintHandler>,
|
||||||
|
pub shutdown: ShutdownSignal,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SlintHandlerUiOperator {
|
||||||
|
pub fn new(
|
||||||
|
db: Arc<StorageEngine>,
|
||||||
|
shutdown: ShutdownSignal,
|
||||||
|
receiver: mpsc::Receiver<SlintHandler>,
|
||||||
|
sender: mpsc::Sender<SlintHandler>,
|
||||||
|
token_tx: mpsc::Sender<TokenHandler>,
|
||||||
|
) -> Self {
|
||||||
|
let slint_handler_ui = SlintHandlerUi::new(receiver, token_tx, shutdown.clone());
|
||||||
|
|
||||||
|
// Spawn the actor
|
||||||
|
tokio::spawn(run_slint_handler_ui(slint_handler_ui));
|
||||||
|
|
||||||
|
Self {
|
||||||
|
db,
|
||||||
|
sender,
|
||||||
|
shutdown,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn clear_new_tokens(&self) {
|
||||||
|
if let Err(e) = self.sender.try_send(SlintHandler::ClearNewTokens {}) {
|
||||||
|
error!("failed_to_send_clear_new_tokens::error::{}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn clear_cex_tokens(&self) {
|
||||||
|
if let Err(e) = self.sender.try_send(SlintHandler::ClearCexTokens {}) {
|
||||||
|
error!("failed_to_send_clear_cex_tokens::error::{}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn clear_analysis_tokens(&self) {
|
||||||
|
if let Err(e) = self.sender.try_send(SlintHandler::ClearAnalysisTokens {}) {
|
||||||
|
error!("failed_to_send_clear_analysis_tokens::error::{}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
361
src/lib.rs
361
src/lib.rs
|
|
@ -1,2 +1,363 @@
|
||||||
// This lib.rs is currently not used since we're building a binary application
|
// This lib.rs is currently not used since we're building a binary application
|
||||||
// If needed in the future, add modules here
|
// If needed in the future, add modules here
|
||||||
|
|
||||||
|
pub mod config;
|
||||||
|
pub mod error;
|
||||||
|
pub mod handler;
|
||||||
|
pub mod model;
|
||||||
|
pub mod storage;
|
||||||
|
pub mod task;
|
||||||
|
pub mod tracing;
|
||||||
|
|
||||||
|
pub mod slint_ui {
|
||||||
|
slint::include_modules!();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-export commonly used types
|
||||||
|
pub use config::Config;
|
||||||
|
pub use error::app::AppError;
|
||||||
|
pub use error::Result;
|
||||||
|
pub use slint::*;
|
||||||
|
pub use slint_ui::*;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
use tokio::sync::mpsc;
|
||||||
|
use tokio_util::sync::CancellationToken;
|
||||||
|
|
||||||
|
use handler::token::TokenMetadataHandlerOperator;
|
||||||
|
use handler::ui::SlintHandlerUiOperator;
|
||||||
|
use handler::SlintHandler;
|
||||||
|
use handler::TokenHandler;
|
||||||
|
use storage::StorageEngine;
|
||||||
|
use task::shutdown::ShutdownSignal;
|
||||||
|
use tracing::{error, info, warn};
|
||||||
|
|
||||||
|
// Application states
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub enum AppState {
|
||||||
|
Loading,
|
||||||
|
Login,
|
||||||
|
Authenticated,
|
||||||
|
}
|
||||||
|
|
||||||
|
// User session
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct UserSession {
|
||||||
|
pub email: String,
|
||||||
|
pub authenticated_at: SystemTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_current_unix_timestamp() -> i32 {
|
||||||
|
SystemTime::now()
|
||||||
|
.duration_since(UNIX_EPOCH)
|
||||||
|
.unwrap_or_default()
|
||||||
|
.as_secs() as i32
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simple email validation
|
||||||
|
pub fn is_valid_email(email: &str) -> bool {
|
||||||
|
email.contains('@') && email.contains('.') && email.len() > 5
|
||||||
|
}
|
||||||
|
|
||||||
|
// Health check function
|
||||||
|
pub async fn check_backend_health(db: &StorageEngine) -> bool {
|
||||||
|
// Check Redis connection
|
||||||
|
match db.redis.pool.get().await {
|
||||||
|
Ok(mut conn) => {
|
||||||
|
match redis::cmd("PING").query_async::<String>(&mut *conn).await {
|
||||||
|
Ok(_) => {
|
||||||
|
info!("Redis health check passed");
|
||||||
|
true
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
warn!("Redis ping failed: {}", e);
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
warn!("Redis health check failed: {}", e);
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ZiyaApp {
|
||||||
|
pub db: Arc<StorageEngine>,
|
||||||
|
pub ui_weak: Weak<MainWindow>,
|
||||||
|
pub shutdown: ShutdownSignal,
|
||||||
|
pub cancellation_token: CancellationToken,
|
||||||
|
pub slint_handler: Arc<SlintHandlerUiOperator>,
|
||||||
|
pub token_handler: Arc<TokenMetadataHandlerOperator>,
|
||||||
|
pub app_state: AppState,
|
||||||
|
pub user_session: Option<UserSession>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ZiyaApp {
|
||||||
|
pub async fn new(config: &Config, ui_weak: Weak<MainWindow>) -> Result<Self> {
|
||||||
|
info!("Creating storage engine");
|
||||||
|
let db = StorageEngine::new(config.clone()).await?;
|
||||||
|
let db = Arc::new(db);
|
||||||
|
|
||||||
|
let shutdown_signal = ShutdownSignal::new();
|
||||||
|
let cancellation_token = CancellationToken::new();
|
||||||
|
|
||||||
|
let (slint_tx, slint_rx) = mpsc::channel::<SlintHandler>(1000);
|
||||||
|
let (token_tx, token_rx) = mpsc::channel::<TokenHandler>(1000);
|
||||||
|
|
||||||
|
let slint_handler = Arc::new(SlintHandlerUiOperator::new(
|
||||||
|
db.clone(),
|
||||||
|
shutdown_signal.clone(),
|
||||||
|
slint_rx,
|
||||||
|
slint_tx.clone(),
|
||||||
|
token_tx.clone(),
|
||||||
|
));
|
||||||
|
|
||||||
|
let token_handler = Arc::new(TokenMetadataHandlerOperator::new(
|
||||||
|
db.clone(),
|
||||||
|
shutdown_signal.clone(),
|
||||||
|
token_rx,
|
||||||
|
token_tx,
|
||||||
|
slint_tx,
|
||||||
|
ui_weak.clone(),
|
||||||
|
));
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
db,
|
||||||
|
ui_weak,
|
||||||
|
shutdown: shutdown_signal,
|
||||||
|
cancellation_token,
|
||||||
|
slint_handler,
|
||||||
|
token_handler,
|
||||||
|
app_state: AppState::Loading,
|
||||||
|
user_session: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize the UI with health check
|
||||||
|
pub fn init_ui(&self) -> Result<()> {
|
||||||
|
if let Some(ui) = self.ui_weak.upgrade() {
|
||||||
|
// Set initial state
|
||||||
|
ui.set_app_state("loading".into());
|
||||||
|
ui.set_is_loading(true);
|
||||||
|
ui.set_has_connection_error(false);
|
||||||
|
ui.set_loading_status("Initializing your trading environment...".into());
|
||||||
|
|
||||||
|
// Set up health check callback
|
||||||
|
let db = self.db.clone();
|
||||||
|
let ui_weak = self.ui_weak.clone();
|
||||||
|
|
||||||
|
ui.on_retry_health_check(move || {
|
||||||
|
let db = db.clone();
|
||||||
|
let ui_weak_for_spawn = ui_weak.clone();
|
||||||
|
let ui_weak_for_immediate = ui_weak.clone();
|
||||||
|
|
||||||
|
// Immediately update UI for loading state
|
||||||
|
if let Some(ui) = ui_weak_for_immediate.upgrade() {
|
||||||
|
ui.set_is_loading(true);
|
||||||
|
ui.set_has_connection_error(false);
|
||||||
|
ui.set_loading_status("Checking backend connection...".into());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spawn background task for health check
|
||||||
|
tokio::spawn(async move {
|
||||||
|
// Simulate loading time
|
||||||
|
tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;
|
||||||
|
|
||||||
|
let health_result = check_backend_health(&db).await;
|
||||||
|
|
||||||
|
// Update UI from event loop thread
|
||||||
|
let _ = slint::invoke_from_event_loop(move || {
|
||||||
|
if let Some(ui) = ui_weak_for_spawn.upgrade() {
|
||||||
|
if health_result {
|
||||||
|
info!("Health check passed, transitioning to login");
|
||||||
|
ui.set_app_state("login".into());
|
||||||
|
} else {
|
||||||
|
warn!("Health check failed");
|
||||||
|
ui.set_has_connection_error(true);
|
||||||
|
ui.set_is_loading(false);
|
||||||
|
ui.set_loading_status("Connection failed".into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set up login callback
|
||||||
|
let ui_weak = self.ui_weak.clone();
|
||||||
|
ui.on_login_attempt(move |email, _password| {
|
||||||
|
let ui_weak = ui_weak.clone();
|
||||||
|
let email_str = email.to_string();
|
||||||
|
|
||||||
|
info!("Login attempt for email: {}", email_str);
|
||||||
|
|
||||||
|
if let Some(ui) = ui_weak.upgrade() {
|
||||||
|
if is_valid_email(&email_str) {
|
||||||
|
ui.set_user_email(email.into());
|
||||||
|
ui.set_app_state("authenticated".into());
|
||||||
|
ui.set_is_authenticated(true);
|
||||||
|
info!("User authenticated successfully");
|
||||||
|
} else {
|
||||||
|
warn!("Invalid email provided: {}", email_str);
|
||||||
|
// Handle login error in the UI
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set up logout callback
|
||||||
|
let ui_weak = self.ui_weak.clone();
|
||||||
|
ui.on_logout_requested(move || {
|
||||||
|
let ui_weak = ui_weak.clone();
|
||||||
|
|
||||||
|
if let Some(ui) = ui_weak.upgrade() {
|
||||||
|
info!("User logout requested");
|
||||||
|
ui.set_user_email("".into());
|
||||||
|
ui.set_app_state("login".into());
|
||||||
|
ui.set_is_authenticated(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set up authenticate user callback (for demo mode)
|
||||||
|
let ui_weak = self.ui_weak.clone();
|
||||||
|
ui.on_authenticate_user(move |email| {
|
||||||
|
let ui_weak = ui_weak.clone();
|
||||||
|
let email_str = email.to_string();
|
||||||
|
|
||||||
|
if let Some(ui) = ui_weak.upgrade() {
|
||||||
|
info!("Demo authentication for: {}", email_str);
|
||||||
|
ui.set_user_email(email.into());
|
||||||
|
ui.set_app_state("authenticated".into());
|
||||||
|
ui.set_is_authenticated(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set up sidebar toggle
|
||||||
|
let ui_weak = self.ui_weak.clone();
|
||||||
|
ui.on_toggle_sidebar(move || {
|
||||||
|
let ui_weak = ui_weak.clone();
|
||||||
|
|
||||||
|
if let Some(ui) = ui_weak.upgrade() {
|
||||||
|
let current_state = ui.get_sidebar_state();
|
||||||
|
if current_state.as_str() == "full" {
|
||||||
|
ui.set_sidebar_state("icon-only".into());
|
||||||
|
} else {
|
||||||
|
ui.set_sidebar_state("full".into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Start initial health check
|
||||||
|
let db = self.db.clone();
|
||||||
|
let ui_weak = self.ui_weak.clone();
|
||||||
|
|
||||||
|
tokio::spawn(async move {
|
||||||
|
// Initial delay to show loading screen
|
||||||
|
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
|
||||||
|
|
||||||
|
let health_result = check_backend_health(&db).await;
|
||||||
|
|
||||||
|
// Update UI from event loop thread
|
||||||
|
let _ = slint::invoke_from_event_loop(move || {
|
||||||
|
if let Some(ui) = ui_weak.upgrade() {
|
||||||
|
ui.set_loading_status("Checking backend connection...".into());
|
||||||
|
|
||||||
|
if health_result {
|
||||||
|
info!("Initial health check passed");
|
||||||
|
ui.set_app_state("login".into());
|
||||||
|
} else {
|
||||||
|
warn!("Initial health check failed");
|
||||||
|
ui.set_has_connection_error(true);
|
||||||
|
ui.set_is_loading(false);
|
||||||
|
ui.set_loading_status("Connection failed".into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(AppError::UiError("Failed to upgrade UI weak reference".into()).into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn run(self) -> Result<()> {
|
||||||
|
info!("Starting Ziya Slint Application");
|
||||||
|
|
||||||
|
// Initialize UI callbacks
|
||||||
|
self.init_ui()?;
|
||||||
|
|
||||||
|
let slint_handler = self.slint_handler.clone();
|
||||||
|
let db = self.db.clone();
|
||||||
|
|
||||||
|
let (shutdown_tx, mut shutdown_rx) = mpsc::channel(1);
|
||||||
|
|
||||||
|
let ui_task = task::ui::spawn_ui_task(
|
||||||
|
slint_handler.clone(),
|
||||||
|
self.ui_weak.clone(),
|
||||||
|
self.cancellation_token.clone(),
|
||||||
|
shutdown_tx.clone(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// ✅ Spawn all background subscriber tasks
|
||||||
|
let new_token_subscriber = task::subscriber::spawn_new_token_subscriber(
|
||||||
|
self.token_handler.clone(),
|
||||||
|
db.clone(),
|
||||||
|
self.cancellation_token.clone(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let cex_updated_subscriber = task::subscriber::spawn_token_cex_updated_subscriber(
|
||||||
|
self.token_handler.clone(),
|
||||||
|
db.clone(),
|
||||||
|
self.cancellation_token.clone(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let max_depth_subscriber = task::subscriber::spawn_max_depth_reached_subscriber(
|
||||||
|
self.token_handler.clone(),
|
||||||
|
db.clone(),
|
||||||
|
self.cancellation_token.clone(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// ✅ Use the original tokio::select! pattern
|
||||||
|
tokio::select! {
|
||||||
|
result = ui_task => {
|
||||||
|
match result {
|
||||||
|
Ok(Ok(())) => info!("ui_task_completed_successfully"),
|
||||||
|
Ok(Err(e)) => error!("ui_task_error: {}", e),
|
||||||
|
Err(e) => error!("ui_task_join_error: {}", e),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
result = new_token_subscriber => {
|
||||||
|
match result {
|
||||||
|
Ok(Ok(())) => info!("new_token_subscriber_completed_successfully"),
|
||||||
|
Ok(Err(e)) => error!("new_token_subscriber_error: {}", e),
|
||||||
|
Err(e) => error!("new_token_subscriber_join_error: {}", e),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
result = cex_updated_subscriber => {
|
||||||
|
match result {
|
||||||
|
Ok(Ok(())) => info!("cex_updated_subscriber_completed_successfully"),
|
||||||
|
Ok(Err(e)) => error!("cex_updated_subscriber_error: {}", e),
|
||||||
|
Err(e) => error!("cex_updated_subscriber_join_error: {}", e),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
result = max_depth_subscriber => {
|
||||||
|
match result {
|
||||||
|
Ok(Ok(())) => info!("max_depth_subscriber_completed_successfully"),
|
||||||
|
Ok(Err(e)) => error!("max_depth_subscriber_error: {}", e),
|
||||||
|
Err(e) => error!("max_depth_subscriber_join_error: {}", e),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ = tokio::signal::ctrl_c() => {
|
||||||
|
info!("ctrl_c_received::initiating_shutdown");
|
||||||
|
let _ = shutdown_tx.send(()).await;
|
||||||
|
},
|
||||||
|
_ = shutdown_rx.recv() => {
|
||||||
|
info!("shutdown_signal_received");
|
||||||
|
self.cancellation_token.cancel();
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("application_shutdown_complete");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
135
src/main.rs
135
src/main.rs
|
|
@ -1,119 +1,36 @@
|
||||||
// Prevent console window in addition to Slint window in Windows release builds when, e.g., starting the app via file manager. Ignored on other platforms.
|
// Prevent console window in addition to Slint window in Windows release builds when, e.g., starting the app via file manager. Ignored on other platforms.
|
||||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||||
|
|
||||||
use slint::*;
|
use ziya::config::load_config;
|
||||||
use i_slint_backend_winit::WinitWindowAccessor;
|
use ziya::error::Result;
|
||||||
|
use ziya::slint_ui::*;
|
||||||
|
use ziya::tracing::setup_tracing;
|
||||||
|
use ziya::ZiyaApp;
|
||||||
|
|
||||||
// Import the main window from our FSD-structured UI
|
#[tokio::main]
|
||||||
slint::include_modules!();
|
async fn main() -> Result<()> {
|
||||||
|
// ✅ Create and show UI on main thread using Slint's built-in pattern
|
||||||
|
let ui = MainWindow::new()
|
||||||
|
.map_err(|e| ziya::error::app::AppError::Slint(format!("failed_to_create_ui: {}", e)))?;
|
||||||
|
|
||||||
fn main() -> Result<(), slint::PlatformError> {
|
// ✅ Set up async tasks using spawn_local with async-compat for Tokio futures
|
||||||
// Create the main window
|
let ui_weak = ui.as_weak();
|
||||||
let ui = MainWindow::new()?;
|
// Load configuration
|
||||||
|
let config = load_config("Config.toml").await.unwrap();
|
||||||
|
|
||||||
// Clone handle for callbacks
|
// Initialize tracing with config
|
||||||
let ui_handle = ui.as_weak();
|
setup_tracing(config.clone(), "ziya-slint").await.unwrap();
|
||||||
|
|
||||||
// Handle navigation changes
|
// Create and run the app
|
||||||
ui.on_navigation_changed({
|
let app = ZiyaApp::new(&config, ui_weak).await.unwrap();
|
||||||
let ui_handle = ui_handle.clone();
|
slint::spawn_local(async_compat::Compat::new(async {
|
||||||
move |page| {
|
app.run().await.unwrap();
|
||||||
println!("Navigation changed to: {}", page);
|
}))
|
||||||
if let Some(ui) = ui_handle.upgrade() {
|
.unwrap();
|
||||||
// Update current page state
|
|
||||||
ui.set_current_page(page.into());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Handle theme toggle - using direct function call as per Slint discussion #5860
|
ui.run().map_err(|e| {
|
||||||
ui.on_theme_toggle_clicked({
|
ziya::error::app::AppError::Slint(format!("failed_to_run_event_loop: {}", e))
|
||||||
let ui_handle = ui_handle.clone();
|
})?;
|
||||||
move || {
|
|
||||||
println!("Theme toggle callback received from UI");
|
|
||||||
if let Some(ui) = ui_handle.upgrade() {
|
|
||||||
// Call the public theme toggle function directly
|
|
||||||
ui.invoke_toggle_theme();
|
|
||||||
println!("Theme toggle function invoked successfully");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Handle logout
|
Ok(())
|
||||||
ui.on_logout_clicked({
|
|
||||||
let _ui_handle = ui_handle.clone();
|
|
||||||
move || {
|
|
||||||
println!("Logout clicked");
|
|
||||||
// Implement logout logic here
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Handle buy action
|
|
||||||
ui.on_buy_clicked({
|
|
||||||
let _ui_handle = ui_handle.clone();
|
|
||||||
move || {
|
|
||||||
println!("Buy clicked");
|
|
||||||
// Implement buy logic here
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Handle sell action
|
|
||||||
ui.on_sell_clicked({
|
|
||||||
let _ui_handle = ui_handle.clone();
|
|
||||||
move || {
|
|
||||||
println!("Sell clicked");
|
|
||||||
// Implement sell logic here
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Window control handlers - simplified drag handling
|
|
||||||
ui.on_start_drag_window({
|
|
||||||
let ui_handle = ui_handle.clone();
|
|
||||||
move || {
|
|
||||||
if let Some(ui) = ui_handle.upgrade() {
|
|
||||||
let window = ui.window();
|
|
||||||
window.with_winit_window(|winit_window| {
|
|
||||||
if !window.is_maximized() {
|
|
||||||
let _ = winit_window.drag_window();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ui.on_minimize_window({
|
|
||||||
let ui_handle = ui_handle.clone();
|
|
||||||
move || {
|
|
||||||
if let Some(ui) = ui_handle.upgrade() {
|
|
||||||
let _ = ui.window().set_minimized(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ui.on_maximize_window({
|
|
||||||
let ui_handle = ui_handle.clone();
|
|
||||||
move || {
|
|
||||||
if let Some(ui) = ui_handle.upgrade() {
|
|
||||||
let window = ui.window();
|
|
||||||
let _ = window.set_maximized(!window.is_maximized());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ui.on_close_window({
|
|
||||||
let ui_handle = ui_handle.clone();
|
|
||||||
move || {
|
|
||||||
if let Some(ui) = ui_handle.upgrade() {
|
|
||||||
let _ = ui.window().hide();
|
|
||||||
let _ = slint::quit_event_loop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Set initial state
|
|
||||||
ui.set_current_page("Dashboard".into());
|
|
||||||
ui.set_user_initials("JD".into());
|
|
||||||
|
|
||||||
// Run the application
|
|
||||||
ui.run()
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
612
src/model/cex.rs
Normal file
612
src/model/cex.rs
Normal file
|
|
@ -0,0 +1,612 @@
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use serde::Deserialize;
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub struct Cex {
|
||||||
|
pub name: CexName,
|
||||||
|
pub address: solana_pubkey::Pubkey,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Cex {
|
||||||
|
pub fn new(name: CexName, address: solana_pubkey::Pubkey) -> Self {
|
||||||
|
Self { name, address }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_exchange_name(address: solana_pubkey::Pubkey) -> Option<CexName> {
|
||||||
|
match address.to_string().as_str() {
|
||||||
|
"FpwQQhQQoEaVu3WU2qZMfF1hx48YyfwsLoRgXG83E99Q" => Some(CexName::CoinbaseHW1),
|
||||||
|
"GJRs4FwHtemZ5ZE9x3FNvJ8TMwitKTh21yxdRPqn7npE" => Some(CexName::CoinbaseHW2),
|
||||||
|
"D89hHJT5Aqyx1trP6EnGY9jJUB3whgnq3aUvvCqedvzf" => Some(CexName::CoinbaseHW3),
|
||||||
|
"DPqsobysNf5iA9w7zrQM8HLzCKZEDMkZsWbiidsAt1xo" => Some(CexName::CoinbaseHW4),
|
||||||
|
"H8sMJSCQxfKiFTCfDR3DUMLPwcRbM61LGFJ8N4dK3WjS" => Some(CexName::Coinbase1),
|
||||||
|
"2AQdpHJ2JpcEgPiATUXjQxA8QmafFegfQwSLWSprPicm" => Some(CexName::Coinbase2),
|
||||||
|
"59L2oxymiQQ9Hvhh92nt8Y7nDYjsauFkdb3SybdnsG6h" => Some(CexName::Coinbase4),
|
||||||
|
"9obNtb5GyUegcs3a1CbBkLuc5hEWynWfJC6gjz5uWQkE" => Some(CexName::Coinbase5),
|
||||||
|
"3vxheE5C46XzK4XftziRhwAf8QAfipD7HXXWj25mgkom" => Some(CexName::CoinbasePrime),
|
||||||
|
"CKy3KzEMSL1PQV6Wppggoqi2nGA7teE4L7JipEK89yqj" => Some(CexName::CoinbaseCW1),
|
||||||
|
"G6zmnfSdG6QJaDWYwbGQ4dpCSUC4gvjfZxYQ4ZharV7C" => Some(CexName::CoinbaseCW2),
|
||||||
|
"VTvk7sG6QQ28iK3NEKRRD9fvPzk5pKpJL2iwgVqMFcL" => Some(CexName::CoinbaseCW3),
|
||||||
|
"85cPov8nuRCkJ88VNMcHaHZ26Ux85PbSrHW4jg7izW4h" => Some(CexName::CoinbaseCW4),
|
||||||
|
"D6gCBB3CZEMNbX1PDr3GtZAMhnebEumcgJ2yv8Etv5hF" => Some(CexName::CoinbaseCW5),
|
||||||
|
"3qP77PzrHxSrW1S8dH4Ss1dmpJDHpC6ATVgwy5FmXDEf" => Some(CexName::CoinbaseCW6),
|
||||||
|
"146yGthSmnTPuCo6Zfbmr56YbAyWZ3rzAhRcT7tTF5ha" => Some(CexName::CoinbaseCW7),
|
||||||
|
"GXTrXayxMJUujsRTxYjAbkdbNvs6u2KN89UpG8f6eMAg" => Some(CexName::CoinbaseCW8),
|
||||||
|
"AzAvbCQsXurd2PbGLYcB61tyvE8kLDaZShE1S5Bp3WeS" => Some(CexName::CoinbaseCW9),
|
||||||
|
"4pHKEisSmAr5CSump4dJnTJgG6eugmtieXcUxDBcQcG5" => Some(CexName::CoinbaseCW10),
|
||||||
|
"BmGyWBMEcjJD7JQD1jRJ5vEt7XX2LyVvtxwtTGV4N1bp" => Some(CexName::CoinbaseCW11),
|
||||||
|
"py5jDEUAynTufQHM7P6Tu9M8NUd8JYux7aMcLXcC51q" => Some(CexName::CoinbaseCW12),
|
||||||
|
"is6MTRHEgyFLNTfYcuV4QBWLjrZBfmhVNYR6ccgr8KV" => Some(CexName::OKXHW1),
|
||||||
|
"C68a6RCGLiPskbPYtAcsCjhG8tfTWYcoB4JjCrXFdqyo" => Some(CexName::OKXHW2),
|
||||||
|
"5VCwKtCXgCJ6kit5FybXjvriW3xELsFDhYrPSqtJNmcD" => Some(CexName::OKX),
|
||||||
|
"9un5wqE3q4oCjyrDkwsdD48KteCJitQX5978Vh7KKxHo" => Some(CexName::OKX2),
|
||||||
|
"ASTyfSima4LLAdDgoFGkgqoKowG1LZFDr9fAQrg7iaJZ" => Some(CexName::MEXC1),
|
||||||
|
"5PAhQiYdLBd6SVdjzBQDxUAEFyDdF5ExNPQfcscnPRj5" => Some(CexName::MEXC2),
|
||||||
|
"FWznbcNXWQuHTawe9RxvQ2LdCENssh12dsznf4RiouN5" => Some(CexName::Kraken),
|
||||||
|
"9cNE6KBg2Xmf34FPMMvzDF8yUHMrgLRzBV3vD7b1JnUS" => Some(CexName::KrakenCW),
|
||||||
|
"F7RkX6Y1qTfBqoX5oHoZEgrG1Dpy55UZ3GfWwPbM58nQ" => Some(CexName::KrakenCW2),
|
||||||
|
"3yFwqXBfZY4jBVUafQ1YEXw189y2dN3V5KQq9uzBDy1E" => Some(CexName::Binance8),
|
||||||
|
"2ojv9BAiHUrvsm9gxDe7fJSzbNZSJcxZvf8dqmWGHG8S" => Some(CexName::Binance1),
|
||||||
|
"5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" => Some(CexName::Binance2),
|
||||||
|
"9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM" => Some(CexName::Binance3),
|
||||||
|
"53unSgGWqEWANcPYRF35B2Bgf8BkszUtcccKiXwGGLyr" => Some(CexName::BinanceUSHW),
|
||||||
|
"3gd3dqgtJ4jWfBfLYTX67DALFetjc5iS72sCgRhCkW2u" => Some(CexName::Binance10),
|
||||||
|
"6QJzieMYfp7yr3EdrePaQoG3Ghxs2wM98xSLRu8Xh56U" => Some(CexName::Binance11),
|
||||||
|
"GBrURzmtWujJRTA3Bkvo7ZgWuZYLMMwPCwre7BejJXnK" => Some(CexName::BinanceCW),
|
||||||
|
"4S8C1yrRZmJYPzCqzEVjZYf6qCYWFoF7hWLRzssTCotX" => Some(CexName::BitgetCW),
|
||||||
|
"A77HErqtfN1hLLpvZ9pCtu66FEtM8BveoaKbbMoZ4RiR" => Some(CexName::BitgetExchange),
|
||||||
|
"u6PJ8DtQuPFnfmwHbGFULQ4u4EgjDiyYKjVEsynXq2w" => Some(CexName::Gateio1),
|
||||||
|
"HiRpdAZifEsZGdzQ5Xo5wcnaH3D2Jj9SoNsUzcYNK78J" => Some(CexName::Gateio2),
|
||||||
|
"AC5RDfQFmDS1deWZos921JfqscXdByf8BKHs5ACWjtW2" => Some(CexName::BybitHW),
|
||||||
|
"42brAgAVNzMBP7aaktPvAmBSPEkehnFQejiZc53EpJFd" => Some(CexName::BybitCW),
|
||||||
|
"FxteHmLwG9nk1eL4pjNve3Eub2goGkkz6g6TbvdmW46a" => Some(CexName::BitfinexHW),
|
||||||
|
"FyJBKcfcEBzGN74uNxZ95GxnCxeuJJujQCELpPv14ZfN" => Some(CexName::BitfinexCW),
|
||||||
|
"57vSaRTqN9iXaemgh4AoDsZ63mcaoshfMK8NP3Z5QNbs" => Some(CexName::KuCoin1),
|
||||||
|
"BmFdpraQhkiDQE6SnfG5omcA1VwzqfXrwtNYBwWTymy6" => Some(CexName::KuCoin2),
|
||||||
|
"HVh6wHNBAsG3pq1Bj5oCzRjoWKVogEDHwUHkRz3ekFgt" => Some(CexName::KuCoin3),
|
||||||
|
"DBmae92YTQKLsNzXcPscxiwPqMcz9stQr2prB5ZCAHPd" => Some(CexName::KuCoinCW),
|
||||||
|
"7Ci23i82UMa8RpfVbdMjTytiDi2VoZS8uLyHhZBV2Qy7" => Some(CexName::PoloniexHW),
|
||||||
|
"8s9j5qUtuE9PGA5s7QeAXEh5oc2UGr71pmJXgyiZMHkt" => Some(CexName::LBank),
|
||||||
|
"G9X7F4JzLzbSGMCndiBdWNi5YzZZakmtkdwq7xS3Q3FE" => Some(CexName::StakecomHotWallet),
|
||||||
|
"2snHHreXbpJ7UwZxPe37gnUNf7Wx7wv6UKDSR2JckKuS" => Some(CexName::DeBridgeVault),
|
||||||
|
"Biw4eeaiYYYq6xSqEd7GzdwsrrndxA8mqdxfAtG3PTUU" => Some(CexName::RevolutHotWallet),
|
||||||
|
"HBxZShcE86UMmF93KUM8eWJKqeEXi5cqWCLYLMMhqMYm" => Some(CexName::BitStampHotWallet),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_exchange_address(name: CexName) -> Option<solana_pubkey::Pubkey> {
|
||||||
|
match name {
|
||||||
|
CexName::CoinbaseHW1 => Some(
|
||||||
|
solana_pubkey::Pubkey::from_str("FpwQQhQQoEaVu3WU2qZMfF1hx48YyfwsLoRgXG83E99Q")
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
CexName::CoinbaseHW2 => Some(
|
||||||
|
solana_pubkey::Pubkey::from_str("GJRs4FwHtemZ5ZE9x3FNvJ8TMwitKTh21yxdRPqn7npE")
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
CexName::CoinbaseHW3 => Some(
|
||||||
|
solana_pubkey::Pubkey::from_str("D89hHJT5Aqyx1trP6EnGY9jJUB3whgnq3aUvvCqedvzf")
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
CexName::CoinbaseHW4 => Some(
|
||||||
|
solana_pubkey::Pubkey::from_str("DPqsobysNf5iA9w7zrQM8HLzCKZEDMkZsWbiidsAt1xo")
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
CexName::Coinbase1 => Some(
|
||||||
|
solana_pubkey::Pubkey::from_str("H8sMJSCQxfKiFTCfDR3DUMLPwcRbM61LGFJ8N4dK3WjS")
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
CexName::Coinbase2 => Some(
|
||||||
|
solana_pubkey::Pubkey::from_str("2AQdpHJ2JpcEgPiATUXjQxA8QmafFegfQwSLWSprPicm")
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
CexName::Coinbase4 => Some(
|
||||||
|
solana_pubkey::Pubkey::from_str("59L2oxymiQQ9Hvhh92nt8Y7nDYjsauFkdb3SybdnsG6h")
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
CexName::Coinbase5 => Some(
|
||||||
|
solana_pubkey::Pubkey::from_str("9obNtb5GyUegcs3a1CbBkLuc5hEWynWfJC6gjz5uWQkE")
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
CexName::CoinbasePrime => Some(
|
||||||
|
solana_pubkey::Pubkey::from_str("3vxheE5C46XzK4XftziRhwAf8QAfipD7HXXWj25mgkom")
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
CexName::CoinbaseCW1 => Some(
|
||||||
|
solana_pubkey::Pubkey::from_str("CKy3KzEMSL1PQV6Wppggoqi2nGA7teE4L7JipEK89yqj")
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
CexName::CoinbaseCW2 => Some(
|
||||||
|
solana_pubkey::Pubkey::from_str("G6zmnfSdG6QJaDWYwbGQ4dpCSUC4gvjfZxYQ4ZharV7C")
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
CexName::CoinbaseCW3 => Some(
|
||||||
|
solana_pubkey::Pubkey::from_str("VTvk7sG6QQ28iK3NEKRRD9fvPzk5pKpJL2iwgVqMFcL")
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
CexName::CoinbaseCW4 => Some(
|
||||||
|
solana_pubkey::Pubkey::from_str("85cPov8nuRCkJ88VNMcHaHZ26Ux85PbSrHW4jg7izW4h")
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
CexName::CoinbaseCW5 => Some(
|
||||||
|
solana_pubkey::Pubkey::from_str("D6gCBB3CZEMNbX1PDr3GtZAMhnebEumcgJ2yv8Etv5hF")
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
CexName::CoinbaseCW6 => Some(
|
||||||
|
solana_pubkey::Pubkey::from_str("3qP77PzrHxSrW1S8dH4Ss1dmpJDHpC6ATVgwy5FmXDEf")
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
CexName::CoinbaseCW7 => Some(
|
||||||
|
solana_pubkey::Pubkey::from_str("146yGthSmnTPuCo6Zfbmr56YbAyWZ3rzAhRcT7tTF5ha")
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
CexName::CoinbaseCW8 => Some(
|
||||||
|
solana_pubkey::Pubkey::from_str("GXTrXayxMJUujsRTxYjAbkdbNvs6u2KN89UpG8f6eMAg")
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
CexName::CoinbaseCW9 => Some(
|
||||||
|
solana_pubkey::Pubkey::from_str("AzAvbCQsXurd2PbGLYcB61tyvE8kLDaZShE1S5Bp3WeS")
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
CexName::CoinbaseCW10 => Some(
|
||||||
|
solana_pubkey::Pubkey::from_str("4pHKEisSmAr5CSump4dJnTJgG6eugmtieXcUxDBcQcG5")
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
CexName::CoinbaseCW11 => Some(
|
||||||
|
solana_pubkey::Pubkey::from_str("BmGyWBMEcjJD7JQD1jRJ5vEt7XX2LyVvtxwtTGV4N1bp")
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
CexName::CoinbaseCW12 => Some(
|
||||||
|
solana_pubkey::Pubkey::from_str("py5jDEUAynTufQHM7P6Tu9M8NUd8JYux7aMcLXcC51q")
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
CexName::OKXHW1 => Some(
|
||||||
|
solana_pubkey::Pubkey::from_str("is6MTRHEgyFLNTfYcuV4QBWLjrZBfmhVNYR6ccgr8KV")
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
CexName::OKXHW2 => Some(
|
||||||
|
solana_pubkey::Pubkey::from_str("C68a6RCGLiPskbPYtAcsCjhG8tfTWYcoB4JjCrXFdqyo")
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
CexName::OKX => Some(
|
||||||
|
solana_pubkey::Pubkey::from_str("5VCwKtCXgCJ6kit5FybXjvriW3xELsFDhYrPSqtJNmcD")
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
CexName::OKX2 => Some(
|
||||||
|
solana_pubkey::Pubkey::from_str("9un5wqE3q4oCjyrDkwsdD48KteCJitQX5978Vh7KKxHo")
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
CexName::MEXC1 => Some(
|
||||||
|
solana_pubkey::Pubkey::from_str("ASTyfSima4LLAdDgoFGkgqoKowG1LZFDr9fAQrg7iaJZ")
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
CexName::MEXC2 => Some(
|
||||||
|
solana_pubkey::Pubkey::from_str("5PAhQiYdLBd6SVdjzBQDxUAEFyDdF5ExNPQfcscnPRj5")
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
CexName::Kraken => Some(
|
||||||
|
solana_pubkey::Pubkey::from_str("FWznbcNXWQuHTawe9RxvQ2LdCENssh12dsznf4RiouN5")
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
CexName::KrakenCW => Some(
|
||||||
|
solana_pubkey::Pubkey::from_str("9cNE6KBg2Xmf34FPMMvzDF8yUHMrgLRzBV3vD7b1JnUS")
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
CexName::KrakenCW2 => Some(
|
||||||
|
solana_pubkey::Pubkey::from_str("F7RkX6Y1qTfBqoX5oHoZEgrG1Dpy55UZ3GfWwPbM58nQ")
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
CexName::Binance8 => Some(
|
||||||
|
solana_pubkey::Pubkey::from_str("3yFwqXBfZY4jBVUafQ1YEXw189y2dN3V5KQq9uzBDy1E")
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
CexName::Binance1 => Some(
|
||||||
|
solana_pubkey::Pubkey::from_str("2ojv9BAiHUrvsm9gxDe7fJSzbNZSJcxZvf8dqmWGHG8S")
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
CexName::Binance2 => Some(
|
||||||
|
solana_pubkey::Pubkey::from_str("5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9")
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
CexName::Binance3 => Some(
|
||||||
|
solana_pubkey::Pubkey::from_str("9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM")
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
CexName::BinanceUSHW => Some(
|
||||||
|
solana_pubkey::Pubkey::from_str("53unSgGWqEWANcPYRF35B2Bgf8BkszUtcccKiXwGGLyr")
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
CexName::Binance10 => Some(
|
||||||
|
solana_pubkey::Pubkey::from_str("3gd3dqgtJ4jWfBfLYTX67DALFetjc5iS72sCgRhCkW2u")
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
CexName::Binance11 => Some(
|
||||||
|
solana_pubkey::Pubkey::from_str("6QJzieMYfp7yr3EdrePaQoG3Ghxs2wM98xSLRu8Xh56U")
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
CexName::BinanceCW => Some(
|
||||||
|
solana_pubkey::Pubkey::from_str("GBrURzmtWujJRTA3Bkvo7ZgWuZYLMMwPCwre7BejJXnK")
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
CexName::BitgetCW => Some(
|
||||||
|
solana_pubkey::Pubkey::from_str("4S8C1yrRZmJYPzCqzEVjZYf6qCYWFoF7hWLRzssTCotX")
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
CexName::BitgetExchange => Some(
|
||||||
|
solana_pubkey::Pubkey::from_str("A77HErqtfN1hLLpvZ9pCtu66FEtM8BveoaKbbMoZ4RiR")
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
CexName::Gateio1 => Some(
|
||||||
|
solana_pubkey::Pubkey::from_str("u6PJ8DtQuPFnfmwHbGFULQ4u4EgjDiyYKjVEsynXq2w")
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
CexName::Gateio2 => Some(
|
||||||
|
solana_pubkey::Pubkey::from_str("HiRpdAZifEsZGdzQ5Xo5wcnaH3D2Jj9SoNsUzcYNK78J")
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
CexName::BybitHW => Some(
|
||||||
|
solana_pubkey::Pubkey::from_str("AC5RDfQFmDS1deWZos921JfqscXdByf8BKHs5ACWjtW2")
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
CexName::BybitCW => Some(
|
||||||
|
solana_pubkey::Pubkey::from_str("42brAgAVNzMBP7aaktPvAmBSPEkehnFQejiZc53EpJFd")
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
CexName::BitfinexHW => Some(
|
||||||
|
solana_pubkey::Pubkey::from_str("FxteHmLwG9nk1eL4pjNve3Eub2goGkkz6g6TbvdmW46a")
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
CexName::BitfinexCW => Some(
|
||||||
|
solana_pubkey::Pubkey::from_str("FyJBKcfcEBzGN74uNxZ95GxnCxeuJJujQCELpPv14ZfN")
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
CexName::KuCoin1 => Some(
|
||||||
|
solana_pubkey::Pubkey::from_str("57vSaRTqN9iXaemgh4AoDsZ63mcaoshfMK8NP3Z5QNbs")
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
CexName::KuCoin2 => Some(
|
||||||
|
solana_pubkey::Pubkey::from_str("BmFdpraQhkiDQE6SnfG5omcA1VwzqfXrwtNYBwWTymy6")
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
CexName::KuCoin3 => Some(
|
||||||
|
solana_pubkey::Pubkey::from_str("HVh6wHNBAsG3pq1Bj5oCzRjoWKVogEDHwUHkRz3ekFgt")
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
CexName::KuCoinCW => Some(
|
||||||
|
solana_pubkey::Pubkey::from_str("DBmae92YTQKLsNzXcPscxiwPqMcz9stQr2prB5ZCAHPd")
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
CexName::PoloniexHW => Some(
|
||||||
|
solana_pubkey::Pubkey::from_str("7Ci23i82UMa8RpfVbdMjTytiDi2VoZS8uLyHhZBV2Qy7")
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
CexName::LBank => Some(
|
||||||
|
solana_pubkey::Pubkey::from_str("8s9j5qUtuE9PGA5s7QeAXEh5oc2UGr71pmJXgyiZMHkt")
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
CexName::StakecomHotWallet => Some(
|
||||||
|
solana_pubkey::Pubkey::from_str("G9X7F4JzLzbSGMCndiBdWNi5YzZZakmtkdwq7xS3Q3FE")
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
CexName::DeBridgeVault => Some(
|
||||||
|
solana_pubkey::Pubkey::from_str("2snHHreXbpJ7UwZxPe37gnUNf7Wx7wv6UKDSR2JckKuS")
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
CexName::RevolutHotWallet => Some(
|
||||||
|
solana_pubkey::Pubkey::from_str("Biw4eeaiYYYq6xSqEd7GzdwsrrndxA8mqdxfAtG3PTUU")
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
CexName::BitStampHotWallet => Some(
|
||||||
|
solana_pubkey::Pubkey::from_str("HBxZShcE86UMmF93KUM8eWJKqeEXi5cqWCLYLMMhqMYm")
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub enum CexName {
|
||||||
|
#[serde(rename = "coinbase_hw1")]
|
||||||
|
CoinbaseHW1,
|
||||||
|
#[serde(rename = "coinbase_hw2")]
|
||||||
|
CoinbaseHW2,
|
||||||
|
#[serde(rename = "coinbase_hw3")]
|
||||||
|
CoinbaseHW3,
|
||||||
|
#[serde(rename = "coinbase_hw4")]
|
||||||
|
CoinbaseHW4,
|
||||||
|
#[serde(rename = "coinbase_1")]
|
||||||
|
Coinbase1,
|
||||||
|
#[serde(rename = "coinbase_2")]
|
||||||
|
Coinbase2,
|
||||||
|
#[serde(rename = "coinbase_4")]
|
||||||
|
Coinbase4,
|
||||||
|
#[serde(rename = "coinbase_5")]
|
||||||
|
Coinbase5,
|
||||||
|
#[serde(rename = "coinbase_prime")]
|
||||||
|
CoinbasePrime,
|
||||||
|
#[serde(rename = "coinbase_cw1")]
|
||||||
|
CoinbaseCW1,
|
||||||
|
#[serde(rename = "coinbase_cw2")]
|
||||||
|
CoinbaseCW2,
|
||||||
|
#[serde(rename = "coinbase_cw3")]
|
||||||
|
CoinbaseCW3,
|
||||||
|
#[serde(rename = "coinbase_cw4")]
|
||||||
|
CoinbaseCW4,
|
||||||
|
#[serde(rename = "coinbase_cw5")]
|
||||||
|
CoinbaseCW5,
|
||||||
|
#[serde(rename = "coinbase_cw6")]
|
||||||
|
CoinbaseCW6,
|
||||||
|
#[serde(rename = "coinbase_cw7")]
|
||||||
|
CoinbaseCW7,
|
||||||
|
#[serde(rename = "coinbase_cw8")]
|
||||||
|
CoinbaseCW8,
|
||||||
|
#[serde(rename = "coinbase_cw9")]
|
||||||
|
CoinbaseCW9,
|
||||||
|
#[serde(rename = "coinbase_cw10")]
|
||||||
|
CoinbaseCW10,
|
||||||
|
#[serde(rename = "coinbase_cw11")]
|
||||||
|
CoinbaseCW11,
|
||||||
|
#[serde(rename = "coinbase_cw12")]
|
||||||
|
CoinbaseCW12,
|
||||||
|
#[serde(rename = "okx_hw1")]
|
||||||
|
OKXHW1,
|
||||||
|
#[serde(rename = "okx_hw2")]
|
||||||
|
OKXHW2,
|
||||||
|
#[serde(rename = "okx")]
|
||||||
|
OKX,
|
||||||
|
#[serde(rename = "okx_2")]
|
||||||
|
OKX2,
|
||||||
|
#[serde(rename = "mexc_1")]
|
||||||
|
MEXC1,
|
||||||
|
#[serde(rename = "mexc_2")]
|
||||||
|
MEXC2,
|
||||||
|
#[serde(rename = "kraken")]
|
||||||
|
Kraken,
|
||||||
|
#[serde(rename = "kraken_cw")]
|
||||||
|
KrakenCW,
|
||||||
|
#[serde(rename = "kraken_cw2")]
|
||||||
|
KrakenCW2,
|
||||||
|
#[serde(rename = "binance_8")]
|
||||||
|
Binance8,
|
||||||
|
#[serde(rename = "binance_1")]
|
||||||
|
Binance1,
|
||||||
|
#[serde(rename = "binance_2")]
|
||||||
|
Binance2,
|
||||||
|
#[serde(rename = "binance_3")]
|
||||||
|
Binance3,
|
||||||
|
#[serde(rename = "binance_us_hw")]
|
||||||
|
BinanceUSHW,
|
||||||
|
#[serde(rename = "binance_10")]
|
||||||
|
Binance10,
|
||||||
|
#[serde(rename = "binance_11")]
|
||||||
|
Binance11,
|
||||||
|
#[serde(rename = "binance_cw")]
|
||||||
|
BinanceCW,
|
||||||
|
#[serde(rename = "bitget_cw")]
|
||||||
|
BitgetCW,
|
||||||
|
#[serde(rename = "bitget_exchange")]
|
||||||
|
BitgetExchange,
|
||||||
|
#[serde(rename = "gateio_1")]
|
||||||
|
Gateio1,
|
||||||
|
#[serde(rename = "gateio_2")]
|
||||||
|
Gateio2,
|
||||||
|
#[serde(rename = "bybit_hw")]
|
||||||
|
BybitHW,
|
||||||
|
#[serde(rename = "bybit_cw")]
|
||||||
|
BybitCW,
|
||||||
|
#[serde(rename = "bitfinex_hw")]
|
||||||
|
BitfinexHW,
|
||||||
|
#[serde(rename = "bitfinex_cw")]
|
||||||
|
BitfinexCW,
|
||||||
|
#[serde(rename = "kucoin_1")]
|
||||||
|
KuCoin1,
|
||||||
|
#[serde(rename = "kucoin_2")]
|
||||||
|
KuCoin2,
|
||||||
|
#[serde(rename = "kucoin_3")]
|
||||||
|
KuCoin3,
|
||||||
|
#[serde(rename = "kucoin_cw")]
|
||||||
|
KuCoinCW,
|
||||||
|
#[serde(rename = "poloniex_hw")]
|
||||||
|
PoloniexHW,
|
||||||
|
#[serde(rename = "lbank")]
|
||||||
|
LBank,
|
||||||
|
#[serde(rename = "stakecom_hot_wallet")]
|
||||||
|
StakecomHotWallet,
|
||||||
|
#[serde(rename = "debridge_vault")]
|
||||||
|
DeBridgeVault,
|
||||||
|
#[serde(rename = "revolut_hot_wallet")]
|
||||||
|
RevolutHotWallet,
|
||||||
|
#[serde(rename = "bitstamp_hot_wallet")]
|
||||||
|
BitStampHotWallet,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for CexName {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
CexName::CoinbaseHW1 => write!(f, "coinbase_hw1"),
|
||||||
|
CexName::CoinbaseHW2 => write!(f, "coinbase_hw2"),
|
||||||
|
CexName::CoinbaseHW3 => write!(f, "coinbase_hw3"),
|
||||||
|
CexName::CoinbaseHW4 => write!(f, "coinbase_hw4"),
|
||||||
|
CexName::Coinbase1 => write!(f, "coinbase_1"),
|
||||||
|
CexName::Coinbase2 => write!(f, "coinbase_2"),
|
||||||
|
CexName::Coinbase4 => write!(f, "coinbase_4"),
|
||||||
|
CexName::Coinbase5 => write!(f, "coinbase_5"),
|
||||||
|
CexName::CoinbasePrime => write!(f, "coinbase_prime"),
|
||||||
|
CexName::CoinbaseCW1 => write!(f, "coinbase_cw1"),
|
||||||
|
CexName::CoinbaseCW2 => write!(f, "coinbase_cw2"),
|
||||||
|
CexName::CoinbaseCW3 => write!(f, "coinbase_cw3"),
|
||||||
|
CexName::CoinbaseCW4 => write!(f, "coinbase_cw4"),
|
||||||
|
CexName::CoinbaseCW5 => write!(f, "coinbase_cw5"),
|
||||||
|
CexName::CoinbaseCW6 => write!(f, "coinbase_cw6"),
|
||||||
|
CexName::CoinbaseCW7 => write!(f, "coinbase_cw7"),
|
||||||
|
CexName::CoinbaseCW8 => write!(f, "coinbase_cw8"),
|
||||||
|
CexName::CoinbaseCW9 => write!(f, "coinbase_cw9"),
|
||||||
|
CexName::CoinbaseCW10 => write!(f, "coinbase_cw10"),
|
||||||
|
CexName::CoinbaseCW11 => write!(f, "coinbase_cw11"),
|
||||||
|
CexName::CoinbaseCW12 => write!(f, "coinbase_cw12"),
|
||||||
|
CexName::OKXHW1 => write!(f, "okx_hw1"),
|
||||||
|
CexName::OKXHW2 => write!(f, "okx_hw2"),
|
||||||
|
CexName::OKX => write!(f, "okx"),
|
||||||
|
CexName::OKX2 => write!(f, "okx_2"),
|
||||||
|
CexName::MEXC1 => write!(f, "mexc_1"),
|
||||||
|
CexName::MEXC2 => write!(f, "mexc_2"),
|
||||||
|
CexName::Kraken => write!(f, "kraken"),
|
||||||
|
CexName::KrakenCW => write!(f, "kraken_cw"),
|
||||||
|
CexName::KrakenCW2 => write!(f, "kraken_cw2"),
|
||||||
|
CexName::Binance8 => write!(f, "binance_8"),
|
||||||
|
CexName::Binance1 => write!(f, "binance_1"),
|
||||||
|
CexName::Binance2 => write!(f, "binance_2"),
|
||||||
|
CexName::Binance3 => write!(f, "binance_3"),
|
||||||
|
CexName::BinanceUSHW => write!(f, "binance_us_hw"),
|
||||||
|
CexName::Binance10 => write!(f, "binance_10"),
|
||||||
|
CexName::Binance11 => write!(f, "binance_11"),
|
||||||
|
CexName::BinanceCW => write!(f, "binance_cw"),
|
||||||
|
CexName::BitgetCW => write!(f, "bitget_cw"),
|
||||||
|
CexName::BitgetExchange => write!(f, "bitget_exchange"),
|
||||||
|
CexName::Gateio1 => write!(f, "gateio_1"),
|
||||||
|
CexName::Gateio2 => write!(f, "gateio_2"),
|
||||||
|
CexName::BybitHW => write!(f, "bybit_hw"),
|
||||||
|
CexName::BybitCW => write!(f, "bybit_cw"),
|
||||||
|
CexName::BitfinexHW => write!(f, "bitfinex_hw"),
|
||||||
|
CexName::BitfinexCW => write!(f, "bitfinex_cw"),
|
||||||
|
CexName::KuCoin1 => write!(f, "kucoin_1"),
|
||||||
|
CexName::KuCoin2 => write!(f, "kucoin_2"),
|
||||||
|
CexName::KuCoin3 => write!(f, "kucoin_3"),
|
||||||
|
CexName::KuCoinCW => write!(f, "kucoin_cw"),
|
||||||
|
CexName::PoloniexHW => write!(f, "poloniex_hw"),
|
||||||
|
CexName::LBank => write!(f, "lbank"),
|
||||||
|
CexName::StakecomHotWallet => write!(f, "stakecom_hot_wallet"),
|
||||||
|
CexName::DeBridgeVault => write!(f, "debridge_vault"),
|
||||||
|
CexName::RevolutHotWallet => write!(f, "revolut_hot_wallet"),
|
||||||
|
CexName::BitStampHotWallet => write!(f, "bitstamp_hot_wallet"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<CexName> for String {
|
||||||
|
fn from(cex: CexName) -> Self {
|
||||||
|
match cex {
|
||||||
|
CexName::CoinbaseHW1 => "coinbase_hw1".to_string(),
|
||||||
|
CexName::CoinbaseHW2 => "coinbase_hw2".to_string(),
|
||||||
|
CexName::CoinbaseHW3 => "coinbase_hw3".to_string(),
|
||||||
|
CexName::CoinbaseHW4 => "coinbase_hw4".to_string(),
|
||||||
|
CexName::Coinbase1 => "coinbase_1".to_string(),
|
||||||
|
CexName::Coinbase2 => "coinbase_2".to_string(),
|
||||||
|
CexName::Coinbase4 => "coinbase_4".to_string(),
|
||||||
|
CexName::Coinbase5 => "coinbase_5".to_string(),
|
||||||
|
CexName::CoinbasePrime => "coinbase_prime".to_string(),
|
||||||
|
CexName::CoinbaseCW1 => "coinbase_cw1".to_string(),
|
||||||
|
CexName::CoinbaseCW2 => "coinbase_cw2".to_string(),
|
||||||
|
CexName::CoinbaseCW3 => "coinbase_cw3".to_string(),
|
||||||
|
CexName::CoinbaseCW4 => "coinbase_cw4".to_string(),
|
||||||
|
CexName::CoinbaseCW5 => "coinbase_cw5".to_string(),
|
||||||
|
CexName::CoinbaseCW6 => "coinbase_cw6".to_string(),
|
||||||
|
CexName::CoinbaseCW7 => "coinbase_cw7".to_string(),
|
||||||
|
CexName::CoinbaseCW8 => "coinbase_cw8".to_string(),
|
||||||
|
CexName::CoinbaseCW9 => "coinbase_cw9".to_string(),
|
||||||
|
CexName::CoinbaseCW10 => "coinbase_cw10".to_string(),
|
||||||
|
CexName::CoinbaseCW11 => "coinbase_cw11".to_string(),
|
||||||
|
CexName::CoinbaseCW12 => "coinbase_cw12".to_string(),
|
||||||
|
CexName::OKXHW1 => "okx_hw1".to_string(),
|
||||||
|
CexName::OKXHW2 => "okx_hw2".to_string(),
|
||||||
|
CexName::OKX => "okx".to_string(),
|
||||||
|
CexName::OKX2 => "okx_2".to_string(),
|
||||||
|
CexName::MEXC1 => "mexc_1".to_string(),
|
||||||
|
CexName::MEXC2 => "mexc_2".to_string(),
|
||||||
|
CexName::Kraken => "kraken".to_string(),
|
||||||
|
CexName::KrakenCW => "kraken_cw".to_string(),
|
||||||
|
CexName::KrakenCW2 => "kraken_cw2".to_string(),
|
||||||
|
CexName::Binance8 => "binance_8".to_string(),
|
||||||
|
CexName::Binance1 => "binance_1".to_string(),
|
||||||
|
CexName::Binance2 => "binance_2".to_string(),
|
||||||
|
CexName::Binance3 => "binance_3".to_string(),
|
||||||
|
CexName::BinanceUSHW => "binance_us_hw".to_string(),
|
||||||
|
CexName::Binance10 => "binance_10".to_string(),
|
||||||
|
CexName::Binance11 => "binance_11".to_string(),
|
||||||
|
CexName::BinanceCW => "binance_cw".to_string(),
|
||||||
|
CexName::BitgetCW => "bitget_cw".to_string(),
|
||||||
|
CexName::BitgetExchange => "bitget_exchange".to_string(),
|
||||||
|
CexName::Gateio1 => "gateio_1".to_string(),
|
||||||
|
CexName::Gateio2 => "gateio_2".to_string(),
|
||||||
|
CexName::BybitHW => "bybit_hw".to_string(),
|
||||||
|
CexName::BybitCW => "bybit_cw".to_string(),
|
||||||
|
CexName::BitfinexHW => "bitfinex_hw".to_string(),
|
||||||
|
CexName::BitfinexCW => "bitfinex_cw".to_string(),
|
||||||
|
CexName::KuCoin1 => "kucoin_1".to_string(),
|
||||||
|
CexName::KuCoin2 => "kucoin_2".to_string(),
|
||||||
|
CexName::KuCoin3 => "kucoin_3".to_string(),
|
||||||
|
CexName::KuCoinCW => "kucoin_cw".to_string(),
|
||||||
|
CexName::PoloniexHW => "poloniex_hw".to_string(),
|
||||||
|
CexName::LBank => "lbank".to_string(),
|
||||||
|
CexName::StakecomHotWallet => "stakecom_hot_wallet".to_string(),
|
||||||
|
CexName::DeBridgeVault => "debridge_vault".to_string(),
|
||||||
|
CexName::RevolutHotWallet => "revolut_hot_wallet".to_string(),
|
||||||
|
CexName::BitStampHotWallet => "bitstamp_hot_wallet".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CexName {
|
||||||
|
pub fn as_str(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
CexName::CoinbaseHW1 => "coinbase_hw1",
|
||||||
|
CexName::CoinbaseHW2 => "coinbase_hw2",
|
||||||
|
CexName::CoinbaseHW3 => "coinbase_hw3",
|
||||||
|
CexName::CoinbaseHW4 => "coinbase_hw4",
|
||||||
|
CexName::Coinbase1 => "coinbase_1",
|
||||||
|
CexName::Coinbase2 => "coinbase_2",
|
||||||
|
CexName::Coinbase4 => "coinbase_4",
|
||||||
|
CexName::Coinbase5 => "coinbase_5",
|
||||||
|
CexName::CoinbasePrime => "coinbase_prime",
|
||||||
|
CexName::CoinbaseCW1 => "coinbase_cw1",
|
||||||
|
CexName::CoinbaseCW2 => "coinbase_cw2",
|
||||||
|
CexName::CoinbaseCW3 => "coinbase_cw3",
|
||||||
|
CexName::CoinbaseCW4 => "coinbase_cw4",
|
||||||
|
CexName::CoinbaseCW5 => "coinbase_cw5",
|
||||||
|
CexName::CoinbaseCW6 => "coinbase_cw6",
|
||||||
|
CexName::CoinbaseCW7 => "coinbase_cw7",
|
||||||
|
CexName::CoinbaseCW8 => "coinbase_cw8",
|
||||||
|
CexName::CoinbaseCW9 => "coinbase_cw9",
|
||||||
|
CexName::CoinbaseCW10 => "coinbase_cw10",
|
||||||
|
CexName::CoinbaseCW11 => "coinbase_cw11",
|
||||||
|
CexName::CoinbaseCW12 => "coinbase_cw12",
|
||||||
|
CexName::OKXHW1 => "okx_hw1",
|
||||||
|
CexName::OKXHW2 => "okx_hw2",
|
||||||
|
CexName::OKX => "okx",
|
||||||
|
CexName::OKX2 => "okx_2",
|
||||||
|
CexName::MEXC1 => "mexc_1",
|
||||||
|
CexName::MEXC2 => "mexc_2",
|
||||||
|
CexName::Kraken => "kraken",
|
||||||
|
CexName::KrakenCW => "kraken_cw",
|
||||||
|
CexName::KrakenCW2 => "kraken_cw2",
|
||||||
|
CexName::Binance8 => "binance_8",
|
||||||
|
CexName::Binance1 => "binance_1",
|
||||||
|
CexName::Binance2 => "binance_2",
|
||||||
|
CexName::Binance3 => "binance_3",
|
||||||
|
CexName::BinanceUSHW => "binance_us_hw",
|
||||||
|
CexName::Binance10 => "binance_10",
|
||||||
|
CexName::Binance11 => "binance_11",
|
||||||
|
CexName::BinanceCW => "binance_cw",
|
||||||
|
CexName::BitgetCW => "bitget_cw",
|
||||||
|
CexName::BitgetExchange => "bitget_exchange",
|
||||||
|
CexName::Gateio1 => "gateio_1",
|
||||||
|
CexName::Gateio2 => "gateio_2",
|
||||||
|
CexName::BybitHW => "bybit_hw",
|
||||||
|
CexName::BybitCW => "bybit_cw",
|
||||||
|
CexName::BitfinexHW => "bitfinex_hw",
|
||||||
|
CexName::BitfinexCW => "bitfinex_cw",
|
||||||
|
CexName::KuCoin1 => "kucoin_1",
|
||||||
|
CexName::KuCoin2 => "kucoin_2",
|
||||||
|
CexName::KuCoin3 => "kucoin_3",
|
||||||
|
CexName::KuCoinCW => "kucoin_cw",
|
||||||
|
CexName::PoloniexHW => "poloniex_hw",
|
||||||
|
CexName::LBank => "lbank",
|
||||||
|
CexName::StakecomHotWallet => "stakecom_hot_wallet",
|
||||||
|
CexName::DeBridgeVault => "debridge_vault",
|
||||||
|
CexName::RevolutHotWallet => "revolut_hot_wallet",
|
||||||
|
CexName::BitStampHotWallet => "bitstamp_hot_wallet",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
121
src/model/graph.rs
Normal file
121
src/model/graph.rs
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use petgraph::prelude::*;
|
||||||
|
use petgraph::Graph;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use serde::Serialize;
|
||||||
|
use solana_pubkey::Pubkey;
|
||||||
|
use tokio::sync::RwLock;
|
||||||
|
|
||||||
|
use crate::model::cex::Cex;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct AddressNode {
|
||||||
|
pub address: solana_pubkey::Pubkey,
|
||||||
|
pub total_received: f64,
|
||||||
|
pub total_balance: f64,
|
||||||
|
pub is_cex: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct TransactionEdge {
|
||||||
|
pub from: solana_pubkey::Pubkey,
|
||||||
|
pub to: solana_pubkey::Pubkey,
|
||||||
|
pub amount: f64,
|
||||||
|
pub timestamp: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||||
|
pub struct CreatorConnectionGraph {
|
||||||
|
graph: Graph<AddressNode, TransactionEdge>,
|
||||||
|
#[serde(skip)]
|
||||||
|
node_indices: HashMap<Pubkey, NodeIndex>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CreatorConnectionGraph {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
graph: Graph::new(),
|
||||||
|
node_indices: HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rebuild the node_indices HashMap from the graph (useful after deserialization)
|
||||||
|
pub fn rebuild_indices(&mut self) {
|
||||||
|
self.node_indices.clear();
|
||||||
|
for node_index in self.graph.node_indices() {
|
||||||
|
if let Some(node) = self.graph.node_weight(node_index) {
|
||||||
|
self.node_indices.insert(node.address, node_index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure indices are available (rebuild if empty and graph has nodes)
|
||||||
|
fn ensure_indices(&mut self) {
|
||||||
|
if self.node_indices.is_empty() && self.graph.node_count() > 0 {
|
||||||
|
self.rebuild_indices();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_node_count(&self) -> usize {
|
||||||
|
self.graph.node_count()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_edge_count(&self) -> usize {
|
||||||
|
self.graph.edge_count()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all nodes in the graph
|
||||||
|
pub fn get_nodes(&self) -> Vec<AddressNode> {
|
||||||
|
self.graph.node_weights().map(|node| node.clone()).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all edges in the graph
|
||||||
|
pub fn get_edges(&self) -> Vec<TransactionEdge> {
|
||||||
|
self.graph.edge_weights().map(|edge| edge.clone()).collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Thread-safe wrapper for the graph
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct SharedCreatorConnectionGraph {
|
||||||
|
#[serde(skip)]
|
||||||
|
inner: Arc<RwLock<CreatorConnectionGraph>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SharedCreatorConnectionGraph {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
inner: Arc::new(RwLock::new(CreatorConnectionGraph::new())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_node_count(&self) -> usize {
|
||||||
|
self.inner.read().await.get_node_count()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_edge_count(&self) -> usize {
|
||||||
|
self.inner.read().await.get_edge_count()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn clone_graph(&self) -> CreatorConnectionGraph {
|
||||||
|
let mut graph = self.inner.read().await.clone();
|
||||||
|
// Ensure indices are rebuilt after cloning (since they're skipped in serialization)
|
||||||
|
graph.rebuild_indices();
|
||||||
|
graph
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method to ensure indices are available (useful after deserialization)
|
||||||
|
pub async fn ensure_indices(&self) {
|
||||||
|
self.inner.write().await.ensure_indices();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<CreatorConnectionGraph> for SharedCreatorConnectionGraph {
|
||||||
|
fn from(graph: CreatorConnectionGraph) -> Self {
|
||||||
|
Self {
|
||||||
|
inner: Arc::new(RwLock::new(graph)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
3
src/model/mod.rs
Normal file
3
src/model/mod.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
pub mod cex;
|
||||||
|
pub mod graph;
|
||||||
|
pub mod token;
|
||||||
48
src/model/token.rs
Normal file
48
src/model/token.rs
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
use super::graph::SharedCreatorConnectionGraph;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
// Type for new token created event (matches NewTokenCache from muhafidh)
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct NewTokenCreatedData {
|
||||||
|
pub mint: solana_pubkey::Pubkey,
|
||||||
|
pub bonding_curve: Option<solana_pubkey::Pubkey>,
|
||||||
|
pub name: String,
|
||||||
|
pub symbol: String,
|
||||||
|
pub uri: String,
|
||||||
|
pub creator: solana_pubkey::Pubkey,
|
||||||
|
pub created_at: u64, // Unix timestamp in seconds
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type for token CEX updated event (matches TokenAnalyzedCache from muhafidh)
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct TokenCexUpdatedData {
|
||||||
|
pub mint: String, // Mint address as string
|
||||||
|
pub name: String,
|
||||||
|
pub uri: String,
|
||||||
|
pub dev_name: String, // DevName from muhafidh
|
||||||
|
pub creator: String, // Creator address as string
|
||||||
|
pub cex_name: String,
|
||||||
|
pub cex_address: String,
|
||||||
|
pub bonding_curve: String, // Bonding curve address as string
|
||||||
|
pub created_at: u64, // Unix timestamp in seconds
|
||||||
|
pub updated_at: u64, // Unix timestamp in seconds
|
||||||
|
pub node_count: usize,
|
||||||
|
pub edge_count: usize,
|
||||||
|
pub graph: SharedCreatorConnectionGraph,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type for max depth reached event (same structure as TokenAnalyzedCache)
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct MaxDepthReachedData {
|
||||||
|
pub mint: String, // Mint address as string
|
||||||
|
pub name: String,
|
||||||
|
pub uri: String,
|
||||||
|
pub dev_name: String, // DevName from muhafidh
|
||||||
|
pub creator: String, // Creator address as string
|
||||||
|
pub bonding_curve: String, // Bonding curve address as string
|
||||||
|
pub created_at: u64, // Unix timestamp in seconds
|
||||||
|
pub updated_at: u64, // Unix timestamp in seconds
|
||||||
|
pub node_count: usize,
|
||||||
|
pub edge_count: usize,
|
||||||
|
pub graph: SharedCreatorConnectionGraph,
|
||||||
|
}
|
||||||
26
src/storage/mod.rs
Normal file
26
src/storage/mod.rs
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
pub mod redis;
|
||||||
|
use crate::config::Config;
|
||||||
|
use crate::error::Result;
|
||||||
|
use crate::storage::redis::RedisStorage;
|
||||||
|
use tracing::error;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct StorageEngine {
|
||||||
|
pub redis: Arc<RedisStorage>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StorageEngine {
|
||||||
|
pub async fn new(config: Config) -> Result<Self> {
|
||||||
|
let redis_storage = Arc::new(RedisStorage::new(&config.storage_redis).await.map_err(
|
||||||
|
|e| {
|
||||||
|
error!("failed_to_create_redis_storage: {}", e);
|
||||||
|
e
|
||||||
|
},
|
||||||
|
)?);
|
||||||
|
Ok(Self {
|
||||||
|
redis: redis_storage,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
73
src/storage/redis.rs
Normal file
73
src/storage/redis.rs
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
use bb8::Pool;
|
||||||
|
use bb8_redis::RedisConnectionManager;
|
||||||
|
use redis::aio::PubSub;
|
||||||
|
use tracing::{error, info, instrument};
|
||||||
|
|
||||||
|
use crate::config::StorageRedisConfig;
|
||||||
|
use crate::err_with_loc;
|
||||||
|
use crate::error::app::AppError;
|
||||||
|
use crate::error::Result;
|
||||||
|
|
||||||
|
pub type RedisPool = Pool<RedisConnectionManager>;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct RedisStorage {
|
||||||
|
pub pool: RedisPool,
|
||||||
|
redis_url: String, // Store the URL to create new connections
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RedisStorage {
|
||||||
|
#[instrument(level = "debug")]
|
||||||
|
pub async fn new(config: &StorageRedisConfig) -> Result<Self> {
|
||||||
|
let redis_url = format!("redis://{}:{}/?protocol=resp3", config.host, config.port);
|
||||||
|
|
||||||
|
let manager = RedisConnectionManager::new(redis_url.clone()).map_err(|e| {
|
||||||
|
error!("failed_to_create_redis_manager: {}", e);
|
||||||
|
err_with_loc!(AppError::Redis(format!(
|
||||||
|
"failed_to_create_redis_manager: {}",
|
||||||
|
e
|
||||||
|
)))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let pool = bb8::Pool::builder()
|
||||||
|
.max_size(config.pool_size)
|
||||||
|
.build(manager)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("failed_to_create_redis_pool: {}", e);
|
||||||
|
err_with_loc!(AppError::Redis(format!(
|
||||||
|
"failed_to_create_redis_pool: {}",
|
||||||
|
e
|
||||||
|
)))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
info!("redis::connection_established");
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
pool,
|
||||||
|
redis_url,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new independent PubSub connection for subscribers
|
||||||
|
/// Each subscriber should use its own connection to avoid blocking
|
||||||
|
pub async fn create_pubsub_connection(&self) -> Result<PubSub> {
|
||||||
|
let client = redis::Client::open(self.redis_url.clone()).map_err(|e| {
|
||||||
|
error!("failed_to_create_redis_client_for_pubsub: {}", e);
|
||||||
|
err_with_loc!(AppError::Redis(format!(
|
||||||
|
"failed_to_create_redis_client_for_pubsub: {}",
|
||||||
|
e
|
||||||
|
)))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let pubsub = client.get_async_pubsub().await.map_err(|e| {
|
||||||
|
error!("failed_to_get_new_pubsub_connection: {}", e);
|
||||||
|
err_with_loc!(AppError::Redis(format!(
|
||||||
|
"failed_to_get_new_pubsub_connection: {}",
|
||||||
|
e
|
||||||
|
)))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(pubsub)
|
||||||
|
}
|
||||||
|
}
|
||||||
3
src/task/mod.rs
Normal file
3
src/task/mod.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
pub mod shutdown;
|
||||||
|
pub mod subscriber;
|
||||||
|
pub mod ui;
|
||||||
33
src/task/shutdown.rs
Normal file
33
src/task/shutdown.rs
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
use std::sync::atomic::AtomicBool;
|
||||||
|
use std::sync::atomic::Ordering;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use tokio::sync::Notify;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ShutdownSignal {
|
||||||
|
pub signal: Arc<Notify>,
|
||||||
|
shutdown_triggered: Arc<AtomicBool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ShutdownSignal {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
signal: Arc::new(Notify::new()),
|
||||||
|
shutdown_triggered: Arc::new(AtomicBool::new(false)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn shutdown(&self) {
|
||||||
|
self.shutdown_triggered.store(true, Ordering::SeqCst);
|
||||||
|
self.signal.notify_waiters();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_shutdown(&self) -> bool {
|
||||||
|
self.shutdown_triggered.load(Ordering::SeqCst)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn wait_for_shutdown(&self) {
|
||||||
|
self.signal.notified().await;
|
||||||
|
}
|
||||||
|
}
|
||||||
186
src/task/subscriber.rs
Normal file
186
src/task/subscriber.rs
Normal file
|
|
@ -0,0 +1,186 @@
|
||||||
|
use crate::err_with_loc;
|
||||||
|
use crate::error::app::AppError;
|
||||||
|
use crate::error::Result;
|
||||||
|
use crate::handler::token::TokenMetadataHandlerOperator;
|
||||||
|
use crate::model::token::{MaxDepthReachedData, NewTokenCreatedData, TokenCexUpdatedData};
|
||||||
|
use crate::slint_ui::{MainWindow, NewTokenUiData};
|
||||||
|
use crate::storage::StorageEngine;
|
||||||
|
use futures_util::StreamExt;
|
||||||
|
use slint::Weak;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use tokio::sync::mpsc;
|
||||||
|
use tokio::task::JoinHandle;
|
||||||
|
use tokio_util::sync::CancellationToken;
|
||||||
|
use tracing::{debug, error, info, warn};
|
||||||
|
|
||||||
|
// ✅ Simplified subscriber that creates its own PubSub connection
|
||||||
|
pub fn spawn_new_token_subscriber(
|
||||||
|
token_handler: Arc<TokenMetadataHandlerOperator>,
|
||||||
|
db: Arc<StorageEngine>,
|
||||||
|
cancellation_token: CancellationToken,
|
||||||
|
) -> JoinHandle<Result<()>> {
|
||||||
|
tokio::spawn(async move {
|
||||||
|
debug!("new_token_subscriber::starting");
|
||||||
|
|
||||||
|
// ✅ Create dedicated PubSub connection for this subscriber
|
||||||
|
let mut pubsub = db.redis.create_pubsub_connection().await?;
|
||||||
|
|
||||||
|
if let Err(e) = pubsub.subscribe("new_token_created").await {
|
||||||
|
error!("failed_to_subscribe_to_new_token_created: {}", e);
|
||||||
|
return Err(err_with_loc!(AppError::Redis(format!(
|
||||||
|
"failed_to_subscribe_to_new_token_created: {}",
|
||||||
|
e
|
||||||
|
))));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a channel for buffering messages - with good capacity for performance
|
||||||
|
let (buffer_tx, mut buffer_rx) = mpsc::channel::<NewTokenCreatedData>(1000);
|
||||||
|
|
||||||
|
info!("new_token_subscriber::subscribed_successfully");
|
||||||
|
let mut msg_stream = pubsub.on_message();
|
||||||
|
let cancellation_token = cancellation_token.clone();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
tokio::select! {
|
||||||
|
Some(token) = buffer_rx.recv() => {
|
||||||
|
// ✅ Just call the existing handler - no duplication!
|
||||||
|
if let Err(e) = token_handler.process_new_token(token.clone()).await {
|
||||||
|
error!("failed_to_send_token_to_token_handler::mint::{}::error::{}", token.mint.clone(), e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Some(message) = msg_stream.next() => {
|
||||||
|
if let Ok(msg) = message.get_payload::<String>() {
|
||||||
|
if let Ok(token) = serde_json::from_str::<NewTokenCreatedData>(&msg) {
|
||||||
|
debug!("new_token_received::mint::{}::name::{}::creator::{}",
|
||||||
|
token.mint, token.name, token.creator);
|
||||||
|
if let Err(e) = buffer_tx.try_send(token.clone()) {
|
||||||
|
error!("failed_to_send_token_to_buffer::mint::{}::error::{}", token.mint, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ = cancellation_token.cancelled() => {
|
||||||
|
warn!("new_token_subscriber::shutdown_signal_received");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("new_token_subscriber::ended");
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn spawn_token_cex_updated_subscriber(
|
||||||
|
token_handler: Arc<TokenMetadataHandlerOperator>,
|
||||||
|
db: Arc<StorageEngine>,
|
||||||
|
cancellation_token: CancellationToken,
|
||||||
|
) -> JoinHandle<Result<()>> {
|
||||||
|
tokio::spawn(async move {
|
||||||
|
debug!("token_cex_updated_subscriber::starting");
|
||||||
|
|
||||||
|
// ✅ Create dedicated PubSub connection for this subscriber
|
||||||
|
let mut pubsub = db.redis.create_pubsub_connection().await?;
|
||||||
|
|
||||||
|
if let Err(e) = pubsub.subscribe("token_cex_updated").await {
|
||||||
|
error!("failed_to_subscribe_to_token_cex_updated: {}", e);
|
||||||
|
return Err(err_with_loc!(AppError::Redis(format!(
|
||||||
|
"failed_to_subscribe_to_token_cex_updated: {}",
|
||||||
|
e
|
||||||
|
))));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a channel for buffering messages - with good capacity for performance
|
||||||
|
let (buffer_tx, mut buffer_rx) = mpsc::channel::<TokenCexUpdatedData>(1000);
|
||||||
|
|
||||||
|
info!("token_cex_updated_subscriber::subscribed_successfully");
|
||||||
|
let mut msg_stream = pubsub.on_message();
|
||||||
|
let cancellation_token = cancellation_token.clone();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
tokio::select! {
|
||||||
|
Some(token) = buffer_rx.recv() => {
|
||||||
|
// ✅ Just call the existing handler - no duplication!
|
||||||
|
if let Err(e) = token_handler.process_cex_updated(token.clone()).await {
|
||||||
|
error!("failed_to_send_cex_updated_to_token_handler::mint::{}::error::{}", token.mint, e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Some(message) = msg_stream.next() => {
|
||||||
|
if let Ok(msg) = message.get_payload::<String>() {
|
||||||
|
if let Ok(token) = serde_json::from_str::<TokenCexUpdatedData>(&msg) {
|
||||||
|
debug!("token_cex_updated_received::mint::{}::name::{}::cex::{}",
|
||||||
|
token.mint, token.name, token.cex_name);
|
||||||
|
if let Err(e) = buffer_tx.try_send(token.clone()) {
|
||||||
|
error!("failed_to_send_cex_updated_to_buffer::mint::{}::error::{}", token.mint, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ = cancellation_token.cancelled() => {
|
||||||
|
warn!("token_cex_updated_subscriber::shutdown_signal_received");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("token_cex_updated_subscriber::ended");
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn spawn_max_depth_reached_subscriber(
|
||||||
|
token_handler: Arc<TokenMetadataHandlerOperator>,
|
||||||
|
db: Arc<StorageEngine>,
|
||||||
|
cancellation_token: CancellationToken,
|
||||||
|
) -> JoinHandle<Result<()>> {
|
||||||
|
tokio::spawn(async move {
|
||||||
|
debug!("max_depth_reached_subscriber::starting");
|
||||||
|
|
||||||
|
// ✅ Create dedicated PubSub connection for this subscriber
|
||||||
|
let mut pubsub = db.redis.create_pubsub_connection().await?;
|
||||||
|
|
||||||
|
if let Err(e) = pubsub.subscribe("max_depth_reached").await {
|
||||||
|
error!("failed_to_subscribe_to_max_depth_reached: {}", e);
|
||||||
|
return Err(err_with_loc!(AppError::Redis(format!(
|
||||||
|
"failed_to_subscribe_to_max_depth_reached: {}",
|
||||||
|
e
|
||||||
|
))));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a channel for buffering messages - with good capacity for performance
|
||||||
|
let (buffer_tx, mut buffer_rx) = mpsc::channel::<MaxDepthReachedData>(1000);
|
||||||
|
|
||||||
|
info!("max_depth_reached_subscriber::subscribed_successfully");
|
||||||
|
let mut msg_stream = pubsub.on_message();
|
||||||
|
let cancellation_token = cancellation_token.clone();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
tokio::select! {
|
||||||
|
Some(token) = buffer_rx.recv() => {
|
||||||
|
// ✅ Just call the existing handler - no duplication!
|
||||||
|
if let Err(e) = token_handler.process_max_depth_reached(token.clone()).await {
|
||||||
|
error!("failed_to_send_max_depth_reached_to_token_handler::mint::{}::error::{}", token.mint, e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Some(message) = msg_stream.next() => {
|
||||||
|
if let Ok(msg) = message.get_payload::<String>() {
|
||||||
|
if let Ok(token) = serde_json::from_str::<MaxDepthReachedData>(&msg) {
|
||||||
|
debug!("max_depth_reached_received::mint::{}::name::{}::nodes::{}::edges::{}",
|
||||||
|
token.mint, token.name, token.node_count, token.edge_count);
|
||||||
|
if let Err(e) = buffer_tx.try_send(token.clone()) {
|
||||||
|
error!("failed_to_send_max_depth_reached_to_buffer::mint::{}::error::{}", token.mint, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ = cancellation_token.cancelled() => {
|
||||||
|
warn!("max_depth_reached_subscriber::shutdown_signal_received");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("max_depth_reached_subscriber::ended");
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
147
src/task/ui.rs
Normal file
147
src/task/ui.rs
Normal file
|
|
@ -0,0 +1,147 @@
|
||||||
|
use i_slint_backend_winit::WinitWindowAccessor;
|
||||||
|
use slint::Weak;
|
||||||
|
use tokio::task::JoinHandle;
|
||||||
|
use tokio_util::sync::CancellationToken;
|
||||||
|
|
||||||
|
use crate::error::Result;
|
||||||
|
use crate::get_current_unix_timestamp;
|
||||||
|
use crate::handler::ui::SlintHandlerUiOperator;
|
||||||
|
use crate::slint_ui::*;
|
||||||
|
use crate::{err_with_loc, error::app::AppError};
|
||||||
|
use std::sync::Arc;
|
||||||
|
use tracing::{error, info};
|
||||||
|
|
||||||
|
pub fn spawn_ui_task(
|
||||||
|
slint_handler: Arc<SlintHandlerUiOperator>,
|
||||||
|
ui_weak: Weak<MainWindow>,
|
||||||
|
cancellation_token: CancellationToken,
|
||||||
|
shutdown_tx: tokio::sync::mpsc::Sender<()>,
|
||||||
|
) -> JoinHandle<Result<()>> {
|
||||||
|
tokio::spawn(async move {
|
||||||
|
ui_weak
|
||||||
|
.clone()
|
||||||
|
.upgrade_in_event_loop(move |ui| {
|
||||||
|
// Window dragging
|
||||||
|
ui.on_start_drag_window({
|
||||||
|
let ui_weak = ui.as_weak();
|
||||||
|
move || {
|
||||||
|
let _ = ui_weak.upgrade_in_event_loop(|ui| {
|
||||||
|
let _ = ui.window().with_winit_window(
|
||||||
|
|winit_window: &winit::window::Window| {
|
||||||
|
let _ = winit_window.drag_window();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Window minimize
|
||||||
|
ui.on_minimize_window({
|
||||||
|
let ui_weak = ui_weak.clone();
|
||||||
|
move || {
|
||||||
|
let _ = ui_weak.upgrade_in_event_loop(|ui| {
|
||||||
|
let _ = ui.window().with_winit_window(
|
||||||
|
|winit_window: &winit::window::Window| {
|
||||||
|
winit_window.set_minimized(true);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Window maximize/restore
|
||||||
|
ui.on_maximize_window({
|
||||||
|
let ui_weak = ui_weak.clone();
|
||||||
|
move || {
|
||||||
|
let _ = ui_weak.upgrade_in_event_loop(|ui| {
|
||||||
|
let _ = ui.window().with_winit_window(
|
||||||
|
|winit_window: &winit::window::Window| {
|
||||||
|
let is_maximized = winit_window.is_maximized();
|
||||||
|
winit_window.set_maximized(!is_maximized);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Theme toggle
|
||||||
|
ui.on_theme_toggle_clicked({
|
||||||
|
let ui_weak = ui_weak.clone();
|
||||||
|
move || {
|
||||||
|
let _ = ui_weak.upgrade_in_event_loop(|ui| {
|
||||||
|
ui.invoke_toggle_theme();
|
||||||
|
info!("Theme toggled");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Navigation callback
|
||||||
|
ui.on_navigation_changed({
|
||||||
|
move |page| {
|
||||||
|
info!("Navigation changed to: {}", page);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let new_tokens_handler = slint_handler.clone();
|
||||||
|
ui.on_clear_new_tokens(move || {
|
||||||
|
let handler = new_tokens_handler.clone();
|
||||||
|
handler.clear_new_tokens();
|
||||||
|
});
|
||||||
|
|
||||||
|
let cex_tokens_handler = slint_handler.clone();
|
||||||
|
ui.on_clear_cex_tokens(move || {
|
||||||
|
let handler = cex_tokens_handler.clone();
|
||||||
|
handler.clear_cex_tokens();
|
||||||
|
});
|
||||||
|
|
||||||
|
let analysis_tokens_handler = slint_handler.clone();
|
||||||
|
ui.on_clear_analysis_tokens(move || {
|
||||||
|
let handler = analysis_tokens_handler.clone();
|
||||||
|
handler.clear_analysis_tokens();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Other callbacks (placeholder implementations)
|
||||||
|
ui.on_logout_clicked({
|
||||||
|
move || {
|
||||||
|
info!("Logout clicked");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Window close - with proper task cleanup
|
||||||
|
ui.on_close_window({
|
||||||
|
let ui_weak = ui_weak.clone();
|
||||||
|
let shutdown_tx = shutdown_tx.clone();
|
||||||
|
let cancellation_token = cancellation_token.clone();
|
||||||
|
move || {
|
||||||
|
let _ = ui_weak.upgrade_in_event_loop({
|
||||||
|
let shutdown_tx = shutdown_tx.clone();
|
||||||
|
let cancellation_token = cancellation_token.clone();
|
||||||
|
move |ui| {
|
||||||
|
info!("close_window::shutting_down_all_tasks");
|
||||||
|
|
||||||
|
// Signal shutdown to all subscriber tasks
|
||||||
|
cancellation_token.cancel();
|
||||||
|
|
||||||
|
// Hide window immediately for responsive UI
|
||||||
|
let _ = ui.window().hide();
|
||||||
|
|
||||||
|
// Send shutdown signal
|
||||||
|
let _ = shutdown_tx.try_send(());
|
||||||
|
|
||||||
|
info!("close_window::all_tasks_cleaned_up");
|
||||||
|
|
||||||
|
// Quit the event loop after cleanup
|
||||||
|
let _ = slint::quit_event_loop();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("failed_to_setup_ui_callbacks: {}", e);
|
||||||
|
err_with_loc!(AppError::Slint(format!(
|
||||||
|
"failed_to_setup_ui_callbacks: {}",
|
||||||
|
e
|
||||||
|
)))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
73
src/tracing/filter.rs
Normal file
73
src/tracing/filter.rs
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
use tracing::Level;
|
||||||
|
use tracing::Metadata;
|
||||||
|
use tracing_subscriber::layer::Context;
|
||||||
|
use tracing_subscriber::layer::Filter;
|
||||||
|
use tracing_subscriber::registry::LookupSpan;
|
||||||
|
|
||||||
|
// Custom filter for exact debug level matching
|
||||||
|
pub struct DebugOnlyFilter;
|
||||||
|
|
||||||
|
impl<S> Filter<S> for DebugOnlyFilter
|
||||||
|
where
|
||||||
|
S: tracing::Subscriber + for<'lookup> LookupSpan<'lookup>,
|
||||||
|
{
|
||||||
|
fn enabled(&self, meta: &Metadata<'_>, _ctx: &Context<'_, S>) -> bool {
|
||||||
|
let target = meta.target();
|
||||||
|
meta.level() == &Level::DEBUG && target.starts_with("ziya")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom filter for error and warn levels
|
||||||
|
pub struct ErrorWarnFilter;
|
||||||
|
|
||||||
|
impl<S> Filter<S> for ErrorWarnFilter
|
||||||
|
where
|
||||||
|
S: tracing::Subscriber + for<'lookup> LookupSpan<'lookup>,
|
||||||
|
{
|
||||||
|
fn enabled(&self, meta: &Metadata<'_>, _ctx: &Context<'_, S>) -> bool {
|
||||||
|
let target = meta.target();
|
||||||
|
(meta.level() == &Level::ERROR || meta.level() == &Level::WARN)
|
||||||
|
&& target.starts_with("ziya")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom filter for info levels
|
||||||
|
#[cfg(feature = "dev")]
|
||||||
|
pub struct InfoOnlyFilter;
|
||||||
|
|
||||||
|
#[cfg(feature = "dev")]
|
||||||
|
impl<S> Filter<S> for InfoOnlyFilter
|
||||||
|
where
|
||||||
|
S: tracing::Subscriber + for<'lookup> LookupSpan<'lookup>,
|
||||||
|
{
|
||||||
|
fn enabled(&self, meta: &Metadata<'_>, _ctx: &Context<'_, S>) -> bool {
|
||||||
|
let target = meta.target();
|
||||||
|
meta.level() == &Level::INFO && target.starts_with("ziya")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom filter for error levels
|
||||||
|
pub struct ErrorOnlyFilter;
|
||||||
|
|
||||||
|
impl<S> Filter<S> for ErrorOnlyFilter
|
||||||
|
where
|
||||||
|
S: tracing::Subscriber + for<'lookup> LookupSpan<'lookup>,
|
||||||
|
{
|
||||||
|
fn enabled(&self, meta: &Metadata<'_>, _ctx: &Context<'_, S>) -> bool {
|
||||||
|
let target = meta.target();
|
||||||
|
meta.level() == &Level::ERROR && target.starts_with("ziya")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom filter for warn levels
|
||||||
|
pub struct WarnOnlyFilter;
|
||||||
|
|
||||||
|
impl<S> Filter<S> for WarnOnlyFilter
|
||||||
|
where
|
||||||
|
S: tracing::Subscriber + for<'lookup> LookupSpan<'lookup>,
|
||||||
|
{
|
||||||
|
fn enabled(&self, meta: &Metadata<'_>, _ctx: &Context<'_, S>) -> bool {
|
||||||
|
let target = meta.target();
|
||||||
|
meta.level() == &Level::WARN && target.starts_with("ziya")
|
||||||
|
}
|
||||||
|
}
|
||||||
60
src/tracing/format.rs
Normal file
60
src/tracing/format.rs
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
use tracing::Event;
|
||||||
|
use tracing_subscriber::fmt::format::Writer;
|
||||||
|
use tracing_subscriber::fmt::FmtContext;
|
||||||
|
use tracing_subscriber::fmt::FormatEvent;
|
||||||
|
use tracing_subscriber::fmt::FormatFields;
|
||||||
|
use tracing_subscriber::registry::LookupSpan;
|
||||||
|
|
||||||
|
pub struct ZiyaFormat {
|
||||||
|
pub app_name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implement Clone for ZiyaFormat
|
||||||
|
impl Clone for ZiyaFormat {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
app_name: self.app_name.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S, N> FormatEvent<S, N> for ZiyaFormat
|
||||||
|
where
|
||||||
|
S: tracing::Subscriber + for<'lookup> LookupSpan<'lookup>,
|
||||||
|
N: for<'writer> FormatFields<'writer> + 'static,
|
||||||
|
{
|
||||||
|
fn format_event(
|
||||||
|
&self,
|
||||||
|
ctx: &FmtContext<'_, S, N>,
|
||||||
|
mut writer: Writer<'_>,
|
||||||
|
event: &Event<'_>,
|
||||||
|
) -> std::fmt::Result {
|
||||||
|
// To get the message, we need to format the fields with a special visitor
|
||||||
|
let metadata = event.metadata();
|
||||||
|
let file = metadata.file().unwrap_or("unknown");
|
||||||
|
let line = metadata.line().unwrap_or(0);
|
||||||
|
|
||||||
|
if file == "unknown" && !cfg!(feature = "deep-trace") {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let utc_timestamp = chrono::Utc::now();
|
||||||
|
let jakarta_timestamp = utc_timestamp.with_timezone(&chrono_tz::Asia::Jakarta);
|
||||||
|
let timestamp = jakarta_timestamp.format("%Y-%m-%d %H:%M:%S");
|
||||||
|
|
||||||
|
write!(
|
||||||
|
writer,
|
||||||
|
"{} {}::{}::{}::{}::",
|
||||||
|
metadata.level(),
|
||||||
|
timestamp,
|
||||||
|
self.app_name,
|
||||||
|
file,
|
||||||
|
line
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Format the actual message
|
||||||
|
ctx.field_format().format_fields(writer.by_ref(), event)?;
|
||||||
|
|
||||||
|
writeln!(writer)
|
||||||
|
}
|
||||||
|
}
|
||||||
187
src/tracing/mod.rs
Normal file
187
src/tracing/mod.rs
Normal file
|
|
@ -0,0 +1,187 @@
|
||||||
|
pub mod filter;
|
||||||
|
pub mod format;
|
||||||
|
|
||||||
|
pub use tracing::*;
|
||||||
|
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use self::filter::DebugOnlyFilter;
|
||||||
|
use self::filter::ErrorOnlyFilter;
|
||||||
|
use self::filter::ErrorWarnFilter;
|
||||||
|
#[cfg(feature = "dev")]
|
||||||
|
use self::filter::InfoOnlyFilter;
|
||||||
|
use self::format::ZiyaFormat;
|
||||||
|
|
||||||
|
use tracing_appender::rolling::RollingFileAppender;
|
||||||
|
use tracing_appender::rolling::Rotation;
|
||||||
|
use tracing_subscriber::prelude::*;
|
||||||
|
use tracing_subscriber::Layer;
|
||||||
|
|
||||||
|
use crate::config::Config;
|
||||||
|
use crate::err_with_loc;
|
||||||
|
use crate::error::app::AppError;
|
||||||
|
use crate::error::Result;
|
||||||
|
|
||||||
|
pub async fn setup_tracing(config: Config, app_name: &str) -> Result<()> {
|
||||||
|
// Get logging config
|
||||||
|
let logging_config = config.logging;
|
||||||
|
|
||||||
|
// Base logs directory
|
||||||
|
let base_logs_dir = Path::new(&logging_config.directory);
|
||||||
|
|
||||||
|
// Create logs directories if they don't exist
|
||||||
|
let logs_dirs = [
|
||||||
|
base_logs_dir,
|
||||||
|
&base_logs_dir.join("debug"),
|
||||||
|
&base_logs_dir.join("error"),
|
||||||
|
];
|
||||||
|
|
||||||
|
for dir in &logs_dirs {
|
||||||
|
if !dir.exists() {
|
||||||
|
std::fs::create_dir_all(dir).map_err(|e| {
|
||||||
|
err_with_loc!(AppError::Config(format!(
|
||||||
|
"Failed to create logs directory {}: {}",
|
||||||
|
dir.display(),
|
||||||
|
e
|
||||||
|
)))
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create file appenders for each log level
|
||||||
|
#[cfg(feature = "dev")]
|
||||||
|
let info_appender =
|
||||||
|
RollingFileAppender::new(Rotation::DAILY, base_logs_dir, format!("{}.log", app_name));
|
||||||
|
|
||||||
|
let debug_appender = RollingFileAppender::new(
|
||||||
|
Rotation::DAILY,
|
||||||
|
base_logs_dir.join("debug"),
|
||||||
|
format!("{}.log", app_name),
|
||||||
|
);
|
||||||
|
|
||||||
|
let error_appender = RollingFileAppender::new(
|
||||||
|
Rotation::DAILY,
|
||||||
|
base_logs_dir.join("error"),
|
||||||
|
format!("{}.log", app_name),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create non-blocking writers
|
||||||
|
#[cfg(feature = "dev")]
|
||||||
|
let (non_blocking_info, info_guard) = tracing_appender::non_blocking(info_appender);
|
||||||
|
let (non_blocking_debug, debug_guard) = tracing_appender::non_blocking(debug_appender);
|
||||||
|
let (non_blocking_error, error_guard) = tracing_appender::non_blocking(error_appender);
|
||||||
|
|
||||||
|
// Store the guards in statics to keep them alive
|
||||||
|
#[cfg(feature = "dev")]
|
||||||
|
static mut INFO_GUARD: Option<tracing_appender::non_blocking::WorkerGuard> = None;
|
||||||
|
static mut DEBUG_GUARD: Option<tracing_appender::non_blocking::WorkerGuard> = None;
|
||||||
|
static mut ERROR_GUARD: Option<tracing_appender::non_blocking::WorkerGuard> = None;
|
||||||
|
|
||||||
|
// Create the custom format for all outputs
|
||||||
|
let format = ZiyaFormat {
|
||||||
|
app_name: app_name.to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Set up the registry with all outputs
|
||||||
|
let subscriber = tracing_subscriber::registry()
|
||||||
|
// DEBUG log file - debug only using custom filter
|
||||||
|
.with(
|
||||||
|
tracing_subscriber::fmt::Layer::default()
|
||||||
|
.with_ansi(false)
|
||||||
|
.with_file(true)
|
||||||
|
.with_line_number(true)
|
||||||
|
.with_target(false)
|
||||||
|
.event_format(format.clone())
|
||||||
|
.with_writer(non_blocking_debug)
|
||||||
|
.with_filter(DebugOnlyFilter),
|
||||||
|
)
|
||||||
|
// ERROR log file - warn and error only
|
||||||
|
.with(
|
||||||
|
tracing_subscriber::fmt::Layer::default()
|
||||||
|
.with_ansi(false)
|
||||||
|
.with_file(true)
|
||||||
|
.with_line_number(true)
|
||||||
|
.with_target(false)
|
||||||
|
.event_format(format.clone())
|
||||||
|
.with_writer(non_blocking_error)
|
||||||
|
.with_filter(ErrorWarnFilter),
|
||||||
|
);
|
||||||
|
|
||||||
|
#[cfg(feature = "prod")]
|
||||||
|
let subscriber = subscriber
|
||||||
|
// Terminal output with custom ZiyaFormat - Error only in production
|
||||||
|
.with(
|
||||||
|
tracing_subscriber::fmt::Layer::default()
|
||||||
|
.with_ansi(true)
|
||||||
|
.with_file(true)
|
||||||
|
.with_line_number(true)
|
||||||
|
.with_target(false)
|
||||||
|
.event_format(format.clone())
|
||||||
|
.with_filter(ErrorOnlyFilter),
|
||||||
|
);
|
||||||
|
|
||||||
|
#[cfg(feature = "dev")]
|
||||||
|
let subscriber = subscriber
|
||||||
|
// Terminal output with custom ZiyaFormat - INFO and above in development
|
||||||
|
.with(
|
||||||
|
tracing_subscriber::fmt::Layer::default()
|
||||||
|
.with_ansi(true)
|
||||||
|
.with_file(true)
|
||||||
|
.with_line_number(true)
|
||||||
|
.with_target(false)
|
||||||
|
.event_format(format.clone())
|
||||||
|
.with_filter(InfoOnlyFilter),
|
||||||
|
)
|
||||||
|
// INFO log file - info and above
|
||||||
|
.with(
|
||||||
|
tracing_subscriber::fmt::Layer::default()
|
||||||
|
.with_ansi(false)
|
||||||
|
.with_file(true)
|
||||||
|
.with_line_number(true)
|
||||||
|
.with_target(false)
|
||||||
|
.event_format(format.clone())
|
||||||
|
.with_writer(non_blocking_info)
|
||||||
|
.with_filter(InfoOnlyFilter),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Set the subscriber as the global default
|
||||||
|
match tracing::subscriber::set_global_default(subscriber) {
|
||||||
|
Ok(_) => {
|
||||||
|
// Store the guards to keep the loggers alive
|
||||||
|
#[cfg(feature = "dev")]
|
||||||
|
unsafe {
|
||||||
|
INFO_GUARD = Some(info_guard);
|
||||||
|
}
|
||||||
|
unsafe {
|
||||||
|
DEBUG_GUARD = Some(debug_guard);
|
||||||
|
ERROR_GUARD = Some(error_guard);
|
||||||
|
}
|
||||||
|
tracing::info!(
|
||||||
|
"{}_logging_started::info_logs::{}\\{}.log",
|
||||||
|
app_name,
|
||||||
|
base_logs_dir.display(),
|
||||||
|
app_name
|
||||||
|
);
|
||||||
|
tracing::info!(
|
||||||
|
"{}_logging_started::debug_logs::{}\\debug\\{}.log",
|
||||||
|
app_name,
|
||||||
|
base_logs_dir.display(),
|
||||||
|
app_name
|
||||||
|
);
|
||||||
|
tracing::info!(
|
||||||
|
"{}_logging_started::error_logs::{}\\error\\{}.log",
|
||||||
|
app_name,
|
||||||
|
base_logs_dir.display(),
|
||||||
|
app_name
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("failed_to_setup_tracing: {}", e);
|
||||||
|
Err(err_with_loc!(AppError::Config(format!(
|
||||||
|
"Failed to setup tracing: {}",
|
||||||
|
e
|
||||||
|
))))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,325 +1,268 @@
|
||||||
// App Layer - Main Application Entry Point
|
// App Layer - Main Application Entry Point
|
||||||
// This is the root of the application following Feature-Sliced Design
|
// This is the root of the application following Feature-Sliced Design
|
||||||
|
|
||||||
// No std-widgets needed since we're using Layout components
|
// Import std-widgets for Palette
|
||||||
|
import { Palette } from "std-widgets.slint";
|
||||||
// Import from shared layer (design system, UI kit)
|
import { NewTokenUiData, CexUpdatedUiData, MaxDepthReachedUiData } from "../shared/types/token.slint";
|
||||||
import { Theme } from "../shared/design-system/tokens/theme.slint";
|
|
||||||
|
|
||||||
// Import widgets (standalone UI blocks)
|
// Import widgets (standalone UI blocks)
|
||||||
import { NavigationWidget } from "../widgets/navigation/index.slint";
|
import { NavigationWidget } from "../widgets/navigation/index.slint";
|
||||||
|
import { TitleBar } from "../widgets/window-controls/ui/title-bar.slint";
|
||||||
|
|
||||||
// Import pages
|
// Import pages
|
||||||
import { Dashboard } from "../pages/dashboard/index.slint";
|
import { Dashboard } from "../pages/dashboard/index.slint";
|
||||||
|
import { HuntingGroundPage } from "../pages/hunting-ground/index.slint";
|
||||||
|
import { TradingPage } from "../pages/trading/index.slint";
|
||||||
|
import { PortfolioPage } from "../pages/portfolio/index.slint";
|
||||||
|
import { MarketsPage } from "../pages/markets/index.slint";
|
||||||
|
import { LoginView } from "../pages/auth/index.slint";
|
||||||
|
import { LoadingView } from "../shared/ui/index.slint";
|
||||||
|
|
||||||
export component App {
|
export component App inherits Window {
|
||||||
width: 100%;
|
title: "Ziya - One Stop Shop for Your Trading Habit";
|
||||||
height: 100%;
|
min-width: 1280px;
|
||||||
|
min-height: 720px;
|
||||||
|
|
||||||
// Application state
|
// Disable default background
|
||||||
|
background: transparent;
|
||||||
|
|
||||||
|
// Theme state
|
||||||
|
in-out property <bool> is-dark-mode: false;
|
||||||
|
|
||||||
|
// Application state management
|
||||||
|
in-out property <string> app-state: "loading"; // "loading", "login", "authenticated"
|
||||||
|
in-out property <bool> is-authenticated: false;
|
||||||
|
in-out property <string> user-email: "";
|
||||||
|
in-out property <string> sidebar-state: "full"; // "full", "icon-only", or "hidden"
|
||||||
|
|
||||||
|
// Loading state
|
||||||
|
in-out property <bool> is-loading: true;
|
||||||
|
in-out property <bool> has-connection-error: false;
|
||||||
|
in-out property <string> loading-status: "Initializing your trading environment...";
|
||||||
|
|
||||||
|
// Navigation state
|
||||||
in-out property <string> current-page: "Dashboard";
|
in-out property <string> current-page: "Dashboard";
|
||||||
in-out property <string> user-initials: "JD";
|
in-out property <string> user-initials: "JD";
|
||||||
|
|
||||||
// Callbacks
|
// Token data
|
||||||
callback navigation-changed(string);
|
in-out property <[NewTokenUiData]> new-tokens: [];
|
||||||
callback theme-toggle-clicked();
|
in-out property <[CexUpdatedUiData]> cex-tokens: [];
|
||||||
callback logout-clicked();
|
in-out property <[MaxDepthReachedUiData]> analysis-tokens: [];
|
||||||
callback buy-clicked();
|
in-out property <int> current-time: 0;
|
||||||
callback sell-clicked();
|
|
||||||
|
// Callbacks for application state
|
||||||
|
callback health-check-completed(bool); // true if healthy, false if error
|
||||||
|
callback retry-health-check();
|
||||||
|
callback login-attempt(string, string);
|
||||||
|
callback logout-requested();
|
||||||
|
callback authenticate-user(string);
|
||||||
|
|
||||||
|
// Callbacks for window controls
|
||||||
callback start-drag-window();
|
callback start-drag-window();
|
||||||
callback minimize-window();
|
callback minimize-window();
|
||||||
callback maximize-window();
|
callback maximize-window();
|
||||||
callback close-window();
|
callback close-window();
|
||||||
|
callback theme-toggle-clicked();
|
||||||
|
|
||||||
// Global drag area - covers entire window but sits behind interactive elements
|
// Callbacks for navigation
|
||||||
TouchArea {
|
callback navigation-changed(string);
|
||||||
|
callback toggle-sidebar();
|
||||||
|
|
||||||
|
// Callbacks for hunting ground
|
||||||
|
callback refresh-hunting-ground();
|
||||||
|
callback clear-column(string);
|
||||||
|
|
||||||
|
// Other callbacks
|
||||||
|
callback logout-clicked();
|
||||||
|
callback buy-clicked();
|
||||||
|
callback sell-clicked();
|
||||||
|
callback clear-new-tokens();
|
||||||
|
callback clear-cex-tokens();
|
||||||
|
callback clear-analysis-tokens();
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
background: Palette.background;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
z: -1;
|
|
||||||
|
|
||||||
moved => {
|
// Apply overflow hidden to prevent scrolling
|
||||||
if (self.pressed) {
|
clip: true;
|
||||||
root.start-drag-window();
|
|
||||||
}
|
// Loading Screen
|
||||||
|
if app-state == "loading": LoadingView {
|
||||||
|
is-loading: root.is-loading;
|
||||||
|
has-error: root.has-connection-error;
|
||||||
|
status-text: root.loading-status;
|
||||||
|
|
||||||
|
retry-connection => {
|
||||||
|
root.retry-health-check();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
VerticalLayout {
|
// Login Screen
|
||||||
|
if app-state == "login": VerticalLayout {
|
||||||
spacing: 0px;
|
spacing: 0px;
|
||||||
|
|
||||||
// Title Bar - fixed height
|
// Title Bar (fixed height)
|
||||||
Rectangle {
|
TitleBar {
|
||||||
height: 32px;
|
height: 40px;
|
||||||
background: Theme.surface;
|
is-dark-theme: root.is-dark-mode;
|
||||||
|
minimize-window => {
|
||||||
HorizontalLayout {
|
root.minimize-window();
|
||||||
padding: 4px;
|
}
|
||||||
spacing: 8px;
|
maximize-window => {
|
||||||
|
root.maximize-window();
|
||||||
// Draggable area (left side) - takes most of the space
|
}
|
||||||
Rectangle {
|
close-window => {
|
||||||
horizontal-stretch: 1;
|
root.close-window();
|
||||||
|
}
|
||||||
Text {
|
toggle-theme => {
|
||||||
text: "Ziya Trading Platform";
|
root.is-dark-mode = !root.is-dark-mode;
|
||||||
color: Theme.text-primary;
|
if (root.is-dark-mode) {
|
||||||
font-size: 14px;
|
Palette.color-scheme = ColorScheme.dark;
|
||||||
font-weight: 600;
|
} else {
|
||||||
vertical-alignment: center;
|
Palette.color-scheme = ColorScheme.light;
|
||||||
x: 8px;
|
}
|
||||||
|
root.theme-toggle-clicked();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Theme Toggle Button - fixed size
|
LoginView {
|
||||||
Rectangle {
|
login-clicked(email, password) => {
|
||||||
width: 40px;
|
root.login-attempt(email, password);
|
||||||
height: 20px;
|
|
||||||
border-radius: 10px;
|
|
||||||
background: Theme.is-dark-mode ? Theme.primary : Theme.border;
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
border-radius: 8px;
|
|
||||||
background: white;
|
|
||||||
x: Theme.is-dark-mode ? 22px : 2px;
|
|
||||||
y: 2px;
|
|
||||||
|
|
||||||
animate x { duration: 200ms; easing: ease-in-out; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TouchArea {
|
navigate-to-dashboard => {
|
||||||
clicked => {
|
// Demo mode - skip login
|
||||||
theme-toggle-clicked();
|
root.authenticate-user("demo@ziya.trading");
|
||||||
|
}
|
||||||
|
|
||||||
|
back-to-loading => {
|
||||||
|
root.app-state = "loading";
|
||||||
|
root.retry-health-check();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Window Controls - fixed size
|
// Main Application (Authenticated State)
|
||||||
HorizontalLayout {
|
if app-state == "authenticated": VerticalLayout {
|
||||||
spacing: 2px;
|
spacing: 0px;
|
||||||
|
// Title Bar (fixed height)
|
||||||
// Minimize Button
|
TitleBar {
|
||||||
Rectangle {
|
height: 40px;
|
||||||
width: 28px;
|
is-dark-theme: root.is-dark-mode;
|
||||||
height: 24px;
|
minimize-window => {
|
||||||
border-radius: 2px;
|
root.minimize-window();
|
||||||
background: minimize-area.pressed ? #30404040 : minimize-area.has-hover ? #20404040 : transparent;
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "−";
|
|
||||||
color: Theme.text-primary;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 400;
|
|
||||||
horizontal-alignment: center;
|
|
||||||
vertical-alignment: center;
|
|
||||||
}
|
}
|
||||||
|
maximize-window => {
|
||||||
minimize-area := TouchArea {
|
root.maximize-window();
|
||||||
clicked => { minimize-window(); }
|
}
|
||||||
|
close-window => {
|
||||||
|
root.close-window();
|
||||||
|
}
|
||||||
|
toggle-theme => {
|
||||||
|
root.is-dark-mode = !root.is-dark-mode;
|
||||||
|
if (root.is-dark-mode) {
|
||||||
|
Palette.color-scheme = ColorScheme.dark;
|
||||||
|
} else {
|
||||||
|
Palette.color-scheme = ColorScheme.light;
|
||||||
|
}
|
||||||
|
root.theme-toggle-clicked();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Maximize Button
|
// Main Content Area (stretches to fill remaining space)
|
||||||
Rectangle {
|
|
||||||
width: 28px;
|
|
||||||
height: 24px;
|
|
||||||
border-radius: 2px;
|
|
||||||
background: maximize-area.pressed ? #30404040 : maximize-area.has-hover ? #20404040 : transparent;
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "□";
|
|
||||||
color: Theme.text-primary;
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 400;
|
|
||||||
horizontal-alignment: center;
|
|
||||||
vertical-alignment: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
maximize-area := TouchArea {
|
|
||||||
clicked => { maximize-window(); }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close Button
|
|
||||||
Rectangle {
|
|
||||||
width: 28px;
|
|
||||||
height: 24px;
|
|
||||||
border-radius: 2px;
|
|
||||||
background: close-area.pressed ? #E53E3E : close-area.has-hover ? #C53030 : transparent;
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "×";
|
|
||||||
color: close-area.has-hover ? white : Theme.text-primary;
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 400;
|
|
||||||
horizontal-alignment: center;
|
|
||||||
vertical-alignment: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
close-area := TouchArea {
|
|
||||||
clicked => { close-window(); }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Main content area - stretches to fill remaining vertical space
|
|
||||||
HorizontalLayout {
|
HorizontalLayout {
|
||||||
spacing: 0px;
|
spacing: 0px;
|
||||||
|
|
||||||
// Navigation Widget (Sidebar) - fixed width
|
// Navigation Sidebar (different states)
|
||||||
NavigationWidget {
|
if sidebar-state == "full": NavigationWidget {
|
||||||
width: 280px;
|
width: 280px;
|
||||||
current-page: root.current-page;
|
current-page: root.current-page;
|
||||||
user-initials: root.user-initials;
|
user-initials: root.user-initials;
|
||||||
|
sidebar-state: root.sidebar-state;
|
||||||
navigation-changed(page) => {
|
navigation-changed(page) => {
|
||||||
root.current-page = page;
|
root.current-page = page;
|
||||||
root.navigation-changed(page);
|
root.navigation-changed(page);
|
||||||
}
|
}
|
||||||
|
logout-clicked => {
|
||||||
logout-clicked => { root.logout-clicked(); }
|
root.logout-requested();
|
||||||
|
}
|
||||||
|
toggle-sidebar => {
|
||||||
|
root.sidebar-state = "icon-only";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Main content area - stretches to fill remaining horizontal space
|
if sidebar-state == "icon-only": NavigationWidget {
|
||||||
Rectangle {
|
width: 80px;
|
||||||
background: Theme.background;
|
current-page: root.current-page;
|
||||||
|
user-initials: root.user-initials;
|
||||||
|
sidebar-state: root.sidebar-state;
|
||||||
|
navigation-changed(page) => {
|
||||||
|
root.current-page = page;
|
||||||
|
root.navigation-changed(page);
|
||||||
|
}
|
||||||
|
logout-clicked => {
|
||||||
|
root.logout-requested();
|
||||||
|
}
|
||||||
|
toggle-sidebar => {
|
||||||
|
root.sidebar-state = "full";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Page routing based on current-page
|
// Page Content (stretches to fill remaining space)
|
||||||
|
Rectangle {
|
||||||
|
background: Palette.background;
|
||||||
|
|
||||||
|
// Content container with proper centering
|
||||||
|
VerticalLayout {
|
||||||
|
alignment: stretch;
|
||||||
|
|
||||||
|
// Route to different pages
|
||||||
if current-page == "Dashboard": Dashboard {
|
if current-page == "Dashboard": Dashboard {
|
||||||
user-initials: root.user-initials;
|
user-initials: root.user-initials;
|
||||||
logout => { root.logout-clicked(); }
|
logout => {
|
||||||
trade-buy(asset, amount) => { root.buy-clicked(); }
|
root.logout-requested();
|
||||||
trade-sell(asset, amount) => { root.sell-clicked(); }
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if current-page == "Trading": Rectangle {
|
if current-page == "Hunting Ground": HuntingGroundPage {
|
||||||
background: Theme.background;
|
new-tokens: root.new-tokens;
|
||||||
|
cex-tokens: root.cex-tokens;
|
||||||
|
analysis-tokens: root.analysis-tokens;
|
||||||
|
current-time: root.current-time;
|
||||||
|
|
||||||
VerticalLayout {
|
clear-new-tokens => {
|
||||||
padding: 24px;
|
root.clear-new-tokens();
|
||||||
spacing: 16px;
|
|
||||||
alignment: center;
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "🎯 Trading Interface";
|
|
||||||
font-size: 32px;
|
|
||||||
font-weight: 700;
|
|
||||||
color: Theme.text-primary;
|
|
||||||
horizontal-alignment: center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
clear-cex-tokens => {
|
||||||
text: "Advanced trading features coming soon...";
|
root.clear-cex-tokens();
|
||||||
font-size: 18px;
|
}
|
||||||
color: Theme.text-secondary;
|
|
||||||
horizontal-alignment: center;
|
clear-analysis-tokens => {
|
||||||
|
root.clear-analysis-tokens();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if current-page == "Trading": TradingPage {
|
||||||
|
buy-clicked => {
|
||||||
|
root.buy-clicked();
|
||||||
|
}
|
||||||
|
sell-clicked => {
|
||||||
|
root.sell-clicked();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if current-page == "Portfolio": PortfolioPage {
|
||||||
|
}
|
||||||
|
|
||||||
|
if current-page == "Markets": MarketsPage {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if current-page == "Portfolio": Rectangle {
|
|
||||||
background: Theme.background;
|
|
||||||
|
|
||||||
VerticalLayout {
|
|
||||||
padding: 24px;
|
|
||||||
spacing: 16px;
|
|
||||||
alignment: center;
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "💼 Portfolio Management";
|
|
||||||
font-size: 32px;
|
|
||||||
font-weight: 700;
|
|
||||||
color: Theme.text-primary;
|
|
||||||
horizontal-alignment: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "Portfolio tracking and management features coming soon...";
|
|
||||||
font-size: 18px;
|
|
||||||
color: Theme.text-secondary;
|
|
||||||
horizontal-alignment: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if current-page == "Markets": Rectangle {
|
|
||||||
background: Theme.background;
|
|
||||||
|
|
||||||
VerticalLayout {
|
|
||||||
padding: 24px;
|
|
||||||
spacing: 16px;
|
|
||||||
alignment: center;
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "📈 Market Data";
|
|
||||||
font-size: 32px;
|
|
||||||
font-weight: 700;
|
|
||||||
color: Theme.text-primary;
|
|
||||||
horizontal-alignment: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "Real-time market data and analysis coming soon...";
|
|
||||||
font-size: 18px;
|
|
||||||
color: Theme.text-secondary;
|
|
||||||
horizontal-alignment: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if current-page == "HuntingGround": Rectangle {
|
|
||||||
background: Theme.background;
|
|
||||||
|
|
||||||
VerticalLayout {
|
|
||||||
padding: 24px;
|
|
||||||
spacing: 16px;
|
|
||||||
alignment: center;
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "🎯 Token Hunting Ground";
|
|
||||||
font-size: 32px;
|
|
||||||
font-weight: 700;
|
|
||||||
color: Theme.text-primary;
|
|
||||||
horizontal-alignment: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "Discover and track new tokens coming soon...";
|
|
||||||
font-size: 18px;
|
|
||||||
color: Theme.text-secondary;
|
|
||||||
horizontal-alignment: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if current-page == "Profile": Rectangle {
|
|
||||||
background: Theme.background;
|
|
||||||
|
|
||||||
VerticalLayout {
|
|
||||||
padding: 24px;
|
|
||||||
spacing: 16px;
|
|
||||||
alignment: center;
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "👤 User Profile";
|
|
||||||
font-size: 32px;
|
|
||||||
font-weight: 700;
|
|
||||||
color: Theme.text-primary;
|
|
||||||
horizontal-alignment: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "Profile management and settings coming soon...";
|
|
||||||
font-size: 18px;
|
|
||||||
color: Theme.text-secondary;
|
|
||||||
horizontal-alignment: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
135
ui/index.slint
135
ui/index.slint
|
|
@ -3,7 +3,8 @@
|
||||||
// Following the pattern from moonlogs: index -> app -> pages/widgets/entities/shared
|
// Following the pattern from moonlogs: index -> app -> pages/widgets/entities/shared
|
||||||
|
|
||||||
import { App } from "app/index.slint";
|
import { App } from "app/index.slint";
|
||||||
import { Theme } from "shared/design-system/index.slint";
|
import { Palette } from "std-widgets.slint";
|
||||||
|
import { NewTokenUiData, CexUpdatedUiData, MaxDepthReachedUiData } from "shared/types/token.slint";
|
||||||
|
|
||||||
export component MainWindow inherits Window {
|
export component MainWindow inherits Window {
|
||||||
// Window properties
|
// Window properties
|
||||||
|
|
@ -12,43 +13,141 @@ export component MainWindow inherits Window {
|
||||||
min-width: 1080px;
|
min-width: 1080px;
|
||||||
min-height: 800px;
|
min-height: 800px;
|
||||||
no-frame: true;
|
no-frame: true;
|
||||||
background: Theme.background;
|
background: Palette.background;
|
||||||
|
|
||||||
// Properties
|
// Theme state
|
||||||
|
in-out property <bool> is-dark-mode: true;
|
||||||
|
|
||||||
|
// Application state management
|
||||||
|
in-out property <string> app-state: "loading"; // "loading", "login", "authenticated"
|
||||||
|
in-out property <bool> is-authenticated: false;
|
||||||
|
in-out property <string> user-email: "";
|
||||||
|
in-out property <string> sidebar-state: "full"; // "full", "icon-only", or "hidden"
|
||||||
|
|
||||||
|
// Loading state
|
||||||
|
in-out property <bool> is-loading: true;
|
||||||
|
in-out property <bool> has-connection-error: false;
|
||||||
|
in-out property <string> loading-status: "Initializing your trading environment...";
|
||||||
|
|
||||||
|
// Navigation state
|
||||||
in-out property <string> current-page: "Dashboard";
|
in-out property <string> current-page: "Dashboard";
|
||||||
in-out property <string> user-initials: "JD";
|
in-out property <string> user-initials: "JD";
|
||||||
|
|
||||||
// Callbacks that will be handled by main.rs
|
// Hunting ground properties - using correct types
|
||||||
callback navigation-changed(string);
|
in-out property <[NewTokenUiData]> new-tokens: [];
|
||||||
callback theme-toggle-clicked();
|
in-out property <[CexUpdatedUiData]> cex-tokens: [];
|
||||||
callback logout-clicked();
|
in-out property <[MaxDepthReachedUiData]> analysis-tokens: [];
|
||||||
callback buy-clicked();
|
in-out property <int> current-time: 0;
|
||||||
callback sell-clicked();
|
|
||||||
|
// Callbacks for application state
|
||||||
|
callback health-check-completed(bool); // true if healthy, false if error
|
||||||
|
callback retry-health-check();
|
||||||
|
callback login-attempt(string, string);
|
||||||
|
callback logout-requested();
|
||||||
|
callback authenticate-user(string);
|
||||||
|
|
||||||
|
// Callbacks for window controls
|
||||||
callback start-drag-window();
|
callback start-drag-window();
|
||||||
callback minimize-window();
|
callback minimize-window();
|
||||||
callback maximize-window();
|
callback maximize-window();
|
||||||
callback close-window();
|
callback close-window();
|
||||||
|
callback theme-toggle-clicked();
|
||||||
|
|
||||||
|
// Callbacks for navigation
|
||||||
|
callback navigation-changed(string);
|
||||||
|
callback toggle-sidebar();
|
||||||
|
|
||||||
|
// Callbacks for hunting ground
|
||||||
|
callback refresh-hunting-ground();
|
||||||
|
callback clear-column(string);
|
||||||
|
|
||||||
|
// Other callbacks
|
||||||
|
callback logout-clicked();
|
||||||
|
callback buy-clicked();
|
||||||
|
callback sell-clicked();
|
||||||
|
callback clear-new-tokens();
|
||||||
|
callback clear-cex-tokens();
|
||||||
|
callback clear-analysis-tokens();
|
||||||
|
|
||||||
// Public function that can be called from Rust to toggle theme
|
// Public function that can be called from Rust to toggle theme
|
||||||
public function toggle_theme() {
|
public function toggle_theme() {
|
||||||
Theme.is-dark-mode = !Theme.is-dark-mode;
|
root.is-dark-mode = !root.is-dark-mode;
|
||||||
debug("Theme toggled from Rust. New state: " + (Theme.is-dark-mode ? "dark" : "light"));
|
if (root.is-dark-mode) {
|
||||||
|
Palette.color-scheme = ColorScheme.dark;
|
||||||
|
} else {
|
||||||
|
Palette.color-scheme = ColorScheme.light;
|
||||||
|
}
|
||||||
|
debug("Theme toggled from Rust. New state: " + (root.is-dark-mode ? "dark" : "light"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TouchArea {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
moved => {
|
||||||
|
if (self.pressed) {
|
||||||
|
start-drag-window();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// App component handles all the UI following FSD layers
|
// App component handles all the UI following FSD layers
|
||||||
App {
|
App {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
|
// Theme state
|
||||||
|
is-dark-mode: root.is-dark-mode;
|
||||||
|
|
||||||
|
// Application state management
|
||||||
|
app-state: root.app-state;
|
||||||
|
is-authenticated: root.is-authenticated;
|
||||||
|
user-email: root.user-email;
|
||||||
|
sidebar-state: root.sidebar-state;
|
||||||
|
|
||||||
|
// Loading state
|
||||||
|
is-loading: root.is-loading;
|
||||||
|
has-connection-error: root.has-connection-error;
|
||||||
|
loading-status: root.loading-status;
|
||||||
|
|
||||||
|
// Navigation state
|
||||||
current-page: root.current-page;
|
current-page: root.current-page;
|
||||||
user-initials: root.user-initials;
|
user-initials: root.user-initials;
|
||||||
|
|
||||||
|
// Hunting ground properties
|
||||||
|
new-tokens: root.new-tokens;
|
||||||
|
cex-tokens: root.cex-tokens;
|
||||||
|
analysis-tokens: root.analysis-tokens;
|
||||||
|
current-time: root.current-time;
|
||||||
|
|
||||||
// Forward all callbacks to main.rs
|
// Forward all callbacks to main.rs
|
||||||
|
health-check-completed(healthy) => {
|
||||||
|
root.health-check-completed(healthy);
|
||||||
|
}
|
||||||
|
retry-health-check => {
|
||||||
|
root.retry-health-check();
|
||||||
|
}
|
||||||
|
login-attempt(email, password) => {
|
||||||
|
root.login-attempt(email, password);
|
||||||
|
}
|
||||||
|
logout-requested => {
|
||||||
|
root.logout-requested();
|
||||||
|
}
|
||||||
|
authenticate-user(email) => {
|
||||||
|
root.authenticate-user(email);
|
||||||
|
}
|
||||||
navigation-changed(page) => {
|
navigation-changed(page) => {
|
||||||
root.current-page = page;
|
root.current-page = page;
|
||||||
root.navigation-changed(page);
|
root.navigation-changed(page);
|
||||||
}
|
}
|
||||||
|
toggle-sidebar => {
|
||||||
|
if (root.sidebar-state == "full") {
|
||||||
|
root.sidebar-state = "icon-only";
|
||||||
|
} else {
|
||||||
|
root.sidebar-state = "full";
|
||||||
|
}
|
||||||
|
}
|
||||||
theme-toggle-clicked => {
|
theme-toggle-clicked => {
|
||||||
root.theme-toggle-clicked();
|
root.toggle_theme();
|
||||||
}
|
}
|
||||||
logout-clicked => {
|
logout-clicked => {
|
||||||
root.logout-clicked();
|
root.logout-clicked();
|
||||||
|
|
@ -71,5 +170,17 @@ export component MainWindow inherits Window {
|
||||||
close-window => {
|
close-window => {
|
||||||
root.close-window();
|
root.close-window();
|
||||||
}
|
}
|
||||||
|
refresh-hunting-ground => {
|
||||||
|
root.refresh-hunting-ground();
|
||||||
|
}
|
||||||
|
clear-new-tokens => {
|
||||||
|
root.clear-new-tokens();
|
||||||
|
}
|
||||||
|
clear-cex-tokens => {
|
||||||
|
root.clear-cex-tokens();
|
||||||
|
}
|
||||||
|
clear-analysis-tokens => {
|
||||||
|
root.clear-analysis-tokens();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,153 +1,135 @@
|
||||||
import { Button, VerticalBox, HorizontalBox, LineEdit } from "std-widgets.slint";
|
import { Palette, Button, VerticalBox, HorizontalBox, LineEdit } from "std-widgets.slint";
|
||||||
|
import { TitleBar } from "../../../widgets/window-controls/ui/title-bar.slint";
|
||||||
|
|
||||||
// Login component with branded interface
|
// Login component with branded interface
|
||||||
export component LoginView {
|
export component LoginView {
|
||||||
in-out property <color> primary-color: #2563eb;
|
width: 100%;
|
||||||
in-out property <color> background-color: #f8fafc;
|
height: 100%;
|
||||||
in-out property <color> text-color: #1e293b;
|
in-out property <bool> is-dark-mode: true;
|
||||||
in-out property <string> app-version: "0.2.0";
|
|
||||||
|
|
||||||
// Login form state
|
// Define the input properties for passing data to parent
|
||||||
in-out property <string> email: "";
|
|
||||||
in-out property <string> password: "";
|
|
||||||
|
|
||||||
// Callbacks
|
|
||||||
callback login-clicked(string, string);
|
callback login-clicked(string, string);
|
||||||
callback navigate-to-dashboard();
|
callback navigate-to-dashboard();
|
||||||
|
callback back-to-loading();
|
||||||
|
callback minimize-window();
|
||||||
|
callback maximize-window();
|
||||||
|
callback close-window();
|
||||||
|
callback theme-toggle-clicked();
|
||||||
|
|
||||||
Rectangle {
|
VerticalLayout {
|
||||||
background: background-color;
|
|
||||||
|
|
||||||
VerticalBox {
|
|
||||||
alignment: center;
|
alignment: center;
|
||||||
spacing: 30px;
|
spacing: 32px;
|
||||||
padding: 40px;
|
padding: 40px;
|
||||||
|
|
||||||
// Logo and branding section
|
Rectangle {
|
||||||
VerticalBox {
|
width: 100%;
|
||||||
alignment: center;
|
height: 100%;
|
||||||
spacing: 10px;
|
background: Palette.background;
|
||||||
|
|
||||||
|
// Center container
|
||||||
|
Rectangle {
|
||||||
|
width: 400px;
|
||||||
|
height: 500px;
|
||||||
|
|
||||||
|
background: Palette.alternate-background;
|
||||||
|
border-radius: 16px;
|
||||||
|
drop-shadow-blur: 24px;
|
||||||
|
drop-shadow-color: #000000.with-alpha(0.1);
|
||||||
|
border-width: 1px;
|
||||||
|
border-color: Palette.border;
|
||||||
|
|
||||||
|
VerticalLayout {
|
||||||
|
alignment: center;
|
||||||
|
spacing: 24px;
|
||||||
|
padding: 32px;
|
||||||
|
|
||||||
|
// Logo/Icon
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: 80px;
|
width: 80px;
|
||||||
height: 80px;
|
height: 80px;
|
||||||
border-radius: 40px;
|
border-radius: 40px;
|
||||||
background: primary-color.with-alpha(0.1);
|
background: Palette.accent-background;
|
||||||
|
|
||||||
// Logo placeholder
|
Text {
|
||||||
Rectangle {
|
text: "🚀";
|
||||||
width: 40px;
|
font-size: 40px;
|
||||||
height: 40px;
|
horizontal-alignment: center;
|
||||||
background: primary-color;
|
vertical-alignment: center;
|
||||||
border-radius: 20px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Title
|
||||||
Text {
|
Text {
|
||||||
text: "Ziya";
|
text: "Ziya";
|
||||||
font-size: 48px;
|
font-size: 32px;
|
||||||
color: text-color;
|
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
|
color: Palette.alternate-foreground;
|
||||||
|
horizontal-alignment: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Subtitle
|
||||||
Text {
|
Text {
|
||||||
text: "/dˤiˈjaːʔ/, \"zee‑yah\" — Proper noun, meaning \"light\"";
|
text: "Your one-stop trading platform";
|
||||||
font-size: 14px;
|
font-size: 16px;
|
||||||
color: text-color;
|
color: Palette.alternate-foreground;
|
||||||
opacity: 0.6;
|
horizontal-alignment: center;
|
||||||
|
opacity: 0.7;
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
// Form container
|
||||||
text: "One stop shop trading solution";
|
VerticalLayout {
|
||||||
font-size: 20px;
|
spacing: 16px;
|
||||||
color: text-color;
|
width: 100%;
|
||||||
opacity: 0.8;
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "A bismillahDAO creation";
|
|
||||||
font-size: 12px;
|
|
||||||
color: primary-color;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Login form section
|
|
||||||
VerticalBox {
|
|
||||||
alignment: center;
|
|
||||||
spacing: 20px;
|
|
||||||
width: 400px;
|
|
||||||
|
|
||||||
// Email field
|
// Email field
|
||||||
VerticalBox {
|
|
||||||
spacing: 8px;
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "Email";
|
|
||||||
color: text-color;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
LineEdit {
|
LineEdit {
|
||||||
placeholder-text: "Enter your email";
|
placeholder-text: "Enter your email";
|
||||||
text <=> email;
|
width: 100%;
|
||||||
height: 45px;
|
height: 44px;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Password field
|
// Password field
|
||||||
VerticalBox {
|
|
||||||
spacing: 8px;
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "Password";
|
|
||||||
color: text-color;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
LineEdit {
|
LineEdit {
|
||||||
placeholder-text: "Enter your password";
|
placeholder-text: "Enter your password";
|
||||||
text <=> password;
|
input-type: password;
|
||||||
height: 45px;
|
width: 100%;
|
||||||
}
|
height: 44px;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Login button
|
// Login button
|
||||||
Button {
|
Button {
|
||||||
text: "Sign In";
|
text: "Sign In";
|
||||||
primary: true;
|
width: 100%;
|
||||||
height: 50px;
|
height: 44px;
|
||||||
clicked => {
|
clicked => {
|
||||||
login-clicked(email, password);
|
root.login-clicked("demo@ziya.trading", "password123");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Quick access button
|
// Demo button for development
|
||||||
HorizontalBox {
|
|
||||||
spacing: 10px;
|
|
||||||
alignment: center;
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "Quick access:";
|
|
||||||
color: text-color;
|
|
||||||
opacity: 0.7;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
text: "Demo Mode";
|
text: "Continue to Demo";
|
||||||
height: 35px;
|
width: 100%;
|
||||||
|
height: 44px;
|
||||||
clicked => {
|
clicked => {
|
||||||
navigate-to-dashboard();
|
root.navigate-to-dashboard();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Back to connection check
|
||||||
Text {
|
Text {
|
||||||
text: "Version " + app-version;
|
text: "← Back to Connection Check";
|
||||||
font-size: 10px;
|
font-size: 14px;
|
||||||
color: text-color;
|
color: Palette.accent-background;
|
||||||
opacity: 0.5;
|
horizontal-alignment: center;
|
||||||
|
|
||||||
|
TouchArea {
|
||||||
|
clicked => {
|
||||||
|
root.back-to-loading();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import { ScrollView, Button, LineEdit, ComboBox } from "std-widgets.slint";
|
import { ScrollView, Button, LineEdit, ComboBox, Palette } from "std-widgets.slint";
|
||||||
import { Theme } from "../../../shared/design-system/tokens/theme.slint";
|
|
||||||
|
|
||||||
// Professional dashboard component with global theme support
|
// Professional dashboard component with global palette support
|
||||||
export component Dashboard {
|
export component Dashboard {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
@ -12,429 +11,107 @@ export component Dashboard {
|
||||||
callback trade-buy(string, string);
|
callback trade-buy(string, string);
|
||||||
callback trade-sell(string, string);
|
callback trade-sell(string, string);
|
||||||
|
|
||||||
// Let the parent layout handle sizing
|
Rectangle {
|
||||||
|
background: Palette.background;
|
||||||
|
|
||||||
VerticalLayout {
|
VerticalLayout {
|
||||||
spacing: 0px;
|
alignment: center;
|
||||||
|
spacing: 20px;
|
||||||
|
padding: 40px;
|
||||||
|
|
||||||
// Top navbar using HorizontalLayout for proper layout
|
// Icon
|
||||||
Rectangle {
|
Rectangle {
|
||||||
height: 64px;
|
width: 80px;
|
||||||
background: Theme.surface;
|
height: 80px;
|
||||||
border-width: 1px;
|
border-radius: 40px;
|
||||||
border-color: Theme.border;
|
background: Palette.accent-background.with-alpha(0.1);
|
||||||
|
|
||||||
HorizontalLayout {
|
|
||||||
padding: 16px;
|
|
||||||
alignment: stretch;
|
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: "Ziya Dashboard";
|
text: "📊";
|
||||||
color: Theme.text-primary;
|
font-size: 40px;
|
||||||
font-size: 20px;
|
|
||||||
font-weight: 700;
|
|
||||||
vertical-alignment: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Spacer to push user profile to the right
|
|
||||||
Rectangle {
|
|
||||||
horizontal-stretch: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// User profile button
|
|
||||||
Rectangle {
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
background: Theme.primary;
|
|
||||||
border-radius: 20px;
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: user-initials;
|
|
||||||
color: white;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 700;
|
|
||||||
horizontal-alignment: center;
|
horizontal-alignment: center;
|
||||||
vertical-alignment: center;
|
vertical-alignment: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
TouchArea {
|
|
||||||
clicked => { logout(); }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dashboard content - no scrollbar, just layout
|
// Title
|
||||||
VerticalLayout {
|
|
||||||
vertical-stretch: 1;
|
|
||||||
padding: 24px;
|
|
||||||
spacing: 24px;
|
|
||||||
|
|
||||||
// Stats cards row using HorizontalLayout with proper stretch
|
|
||||||
HorizontalLayout {
|
|
||||||
spacing: 24px;
|
|
||||||
alignment: stretch;
|
|
||||||
|
|
||||||
// Total Balance
|
|
||||||
Rectangle {
|
|
||||||
horizontal-stretch: 1;
|
|
||||||
background: Theme.card-background;
|
|
||||||
border-radius: 12px;
|
|
||||||
border-width: 1px;
|
|
||||||
border-color: Theme.card-border;
|
|
||||||
drop-shadow-blur: 8px;
|
|
||||||
drop-shadow-color: Theme.is-dark-mode ? #00000020 : #00000008;
|
|
||||||
|
|
||||||
VerticalLayout {
|
|
||||||
padding: 20px;
|
|
||||||
spacing: 8px;
|
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: "Total Balance";
|
text: "Dashboard";
|
||||||
color: Theme.text-secondary;
|
font-size: 48px;
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 500;
|
|
||||||
horizontal-alignment: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "$25,600";
|
|
||||||
color: Theme.primary;
|
|
||||||
font-size: 32px;
|
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
horizontal-alignment: left;
|
color: Palette.foreground;
|
||||||
|
horizontal-alignment: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Coming soon message
|
||||||
Text {
|
Text {
|
||||||
text: "↗︎ 12% (30d)";
|
text: "Coming Soon";
|
||||||
color: Theme.success;
|
font-size: 24px;
|
||||||
font-size: 13px;
|
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
horizontal-alignment: left;
|
color: Palette.foreground;
|
||||||
}
|
horizontal-alignment: center;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Active Positions
|
// Description
|
||||||
Rectangle {
|
|
||||||
horizontal-stretch: 1;
|
|
||||||
background: Theme.card-background;
|
|
||||||
border-radius: 12px;
|
|
||||||
border-width: 1px;
|
|
||||||
border-color: Theme.card-border;
|
|
||||||
drop-shadow-blur: 8px;
|
|
||||||
drop-shadow-color: Theme.is-dark-mode ? #00000020 : #00000008;
|
|
||||||
|
|
||||||
VerticalLayout {
|
|
||||||
padding: 20px;
|
|
||||||
spacing: 8px;
|
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: "Active Positions";
|
text: "We're working hard to bring you an amazing dashboard experience.\nCheck back soon for portfolio insights, trading analytics, and more!";
|
||||||
color: Theme.text-secondary;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 500;
|
|
||||||
horizontal-alignment: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "8";
|
|
||||||
color: Theme.warning;
|
|
||||||
font-size: 32px;
|
|
||||||
font-weight: 700;
|
|
||||||
horizontal-alignment: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "↗︎ 2 new today";
|
|
||||||
color: Theme.success;
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: 500;
|
|
||||||
horizontal-alignment: left;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// P&L Today
|
|
||||||
Rectangle {
|
|
||||||
horizontal-stretch: 1;
|
|
||||||
background: Theme.card-background;
|
|
||||||
border-radius: 12px;
|
|
||||||
border-width: 1px;
|
|
||||||
border-color: Theme.card-border;
|
|
||||||
drop-shadow-blur: 8px;
|
|
||||||
drop-shadow-color: Theme.is-dark-mode ? #00000020 : #00000008;
|
|
||||||
|
|
||||||
VerticalLayout {
|
|
||||||
padding: 20px;
|
|
||||||
spacing: 8px;
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "P&L Today";
|
|
||||||
color: Theme.text-secondary;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 500;
|
|
||||||
horizontal-alignment: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "+$450";
|
|
||||||
color: Theme.success;
|
|
||||||
font-size: 32px;
|
|
||||||
font-weight: 700;
|
|
||||||
horizontal-alignment: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "↗︎ +2.1%";
|
|
||||||
color: Theme.success;
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: 500;
|
|
||||||
horizontal-alignment: left;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Trading interface row using HorizontalLayout
|
|
||||||
HorizontalLayout {
|
|
||||||
spacing: 24px;
|
|
||||||
alignment: stretch;
|
|
||||||
|
|
||||||
// Quick Trade card
|
|
||||||
Rectangle {
|
|
||||||
horizontal-stretch: 2;
|
|
||||||
background: Theme.card-background;
|
|
||||||
border-radius: 16px;
|
|
||||||
border-width: 1px;
|
|
||||||
border-color: Theme.card-border;
|
|
||||||
drop-shadow-blur: 16px;
|
|
||||||
drop-shadow-color: Theme.is-dark-mode ? #00000020 : #00000008;
|
|
||||||
|
|
||||||
VerticalLayout {
|
|
||||||
padding: 24px;
|
|
||||||
spacing: 20px;
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "Quick Trade";
|
|
||||||
color: Theme.text-primary;
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: 700;
|
|
||||||
horizontal-alignment: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Token selector using HorizontalLayout
|
|
||||||
HorizontalLayout {
|
|
||||||
spacing: 12px;
|
|
||||||
alignment: start;
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "Token:";
|
|
||||||
color: Theme.text-secondary;
|
|
||||||
font-size: 14px;
|
|
||||||
vertical-alignment: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
ComboBox {
|
|
||||||
model: ["SOL", "BTC", "ETH", "USDC"];
|
|
||||||
current-value: "SOL";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Amount input using HorizontalLayout
|
|
||||||
HorizontalLayout {
|
|
||||||
spacing: 12px;
|
|
||||||
alignment: start;
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "Amount:";
|
|
||||||
color: Theme.text-secondary;
|
|
||||||
font-size: 14px;
|
|
||||||
vertical-alignment: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
LineEdit {
|
|
||||||
placeholder-text: "0.00";
|
|
||||||
horizontal-stretch: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Trade buttons using HorizontalLayout
|
|
||||||
HorizontalLayout {
|
|
||||||
spacing: 12px;
|
|
||||||
alignment: stretch;
|
|
||||||
|
|
||||||
Button {
|
|
||||||
text: "Buy";
|
|
||||||
primary: true;
|
|
||||||
horizontal-stretch: 1;
|
|
||||||
clicked => {
|
|
||||||
trade-buy("SOL", "100");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Button {
|
|
||||||
text: "Sell";
|
|
||||||
horizontal-stretch: 1;
|
|
||||||
clicked => {
|
|
||||||
trade-sell("SOL", "50");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Recent Activity card
|
|
||||||
Rectangle {
|
|
||||||
horizontal-stretch: 1;
|
|
||||||
background: Theme.card-background;
|
|
||||||
border-radius: 16px;
|
|
||||||
border-width: 1px;
|
|
||||||
border-color: Theme.card-border;
|
|
||||||
drop-shadow-blur: 16px;
|
|
||||||
drop-shadow-color: Theme.is-dark-mode ? #00000020 : #00000008;
|
|
||||||
|
|
||||||
VerticalLayout {
|
|
||||||
padding: 24px;
|
|
||||||
spacing: 16px;
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "Recent Activity";
|
|
||||||
color: Theme.text-primary;
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: 700;
|
|
||||||
horizontal-alignment: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Activity items using VerticalLayout
|
|
||||||
VerticalLayout {
|
|
||||||
spacing: 12px;
|
|
||||||
|
|
||||||
// Activity item 1
|
|
||||||
HorizontalLayout {
|
|
||||||
spacing: 12px;
|
|
||||||
alignment: stretch;
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: 8px;
|
|
||||||
height: 8px;
|
|
||||||
border-radius: 4px;
|
|
||||||
background: Theme.success;
|
|
||||||
}
|
|
||||||
|
|
||||||
VerticalLayout {
|
|
||||||
spacing: 2px;
|
|
||||||
horizontal-stretch: 1;
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "Bought 2.5 SOL";
|
|
||||||
color: Theme.text-primary;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "2 minutes ago";
|
|
||||||
color: Theme.text-secondary;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Activity item 2
|
|
||||||
HorizontalLayout {
|
|
||||||
spacing: 12px;
|
|
||||||
alignment: stretch;
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: 8px;
|
|
||||||
height: 8px;
|
|
||||||
border-radius: 4px;
|
|
||||||
background: Theme.error;
|
|
||||||
}
|
|
||||||
|
|
||||||
VerticalLayout {
|
|
||||||
spacing: 2px;
|
|
||||||
horizontal-stretch: 1;
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "Sold 1.0 ETH";
|
|
||||||
color: Theme.text-primary;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "1 hour ago";
|
|
||||||
color: Theme.text-secondary;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Activity item 3
|
|
||||||
HorizontalLayout {
|
|
||||||
spacing: 12px;
|
|
||||||
alignment: stretch;
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: 8px;
|
|
||||||
height: 8px;
|
|
||||||
border-radius: 4px;
|
|
||||||
background: Theme.warning;
|
|
||||||
}
|
|
||||||
|
|
||||||
VerticalLayout {
|
|
||||||
spacing: 2px;
|
|
||||||
horizontal-stretch: 1;
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "Staked 100 USDC";
|
|
||||||
color: Theme.text-primary;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "3 hours ago";
|
|
||||||
color: Theme.text-secondary;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Theme demonstration section
|
|
||||||
Rectangle {
|
|
||||||
background: Theme.surface;
|
|
||||||
border-radius: 12px;
|
|
||||||
border-width: 1px;
|
|
||||||
border-color: Theme.border;
|
|
||||||
|
|
||||||
VerticalLayout {
|
|
||||||
padding: 20px;
|
|
||||||
spacing: 12px;
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "Theme Status";
|
|
||||||
color: Theme.text-primary;
|
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
|
color: Palette.foreground;
|
||||||
|
horizontal-alignment: center;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Placeholder for future features
|
||||||
|
Rectangle {
|
||||||
|
width: 400px;
|
||||||
|
height: 200px;
|
||||||
|
background: Palette.alternate-background;
|
||||||
|
border-radius: 12px;
|
||||||
|
border-width: 1px;
|
||||||
|
border-color: Palette.border;
|
||||||
|
|
||||||
|
VerticalLayout {
|
||||||
|
alignment: center;
|
||||||
|
spacing: 10px;
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "✨ Coming Features:";
|
||||||
|
font-size: 18px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
color: Palette.alternate-foreground;
|
||||||
|
horizontal-alignment: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
VerticalLayout {
|
||||||
|
spacing: 8px;
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: "Current theme: " + (Theme.is-dark-mode ? "Dark Mode 🌙" : "Light Mode ☀️");
|
text: "• Real-time Portfolio Tracking";
|
||||||
color: Theme.text-secondary;
|
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
color: Palette.alternate-foreground;
|
||||||
|
horizontal-alignment: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: "All components are now using the global theme system for consistent theming across the application.";
|
text: "• Advanced Trading Analytics";
|
||||||
color: Theme.text-secondary;
|
font-size: 14px;
|
||||||
font-size: 12px;
|
color: Palette.alternate-foreground;
|
||||||
|
horizontal-alignment: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "• Performance Metrics";
|
||||||
|
font-size: 14px;
|
||||||
|
color: Palette.alternate-foreground;
|
||||||
|
horizontal-alignment: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "• Custom Widgets";
|
||||||
|
font-size: 14px;
|
||||||
|
color: Palette.alternate-foreground;
|
||||||
|
horizontal-alignment: center;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,79 @@
|
||||||
import { VerticalBox } from "std-widgets.slint";
|
import { Palette, ScrollView } from "std-widgets.slint";
|
||||||
import { Theme } from "../../../shared/design-system/tokens/theme.slint";
|
|
||||||
|
// Import proper token data structs from shared types
|
||||||
|
import { NewTokenUiData, CexUpdatedUiData, MaxDepthReachedUiData } from "../../../shared/types/token.slint";
|
||||||
|
|
||||||
|
// Import the new token column widgets
|
||||||
|
import { NewTokensColumn, CexTokensColumn, AnalysisCompleteColumn } from "../../../widgets/token-column/ui/mod.slint";
|
||||||
|
|
||||||
// Hunting Ground Page Component
|
// Hunting Ground Page Component
|
||||||
export component HuntingGroundPage {
|
export component HuntingGroundPage {
|
||||||
Rectangle {
|
|
||||||
background: Theme.background;
|
|
||||||
|
|
||||||
VerticalBox {
|
// Properties for token data - using correct types
|
||||||
padding: 32px;
|
in property <[NewTokenUiData]> new-tokens: [];
|
||||||
spacing: 24px;
|
in property <[CexUpdatedUiData]> cex-tokens: [];
|
||||||
alignment: center;
|
in property <[MaxDepthReachedUiData]> analysis-tokens: [];
|
||||||
|
in property <int> current-time: 0;
|
||||||
|
|
||||||
|
callback clear-new-tokens();
|
||||||
|
callback clear-cex-tokens();
|
||||||
|
callback clear-analysis-tokens();
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
background: Palette.background;
|
||||||
|
|
||||||
|
VerticalLayout {
|
||||||
|
alignment: start; // Explicitly align to top
|
||||||
|
padding: 16px; // Reduced from 24px
|
||||||
|
spacing: 16px; // Reduced from 24px
|
||||||
|
|
||||||
|
// Header with refresh button
|
||||||
|
HorizontalLayout {
|
||||||
|
alignment: space-between;
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: "🎯 Hunting Ground";
|
text: "🎯 Token Hunting Ground";
|
||||||
color: Theme.text-primary;
|
font-size: 28px;
|
||||||
font-size: 32px;
|
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
horizontal-alignment: center;
|
color: Palette.foreground;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
// Three columns layout with fixed widths to prevent binding loops
|
||||||
text: "Advanced token hunting and discovery features";
|
HorizontalLayout {
|
||||||
color: Theme.text-secondary;
|
spacing: 16px;
|
||||||
font-size: 16px;
|
padding: 8px;
|
||||||
horizontal-alignment: center;
|
height: 600px; // Fixed height to prevent overflow
|
||||||
|
|
||||||
|
// Column 1: New Tokens (fixed width)
|
||||||
|
NewTokensColumn {
|
||||||
|
width: 350px; // Fixed width instead of calculated
|
||||||
|
height: 100%;
|
||||||
|
tokens: root.new-tokens;
|
||||||
|
clear-tokens => {
|
||||||
|
root.clear-new-tokens();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Column 2: CEX Analyst (fixed width)
|
||||||
|
CexTokensColumn {
|
||||||
|
width: 350px; // Fixed width instead of calculated
|
||||||
|
height: 100%;
|
||||||
|
tokens: root.cex-tokens;
|
||||||
|
clear-tokens => {
|
||||||
|
root.clear-cex-tokens();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Column 3: Analysis Complete (fixed width)
|
||||||
|
AnalysisCompleteColumn {
|
||||||
|
width: 350px; // Fixed width instead of calculated
|
||||||
|
height: 100%;
|
||||||
|
tokens: root.analysis-tokens;
|
||||||
|
clear-tokens => {
|
||||||
|
root.clear-analysis-tokens();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,113 @@
|
||||||
import { VerticalBox } from "std-widgets.slint";
|
import { Palette } from "std-widgets.slint";
|
||||||
import { Theme } from "../../../shared/design-system/tokens/theme.slint";
|
|
||||||
|
|
||||||
// Markets Page Component
|
|
||||||
export component MarketsPage {
|
export component MarketsPage {
|
||||||
Rectangle {
|
width: 100%;
|
||||||
background: Theme.background;
|
height: 100%;
|
||||||
|
|
||||||
VerticalBox {
|
Rectangle {
|
||||||
padding: 32px;
|
background: Palette.background;
|
||||||
spacing: 24px;
|
|
||||||
|
VerticalLayout {
|
||||||
alignment: center;
|
alignment: center;
|
||||||
|
spacing: 20px;
|
||||||
|
padding: 40px;
|
||||||
|
|
||||||
|
// Icon
|
||||||
|
Rectangle {
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
border-radius: 40px;
|
||||||
|
background: Palette.accent-background.with-alpha(0.1);
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "📊";
|
||||||
|
font-size: 40px;
|
||||||
|
horizontal-alignment: center;
|
||||||
|
vertical-alignment: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Title
|
||||||
Text {
|
Text {
|
||||||
text: "Markets";
|
text: "Markets";
|
||||||
color: Theme.text-primary;
|
font-size: 48px;
|
||||||
font-size: 32px;
|
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
|
color: Palette.foreground;
|
||||||
|
horizontal-alignment: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Coming soon message
|
||||||
|
Text {
|
||||||
|
text: "Coming Soon";
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: Palette.foreground;
|
||||||
|
horizontal-alignment: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Description
|
||||||
|
Text {
|
||||||
|
text: "Real-time market data and analysis tools are on the way.\nStay tuned for comprehensive market insights!";
|
||||||
|
font-size: 16px;
|
||||||
|
color: Palette.foreground;
|
||||||
|
horizontal-alignment: center;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Placeholder for future features
|
||||||
|
Rectangle {
|
||||||
|
width: 400px;
|
||||||
|
height: 200px;
|
||||||
|
background: Palette.alternate-background;
|
||||||
|
border-radius: 12px;
|
||||||
|
border-width: 1px;
|
||||||
|
border-color: Palette.border;
|
||||||
|
|
||||||
|
VerticalLayout {
|
||||||
|
alignment: center;
|
||||||
|
spacing: 10px;
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "🌍 Coming Features:";
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: Palette.alternate-foreground;
|
||||||
|
horizontal-alignment: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
VerticalLayout {
|
||||||
|
spacing: 8px;
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "• Live Price Feeds";
|
||||||
|
font-size: 14px;
|
||||||
|
color: Palette.alternate-foreground;
|
||||||
horizontal-alignment: center;
|
horizontal-alignment: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: "Market data and analysis will be displayed here";
|
text: "• Market Cap Rankings";
|
||||||
color: Theme.text-secondary;
|
font-size: 14px;
|
||||||
font-size: 16px;
|
color: Palette.alternate-foreground;
|
||||||
horizontal-alignment: center;
|
horizontal-alignment: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "• Technical Analysis Tools";
|
||||||
|
font-size: 14px;
|
||||||
|
color: Palette.alternate-foreground;
|
||||||
|
horizontal-alignment: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "• News & Market Sentiment";
|
||||||
|
font-size: 14px;
|
||||||
|
color: Palette.alternate-foreground;
|
||||||
|
horizontal-alignment: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,30 +1,114 @@
|
||||||
import { VerticalBox } from "std-widgets.slint";
|
import { Palette } from "std-widgets.slint";
|
||||||
import { Theme } from "../../../shared/design-system/tokens/theme.slint";
|
|
||||||
|
|
||||||
// Portfolio Page Component
|
// Portfolio Page Component
|
||||||
export component PortfolioPage {
|
export component PortfolioPage {
|
||||||
Rectangle {
|
width: 100%;
|
||||||
background: Theme.background;
|
height: 100%;
|
||||||
|
|
||||||
VerticalBox {
|
Rectangle {
|
||||||
padding: 32px;
|
background: Palette.background;
|
||||||
spacing: 24px;
|
|
||||||
|
VerticalLayout {
|
||||||
alignment: center;
|
alignment: center;
|
||||||
|
spacing: 20px;
|
||||||
|
padding: 40px;
|
||||||
|
|
||||||
|
// Icon
|
||||||
|
Rectangle {
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
border-radius: 40px;
|
||||||
|
background: Palette.accent-background.with-alpha(0.1);
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "💼";
|
||||||
|
font-size: 40px;
|
||||||
|
horizontal-alignment: center;
|
||||||
|
vertical-alignment: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Title
|
||||||
Text {
|
Text {
|
||||||
text: "Portfolio";
|
text: "Portfolio";
|
||||||
color: Theme.text-primary;
|
font-size: 48px;
|
||||||
font-size: 32px;
|
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
|
color: Palette.foreground;
|
||||||
|
horizontal-alignment: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Coming soon message
|
||||||
|
Text {
|
||||||
|
text: "Coming Soon";
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: Palette.foreground;
|
||||||
|
horizontal-alignment: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Description
|
||||||
|
Text {
|
||||||
|
text: "Track your investments and performance with detailed portfolio analytics.\nComprehensive portfolio management tools are coming soon!";
|
||||||
|
font-size: 16px;
|
||||||
|
color: Palette.foreground;
|
||||||
|
horizontal-alignment: center;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Placeholder for future features
|
||||||
|
Rectangle {
|
||||||
|
width: 400px;
|
||||||
|
height: 200px;
|
||||||
|
background: Palette.alternate-background;
|
||||||
|
border-radius: 12px;
|
||||||
|
border-width: 1px;
|
||||||
|
border-color: Palette.border;
|
||||||
|
|
||||||
|
VerticalLayout {
|
||||||
|
alignment: center;
|
||||||
|
spacing: 10px;
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "📊 Coming Features:";
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: Palette.alternate-foreground;
|
||||||
|
horizontal-alignment: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
VerticalLayout {
|
||||||
|
spacing: 8px;
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "• Asset Allocation Overview";
|
||||||
|
font-size: 14px;
|
||||||
|
color: Palette.alternate-foreground;
|
||||||
horizontal-alignment: center;
|
horizontal-alignment: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: "Your investment portfolio will be displayed here";
|
text: "• Performance Analytics";
|
||||||
color: Theme.text-secondary;
|
font-size: 14px;
|
||||||
font-size: 16px;
|
color: Palette.alternate-foreground;
|
||||||
horizontal-alignment: center;
|
horizontal-alignment: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "• Historical P&L Reports";
|
||||||
|
font-size: 14px;
|
||||||
|
color: Palette.alternate-foreground;
|
||||||
|
horizontal-alignment: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "• Portfolio Rebalancing Tools";
|
||||||
|
font-size: 14px;
|
||||||
|
color: Palette.alternate-foreground;
|
||||||
|
horizontal-alignment: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,75 +1,113 @@
|
||||||
import { VerticalBox, HorizontalBox } from "std-widgets.slint";
|
import { Palette } from "std-widgets.slint";
|
||||||
import { Theme } from "../../../shared/design-system/tokens/theme.slint";
|
|
||||||
|
|
||||||
// Trading Page Component
|
|
||||||
export component TradingPage {
|
export component TradingPage {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
callback buy-clicked();
|
callback buy-clicked();
|
||||||
callback sell-clicked();
|
callback sell-clicked();
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
background: Theme.background;
|
background: Palette.background;
|
||||||
|
|
||||||
VerticalBox {
|
VerticalLayout {
|
||||||
padding: 32px;
|
|
||||||
spacing: 24px;
|
|
||||||
alignment: center;
|
alignment: center;
|
||||||
|
spacing: 20px;
|
||||||
|
padding: 40px;
|
||||||
|
|
||||||
|
// Icon
|
||||||
|
Rectangle {
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
border-radius: 40px;
|
||||||
|
background: Palette.accent-background.with-alpha(0.1);
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: "Trading Page";
|
text: "📈";
|
||||||
color: Theme.text-primary;
|
font-size: 40px;
|
||||||
font-size: 32px;
|
horizontal-alignment: center;
|
||||||
|
vertical-alignment: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Title
|
||||||
|
Text {
|
||||||
|
text: "Trading";
|
||||||
|
font-size: 48px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
|
color: Palette.foreground;
|
||||||
horizontal-alignment: center;
|
horizontal-alignment: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Coming soon message
|
||||||
Text {
|
Text {
|
||||||
text: "Advanced trading features coming soon...";
|
text: "Coming Soon";
|
||||||
color: Theme.text-secondary;
|
font-size: 24px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: Palette.foreground;
|
||||||
|
horizontal-alignment: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Description
|
||||||
|
Text {
|
||||||
|
text: "Advanced trading interface with real-time market data is coming.\nGet ready for seamless trading experience!";
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
|
color: Palette.foreground;
|
||||||
horizontal-alignment: center;
|
horizontal-alignment: center;
|
||||||
|
opacity: 0.8;
|
||||||
}
|
}
|
||||||
|
|
||||||
HorizontalBox {
|
// Placeholder for future features
|
||||||
spacing: 16px;
|
Rectangle {
|
||||||
|
width: 400px;
|
||||||
|
height: 200px;
|
||||||
|
background: Palette.alternate-background;
|
||||||
|
border-radius: 12px;
|
||||||
|
border-width: 1px;
|
||||||
|
border-color: Palette.border;
|
||||||
|
|
||||||
|
VerticalLayout {
|
||||||
alignment: center;
|
alignment: center;
|
||||||
|
spacing: 10px;
|
||||||
Rectangle {
|
|
||||||
width: 120px;
|
|
||||||
height: 40px;
|
|
||||||
background: #10b981;
|
|
||||||
border-radius: 8px;
|
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: "Buy";
|
text: "🚀 Coming Features:";
|
||||||
color: white;
|
font-size: 18px;
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
color: Palette.alternate-foreground;
|
||||||
horizontal-alignment: center;
|
horizontal-alignment: center;
|
||||||
vertical-alignment: center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TouchArea {
|
VerticalLayout {
|
||||||
clicked => { buy-clicked(); }
|
spacing: 8px;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: 120px;
|
|
||||||
height: 40px;
|
|
||||||
background: #ef4444;
|
|
||||||
border-radius: 8px;
|
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: "Sell";
|
text: "• One-Click Trading";
|
||||||
color: white;
|
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 600;
|
color: Palette.alternate-foreground;
|
||||||
horizontal-alignment: center;
|
horizontal-alignment: center;
|
||||||
vertical-alignment: center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TouchArea {
|
Text {
|
||||||
clicked => { sell-clicked(); }
|
text: "• Advanced Order Types";
|
||||||
|
font-size: 14px;
|
||||||
|
color: Palette.alternate-foreground;
|
||||||
|
horizontal-alignment: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "• Portfolio Integration";
|
||||||
|
font-size: 14px;
|
||||||
|
color: Palette.alternate-foreground;
|
||||||
|
horizontal-alignment: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "• Risk Management Tools";
|
||||||
|
font-size: 14px;
|
||||||
|
color: Palette.alternate-foreground;
|
||||||
|
horizontal-alignment: center;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,971 +0,0 @@
|
||||||
// Fluent 2 Design System for Ziya Trading Platform
|
|
||||||
// Based on Microsoft Fluent 2 Design Language
|
|
||||||
// https://fluent2.microsoft.design/
|
|
||||||
|
|
||||||
// Complete Fluent UI Color System - All Themes Support
|
|
||||||
// Theme mode enumeration
|
|
||||||
export enum ColorMode { light, dark, team-light, team-dark }
|
|
||||||
|
|
||||||
export global FluentColors {
|
|
||||||
// Current theme mode
|
|
||||||
in-out property <ColorMode> current-mode: ColorMode.light;
|
|
||||||
|
|
||||||
// Theme switching callback
|
|
||||||
callback switch-theme(ColorMode);
|
|
||||||
|
|
||||||
// ===== LIGHT THEME COLORS =====
|
|
||||||
|
|
||||||
// Neutral Colors - Light
|
|
||||||
out property <color> neutral-foreground-1-light: #242424;
|
|
||||||
out property <color> neutral-foreground-2-light: #424242;
|
|
||||||
out property <color> neutral-foreground-3-light: #616161;
|
|
||||||
out property <color> neutral-foreground-4-light: #757575;
|
|
||||||
out property <color> neutral-foreground-disabled-light: #bdbdbd;
|
|
||||||
out property <color> neutral-foreground-inverted-light: #ffffff;
|
|
||||||
out property <color> neutral-foreground-inverted-2-light: #f5f5f5;
|
|
||||||
out property <color> neutral-foreground-on-brand-light: #ffffff;
|
|
||||||
out property <color> neutral-foreground-static-inverted-light: #ffffff;
|
|
||||||
out property <color> neutral-foreground-inverted-disabled-light: #ffffff;
|
|
||||||
|
|
||||||
// Interactive states - Light theme
|
|
||||||
out property <color> neutral-foreground-1-hover-light: #424242;
|
|
||||||
out property <color> neutral-foreground-1-pressed-light: #242424;
|
|
||||||
out property <color> neutral-foreground-2-hover-light: #616161;
|
|
||||||
out property <color> neutral-foreground-2-pressed-light: #424242;
|
|
||||||
out property <color> neutral-foreground-2-brand-hover-light: #0078d4;
|
|
||||||
out property <color> neutral-foreground-2-brand-pressed-light: #005a9e;
|
|
||||||
out property <color> neutral-foreground-2-brand-selected-light: #0078d4;
|
|
||||||
|
|
||||||
// Link colors - Light theme
|
|
||||||
out property <color> neutral-foreground-link-light: #0078d4;
|
|
||||||
out property <color> neutral-foreground-link-hover-light: #106ebe;
|
|
||||||
out property <color> neutral-foreground-link-pressed-light: #005a9e;
|
|
||||||
out property <color> neutral-foreground-link-selected-light: #0078d4;
|
|
||||||
|
|
||||||
out property <color> neutral-background-1-light: #ffffff;
|
|
||||||
out property <color> neutral-background-2-light: #fafafa;
|
|
||||||
out property <color> neutral-background-3-light: #f5f5f5;
|
|
||||||
out property <color> neutral-background-4-light: #f0f0f0;
|
|
||||||
out property <color> neutral-background-5-light: #ebebeb;
|
|
||||||
out property <color> neutral-background-6-light: #e1e1e1;
|
|
||||||
out property <color> neutral-background-inverted-light: #292929;
|
|
||||||
out property <color> neutral-background-static-light: #f0f0f0;
|
|
||||||
out property <color> neutral-background-alpha-light: #ffffff;
|
|
||||||
out property <color> neutral-background-alpha-2-light: #ffffff;
|
|
||||||
|
|
||||||
// Background interactive states - Light theme
|
|
||||||
out property <color> neutral-background-1-hover-light: #f5f5f5;
|
|
||||||
out property <color> neutral-background-1-pressed-light: #f0f0f0;
|
|
||||||
out property <color> neutral-background-1-selected-light: #ebebeb;
|
|
||||||
out property <color> neutral-background-2-hover-light: #f0f0f0;
|
|
||||||
out property <color> neutral-background-2-pressed-light: #ebebeb;
|
|
||||||
out property <color> neutral-background-2-selected-light: #e1e1e1;
|
|
||||||
out property <color> neutral-background-3-hover-light: #ebebeb;
|
|
||||||
out property <color> neutral-background-3-pressed-light: #e1e1e1;
|
|
||||||
out property <color> neutral-background-3-selected-light: #c7c7c7;
|
|
||||||
|
|
||||||
out property <color> neutral-stroke-1-light: #e1e1e1;
|
|
||||||
out property <color> neutral-stroke-2-light: #c7c7c7;
|
|
||||||
out property <color> neutral-stroke-3-light: #b3b3b3;
|
|
||||||
out property <color> neutral-stroke-accessible-light: #616161;
|
|
||||||
out property <color> neutral-stroke-focus-1-light: #ffffff;
|
|
||||||
out property <color> neutral-stroke-focus-2-light: #000000;
|
|
||||||
out property <color> neutral-stroke-disabled-light: #e1e1e1;
|
|
||||||
|
|
||||||
// Stroke interactive states - Light theme
|
|
||||||
out property <color> neutral-stroke-1-hover-light: #c7c7c7;
|
|
||||||
out property <color> neutral-stroke-1-pressed-light: #b3b3b3;
|
|
||||||
out property <color> neutral-stroke-1-selected-light: #b3b3b3;
|
|
||||||
out property <color> neutral-stroke-2-hover-light: #b3b3b3;
|
|
||||||
out property <color> neutral-stroke-2-pressed-light: #9e9e9e;
|
|
||||||
|
|
||||||
// ===== DARK THEME COLORS =====
|
|
||||||
|
|
||||||
// Neutral Colors - Dark
|
|
||||||
out property <color> neutral-foreground-1-dark: #ffffff;
|
|
||||||
out property <color> neutral-foreground-2-dark: #f5f5f5;
|
|
||||||
out property <color> neutral-foreground-3-dark: #ebebeb;
|
|
||||||
out property <color> neutral-foreground-4-dark: #cccccc;
|
|
||||||
out property <color> neutral-foreground-disabled-dark: #858585;
|
|
||||||
out property <color> neutral-foreground-inverted-dark: #242424;
|
|
||||||
out property <color> neutral-foreground-inverted-2-dark: #292929;
|
|
||||||
out property <color> neutral-foreground-on-brand-dark: #ffffff;
|
|
||||||
out property <color> neutral-foreground-static-inverted-dark: #ffffff;
|
|
||||||
out property <color> neutral-foreground-inverted-disabled-dark: #858585;
|
|
||||||
|
|
||||||
// Interactive states - Dark theme
|
|
||||||
out property <color> neutral-foreground-1-hover-dark: #f5f5f5;
|
|
||||||
out property <color> neutral-foreground-1-pressed-dark: #ffffff;
|
|
||||||
out property <color> neutral-foreground-2-hover-dark: #ebebeb;
|
|
||||||
out property <color> neutral-foreground-2-pressed-dark: #f5f5f5;
|
|
||||||
out property <color> neutral-foreground-2-brand-hover-dark: #62abf5;
|
|
||||||
out property <color> neutral-foreground-2-brand-pressed-dark: #77b7f7;
|
|
||||||
out property <color> neutral-foreground-2-brand-selected-dark: #479ef5;
|
|
||||||
|
|
||||||
// Link colors - Dark theme
|
|
||||||
out property <color> neutral-foreground-link-dark: #479ef5;
|
|
||||||
out property <color> neutral-foreground-link-hover-dark: #62abf5;
|
|
||||||
out property <color> neutral-foreground-link-pressed-dark: #77b7f7;
|
|
||||||
out property <color> neutral-foreground-link-selected-dark: #479ef5;
|
|
||||||
|
|
||||||
out property <color> neutral-background-1-dark: #1c1c1c;
|
|
||||||
out property <color> neutral-background-2-dark: #242424;
|
|
||||||
out property <color> neutral-background-3-dark: #292929;
|
|
||||||
out property <color> neutral-background-4-dark: #333333;
|
|
||||||
out property <color> neutral-background-5-dark: #3d3d3d;
|
|
||||||
out property <color> neutral-background-6-dark: #474747;
|
|
||||||
out property <color> neutral-background-inverted-dark: #ffffff;
|
|
||||||
out property <color> neutral-background-static-dark: #333333;
|
|
||||||
out property <color> neutral-background-alpha-dark: #1c1c1c;
|
|
||||||
out property <color> neutral-background-alpha-2-dark: #1c1c1c;
|
|
||||||
|
|
||||||
// Background interactive states - Dark theme
|
|
||||||
out property <color> neutral-background-1-hover-dark: #242424;
|
|
||||||
out property <color> neutral-background-1-pressed-dark: #292929;
|
|
||||||
out property <color> neutral-background-1-selected-dark: #333333;
|
|
||||||
out property <color> neutral-background-2-hover-dark: #292929;
|
|
||||||
out property <color> neutral-background-2-pressed-dark: #333333;
|
|
||||||
out property <color> neutral-background-2-selected-dark: #3d3d3d;
|
|
||||||
out property <color> neutral-background-3-hover-dark: #333333;
|
|
||||||
out property <color> neutral-background-3-pressed-dark: #3d3d3d;
|
|
||||||
out property <color> neutral-background-3-selected-dark: #474747;
|
|
||||||
|
|
||||||
out property <color> neutral-stroke-1-dark: #3d3d3d;
|
|
||||||
out property <color> neutral-stroke-2-dark: #525252;
|
|
||||||
out property <color> neutral-stroke-3-dark: #666666;
|
|
||||||
out property <color> neutral-stroke-accessible-dark: #cccccc;
|
|
||||||
out property <color> neutral-stroke-focus-1-dark: #ffffff;
|
|
||||||
out property <color> neutral-stroke-focus-2-dark: #000000;
|
|
||||||
out property <color> neutral-stroke-disabled-dark: #3d3d3d;
|
|
||||||
|
|
||||||
// Stroke interactive states - Dark theme
|
|
||||||
out property <color> neutral-stroke-1-hover-dark: #525252;
|
|
||||||
out property <color> neutral-stroke-1-pressed-dark: #666666;
|
|
||||||
out property <color> neutral-stroke-1-selected-dark: #666666;
|
|
||||||
out property <color> neutral-stroke-2-hover-dark: #666666;
|
|
||||||
out property <color> neutral-stroke-2-pressed-dark: #757575;
|
|
||||||
|
|
||||||
// ===== TEAM LIGHT THEME COLORS =====
|
|
||||||
|
|
||||||
// Neutral Colors - Team Light
|
|
||||||
out property <color> neutral-foreground-1-team-light: #242424;
|
|
||||||
out property <color> neutral-foreground-2-team-light: #424242;
|
|
||||||
out property <color> neutral-foreground-3-team-light: #616161;
|
|
||||||
out property <color> neutral-foreground-4-team-light: #757575;
|
|
||||||
out property <color> neutral-foreground-disabled-team-light: #bdbdbd;
|
|
||||||
out property <color> neutral-foreground-inverted-team-light: #ffffff;
|
|
||||||
out property <color> neutral-foreground-inverted-2-team-light: #f5f5f5;
|
|
||||||
out property <color> neutral-foreground-on-brand-team-light: #ffffff;
|
|
||||||
out property <color> neutral-foreground-static-inverted-team-light: #ffffff;
|
|
||||||
out property <color> neutral-foreground-inverted-disabled-team-light: #ffffff;
|
|
||||||
|
|
||||||
// Interactive states - Team Light theme
|
|
||||||
out property <color> neutral-foreground-1-hover-team-light: #424242;
|
|
||||||
out property <color> neutral-foreground-1-pressed-team-light: #242424;
|
|
||||||
out property <color> neutral-foreground-2-hover-team-light: #616161;
|
|
||||||
out property <color> neutral-foreground-2-pressed-team-light: #424242;
|
|
||||||
out property <color> neutral-foreground-2-brand-hover-team-light: #585a96;
|
|
||||||
out property <color> neutral-foreground-2-brand-pressed-team-light: #4c4e85;
|
|
||||||
out property <color> neutral-foreground-2-brand-selected-team-light: #6264a7;
|
|
||||||
|
|
||||||
// Link colors - Team Light theme
|
|
||||||
out property <color> neutral-foreground-link-team-light: #6264a7;
|
|
||||||
out property <color> neutral-foreground-link-hover-team-light: #585a96;
|
|
||||||
out property <color> neutral-foreground-link-pressed-team-light: #4c4e85;
|
|
||||||
out property <color> neutral-foreground-link-selected-team-light: #6264a7;
|
|
||||||
|
|
||||||
out property <color> neutral-background-1-team-light: #ffffff;
|
|
||||||
out property <color> neutral-background-2-team-light: #f8f8f8;
|
|
||||||
out property <color> neutral-background-3-team-light: #f3f2f1;
|
|
||||||
out property <color> neutral-background-4-team-light: #edebe9;
|
|
||||||
out property <color> neutral-background-5-team-light: #e1dfdd;
|
|
||||||
out property <color> neutral-background-6-team-light: #d2d0ce;
|
|
||||||
out property <color> neutral-background-inverted-team-light: #292929;
|
|
||||||
out property <color> neutral-background-static-team-light: #edebe9;
|
|
||||||
out property <color> neutral-background-alpha-team-light: #ffffff;
|
|
||||||
out property <color> neutral-background-alpha-2-team-light: #ffffff;
|
|
||||||
|
|
||||||
// Background interactive states - Team Light theme
|
|
||||||
out property <color> neutral-background-1-hover-team-light: #f8f8f8;
|
|
||||||
out property <color> neutral-background-1-pressed-team-light: #f3f2f1;
|
|
||||||
out property <color> neutral-background-1-selected-team-light: #edebe9;
|
|
||||||
out property <color> neutral-background-2-hover-team-light: #f3f2f1;
|
|
||||||
out property <color> neutral-background-2-pressed-team-light: #edebe9;
|
|
||||||
out property <color> neutral-background-2-selected-team-light: #e1dfdd;
|
|
||||||
out property <color> neutral-background-3-hover-team-light: #edebe9;
|
|
||||||
out property <color> neutral-background-3-pressed-team-light: #e1dfdd;
|
|
||||||
out property <color> neutral-background-3-selected-team-light: #d2d0ce;
|
|
||||||
|
|
||||||
out property <color> neutral-stroke-1-team-light: #d2d0ce;
|
|
||||||
out property <color> neutral-stroke-2-team-light: #c8c6c4;
|
|
||||||
out property <color> neutral-stroke-3-team-light: #b3b0ad;
|
|
||||||
out property <color> neutral-stroke-accessible-team-light: #605e5c;
|
|
||||||
out property <color> neutral-stroke-focus-1-team-light: #ffffff;
|
|
||||||
out property <color> neutral-stroke-focus-2-team-light: #000000;
|
|
||||||
out property <color> neutral-stroke-disabled-team-light: #d2d0ce;
|
|
||||||
|
|
||||||
// Stroke interactive states - Team Light theme
|
|
||||||
out property <color> neutral-stroke-1-hover-team-light: #c8c6c4;
|
|
||||||
out property <color> neutral-stroke-1-pressed-team-light: #b3b0ad;
|
|
||||||
out property <color> neutral-stroke-1-selected-team-light: #b3b0ad;
|
|
||||||
out property <color> neutral-stroke-2-hover-team-light: #b3b0ad;
|
|
||||||
out property <color> neutral-stroke-2-pressed-team-light: #a19f9d;
|
|
||||||
|
|
||||||
// ===== TEAM DARK THEME COLORS =====
|
|
||||||
|
|
||||||
// Neutral Colors - Team Dark
|
|
||||||
out property <color> neutral-foreground-1-team-dark: #ffffff;
|
|
||||||
out property <color> neutral-foreground-2-team-dark: #f3f2f1;
|
|
||||||
out property <color> neutral-foreground-3-team-dark: #edebe9;
|
|
||||||
out property <color> neutral-foreground-4-team-dark: #d2d0ce;
|
|
||||||
out property <color> neutral-foreground-disabled-team-dark: #8a8886;
|
|
||||||
out property <color> neutral-foreground-inverted-team-dark: #242424;
|
|
||||||
out property <color> neutral-foreground-inverted-2-team-dark: #292929;
|
|
||||||
out property <color> neutral-foreground-on-brand-team-dark: #ffffff;
|
|
||||||
out property <color> neutral-foreground-static-inverted-team-dark: #ffffff;
|
|
||||||
out property <color> neutral-foreground-inverted-disabled-team-dark: #8a8886;
|
|
||||||
|
|
||||||
// Interactive states - Team Dark theme
|
|
||||||
out property <color> neutral-foreground-1-hover-team-dark: #f3f2f1;
|
|
||||||
out property <color> neutral-foreground-1-pressed-team-dark: #ffffff;
|
|
||||||
out property <color> neutral-foreground-2-hover-team-dark: #edebe9;
|
|
||||||
out property <color> neutral-foreground-2-pressed-team-dark: #f3f2f1;
|
|
||||||
out property <color> neutral-foreground-2-brand-hover-team-dark: #9a9bd2;
|
|
||||||
out property <color> neutral-foreground-2-brand-pressed-team-dark: #a8a9db;
|
|
||||||
out property <color> neutral-foreground-2-brand-selected-team-dark: #8b8cc8;
|
|
||||||
|
|
||||||
// Link colors - Team Dark theme
|
|
||||||
out property <color> neutral-foreground-link-team-dark: #8b8cc8;
|
|
||||||
out property <color> neutral-foreground-link-hover-team-dark: #9a9bd2;
|
|
||||||
out property <color> neutral-foreground-link-pressed-team-dark: #a8a9db;
|
|
||||||
out property <color> neutral-foreground-link-selected-team-dark: #8b8cc8;
|
|
||||||
|
|
||||||
out property <color> neutral-background-1-team-dark: #252423;
|
|
||||||
out property <color> neutral-background-2-team-dark: #2d2c2b;
|
|
||||||
out property <color> neutral-background-3-team-dark: #323130;
|
|
||||||
out property <color> neutral-background-4-team-dark: #3b3a39;
|
|
||||||
out property <color> neutral-background-5-team-dark: #484644;
|
|
||||||
out property <color> neutral-background-6-team-dark: #605e5c;
|
|
||||||
out property <color> neutral-background-inverted-team-dark: #ffffff;
|
|
||||||
out property <color> neutral-background-static-team-dark: #3b3a39;
|
|
||||||
out property <color> neutral-background-alpha-team-dark: #252423;
|
|
||||||
out property <color> neutral-background-alpha-2-team-dark: #252423;
|
|
||||||
|
|
||||||
// Background interactive states - Team Dark theme
|
|
||||||
out property <color> neutral-background-1-hover-team-dark: #2d2c2b;
|
|
||||||
out property <color> neutral-background-1-pressed-team-dark: #323130;
|
|
||||||
out property <color> neutral-background-1-selected-team-dark: #3b3a39;
|
|
||||||
out property <color> neutral-background-2-hover-team-dark: #323130;
|
|
||||||
out property <color> neutral-background-2-pressed-team-dark: #3b3a39;
|
|
||||||
out property <color> neutral-background-2-selected-team-dark: #484644;
|
|
||||||
out property <color> neutral-background-3-hover-team-dark: #3b3a39;
|
|
||||||
out property <color> neutral-background-3-pressed-team-dark: #484644;
|
|
||||||
out property <color> neutral-background-3-selected-team-dark: #605e5c;
|
|
||||||
|
|
||||||
out property <color> neutral-stroke-1-team-dark: #484644;
|
|
||||||
out property <color> neutral-stroke-2-team-dark: #605e5c;
|
|
||||||
out property <color> neutral-stroke-3-team-dark: #797775;
|
|
||||||
out property <color> neutral-stroke-accessible-team-dark: #d2d0ce;
|
|
||||||
out property <color> neutral-stroke-focus-1-team-dark: #ffffff;
|
|
||||||
out property <color> neutral-stroke-focus-2-team-dark: #000000;
|
|
||||||
out property <color> neutral-stroke-disabled-team-dark: #484644;
|
|
||||||
|
|
||||||
// Stroke interactive states - Team Dark theme
|
|
||||||
out property <color> neutral-stroke-1-hover-team-dark: #605e5c;
|
|
||||||
out property <color> neutral-stroke-1-pressed-team-dark: #797775;
|
|
||||||
out property <color> neutral-stroke-1-selected-team-dark: #797775;
|
|
||||||
out property <color> neutral-stroke-2-hover-team-dark: #797775;
|
|
||||||
out property <color> neutral-stroke-2-pressed-team-dark: #8a8886;
|
|
||||||
|
|
||||||
// ===== BRAND COLORS (All Themes) =====
|
|
||||||
|
|
||||||
// Brand Colors - Light
|
|
||||||
out property <color> brand-background-1-light: #0078d4;
|
|
||||||
out property <color> brand-background-2-light: #106ebe;
|
|
||||||
out property <color> brand-background-3-light: #005a9e;
|
|
||||||
out property <color> brand-foreground-1-light: #ffffff;
|
|
||||||
out property <color> brand-foreground-2-light: #f3f2f1;
|
|
||||||
out property <color> brand-stroke-1-light: #0078d4;
|
|
||||||
out property <color> brand-stroke-2-light: #106ebe;
|
|
||||||
|
|
||||||
// Brand interactive states - Light theme
|
|
||||||
out property <color> brand-background-1-hover-light: #106ebe;
|
|
||||||
out property <color> brand-background-1-pressed-light: #005a9e;
|
|
||||||
out property <color> brand-background-1-selected-light: #0078d4;
|
|
||||||
out property <color> brand-background-2-hover-light: #005a9e;
|
|
||||||
out property <color> brand-background-2-pressed-light: #004578;
|
|
||||||
out property <color> brand-stroke-1-hover-light: #106ebe;
|
|
||||||
out property <color> brand-stroke-1-pressed-light: #005a9e;
|
|
||||||
|
|
||||||
// Brand Colors - Dark
|
|
||||||
out property <color> brand-background-1-dark: #479ef5;
|
|
||||||
out property <color> brand-background-2-dark: #62abf5;
|
|
||||||
out property <color> brand-background-3-dark: #77b7f7;
|
|
||||||
out property <color> brand-foreground-1-dark: #ffffff;
|
|
||||||
out property <color> brand-foreground-2-dark: #f3f2f1;
|
|
||||||
out property <color> brand-stroke-1-dark: #479ef5;
|
|
||||||
out property <color> brand-stroke-2-dark: #62abf5;
|
|
||||||
|
|
||||||
// Brand interactive states - Dark theme
|
|
||||||
out property <color> brand-background-1-hover-dark: #62abf5;
|
|
||||||
out property <color> brand-background-1-pressed-dark: #77b7f7;
|
|
||||||
out property <color> brand-background-1-selected-dark: #479ef5;
|
|
||||||
out property <color> brand-background-2-hover-dark: #77b7f7;
|
|
||||||
out property <color> brand-background-2-pressed-dark: #8cc8f8;
|
|
||||||
out property <color> brand-stroke-1-hover-dark: #62abf5;
|
|
||||||
out property <color> brand-stroke-1-pressed-dark: #77b7f7;
|
|
||||||
|
|
||||||
// Brand Colors - Team Light
|
|
||||||
out property <color> brand-background-1-team-light: #6264a7;
|
|
||||||
out property <color> brand-background-2-team-light: #585a96;
|
|
||||||
out property <color> brand-background-3-team-light: #4c4e85;
|
|
||||||
out property <color> brand-foreground-1-team-light: #ffffff;
|
|
||||||
out property <color> brand-foreground-2-team-light: #f3f2f1;
|
|
||||||
out property <color> brand-stroke-1-team-light: #6264a7;
|
|
||||||
out property <color> brand-stroke-2-team-light: #585a96;
|
|
||||||
|
|
||||||
// Brand interactive states - Team Light theme
|
|
||||||
out property <color> brand-background-1-hover-team-light: #585a96;
|
|
||||||
out property <color> brand-background-1-pressed-team-light: #4c4e85;
|
|
||||||
out property <color> brand-background-1-selected-team-light: #6264a7;
|
|
||||||
out property <color> brand-background-2-hover-team-light: #4c4e85;
|
|
||||||
out property <color> brand-background-2-pressed-team-light: #414374;
|
|
||||||
out property <color> brand-stroke-1-hover-team-light: #585a96;
|
|
||||||
out property <color> brand-stroke-1-pressed-team-light: #4c4e85;
|
|
||||||
|
|
||||||
// Brand Colors - Team Dark
|
|
||||||
out property <color> brand-background-1-team-dark: #8b8cc8;
|
|
||||||
out property <color> brand-background-2-team-dark: #9a9bd2;
|
|
||||||
out property <color> brand-background-3-team-dark: #a8a9db;
|
|
||||||
out property <color> brand-foreground-1-team-dark: #ffffff;
|
|
||||||
out property <color> brand-foreground-2-team-dark: #f3f2f1;
|
|
||||||
out property <color> brand-stroke-1-team-dark: #8b8cc8;
|
|
||||||
out property <color> brand-stroke-2-team-dark: #9a9bd2;
|
|
||||||
|
|
||||||
// Brand interactive states - Team Dark theme
|
|
||||||
out property <color> brand-background-1-hover-team-dark: #9a9bd2;
|
|
||||||
out property <color> brand-background-1-pressed-team-dark: #a8a9db;
|
|
||||||
out property <color> brand-background-1-selected-team-dark: #8b8cc8;
|
|
||||||
out property <color> brand-background-2-hover-team-dark: #a8a9db;
|
|
||||||
out property <color> brand-background-2-pressed-team-dark: #b6b7e4;
|
|
||||||
out property <color> brand-stroke-1-hover-team-dark: #9a9bd2;
|
|
||||||
out property <color> brand-stroke-1-pressed-team-dark: #a8a9db;
|
|
||||||
|
|
||||||
// ===== SEMANTIC COLORS (All Themes) =====
|
|
||||||
|
|
||||||
// Success Colors - All Themes
|
|
||||||
out property <color> success-background-1-light: #107c10;
|
|
||||||
out property <color> success-background-2-light: #0e6e0e;
|
|
||||||
out property <color> success-foreground-1-light: #ffffff;
|
|
||||||
out property <color> success-stroke-1-light: #107c10;
|
|
||||||
|
|
||||||
out property <color> success-background-1-dark: #54b054;
|
|
||||||
out property <color> success-background-2-dark: #6bb26b;
|
|
||||||
out property <color> success-foreground-1-dark: #ffffff;
|
|
||||||
out property <color> success-stroke-1-dark: #54b054;
|
|
||||||
|
|
||||||
out property <color> success-background-1-team-light: #237b4b;
|
|
||||||
out property <color> success-background-2-team-light: #1e6f42;
|
|
||||||
out property <color> success-foreground-1-team-light: #ffffff;
|
|
||||||
out property <color> success-stroke-1-team-light: #237b4b;
|
|
||||||
|
|
||||||
out property <color> success-background-1-team-dark: #5bb85b;
|
|
||||||
out property <color> success-background-2-team-dark: #6fc46f;
|
|
||||||
out property <color> success-foreground-1-team-dark: #ffffff;
|
|
||||||
out property <color> success-stroke-1-team-dark: #5bb85b;
|
|
||||||
|
|
||||||
// Warning Colors - All Themes
|
|
||||||
out property <color> warning-background-1-light: #fde047;
|
|
||||||
out property <color> warning-background-2-light: #facc15;
|
|
||||||
out property <color> warning-foreground-1-light: #323130;
|
|
||||||
out property <color> warning-stroke-1-light: #f7c52d;
|
|
||||||
|
|
||||||
out property <color> warning-background-1-dark: #ffb900;
|
|
||||||
out property <color> warning-background-2-dark: #ffc328;
|
|
||||||
out property <color> warning-foreground-1-dark: #323130;
|
|
||||||
out property <color> warning-stroke-1-dark: #ffb900;
|
|
||||||
|
|
||||||
out property <color> warning-background-1-team-light: #c19c00;
|
|
||||||
out property <color> warning-background-2-team-light: #a18600;
|
|
||||||
out property <color> warning-foreground-1-team-light: #ffffff;
|
|
||||||
out property <color> warning-stroke-1-team-light: #c19c00;
|
|
||||||
|
|
||||||
out property <color> warning-background-1-team-dark: #ffd43a;
|
|
||||||
out property <color> warning-background-2-team-dark: #ffda56;
|
|
||||||
out property <color> warning-foreground-1-team-dark: #323130;
|
|
||||||
out property <color> warning-stroke-1-team-dark: #ffd43a;
|
|
||||||
|
|
||||||
// Critical/Error Colors - All Themes
|
|
||||||
out property <color> critical-background-1-light: #d13438;
|
|
||||||
out property <color> critical-background-2-light: #b91c1c;
|
|
||||||
out property <color> critical-foreground-1-light: #ffffff;
|
|
||||||
out property <color> critical-stroke-1-light: #d13438;
|
|
||||||
|
|
||||||
out property <color> critical-background-1-dark: #ff6b6b;
|
|
||||||
out property <color> critical-background-2-dark: #ff8080;
|
|
||||||
out property <color> critical-foreground-1-dark: #ffffff;
|
|
||||||
out property <color> critical-stroke-1-dark: #ff6b6b;
|
|
||||||
|
|
||||||
out property <color> critical-background-1-team-light: #c50e1f;
|
|
||||||
out property <color> critical-background-2-team-light: #a80e1b;
|
|
||||||
out property <color> critical-foreground-1-team-light: #ffffff;
|
|
||||||
out property <color> critical-stroke-1-team-light: #c50e1f;
|
|
||||||
|
|
||||||
out property <color> critical-background-1-team-dark: #ff8080;
|
|
||||||
out property <color> critical-background-2-team-dark: #ff9494;
|
|
||||||
out property <color> critical-foreground-1-team-dark: #ffffff;
|
|
||||||
out property <color> critical-stroke-1-team-dark: #ff8080;
|
|
||||||
|
|
||||||
// ===== CURRENT THEME ACCESSORS =====
|
|
||||||
// These automatically switch based on current-mode
|
|
||||||
|
|
||||||
// Current Neutral Colors
|
|
||||||
out property <color> neutral-foreground-1: current-mode == ColorMode.light ? neutral-foreground-1-light :
|
|
||||||
current-mode == ColorMode.dark ? neutral-foreground-1-dark :
|
|
||||||
current-mode == ColorMode.team-light ? neutral-foreground-1-team-light : neutral-foreground-1-team-dark;
|
|
||||||
|
|
||||||
out property <color> neutral-foreground-2: current-mode == ColorMode.light ? neutral-foreground-2-light :
|
|
||||||
current-mode == ColorMode.dark ? neutral-foreground-2-dark :
|
|
||||||
current-mode == ColorMode.team-light ? neutral-foreground-2-team-light : neutral-foreground-2-team-dark;
|
|
||||||
|
|
||||||
out property <color> neutral-foreground-3: current-mode == ColorMode.light ? neutral-foreground-3-light :
|
|
||||||
current-mode == ColorMode.dark ? neutral-foreground-3-dark :
|
|
||||||
current-mode == ColorMode.team-light ? neutral-foreground-3-team-light : neutral-foreground-3-team-dark;
|
|
||||||
|
|
||||||
out property <color> neutral-foreground-disabled: current-mode == ColorMode.light ? neutral-foreground-disabled-light :
|
|
||||||
current-mode == ColorMode.dark ? neutral-foreground-disabled-dark :
|
|
||||||
current-mode == ColorMode.team-light ? neutral-foreground-disabled-team-light : neutral-foreground-disabled-team-dark;
|
|
||||||
|
|
||||||
out property <color> neutral-background-1: current-mode == ColorMode.light ? neutral-background-1-light :
|
|
||||||
current-mode == ColorMode.dark ? neutral-background-1-dark :
|
|
||||||
current-mode == ColorMode.team-light ? neutral-background-1-team-light : neutral-background-1-team-dark;
|
|
||||||
|
|
||||||
out property <color> neutral-background-2: current-mode == ColorMode.light ? neutral-background-2-light :
|
|
||||||
current-mode == ColorMode.dark ? neutral-background-2-dark :
|
|
||||||
current-mode == ColorMode.team-light ? neutral-background-2-team-light : neutral-background-2-team-dark;
|
|
||||||
|
|
||||||
out property <color> neutral-background-3: current-mode == ColorMode.light ? neutral-background-3-light :
|
|
||||||
current-mode == ColorMode.dark ? neutral-background-3-dark :
|
|
||||||
current-mode == ColorMode.team-light ? neutral-background-3-team-light : neutral-background-3-team-dark;
|
|
||||||
|
|
||||||
out property <color> neutral-background-4: current-mode == ColorMode.light ? neutral-background-4-light :
|
|
||||||
current-mode == ColorMode.dark ? neutral-background-4-dark :
|
|
||||||
current-mode == ColorMode.team-light ? neutral-background-4-team-light : neutral-background-4-team-dark;
|
|
||||||
|
|
||||||
out property <color> neutral-stroke-1: current-mode == ColorMode.light ? neutral-stroke-1-light :
|
|
||||||
current-mode == ColorMode.dark ? neutral-stroke-1-dark :
|
|
||||||
current-mode == ColorMode.team-light ? neutral-stroke-1-team-light : neutral-stroke-1-team-dark;
|
|
||||||
|
|
||||||
out property <color> neutral-stroke-2: current-mode == ColorMode.light ? neutral-stroke-2-light :
|
|
||||||
current-mode == ColorMode.dark ? neutral-stroke-2-dark :
|
|
||||||
current-mode == ColorMode.team-light ? neutral-stroke-2-team-light : neutral-stroke-2-team-dark;
|
|
||||||
|
|
||||||
out property <color> neutral-stroke-accessible: current-mode == ColorMode.light ? neutral-stroke-accessible-light :
|
|
||||||
current-mode == ColorMode.dark ? neutral-stroke-accessible-dark :
|
|
||||||
current-mode == ColorMode.team-light ? neutral-stroke-accessible-team-light : neutral-stroke-accessible-team-dark;
|
|
||||||
|
|
||||||
// Current Interactive States
|
|
||||||
out property <color> neutral-foreground-1-hover: current-mode == ColorMode.light ? neutral-foreground-1-hover-light :
|
|
||||||
current-mode == ColorMode.dark ? neutral-foreground-1-hover-dark :
|
|
||||||
current-mode == ColorMode.team-light ? neutral-foreground-1-hover-team-light : neutral-foreground-1-hover-team-dark;
|
|
||||||
|
|
||||||
out property <color> neutral-foreground-2-brand-hover: current-mode == ColorMode.light ? neutral-foreground-2-brand-hover-light :
|
|
||||||
current-mode == ColorMode.dark ? neutral-foreground-2-brand-hover-dark :
|
|
||||||
current-mode == ColorMode.team-light ? neutral-foreground-2-brand-hover-team-light : neutral-foreground-2-brand-hover-team-dark;
|
|
||||||
|
|
||||||
// Current Link Colors
|
|
||||||
out property <color> neutral-foreground-link: current-mode == ColorMode.light ? neutral-foreground-link-light :
|
|
||||||
current-mode == ColorMode.dark ? neutral-foreground-link-dark :
|
|
||||||
current-mode == ColorMode.team-light ? neutral-foreground-link-team-light : neutral-foreground-link-team-dark;
|
|
||||||
|
|
||||||
out property <color> neutral-foreground-link-hover: current-mode == ColorMode.light ? neutral-foreground-link-hover-light :
|
|
||||||
current-mode == ColorMode.dark ? neutral-foreground-link-hover-dark :
|
|
||||||
current-mode == ColorMode.team-light ? neutral-foreground-link-hover-team-light : neutral-foreground-link-hover-team-dark;
|
|
||||||
|
|
||||||
out property <color> neutral-foreground-link-pressed: current-mode == ColorMode.light ? neutral-foreground-link-pressed-light :
|
|
||||||
current-mode == ColorMode.dark ? neutral-foreground-link-pressed-dark :
|
|
||||||
current-mode == ColorMode.team-light ? neutral-foreground-link-pressed-team-light : neutral-foreground-link-pressed-team-dark;
|
|
||||||
|
|
||||||
// Current Background Interactive States
|
|
||||||
out property <color> neutral-background-1-hover: current-mode == ColorMode.light ? neutral-background-1-hover-light :
|
|
||||||
current-mode == ColorMode.dark ? neutral-background-1-hover-dark :
|
|
||||||
current-mode == ColorMode.team-light ? neutral-background-1-hover-team-light : neutral-background-1-hover-team-dark;
|
|
||||||
|
|
||||||
out property <color> neutral-background-1-pressed: current-mode == ColorMode.light ? neutral-background-1-pressed-light :
|
|
||||||
current-mode == ColorMode.dark ? neutral-background-1-pressed-dark :
|
|
||||||
current-mode == ColorMode.team-light ? neutral-background-1-pressed-team-light : neutral-background-1-pressed-team-dark;
|
|
||||||
|
|
||||||
// Current Stroke Interactive States
|
|
||||||
out property <color> neutral-stroke-1-hover: current-mode == ColorMode.light ? neutral-stroke-1-hover-light :
|
|
||||||
current-mode == ColorMode.dark ? neutral-stroke-1-hover-dark :
|
|
||||||
current-mode == ColorMode.team-light ? neutral-stroke-1-hover-team-light : neutral-stroke-1-hover-team-dark;
|
|
||||||
|
|
||||||
out property <color> neutral-stroke-1-pressed: current-mode == ColorMode.light ? neutral-stroke-1-pressed-light :
|
|
||||||
current-mode == ColorMode.dark ? neutral-stroke-1-pressed-dark :
|
|
||||||
current-mode == ColorMode.team-light ? neutral-stroke-1-pressed-team-light : neutral-stroke-1-pressed-team-dark;
|
|
||||||
|
|
||||||
// Current Brand Colors
|
|
||||||
out property <color> brand-background-1: current-mode == ColorMode.light ? brand-background-1-light :
|
|
||||||
current-mode == ColorMode.dark ? brand-background-1-dark :
|
|
||||||
current-mode == ColorMode.team-light ? brand-background-1-team-light : brand-background-1-team-dark;
|
|
||||||
|
|
||||||
out property <color> brand-background-2: current-mode == ColorMode.light ? brand-background-2-light :
|
|
||||||
current-mode == ColorMode.dark ? brand-background-2-dark :
|
|
||||||
current-mode == ColorMode.team-light ? brand-background-2-team-light : brand-background-2-team-dark;
|
|
||||||
|
|
||||||
out property <color> brand-foreground-1: current-mode == ColorMode.light ? brand-foreground-1-light :
|
|
||||||
current-mode == ColorMode.dark ? brand-foreground-1-dark :
|
|
||||||
current-mode == ColorMode.team-light ? brand-foreground-1-team-light : brand-foreground-1-team-dark;
|
|
||||||
|
|
||||||
// Current Brand Interactive States
|
|
||||||
out property <color> brand-background-1-hover: current-mode == ColorMode.light ? brand-background-1-hover-light :
|
|
||||||
current-mode == ColorMode.dark ? brand-background-1-hover-dark :
|
|
||||||
current-mode == ColorMode.team-light ? brand-background-1-hover-team-light : brand-background-1-hover-team-dark;
|
|
||||||
|
|
||||||
out property <color> brand-background-1-pressed: current-mode == ColorMode.light ? brand-background-1-pressed-light :
|
|
||||||
current-mode == ColorMode.dark ? brand-background-1-pressed-dark :
|
|
||||||
current-mode == ColorMode.team-light ? brand-background-1-pressed-team-light : brand-background-1-pressed-team-dark;
|
|
||||||
|
|
||||||
// Current Semantic Colors
|
|
||||||
out property <color> success-background-1: current-mode == ColorMode.light ? success-background-1-light :
|
|
||||||
current-mode == ColorMode.dark ? success-background-1-dark :
|
|
||||||
current-mode == ColorMode.team-light ? success-background-1-team-light : success-background-1-team-dark;
|
|
||||||
|
|
||||||
out property <color> warning-background-1: current-mode == ColorMode.light ? warning-background-1-light :
|
|
||||||
current-mode == ColorMode.dark ? warning-background-1-dark :
|
|
||||||
current-mode == ColorMode.team-light ? warning-background-1-team-light : warning-background-1-team-dark;
|
|
||||||
|
|
||||||
out property <color> critical-background-1: current-mode == ColorMode.light ? critical-background-1-light :
|
|
||||||
current-mode == ColorMode.dark ? critical-background-1-dark :
|
|
||||||
current-mode == ColorMode.team-light ? critical-background-1-team-light : critical-background-1-team-dark;
|
|
||||||
|
|
||||||
// Legacy compatibility and Trading-specific colors
|
|
||||||
out property <color> neutral-foreground-rest: neutral-foreground-1;
|
|
||||||
out property <color> profit-green: success-background-1;
|
|
||||||
out property <color> loss-red: critical-background-1;
|
|
||||||
out property <color> neutral-gray: neutral-foreground-3;
|
|
||||||
|
|
||||||
// Accent colors for trading UI
|
|
||||||
out property <color> accent-purple: #f000b8;
|
|
||||||
out property <color> accent-teal: #37cdbe;
|
|
||||||
out property <color> accent-blue: brand-background-1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Typography System - Official Fluent 2 Typography Tokens
|
|
||||||
// Based on: https://fluent2.microsoft.design/typography
|
|
||||||
export global FluentTypography {
|
|
||||||
// ===== FLUENT FONT STACKS =====
|
|
||||||
// Segoe - unmistakably Microsoft (Primary Web/Windows font stack)
|
|
||||||
out property <string> font-family-base: "Segoe UI Variable, Segoe UI, system-ui, sans-serif";
|
|
||||||
out property <string> font-family-web: "Segoe UI Variable, Segoe UI, system-ui, sans-serif";
|
|
||||||
out property <string> font-family-windows: "Segoe UI Variable, Segoe UI, system-ui, sans-serif";
|
|
||||||
|
|
||||||
// Platform-specific native font stacks for cross-platform consistency
|
|
||||||
out property <string> font-family-macos: "SF Pro Display, SF Pro Text, system-ui, sans-serif";
|
|
||||||
out property <string> font-family-ios: "SF Pro Display, SF Pro Text, system-ui, sans-serif";
|
|
||||||
out property <string> font-family-android: "Roboto, system-ui, sans-serif";
|
|
||||||
|
|
||||||
// Specialized font families
|
|
||||||
out property <string> font-family-monospace: "Cascadia Code, Cascadia Mono, Consolas, Courier New, monospace";
|
|
||||||
out property <string> font-family-numeric: "Bahnschrift, Segoe UI Variable, Segoe UI, system-ui, sans-serif";
|
|
||||||
|
|
||||||
// ===== FONT WEIGHTS - Fluent 2 Weight Scale =====
|
|
||||||
out property <int> font-weight-regular: 400; // Regular
|
|
||||||
out property <int> font-weight-medium: 500; // Medium (Android)
|
|
||||||
out property <int> font-weight-semibold: 600; // Semibold
|
|
||||||
out property <int> font-weight-bold: 700; // Bold
|
|
||||||
|
|
||||||
// ===== WEB TYPE RAMP - Official Fluent 2 Web Typography =====
|
|
||||||
// Caption styles
|
|
||||||
out property <length> font-size-caption-2: 10px; // Caption 2
|
|
||||||
out property <length> line-height-caption-2: 14px;
|
|
||||||
out property <int> font-weight-caption-2: font-weight-regular;
|
|
||||||
out property <int> font-weight-caption-2-strong: font-weight-semibold;
|
|
||||||
|
|
||||||
out property <length> font-size-caption-1: 12px; // Caption 1
|
|
||||||
out property <length> line-height-caption-1: 16px;
|
|
||||||
out property <int> font-weight-caption-1: font-weight-regular;
|
|
||||||
out property <int> font-weight-caption-1-strong: font-weight-semibold;
|
|
||||||
out property <int> font-weight-caption-1-stronger: font-weight-bold;
|
|
||||||
|
|
||||||
// Body styles
|
|
||||||
out property <length> font-size-body-1: 14px; // Body 1 (Default)
|
|
||||||
out property <length> line-height-body-1: 20px;
|
|
||||||
out property <int> font-weight-body-1: font-weight-regular;
|
|
||||||
out property <int> font-weight-body-1-strong: font-weight-semibold;
|
|
||||||
out property <int> font-weight-body-1-stronger: font-weight-bold;
|
|
||||||
|
|
||||||
// Subtitle styles
|
|
||||||
out property <length> font-size-subtitle-2: 16px; // Subtitle 2
|
|
||||||
out property <length> line-height-subtitle-2: 22px;
|
|
||||||
out property <int> font-weight-subtitle-2: font-weight-semibold;
|
|
||||||
out property <int> font-weight-subtitle-2-stronger: font-weight-bold;
|
|
||||||
|
|
||||||
out property <length> font-size-subtitle-1: 20px; // Subtitle 1
|
|
||||||
out property <length> line-height-subtitle-1: 26px;
|
|
||||||
out property <int> font-weight-subtitle-1: font-weight-semibold;
|
|
||||||
|
|
||||||
// Title styles
|
|
||||||
out property <length> font-size-title-3: 24px; // Title 3
|
|
||||||
out property <length> line-height-title-3: 32px;
|
|
||||||
out property <int> font-weight-title-3: font-weight-semibold;
|
|
||||||
|
|
||||||
out property <length> font-size-title-2: 28px; // Title 2
|
|
||||||
out property <length> line-height-title-2: 36px;
|
|
||||||
out property <int> font-weight-title-2: font-weight-semibold;
|
|
||||||
|
|
||||||
out property <length> font-size-title-1: 32px; // Title 1
|
|
||||||
out property <length> line-height-title-1: 40px;
|
|
||||||
out property <int> font-weight-title-1: font-weight-semibold;
|
|
||||||
|
|
||||||
// Large title and display
|
|
||||||
out property <length> font-size-large-title: 40px; // Large Title
|
|
||||||
out property <length> line-height-large-title: 52px;
|
|
||||||
out property <int> font-weight-large-title: font-weight-semibold;
|
|
||||||
|
|
||||||
out property <length> font-size-display: 68px; // Display
|
|
||||||
out property <length> line-height-display: 92px;
|
|
||||||
out property <int> font-weight-display: font-weight-semibold;
|
|
||||||
|
|
||||||
// ===== WINDOWS TYPE RAMP - Official Fluent 2 Windows Typography =====
|
|
||||||
// Windows-specific variants using Segoe UI Variable
|
|
||||||
out property <length> font-size-windows-caption: 12px; // Caption
|
|
||||||
out property <length> line-height-windows-caption: 16px;
|
|
||||||
out property <int> font-weight-windows-caption: font-weight-regular;
|
|
||||||
|
|
||||||
out property <length> font-size-windows-body: 14px; // Body
|
|
||||||
out property <length> line-height-windows-body: 20px;
|
|
||||||
out property <int> font-weight-windows-body: font-weight-regular;
|
|
||||||
out property <int> font-weight-windows-body-strong: font-weight-semibold;
|
|
||||||
|
|
||||||
out property <length> font-size-windows-body-large: 18px; // Body Large
|
|
||||||
out property <length> line-height-windows-body-large: 24px;
|
|
||||||
out property <int> font-weight-windows-body-large: font-weight-regular;
|
|
||||||
|
|
||||||
out property <length> font-size-windows-subtitle: 20px; // Subtitle
|
|
||||||
out property <length> line-height-windows-subtitle: 28px;
|
|
||||||
out property <int> font-weight-windows-subtitle: font-weight-semibold;
|
|
||||||
|
|
||||||
out property <length> font-size-windows-title: 28px; // Title
|
|
||||||
out property <length> line-height-windows-title: 36px;
|
|
||||||
out property <int> font-weight-windows-title: font-weight-semibold;
|
|
||||||
|
|
||||||
out property <length> font-size-windows-large-title: 40px; // Large Title
|
|
||||||
out property <length> line-height-windows-large-title: 52px;
|
|
||||||
out property <int> font-weight-windows-large-title: font-weight-semibold;
|
|
||||||
|
|
||||||
out property <length> font-size-windows-display: 68px; // Display
|
|
||||||
out property <length> line-height-windows-display: 92px;
|
|
||||||
out property <int> font-weight-windows-display: font-weight-semibold;
|
|
||||||
|
|
||||||
// ===== LEGACY NUMERIC TOKENS - For backward compatibility =====
|
|
||||||
out property <length> font-size-100: font-size-caption-2; // 10px
|
|
||||||
out property <length> font-size-200: font-size-caption-1; // 12px
|
|
||||||
out property <length> font-size-300: font-size-body-1; // 14px
|
|
||||||
out property <length> font-size-400: font-size-subtitle-2; // 16px
|
|
||||||
out property <length> font-size-500: 18px; // Custom
|
|
||||||
out property <length> font-size-600: font-size-subtitle-1; // 20px
|
|
||||||
out property <length> font-size-700: font-size-title-3; // 24px
|
|
||||||
out property <length> font-size-800: font-size-title-2; // 28px
|
|
||||||
out property <length> font-size-900: font-size-title-1; // 32px
|
|
||||||
out property <length> font-size-1000: font-size-large-title; // 40px
|
|
||||||
out property <length> font-size-1100: 48px; // Custom Large
|
|
||||||
out property <length> font-size-1200: font-size-display; // 68px
|
|
||||||
|
|
||||||
out property <length> line-height-100: line-height-caption-2; // 14px
|
|
||||||
out property <length> line-height-200: line-height-caption-1; // 16px
|
|
||||||
out property <length> line-height-300: line-height-body-1; // 20px
|
|
||||||
out property <length> line-height-400: line-height-subtitle-2; // 22px
|
|
||||||
out property <length> line-height-500: 24px; // Custom
|
|
||||||
out property <length> line-height-600: line-height-subtitle-1; // 26px
|
|
||||||
out property <length> line-height-700: line-height-title-3; // 32px
|
|
||||||
out property <length> line-height-800: line-height-title-2; // 36px
|
|
||||||
out property <length> line-height-900: line-height-title-1; // 40px
|
|
||||||
out property <length> line-height-1000: line-height-large-title; // 52px
|
|
||||||
out property <length> line-height-1100: 60px; // Custom
|
|
||||||
out property <length> line-height-1200: line-height-display; // 92px
|
|
||||||
|
|
||||||
// ===== TEXT STYLING UTILITIES =====
|
|
||||||
// Letter spacing for fine typography control
|
|
||||||
out property <length> letter-spacing-tight: -0.16px; // For large text
|
|
||||||
out property <length> letter-spacing-normal: 0px; // Standard
|
|
||||||
out property <length> letter-spacing-wide: 0.32px; // For small text
|
|
||||||
|
|
||||||
// Text decoration
|
|
||||||
out property <string> text-decoration-none: "none";
|
|
||||||
out property <string> text-decoration-underline: "underline";
|
|
||||||
out property <string> text-decoration-line-through: "line-through";
|
|
||||||
|
|
||||||
// Text transform (following Fluent guidance for sentence case)
|
|
||||||
out property <string> text-transform-none: "none";
|
|
||||||
out property <string> text-transform-uppercase: "uppercase";
|
|
||||||
out property <string> text-transform-lowercase: "lowercase";
|
|
||||||
out property <string> text-transform-capitalize: "capitalize";
|
|
||||||
|
|
||||||
// ===== USAGE EXAMPLES =====
|
|
||||||
// Caption 2: font-size: FluentTypography.font-size-caption-2; line-height: FluentTypography.line-height-caption-2; font-weight: FluentTypography.font-weight-caption-2;
|
|
||||||
// Body 1: font-size: FluentTypography.font-size-body-1; line-height: FluentTypography.line-height-body-1; font-weight: FluentTypography.font-weight-body-1;
|
|
||||||
// Title 1: font-size: FluentTypography.font-size-title-1; line-height: FluentTypography.line-height-title-1; font-weight: FluentTypography.font-weight-title-1;
|
|
||||||
// Display: font-size: FluentTypography.font-size-display; line-height: FluentTypography.line-height-display; font-weight: FluentTypography.font-weight-display;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Spacing System - Fluent 2 Spacing Scale
|
|
||||||
export global FluentSpacing {
|
|
||||||
out property <length> space-2: 2px;
|
|
||||||
out property <length> space-4: 4px;
|
|
||||||
out property <length> space-6: 6px;
|
|
||||||
out property <length> space-8: 8px;
|
|
||||||
out property <length> space-10: 10px;
|
|
||||||
out property <length> space-12: 12px;
|
|
||||||
out property <length> space-16: 16px;
|
|
||||||
out property <length> space-20: 20px;
|
|
||||||
out property <length> space-24: 24px;
|
|
||||||
out property <length> space-28: 28px;
|
|
||||||
out property <length> space-32: 32px;
|
|
||||||
out property <length> space-36: 36px;
|
|
||||||
out property <length> space-40: 40px;
|
|
||||||
out property <length> space-48: 48px;
|
|
||||||
out property <length> space-64: 64px;
|
|
||||||
out property <length> space-80: 80px;
|
|
||||||
out property <length> space-96: 96px;
|
|
||||||
out property <length> space-120: 120px;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Border Radius - Complete Fluent UI Border Radius System
|
|
||||||
export global FluentRadius {
|
|
||||||
// Corner Radius Scale
|
|
||||||
out property <length> border-radius-none: 0px;
|
|
||||||
out property <length> border-radius-small: 2px;
|
|
||||||
out property <length> border-radius-medium: 4px;
|
|
||||||
out property <length> border-radius-large: 6px;
|
|
||||||
out property <length> border-radius-x-large: 8px;
|
|
||||||
out property <length> border-radius-circular: 10000px;
|
|
||||||
|
|
||||||
// Legacy support
|
|
||||||
out property <length> radius-none: 0px;
|
|
||||||
out property <length> radius-small: 2px;
|
|
||||||
out property <length> radius-medium: 4px;
|
|
||||||
out property <length> radius-large: 6px;
|
|
||||||
out property <length> radius-xlarge: 8px;
|
|
||||||
out property <length> radius-circular: 10000px;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shadows - Complete Fluent UI Shadow System
|
|
||||||
// Based on: https://react.fluentui.dev/?path=/docs/theme-shadows--docs
|
|
||||||
export global FluentShadows {
|
|
||||||
// ===== SHADOW BRAND TOKENS =====
|
|
||||||
// These are the primary shadow tokens used throughout Fluent UI
|
|
||||||
|
|
||||||
// Shadow 2 - Subtle elevation (tooltips, dropdowns)
|
|
||||||
out property <length> shadow-2-x: 0px;
|
|
||||||
out property <length> shadow-2-y: 1px;
|
|
||||||
out property <length> shadow-2-blur: 2px;
|
|
||||||
out property <length> shadow-2-spread: 0px;
|
|
||||||
out property <color> shadow-2-color: #00000014; // 8% opacity black
|
|
||||||
|
|
||||||
// Shadow 4 - Low elevation (cards, buttons)
|
|
||||||
out property <length> shadow-4-x: 0px;
|
|
||||||
out property <length> shadow-4-y: 2px;
|
|
||||||
out property <length> shadow-4-blur: 4px;
|
|
||||||
out property <length> shadow-4-spread: 0px;
|
|
||||||
out property <color> shadow-4-color: #0000001f; // 12% opacity black
|
|
||||||
|
|
||||||
// Shadow 8 - Medium elevation (dialogs, menus)
|
|
||||||
out property <length> shadow-8-x: 0px;
|
|
||||||
out property <length> shadow-8-y: 4px;
|
|
||||||
out property <length> shadow-8-blur: 8px;
|
|
||||||
out property <length> shadow-8-spread: 0px;
|
|
||||||
out property <color> shadow-8-color: #0000001f; // 12% opacity black
|
|
||||||
|
|
||||||
// Shadow 16 - High elevation (modals, flyouts)
|
|
||||||
out property <length> shadow-16-x: 0px;
|
|
||||||
out property <length> shadow-16-y: 8px;
|
|
||||||
out property <length> shadow-16-blur: 16px;
|
|
||||||
out property <length> shadow-16-spread: 0px;
|
|
||||||
out property <color> shadow-16-color: #00000024; // 14% opacity black
|
|
||||||
|
|
||||||
// Shadow 28 - Very high elevation (teaching callouts)
|
|
||||||
out property <length> shadow-28-x: 0px;
|
|
||||||
out property <length> shadow-28-y: 14px;
|
|
||||||
out property <length> shadow-28-blur: 28px;
|
|
||||||
out property <length> shadow-28-spread: 0px;
|
|
||||||
out property <color> shadow-28-color: #00000024; // 14% opacity black
|
|
||||||
|
|
||||||
// Shadow 64 - Maximum elevation (panels, navigation)
|
|
||||||
out property <length> shadow-64-x: 0px;
|
|
||||||
out property <length> shadow-64-y: 32px;
|
|
||||||
out property <length> shadow-64-blur: 64px;
|
|
||||||
out property <length> shadow-64-spread: 0px;
|
|
||||||
out property <color> shadow-64-color: #00000033; // 20% opacity black
|
|
||||||
|
|
||||||
// ===== THEME-AWARE SHADOW COLORS =====
|
|
||||||
// Light theme shadow colors
|
|
||||||
out property <color> shadow-light-ambient: #0000000f; // 6% opacity black
|
|
||||||
out property <color> shadow-light-key: #00000014; // 8% opacity black
|
|
||||||
out property <color> shadow-light-ambient-darker: #00000014; // 8% opacity black
|
|
||||||
out property <color> shadow-light-key-darker: #0000001f; // 12% opacity black
|
|
||||||
out property <color> shadow-light-ambient-darkest: #0000001f; // 12% opacity black
|
|
||||||
out property <color> shadow-light-key-darkest: #00000024; // 14% opacity black
|
|
||||||
|
|
||||||
// Dark theme shadow colors
|
|
||||||
out property <color> shadow-dark-ambient: #0000001f; // 12% opacity black
|
|
||||||
out property <color> shadow-dark-key: #00000024; // 14% opacity black
|
|
||||||
out property <color> shadow-dark-ambient-darker: #00000024; // 14% opacity black
|
|
||||||
out property <color> shadow-dark-key-darker: #00000033; // 20% opacity black
|
|
||||||
out property <color> shadow-dark-ambient-darkest: #00000033; // 20% opacity black
|
|
||||||
out property <color> shadow-dark-key-darkest: #00000047; // 28% opacity black
|
|
||||||
|
|
||||||
// ===== SPECIALIZED SHADOW TOKENS =====
|
|
||||||
// Brand shadow (with brand color tint)
|
|
||||||
out property <length> shadow-brand-x: 0px;
|
|
||||||
out property <length> shadow-brand-y: 2px;
|
|
||||||
out property <length> shadow-brand-blur: 8px;
|
|
||||||
out property <length> shadow-brand-spread: 0px;
|
|
||||||
out property <color> shadow-brand-color: #0078d414; // Brand blue with 8% opacity
|
|
||||||
|
|
||||||
// Inset shadow (for pressed states)
|
|
||||||
out property <length> shadow-inset-x: 0px;
|
|
||||||
out property <length> shadow-inset-y: 1px;
|
|
||||||
out property <length> shadow-inset-blur: 2px;
|
|
||||||
out property <length> shadow-inset-spread: 0px;
|
|
||||||
out property <color> shadow-inset-color: #0000001f; // 12% opacity black
|
|
||||||
|
|
||||||
// Focus shadow (for accessibility)
|
|
||||||
out property <length> shadow-focus-x: 0px;
|
|
||||||
out property <length> shadow-focus-y: 0px;
|
|
||||||
out property <length> shadow-focus-blur: 0px;
|
|
||||||
out property <length> shadow-focus-spread: 2px;
|
|
||||||
out property <color> shadow-focus-color: #0078d4; // Brand blue
|
|
||||||
|
|
||||||
// ===== COMPONENT-SPECIFIC SHADOW TOKENS =====
|
|
||||||
// Button shadows
|
|
||||||
out property <color> shadow-button-rest: shadow-2-color;
|
|
||||||
out property <color> shadow-button-hover: shadow-4-color;
|
|
||||||
out property <color> shadow-button-pressed: shadow-inset-color;
|
|
||||||
|
|
||||||
// Card shadows
|
|
||||||
out property <color> shadow-card-rest: shadow-4-color;
|
|
||||||
out property <color> shadow-card-hover: shadow-8-color;
|
|
||||||
|
|
||||||
// Dialog shadows
|
|
||||||
out property <color> shadow-dialog: shadow-64-color;
|
|
||||||
|
|
||||||
// Flyout shadows
|
|
||||||
out property <color> shadow-flyout: shadow-16-color;
|
|
||||||
|
|
||||||
// Tooltip shadows
|
|
||||||
out property <color> shadow-tooltip: shadow-8-color;
|
|
||||||
|
|
||||||
// ===== LEGACY SHADOW SUPPORT =====
|
|
||||||
// Note: shadow-*-blur properties are already defined above in the main shadow tokens
|
|
||||||
// They can be used directly for backward compatibility
|
|
||||||
|
|
||||||
// ===== SHADOW UTILITY FUNCTIONS =====
|
|
||||||
// Helper properties for common shadow combinations
|
|
||||||
|
|
||||||
// Subtle shadow (for subtle elevation)
|
|
||||||
out property <length> subtle-shadow-x: shadow-2-x;
|
|
||||||
out property <length> subtle-shadow-y: shadow-2-y;
|
|
||||||
out property <length> subtle-shadow-blur: shadow-2-blur;
|
|
||||||
out property <color> subtle-shadow-color: shadow-2-color;
|
|
||||||
|
|
||||||
// Default shadow (for standard elevation)
|
|
||||||
out property <length> default-shadow-x: shadow-4-x;
|
|
||||||
out property <length> default-shadow-y: shadow-4-y;
|
|
||||||
out property <length> default-shadow-blur: shadow-4-blur;
|
|
||||||
out property <color> default-shadow-color: shadow-4-color;
|
|
||||||
|
|
||||||
// Deep shadow (for high elevation)
|
|
||||||
out property <length> deep-shadow-x: shadow-16-x;
|
|
||||||
out property <length> deep-shadow-y: shadow-16-y;
|
|
||||||
out property <length> deep-shadow-blur: shadow-16-blur;
|
|
||||||
out property <color> deep-shadow-color: shadow-16-color;
|
|
||||||
|
|
||||||
// ===== USAGE NOTES =====
|
|
||||||
// To apply shadows in components, use the individual tokens:
|
|
||||||
// Example: Rectangle { drop-shadow-offset-x: FluentShadows.shadow-4-x; drop-shadow-offset-y: FluentShadows.shadow-4-y; drop-shadow-blur: FluentShadows.shadow-4-blur; drop-shadow-color: FluentShadows.shadow-4-color; }
|
|
||||||
// For focus states: Rectangle { border-width: FluentShadows.shadow-focus-spread; border-color: FluentShadows.shadow-focus-color; }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Component Tokens - Fluent 2 Component Specifications
|
|
||||||
export global FluentComponents {
|
|
||||||
// Button
|
|
||||||
out property <length> button-height-small: 24px;
|
|
||||||
out property <length> button-height-medium: 32px;
|
|
||||||
out property <length> button-height-large: 40px;
|
|
||||||
out property <length> button-padding-horizontal: 16px;
|
|
||||||
out property <length> button-border-radius: FluentRadius.border-radius-medium;
|
|
||||||
|
|
||||||
// Input
|
|
||||||
out property <length> input-height: 32px;
|
|
||||||
out property <length> input-padding-horizontal: 12px;
|
|
||||||
out property <length> input-border-radius: FluentRadius.border-radius-medium;
|
|
||||||
out property <length> input-border-width: 1px;
|
|
||||||
|
|
||||||
// Card
|
|
||||||
out property <length> card-padding: FluentSpacing.space-16;
|
|
||||||
out property <length> card-border-radius: FluentRadius.border-radius-large;
|
|
||||||
out property <length> card-border-width: 1px;
|
|
||||||
|
|
||||||
// Navigation
|
|
||||||
out property <length> nav-item-height: 40px;
|
|
||||||
out property <length> nav-item-padding: FluentSpacing.space-12;
|
|
||||||
out property <length> nav-item-border-radius: FluentRadius.border-radius-medium;
|
|
||||||
|
|
||||||
// Title Bar
|
|
||||||
out property <length> titlebar-height: 32px;
|
|
||||||
out property <length> titlebar-button-width: 46px;
|
|
||||||
|
|
||||||
// Sidebar
|
|
||||||
out property <length> sidebar-width: 280px;
|
|
||||||
out property <length> sidebar-collapsed-width: 48px;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Animation & Motion - Fluent 2 Motion System
|
|
||||||
export global FluentMotion {
|
|
||||||
out property <duration> duration-ultra-fast: 50ms;
|
|
||||||
out property <duration> duration-faster: 100ms;
|
|
||||||
out property <duration> duration-fast: 150ms;
|
|
||||||
out property <duration> duration-normal: 200ms;
|
|
||||||
out property <duration> duration-slow: 300ms;
|
|
||||||
out property <duration> duration-slower: 400ms;
|
|
||||||
out property <duration> duration-ultra-slow: 500ms;
|
|
||||||
|
|
||||||
// Easing curves (represented as animation properties)
|
|
||||||
out property <easing> ease-accelerate: ease-in;
|
|
||||||
out property <easing> ease-decelerate: ease-out;
|
|
||||||
out property <easing> ease-standard: ease-in-out;
|
|
||||||
out property <easing> ease-max: ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Layout Grid - Fluent 2 Layout System
|
|
||||||
export global FluentLayout {
|
|
||||||
// Breakpoints
|
|
||||||
out property <length> breakpoint-small: 480px;
|
|
||||||
out property <length> breakpoint-medium: 768px;
|
|
||||||
out property <length> breakpoint-large: 1024px;
|
|
||||||
out property <length> breakpoint-xlarge: 1440px;
|
|
||||||
|
|
||||||
// Container max widths
|
|
||||||
out property <length> container-small: 640px;
|
|
||||||
out property <length> container-medium: 768px;
|
|
||||||
out property <length> container-large: 1024px;
|
|
||||||
out property <length> container-xlarge: 1280px;
|
|
||||||
|
|
||||||
// Grid
|
|
||||||
out property <length> grid-gutter: FluentSpacing.space-24;
|
|
||||||
out property <int> grid-columns: 12;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Z-Index Scale
|
|
||||||
export global FluentLayers {
|
|
||||||
out property <int> layer-base: 0;
|
|
||||||
out property <int> layer-dropdown: 1000;
|
|
||||||
out property <int> layer-sticky: 1020;
|
|
||||||
out property <int> layer-banner: 1030;
|
|
||||||
out property <int> layer-overlay: 1040;
|
|
||||||
out property <int> layer-modal: 1050;
|
|
||||||
out property <int> layer-popover: 1060;
|
|
||||||
out property <int> layer-tooltip: 1070;
|
|
||||||
out property <int> layer-toast: 1080;
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
// Design System Public API
|
|
||||||
// This file exports all design tokens and components from the design system
|
|
||||||
|
|
||||||
// Export design tokens
|
|
||||||
export { Theme } from "tokens/theme.slint";
|
|
||||||
|
|
||||||
// Export design system components
|
|
||||||
export * from "components/design-system.slint";
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
// Global theme management system
|
|
||||||
export global Theme {
|
|
||||||
in-out property <bool> is-dark-mode: false;
|
|
||||||
|
|
||||||
// Color scheme properties that automatically update based on theme
|
|
||||||
out property <color> background: is-dark-mode ? #1a1a1a : #ffffff;
|
|
||||||
out property <color> surface: is-dark-mode ? #2a2a2a : #f5f5f5;
|
|
||||||
out property <color> primary: #3b82f6;
|
|
||||||
out property <color> primary-variant: is-dark-mode ? #60a5fa : #2563eb;
|
|
||||||
out property <color> secondary: is-dark-mode ? #64748b : #475569;
|
|
||||||
out property <color> text-primary: is-dark-mode ? #ffffff : #1f2937;
|
|
||||||
out property <color> text-secondary: is-dark-mode ? #d1d5db : #6b7280;
|
|
||||||
out property <color> border: is-dark-mode ? #374151 : #e5e7eb;
|
|
||||||
out property <color> accent: is-dark-mode ? #22c55e : #16a34a;
|
|
||||||
out property <color> error: #ef4444;
|
|
||||||
out property <color> warning: #f59e0b;
|
|
||||||
out property <color> success: #10b981;
|
|
||||||
|
|
||||||
// Card and container colors
|
|
||||||
out property <color> card-background: is-dark-mode ? #2a2a2a : #ffffff;
|
|
||||||
out property <color> card-border: is-dark-mode ? #404040 : #e5e7eb;
|
|
||||||
|
|
||||||
// Navigation colors
|
|
||||||
out property <color> nav-background: is-dark-mode ? #1f1f1f : #f8fafc;
|
|
||||||
out property <color> nav-active: primary;
|
|
||||||
out property <color> nav-hover: is-dark-mode ? #374151 : #f1f5f9;
|
|
||||||
|
|
||||||
// Theme state is directly modified by components and Rust code
|
|
||||||
}
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
export { Theme, FluentColors, FluentTypography, FluentSpacing, FluentRadius, FluentShadows, FluentComponents, FluentMotion, FluentLayout, FluentLayers, ColorMode } from "design-system/index.slint";
|
export { Theme, FluentColors, FluentTypography, FluentSpacing, FluentRadius, FluentShadows, FluentComponents, FluentMotion, FluentLayout, FluentLayers, ColorMode } from "design-system/index.slint";
|
||||||
|
|
||||||
// Export UI components
|
// Export UI components
|
||||||
export { Button, Card, Input, LoadingView, Container, Grid } from "ui/index.slint";
|
export { LoadingView } from "ui/index.slint";
|
||||||
|
|
||||||
// Export utilities and config - currently commented out in their index files
|
// Export utilities and config - currently commented out in their index files
|
||||||
// export * from "lib/index.slint";
|
// export * from "lib/index.slint";
|
||||||
|
|
|
||||||
43
ui/shared/types/token.slint
Normal file
43
ui/shared/types/token.slint
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
// Shared token data structures for Slint UI - matching muhafidh Redis models
|
||||||
|
|
||||||
|
// For new tokens from NewTokenCache
|
||||||
|
export struct NewTokenUiData {
|
||||||
|
mint: string,
|
||||||
|
bonding_curve: string, // Optional converted to string, empty if None
|
||||||
|
name: string,
|
||||||
|
symbol: string,
|
||||||
|
uri: string,
|
||||||
|
creator: string,
|
||||||
|
created_at: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
// For analyzed tokens from TokenAnalyzedCache (covers both CEX and analysis complete)
|
||||||
|
export struct CexUpdatedUiData {
|
||||||
|
mint: string,
|
||||||
|
name: string,
|
||||||
|
uri: string,
|
||||||
|
dev_name: string,
|
||||||
|
creator: string,
|
||||||
|
cex_name: string,
|
||||||
|
cex_address: string,
|
||||||
|
bonding_curve: string,
|
||||||
|
created_at: string,
|
||||||
|
updated_at: string,
|
||||||
|
node_count: int,
|
||||||
|
edge_count: int,
|
||||||
|
// Note: graph field omitted as it's complex and not needed for UI display
|
||||||
|
}
|
||||||
|
|
||||||
|
export struct MaxDepthReachedUiData {
|
||||||
|
mint: string,
|
||||||
|
name: string,
|
||||||
|
uri: string,
|
||||||
|
dev_name: string,
|
||||||
|
creator: string,
|
||||||
|
bonding_curve: string,
|
||||||
|
created_at: string,
|
||||||
|
updated_at: string,
|
||||||
|
node_count: int,
|
||||||
|
edge_count: int,
|
||||||
|
// Note: graph field omitted as it's complex and not needed for UI display
|
||||||
|
}
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
import { VerticalBox, HorizontalBox } from "std-widgets.slint";
|
|
||||||
|
|
||||||
// Basic Button Component
|
|
||||||
export component Button {
|
|
||||||
in property <string> text;
|
|
||||||
in property <bool> enabled: true;
|
|
||||||
in property <color> background-color: #2563eb;
|
|
||||||
in property <color> text-color: white;
|
|
||||||
in property <color> hover-color: #1d4ed8;
|
|
||||||
in property <color> disabled-color: #94a3b8;
|
|
||||||
in property <length> border-radius: 6px;
|
|
||||||
in property <length> padding-horizontal: 16px;
|
|
||||||
in property <length> padding-vertical: 8px;
|
|
||||||
|
|
||||||
callback clicked();
|
|
||||||
|
|
||||||
min-width: 80px;
|
|
||||||
min-height: 36px;
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
background: enabled ? (touch-area.has-hover ? hover-color : background-color) : disabled-color;
|
|
||||||
border-radius: border-radius;
|
|
||||||
|
|
||||||
HorizontalBox {
|
|
||||||
padding-left: padding-horizontal;
|
|
||||||
padding-right: padding-horizontal;
|
|
||||||
padding-top: padding-vertical;
|
|
||||||
padding-bottom: padding-vertical;
|
|
||||||
alignment: center;
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: root.text;
|
|
||||||
color: text-color;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 500;
|
|
||||||
horizontal-alignment: center;
|
|
||||||
vertical-alignment: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
touch-area := TouchArea {
|
|
||||||
enabled: root.enabled;
|
|
||||||
clicked => {
|
|
||||||
if (root.enabled) {
|
|
||||||
root.clicked();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
// Button Component Public API
|
|
||||||
export { Button } from "button.slint";
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
import { VerticalBox } from "std-widgets.slint";
|
|
||||||
|
|
||||||
// Basic Card Component
|
|
||||||
export component Card {
|
|
||||||
in property <color> background-color: white;
|
|
||||||
in property <color> border-color: #e2e8f0;
|
|
||||||
in property <length> border-radius: 8px;
|
|
||||||
in property <length> border-width: 1px;
|
|
||||||
in property <length> card-padding: 16px;
|
|
||||||
in property <length> shadow-blur: 4px;
|
|
||||||
in property <color> shadow-color: #00000010;
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
background: background-color;
|
|
||||||
border-radius: border-radius;
|
|
||||||
border-width: border-width;
|
|
||||||
border-color: border-color;
|
|
||||||
drop-shadow-blur: shadow-blur;
|
|
||||||
drop-shadow-color: shadow-color;
|
|
||||||
drop-shadow-offset-y: 2px;
|
|
||||||
|
|
||||||
VerticalBox {
|
|
||||||
padding: root.card-padding;
|
|
||||||
@children
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
// Card Component Public API
|
|
||||||
export { Card } from "card.slint";
|
|
||||||
|
|
@ -1,9 +1,5 @@
|
||||||
// UI Kit Public API
|
// UI Kit Public API
|
||||||
// This file exports all shared UI components
|
// This file exports all shared UI components
|
||||||
|
|
||||||
// Export basic components
|
// Export shared components
|
||||||
export { Button } from "button/index.slint";
|
export { LoadingView } from "loading/loading.slint";
|
||||||
export { Card } from "card/index.slint";
|
|
||||||
export { Input } from "input/index.slint";
|
|
||||||
export { LoadingView } from "loading/index.slint";
|
|
||||||
export { Container, Grid } from "layout/index.slint";
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
// Input Component Public API
|
|
||||||
export { Input } from "input.slint";
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
import { LineEdit } from "std-widgets.slint";
|
|
||||||
|
|
||||||
// Basic Input Component
|
|
||||||
export component Input {
|
|
||||||
in-out property <string> text;
|
|
||||||
in property <string> placeholder;
|
|
||||||
in property <bool> enabled: true;
|
|
||||||
|
|
||||||
callback edited(string);
|
|
||||||
|
|
||||||
LineEdit {
|
|
||||||
text: root.text;
|
|
||||||
placeholder-text: root.placeholder;
|
|
||||||
enabled: root.enabled;
|
|
||||||
edited(t) => {
|
|
||||||
root.text = t;
|
|
||||||
root.edited(t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
// Layout Components Public API
|
|
||||||
export { Container, Grid } from "layout.slint";
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
import { VerticalBox, HorizontalBox } from "std-widgets.slint";
|
|
||||||
|
|
||||||
// Container Component
|
|
||||||
export component Container {
|
|
||||||
in property <length> container-max-width: 1200px;
|
|
||||||
in property <length> container-padding: 16px;
|
|
||||||
|
|
||||||
HorizontalBox {
|
|
||||||
alignment: center;
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
max-width: root.container-max-width;
|
|
||||||
|
|
||||||
VerticalBox {
|
|
||||||
padding: root.container-padding;
|
|
||||||
@children
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Grid Component
|
|
||||||
export component Grid {
|
|
||||||
in property <length> gap: 16px;
|
|
||||||
in property <int> columns: 2;
|
|
||||||
|
|
||||||
// Basic grid implementation - can be enhanced
|
|
||||||
VerticalBox {
|
|
||||||
spacing: root.gap;
|
|
||||||
@children
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
// Loading Component Public API
|
|
||||||
export { LoadingView } from "loading.slint";
|
|
||||||
|
|
@ -1,53 +1,177 @@
|
||||||
import { VerticalBox } from "std-widgets.slint";
|
import { ProgressIndicator, Button, Palette } from "std-widgets.slint";
|
||||||
|
|
||||||
// Loading component for app initialization
|
// Loading component for app initialization with health check
|
||||||
export component LoadingView {
|
export component LoadingView {
|
||||||
in-out property <color> primary-color: #2563eb;
|
// Health check state
|
||||||
in-out property <color> background-color: #f8fafc;
|
in-out property <bool> is-loading: true;
|
||||||
in-out property <color> text-color: #1e293b;
|
in-out property <bool> has-error: false;
|
||||||
|
in-out property <string> error-message: "Connection problem";
|
||||||
|
in-out property <string> status-text: "Initializing your trading environment...";
|
||||||
in-out property <string> app-version: "0.2.0";
|
in-out property <string> app-version: "0.2.0";
|
||||||
|
|
||||||
Rectangle {
|
// Callbacks
|
||||||
background: background-color;
|
callback retry-connection();
|
||||||
|
callback health-check-complete();
|
||||||
|
|
||||||
VerticalBox {
|
Rectangle {
|
||||||
|
background: Palette.background;
|
||||||
|
|
||||||
|
VerticalLayout {
|
||||||
alignment: center;
|
alignment: center;
|
||||||
spacing: 20px;
|
spacing: 32px;
|
||||||
|
padding: 40px;
|
||||||
|
|
||||||
|
// Logo section
|
||||||
|
VerticalLayout {
|
||||||
|
alignment: center;
|
||||||
|
spacing: 16px;
|
||||||
|
|
||||||
// Loading spinner
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: 60px;
|
width: 120px;
|
||||||
height: 60px;
|
height: 120px;
|
||||||
border-radius: 30px;
|
border-radius: 60px;
|
||||||
background: primary-color;
|
background: Palette.accent-background;
|
||||||
|
|
||||||
// Pulsing animation effect
|
states [
|
||||||
animate background {
|
error when has-error: {
|
||||||
duration: 1s;
|
background: #dc2626;
|
||||||
iteration-count: -1;
|
in {
|
||||||
easing: ease-in-out;
|
animate background { duration: 300ms; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "🚀";
|
||||||
|
font-size: 48px;
|
||||||
|
horizontal-alignment: center;
|
||||||
|
vertical-alignment: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "Ziya";
|
||||||
|
font-size: 48px;
|
||||||
|
color: Palette.foreground;
|
||||||
|
font-weight: 700;
|
||||||
|
horizontal-alignment: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "Your one-stop trading platform";
|
||||||
|
font-size: 16px;
|
||||||
|
color: Palette.foreground;
|
||||||
|
opacity: 0.7;
|
||||||
|
horizontal-alignment: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Status section
|
||||||
|
VerticalLayout {
|
||||||
|
alignment: center;
|
||||||
|
spacing: 24px;
|
||||||
|
|
||||||
|
if is-loading && !has-error: VerticalLayout {
|
||||||
|
alignment: center;
|
||||||
|
spacing: 16px;
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: "Loading Ziya";
|
text: "Loading Ziya";
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
color: text-color;
|
color: Palette.foreground;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
horizontal-alignment: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Using std ProgressIndicator
|
||||||
|
ProgressIndicator {
|
||||||
|
width: 200px;
|
||||||
|
height: 8px;
|
||||||
|
indeterminate: true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: "Initializing your trading environment...";
|
text: status-text;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: text-color;
|
color: Palette.foreground;
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
|
horizontal-alignment: center;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if has-error: VerticalLayout {
|
||||||
|
alignment: center;
|
||||||
|
spacing: 20px;
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "⚠️ Connection Failed";
|
||||||
|
font-size: 24px;
|
||||||
|
color: #dc2626;
|
||||||
|
font-weight: 600;
|
||||||
|
horizontal-alignment: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: error-message;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #dc2626;
|
||||||
|
horizontal-alignment: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "Please check your internet connection and try again";
|
||||||
|
font-size: 12px;
|
||||||
|
color: Palette.foreground;
|
||||||
|
opacity: 0.6;
|
||||||
|
horizontal-alignment: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
height: 45px;
|
||||||
|
width: 200px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: Palette.accent-background;
|
||||||
|
|
||||||
|
states [
|
||||||
|
pressed when retry-touch-area.pressed: {
|
||||||
|
background: Palette.selection-background;
|
||||||
|
in {
|
||||||
|
animate background { duration: 100ms; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hover when retry-touch-area.has-hover: {
|
||||||
|
background: Palette.control-background;
|
||||||
|
in {
|
||||||
|
animate background { duration: 150ms; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "🔄 Retry Connection";
|
||||||
|
color: Palette.accent-foreground;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
horizontal-alignment: center;
|
||||||
|
vertical-alignment: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
retry-touch-area := TouchArea {
|
||||||
|
clicked => {
|
||||||
|
retry-connection();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Version info at bottom
|
||||||
Text {
|
Text {
|
||||||
text: "Version " + app-version;
|
text: "Version " + app-version;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
color: text-color;
|
color: Palette.foreground;
|
||||||
opacity: 0.5;
|
opacity: 0.4;
|
||||||
|
horizontal-alignment: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,97 +1,210 @@
|
||||||
import { Theme } from "../../../shared/design-system/tokens/theme.slint";
|
import { Palette, ScrollView } from "std-widgets.slint";
|
||||||
|
|
||||||
// Navigation Widget (Sidebar)
|
// Navigation Widget (Sidebar)
|
||||||
export component NavigationWidget {
|
export component NavigationWidget {
|
||||||
|
width: 280px;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
in-out property <string> current-page: "Dashboard";
|
in-out property <string> current-page: "Dashboard";
|
||||||
in property <string> user-initials: "JD";
|
in-out property <string> user-initials: "JD";
|
||||||
|
in-out property <string> sidebar-state: "full";
|
||||||
|
|
||||||
callback navigation-changed(string);
|
callback navigation-changed(string);
|
||||||
callback logout-clicked();
|
callback logout-clicked();
|
||||||
|
callback toggle-sidebar();
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: 100%;
|
background: Palette.alternate-background;
|
||||||
|
|
||||||
|
// Right border using a separate rectangle
|
||||||
|
Rectangle {
|
||||||
|
width: 1px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background: Theme.nav-background;
|
x: parent.width - 1px;
|
||||||
|
background: Palette.border;
|
||||||
VerticalLayout {
|
}
|
||||||
padding: 16px;
|
|
||||||
spacing: 20px;
|
|
||||||
alignment: space-between;
|
|
||||||
|
|
||||||
// Top section with logo and navigation
|
|
||||||
VerticalLayout {
|
|
||||||
spacing: 20px;
|
|
||||||
|
|
||||||
// Logo Section
|
|
||||||
HorizontalLayout {
|
|
||||||
spacing: 12px;
|
|
||||||
alignment: start;
|
|
||||||
padding-top: 16px;
|
|
||||||
|
|
||||||
|
// Toggle button positioned in the middle of the sidebar edge
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: 40px;
|
width: 24px;
|
||||||
height: 40px;
|
height: 80px;
|
||||||
border-radius: 20px;
|
x: parent.width - 12px; // Half of the button extends outside the sidebar
|
||||||
background: Theme.primary;
|
y: (parent.height - 80px) / 2; // Center vertically
|
||||||
|
z: 10; // Ensure it's above other elements
|
||||||
|
border-radius: 12px;
|
||||||
|
background: Palette.alternate-background;
|
||||||
|
border-width: 1px;
|
||||||
|
border-color: Palette.border;
|
||||||
|
|
||||||
|
states [
|
||||||
|
pressed when toggle-touch-area.pressed: {
|
||||||
|
background: Palette.selection-background;
|
||||||
|
in {
|
||||||
|
animate background { duration: 100ms; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hover when toggle-touch-area.has-hover: {
|
||||||
|
background: Palette.accent-background;
|
||||||
|
x: parent.width - 18px; // Slide out slightly on hover
|
||||||
|
in {
|
||||||
|
animate background { duration: 150ms; }
|
||||||
|
animate x { duration: 150ms; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: "Z";
|
text: sidebar-state == "full" ? "‹" : "›";
|
||||||
color: white;
|
color: Palette.alternate-foreground;
|
||||||
font-size: 20px;
|
font-size: 16px;
|
||||||
font-weight: 700;
|
font-weight: 600;
|
||||||
|
horizontal-alignment: center;
|
||||||
|
vertical-alignment: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
toggle-touch-area := TouchArea {
|
||||||
|
clicked => {
|
||||||
|
root.toggle-sidebar();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
VerticalLayout {
|
||||||
|
spacing: 20px;
|
||||||
|
padding: 20px;
|
||||||
|
padding-bottom: 40px; // Extra padding to prevent logout cut-off
|
||||||
|
|
||||||
|
// User Profile Section - Only show in full mode
|
||||||
|
if sidebar-state == "full": Rectangle {
|
||||||
|
height: 80px;
|
||||||
|
background: Palette.control-background;
|
||||||
|
border-radius: 12px;
|
||||||
|
border-width: 1px;
|
||||||
|
border-color: Palette.border;
|
||||||
|
|
||||||
|
HorizontalLayout {
|
||||||
|
spacing: 12px;
|
||||||
|
padding: 16px;
|
||||||
|
alignment: start;
|
||||||
|
|
||||||
|
// User Avatar
|
||||||
|
Rectangle {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
border-radius: 24px;
|
||||||
|
background: Palette.accent-background;
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: user-initials;
|
||||||
|
color: Palette.accent-foreground;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
horizontal-alignment: center;
|
horizontal-alignment: center;
|
||||||
vertical-alignment: center;
|
vertical-alignment: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
VerticalLayout {
|
// User Info
|
||||||
spacing: 2px;
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "Ziya";
|
|
||||||
color: Theme.text-primary;
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
Text {
|
|
||||||
text: "Trading Platform";
|
|
||||||
color: Theme.text-secondary;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Navigation Menu
|
|
||||||
VerticalLayout {
|
VerticalLayout {
|
||||||
spacing: 4px;
|
spacing: 4px;
|
||||||
|
alignment: center;
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "Trading Account";
|
||||||
|
color: Palette.control-foreground;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
horizontal-alignment: left;
|
||||||
|
vertical-alignment: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "Active Status";
|
||||||
|
color: Palette.control-foreground;
|
||||||
|
font-size: 12px;
|
||||||
|
opacity: 0.7;
|
||||||
|
horizontal-alignment: left;
|
||||||
|
vertical-alignment: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// User Profile Section - Icon only mode
|
||||||
|
if sidebar-state == "icon-only": Rectangle {
|
||||||
|
height: 48px;
|
||||||
|
width: 48px;
|
||||||
|
background: Palette.control-background;
|
||||||
|
border-radius: 24px;
|
||||||
|
border-width: 1px;
|
||||||
|
border-color: Palette.border;
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: user-initials;
|
||||||
|
color: Palette.accent-foreground;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
horizontal-alignment: center;
|
||||||
|
vertical-alignment: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Navigation Items
|
||||||
|
ScrollView {
|
||||||
|
VerticalLayout {
|
||||||
|
spacing: 8px;
|
||||||
|
|
||||||
// Dashboard
|
// Dashboard
|
||||||
Rectangle {
|
Rectangle {
|
||||||
height: 44px;
|
height: 48px;
|
||||||
|
background: current-page == "Dashboard" ? Palette.selection-background : transparent;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
background: current-page == "Dashboard" ? Theme.nav-active : transparent;
|
border-width: current-page == "Dashboard" ? 1px : 0px;
|
||||||
|
border-color: current-page == "Dashboard" ? Palette.border : transparent;
|
||||||
|
|
||||||
HorizontalLayout {
|
states [
|
||||||
padding: 12px;
|
pressed when dashboard-touch-area.pressed: {
|
||||||
|
background: Palette.selection-background;
|
||||||
|
in {
|
||||||
|
animate background { duration: 100ms; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hover when dashboard-touch-area.has-hover && current-page != "Dashboard": {
|
||||||
|
background: Palette.control-background;
|
||||||
|
in {
|
||||||
|
animate background { duration: 150ms; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
if sidebar-state == "full": HorizontalLayout {
|
||||||
spacing: 12px;
|
spacing: 12px;
|
||||||
|
padding: 12px;
|
||||||
alignment: start;
|
alignment: start;
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: "📊";
|
text: "📊";
|
||||||
font-size: 16px;
|
font-size: 20px;
|
||||||
vertical-alignment: center;
|
vertical-alignment: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: "Dashboard";
|
text: "Dashboard";
|
||||||
color: current-page == "Dashboard" ? white : Theme.text-primary;
|
color: current-page == "Dashboard" ? Palette.selection-foreground : Palette.alternate-foreground;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: current-page == "Dashboard" ? 600 : 400;
|
font-weight: current-page == "Dashboard" ? 600 : 400;
|
||||||
vertical-alignment: center;
|
vertical-alignment: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TouchArea {
|
if sidebar-state == "icon-only": Text {
|
||||||
|
text: "📊";
|
||||||
|
font-size: 20px;
|
||||||
|
horizontal-alignment: center;
|
||||||
|
vertical-alignment: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
dashboard-touch-area := TouchArea {
|
||||||
clicked => {
|
clicked => {
|
||||||
root.current-page = "Dashboard";
|
root.current-page = "Dashboard";
|
||||||
root.navigation-changed("Dashboard");
|
root.navigation-changed("Dashboard");
|
||||||
|
|
@ -99,33 +212,115 @@ export component NavigationWidget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trading
|
// Hunting Ground
|
||||||
Rectangle {
|
Rectangle {
|
||||||
height: 44px;
|
height: 48px;
|
||||||
|
background: current-page == "Hunting Ground" ? Palette.selection-background : transparent;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
background: current-page == "Trading" ? Theme.nav-active : transparent;
|
border-width: current-page == "Hunting Ground" ? 1px : 0px;
|
||||||
|
border-color: current-page == "Hunting Ground" ? Palette.border : transparent;
|
||||||
|
|
||||||
HorizontalLayout {
|
states [
|
||||||
padding: 12px;
|
pressed when hunting-touch-area.pressed: {
|
||||||
|
background: Palette.selection-background;
|
||||||
|
in {
|
||||||
|
animate background { duration: 100ms; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hover when hunting-touch-area.has-hover && current-page != "Hunting Ground": {
|
||||||
|
background: Palette.control-background;
|
||||||
|
in {
|
||||||
|
animate background { duration: 150ms; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
if sidebar-state == "full": HorizontalLayout {
|
||||||
spacing: 12px;
|
spacing: 12px;
|
||||||
|
padding: 12px;
|
||||||
alignment: start;
|
alignment: start;
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: "💹";
|
text: "🎯";
|
||||||
font-size: 16px;
|
font-size: 20px;
|
||||||
|
vertical-alignment: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "Hunting Ground";
|
||||||
|
color: current-page == "Hunting Ground" ? Palette.selection-foreground : Palette.alternate-foreground;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: current-page == "Hunting Ground" ? 600 : 400;
|
||||||
|
vertical-alignment: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if sidebar-state == "icon-only": Text {
|
||||||
|
text: "🎯";
|
||||||
|
font-size: 20px;
|
||||||
|
horizontal-alignment: center;
|
||||||
|
vertical-alignment: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
hunting-touch-area := TouchArea {
|
||||||
|
clicked => {
|
||||||
|
root.current-page = "Hunting Ground";
|
||||||
|
root.navigation-changed("Hunting Ground");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trading
|
||||||
|
Rectangle {
|
||||||
|
height: 48px;
|
||||||
|
background: current-page == "Trading" ? Palette.selection-background : transparent;
|
||||||
|
border-radius: 8px;
|
||||||
|
border-width: current-page == "Trading" ? 1px : 0px;
|
||||||
|
border-color: current-page == "Trading" ? Palette.border : transparent;
|
||||||
|
|
||||||
|
states [
|
||||||
|
pressed when trading-touch-area.pressed: {
|
||||||
|
background: Palette.selection-background;
|
||||||
|
in {
|
||||||
|
animate background { duration: 100ms; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hover when trading-touch-area.has-hover && current-page != "Trading": {
|
||||||
|
background: Palette.control-background;
|
||||||
|
in {
|
||||||
|
animate background { duration: 150ms; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
if sidebar-state == "full": HorizontalLayout {
|
||||||
|
spacing: 12px;
|
||||||
|
padding: 12px;
|
||||||
|
alignment: start;
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "⚡";
|
||||||
|
font-size: 20px;
|
||||||
vertical-alignment: center;
|
vertical-alignment: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: "Trading";
|
text: "Trading";
|
||||||
color: current-page == "Trading" ? white : Theme.text-primary;
|
color: current-page == "Trading" ? Palette.selection-foreground : Palette.alternate-foreground;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: current-page == "Trading" ? 600 : 400;
|
font-weight: current-page == "Trading" ? 600 : 400;
|
||||||
vertical-alignment: center;
|
vertical-alignment: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TouchArea {
|
if sidebar-state == "icon-only": Text {
|
||||||
|
text: "⚡";
|
||||||
|
font-size: 20px;
|
||||||
|
horizontal-alignment: center;
|
||||||
|
vertical-alignment: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
trading-touch-area := TouchArea {
|
||||||
clicked => {
|
clicked => {
|
||||||
root.current-page = "Trading";
|
root.current-page = "Trading";
|
||||||
root.navigation-changed("Trading");
|
root.navigation-changed("Trading");
|
||||||
|
|
@ -135,31 +330,55 @@ export component NavigationWidget {
|
||||||
|
|
||||||
// Portfolio
|
// Portfolio
|
||||||
Rectangle {
|
Rectangle {
|
||||||
height: 44px;
|
height: 48px;
|
||||||
|
background: current-page == "Portfolio" ? Palette.selection-background : transparent;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
background: current-page == "Portfolio" ? Theme.nav-active : transparent;
|
border-width: current-page == "Portfolio" ? 1px : 0px;
|
||||||
|
border-color: current-page == "Portfolio" ? Palette.border : transparent;
|
||||||
|
|
||||||
HorizontalLayout {
|
states [
|
||||||
padding: 12px;
|
pressed when portfolio-touch-area.pressed: {
|
||||||
|
background: Palette.selection-background;
|
||||||
|
in {
|
||||||
|
animate background { duration: 100ms; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hover when portfolio-touch-area.has-hover && current-page != "Portfolio": {
|
||||||
|
background: Palette.control-background;
|
||||||
|
in {
|
||||||
|
animate background { duration: 150ms; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
if sidebar-state == "full": HorizontalLayout {
|
||||||
spacing: 12px;
|
spacing: 12px;
|
||||||
|
padding: 12px;
|
||||||
alignment: start;
|
alignment: start;
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: "💼";
|
text: "💼";
|
||||||
font-size: 16px;
|
font-size: 20px;
|
||||||
vertical-alignment: center;
|
vertical-alignment: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: "Portfolio";
|
text: "Portfolio";
|
||||||
color: current-page == "Portfolio" ? white : Theme.text-primary;
|
color: current-page == "Portfolio" ? Palette.selection-foreground : Palette.alternate-foreground;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: current-page == "Portfolio" ? 600 : 400;
|
font-weight: current-page == "Portfolio" ? 600 : 400;
|
||||||
vertical-alignment: center;
|
vertical-alignment: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TouchArea {
|
if sidebar-state == "icon-only": Text {
|
||||||
|
text: "💼";
|
||||||
|
font-size: 20px;
|
||||||
|
horizontal-alignment: center;
|
||||||
|
vertical-alignment: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
portfolio-touch-area := TouchArea {
|
||||||
clicked => {
|
clicked => {
|
||||||
root.current-page = "Portfolio";
|
root.current-page = "Portfolio";
|
||||||
root.navigation-changed("Portfolio");
|
root.navigation-changed("Portfolio");
|
||||||
|
|
@ -169,121 +388,118 @@ export component NavigationWidget {
|
||||||
|
|
||||||
// Markets
|
// Markets
|
||||||
Rectangle {
|
Rectangle {
|
||||||
height: 44px;
|
height: 48px;
|
||||||
|
background: current-page == "Markets" ? Palette.selection-background : transparent;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
background: current-page == "Markets" ? Theme.nav-active : transparent;
|
border-width: current-page == "Markets" ? 1px : 0px;
|
||||||
|
border-color: current-page == "Markets" ? Palette.border : transparent;
|
||||||
|
|
||||||
HorizontalLayout {
|
states [
|
||||||
padding: 12px;
|
pressed when markets-touch-area.pressed: {
|
||||||
|
background: Palette.selection-background;
|
||||||
|
in {
|
||||||
|
animate background { duration: 100ms; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hover when markets-touch-area.has-hover && current-page != "Markets": {
|
||||||
|
background: Palette.control-background;
|
||||||
|
in {
|
||||||
|
animate background { duration: 150ms; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
if sidebar-state == "full": HorizontalLayout {
|
||||||
spacing: 12px;
|
spacing: 12px;
|
||||||
|
padding: 12px;
|
||||||
alignment: start;
|
alignment: start;
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: "📈";
|
text: "📈";
|
||||||
font-size: 16px;
|
font-size: 20px;
|
||||||
vertical-alignment: center;
|
vertical-alignment: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: "Markets";
|
text: "Markets";
|
||||||
color: current-page == "Markets" ? white : Theme.text-primary;
|
color: current-page == "Markets" ? Palette.selection-foreground : Palette.alternate-foreground;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: current-page == "Markets" ? 600 : 400;
|
font-weight: current-page == "Markets" ? 600 : 400;
|
||||||
vertical-alignment: center;
|
vertical-alignment: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TouchArea {
|
if sidebar-state == "icon-only": Text {
|
||||||
|
text: "📈";
|
||||||
|
font-size: 20px;
|
||||||
|
horizontal-alignment: center;
|
||||||
|
vertical-alignment: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
markets-touch-area := TouchArea {
|
||||||
clicked => {
|
clicked => {
|
||||||
root.current-page = "Markets";
|
root.current-page = "Markets";
|
||||||
root.navigation-changed("Markets");
|
root.navigation-changed("Markets");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Hunting Ground
|
// Logout Section
|
||||||
Rectangle {
|
Rectangle {
|
||||||
height: 44px;
|
height: 48px;
|
||||||
|
background: transparent;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
background: current-page == "HuntingGround" ? Theme.nav-active : transparent;
|
|
||||||
|
|
||||||
HorizontalLayout {
|
states [
|
||||||
padding: 12px;
|
pressed when logout-touch-area.pressed: {
|
||||||
|
background: Palette.selection-background;
|
||||||
|
in {
|
||||||
|
animate background { duration: 100ms; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hover when logout-touch-area.has-hover: {
|
||||||
|
background: Palette.control-background;
|
||||||
|
in {
|
||||||
|
animate background { duration: 150ms; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
if sidebar-state == "full": HorizontalLayout {
|
||||||
spacing: 12px;
|
spacing: 12px;
|
||||||
|
padding: 12px;
|
||||||
alignment: start;
|
alignment: start;
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: "🎯";
|
text: "🚪";
|
||||||
font-size: 16px;
|
font-size: 20px;
|
||||||
vertical-alignment: center;
|
vertical-alignment: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "Hunting Ground";
|
|
||||||
color: current-page == "HuntingGround" ? white : Theme.text-primary;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: current-page == "HuntingGround" ? 600 : 400;
|
|
||||||
vertical-alignment: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TouchArea {
|
|
||||||
clicked => {
|
|
||||||
root.current-page = "HuntingGround";
|
|
||||||
root.navigation-changed("HuntingGround");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// User Profile Section (bottom)
|
|
||||||
Rectangle {
|
|
||||||
height: 60px;
|
|
||||||
border-radius: 8px;
|
|
||||||
background: Theme.surface;
|
|
||||||
|
|
||||||
HorizontalLayout {
|
|
||||||
padding: 12px;
|
|
||||||
spacing: 12px;
|
|
||||||
alignment: start;
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: 36px;
|
|
||||||
height: 36px;
|
|
||||||
border-radius: 18px;
|
|
||||||
background: Theme.primary;
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: user-initials;
|
|
||||||
color: white;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 600;
|
|
||||||
horizontal-alignment: center;
|
|
||||||
vertical-alignment: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
VerticalLayout {
|
|
||||||
spacing: 2px;
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "User";
|
|
||||||
color: Theme.text-primary;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: "Logout";
|
text: "Logout";
|
||||||
color: Theme.text-secondary;
|
color: Palette.alternate-foreground;
|
||||||
font-size: 12px;
|
font-size: 14px;
|
||||||
}
|
font-weight: 400;
|
||||||
|
vertical-alignment: center;
|
||||||
|
opacity: 0.8;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TouchArea {
|
if sidebar-state == "icon-only": Text {
|
||||||
clicked => { root.logout-clicked(); }
|
text: "🚪";
|
||||||
|
font-size: 20px;
|
||||||
|
horizontal-alignment: center;
|
||||||
|
vertical-alignment: center;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
logout-touch-area := TouchArea {
|
||||||
|
clicked => {
|
||||||
|
root.logout-clicked();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,178 +0,0 @@
|
||||||
import { VerticalBox, HorizontalBox, ScrollView } from "std-widgets.slint";
|
|
||||||
|
|
||||||
// Navigation menu item data structure
|
|
||||||
export struct MenuItem {
|
|
||||||
name: string,
|
|
||||||
icon: string,
|
|
||||||
active: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Professional sidebar navigation with direct styling
|
|
||||||
export component Sidebar {
|
|
||||||
in property <[MenuItem]> menu-items: [
|
|
||||||
{ name: "Dashboard", icon: "📊", active: true },
|
|
||||||
{ name: "Profile", icon: "👤", active: false },
|
|
||||||
{ name: "Trading", icon: "💹", active: false },
|
|
||||||
{ name: "Portfolio", icon: "💼", active: false },
|
|
||||||
{ name: "Markets", icon: "📈", active: false },
|
|
||||||
{ name: "Hunting Ground", icon: "🎯", active: false },
|
|
||||||
{ name: "Analytics", icon: "📊", active: false },
|
|
||||||
];
|
|
||||||
|
|
||||||
callback menu-item-clicked(string);
|
|
||||||
|
|
||||||
width: 256px;
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
background: #f2f2f2;
|
|
||||||
border-width: 0px;
|
|
||||||
border-color: #e5e6e6;
|
|
||||||
|
|
||||||
ScrollView {
|
|
||||||
viewport-height: root.height;
|
|
||||||
|
|
||||||
VerticalBox {
|
|
||||||
padding: 16px;
|
|
||||||
spacing: 8px;
|
|
||||||
|
|
||||||
// Sidebar Header
|
|
||||||
Rectangle {
|
|
||||||
height: 60px;
|
|
||||||
|
|
||||||
VerticalBox {
|
|
||||||
alignment: center;
|
|
||||||
spacing: 8px;
|
|
||||||
|
|
||||||
// Logo area
|
|
||||||
Rectangle {
|
|
||||||
height: 32px;
|
|
||||||
|
|
||||||
HorizontalBox {
|
|
||||||
alignment: center;
|
|
||||||
spacing: 8px;
|
|
||||||
|
|
||||||
// App icon
|
|
||||||
Rectangle {
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
background: #570df8;
|
|
||||||
border-radius: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "Ziya";
|
|
||||||
color: #1f2937;
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Divider
|
|
||||||
Rectangle {
|
|
||||||
height: 1px;
|
|
||||||
background: #e5e6e6;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Menu Items
|
|
||||||
for item[index] in menu-items: Rectangle {
|
|
||||||
height: 44px;
|
|
||||||
border-radius: 8px;
|
|
||||||
|
|
||||||
// Active/hover state styling
|
|
||||||
background: item.active ? #570df8 : transparent;
|
|
||||||
|
|
||||||
states [
|
|
||||||
active when item.active: {
|
|
||||||
background: #570df8;
|
|
||||||
}
|
|
||||||
hover when menu-area.has-hover && !item.active: {
|
|
||||||
background: #e5e6e6;
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
animate background { duration: 150ms; }
|
|
||||||
|
|
||||||
HorizontalBox {
|
|
||||||
padding-left: 16px;
|
|
||||||
padding-right: 16px;
|
|
||||||
alignment: start;
|
|
||||||
spacing: 12px;
|
|
||||||
|
|
||||||
// Icon
|
|
||||||
Text {
|
|
||||||
text: item.icon;
|
|
||||||
font-size: 16px;
|
|
||||||
vertical-alignment: center;
|
|
||||||
width: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Menu item text
|
|
||||||
Text {
|
|
||||||
text: item.name;
|
|
||||||
color: item.active ? #ffffff : #1f2937;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: item.active ? 600 : 500;
|
|
||||||
vertical-alignment: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Active indicator
|
|
||||||
if item.active: Rectangle {
|
|
||||||
width: 4px;
|
|
||||||
height: 20px;
|
|
||||||
background: #ffffff;
|
|
||||||
border-radius: 2px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
menu-area := TouchArea {
|
|
||||||
clicked => { menu-item-clicked(item.name); }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Spacer to push footer to bottom
|
|
||||||
Rectangle {
|
|
||||||
height: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sidebar Footer
|
|
||||||
Rectangle {
|
|
||||||
height: 60px;
|
|
||||||
|
|
||||||
VerticalBox {
|
|
||||||
alignment: center;
|
|
||||||
spacing: 8px;
|
|
||||||
|
|
||||||
// Divider
|
|
||||||
Rectangle {
|
|
||||||
height: 1px;
|
|
||||||
background: #e5e6e6;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Status section
|
|
||||||
HorizontalBox {
|
|
||||||
alignment: center;
|
|
||||||
spacing: 8px;
|
|
||||||
|
|
||||||
// Status indicator
|
|
||||||
Rectangle {
|
|
||||||
width: 8px;
|
|
||||||
height: 8px;
|
|
||||||
background: #36d399;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "Connected";
|
|
||||||
color: #6b7280;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
193
ui/widgets/token-column/ui/analysis-complete-column.slint
Normal file
193
ui/widgets/token-column/ui/analysis-complete-column.slint
Normal file
|
|
@ -0,0 +1,193 @@
|
||||||
|
import { Palette, ScrollView } from "std-widgets.slint";
|
||||||
|
import { MaxDepthReachedUiData } from "../../../shared/types/token.slint";
|
||||||
|
|
||||||
|
export component AnalysisCompleteColumn {
|
||||||
|
width: 100%; // Will be constrained by parent
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
in property <[MaxDepthReachedUiData]> tokens: [];
|
||||||
|
callback clear-tokens();
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
background: Palette.alternate-background;
|
||||||
|
border-radius: 12px;
|
||||||
|
border-width: 1px;
|
||||||
|
border-color: Palette.border;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
VerticalLayout {
|
||||||
|
spacing: 0px;
|
||||||
|
|
||||||
|
// Column Header
|
||||||
|
Rectangle {
|
||||||
|
height: 48px;
|
||||||
|
background: Palette.control-background;
|
||||||
|
border-radius: 12px;
|
||||||
|
border-width: 1px;
|
||||||
|
border-color: Palette.border;
|
||||||
|
|
||||||
|
HorizontalLayout {
|
||||||
|
alignment: space-between;
|
||||||
|
padding: 12px;
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "✅ Analysis Complete (" + tokens.length + ")";
|
||||||
|
color: Palette.control-foreground;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
vertical-alignment: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
background: #f56565;
|
||||||
|
border-radius: 10px;
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "×";
|
||||||
|
color: white;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
horizontal-alignment: center;
|
||||||
|
vertical-alignment: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
TouchArea {
|
||||||
|
clicked => { root.clear-tokens(); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scrollable content area
|
||||||
|
ScrollView {
|
||||||
|
height: parent.height - 48px;
|
||||||
|
|
||||||
|
VerticalLayout {
|
||||||
|
spacing: 8px;
|
||||||
|
padding: 8px;
|
||||||
|
padding-bottom: 16px;
|
||||||
|
|
||||||
|
// Analysis token cards
|
||||||
|
for token[i] in tokens: Rectangle {
|
||||||
|
min-height: 80px;
|
||||||
|
background: Palette.background;
|
||||||
|
border-width: 1px;
|
||||||
|
border-color: Palette.border;
|
||||||
|
border-radius: 8px;
|
||||||
|
|
||||||
|
states [
|
||||||
|
pressed when touch-area.pressed: {
|
||||||
|
background: Palette.selection-background;
|
||||||
|
in {
|
||||||
|
animate background { duration: 100ms; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hover when touch-area.has-hover: {
|
||||||
|
background: Palette.control-background;
|
||||||
|
border-color: #10b981;
|
||||||
|
in {
|
||||||
|
animate background { duration: 150ms; }
|
||||||
|
animate border-color { duration: 150ms; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
// Left border accent (green for complete)
|
||||||
|
Rectangle {
|
||||||
|
width: 3px;
|
||||||
|
height: 100%;
|
||||||
|
background: #10b981;
|
||||||
|
x: 0px;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
HorizontalLayout {
|
||||||
|
padding: 12px;
|
||||||
|
spacing: 12px;
|
||||||
|
|
||||||
|
// Token avatar
|
||||||
|
Rectangle {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: #10b981;
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "✓";
|
||||||
|
color: white;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
horizontal-alignment: center;
|
||||||
|
vertical-alignment: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Token info
|
||||||
|
VerticalLayout {
|
||||||
|
spacing: 6px;
|
||||||
|
alignment: start;
|
||||||
|
|
||||||
|
// Name and completion status
|
||||||
|
HorizontalLayout {
|
||||||
|
spacing: 8px;
|
||||||
|
alignment: start;
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: token.name;
|
||||||
|
color: Palette.foreground;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
vertical-alignment: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
height: 18px;
|
||||||
|
border-radius: 9px;
|
||||||
|
background: #10b981;
|
||||||
|
padding-left: 8px;
|
||||||
|
padding-right: 8px;
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "Complete";
|
||||||
|
color: white;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 600;
|
||||||
|
horizontal-alignment: center;
|
||||||
|
vertical-alignment: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mint address
|
||||||
|
Text {
|
||||||
|
text: token.mint;
|
||||||
|
color: Palette.foreground;
|
||||||
|
font-size: 11px;
|
||||||
|
wrap: word-wrap;
|
||||||
|
opacity: 0.7;
|
||||||
|
vertical-alignment: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Icons and timestamp
|
||||||
|
Text {
|
||||||
|
text: "";
|
||||||
|
color: Palette.foreground;
|
||||||
|
font-size: 11px;
|
||||||
|
opacity: 0.7;
|
||||||
|
vertical-alignment: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
touch-area := TouchArea {
|
||||||
|
// Add click functionality here if needed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
193
ui/widgets/token-column/ui/cex-tokens-column.slint
Normal file
193
ui/widgets/token-column/ui/cex-tokens-column.slint
Normal file
|
|
@ -0,0 +1,193 @@
|
||||||
|
import { Palette, ScrollView } from "std-widgets.slint";
|
||||||
|
import { CexUpdatedUiData } from "../../../shared/types/token.slint";
|
||||||
|
|
||||||
|
export component CexTokensColumn {
|
||||||
|
width: 100%; // Will be constrained by parent
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
in property <[CexUpdatedUiData]> tokens: [];
|
||||||
|
callback clear-tokens();
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
background: Palette.alternate-background;
|
||||||
|
border-radius: 12px;
|
||||||
|
border-width: 1px;
|
||||||
|
border-color: Palette.border;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
VerticalLayout {
|
||||||
|
spacing: 0px;
|
||||||
|
|
||||||
|
// Column Header
|
||||||
|
Rectangle {
|
||||||
|
height: 48px;
|
||||||
|
background: Palette.control-background;
|
||||||
|
border-radius: 12px;
|
||||||
|
border-width: 1px;
|
||||||
|
border-color: Palette.border;
|
||||||
|
|
||||||
|
HorizontalLayout {
|
||||||
|
alignment: space-between;
|
||||||
|
padding: 12px;
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "📊 CEX Analyst (" + tokens.length + ")";
|
||||||
|
color: Palette.control-foreground;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
vertical-alignment: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
background: #f56565;
|
||||||
|
border-radius: 10px;
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "×";
|
||||||
|
color: white;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
horizontal-alignment: center;
|
||||||
|
vertical-alignment: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
TouchArea {
|
||||||
|
clicked => { root.clear-tokens(); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scrollable content area
|
||||||
|
ScrollView {
|
||||||
|
height: parent.height - 48px;
|
||||||
|
|
||||||
|
VerticalLayout {
|
||||||
|
spacing: 8px;
|
||||||
|
padding: 8px;
|
||||||
|
padding-bottom: 16px;
|
||||||
|
|
||||||
|
// CEX token cards
|
||||||
|
for token[i] in tokens: Rectangle {
|
||||||
|
min-height: 80px;
|
||||||
|
background: Palette.background;
|
||||||
|
border-width: 1px;
|
||||||
|
border-color: Palette.border;
|
||||||
|
border-radius: 8px;
|
||||||
|
|
||||||
|
states [
|
||||||
|
pressed when touch-area.pressed: {
|
||||||
|
background: Palette.selection-background;
|
||||||
|
in {
|
||||||
|
animate background { duration: 100ms; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hover when touch-area.has-hover: {
|
||||||
|
background: Palette.control-background;
|
||||||
|
border-color: #f59e0b;
|
||||||
|
in {
|
||||||
|
animate background { duration: 150ms; }
|
||||||
|
animate border-color { duration: 150ms; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
// Left border accent (orange for CEX)
|
||||||
|
Rectangle {
|
||||||
|
width: 3px;
|
||||||
|
height: 100%;
|
||||||
|
background: #f59e0b;
|
||||||
|
x: 0px;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
HorizontalLayout {
|
||||||
|
padding: 12px;
|
||||||
|
spacing: 12px;
|
||||||
|
|
||||||
|
// Token avatar
|
||||||
|
Rectangle {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: #f59e0b;
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "C";
|
||||||
|
color: white;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
horizontal-alignment: center;
|
||||||
|
vertical-alignment: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Token info
|
||||||
|
VerticalLayout {
|
||||||
|
spacing: 6px;
|
||||||
|
alignment: start;
|
||||||
|
|
||||||
|
// Name and CEX info
|
||||||
|
HorizontalLayout {
|
||||||
|
spacing: 8px;
|
||||||
|
alignment: start;
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: token.name;
|
||||||
|
color: Palette.foreground;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
vertical-alignment: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
height: 18px;
|
||||||
|
border-radius: 9px;
|
||||||
|
background: #f59e0b;
|
||||||
|
padding-left: 8px;
|
||||||
|
padding-right: 8px;
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: token.cex-name;
|
||||||
|
color: white;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 600;
|
||||||
|
horizontal-alignment: center;
|
||||||
|
vertical-alignment: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mint address
|
||||||
|
Text {
|
||||||
|
text: token.mint;
|
||||||
|
color: Palette.foreground;
|
||||||
|
font-size: 11px;
|
||||||
|
wrap: word-wrap;
|
||||||
|
opacity: 0.7;
|
||||||
|
vertical-alignment: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Icons and timestamp
|
||||||
|
Text {
|
||||||
|
text: "";
|
||||||
|
color: Palette.foreground;
|
||||||
|
font-size: 11px;
|
||||||
|
opacity: 0.7;
|
||||||
|
vertical-alignment: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
touch-area := TouchArea {
|
||||||
|
// Add click functionality here if needed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
6
ui/widgets/token-column/ui/mod.slint
Normal file
6
ui/widgets/token-column/ui/mod.slint
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
// Token Column Widgets Module
|
||||||
|
// Following Feature-Sliced Design - widgets layer
|
||||||
|
|
||||||
|
export { NewTokensColumn } from "new-tokens-column.slint";
|
||||||
|
export { CexTokensColumn } from "cex-tokens-column.slint";
|
||||||
|
export { AnalysisCompleteColumn } from "analysis-complete-column.slint";
|
||||||
193
ui/widgets/token-column/ui/new-tokens-column.slint
Normal file
193
ui/widgets/token-column/ui/new-tokens-column.slint
Normal file
|
|
@ -0,0 +1,193 @@
|
||||||
|
import { Palette, ScrollView } from "std-widgets.slint";
|
||||||
|
import { NewTokenUiData } from "../../../shared/types/token.slint";
|
||||||
|
|
||||||
|
export component NewTokensColumn {
|
||||||
|
width: 100%; // Will be constrained by parent
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
in property <[NewTokenUiData]> tokens: [];
|
||||||
|
callback clear-tokens();
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
background: Palette.alternate-background;
|
||||||
|
border-radius: 12px;
|
||||||
|
border-width: 1px;
|
||||||
|
border-color: Palette.border;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
VerticalLayout {
|
||||||
|
spacing: 0px;
|
||||||
|
|
||||||
|
// Column Header
|
||||||
|
Rectangle {
|
||||||
|
height: 48px;
|
||||||
|
background: Palette.control-background;
|
||||||
|
border-radius: 12px;
|
||||||
|
border-width: 1px;
|
||||||
|
border-color: Palette.border;
|
||||||
|
|
||||||
|
HorizontalLayout {
|
||||||
|
alignment: space-between;
|
||||||
|
padding: 12px;
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "🆕 New Tokens (" + tokens.length + ")";
|
||||||
|
color: Palette.control-foreground;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
vertical-alignment: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
background: #f56565;
|
||||||
|
border-radius: 10px;
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "×";
|
||||||
|
color: white;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
horizontal-alignment: center;
|
||||||
|
vertical-alignment: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
TouchArea {
|
||||||
|
clicked => { root.clear-tokens(); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scrollable content area
|
||||||
|
ScrollView {
|
||||||
|
height: parent.height - 48px;
|
||||||
|
|
||||||
|
VerticalLayout {
|
||||||
|
spacing: 8px;
|
||||||
|
padding: 8px;
|
||||||
|
padding-bottom: 16px;
|
||||||
|
|
||||||
|
// New token cards
|
||||||
|
for token[i] in tokens: Rectangle {
|
||||||
|
min-height: 80px;
|
||||||
|
background: Palette.background;
|
||||||
|
border-width: 1px;
|
||||||
|
border-color: Palette.border;
|
||||||
|
border-radius: 8px;
|
||||||
|
|
||||||
|
states [
|
||||||
|
pressed when touch-area.pressed: {
|
||||||
|
background: Palette.selection-background;
|
||||||
|
in {
|
||||||
|
animate background { duration: 100ms; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hover when touch-area.has-hover: {
|
||||||
|
background: Palette.control-background;
|
||||||
|
border-color: Palette.accent-background;
|
||||||
|
in {
|
||||||
|
animate background { duration: 150ms; }
|
||||||
|
animate border-color { duration: 150ms; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
// Left border accent
|
||||||
|
Rectangle {
|
||||||
|
width: 3px;
|
||||||
|
height: 100%;
|
||||||
|
background: Palette.accent-background;
|
||||||
|
x: 0px;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
HorizontalLayout {
|
||||||
|
padding: 12px;
|
||||||
|
spacing: 12px;
|
||||||
|
|
||||||
|
// Token avatar
|
||||||
|
Rectangle {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: Palette.accent-background;
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: token.symbol != "" ? token.symbol : "?";
|
||||||
|
color: Palette.accent-foreground;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
horizontal-alignment: center;
|
||||||
|
vertical-alignment: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Token info
|
||||||
|
VerticalLayout {
|
||||||
|
spacing: 6px;
|
||||||
|
alignment: start;
|
||||||
|
|
||||||
|
// Name and symbol
|
||||||
|
HorizontalLayout {
|
||||||
|
spacing: 8px;
|
||||||
|
alignment: start;
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: token.name;
|
||||||
|
color: Palette.foreground;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
vertical-alignment: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
if token.symbol != "": Rectangle {
|
||||||
|
height: 18px;
|
||||||
|
border-radius: 9px;
|
||||||
|
background: Palette.accent-background;
|
||||||
|
padding-left: 8px;
|
||||||
|
padding-right: 8px;
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: token.symbol;
|
||||||
|
color: Palette.accent-foreground;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 600;
|
||||||
|
horizontal-alignment: center;
|
||||||
|
vertical-alignment: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mint address
|
||||||
|
Text {
|
||||||
|
text: token.mint;
|
||||||
|
color: Palette.foreground;
|
||||||
|
font-size: 11px;
|
||||||
|
wrap: word-wrap;
|
||||||
|
opacity: 0.7;
|
||||||
|
vertical-alignment: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Icons and timestamp
|
||||||
|
Text {
|
||||||
|
text: "";
|
||||||
|
color: Palette.foreground;
|
||||||
|
font-size: 11px;
|
||||||
|
opacity: 0.7;
|
||||||
|
vertical-alignment: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
touch-area := TouchArea {
|
||||||
|
// Add click functionality here if needed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,2 +1,2 @@
|
||||||
// Window Controls Widget Public API
|
// Window Controls Widget Public API
|
||||||
export { WindowControlsWidget } from "ui/window-controls-widget.slint";
|
export { TitleBar } from "ui/title-bar.slint";
|
||||||
|
|
@ -1,108 +1,106 @@
|
||||||
import { Button, HorizontalBox } from "std-widgets.slint";
|
import { Palette } from "std-widgets.slint";
|
||||||
import { Theme } from "../../../shared/design-system/tokens/theme.slint";
|
|
||||||
|
|
||||||
// Professional title bar component with theme support
|
// Professional title bar component with theme support
|
||||||
export component TitleBar {
|
export component TitleBar {
|
||||||
// Window state
|
// Window state
|
||||||
in-out property <bool> is-maximized: false;
|
in-out property <bool> is-maximized: false;
|
||||||
|
in-out property <bool> is-dark-theme: true;
|
||||||
|
|
||||||
// Callbacks for window controls
|
// Callbacks for window controls
|
||||||
callback minimize-window();
|
callback minimize-window();
|
||||||
callback maximize-window();
|
callback maximize-window();
|
||||||
callback close-window();
|
callback close-window();
|
||||||
callback toggle-theme();
|
callback toggle-theme();
|
||||||
callback start-drag-window();
|
|
||||||
|
|
||||||
height: 32px;
|
height: 40px;
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
background: Theme.surface;
|
background: Palette.alternate-background;
|
||||||
border-width: 0px;
|
|
||||||
border-color: Theme.border;
|
|
||||||
|
|
||||||
// Drag area for the title bar
|
// Bottom border using a separate rectangle
|
||||||
drag-area := TouchArea {
|
Rectangle {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 1px;
|
||||||
|
y: parent.height - 1px;
|
||||||
moved => {
|
background: Palette.border;
|
||||||
if (self.pressed) {
|
|
||||||
start-drag-window();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
HorizontalBox {
|
HorizontalLayout {
|
||||||
padding: 16px;
|
spacing: 0px;
|
||||||
alignment: stretch;
|
alignment: space-between;
|
||||||
|
padding-left: 16px;
|
||||||
|
padding-right: 8px;
|
||||||
|
padding-top: 8px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
|
||||||
// Left side - App branding
|
// Left side - App title and drag area
|
||||||
HorizontalBox {
|
HorizontalLayout {
|
||||||
|
spacing: 12px;
|
||||||
alignment: start;
|
alignment: start;
|
||||||
spacing: 8px;
|
|
||||||
|
|
||||||
// App icon
|
// App icon
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: 20px;
|
width: 24px;
|
||||||
height: 20px;
|
height: 24px;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: Palette.accent-background;
|
||||||
|
|
||||||
// Ziya logo (layered rectangles)
|
|
||||||
Rectangle {
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
background: #570df8;
|
|
||||||
border-radius: 3px;
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
x: 2px;
|
|
||||||
y: 2px;
|
|
||||||
width: 12px;
|
|
||||||
height: 12px;
|
|
||||||
background: #4506cb;
|
|
||||||
border-radius: 2px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// App name
|
|
||||||
Text {
|
Text {
|
||||||
text: "Ziya";
|
text: "Z";
|
||||||
color: Theme.text-primary;
|
color: Palette.accent-foreground;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 600;
|
font-weight: 700;
|
||||||
|
horizontal-alignment: center;
|
||||||
vertical-alignment: center;
|
vertical-alignment: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Right side - Controls
|
// App title
|
||||||
HorizontalBox {
|
Text {
|
||||||
alignment: end;
|
text: "Ziya - Trading Platform";
|
||||||
spacing: 4px;
|
color: Palette.alternate-foreground;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
vertical-alignment: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Theme switcher
|
// Right side - Theme toggle and window controls
|
||||||
|
HorizontalLayout {
|
||||||
|
spacing: 4px;
|
||||||
|
alignment: end;
|
||||||
|
|
||||||
|
// Theme toggle button
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: 32px;
|
width: 32px;
|
||||||
height: 32px;
|
height: 24px;
|
||||||
border-radius: 4px;
|
border-radius: 6px;
|
||||||
|
|
||||||
states [
|
states [
|
||||||
hover when theme-area.has-hover: {
|
pressed when theme-touch-area.pressed: {
|
||||||
background: Theme.nav-hover;
|
background: Palette.selection-background;
|
||||||
|
in {
|
||||||
|
animate background { duration: 100ms; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hover when theme-touch-area.has-hover: {
|
||||||
|
background: Palette.control-background;
|
||||||
|
in {
|
||||||
|
animate background { duration: 150ms; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
// Theme icon - shows current theme state
|
|
||||||
Text {
|
Text {
|
||||||
text: Theme.is-dark-mode ? "🌙" : "☀️";
|
text: is-dark-theme ? "☀️" : "🌙";
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
horizontal-alignment: center;
|
horizontal-alignment: center;
|
||||||
vertical-alignment: center;
|
vertical-alignment: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
theme-area := TouchArea {
|
theme-touch-area := TouchArea {
|
||||||
clicked => {
|
clicked => {
|
||||||
Theme.toggle-theme();
|
root.toggle-theme();
|
||||||
toggle-theme();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -110,78 +108,110 @@ export component TitleBar {
|
||||||
// Minimize button
|
// Minimize button
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: 32px;
|
width: 32px;
|
||||||
height: 32px;
|
height: 24px;
|
||||||
border-radius: 4px;
|
border-radius: 6px;
|
||||||
|
|
||||||
states [
|
states [
|
||||||
hover when min-area.has-hover: {
|
pressed when minimize-touch-area.pressed: {
|
||||||
background: Theme.nav-hover;
|
background: Palette.selection-background;
|
||||||
|
in {
|
||||||
|
animate background { duration: 100ms; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hover when minimize-touch-area.has-hover: {
|
||||||
|
background: Palette.control-background;
|
||||||
|
in {
|
||||||
|
animate background { duration: 150ms; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
// Minimize icon
|
|
||||||
Text {
|
Text {
|
||||||
text: "−";
|
text: "−";
|
||||||
color: Theme.text-secondary;
|
color: Palette.alternate-foreground;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 300;
|
||||||
|
horizontal-alignment: center;
|
||||||
|
vertical-alignment: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
minimize-touch-area := TouchArea {
|
||||||
|
clicked => {
|
||||||
|
root.minimize-window();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Maximize button
|
||||||
|
Rectangle {
|
||||||
|
width: 32px;
|
||||||
|
height: 24px;
|
||||||
|
border-radius: 6px;
|
||||||
|
|
||||||
|
states [
|
||||||
|
pressed when maximize-touch-area.pressed: {
|
||||||
|
background: Palette.selection-background;
|
||||||
|
in {
|
||||||
|
animate background { duration: 100ms; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hover when maximize-touch-area.has-hover: {
|
||||||
|
background: Palette.control-background;
|
||||||
|
in {
|
||||||
|
animate background { duration: 150ms; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "□";
|
||||||
|
color: Palette.alternate-foreground;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
horizontal-alignment: center;
|
horizontal-alignment: center;
|
||||||
vertical-alignment: center;
|
vertical-alignment: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
min-area := TouchArea {
|
maximize-touch-area := TouchArea {
|
||||||
clicked => { minimize-window(); }
|
clicked => {
|
||||||
|
root.maximize-window();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Maximize/Restore button
|
|
||||||
Rectangle {
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
border-radius: 4px;
|
|
||||||
|
|
||||||
states [
|
|
||||||
hover when max-area.has-hover: {
|
|
||||||
background: Theme.nav-hover;
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
// Maximize icon
|
|
||||||
Text {
|
|
||||||
text: is-maximized ? "❐" : "□";
|
|
||||||
color: Theme.text-secondary;
|
|
||||||
font-size: 12px;
|
|
||||||
horizontal-alignment: center;
|
|
||||||
vertical-alignment: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
max-area := TouchArea {
|
|
||||||
clicked => { maximize-window(); }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close button
|
// Close button
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: 32px;
|
width: 32px;
|
||||||
height: 32px;
|
height: 24px;
|
||||||
border-radius: 4px;
|
border-radius: 6px;
|
||||||
|
|
||||||
states [
|
states [
|
||||||
hover when close-area.has-hover: {
|
pressed when close-touch-area.pressed: {
|
||||||
background: #ef4444;
|
background: #d53f3f;
|
||||||
|
in {
|
||||||
|
animate background { duration: 100ms; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hover when close-touch-area.has-hover: {
|
||||||
|
background: #f56565;
|
||||||
|
in {
|
||||||
|
animate background { duration: 150ms; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
// Close icon
|
|
||||||
Text {
|
Text {
|
||||||
text: "✕";
|
text: "×";
|
||||||
color: close-area.has-hover ? #ffffff : Theme.text-secondary;
|
color: close-touch-area.has-hover ? white : Palette.alternate-foreground;
|
||||||
font-size: 10px;
|
font-size: 16px;
|
||||||
|
font-weight: 400;
|
||||||
horizontal-alignment: center;
|
horizontal-alignment: center;
|
||||||
vertical-alignment: center;
|
vertical-alignment: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
close-area := TouchArea {
|
close-touch-area := TouchArea {
|
||||||
clicked => { close-window(); }
|
clicked => {
|
||||||
|
root.close-window();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
import { TitleBar } from "title-bar.slint";
|
|
||||||
|
|
||||||
// Window Controls Widget
|
|
||||||
export component WindowControlsWidget {
|
|
||||||
callback theme-toggle-clicked();
|
|
||||||
callback minimize-clicked();
|
|
||||||
callback maximize-clicked();
|
|
||||||
callback close-clicked();
|
|
||||||
callback start-drag-window();
|
|
||||||
|
|
||||||
TitleBar {
|
|
||||||
toggle-theme => { root.theme-toggle-clicked(); }
|
|
||||||
minimize-window => { root.minimize-clicked(); }
|
|
||||||
maximize-window => { root.maximize-clicked(); }
|
|
||||||
close-window => { root.close-clicked(); }
|
|
||||||
start-drag-window => { root.start-drag-window(); }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Add table
Reference in a new issue