-
This commit is contained in:
@@ -1,243 +0,0 @@
|
||||
import { Theme } from "./theme/theme.slint";
|
||||
import { Button } from "./components/button.slint";
|
||||
import { StatusIndicator, StatusState } from "./components/status-indicator.slint";
|
||||
import { HomePage, ProfilesPage, ProxiesPage, LogsPage, ConnectionsPage, SettingsPage } from "./pages-complete.slint";
|
||||
|
||||
component NavButton {
|
||||
in property <string> icon: "";
|
||||
in property <string> title: "";
|
||||
in property <bool> active: false;
|
||||
|
||||
callback clicked();
|
||||
|
||||
private property <bool> hovered: false;
|
||||
|
||||
height: 40px;
|
||||
|
||||
Rectangle {
|
||||
background: active ? Theme.colors.primary.transparentize(0.9) :
|
||||
hovered ? Theme.colors.muted.transparentize(0.5) :
|
||||
transparent;
|
||||
border-radius: 8px;
|
||||
|
||||
HorizontalLayout {
|
||||
padding-left: 12px;
|
||||
padding-right: 12px;
|
||||
spacing: 12px;
|
||||
|
||||
Text {
|
||||
text: root.icon;
|
||||
color: active ? Theme.colors.primary : Theme.colors.muted-foreground;
|
||||
font-size: 18px;
|
||||
vertical-alignment: center;
|
||||
width: 20px;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: root.title;
|
||||
color: active ? Theme.colors.foreground : Theme.colors.muted-foreground;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
|
||||
TouchArea {
|
||||
clicked => { root.clicked(); }
|
||||
moved => { root.hovered = self.has-hover; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export component App inherits Window {
|
||||
title: "Clash Manager";
|
||||
preferred-width: 1200px;
|
||||
preferred-height: 800px;
|
||||
background: Theme.colors.background;
|
||||
|
||||
in-out property <int> current-page: 0;
|
||||
in-out property <StatusState> mihomo-status: StatusState.stopped;
|
||||
in-out property <bool> mihomo-loading: false;
|
||||
|
||||
callback toggle-mihomo();
|
||||
|
||||
HorizontalLayout {
|
||||
// Sidebar
|
||||
Rectangle {
|
||||
width: 240px;
|
||||
background: Theme.colors.background;
|
||||
border-width: 1px;
|
||||
border-color: Theme.colors.border;
|
||||
|
||||
VerticalLayout {
|
||||
padding: 16px;
|
||||
spacing: 16px;
|
||||
|
||||
// Header
|
||||
HorizontalLayout {
|
||||
spacing: 12px;
|
||||
|
||||
Rectangle {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 8px;
|
||||
background: Theme.colors.primary;
|
||||
|
||||
Text {
|
||||
text: "C";
|
||||
color: Theme.colors.primary-foreground;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
horizontal-alignment: center;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
|
||||
VerticalLayout {
|
||||
spacing: 2px;
|
||||
|
||||
Text {
|
||||
text: "Clash Manager";
|
||||
color: Theme.colors.foreground;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "v1.0.0";
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Navigation
|
||||
VerticalLayout {
|
||||
spacing: 4px;
|
||||
|
||||
Text {
|
||||
text: "NAVIGATION";
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
NavButton {
|
||||
icon: "🏠";
|
||||
title: "Home";
|
||||
active: root.current-page == 0;
|
||||
clicked => { root.current-page = 0; }
|
||||
}
|
||||
|
||||
NavButton {
|
||||
icon: "🌐";
|
||||
title: "Proxies";
|
||||
active: root.current-page == 1;
|
||||
clicked => { root.current-page = 1; }
|
||||
}
|
||||
|
||||
NavButton {
|
||||
icon: "📋";
|
||||
title: "Profiles";
|
||||
active: root.current-page == 2;
|
||||
clicked => { root.current-page = 2; }
|
||||
}
|
||||
|
||||
NavButton {
|
||||
icon: "🔗";
|
||||
title: "Connections";
|
||||
active: root.current-page == 3;
|
||||
clicked => { root.current-page = 3; }
|
||||
}
|
||||
|
||||
NavButton {
|
||||
icon: "📝";
|
||||
title: "Logs";
|
||||
active: root.current-page == 4;
|
||||
clicked => { root.current-page = 4; }
|
||||
}
|
||||
|
||||
NavButton {
|
||||
icon: "⚙";
|
||||
title: "Settings";
|
||||
active: root.current-page == 5;
|
||||
clicked => { root.current-page = 5; }
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
vertical-stretch: 1;
|
||||
}
|
||||
|
||||
// Status Footer
|
||||
Rectangle {
|
||||
background: mihomo-status == StatusState.starting ? #eab30820 :
|
||||
mihomo-status == StatusState.running ? #22c55e20 :
|
||||
transparent;
|
||||
border-radius: 8px;
|
||||
|
||||
HorizontalLayout {
|
||||
padding: 12px;
|
||||
spacing: 12px;
|
||||
alignment: space-between;
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: 12px;
|
||||
|
||||
StatusIndicator {
|
||||
state: root.mihomo-status;
|
||||
}
|
||||
|
||||
VerticalLayout {
|
||||
spacing: 2px;
|
||||
|
||||
Text {
|
||||
text: "Clash";
|
||||
color: mihomo-status == StatusState.running ? #22c55e :
|
||||
mihomo-status == StatusState.starting ? #eab308 :
|
||||
#9ca3af;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: mihomo-status == StatusState.running ? "Running" :
|
||||
mihomo-status == StatusState.starting ? "Starting..." :
|
||||
"Stopped";
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
text: mihomo-loading ? "..." : (mihomo-status == StatusState.running ? "||" : ">");
|
||||
variant: "ghost";
|
||||
disabled: mihomo-loading;
|
||||
|
||||
clicked => {
|
||||
root.toggle-mihomo();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Content Area
|
||||
Rectangle {
|
||||
background: Theme.colors.background;
|
||||
horizontal-stretch: 1;
|
||||
vertical-stretch: 1;
|
||||
|
||||
if root.current-page == 0: HomePage {}
|
||||
if root.current-page == 1: ProxiesPage {}
|
||||
if root.current-page == 2: ProfilesPage {}
|
||||
if root.current-page == 3: ConnectionsPage {}
|
||||
if root.current-page == 4: LogsPage {}
|
||||
if root.current-page == 5: SettingsPage {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,366 +0,0 @@
|
||||
import { Theme } from "./theme/theme.slint";
|
||||
import { Button } from "./components/button.slint";
|
||||
import { Card } from "./components/card.slint";
|
||||
import { Switch } from "./components/switch.slint";
|
||||
import { StatusIndicator, StatusState } from "./components/status-indicator.slint";
|
||||
import { ScrollView } from "std-widgets.slint";
|
||||
|
||||
// Component definitions must come before usage
|
||||
component NavButton {
|
||||
in property <string> icon: "";
|
||||
in property <string> title: "";
|
||||
in property <bool> active: false;
|
||||
|
||||
callback clicked();
|
||||
|
||||
private property <bool> hovered: false;
|
||||
|
||||
height: 40px;
|
||||
|
||||
Rectangle {
|
||||
background: active ? Theme.colors.primary.transparentize(0.9) :
|
||||
hovered ? Theme.colors.muted.transparentize(0.5) :
|
||||
transparent;
|
||||
border-radius: 8px;
|
||||
|
||||
HorizontalLayout {
|
||||
padding-left: 12px;
|
||||
padding-right: 12px;
|
||||
spacing: 12px;
|
||||
|
||||
Text {
|
||||
text: root.icon;
|
||||
color: active ? Theme.colors.primary : Theme.colors.muted-foreground;
|
||||
font-size: 18px;
|
||||
vertical-alignment: center;
|
||||
width: 20px;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: root.title;
|
||||
color: active ? Theme.colors.foreground : Theme.colors.muted-foreground;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
|
||||
TouchArea {
|
||||
clicked => { root.clicked(); }
|
||||
moved => { root.hovered = self.has-hover; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component HomePage {
|
||||
VerticalLayout {
|
||||
padding: 24px;
|
||||
spacing: 16px;
|
||||
|
||||
Text {
|
||||
text: "Home";
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
color: Theme.colors.foreground;
|
||||
}
|
||||
|
||||
ScrollView {
|
||||
VerticalLayout {
|
||||
spacing: 16px;
|
||||
|
||||
Card {
|
||||
VerticalLayout {
|
||||
spacing: 16px;
|
||||
|
||||
Text {
|
||||
text: "Welcome to Clash Manager";
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: Theme.colors.foreground;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Click the navigation buttons on the left to switch between pages.";
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Card {
|
||||
VerticalLayout {
|
||||
spacing: 12px;
|
||||
|
||||
Text {
|
||||
text: "Quick Settings";
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: Theme.colors.foreground;
|
||||
}
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: 12px;
|
||||
alignment: space-between;
|
||||
|
||||
Text {
|
||||
text: "TUN Mode";
|
||||
color: Theme.colors.foreground;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
|
||||
Switch {
|
||||
checked: false;
|
||||
toggled(checked) => {
|
||||
debug("TUN mode:", checked);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: 12px;
|
||||
alignment: space-between;
|
||||
|
||||
Text {
|
||||
text: "System Proxy";
|
||||
color: Theme.colors.foreground;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
|
||||
Switch {
|
||||
checked: false;
|
||||
toggled(checked) => {
|
||||
debug("System proxy:", checked);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component SimplePage {
|
||||
in property <string> page-title: "Page";
|
||||
|
||||
VerticalLayout {
|
||||
padding: 24px;
|
||||
spacing: 16px;
|
||||
|
||||
Text {
|
||||
text: root.page-title;
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
color: Theme.colors.foreground;
|
||||
}
|
||||
|
||||
Card {
|
||||
VerticalLayout {
|
||||
spacing: 12px;
|
||||
|
||||
Text {
|
||||
text: root.page-title + " page content";
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: Theme.colors.foreground;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "This page is under construction. Full implementation coming soon!";
|
||||
color: Theme.colors.muted-foreground;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export component App inherits Window {
|
||||
title: "Clash Manager";
|
||||
preferred-width: 1200px;
|
||||
preferred-height: 800px;
|
||||
background: Theme.colors.background;
|
||||
|
||||
in-out property <int> current-page: 0;
|
||||
in-out property <StatusState> mihomo-status: StatusState.stopped;
|
||||
in-out property <bool> mihomo-loading: false;
|
||||
|
||||
callback toggle-mihomo();
|
||||
|
||||
HorizontalLayout {
|
||||
// Sidebar
|
||||
Rectangle {
|
||||
width: 240px;
|
||||
background: Theme.colors.background;
|
||||
border-width: 1px;
|
||||
border-color: Theme.colors.border;
|
||||
|
||||
VerticalLayout {
|
||||
padding: 16px;
|
||||
spacing: 16px;
|
||||
|
||||
// Header
|
||||
HorizontalLayout {
|
||||
spacing: 12px;
|
||||
|
||||
Rectangle {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 8px;
|
||||
background: Theme.colors.primary;
|
||||
|
||||
Text {
|
||||
text: "C";
|
||||
color: Theme.colors.primary-foreground;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
horizontal-alignment: center;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
|
||||
VerticalLayout {
|
||||
spacing: 2px;
|
||||
|
||||
Text {
|
||||
text: "Clash Manager";
|
||||
color: Theme.colors.foreground;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "v1.0.0";
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Navigation
|
||||
VerticalLayout {
|
||||
spacing: 4px;
|
||||
|
||||
Text {
|
||||
text: "NAVIGATION";
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
NavButton {
|
||||
icon: "🏠";
|
||||
title: "Home";
|
||||
active: root.current-page == 0;
|
||||
clicked => { root.current-page = 0; }
|
||||
}
|
||||
|
||||
NavButton {
|
||||
icon: "🌐";
|
||||
title: "Proxies";
|
||||
active: root.current-page == 1;
|
||||
clicked => { root.current-page = 1; }
|
||||
}
|
||||
|
||||
NavButton {
|
||||
icon: "📋";
|
||||
title: "Profiles";
|
||||
active: root.current-page == 2;
|
||||
clicked => { root.current-page = 2; }
|
||||
}
|
||||
|
||||
NavButton {
|
||||
icon: "🔗";
|
||||
title: "Connections";
|
||||
active: root.current-page == 3;
|
||||
clicked => { root.current-page = 3; }
|
||||
}
|
||||
|
||||
NavButton {
|
||||
icon: "📝";
|
||||
title: "Logs";
|
||||
active: root.current-page == 4;
|
||||
clicked => { root.current-page = 4; }
|
||||
}
|
||||
|
||||
NavButton {
|
||||
icon: "⚙";
|
||||
title: "Settings";
|
||||
active: root.current-page == 5;
|
||||
clicked => { root.current-page = 5; }
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
vertical-stretch: 1;
|
||||
}
|
||||
|
||||
// Status Footer
|
||||
Rectangle {
|
||||
background: mihomo-status == StatusState.starting ? #eab30820 :
|
||||
mihomo-status == StatusState.running ? #22c55e20 :
|
||||
transparent;
|
||||
border-radius: 8px;
|
||||
|
||||
HorizontalLayout {
|
||||
padding: 12px;
|
||||
spacing: 12px;
|
||||
alignment: space-between;
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: 12px;
|
||||
|
||||
StatusIndicator {
|
||||
state: root.mihomo-status;
|
||||
}
|
||||
|
||||
VerticalLayout {
|
||||
spacing: 2px;
|
||||
|
||||
Text {
|
||||
text: "Clash";
|
||||
color: mihomo-status == StatusState.running ? #22c55e :
|
||||
mihomo-status == StatusState.starting ? #eab308 :
|
||||
#9ca3af;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: mihomo-status == StatusState.running ? "Running" :
|
||||
mihomo-status == StatusState.starting ? "Starting..." :
|
||||
"Stopped";
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
text: mihomo-loading ? "..." : (mihomo-status == StatusState.running ? "||" : ">");
|
||||
variant: "ghost";
|
||||
disabled: mihomo-loading;
|
||||
|
||||
clicked => {
|
||||
root.toggle-mihomo();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Content Area
|
||||
Rectangle {
|
||||
background: Theme.colors.background;
|
||||
|
||||
if root.current-page == 0: HomePage {}
|
||||
if root.current-page == 1: SimplePage { page-title: "Proxies"; }
|
||||
if root.current-page == 2: SimplePage { page-title: "Profiles"; }
|
||||
if root.current-page == 3: SimplePage { page-title: "Connections"; }
|
||||
if root.current-page == 4: SimplePage { page-title: "Logs"; }
|
||||
if root.current-page == 5: SimplePage { page-title: "Settings"; }
|
||||
}
|
||||
}
|
||||
}
|
||||
366
ui/app-nav.slint
366
ui/app-nav.slint
@@ -1,366 +0,0 @@
|
||||
import { Theme } from "./theme/theme.slint";
|
||||
import { Button } from "./components/button.slint";
|
||||
import { Card } from "./components/card.slint";
|
||||
import { Switch } from "./components/switch.slint";
|
||||
import { StatusIndicator, StatusState } from "./components/status-indicator.slint";
|
||||
import { ScrollView } from "std-widgets.slint";
|
||||
|
||||
// Component definitions must come before usage
|
||||
component NavButton {
|
||||
in property <string> icon: "";
|
||||
in property <string> title: "";
|
||||
in property <bool> active: false;
|
||||
|
||||
callback clicked();
|
||||
|
||||
private property <bool> hovered: false;
|
||||
|
||||
height: 40px;
|
||||
|
||||
Rectangle {
|
||||
background: active ? Theme.colors.primary.transparentize(0.9) :
|
||||
hovered ? Theme.colors.muted.transparentize(0.5) :
|
||||
transparent;
|
||||
border-radius: 8px;
|
||||
|
||||
HorizontalLayout {
|
||||
padding-left: 12px;
|
||||
padding-right: 12px;
|
||||
spacing: 12px;
|
||||
|
||||
Text {
|
||||
text: root.icon;
|
||||
color: active ? Theme.colors.primary : Theme.colors.muted-foreground;
|
||||
font-size: 18px;
|
||||
vertical-alignment: center;
|
||||
width: 20px;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: root.title;
|
||||
color: active ? Theme.colors.foreground : Theme.colors.muted-foreground;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
|
||||
TouchArea {
|
||||
clicked => { root.clicked(); }
|
||||
moved => { root.hovered = self.has-hover; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component HomePage {
|
||||
VerticalLayout {
|
||||
padding: 24px;
|
||||
spacing: 16px;
|
||||
|
||||
Text {
|
||||
text: "Home";
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
color: Theme.colors.foreground;
|
||||
}
|
||||
|
||||
ScrollView {
|
||||
VerticalLayout {
|
||||
spacing: 16px;
|
||||
|
||||
Card {
|
||||
VerticalLayout {
|
||||
spacing: 16px;
|
||||
|
||||
Text {
|
||||
text: "Welcome to Clash Manager";
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: Theme.colors.foreground;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Click the navigation buttons on the left to switch between pages.";
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Card {
|
||||
VerticalLayout {
|
||||
spacing: 12px;
|
||||
|
||||
Text {
|
||||
text: "Quick Settings";
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: Theme.colors.foreground;
|
||||
}
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: 12px;
|
||||
alignment: space-between;
|
||||
|
||||
Text {
|
||||
text: "TUN Mode";
|
||||
color: Theme.colors.foreground;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
|
||||
Switch {
|
||||
checked: false;
|
||||
toggled(checked) => {
|
||||
debug("TUN mode:", checked);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: 12px;
|
||||
alignment: space-between;
|
||||
|
||||
Text {
|
||||
text: "System Proxy";
|
||||
color: Theme.colors.foreground;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
|
||||
Switch {
|
||||
checked: false;
|
||||
toggled(checked) => {
|
||||
debug("System proxy:", checked);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component SimplePage {
|
||||
in property <string> page-title: "Page";
|
||||
|
||||
VerticalLayout {
|
||||
padding: 24px;
|
||||
spacing: 16px;
|
||||
|
||||
Text {
|
||||
text: root.page-title;
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
color: Theme.colors.foreground;
|
||||
}
|
||||
|
||||
Card {
|
||||
VerticalLayout {
|
||||
spacing: 12px;
|
||||
|
||||
Text {
|
||||
text: root.page-title + " page content";
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: Theme.colors.foreground;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "This page is under construction. Full implementation coming soon!";
|
||||
color: Theme.colors.muted-foreground;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export component App inherits Window {
|
||||
title: "Clash Manager";
|
||||
preferred-width: 1200px;
|
||||
preferred-height: 800px;
|
||||
background: Theme.colors.background;
|
||||
|
||||
in-out property <int> current-page: 0;
|
||||
in-out property <StatusState> mihomo-status: StatusState.stopped;
|
||||
in-out property <bool> mihomo-loading: false;
|
||||
|
||||
callback toggle-mihomo();
|
||||
|
||||
HorizontalLayout {
|
||||
// Sidebar
|
||||
Rectangle {
|
||||
width: 240px;
|
||||
background: Theme.colors.background;
|
||||
border-width: 1px;
|
||||
border-color: Theme.colors.border;
|
||||
|
||||
VerticalLayout {
|
||||
padding: 16px;
|
||||
spacing: 16px;
|
||||
|
||||
// Header
|
||||
HorizontalLayout {
|
||||
spacing: 12px;
|
||||
|
||||
Rectangle {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 8px;
|
||||
background: Theme.colors.primary;
|
||||
|
||||
Text {
|
||||
text: "C";
|
||||
color: Theme.colors.primary-foreground;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
horizontal-alignment: center;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
|
||||
VerticalLayout {
|
||||
spacing: 2px;
|
||||
|
||||
Text {
|
||||
text: "Clash Manager";
|
||||
color: Theme.colors.foreground;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "v1.0.0";
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Navigation
|
||||
VerticalLayout {
|
||||
spacing: 4px;
|
||||
|
||||
Text {
|
||||
text: "NAVIGATION";
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
NavButton {
|
||||
icon: "🏠";
|
||||
title: "Home";
|
||||
active: root.current-page == 0;
|
||||
clicked => { root.current-page = 0; }
|
||||
}
|
||||
|
||||
NavButton {
|
||||
icon: "🌐";
|
||||
title: "Proxies";
|
||||
active: root.current-page == 1;
|
||||
clicked => { root.current-page = 1; }
|
||||
}
|
||||
|
||||
NavButton {
|
||||
icon: "📋";
|
||||
title: "Profiles";
|
||||
active: root.current-page == 2;
|
||||
clicked => { root.current-page = 2; }
|
||||
}
|
||||
|
||||
NavButton {
|
||||
icon: "🔗";
|
||||
title: "Connections";
|
||||
active: root.current-page == 3;
|
||||
clicked => { root.current-page = 3; }
|
||||
}
|
||||
|
||||
NavButton {
|
||||
icon: "📝";
|
||||
title: "Logs";
|
||||
active: root.current-page == 4;
|
||||
clicked => { root.current-page = 4; }
|
||||
}
|
||||
|
||||
NavButton {
|
||||
icon: "⚙";
|
||||
title: "Settings";
|
||||
active: root.current-page == 5;
|
||||
clicked => { root.current-page = 5; }
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
vertical-stretch: 1;
|
||||
}
|
||||
|
||||
// Status Footer
|
||||
Rectangle {
|
||||
background: mihomo-status == StatusState.starting ? #eab30820 :
|
||||
mihomo-status == StatusState.running ? #22c55e20 :
|
||||
transparent;
|
||||
border-radius: 8px;
|
||||
|
||||
HorizontalLayout {
|
||||
padding: 12px;
|
||||
spacing: 12px;
|
||||
alignment: space-between;
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: 12px;
|
||||
|
||||
StatusIndicator {
|
||||
state: root.mihomo-status;
|
||||
}
|
||||
|
||||
VerticalLayout {
|
||||
spacing: 2px;
|
||||
|
||||
Text {
|
||||
text: "Clash";
|
||||
color: mihomo-status == StatusState.running ? #22c55e :
|
||||
mihomo-status == StatusState.starting ? #eab308 :
|
||||
#9ca3af;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: mihomo-status == StatusState.running ? "Running" :
|
||||
mihomo-status == StatusState.starting ? "Starting..." :
|
||||
"Stopped";
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
text: mihomo-loading ? "..." : (mihomo-status == StatusState.running ? "||" : ">");
|
||||
variant: "ghost";
|
||||
disabled: mihomo-loading;
|
||||
|
||||
clicked => {
|
||||
root.toggle-mihomo();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Content Area
|
||||
Rectangle {
|
||||
background: Theme.colors.background;
|
||||
|
||||
if root.current-page == 0: HomePage {}
|
||||
if root.current-page == 1: SimplePage { page-title: "Proxies"; }
|
||||
if root.current-page == 2: SimplePage { page-title: "Profiles"; }
|
||||
if root.current-page == 3: SimplePage { page-title: "Connections"; }
|
||||
if root.current-page == 4: SimplePage { page-title: "Logs"; }
|
||||
if root.current-page == 5: SimplePage { page-title: "Settings"; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,884 +0,0 @@
|
||||
import { Theme, SpacingSystem, Typography } from "./theme/theme.slint";
|
||||
import { Button } from "./components/button.slint";
|
||||
import { Card } from "./components/card.slint";
|
||||
import { Switch } from "./components/switch.slint";
|
||||
import { Badge } from "./components/badge.slint";
|
||||
import { StatusIndicator, StatusState } from "./components/status-indicator.slint";
|
||||
import { Progress } from "./components/progress.slint";
|
||||
|
||||
export component App inherits Window {
|
||||
title: "Clash Manager";
|
||||
preferred-width: 1200px;
|
||||
preferred-height: 800px;
|
||||
background: Theme.colors.background;
|
||||
|
||||
in-out property <int> current-page: 0;
|
||||
in-out property <StatusState> mihomo-status: StatusState.stopped;
|
||||
in-out property <bool> mihomo-loading: false;
|
||||
|
||||
callback toggle-mihomo();
|
||||
callback navigate(int);
|
||||
|
||||
HorizontalLayout {
|
||||
// Sidebar
|
||||
sidebar := Rectangle {
|
||||
width: 240px;
|
||||
background: Theme.colors.background;
|
||||
border-width: 1px;
|
||||
border-color: Theme.colors.border;
|
||||
|
||||
VerticalLayout {
|
||||
padding: 16px;
|
||||
spacing: 16px;
|
||||
|
||||
// Header
|
||||
HorizontalLayout {
|
||||
spacing: 12px;
|
||||
|
||||
Rectangle {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 8px;
|
||||
background: Theme.colors.primary;
|
||||
|
||||
Text {
|
||||
text: "C";
|
||||
color: Theme.colors.primary-foreground;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
horizontal-alignment: center;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
|
||||
VerticalLayout {
|
||||
spacing: 2px;
|
||||
|
||||
Text {
|
||||
text: "Clash Manager";
|
||||
color: Theme.colors.foreground;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "v1.0.0";
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Navigation
|
||||
VerticalLayout {
|
||||
spacing: 4px;
|
||||
|
||||
Text {
|
||||
text: "NAVIGATION";
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
NavButton {
|
||||
icon: "🏠";
|
||||
title: "Home";
|
||||
active: root.current-page == 0;
|
||||
clicked => { root.current-page = 0; root.navigate(0); }
|
||||
}
|
||||
|
||||
NavButton {
|
||||
icon: "🌐";
|
||||
title: "Proxies";
|
||||
active: root.current-page == 1;
|
||||
clicked => { rourrent-page = 1; root.navigate(1); }
|
||||
}
|
||||
|
||||
NavButton {
|
||||
icon: "📋";
|
||||
title: "Profiles";
|
||||
active: root.current-page == 2;
|
||||
clicked => { root.current-page = 2; root.navigate(2); }
|
||||
}
|
||||
|
||||
NavButton {
|
||||
icon: "🔗";
|
||||
title: "Connections";
|
||||
active: root.current-page == 3;
|
||||
clicked => { root.current-page = 3; root.navigate(3); }
|
||||
}
|
||||
|
||||
NavButton {
|
||||
icon: "📝";
|
||||
title: "Logs";
|
||||
active: root.current-page == 4;
|
||||
clicked => { root.current-page = 4; root.navigate(4); }
|
||||
}
|
||||
|
||||
NavButton {
|
||||
icon: "⚙";
|
||||
title: "Settings";
|
||||
active: root.current-page == 5;
|
||||
clicked => { root.current-page = 5; root.navigate(5); }
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
vertical-stretch: 1;
|
||||
}
|
||||
|
||||
// Status Footer
|
||||
Rectangle {
|
||||
background: mihomo-status == StatusState.starting ? #eab30820 :
|
||||
mihomo-status == StatusState.running ? #22c55e20 :
|
||||
transparent;
|
||||
border-radius: 8px;
|
||||
|
||||
HorizontalLayout {
|
||||
padding: 12px;
|
||||
spacing: 12px;
|
||||
alignment: space-between;
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: 12px;
|
||||
|
||||
StatusIndicator {
|
||||
state: root.mihomo-status;
|
||||
}
|
||||
|
||||
VerticalLayout {
|
||||
spacing: 2px;
|
||||
|
||||
Text {
|
||||
text: "Clash";
|
||||
color: mihomo-status == StatusState.running ? #22c55e :
|
||||
mihomo-status == StatusState.starting ? #eab308 :
|
||||
#9ca3af;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: mihomo-status == StatusState.running ? "Running" :
|
||||
mihomo-status == StatusState.starting ? "Starting..." :
|
||||
"Stopped";
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
text: mihomo-loading ? "..." : (mihomo-status == StatusState.running ? "||" : ">");
|
||||
variant: "ghost";
|
||||
disabled: mihomo-loading;
|
||||
|
||||
clicked => {
|
||||
root.toggle-mihomo();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Content Area
|
||||
content := Rectangle {
|
||||
background: Theme.colors.background;
|
||||
|
||||
// Home Page
|
||||
if root.current-page == 0: HomePage {
|
||||
toggle-mihomo => { root.toggle-mihomo(); }
|
||||
}
|
||||
// Proxies Page
|
||||
if root.current-page == 1: ProxiesPage {}
|
||||
|
||||
// Profiles Page
|
||||
if root.current-page == 2: ProfilesPage {}
|
||||
|
||||
// Connections Page
|
||||
if root.current-page == 3: ConnectionsPage {}
|
||||
|
||||
// Logs Page
|
||||
if root.current-page == 4: LogsPage {}
|
||||
|
||||
// Settings Page
|
||||
if root.current-page == 5: SettingsPage {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component NavButton {
|
||||
in property <string> icon: "";
|
||||
in property <string> title: "";
|
||||
in property <bool> active: false;
|
||||
|
||||
callback clicked();
|
||||
|
||||
private property <bool> hovered: false;
|
||||
|
||||
height: 40px;
|
||||
|
||||
container := Rectangle {
|
||||
background: active ? Theme.colors.primary.transparentize(0.9) :
|
||||
hovered ? Theme.colors.muted.transparentize(0.5) :
|
||||
transparent;
|
||||
border-radius: 8px;
|
||||
|
||||
HorizontalLayout {
|
||||
padding-left: 12px;
|
||||
padding-right: 12px;
|
||||
spacing: 12px;
|
||||
|
||||
Text {
|
||||
text: root.icon;
|
||||
color: active ? Theme.colors.primary : Theme.colors.muted-foregr font-size: 18px;
|
||||
vertical-alignment: center;
|
||||
width: 20px;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: root.title;
|
||||
color: active ? Theme.colors.foreground : Theme.colors.muted-foreground;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
|
||||
TouchArea {
|
||||
clicked => { root.clicked(); }
|
||||
moved => { root.hovered = self.has-hover; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component HomePage {
|
||||
toggle-mihomo();
|
||||
|
||||
VerticalLayout {
|
||||
padding: 24px;
|
||||
spacing: 16px;
|
||||
|
||||
Text {
|
||||
text: "Home";
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
color: Theme.colors.foreground;
|
||||
}
|
||||
|
||||
ScrollView {
|
||||
VerticalLayout {
|
||||
spacing: 16px;
|
||||
|
||||
// Profile Card
|
||||
Card {
|
||||
VerticalLayout {
|
||||
spacing: 16px;
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: 12px;
|
||||
|
||||
Rectangle {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 12px;
|
||||
background: Theme.colors.primary.transparentize(0.9);
|
||||
|
||||
Text {
|
||||
text: "☁";
|
||||
color: Theme.colors.primary;
|
||||
font-size: 24px;
|
||||
horizontal-alignment: center;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
|
||||
VerticalLayout {
|
||||
spacing: 4px;
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: 8px;
|
||||
|
||||
Text {
|
||||
text: "NanoCloud";
|
||||
color: Theme.colors.foreground;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: 8px;
|
||||
|
||||
Text {
|
||||
text: "🌐 Free-Japan1-Ver.7";
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: 12px;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
|
||||
Badge {
|
||||
text: "Vmess";
|
||||
variant: "outline";
|
||||
}
|
||||
|
||||
Badge {
|
||||
text: "UDP";
|
||||
variant: "outline";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
height: 1px;
|
||||
background: Theme.colors.border.transparentize(0.6);
|
||||
}
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: 24px;
|
||||
|
||||
VerticalLayout {
|
||||
spacing: 4px;
|
||||
|
||||
Text {
|
||||
text: "☁ Usage / Total";
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "1.26GB / 100GB";
|
||||
color: Theme.colors.foreground;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
}
|
||||
n
|
||||
VerticalLayout {
|
||||
spacing: 4px;
|
||||
|
||||
Text {
|
||||
text: "✓ Expiry Date";
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "2025-11-11";
|
||||
color: Theme.colors.foreground;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
VerticalLayout {
|
||||
spacing: 4px;
|
||||
|
||||
Text {
|
||||
text: "✓ Last Update";
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "2025-10-12 10:05";
|
||||
color: Theme.colors.foreground;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Location Card
|
||||
Card {
|
||||
VerticalLayout {
|
||||
spacing: 16px;
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: 24px;
|
||||
|
||||
VerticalLayout {
|
||||
spacing: 4px;
|
||||
|
||||
Text {
|
||||
text: "🌐 IP Address";
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "47.238.198.100";
|
||||
color: Theme.colors.foreground;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
VerticalLayout {
|
||||
spacing: 4px;
|
||||
|
||||
Text {
|
||||
text: "🌐 Location";
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Japan · Tokyo";
|
||||
color: Theme.colors.foreground;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Settings Card
|
||||
Card {
|
||||
VerticalLayout {
|
||||
spacing: 0;
|
||||
|
||||
SettingRow {
|
||||
label: "TUN Mode";
|
||||
Switch {
|
||||
checked: false;
|
||||
toggled(checked) => { debug("TUN mode:", checked); }
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
height: 1px;
|
||||
background: Theme.colors.border.transparentize(0.6);
|
||||
}
|
||||
|
||||
SettingRow {
|
||||
label: "System Proxy";
|
||||
Switch {
|
||||
checked: false;
|
||||
toggled(checked) => { debug("System proxy:", checked); }
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
height: 1px;
|
||||
background: Themelors.border.transparentize(0.6);
|
||||
}
|
||||
|
||||
SettingRow {
|
||||
label: "Proxy Port";
|
||||
Text {
|
||||
text: "7890";
|
||||
color: Theme.colors.primary;
|
||||
font-size: 14px;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component SettingRow {
|
||||
in property <string> label: "";
|
||||
|
||||
height: 48px;
|
||||
|
||||
Horout {
|
||||
spacing: 12px;
|
||||
alignment: space-between;
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: 8px;
|
||||
|
||||
Text {
|
||||
text: "✓";
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: 14px;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: root.label;
|
||||
color: Theme.colors.foreground;
|
||||
font-size: 14px;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
|
||||
@children
|
||||
}
|
||||
}
|
||||
|
||||
component ProxiesPage {
|
||||
Vertica {
|
||||
padding: 24px;
|
||||
spacing: 16px;
|
||||
|
||||
Text {
|
||||
text: "Proxies";
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
color: Theme.colors.foreground;
|
||||
}
|
||||
|
||||
Card {
|
||||
Text {
|
||||
text: "Proxy management page - Coming soon";
|
||||
color: Theme.colors.muted-foreground;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component ProfilesPage {
|
||||
VerticalLayout {
|
||||
padding: 24px;
|
||||
spacing: 16px;
|
||||
|
||||
Text {
|
||||
text: "Profiles";
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
color: Theme.colors.foreground;
|
||||
}
|
||||
|
||||
ScrollView {
|
||||
GridLayout {
|
||||
spacing: 16px;
|
||||
|
||||
Card {
|
||||
min-width: 280px;
|
||||
height: 120px;
|
||||
|
||||
VerticalLayout {
|
||||
spacing: 8px;
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: 8px;
|
||||
|
||||
Text {
|
||||
text: "☁";
|
||||
font-size: 18px;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "NanoCloud";
|
||||
color: Theme.colors.foreground;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Free tier subscription - Japan servers";
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
Progress {
|
||||
value: 1.26;
|
||||
}
|
||||
|
||||
HorizontalLayout {
|
||||
alignment: space-between;
|
||||
|
||||
Text {
|
||||
text: "Updated: Just now";
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "1.26 / 100 GB";
|
||||
color: Theme.colors.foreground;
|
||||
font-size: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Card {
|
||||
min-width: 280px;
|
||||
height: 120px;
|
||||
|
||||
VerticalLayout {
|
||||
spacing: 8px;
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: 8px;
|
||||
|
||||
Text {
|
||||
text: "📄";
|
||||
font-size: 18px;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Local Config";
|
||||
color: Theme.colors.foreground;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Custom local configuration file";
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Local Profile";
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component ConnectionsPage {
|
||||
VerticalLayout {
|
||||
padding: 24px;
|
||||
spacing: 16px;
|
||||
|
||||
Text {
|
||||
text: "Connections";
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
color: Theme.colors.foreground;
|
||||
}
|
||||
|
||||
ScrollView {
|
||||
VerticalLayout {
|
||||
spacing: 8px;
|
||||
|
||||
for Card {
|
||||
height: 60px;
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: 12px;
|
||||
|
||||
Rectangle {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 8px;
|
||||
background: Theme.colors.primary.transparentize(0.9);
|
||||
|
||||
Text {
|
||||
text: "🔗";
|
||||
font-size: 18px;
|
||||
horizontal-alignment: center;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
|
||||
VerticalLayout {
|
||||
spacing: 4px;
|
||||
|
||||
Text {
|
||||
text: "192.168.1.100:54321 → github.com:443";
|
||||
color: Theme.colors.foreground;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: 12px;
|
||||
|
||||
Text {
|
||||
text: "HTTPS";
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "↑ 1.2 KB/s";
|
||||
color: Theme.colors.primary;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "↓ 45.6 KB/s";
|
||||
color: Theme.colors.secondary;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component LogsPage {
|
||||
VerticalLayout {
|
||||
padding: 24px;
|
||||
spacing: 16px;
|
||||
|
||||
Text {
|
||||
text: "Logs";
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
color: Theme.colors.foreground;
|
||||
}
|
||||
|
||||
Card {
|
||||
ScrollView {
|
||||
VerticalLayout {
|
||||
spacing: 0;
|
||||
|
||||
for i in 20: LogRow {
|
||||
time: "10:23:45";
|
||||
level: i < 5 ? "info" : (i < 10 ? "debug" : (i < 15 ? "warn" : "error"));
|
||||
message: "Sample log message " + i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component LogRow {
|
||||
in property <string> time: "";
|
||||
in property <string> level: "";
|
||||
in property <string> message: "";
|
||||
|
||||
private property <color> level-color: level == "error" ? #ef4444 :
|
||||
level == "warn" ? #eab308 :
|
||||
level == "info" ? #3b82f6 :
|
||||
#6b7280;
|
||||
|
||||
: 24px;
|
||||
|
||||
HorizontalLayout {
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
spacing: 8px;
|
||||
|
||||
Text {
|
||||
text: root.time;
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: 11px;
|
||||
font-family: "monospace";
|
||||
vertical-alignment: center;
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: root.level;
|
||||
color: root.level-color;
|
||||
font-size: 11px;
|
||||
font-family: "monospace";
|
||||
font-weight: 700;
|
||||
vertical-alignment: center;
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: root.message;
|
||||
color: Theme.colors.foreground;
|
||||
font-size: 11px;
|
||||
font-family: "monospace";
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component SettingsPage {
|
||||
VerticalLayout {
|
||||
padding: 24px;
|
||||
spacing: 16px;
|
||||
|
||||
Text {
|
||||
text: "Settings";
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
color: Theme.colors.foreground;
|
||||
}
|
||||
|
||||
ScrollView {
|
||||
VerticalLayout {
|
||||
spacing: 16px;
|
||||
|
||||
Card {
|
||||
VerticalLayout {
|
||||
spacing: 12px;
|
||||
|
||||
Text {
|
||||
text: "App Settings";
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: Theme.colors.foreground;
|
||||
}
|
||||
|
||||
SettingRow {
|
||||
label: "Auto Start";
|
||||
Switch {
|
||||
checked: false;
|
||||
toggled(checked) debug("Auto start:", checked); }
|
||||
}
|
||||
}
|
||||
|
||||
SettingRow {
|
||||
label: "Silent Start";
|
||||
Switch {
|
||||
checked: false;
|
||||
toggled(checked) => { debug("Silent start:", checked); }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Card {
|
||||
VerticalLayout {
|
||||
spacing: 12px;
|
||||
|
||||
Text {
|
||||
t Settings";
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: Theme.colors.foreground;
|
||||
}
|
||||
|
||||
SettingRow {
|
||||
label: "Allow LAN";
|
||||
Switch {
|
||||
checked: false;
|
||||
toggled(checked) => { debug("Allow LAN:", checked); }
|
||||
}
|
||||
}
|
||||
|
||||
SettingRow {
|
||||
label: "IPv6";
|
||||
Switch {
|
||||
checked: false;
|
||||
toggled(checked) => { debug("IPv6:", checked); }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
import { Theme, SpacingSystem, Typography } from "./theme/theme.slint";
|
||||
import { Button } from "./components/button.slint";
|
||||
import { Card } from "./components/card.slint";
|
||||
import { Switch } from "./components/switch.slint";
|
||||
import { StatusIndicator, StatusState } from "./components/status-indicator.slint";
|
||||
@@ -1,884 +0,0 @@
|
||||
import { Theme, SpacingSystem, Typography } from "./theme/theme.slint";
|
||||
import { Button } from "./components/button.slint";
|
||||
import { Card } from "./components/card.slint";
|
||||
import { Switch } from "./components/switch.slint";
|
||||
import { Badge } from "./components/badge.slint";
|
||||
import { StatusIndicator, StatusState } from "./components/status-indicator.slint";
|
||||
import { Progress } from "./components/progress.slint";
|
||||
|
||||
export component App inherits Window {
|
||||
title: "Clash Manager";
|
||||
preferred-width: 1200px;
|
||||
preferred-height: 800px;
|
||||
background: Theme.colors.background;
|
||||
|
||||
in-out property <int> current-page: 0;
|
||||
in-out property <StatusState> mihomo-status: StatusState.stopped;
|
||||
in-out property <bool> mihomo-loading: false;
|
||||
|
||||
callback toggle-mihomo();
|
||||
callback navigate(int);
|
||||
|
||||
HorizontalLayout {
|
||||
// Sidebar
|
||||
sidebar := Rectangle {
|
||||
width: 240px;
|
||||
background: Theme.colors.background;
|
||||
border-width: 1px;
|
||||
border-color: Theme.colors.border;
|
||||
|
||||
VerticalLayout {
|
||||
padding: 16px;
|
||||
spacing: 16px;
|
||||
|
||||
// Header
|
||||
HorizontalLayout {
|
||||
spacing: 12px;
|
||||
|
||||
Rectangle {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 8px;
|
||||
background: Theme.colors.primary;
|
||||
|
||||
Text {
|
||||
text: "C";
|
||||
color: Theme.colors.primary-foreground;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
horizontal-alignment: center;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
|
||||
VerticalLayout {
|
||||
spacing: 2px;
|
||||
|
||||
Text {
|
||||
text: "Clash Manager";
|
||||
color: Theme.colors.foreground;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "v1.0.0";
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Navigation
|
||||
VerticalLayout {
|
||||
spacing: 4px;
|
||||
|
||||
Text {
|
||||
text: "NAVIGATION";
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
NavButton {
|
||||
icon: "🏠";
|
||||
title: "Home";
|
||||
active: root.current-page == 0;
|
||||
clicked => { root.current-page = 0; root.navigate(0); }
|
||||
}
|
||||
|
||||
NavButton {
|
||||
icon: "🌐";
|
||||
title: "Proxies";
|
||||
active: root.current-page == 1;
|
||||
clicked => { rourrent-page = 1; root.navigate(1); }
|
||||
}
|
||||
|
||||
NavButton {
|
||||
icon: "📋";
|
||||
title: "Profiles";
|
||||
active: root.current-page == 2;
|
||||
clicked => { root.current-page = 2; root.navigate(2); }
|
||||
}
|
||||
|
||||
NavButton {
|
||||
icon: "🔗";
|
||||
title: "Connections";
|
||||
active: root.current-page == 3;
|
||||
clicked => { root.current-page = 3; root.navigate(3); }
|
||||
}
|
||||
|
||||
NavButton {
|
||||
icon: "📝";
|
||||
title: "Logs";
|
||||
active: root.current-page == 4;
|
||||
clicked => { root.current-page = 4; root.navigate(4); }
|
||||
}
|
||||
|
||||
NavButton {
|
||||
icon: "⚙";
|
||||
title: "Settings";
|
||||
active: root.current-page == 5;
|
||||
clicked => { root.current-page = 5; root.navigate(5); }
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
vertical-stretch: 1;
|
||||
}
|
||||
|
||||
// Status Footer
|
||||
Rectangle {
|
||||
background: mihomo-status == StatusState.starting ? #eab30820 :
|
||||
mihomo-status == StatusState.running ? #22c55e20 :
|
||||
transparent;
|
||||
border-radius: 8px;
|
||||
|
||||
HorizontalLayout {
|
||||
padding: 12px;
|
||||
spacing: 12px;
|
||||
alignment: space-between;
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: 12px;
|
||||
|
||||
StatusIndicator {
|
||||
state: root.mihomo-status;
|
||||
}
|
||||
|
||||
VerticalLayout {
|
||||
spacing: 2px;
|
||||
|
||||
Text {
|
||||
text: "Clash";
|
||||
color: mihomo-status == StatusState.running ? #22c55e :
|
||||
mihomo-status == StatusState.starting ? #eab308 :
|
||||
#9ca3af;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: mihomo-status == StatusState.running ? "Running" :
|
||||
mihomo-status == StatusState.starting ? "Starting..." :
|
||||
"Stopped";
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
text: mihomo-loading ? "..." : (mihomo-status == StatusState.running ? "||" : ">");
|
||||
variant: "ghost";
|
||||
disabled: mihomo-loading;
|
||||
|
||||
clicked => {
|
||||
root.toggle-mihomo();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Content Area
|
||||
content := Rectangle {
|
||||
background: Theme.colors.background;
|
||||
|
||||
// Home Page
|
||||
if root.current-page == 0: HomePage {
|
||||
toggle-mihomo => { root.toggle-mihomo(); }
|
||||
}
|
||||
// Proxies Page
|
||||
if root.current-page == 1: ProxiesPage {}
|
||||
|
||||
// Profiles Page
|
||||
if root.current-page == 2: ProfilesPage {}
|
||||
|
||||
// Connections Page
|
||||
if root.current-page == 3: ConnectionsPage {}
|
||||
|
||||
// Logs Page
|
||||
if root.current-page == 4: LogsPage {}
|
||||
|
||||
// Settings Page
|
||||
if root.current-page == 5: SettingsPage {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component NavButton {
|
||||
in property <string> icon: "";
|
||||
in property <string> title: "";
|
||||
in property <bool> active: false;
|
||||
|
||||
callback clicked();
|
||||
|
||||
private property <bool> hovered: false;
|
||||
|
||||
height: 40px;
|
||||
|
||||
container := Rectangle {
|
||||
background: active ? Theme.colors.primary.transparentize(0.9) :
|
||||
hovered ? Theme.colors.muted.transparentize(0.5) :
|
||||
transparent;
|
||||
border-radius: 8px;
|
||||
|
||||
HorizontalLayout {
|
||||
padding-left: 12px;
|
||||
padding-right: 12px;
|
||||
spacing: 12px;
|
||||
|
||||
Text {
|
||||
text: root.icon;
|
||||
color: active ? Theme.colors.primary : Theme.colors.muted-foregr font-size: 18px;
|
||||
vertical-alignment: center;
|
||||
width: 20px;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: root.title;
|
||||
color: active ? Theme.colors.foreground : Theme.colors.muted-foreground;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
|
||||
TouchArea {
|
||||
clicked => { root.clicked(); }
|
||||
moved => { root.hovered = self.has-hover; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component HomePage {
|
||||
toggle-mihomo();
|
||||
|
||||
VerticalLayout {
|
||||
padding: 24px;
|
||||
spacing: 16px;
|
||||
|
||||
Text {
|
||||
text: "Home";
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
color: Theme.colors.foreground;
|
||||
}
|
||||
|
||||
ScrollView {
|
||||
VerticalLayout {
|
||||
spacing: 16px;
|
||||
|
||||
// Profile Card
|
||||
Card {
|
||||
VerticalLayout {
|
||||
spacing: 16px;
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: 12px;
|
||||
|
||||
Rectangle {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 12px;
|
||||
background: Theme.colors.primary.transparentize(0.9);
|
||||
|
||||
Text {
|
||||
text: "☁";
|
||||
color: Theme.colors.primary;
|
||||
font-size: 24px;
|
||||
horizontal-alignment: center;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
|
||||
VerticalLayout {
|
||||
spacing: 4px;
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: 8px;
|
||||
|
||||
Text {
|
||||
text: "NanoCloud";
|
||||
color: Theme.colors.foreground;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: 8px;
|
||||
|
||||
Text {
|
||||
text: "🌐 Free-Japan1-Ver.7";
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: 12px;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
|
||||
Badge {
|
||||
text: "Vmess";
|
||||
variant: "outline";
|
||||
}
|
||||
|
||||
Badge {
|
||||
text: "UDP";
|
||||
variant: "outline";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
height: 1px;
|
||||
background: Theme.colors.border.transparentize(0.6);
|
||||
}
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: 24px;
|
||||
|
||||
VerticalLayout {
|
||||
spacing: 4px;
|
||||
|
||||
Text {
|
||||
text: "☁ Usage / Total";
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "1.26GB / 100GB";
|
||||
color: Theme.colors.foreground;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
}
|
||||
n
|
||||
VerticalLayout {
|
||||
spacing: 4px;
|
||||
|
||||
Text {
|
||||
text: "✓ Expiry Date";
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "2025-11-11";
|
||||
color: Theme.colors.foreground;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
VerticalLayout {
|
||||
spacing: 4px;
|
||||
|
||||
Text {
|
||||
text: "✓ Last Update";
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "2025-10-12 10:05";
|
||||
color: Theme.colors.foreground;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Location Card
|
||||
Card {
|
||||
VerticalLayout {
|
||||
spacing: 16px;
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: 24px;
|
||||
|
||||
VerticalLayout {
|
||||
spacing: 4px;
|
||||
|
||||
Text {
|
||||
text: "🌐 IP Address";
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "47.238.198.100";
|
||||
color: Theme.colors.foreground;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
VerticalLayout {
|
||||
spacing: 4px;
|
||||
|
||||
Text {
|
||||
text: "🌐 Location";
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Japan · Tokyo";
|
||||
color: Theme.colors.foreground;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Settings Card
|
||||
Card {
|
||||
VerticalLayout {
|
||||
spacing: 0;
|
||||
|
||||
SettingRow {
|
||||
label: "TUN Mode";
|
||||
Switch {
|
||||
checked: false;
|
||||
toggled(checked) => { debug("TUN mode:", checked); }
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
height: 1px;
|
||||
background: Theme.colors.border.transparentize(0.6);
|
||||
}
|
||||
|
||||
SettingRow {
|
||||
label: "System Proxy";
|
||||
Switch {
|
||||
checked: false;
|
||||
toggled(checked) => { debug("System proxy:", checked); }
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
height: 1px;
|
||||
background: Themelors.border.transparentize(0.6);
|
||||
}
|
||||
|
||||
SettingRow {
|
||||
label: "Proxy Port";
|
||||
Text {
|
||||
text: "7890";
|
||||
color: Theme.colors.primary;
|
||||
font-size: 14px;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component SettingRow {
|
||||
in property <string> label: "";
|
||||
|
||||
height: 48px;
|
||||
|
||||
Horout {
|
||||
spacing: 12px;
|
||||
alignment: space-between;
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: 8px;
|
||||
|
||||
Text {
|
||||
text: "✓";
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: 14px;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: root.label;
|
||||
color: Theme.colors.foreground;
|
||||
font-size: 14px;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
|
||||
@children
|
||||
}
|
||||
}
|
||||
|
||||
component ProxiesPage {
|
||||
Vertica {
|
||||
padding: 24px;
|
||||
spacing: 16px;
|
||||
|
||||
Text {
|
||||
text: "Proxies";
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
color: Theme.colors.foreground;
|
||||
}
|
||||
|
||||
Card {
|
||||
Text {
|
||||
text: "Proxy management page - Coming soon";
|
||||
color: Theme.colors.muted-foreground;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component ProfilesPage {
|
||||
VerticalLayout {
|
||||
padding: 24px;
|
||||
spacing: 16px;
|
||||
|
||||
Text {
|
||||
text: "Profiles";
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
color: Theme.colors.foreground;
|
||||
}
|
||||
|
||||
ScrollView {
|
||||
GridLayout {
|
||||
spacing: 16px;
|
||||
|
||||
Card {
|
||||
min-width: 280px;
|
||||
height: 120px;
|
||||
|
||||
VerticalLayout {
|
||||
spacing: 8px;
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: 8px;
|
||||
|
||||
Text {
|
||||
text: "☁";
|
||||
font-size: 18px;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "NanoCloud";
|
||||
color: Theme.colors.foreground;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Free tier subscription - Japan servers";
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
Progress {
|
||||
value: 1.26;
|
||||
}
|
||||
|
||||
HorizontalLayout {
|
||||
alignment: space-between;
|
||||
|
||||
Text {
|
||||
text: "Updated: Just now";
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "1.26 / 100 GB";
|
||||
color: Theme.colors.foreground;
|
||||
font-size: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Card {
|
||||
min-width: 280px;
|
||||
height: 120px;
|
||||
|
||||
VerticalLayout {
|
||||
spacing: 8px;
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: 8px;
|
||||
|
||||
Text {
|
||||
text: "📄";
|
||||
font-size: 18px;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Local Config";
|
||||
color: Theme.colors.foreground;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Custom local configuration file";
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Local Profile";
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component ConnectionsPage {
|
||||
VerticalLayout {
|
||||
padding: 24px;
|
||||
spacing: 16px;
|
||||
|
||||
Text {
|
||||
text: "Connections";
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
color: Theme.colors.foreground;
|
||||
}
|
||||
|
||||
ScrollView {
|
||||
VerticalLayout {
|
||||
spacing: 8px;
|
||||
|
||||
for Card {
|
||||
height: 60px;
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: 12px;
|
||||
|
||||
Rectangle {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 8px;
|
||||
background: Theme.colors.primary.transparentize(0.9);
|
||||
|
||||
Text {
|
||||
text: "🔗";
|
||||
font-size: 18px;
|
||||
horizontal-alignment: center;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
|
||||
VerticalLayout {
|
||||
spacing: 4px;
|
||||
|
||||
Text {
|
||||
text: "192.168.1.100:54321 → github.com:443";
|
||||
color: Theme.colors.foreground;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: 12px;
|
||||
|
||||
Text {
|
||||
text: "HTTPS";
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "↑ 1.2 KB/s";
|
||||
color: Theme.colors.primary;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "↓ 45.6 KB/s";
|
||||
color: Theme.colors.secondary;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component LogsPage {
|
||||
VerticalLayout {
|
||||
padding: 24px;
|
||||
spacing: 16px;
|
||||
|
||||
Text {
|
||||
text: "Logs";
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
color: Theme.colors.foreground;
|
||||
}
|
||||
|
||||
Card {
|
||||
ScrollView {
|
||||
VerticalLayout {
|
||||
spacing: 0;
|
||||
|
||||
for i in 20: LogRow {
|
||||
time: "10:23:45";
|
||||
level: i < 5 ? "info" : (i < 10 ? "debug" : (i < 15 ? "warn" : "error"));
|
||||
message: "Sample log message " + i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component LogRow {
|
||||
in property <string> time: "";
|
||||
in property <string> level: "";
|
||||
in property <string> message: "";
|
||||
|
||||
private property <color> level-color: level == "error" ? #ef4444 :
|
||||
level == "warn" ? #eab308 :
|
||||
level == "info" ? #3b82f6 :
|
||||
#6b7280;
|
||||
|
||||
: 24px;
|
||||
|
||||
HorizontalLayout {
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
spacing: 8px;
|
||||
|
||||
Text {
|
||||
text: root.time;
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: 11px;
|
||||
font-family: "monospace";
|
||||
vertical-alignment: center;
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: root.level;
|
||||
color: root.level-color;
|
||||
font-size: 11px;
|
||||
font-family: "monospace";
|
||||
font-weight: 700;
|
||||
vertical-alignment: center;
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: root.message;
|
||||
color: Theme.colors.foreground;
|
||||
font-size: 11px;
|
||||
font-family: "monospace";
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component SettingsPage {
|
||||
VerticalLayout {
|
||||
padding: 24px;
|
||||
spacing: 16px;
|
||||
|
||||
Text {
|
||||
text: "Settings";
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
color: Theme.colors.foreground;
|
||||
}
|
||||
|
||||
ScrollView {
|
||||
VerticalLayout {
|
||||
spacing: 16px;
|
||||
|
||||
Card {
|
||||
VerticalLayout {
|
||||
spacing: 12px;
|
||||
|
||||
Text {
|
||||
text: "App Settings";
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: Theme.colors.foreground;
|
||||
}
|
||||
|
||||
SettingRow {
|
||||
label: "Auto Start";
|
||||
Switch {
|
||||
checked: false;
|
||||
toggled(checked) debug("Auto start:", checked); }
|
||||
}
|
||||
}
|
||||
|
||||
SettingRow {
|
||||
label: "Silent Start";
|
||||
Switch {
|
||||
checked: false;
|
||||
toggled(checked) => { debug("Silent start:", checked); }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Card {
|
||||
VerticalLayout {
|
||||
spacing: 12px;
|
||||
|
||||
Text {
|
||||
t Settings";
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: Theme.colors.foreground;
|
||||
}
|
||||
|
||||
SettingRow {
|
||||
label: "Allow LAN";
|
||||
Switch {
|
||||
checked: false;
|
||||
toggled(checked) => { debug("Allow LAN:", checked); }
|
||||
}
|
||||
}
|
||||
|
||||
SettingRow {
|
||||
label: "IPv6";
|
||||
Switch {
|
||||
checked: false;
|
||||
toggled(checked) => { debug("IPv6:", checked); }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
452
ui/app.slint
452
ui/app.slint
@@ -1,210 +1,270 @@
|
||||
import { Theme } from "./theme/theme.slint";
|
||||
import { AppSidebar, NavItem } from "./components/app-sidebar.slint";
|
||||
import { StatusState } from "./components/status-indicator.slint";
|
||||
import { HomePage } from "./pages/home.slint";
|
||||
import { ProfilesPage, ProfileData } from "./pages/profiles.slint";
|
||||
import { ProxiesPage, ProxyGroup, ProxyNode } from "./pages/proxies.slint";
|
||||
import { LogsPage, LogEntry } from "./pages/logs.slint";
|
||||
import { ConnectionsPage, Connection } from "./pages/connections.slint";
|
||||
import { SettingsPage } from "./pages/settings.slint";
|
||||
// Main Application - Proxy Management Tool
|
||||
import { Theme, Typography, SpacingSystem } from "theme/theme.slint";
|
||||
import { Button } from "components/button.slint";
|
||||
import { Sidebar, NavItem } from "components/sidebar.slint";
|
||||
import { ToastContainer, ToastMessage } from "components/toast.slint";
|
||||
import { Profile, ProxyGroup, Connection, LogEntry, SettingItem } from "types.slint";
|
||||
import { ConnectionsPage } from "pages/connections.slint";
|
||||
import { LogsPage } from "pages/logs.slint";
|
||||
import { SettingsPage } from "pages/settings.slint";
|
||||
import { ProfilesPage } from "pages/profiles.slint";
|
||||
import { HomePage } from "pages/home.slint";
|
||||
import { ProxiesPage } from "pages/proxies.slint";
|
||||
|
||||
export component App inherits Window {
|
||||
title: "Clash Manager";
|
||||
preferred-width: 1200px;
|
||||
preferred-height: 800px;
|
||||
title: "Proxy Manager";
|
||||
background: Theme.colors.background;
|
||||
|
||||
// Navigation state
|
||||
in-out property <int> current-page: 0;
|
||||
|
||||
// Mihomo status
|
||||
in-out property <StatusState> mihomo-status: StatusState.stopped;
|
||||
in-out property <bool> mihomo-loading: false;
|
||||
|
||||
// Home page data
|
||||
in-out property <string> profile-name: "NanoCloud";
|
||||
in-out property <string> profile-node: "Free-Japan1-Ver.7";
|
||||
in-out property <float> profile-usage: 1.26;
|
||||
in-out property <float> profile-total: 100;
|
||||
in-out property <string> profile-expiry: "2025-11-11";
|
||||
in-out property <string> profile-updated: "2025-10-12 10:05";
|
||||
in-out property <string> location-ip: "47.238.198.100";
|
||||
in-out property <string> location-region: "Japan · Tokyo";
|
||||
in-out property <bool> tun-mode-enabled: false;
|
||||
in-out property <bool> system-proxy-enabled: false;
|
||||
in-out property <int> proxy-port: 7890;
|
||||
|
||||
// Profiles page data
|
||||
in-out property <[ProfileData]> profiles: [];
|
||||
in-out property <string> selected-profile-id: "";
|
||||
|
||||
// Proxies page data
|
||||
|
||||
// Window size settings
|
||||
min-width: 800px;
|
||||
min-height: 600px;
|
||||
preferred-width: 1000px;
|
||||
preferred-height: 700px;
|
||||
|
||||
// ===== Application State =====
|
||||
in-out property <string> current-view: "home";
|
||||
in-out property <bool> sidebar-collapsed: false;
|
||||
|
||||
// ===== Data State =====
|
||||
in-out property <Profile> current-profile;
|
||||
in-out property <[Profile]> profiles: [];
|
||||
in-out property <[ProxyGroup]> proxy-groups: [];
|
||||
in-out property <int> proxy-mode: 0;
|
||||
in-out property <string> selected-proxy: "";
|
||||
|
||||
// Logs page data
|
||||
in-out property <string> proxy-mode: "rule"; // "rule" | "global" | "direct"
|
||||
in-out property <[Connection]> connections: [];
|
||||
in-out property <[LogEntry]> mihomo-logs: [];
|
||||
in-out property <[LogEntry]> app-logs: [];
|
||||
in-out property <int> log-tab: 0;
|
||||
|
||||
// Connections page data
|
||||
in-out property <[Connection]> connections: [];
|
||||
|
||||
// Settings page data
|
||||
in-out property <bool> auto-start: false;
|
||||
in-out property <bool> silent-start: false;
|
||||
in-out property <bool> clash-core: false;
|
||||
in-out property <bool> allow-lan: false;
|
||||
in-out property <bool> ipv6: false;
|
||||
in-out property <bool> unified-delay: false;
|
||||
in-out property <int> log-level-index: 1;
|
||||
in-out property <string> app-dir: "C:/Program Files/Clash";
|
||||
in-out property <string> config-dir: "C:/Users/User/.config/clash";
|
||||
in-out property <string> core-dir: "C:/Program Files/Clash/core";
|
||||
in-out property <string> app-version: "1.0.0";
|
||||
|
||||
// Callbacks
|
||||
callback navigate(int);
|
||||
callback toggle-mihomo();
|
||||
callback refresh-profile();
|
||||
callback toggle-tun-mode(bool);
|
||||
callback toggle-system-proxy(bool);
|
||||
callback open-port-settings();
|
||||
callback add-profile();
|
||||
callback refresh-all-profiles();
|
||||
callback select-profile(string);
|
||||
callback edit-profile(string);
|
||||
callback delete-profile(string);
|
||||
callback refresh-single-profile(string);
|
||||
callback proxy-mode-changed(int);
|
||||
callback proxy-group-toggled(int, bool);
|
||||
callback proxy-selected(string);
|
||||
callback log-tab-changed(int);
|
||||
callback toggle-auto-start(bool);
|
||||
callback toggle-silent-start(bool);
|
||||
callback toggle-clash-core(bool);
|
||||
callback toggle-allow-lan(bool);
|
||||
callback toggle-ipv6(bool);
|
||||
callback toggle-unified-delay(bool);
|
||||
callback log-level-changed(int);
|
||||
callback open-tun-config();
|
||||
callback open-directory(string);
|
||||
|
||||
HorizontalLayout {
|
||||
// Sidebar
|
||||
AppSidebar {
|
||||
nav-items: [
|
||||
{ title: "Home", icon: "🏠" },
|
||||
{ title: "Proxies", icon: "🌐" },
|
||||
{ title: "Profiles", icon: "📋" },
|
||||
{ title: "Connections", icon: "🔗" },
|
||||
{ title: "Logs", icon: "📝" },
|
||||
{ title: "Settings", icon: "⚙" },
|
||||
];
|
||||
current-page: root.current-page;
|
||||
mihomo-status: root.mihomo-status;
|
||||
mihomo-loading: root.mihomo-loading;
|
||||
|
||||
navigate(index) => {
|
||||
root.current-page = index;
|
||||
root.navigate(index);
|
||||
in-out property <[SettingItem]> app-settings: [];
|
||||
in-out property <[SettingItem]> clash-settings: [];
|
||||
in-out property <[ToastMessage]> toasts: [];
|
||||
|
||||
// ===== Callbacks (Rust Implementation) =====
|
||||
// Data loading
|
||||
callback load-profiles();
|
||||
callback load-proxy-groups(string /* mode: rule/global/direct */);
|
||||
callback load-connections();
|
||||
callback load-logs(string /* type: mihomo/app */);
|
||||
callback load-settings();
|
||||
|
||||
// User actions
|
||||
callback select-profile(string /* profile-id */);
|
||||
callback select-proxy(string /* group-name */, string /* node-name */);
|
||||
callback test-proxy-delay(string /* node-name */);
|
||||
callback toggle-setting(string /* key */, bool /* value */);
|
||||
callback update-profile(string /* profile-id */);
|
||||
callback delete-profile(string /* profile-id */);
|
||||
callback add-profile(string /* url */);
|
||||
|
||||
// Toast notification
|
||||
callback show-toast(string /* message */, string /* type */);
|
||||
|
||||
// Sidebar navigation items
|
||||
property <[NavItem]> nav-items: [
|
||||
{ icon: "🏠", label: "Home", active: current-view == "home" },
|
||||
{ icon: "🌍", label: "Proxies", active: current-view == "proxies" },
|
||||
{ icon: "📋", label: "Profiles", active: current-view == "profiles" },
|
||||
{ icon: "🔗", label: "Connections", active: current-view == "connections" },
|
||||
{ icon: "📝", label: "Logs", active: current-view == "logs" },
|
||||
{ icon: "⚙", label: "Settings", active: current-view == "settings" },
|
||||
];
|
||||
|
||||
// Main layout
|
||||
VerticalLayout {
|
||||
// Top bar
|
||||
Rectangle {
|
||||
height: 60px;
|
||||
background: Theme.colors.card;
|
||||
|
||||
// Bottom border
|
||||
Rectangle {
|
||||
y: parent.height - 1px;
|
||||
height: 1px;
|
||||
background: Theme.colors.border;
|
||||
}
|
||||
|
||||
toggle-mihomo => {
|
||||
root.toggle-mihomo();
|
||||
|
||||
HorizontalLayout {
|
||||
padding: SpacingSystem.spacing.s4;
|
||||
spacing: SpacingSystem.spacing.s4;
|
||||
alignment: space-between;
|
||||
|
||||
// Left: Title
|
||||
HorizontalLayout {
|
||||
spacing: SpacingSystem.spacing.s3;
|
||||
alignment: start;
|
||||
|
||||
Text {
|
||||
text: "🔐 Proxy Manager";
|
||||
font-size: Typography.sizes.xl;
|
||||
font-weight: Typography.weights.bold;
|
||||
color: Theme.colors.foreground;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
|
||||
// Right: Theme toggle button
|
||||
Button {
|
||||
text: Theme.is-dark-mode ? "☀ Light" : "🌙 Dark";
|
||||
variant: "outline";
|
||||
size: "sm";
|
||||
|
||||
clicked => {
|
||||
Theme.toggle-theme();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Content Area
|
||||
Rectangle {
|
||||
background: Theme.colors.background;
|
||||
|
||||
// Home Page
|
||||
if root.current-page == 0: HomePage {
|
||||
profile-name: root.profile-name;
|
||||
profile-node: root.profile-node;
|
||||
usage: root.profile-usage;
|
||||
total: root.profile-total;
|
||||
expiry: root.profile-expiry;
|
||||
updated: root.profile-updated;
|
||||
location-ip: root.location-ip;
|
||||
location-region: root.location-region;
|
||||
tun-mode-enabled: root.tun-mode-enabled;
|
||||
system-proxy-enabled: root.system-proxy-enabled;
|
||||
proxy-port: root.proxy-port;
|
||||
|
||||
refresh-profile => { root.refresh-profile(); }
|
||||
toggle-tun-mode(enabled) => { root.toggle-tun-mode(enabled); }
|
||||
toggle-system-proxy(enabled) => { root.toggle-system-proxy(enabled); }
|
||||
open-port-settings => { root.open-port-settings(); }
|
||||
|
||||
// Main content area
|
||||
HorizontalLayout {
|
||||
// Sidebar
|
||||
Sidebar {
|
||||
items: nav-items;
|
||||
collapsed: sidebar-collapsed;
|
||||
|
||||
item-clicked(index) => {
|
||||
if index == 0 {
|
||||
current-view = "home";
|
||||
root.load-profiles();
|
||||
root.load-settings();
|
||||
}
|
||||
else if index == 1 {
|
||||
current-view = "proxies";
|
||||
root.load-proxy-groups(proxy-mode);
|
||||
}
|
||||
else if index == 2 {
|
||||
current-view = "profiles";
|
||||
root.load-profiles();
|
||||
}
|
||||
else if index == 3 {
|
||||
current-view = "connections";
|
||||
root.load-connections();
|
||||
}
|
||||
else if index == 4 {
|
||||
current-view = "logs";
|
||||
root.load-logs("mihomo");
|
||||
}
|
||||
else if index == 5 {
|
||||
current-view = "settings";
|
||||
root.load-settings();
|
||||
}
|
||||
}
|
||||
|
||||
toggle-collapsed => {
|
||||
sidebar-collapsed = !sidebar-collapsed;
|
||||
}
|
||||
}
|
||||
|
||||
// Proxies Page
|
||||
if root.current-page == 1: ProxiesPage {
|
||||
proxy-groups: root.proxy-groups;
|
||||
current-mode: root.proxy-mode;
|
||||
selected-proxy: root.selected-proxy;
|
||||
|
||||
mode-changed(mode) => { root.proxy-mode-changed(mode); }
|
||||
group-toggled(index, expanded) => { root.proxy-group-toggled(index, expanded); }
|
||||
proxy-selected(name) => { root.proxy-selected(name); }
|
||||
|
||||
// Main content area - Pages
|
||||
Rectangle {
|
||||
background: Theme.colors.background;
|
||||
horizontal-stretch: 1;
|
||||
vertical-stretch: 1;
|
||||
|
||||
// Home page
|
||||
if current-view == "home": HomePage {
|
||||
current-profile: root.current-profile;
|
||||
quick-settings: root.app-settings;
|
||||
|
||||
load-home-data => {
|
||||
root.load-profiles();
|
||||
root.load-settings();
|
||||
}
|
||||
|
||||
toggle-setting(key, value) => {
|
||||
root.toggle-setting(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
// Proxies page
|
||||
if current-view == "proxies": ProxiesPage {
|
||||
proxy-groups: root.proxy-groups;
|
||||
proxy-mode: root.proxy-mode;
|
||||
|
||||
load-proxy-groups(mode) => {
|
||||
root.proxy-mode = mode;
|
||||
root.load-proxy-groups(mode);
|
||||
}
|
||||
|
||||
select-proxy(group-name, node-name) => {
|
||||
root.select-proxy(group-name, node-name);
|
||||
}
|
||||
|
||||
test-proxy-delay(node-name) => {
|
||||
root.test-proxy-delay(node-name);
|
||||
}
|
||||
}
|
||||
|
||||
// Profiles page
|
||||
if current-view == "profiles": ProfilesPage {
|
||||
profiles: root.profiles;
|
||||
|
||||
load-profiles => {
|
||||
root.load-profiles();
|
||||
}
|
||||
|
||||
select-profile(profile-id) => {
|
||||
root.select-profile(profile-id);
|
||||
}
|
||||
|
||||
update-profile(profile-id) => {
|
||||
root.update-profile(profile-id);
|
||||
}
|
||||
|
||||
delete-profile(profile-id) => {
|
||||
root.delete-profile(profile-id);
|
||||
}
|
||||
|
||||
add-profile => {
|
||||
root.add-profile("");
|
||||
}
|
||||
}
|
||||
|
||||
// Connections page
|
||||
if current-view == "connections": ConnectionsPage {
|
||||
connections: root.connections;
|
||||
|
||||
load-connections => {
|
||||
root.load-connections();
|
||||
}
|
||||
}
|
||||
|
||||
// Logs page
|
||||
if current-view == "logs": LogsPage {
|
||||
mihomo-logs: root.mihomo-logs;
|
||||
app-logs: root.app-logs;
|
||||
|
||||
load-logs(log-type) => {
|
||||
root.load-logs(log-type);
|
||||
}
|
||||
}
|
||||
|
||||
// Settings page
|
||||
if current-view == "settings": SettingsPage {
|
||||
app-settings: root.app-settings;
|
||||
clash-settings: root.clash-settings;
|
||||
|
||||
load-settings => {
|
||||
root.load-settings();
|
||||
}
|
||||
|
||||
toggle-setting(key, value) => {
|
||||
root.toggle-setting(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Profiles Page
|
||||
if root.current-page == 2: ProfilesPage {
|
||||
profiles: root.profiles;
|
||||
selected-profile-id: root.selected-profile-id;
|
||||
|
||||
adrofile => { root.add-profile(); }
|
||||
refresh-all => { root.refresh-all-profiles(); }
|
||||
select-profile(id) => { root.select-profile(id); }
|
||||
edit-profile(id) => { root.edit-profile(id); }
|
||||
delete-profile(id) => { root.delete-profile(id); }
|
||||
refresh-profile(id) => { root.refresh-single-profile(id); }
|
||||
}
|
||||
|
||||
// Connections Page
|
||||
if root.current-page == 3: ConnectionsPage {
|
||||
connections: root.connections;
|
||||
}
|
||||
|
||||
// Logs Page
|
||||
if root.current-page == 4: LogsPage {
|
||||
mihomo-logs: root.mihomo-logs;
|
||||
app-logs: root.app-logs;
|
||||
current-tab: root.log-tab;
|
||||
|
||||
tab-changed(index) => { root.log-tab-changed(index); }
|
||||
}
|
||||
|
||||
// Settings Page
|
||||
if root.current-page == 5: SettingsPage {
|
||||
auto-start: root.auto-start;
|
||||
silent-start: root.silent-start;
|
||||
clash-core: root.clash-core;
|
||||
tun-mode: root.tun-mode-enabled;
|
||||
allow-lan: root.allow-lan;
|
||||
ipv6: root.ipv6;
|
||||
unified-delay: root.unified-delay;
|
||||
log-level-index: root.log-level-index;
|
||||
proxy-port: root.proxy-port;
|
||||
app-dir: root.app-dir;
|
||||
config-dir: root.config-dir;
|
||||
core-dir: root.core-dir;
|
||||
app-version: root.app-version;
|
||||
|
||||
toggle-auto-start(enabled) => { root.toggle-auto-start(enabled); }
|
||||
toggle-silent-start(enabled) => { root.toggle-silent-start(enabled); }
|
||||
toggle-clash-core(enabled) => { root.toggle-clash-core(enabled); }
|
||||
toggle-tun-mode(enabled) => { root.toggle-tun-mode(enabled); }
|
||||
toggle-allow-lan(enabled) => { root.toggle-allow-lan(enabled); }
|
||||
toggle-ipv6(enabled) => { root.toggle-ipv6(enabled); }
|
||||
toggle-unified-delay(enabled) => { root.toggle-unified-delay(enabled); }
|
||||
log-level-changed(index) => { root.log-level-changed(index); }
|
||||
open-port-settings => { root.open-port-settings(); }
|
||||
open-tun-config => { root.open-tun-config(); }
|
||||
open-directory(dir) => { root.open-directory(dir); }
|
||||
}
|
||||
}
|
||||
|
||||
// Toast container
|
||||
Rectangle {
|
||||
x: parent.width - 390px;
|
||||
y: 20px;
|
||||
width: 370px;
|
||||
|
||||
ToastContainer {
|
||||
toasts: toasts;
|
||||
|
||||
toast-dismissed(index) => {
|
||||
// Will be handled by Rust
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Theme, SpacingSystem, Typography, Animations } from "../theme/theme.slint";
|
||||
import { Theme, SpacingSystem, Typography } from "../theme/theme.slint";
|
||||
import { Animations } from "../utils/animations.slint";
|
||||
|
||||
export struct AccordionItemData {
|
||||
title: string,
|
||||
|
||||
40
ui/components/empty.slint
Normal file
40
ui/components/empty.slint
Normal file
@@ -0,0 +1,40 @@
|
||||
// Empty state component - Shows when no data is available
|
||||
import { Theme, Typography, SpacingSystem } from "../theme/theme.slint";
|
||||
|
||||
export component Empty inherits Rectangle {
|
||||
in property <string> icon: "📭";
|
||||
in property <string> title: "No Data";
|
||||
in property <string> description: "";
|
||||
|
||||
background: transparent;
|
||||
|
||||
VerticalLayout {
|
||||
alignment: center;
|
||||
spacing: SpacingSystem.spacing.s3;
|
||||
|
||||
// Icon
|
||||
Text {
|
||||
text: root.icon;
|
||||
font-size: 48px;
|
||||
horizontal-alignment: center;
|
||||
}
|
||||
|
||||
// Title
|
||||
Text {
|
||||
text: root.title;
|
||||
font-size: Typography.sizes.lg;
|
||||
font-weight: Typography.weights.semibold;
|
||||
color: Theme.colors.foreground;
|
||||
horizontal-alignment: center;
|
||||
}
|
||||
|
||||
// Description (optional)
|
||||
if root.description != "": Text {
|
||||
text: root.description;
|
||||
font-size: Typography.sizes.sm;
|
||||
color: Theme.colors.muted-foreground;
|
||||
horizontal-alignment: center;
|
||||
wrap: word-wrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Theme, SpacingSystem, Animations } from "../theme/theme.slint";
|
||||
import { Theme, SpacingSystem } from "../theme/theme.slint";
|
||||
import { Animations } from "../utils/animations.slint";
|
||||
|
||||
export component Switch {
|
||||
in property <bool> checked: false;
|
||||
|
||||
11
ui/layouts/container.slint
Normal file
11
ui/layouts/container.slint
Normal file
@@ -0,0 +1,11 @@
|
||||
// Container component - Card-style container with border and rounded corners
|
||||
import { Theme, SpacingSystem } from "../theme/theme.slint";
|
||||
|
||||
export component Container inherits Rectangle {
|
||||
@children
|
||||
|
||||
background: Theme.colors.card;
|
||||
border-radius: SpacingSystem.radius.md;
|
||||
border-width: 1px;
|
||||
border-color: Theme.colors.border;
|
||||
}
|
||||
52
ui/layouts/page-layout.slint
Normal file
52
ui/layouts/page-layout.slint
Normal file
@@ -0,0 +1,52 @@
|
||||
// Page layout component - Fixed header with scrollable content area
|
||||
import { Theme, Typography, SpacingSystem } from "../theme/theme.slint";
|
||||
|
||||
export component PageLayout inherits Rectangle {
|
||||
in property <string> title;
|
||||
in property <bool> show-actions: false;
|
||||
|
||||
@children
|
||||
|
||||
background: Theme.colors.background;
|
||||
|
||||
VerticalLayout {
|
||||
// Fixed header
|
||||
header := Rectangle {
|
||||
height: 48px;
|
||||
background: Theme.colors.background;
|
||||
|
||||
HorizontalLayout {
|
||||
padding-left: SpacingSystem.spacing.s4;
|
||||
padding-right: SpacingSystem.spacing.s4;
|
||||
padding-top: SpacingSystem.spacing.s3;
|
||||
padding-bottom: SpacingSystem.spacing.s3;
|
||||
spacing: SpacingSystem.spacing.s4;
|
||||
alignment: space-between;
|
||||
|
||||
Text {
|
||||
text: root.title;
|
||||
font-size: Typography.sizes.xl;
|
||||
font-weight: Typography.weights.bold;
|
||||
color: Theme.colors.foreground;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
|
||||
// Right side actions area (inserted via @children)
|
||||
if root.show-actions: HorizontalLayout {
|
||||
spacing: SpacingSystem.spacing.s2;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Scrollable content area
|
||||
Flickable {
|
||||
viewport-height: content-layout.preferred-height + SpacingSystem.spacing.s4 * 2;
|
||||
|
||||
content-layout := VerticalLayout {
|
||||
padding: SpacingSystem.spacing.s4;
|
||||
spacing: SpacingSystem.spacing.s4;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,967 +0,0 @@
|
||||
import { Theme, SpacingSystem, Typography } from "./theme/theme.slint";
|
||||
import { Button } from "./components/button.slint";
|
||||
import { Card } from "./components/card.slint";
|
||||
import { Switch } from "./components/switch.slint";
|
||||
import { Badge } from "./components/badge.slint";
|
||||
import { Progress } from "./components/progress.slint";
|
||||
import { ScrollView } from "std-widgets.slint";
|
||||
|
||||
// Home Page - Redesigned with better UI
|
||||
export component HomePage {
|
||||
VerticalLayout {
|
||||
padding: 24px;
|
||||
spacing: 20px;
|
||||
|
||||
Text {
|
||||
text: "Home";
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
color: Theme.colors.foreground;
|
||||
}
|
||||
|
||||
ScrollView {
|
||||
VerticalLayout {
|
||||
spacing: 20px;
|
||||
|
||||
// Profile Card
|
||||
Card {
|
||||
VerticalLayout {
|
||||
spacing: 16px;
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: 16px;
|
||||
|
||||
Rectangle {
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
border-radius: 12px;
|
||||
background: Theme.colors.primary.transparentize(0.9);
|
||||
|
||||
Text {
|
||||
text: "☁";
|
||||
color: Theme.colors.primary;
|
||||
font-size: 28px;
|
||||
horizontal-alignment: center;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
|
||||
VerticalLayout {
|
||||
spacing: 6px;
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: 10px;
|
||||
|
||||
Text {
|
||||
text: "NanoCloud";
|
||||
color: Theme.colors.foreground;
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: 8px;
|
||||
|
||||
Text {
|
||||
text: "🌐 Free-Japan1-Ver.7";
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: 13px;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
|
||||
Badge {
|
||||
text: "Vmess";
|
||||
variant: "outline";
|
||||
}
|
||||
|
||||
Badge {
|
||||
text: "UDP";
|
||||
variant: "secondary";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
horizontal-stretch: 1;
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "↻";
|
||||
variant: "ghost";
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
height: 1px;
|
||||
background: Theme.colors.border;
|
||||
}
|
||||
|
||||
GridLayout {
|
||||
spacing: 20px;
|
||||
Row {
|
||||
VerticalLayout {
|
||||
spacing: 6px;
|
||||
|
||||
Text {
|
||||
text: "Usage / Total";
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "1.26GB / 100GB";
|
||||
color: Theme.colors.foreground;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
Progress {
|
||||
value: 1.26;
|
||||
}
|
||||
}
|
||||
|
||||
VerticalLayout {
|
||||
spacing: 6px;
|
||||
|
||||
Text {
|
||||
text: "Expiry Date";
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "2025-11-11";
|
||||
color: Theme.colors.foreground;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
VerticalLayout {
|
||||
spacing: 6px;
|
||||
|
||||
Text {
|
||||
text: "Last Update";
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "2025-10-12";
|
||||
color: Theme.colors.foreground;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Location Card
|
||||
Card {
|
||||
VerticalLayout {
|
||||
spacing: 16px;
|
||||
|
||||
Text {
|
||||
text: "Location Info";
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: Theme.colors.foreground;
|
||||
}
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: 32px;
|
||||
|
||||
VerticalLayout {
|
||||
spacing: 6px;
|
||||
|
||||
Text {
|
||||
text: "IP Address";
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "47.238.198.100";
|
||||
color: Theme.colors.foreground;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
VerticalLayout {
|
||||
spacing: 6px;
|
||||
|
||||
Text {
|
||||
text: "Location";
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Japan · Tokyo";
|
||||
color: Theme.colors.foreground;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Quick Settings Card
|
||||
Card {
|
||||
VerticalLayout {
|
||||
spacing: 0;
|
||||
|
||||
Text {
|
||||
text: "Quick Settings";
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: Theme.colors.foreground;
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
|
||||
SettingRow {
|
||||
label: "TUN Mode";
|
||||
Switch {
|
||||
checked: false;
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
height: 1px;
|
||||
background: Theme.colors.border;
|
||||
}
|
||||
|
||||
SettingRow {
|
||||
label: "System Proxy";
|
||||
Switch {
|
||||
checked: false;
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
height: 1px;
|
||||
background: Theme.colors.border;
|
||||
}
|
||||
|
||||
SettingRow {
|
||||
label: "Proxy Port";
|
||||
Text {
|
||||
text: "7890";
|
||||
color: Theme.colors.primary;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component SettingRow {
|
||||
in property <string> label: "";
|
||||
|
||||
height: 52px;
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: 12px;
|
||||
alignment: space-between;
|
||||
|
||||
Text {
|
||||
text: root.label;
|
||||
color: Theme.colors.foreground;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
|
||||
@children
|
||||
}
|
||||
}
|
||||
|
||||
// Profiles Page
|
||||
export component ProfilesPage {
|
||||
VerticalLayout {
|
||||
padding: 24px;
|
||||
spacing: 20px;
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: 12px;
|
||||
alignment: space-between;
|
||||
|
||||
Text {
|
||||
text: "Profiles";
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
color: Theme.colors.foreground;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: 8px;
|
||||
|
||||
Button {
|
||||
text: "+ Add";
|
||||
variant: "default";
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "↻";
|
||||
variant: "ghost";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ScrollView {
|
||||
GridLayout {
|
||||
spacing: 16px;
|
||||
Row {
|
||||
ProfileCard {
|
||||
name: "NanoCloud";
|
||||
type-icon: "☁";
|
||||
description: "Free tier subscription - Japan servers";
|
||||
is-remote: true;
|
||||
usage: 1.26;
|
||||
total: 100.0;
|
||||
updated: "Just now";
|
||||
}
|
||||
|
||||
ProfileCard {
|
||||
name: "Local Config";
|
||||
type-icon: "📄";
|
||||
description: "Custom local configuration file";
|
||||
is-remote: false;
|
||||
updated: "2025-01-15";
|
||||
}
|
||||
|
||||
ProfileCard {
|
||||
name: "Premium VPN";
|
||||
type-icon: "☁";
|
||||
description: "Premium subscription with global servers";
|
||||
is-remote: true;
|
||||
usage: 45.8;
|
||||
total: 500.0;
|
||||
updated: "2 hours ago";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component ProfileCard {
|
||||
in property <string> name: "";
|
||||
in property <string> type-icon: "";
|
||||
in property <string> description: "";
|
||||
in property <bool> is-remote: false;
|
||||
in property <float> usage: 0;
|
||||
in property <float> total: 100;
|
||||
in property <string> updated: "";
|
||||
|
||||
min-width: 300px;
|
||||
height: 140px;
|
||||
|
||||
Card {
|
||||
VerticalLayout {
|
||||
spacing: 10px;
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: 10px;
|
||||
|
||||
Text {
|
||||
text: root.type-icon;
|
||||
font-size: 20px;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: root.name;
|
||||
color: Theme.colors.foreground;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
horizontal-stretch: 1;
|
||||
}
|
||||
|
||||
if root.is-remote: Button {
|
||||
text: "↻";
|
||||
variant: "ghost";
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
text: root.description;
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: 13px;
|
||||
overflow: elide;
|
||||
}
|
||||
|
||||
if root.is-remote: VerticalLayout {
|
||||
spacing: 6px;
|
||||
|
||||
Progress {
|
||||
value: (root.usage / root.total) * 100;
|
||||
}
|
||||
|
||||
HorizontalLayout {
|
||||
alignment: space-between;
|
||||
|
||||
Text {
|
||||
text: "Updated: " + root.updated;
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: root.usage + " / " + root.total + " GB";
|
||||
color: Theme.colors.foreground;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !root.is-remote: Text {
|
||||
text: "Local Profile";
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: 11px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Logs Page
|
||||
export component LogsPage {
|
||||
VerticalLayout {
|
||||
padding: 24px;
|
||||
spacing: 20px;
|
||||
|
||||
Text {
|
||||
text: "Logs";
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
color: Theme.colors.foreground;
|
||||
}
|
||||
|
||||
Card {
|
||||
height: 500px;
|
||||
|
||||
ScrollView {
|
||||
VerticalLayout {
|
||||
spacing: 0;
|
||||
|
||||
LogRow {
|
||||
time: "10:23:45";
|
||||
level: "info";
|
||||
message: "Mihomo core started successfully";
|
||||
}
|
||||
|
||||
LogRow {
|
||||
time: "10:23:46";
|
||||
level: "info";
|
||||
message: "HTTP proxy listening on 127.0.0.1:7890";
|
||||
}
|
||||
|
||||
LogRow {
|
||||
time: "10:23:47";
|
||||
level: "debug";
|
||||
message: "Loading configuration from config.yaml";
|
||||
}
|
||||
|
||||
LogRow {
|
||||
time: "10:23:48";
|
||||
level: "info";
|
||||
message: "Profile loaded: NanoCloud";
|
||||
}
|
||||
|
||||
LogRow {
|
||||
time: "10:23:50";
|
||||
level: "warn";
|
||||
message: "DNS resolution timeout for example.com";
|
||||
}
|
||||
|
||||
LogRow {
|
||||
time: "10:23:51";
|
||||
level: "error";
|
||||
message: "Failed to connect to proxy server";
|
||||
}
|
||||
|
||||
LogRow {
|
||||
time: "10:23:52";
|
||||
level: "info";
|
||||
message: "Retrying connection...";
|
||||
}
|
||||
|
||||
LogRow {
|
||||
time: "10:23:53";
|
||||
level: "info";
|
||||
message: "Connection established successfully";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component LogRow {
|
||||
in property <string> time: "";
|
||||
in property <string> level: "";
|
||||
in property <string> message: "";
|
||||
|
||||
private property <color> level-color: level == "error" ? #ef4444 :
|
||||
level == "warn" ? #eab308 :
|
||||
level == "info" ? #3b82f6 :
|
||||
#6b7280;
|
||||
|
||||
height: 28px;
|
||||
|
||||
HorizontalLayout {
|
||||
padding-left: 12px;
|
||||
padding-right: 12px;
|
||||
spacing: 12px;
|
||||
|
||||
Text {
|
||||
text: root.time;
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: 12px;
|
||||
font-family: "monospace";
|
||||
vertical-alignment: center;
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: root.level;
|
||||
color: root.level-color;
|
||||
font-size: 12px;
|
||||
font-family: "monospace";
|
||||
font-weight: 700;
|
||||
vertical-alignment: center;
|
||||
width: 70px;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: root.message;
|
||||
color: Theme.colors.foreground;
|
||||
font-size: 12px;
|
||||
font-family: "monospace";
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Connections Page
|
||||
export component ConnectionsPage {
|
||||
VerticalLayout {
|
||||
padding: 24px;
|
||||
spacing: 20px;
|
||||
|
||||
Text {
|
||||
text: "Connections";
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
color: Theme.colors.foreground;
|
||||
}
|
||||
|
||||
ScrollView {
|
||||
VerticalLayout {
|
||||
spacing: 12px;
|
||||
|
||||
ConnectionItem {
|
||||
source: "192.168.1.100:54321";
|
||||
destination: "github.com:443";
|
||||
protocol: "HTTPS";
|
||||
upload: "1.2 KB/s";
|
||||
download: "45.6 KB/s";
|
||||
duration: "00:02:15";
|
||||
}
|
||||
|
||||
ConnectionItem {
|
||||
source: "192.168.1.100:54322";
|
||||
destination: "google.com:443";
|
||||
protocol: "HTTPS";
|
||||
upload: "0.5 KB/s";
|
||||
download: "12.3 KB/s";
|
||||
duration: "00:01:30";
|
||||
}
|
||||
|
||||
ConnectionItem {
|
||||
source: "192.168.1.100:54323";
|
||||
destination: "youtube.com:443";
|
||||
protocol: "HTTPS";
|
||||
upload: "2.1 KB/s";
|
||||
download: "256.7 KB/s";
|
||||
duration: "00:05:42";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component ConnectionItem {
|
||||
in property <string> source: "";
|
||||
in property <string> destination: "";
|
||||
in property <string> protocol: "";
|
||||
in property <string> upload: "";
|
||||
in property <string> download: "";
|
||||
in property <string> duration: "";
|
||||
|
||||
height: 72px;
|
||||
|
||||
Card {
|
||||
HorizontalLayout {
|
||||
spacing: 16px;
|
||||
|
||||
Rectangle {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 10px;
|
||||
background: Theme.colors.primary.transparentize(0.9);
|
||||
|
||||
Text {
|
||||
text: "🔗";
|
||||
font-size: 20px;
|
||||
horizontal-alignment: center;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
|
||||
VerticalLayout {
|
||||
spacing: 6px;
|
||||
|
||||
Text {
|
||||
text: root.source + " → " + root.destination;
|
||||
color: Theme.colors.foreground;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
overflow: elide;
|
||||
}
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: 16px;
|
||||
|
||||
Text {
|
||||
text: root.protocol;
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "↑ " + root.upload;
|
||||
color: Theme.colors.primary;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "↓ " + root.download;
|
||||
color: #22c55e;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: root.duration;
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Settings Page
|
||||
export component SettingsPage {
|
||||
VerticalLayout {
|
||||
padding: 24px;
|
||||
spacing: 20px;
|
||||
|
||||
Text {
|
||||
text: "Settings";
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
color: Theme.colors.foreground;
|
||||
}
|
||||
|
||||
ScrollView {
|
||||
VerticalLayout {
|
||||
spacing: 16px;
|
||||
|
||||
Card {
|
||||
VerticalLayout {
|
||||
spacing: 0;
|
||||
|
||||
Text {
|
||||
text: "App Settings";
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: Theme.colors.foreground;
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
|
||||
SettingRow {
|
||||
label: "Auto Start";
|
||||
Switch {
|
||||
checked: false;
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
height: 1px;
|
||||
background: Theme.colors.border;
|
||||
}
|
||||
|
||||
SettingRow {
|
||||
label: "Silent Start";
|
||||
Switch {
|
||||
checked: false;
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
height: 1px;
|
||||
background: Theme.colors.border;
|
||||
}
|
||||
|
||||
SettingRow {
|
||||
label: "Clash Core";
|
||||
Switch {
|
||||
checked: true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Card {
|
||||
VerticalLayout {
|
||||
spacing: 0;
|
||||
|
||||
Text {
|
||||
text: "Clash Settings";
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: Theme.colors.foreground;
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
|
||||
SettingRow {
|
||||
label: "Allow LAN";
|
||||
Switch {
|
||||
checked: false;
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
height: 1px;
|
||||
background: Theme.colors.border;
|
||||
}
|
||||
|
||||
SettingRow {
|
||||
label: "IPv6";
|
||||
Switch {
|
||||
checked: false;
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
height: 1px;
|
||||
background: Theme.colors.border;
|
||||
}
|
||||
|
||||
SettingRow {
|
||||
label: "Unified Delay";
|
||||
Switch {
|
||||
checked: true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Card {
|
||||
VerticalLayout {
|
||||
spacing: 12px;
|
||||
|
||||
Text {
|
||||
text: "About";
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: Theme.colors.foreground;
|
||||
}
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: 12px;
|
||||
alignment: space-between;
|
||||
|
||||
Text {
|
||||
text: "App Version";
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "1.0.0";
|
||||
color: Theme.colors.foreground;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Proxies Page
|
||||
export component ProxiesPage {
|
||||
VerticalLayout {
|
||||
padding: 24px;
|
||||
spacing: 20px;
|
||||
|
||||
Text {
|
||||
text: "Proxies";
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
color: Theme.colors.foreground;
|
||||
}
|
||||
|
||||
ScrollView {
|
||||
VerticalLayout {
|
||||
spacing: 16px;
|
||||
|
||||
Card {
|
||||
VerticalLayout {
|
||||
spacing: 12px;
|
||||
|
||||
Text {
|
||||
text: "Hong Kong";
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: Theme.colors.foreground;
|
||||
}
|
||||
|
||||
GridLayout {
|
||||
spacing: 12px;
|
||||
Row {
|
||||
ProxyNode {
|
||||
name: "HK-01";
|
||||
type-text: "Vmess";
|
||||
latency: "45ms";
|
||||
}
|
||||
|
||||
ProxyNode {
|
||||
name: "HK-02";
|
||||
type-text: "Vmess";
|
||||
latency: "52ms";
|
||||
}
|
||||
|
||||
ProxyNode {
|
||||
name: "HK-03";
|
||||
type-text: "Trojan";
|
||||
latency: "38ms";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Card {
|
||||
VerticalLayout {
|
||||
spacing: 12px;
|
||||
|
||||
Text {
|
||||
text: "Japan";
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: Theme.colors.foreground;
|
||||
}
|
||||
|
||||
GridLayout {
|
||||
spacing: 12px;
|
||||
Row {
|
||||
ProxyNode {
|
||||
name: "JP-Tokyo-01";
|
||||
type-text: "Vmess";
|
||||
latency: "122ms";
|
||||
}
|
||||
|
||||
ProxyNode {
|
||||
name: "JP-Tokyo-02";
|
||||
type-text: "SS";
|
||||
latency: "115ms";
|
||||
}
|
||||
|
||||
ProxyNode {
|
||||
name: "JP-Osaka-01";
|
||||
type-text: "Vmess";
|
||||
latency: "135ms";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component ProxyNode {
|
||||
in property <string> name: "";
|
||||
in property <string> type-text: "";
|
||||
in property <string> latency: "";
|
||||
|
||||
min-width: 200px;
|
||||
height: 70px;
|
||||
|
||||
Card {
|
||||
HorizontalLayout {
|
||||
spacing: 12px;
|
||||
alignment: space-between;
|
||||
|
||||
VerticalLayout {
|
||||
spacing: 6px;
|
||||
|
||||
Text {
|
||||
text: root.name;
|
||||
color: Theme.colors.foreground;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: root.type-text;
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
text: root.latency;
|
||||
color: Theme.colors.primary;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,799 +0,0 @@
|
||||
import { Theme, SpacingSystem, Typography } from "./theme/theme.slint";
|
||||
import { Button } from "./components/button.slint";
|
||||
import { Card } from "./components/card.slint";
|
||||
import { Switch } from "./components/switch.slint";
|
||||
import { Badge } from "./components/badge.slint";
|
||||
import { Progress } from "./components/progress.slint";
|
||||
import { ScrollView } from "std-widgets.slint";
|
||||
|
||||
// Helper components - must be defined before they are used
|
||||
|
||||
component SettingRow {
|
||||
in property <string> label: "";
|
||||
height: 52px;
|
||||
HorizontalLayout {
|
||||
spacing: 12px;
|
||||
alignment: space-between;
|
||||
Text {
|
||||
text: root.label;
|
||||
color: Theme.colors.foreground;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
@children
|
||||
}
|
||||
}
|
||||
|
||||
component ProfileCard {
|
||||
in property <string> name: "";
|
||||
in property <string> type-icon: "";
|
||||
in property <string> description: "";
|
||||
in property <bool> is-remote: false;
|
||||
in property <float> usage: 0;
|
||||
in property <float> total: 100;
|
||||
in property <string> updated: "";
|
||||
min-width: 300px;
|
||||
height: 140px;
|
||||
Card {
|
||||
VerticalLayout {
|
||||
spacing: 10px;
|
||||
HorizontalLayout {
|
||||
spacing: 10px;
|
||||
Text {
|
||||
text: root.type-icon;
|
||||
font-size: 20px;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
Text {
|
||||
text: root.name;
|
||||
color: Theme.colors.foreground;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
Rectangle { horizontal-stretch: 1; }
|
||||
if root.is-remote: Button {
|
||||
text: "↻";
|
||||
variant: "ghost";
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
}
|
||||
Text {
|
||||
text: root.description;
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: 13px;
|
||||
overflow: elide;
|
||||
}
|
||||
if root.is-remote: VerticalLayout {
|
||||
spacing: 6px;
|
||||
Progress {
|
||||
value: (root.usage / root.total) * 100;
|
||||
}
|
||||
HorizontalLayout {
|
||||
alignment: space-between;
|
||||
Text {
|
||||
text: "Updated: " + root.updated;
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: 11px;
|
||||
}
|
||||
Text {
|
||||
text: root.usage + " / " + root.total + " GB";
|
||||
color: Theme.colors.foreground;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
if !root.is-remote: Text {
|
||||
text: "Local Profile";
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: 11px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component LogRow {
|
||||
in property <string> time: "";
|
||||
in property <string> level: "";
|
||||
in property <string> message: "";
|
||||
private property <color> level-color: level == "error" ? #ef4444 :
|
||||
level == "warn" ? #eab308 :
|
||||
level == "info" ? #3b82f6 : #6b7280;
|
||||
height: 28px;
|
||||
HorizontalLayout {
|
||||
padding-left: 12px;
|
||||
padding-right: 12px;
|
||||
spacing: 12px;
|
||||
Text {
|
||||
text: root.time;
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: 12px;
|
||||
font-family: "monospace";
|
||||
vertical-alignment: center;
|
||||
width: 80px;
|
||||
}
|
||||
Text {
|
||||
text: root.level;
|
||||
color: root.level-color;
|
||||
font-size: 12px;
|
||||
font-family: "monospace";
|
||||
font-weight: 700;
|
||||
vertical-alignment: center;
|
||||
width: 70px;
|
||||
}
|
||||
Text {
|
||||
text: root.message;
|
||||
color: Theme.colors.foreground;
|
||||
font-size: 12px;
|
||||
font-family: "monospace";
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component ConnectionItem {
|
||||
in property <string> source: "";
|
||||
in property <string> destination: "";
|
||||
in property <string> protocol: "";
|
||||
in property <string> upload: "";
|
||||
in property <string> download: "";
|
||||
in property <string> duration: "";
|
||||
height: 72px;
|
||||
Card {
|
||||
HorizontalLayout {
|
||||
spacing: 16px;
|
||||
Rectangle {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 10px;
|
||||
background: Theme.colors.primary.transparentize(0.9);
|
||||
Text {
|
||||
text: "🔗";
|
||||
font-size: 20px;
|
||||
horizontal-alignment: center;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
VerticalLayout {
|
||||
spacing: 6px;
|
||||
Text {
|
||||
text: root.source + " → " + root.destination;
|
||||
color: Theme.colors.foreground;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
overflow: elide;
|
||||
}
|
||||
HorizontalLayout {
|
||||
spacing: 16px;
|
||||
Text {
|
||||
text: root.protocol;
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: 12px;
|
||||
}
|
||||
Text {
|
||||
text: "↑ " + root.upload;
|
||||
color: Theme.colors.primary;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
Text {
|
||||
text: "↓ " + root.download;
|
||||
color: #22c55e;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
Text {
|
||||
text: root.duration;
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component ProxyNode {
|
||||
in property <string> name: "";
|
||||
in property <string> type-text: "";
|
||||
in property <string> latency: "";
|
||||
min-width: 200px;
|
||||
height: 70px;
|
||||
Card {
|
||||
HorizontalLayout {
|
||||
spacing: 12px;
|
||||
alignment: space-between;
|
||||
VerticalLayout {
|
||||
spacing: 6px;
|
||||
Text {
|
||||
text: root.name;
|
||||
color: Theme.colors.foreground;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
Text {
|
||||
text: root.type-text;
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
Text {
|
||||
text: root.latency;
|
||||
color: Theme.colors.primary;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Exported page components
|
||||
|
||||
export component HomePage {
|
||||
VerticalLayout {
|
||||
padding: 24px;
|
||||
spacing: 20px;
|
||||
Text {
|
||||
text: "Home";
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
color: Theme.colors.foreground;
|
||||
}
|
||||
ScrollView {
|
||||
VerticalLayout {
|
||||
spacing: 20px;
|
||||
Card {
|
||||
VerticalLayout {
|
||||
spacing: 16px;
|
||||
HorizontalLayout {
|
||||
spacing: 16px;
|
||||
Rectangle {
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
border-radius: 12px;
|
||||
background: Theme.colors.primary.transparentize(0.9);
|
||||
Text {
|
||||
text: "☁";
|
||||
color: Theme.colors.primary;
|
||||
font-size: 28px;
|
||||
horizontal-alignment: center;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
VerticalLayout {
|
||||
spacing: 6px;
|
||||
HorizontalLayout {
|
||||
spacing: 10px;
|
||||
Text {
|
||||
text: "NanoCloud";
|
||||
color: Theme.colors.foreground;
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
HorizontalLayout {
|
||||
spacing: 8px;
|
||||
Text {
|
||||
text: "🌐 Free-Japan1-Ver.7";
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: 13px;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
Badge {
|
||||
text: "Vmess";
|
||||
variant: "outline";
|
||||
}
|
||||
Badge {
|
||||
text: "UDP";
|
||||
variant: "secondary";
|
||||
}
|
||||
}
|
||||
}
|
||||
Rectangle { horizontal-stretch: 1; }
|
||||
Button {
|
||||
text: "↻";
|
||||
variant: "ghost";
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
}
|
||||
Rectangle {
|
||||
height: 1px;
|
||||
background: Theme.colors.border;
|
||||
}
|
||||
GridLayout {
|
||||
spacing: 20px;
|
||||
Row {
|
||||
VerticalLayout {
|
||||
spacing: 6px;
|
||||
Text {
|
||||
text: "Usage / Total";
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
Text {
|
||||
text: "1.26GB / 100GB";
|
||||
color: Theme.colors.foreground;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
Progress { value: 1.26; }
|
||||
}
|
||||
VerticalLayout {
|
||||
spacing: 6px;
|
||||
Text {
|
||||
text: "Expiry Date";
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
Text {
|
||||
text: "2025-11-11";
|
||||
color: Theme.colors.foreground;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
VerticalLayout {
|
||||
spacing: 6px;
|
||||
Text {
|
||||
text: "Last Update";
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
Text {
|
||||
text: "2025-10-12";
|
||||
color: Theme.colors.foreground;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Card {
|
||||
VerticalLayout {
|
||||
spacing: 16px;
|
||||
Text {
|
||||
text: "Location Info";
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: Theme.colors.foreground;
|
||||
}
|
||||
HorizontalLayout {
|
||||
spacing: 32px;
|
||||
VerticalLayout {
|
||||
spacing: 6px;
|
||||
Text {
|
||||
text: "IP Address";
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
Text {
|
||||
text: "47.238.198.100";
|
||||
color: Theme.colors.foreground;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
VerticalLayout {
|
||||
spacing: 6px;
|
||||
Text {
|
||||
text: "Location";
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
Text {
|
||||
text: "Japan · Tokyo";
|
||||
color: Theme.colors.foreground;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Card {
|
||||
VerticalLayout {
|
||||
spacing: 0;
|
||||
Text {
|
||||
text: "Quick Settings";
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: Theme.colors.foreground;
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
SettingRow {
|
||||
label: "TUN Mode";
|
||||
Switch { checked: false; }
|
||||
}
|
||||
Rectangle {
|
||||
height: 1px;
|
||||
background: Theme.colors.border;
|
||||
}
|
||||
SettingRow {
|
||||
label: "System Proxy";
|
||||
Switch { checked: false; }
|
||||
}
|
||||
Rectangle {
|
||||
height: 1px;
|
||||
background: Theme.colors.border;
|
||||
}
|
||||
SettingRow {
|
||||
label: "Proxy Port";
|
||||
Text {
|
||||
text: "7890";
|
||||
color: Theme.colors.primary;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export component ProfilesPage {
|
||||
VerticalLayout {
|
||||
padding: 24px;
|
||||
spacing: 20px;
|
||||
HorizontalLayout {
|
||||
spacing: 12px;
|
||||
alignment: space-between;
|
||||
Text {
|
||||
text: "Profiles";
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
color: Theme.colors.foreground;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
HorizontalLayout {
|
||||
spacing: 8px;
|
||||
Button {
|
||||
text: "+ Add";
|
||||
variant: "default";
|
||||
}
|
||||
Button {
|
||||
text: "↻";
|
||||
variant: "ghost";
|
||||
}
|
||||
}
|
||||
}
|
||||
ScrollView {
|
||||
GridLayout {
|
||||
spacing: 16px;
|
||||
Row {
|
||||
ProfileCard {
|
||||
name: "NanoCloud";
|
||||
type-icon: "☁";
|
||||
description: "Free tier subscription - Japan servers";
|
||||
is-remote: true;
|
||||
usage: 1.26;
|
||||
total: 100.0;
|
||||
updated: "Just now";
|
||||
}
|
||||
ProfileCard {
|
||||
name: "Local Config";
|
||||
type-icon: "📄";
|
||||
description: "Custom local configuration file";
|
||||
is-remote: false;
|
||||
updated: "2025-01-15";
|
||||
}
|
||||
ProfileCard {
|
||||
name: "Premium VPN";
|
||||
type-icon: "☁";
|
||||
description: "Premium subscription with global servers";
|
||||
is-remote: true;
|
||||
usage: 45.8;
|
||||
total: 500.0;
|
||||
updated: "2 hours ago";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export component LogsPage {
|
||||
VerticalLayout {
|
||||
padding: 24px;
|
||||
spacing: 20px;
|
||||
Text {
|
||||
text: "Logs";
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
color: Theme.colors.foreground;
|
||||
}
|
||||
Card {
|
||||
height: 500px;
|
||||
ScrollView {
|
||||
VerticalLayout {
|
||||
spacing: 0;
|
||||
LogRow {
|
||||
time: "10:23:45";
|
||||
level: "info";
|
||||
message: "Mihomo core started successfully";
|
||||
}
|
||||
LogRow {
|
||||
time: "10:23:46";
|
||||
level: "info";
|
||||
message: "HTTP proxy listening on 127.0.0.1:7890";
|
||||
}
|
||||
LogRow {
|
||||
time: "10:23:47";
|
||||
level: "debug";
|
||||
message: "Loading configuration from config.yaml";
|
||||
}
|
||||
LogRow {
|
||||
time: "10:23:48";
|
||||
level: "info";
|
||||
message: "Profile loaded: NanoCloud";
|
||||
}
|
||||
LogRow {
|
||||
time: "10:23:50";
|
||||
level: "warn";
|
||||
message: "DNS resolution timeout for example.com";
|
||||
}
|
||||
LogRow {
|
||||
time: "10:23:51";
|
||||
level: "error";
|
||||
message: "Failed to connect to proxy server";
|
||||
}
|
||||
LogRow {
|
||||
time: "10:23:52";
|
||||
level: "info";
|
||||
message: "Retrying connection...";
|
||||
}
|
||||
LogRow {
|
||||
time: "10:23:53";
|
||||
level: "info";
|
||||
message: "Connection established successfully";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export component ConnectionsPage {
|
||||
VerticalLayout {
|
||||
padding: 24px;
|
||||
spacing: 20px;
|
||||
Text {
|
||||
text: "Connections";
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
color: Theme.colors.foreground;
|
||||
}
|
||||
ScrollView {
|
||||
VerticalLayout {
|
||||
spacing: 12px;
|
||||
ConnectionItem {
|
||||
source: "192.168.1.100:54321";
|
||||
destination: "github.com:443";
|
||||
protocol: "HTTPS";
|
||||
upload: "1.2 KB/s";
|
||||
download: "45.6 KB/s";
|
||||
duration: "00:02:15";
|
||||
}
|
||||
ConnectionItem {
|
||||
source: "192.168.1.100:54322";
|
||||
destination: "google.com:443";
|
||||
protocol: "HTTPS";
|
||||
upload: "0.5 KB/s";
|
||||
download: "12.3 KB/s";
|
||||
duration: "00:01:30";
|
||||
}
|
||||
ConnectionItem {
|
||||
source: "192.168.1.100:54323";
|
||||
destination: "youtube.com:443";
|
||||
protocol: "HTTPS";
|
||||
upload: "2.1 KB/s";
|
||||
download: "256.7 KB/s";
|
||||
duration: "00:05:42";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export component SettingsPage {
|
||||
VerticalLayout {
|
||||
padding: 24px;
|
||||
spacing: 20px;
|
||||
Text {
|
||||
text: "Settings";
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
color: Theme.colors.foreground;
|
||||
}
|
||||
ScrollView {
|
||||
VerticalLayout {
|
||||
spacing: 16px;
|
||||
Card {
|
||||
VerticalLayout {
|
||||
spacing: 0;
|
||||
Text {
|
||||
text: "App Settings";
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: Theme.colors.foreground;
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
SettingRow {
|
||||
label: "Auto Start";
|
||||
Switch { checked: false; }
|
||||
}
|
||||
Rectangle {
|
||||
height: 1px;
|
||||
background: Theme.colors.border;
|
||||
}
|
||||
SettingRow {
|
||||
label: "Silent Start";
|
||||
Switch { checked: false; }
|
||||
}
|
||||
Rectangle {
|
||||
height: 1px;
|
||||
background: Theme.colors.border;
|
||||
}
|
||||
SettingRow {
|
||||
label: "Clash Core";
|
||||
Switch { checked: true; }
|
||||
}
|
||||
}
|
||||
}
|
||||
Card {
|
||||
VerticalLayout {
|
||||
spacing: 0;
|
||||
Text {
|
||||
text: "Clash Settings";
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: Theme.colors.foreground;
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
SettingRow {
|
||||
label: "Allow LAN";
|
||||
Switch { checked: false; }
|
||||
}
|
||||
Rectangle {
|
||||
height: 1px;
|
||||
background: Theme.colors.border;
|
||||
}
|
||||
SettingRow {
|
||||
label: "IPv6";
|
||||
Switch { checked: false; }
|
||||
}
|
||||
Rectangle {
|
||||
height: 1px;
|
||||
background: Theme.colors.border;
|
||||
}
|
||||
SettingRow {
|
||||
label: "Unified Delay";
|
||||
Switch { checked: true; }
|
||||
}
|
||||
}
|
||||
}
|
||||
Card {
|
||||
VerticalLayout {
|
||||
spacing: 12px;
|
||||
Text {
|
||||
text: "About";
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: Theme.colors.foreground;
|
||||
}
|
||||
HorizontalLayout {
|
||||
spacing: 12px;
|
||||
alignment: space-between;
|
||||
Text {
|
||||
text: "App Version";
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: 14px;
|
||||
}
|
||||
Text {
|
||||
text: "1.0.0";
|
||||
color: Theme.colors.foreground;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export component ProxiesPage {
|
||||
VerticalLayout {
|
||||
padding: 24px;
|
||||
spacing: 20px;
|
||||
Text {
|
||||
text: "Proxies";
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
color: Theme.colors.foreground;
|
||||
}
|
||||
ScrollView {
|
||||
VerticalLayout {
|
||||
spacing: 16px;
|
||||
Card {
|
||||
VerticalLayout {
|
||||
spacing: 12px;
|
||||
Text {
|
||||
text: "Hong Kong";
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: Theme.colors.foreground;
|
||||
}
|
||||
GridLayout {
|
||||
spacing: 12px;
|
||||
Row {
|
||||
ProxyNode {
|
||||
name: "HK-01";
|
||||
type-text: "Vmess";
|
||||
latency: "45ms";
|
||||
}
|
||||
ProxyNode {
|
||||
name: "HK-02";
|
||||
type-text: "Vmess";
|
||||
latency: "52ms";
|
||||
}
|
||||
ProxyNode {
|
||||
name: "HK-03";
|
||||
type-text: "Trojan";
|
||||
latency: "38ms";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Card {
|
||||
VerticalLayout {
|
||||
spacing: 12px;
|
||||
Text {
|
||||
text: "Japan";
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: Theme.colors.foreground;
|
||||
}
|
||||
GridLayout {
|
||||
spacing: 12px;
|
||||
Row {
|
||||
ProxyNode {
|
||||
name: "JP-Tokyo-01";
|
||||
type-text: "Vmess";
|
||||
latency: "122ms";
|
||||
}
|
||||
ProxyNode {
|
||||
name: "JP-Tokyo-02";
|
||||
type-text: "SS";
|
||||
latency: "115ms";
|
||||
}
|
||||
ProxyNode {
|
||||
name: "JP-Osaka-01";
|
||||
type-text: "Vmess";
|
||||
latency: "135ms";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,130 +1,57 @@
|
||||
import { Theme, SpacingSystem, Typography } from "../theme/theme.slint";
|
||||
// Connections Page - Display active connections list
|
||||
import { Theme, Typography, SpacingSystem } from "../theme/theme.slint";
|
||||
import { Item } from "../components/item.slint";
|
||||
import { Empty } from "../components/empty.slint";
|
||||
import { Connection } from "../types.slint";
|
||||
|
||||
export struct Connection {
|
||||
source: string,
|
||||
destination: string,
|
||||
protocol: string,
|
||||
upload: string,
|
||||
download: string,
|
||||
duration: string,
|
||||
}
|
||||
export component ConnectionsPage inherits Rectangle {
|
||||
in property <[Connection]> connections;
|
||||
|
||||
callback load-connections();
|
||||
|
||||
background: Theme.colors.background;
|
||||
|
||||
export component ConnectionsPage {
|
||||
in property <[Connection]> connections: [];
|
||||
|
||||
VerticalLayout {
|
||||
padding: SpacingSystem.spacing.s4;
|
||||
spacing: SpacingSystem.spacing.s4;
|
||||
|
||||
// Header
|
||||
Text {
|
||||
text: "Connections";
|
||||
color: Theme.colors.foreground;
|
||||
font-size: Typography.sizes.xl;
|
||||
font-weight: Typography.weights.bold;
|
||||
}
|
||||
|
||||
// Connections List
|
||||
ScrollView {
|
||||
VerticalLayout {
|
||||
spacing: SpacingSystem.spacing.s2;
|
||||
|
||||
for connection in root.connections: ConnectionItem {
|
||||
connection-data: connection;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Fixed header
|
||||
Rectangle {
|
||||
height: 48px;
|
||||
background: Theme.colors.background;
|
||||
|
||||
HorizontalLayout {
|
||||
padding: SpacingSystem.spacing.s4;
|
||||
|
||||
component ConnectionItem {
|
||||
in property <Connection> connection-data;
|
||||
|
||||
private property <bool> hovered: false;
|
||||
|
||||
height: 60px;
|
||||
|
||||
states [
|
||||
hovered when root.hovered: {
|
||||
container.background: Theme.colors.muted.transparentize(0.5);
|
||||
}
|
||||
]
|
||||
|
||||
container := Rectangle {
|
||||
background: transparent;
|
||||
border-radius: SpacingSystem.radius.md;
|
||||
border-width: 1px;
|
||||
border-color: Theme.colors.border;
|
||||
|
||||
animate background { duration: 150ms; }
|
||||
|
||||
HorizontalLayout {
|
||||
padding: SpacingSystem.spacing.s3;
|
||||
spacing: SpacingSystem.spacing.s3;
|
||||
alignment: space-between;
|
||||
|
||||
// Icon
|
||||
Rectangle {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: SpacingSystem.radius.md;
|
||||
background: Theme.colors.primary.transparentize(0.9);
|
||||
|
||||
Text {
|
||||
text: "🔗";
|
||||
font-size: Typography.sizes.lg;
|
||||
horizontal-alignment: center;
|
||||
text: "Connections";
|
||||
font-size: Typography.sizes.xl;
|
||||
font-weight: Typography.weights.bold;
|
||||
color: Theme.colors.foreground;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
|
||||
// Connection Info
|
||||
VerticalLayout {
|
||||
spacing: SpacingSystem.spacing.s1;
|
||||
|
||||
Text {
|
||||
text: root.connection-data.source + " → " + root.connection-data.destination;
|
||||
color: Theme.colors.foreground;
|
||||
font-size: Typography.sizes.sm;
|
||||
font-weight: Typography.weights.medium;
|
||||
overflow: elide;
|
||||
}
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: SpacingSystem.spacing.s3;
|
||||
|
||||
Text {
|
||||
text: root.connection-data.protocol;
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: Typography.sizes.xs;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "↑ " + root.connection-data.upload;
|
||||
color: Theme.colors.primary;
|
||||
font-size: Typography.sizes.xs;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "↓ " + root.connection-data.download;
|
||||
color: Theme.colors.secondary;
|
||||
font-size: Typography.sizes.xs;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Duration
|
||||
Text {
|
||||
text: root.connection-data.duration;
|
||||
color: Theme.colmuted-foreground;
|
||||
font-size: Typography.sizes.xs;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
|
||||
touch := TouchArea {
|
||||
moved => {
|
||||
root.hovered = self.has-hover;
|
||||
|
||||
// Scrollable content
|
||||
Flickable {
|
||||
viewport-height: content-layout.preferred-height + SpacingSystem.spacing.s4 * 2;
|
||||
|
||||
content-layout := VerticalLayout {
|
||||
padding: SpacingSystem.spacing.s4;
|
||||
spacing: SpacingSystem.spacing.s2;
|
||||
|
||||
// Empty state
|
||||
if connections.length == 0: Empty {
|
||||
height: 300px;
|
||||
icon: "🔗";
|
||||
title: "No Active Connections";
|
||||
description: "Connections will appear here when active";
|
||||
}
|
||||
|
||||
// Connection list
|
||||
for connection in connections: Item {
|
||||
icon: "✓";
|
||||
title: connection.host;
|
||||
description: "Process: " + connection.process + " | Rule: " + connection.rule + " | ↑ " + connection.upload + " ↓ " + connection.download;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,519 +1,380 @@
|
||||
import { Theme, SpacingSystem, Typography } from "../theme/theme.slint";
|
||||
import { Card } from "../components/card.slint";
|
||||
// Home Page - Dashboard with profile info and quick settings
|
||||
import { Theme, Typography, SpacingSystem } from "../theme/theme.slint";
|
||||
import { Container } from "../layouts/container.slint";
|
||||
import { Badge } from "../components/badge.slint";
|
||||
import { Button } from "../components/button.slint";
|
||||
import { Switch } from "../components/switch.slint";
|
||||
import { Label, LabelSize, LabelColor } from "../components/label.slint";
|
||||
import { Progress } from "../components/progress.slint";
|
||||
import { Profile, SettingItem } from "../types.slint";
|
||||
|
||||
export component HomePage inherits Rectangle {
|
||||
in property <Profile> current-profile;
|
||||
in property <[SettingItem]> quick-settings;
|
||||
|
||||
callback load-home-data();
|
||||
callback toggle-setting(string /* key */, bool /* value */);
|
||||
|
||||
background: Theme.colors.background;
|
||||
|
||||
export component HomePage {
|
||||
callback refresh-profile();
|
||||
callback toggle-tun-mode(bool);
|
||||
callback toggle-system-proxy(bool);
|
||||
callback open-port-settings();
|
||||
|
||||
in property <string> profile-name: "NanoCloud";
|
||||
in property <string> profile-node: "免费-日本1-Ver.7";
|
||||
in property <float> profile-usage: 1.26;
|
||||
in property <float> profile-total: 100;
|
||||
in property <string> profile-expiry: "2025-11-11";
|
||||
in property <string> profile-updated: "2025-10-12 10:05";
|
||||
|
||||
in property <string> location-ip: "47.238.198.100";
|
||||
in property <string> location-region: "日本 · 东京";
|
||||
|
||||
in property <bool> tun-mode-enabled: false;
|
||||
in property <bool> system-proxy-enabled: false;
|
||||
in property <int> proxy-port: 7890;
|
||||
|
||||
VerticalLayout {
|
||||
padding: SpacingSystem.spacing.s4;
|
||||
spacing: SpacingSystem.spacing.s4;
|
||||
|
||||
// Header
|
||||
Text {
|
||||
text: "Home";
|
||||
color: Theme.colors.foreground;
|
||||
font-size: Typography.sizes.xl;
|
||||
font-weight: Typography.weights.bold;
|
||||
// Fixed header
|
||||
Rectangle {
|
||||
height: 48px;
|
||||
background: Theme.colors.background;
|
||||
|
||||
HorizontalLayout {
|
||||
padding: SpacingSystem.spacing.s4;
|
||||
|
||||
Text {
|
||||
text: "Home";
|
||||
font-size: Typography.sizes.xl;
|
||||
font-weight: Typography.weights.bold;
|
||||
color: Theme.colors.foreground;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ScrollView {
|
||||
VerticalLayout {
|
||||
spacing: SpacingSystem.spacing.s3;
|
||||
|
||||
|
||||
// Scrollable content
|
||||
Flickable {
|
||||
viewport-height: content-layout.preferred-height + SpacingSystem.spacing.s4 * 2;
|
||||
|
||||
content-layout := VerticalLayout {
|
||||
padding: SpacingSystem.spacing.s4;
|
||||
spacing: SpacingSystem.spacing.s4;
|
||||
|
||||
// Profile Card
|
||||
ProfileCard {
|
||||
profile-name: root.profile-name;
|
||||
profile-node: root.profile-node;
|
||||
usage: root.profile-usage;
|
||||
total: root.profile-total;
|
||||
expiry: root.profile-expiry;
|
||||
updated: root.profile-updated;
|
||||
|
||||
refresh => {
|
||||
root.refresh-profile();
|
||||
}
|
||||
}
|
||||
|
||||
// Location Card
|
||||
LocationCard {
|
||||
ip: root.location-ip;
|
||||
region: root.location-region;
|
||||
}
|
||||
|
||||
// Settings Card
|
||||
gsCard {
|
||||
tun-enabled: root.tun-mode-enabled;
|
||||
proxy-enabled: root.system-proxy-enabled;
|
||||
port: root.proxy-port;
|
||||
|
||||
tun-toggled(enabled) => {
|
||||
root.toggle-tun-mode(enabled);
|
||||
}
|
||||
|
||||
proxy-toggled(enabled) => {
|
||||
root.toggle-system-proxy(enabled);
|
||||
}
|
||||
|
||||
port-clicked => {
|
||||
root.open-port-settings();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Container {
|
||||
VerticalLayout {
|
||||
padding: SpacingSystem.spacing.s4;
|
||||
spacing: SpacingSystem.spacing.s4;
|
||||
|
||||
component ProfileCard {
|
||||
in property <string> profile-name: "";
|
||||
in property <string> profile-node: "";
|
||||
in property <float> usage: 0;
|
||||
in property <float> total: 100;
|
||||
in property <string> expiry: "";
|
||||
in property <string> updated: "";
|
||||
|
||||
callback refresh();
|
||||
|
||||
Card {
|
||||
VerticalLayout {
|
||||
spacing: SpacingSystem.spacing.s4;
|
||||
|
||||
// Header
|
||||
HorizontalLayout {
|
||||
spacing: SpacingSystem.spacing.s3;
|
||||
padding-bottom: SpacingSystem.ss4;
|
||||
|
||||
// Icon
|
||||
Rectangle {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 12px;
|
||||
background: Theme.colors.primary.transparentize(0.9);
|
||||
|
||||
Text {
|
||||
text: "☁";
|
||||
color: Theme.colors.primary;
|
||||
font-size: 24px;
|
||||
horizontal-alignment: center;
|
||||
vertical-alignment: center;
|
||||
// Header with icon and name
|
||||
HorizontalLayout {
|
||||
spacing: SpacingSystem.spacing.s3;
|
||||
alignment: space-between;
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: SpacingSystem.spacing.s3;
|
||||
|
||||
// Icon
|
||||
Rectangle {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
background: Theme.colors.primary;
|
||||
border-radius: SpacingSystem.radius.lg;
|
||||
|
||||
Text {
|
||||
text: "☁";
|
||||
font-size: 28px;
|
||||
horizontal-alignment: center;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
|
||||
// Profile info
|
||||
VerticalLayout {
|
||||
spacing: SpacingSystem.spacing.s1;
|
||||
|
||||
Text {
|
||||
text: current-profile.name;
|
||||
font-size: Typography.sizes.lg;
|
||||
font-weight: Typography.weights.bold;
|
||||
color: Theme.colors.foreground;
|
||||
}
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: SpacingSystem.spacing.s2;
|
||||
|
||||
Text {
|
||||
text: "🌍";
|
||||
font-size: Typography.sizes.xs;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: current-profile.type;
|
||||
font-size: Typography.sizes.xs;
|
||||
color: Theme.colors.muted-foreground;
|
||||
}
|
||||
|
||||
Badge {
|
||||
text: "Vmess";
|
||||
variant: "outline";
|
||||
}
|
||||
|
||||
Badge {
|
||||
text: "UDP";
|
||||
variant: "outline";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Refresh button
|
||||
Button {
|
||||
text: "🔄";
|
||||
variant: "ghost";
|
||||
size: "sm";
|
||||
}
|
||||
}
|
||||
|
||||
// Divider
|
||||
Rectangle {
|
||||
height: 1px;
|
||||
background: Theme.colors.border;
|
||||
}
|
||||
|
||||
// Stats grid
|
||||
HorizontalLayout {
|
||||
spacing: SpacingSystem.spacing.s6;
|
||||
|
||||
// Usage
|
||||
VerticalLayout {
|
||||
spacing: SpacingSystem.spacing.s1;
|
||||
horizontal-stretch: 1;
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: SpacingSystem.spacing.s2;
|
||||
|
||||
Text {
|
||||
text: "☁";
|
||||
font-size: Typography.sizes.xs;
|
||||
color: Theme.colors.muted-foreground;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "已使用 / 总量";
|
||||
font-size: Typography.sizes.xs;
|
||||
color: Theme.colors.muted-foreground;
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
text: current-profile.usage;
|
||||
font-size: Typography.sizes.sm;
|
||||
font-weight: Typography.weights.medium;
|
||||
color: Theme.colors.foreground;
|
||||
}
|
||||
}
|
||||
|
||||
// Expire date
|
||||
VerticalLayout {
|
||||
spacing: SpacingSystem.spacing.s1;
|
||||
horizontal-stretch: 1;
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: SpacingSystem.spacing.s2;
|
||||
|
||||
Text {
|
||||
text: "✓";
|
||||
font-size: Typography.sizes.xs;
|
||||
color: Theme.colors.muted-foreground;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "到期时间";
|
||||
font-size: Typography.sizes.xs;
|
||||
color: Theme.colors.muted-foreground;
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
text: current-profile.expire-at;
|
||||
font-size: Typography.sizes.sm;
|
||||
font-weight: Typography.weights.medium;
|
||||
color: Theme.colors.foreground;
|
||||
}
|
||||
}
|
||||
|
||||
// Updated date
|
||||
VerticalLayout {
|
||||
spacing: SpacingSystem.spacing.s1;
|
||||
horizontal-stretch: 1;
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: SpacingSystem.spacing.s2;
|
||||
|
||||
Text {
|
||||
text: "🕐";
|
||||
font-size: Typography.sizes.xs;
|
||||
color: Theme.colors.muted-foreground;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "更新时间";
|
||||
font-size: Typography.sizes.xs;
|
||||
color: Theme.colors.muted-foreground;
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
text: current-profile.updated-at;
|
||||
font-size: Typography.sizes.sm;
|
||||
font-weight: Typography.weights.medium;
|
||||
color: Theme.colors.foreground;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Info
|
||||
VerticalLayout {
|
||||
spacing: SpacingSystem.spacing.s1;
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: SpacingSystem.spacing.s2;
|
||||
|
||||
|
||||
// Location Card (placeholder)
|
||||
Container {
|
||||
VerticalLayout {
|
||||
padding: SpacingSystem.spacing.s4;
|
||||
spacing: SpacingSystem.spacing.s3;
|
||||
|
||||
Text {
|
||||
text: root.profile-name;
|
||||
text: "位置信息";
|
||||
font-size: Typography.sizes.base;
|
||||
font-weight: Typography.weights.semibold;
|
||||
color: Theme.colors.foreground;
|
||||
font-size: Typography.sizes.lg;
|
||||
font-weight: Typography.weights.bold;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: SpacingSystem.spacing.s2;
|
||||
|
||||
Text {
|
||||
text: "🌐";
|
||||
font-size: Typography.sizes.xs;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: root.profile-node;
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: Typography.sizes.xs;
|
||||
font-weight: Typography.weights.medium;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
|
||||
Badge {
|
||||
text: "Vmess";
|
||||
variant: "outline";
|
||||
}
|
||||
|
||||
Badge {
|
||||
text: "UDP";
|
||||
variant: "outline";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectan horizontal-stretch: 1;
|
||||
}
|
||||
|
||||
// Refresh button
|
||||
Button {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
variant: "ghost";
|
||||
text: "↻";
|
||||
|
||||
clicked => {
|
||||
root.refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Separator
|
||||
Rectangle {
|
||||
height: 1px;
|
||||
background: Theme.colors.border.transparentize(0.6);
|
||||
}
|
||||
|
||||
// Stats
|
||||
HorizontalLayout {
|
||||
spacing: SpacingSystem.spacing.s6;
|
||||
|
||||
VerticalLayout {
|
||||
spacing: SpacingSystem.spacing.s1;
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: SpacingSystem.spacing.s1;
|
||||
|
||||
Text {
|
||||
text: "☁";
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: Typography.sizes.xs;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "已使用 / 总量";
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: Typography.sizes.xs;
|
||||
font-weight: Typography.weights.semibold;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
text: root.usage + "GB / " + root.total + "GB";
|
||||
color: Theme.colors.foreground;
|
||||
font-size: Typography.sizes.sm;
|
||||
font-weight: Typography.weights.medium;
|
||||
}
|
||||
}
|
||||
|
||||
VerticalLayout {
|
||||
spacing: SpacingSystem.spacing.s1;
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: SpacingSystem.spacing.s1;
|
||||
|
||||
Text {
|
||||
text: "✓";
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: Typography.sizes.xs;
|
||||
vertical-aliter;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "到期时间";
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: Typography.sizes.xs;
|
||||
font-weight: Typography.weights.semibold;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
text: root.expiry;
|
||||
color: Theme.colors.foreground;
|
||||
font-size: Typography.sizes.sm;
|
||||
font-weight: Typography.weights.medium;
|
||||
}
|
||||
}
|
||||
|
||||
VerticalLayout {
|
||||
spacing: SpacingSystem.spacing.s1;
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: SpacingSystem.spacing.s1;
|
||||
|
||||
Text {
|
||||
text: "✓";
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: Typography.sizes.xs;
|
||||
vertical-: center;
|
||||
n
|
||||
Text {
|
||||
text: "更新时间";
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: Typography.sizes.xs;
|
||||
font-weight: Typography.weights.semibold;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
text: root.updated;
|
||||
color: Theme.colors.foreground;
|
||||
font-size: Typography.sizes.sm;
|
||||
font-weight: Typography.weights.medium;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component LocationCard {
|
||||
in property <string> ip: "";
|
||||
in property <string> region: "";
|
||||
|
||||
Card {
|
||||
VerticalLayout {
|
||||
spacing: SpacingSystem.spacing.s4;
|
||||
|
||||
// IP and Location
|
||||
HorizontalLayout {
|
||||
spacing: SpacingSystem.spacing.s6;
|
||||
VerticalLayout {
|
||||
spacing: SpacingSystem.spacing.s1;
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: SpacingSystem.spacing.s1;
|
||||
|
||||
Text {
|
||||
text: "🌐";
|
||||
font-size: Typography.sizes.xs;
|
||||
vertical-alignment: center;
|
||||
HorizontalLayout {
|
||||
spacing: SpacingSystem.spacing.s3;
|
||||
|
||||
Text {
|
||||
text: "🌍";
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
VerticalLayout {
|
||||
spacing: SpacingSystem.spacing.s1;
|
||||
|
||||
Text {
|
||||
text: "当前位置: 日本 东京";
|
||||
font-size: Typography.sizes.sm;
|
||||
color: Theme.colors.foreground;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "IP: 192.168.1.1";
|
||||
font-size: Typography.sizes.xs;
|
||||
color: Theme.colors.muted-foreground;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Quick Settings Card
|
||||
Container {
|
||||
VerticalLayout {
|
||||
padding: SpacingSystem.spacing.s4;
|
||||
spacing: SpacingSystem.spacing.s3;
|
||||
|
||||
Text {
|
||||
text: "IP";
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: Typography.sizes.xs;
|
||||
text: "快捷设置";
|
||||
font-size: Typography.sizes.base;
|
||||
font-weight: Typography.weights.semibold;
|
||||
vertical-alignment: center;
|
||||
color: Theme.colors.foreground;
|
||||
}
|
||||
|
||||
// Quick settings grid
|
||||
HorizontalLayout {
|
||||
spacing: SpacingSystem.spacing.s3;
|
||||
|
||||
// System Proxy
|
||||
Rectangle {
|
||||
horizontal-stretch: 1;
|
||||
height: 80px;
|
||||
background: Theme.colors.muted;
|
||||
border-radius: SpacingSystem.radius.md;
|
||||
|
||||
VerticalLayout {
|
||||
padding: SpacingSystem.spacing.s3;
|
||||
spacing: SpacingSystem.spacing.s2;
|
||||
alignment: center;
|
||||
|
||||
Text {
|
||||
text: "🌐";
|
||||
font-size: 24px;
|
||||
horizontal-alignment: center;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "系统代理";
|
||||
font-size: Typography.sizes.xs;
|
||||
color: Theme.colors.foreground;
|
||||
horizontal-alignment: center;
|
||||
}
|
||||
}
|
||||
|
||||
TouchArea {
|
||||
clicked => {
|
||||
root.toggle-setting("system-proxy", true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TUN Mode
|
||||
Rectangle {
|
||||
horizontal-stretch: 1;
|
||||
height: 80px;
|
||||
background: Theme.colors.muted;
|
||||
border-radius: SpacingSystem.radius.md;
|
||||
|
||||
VerticalLayout {
|
||||
padding: SpacingSystem.spacing.s3;
|
||||
spacing: SpacingSystem.spacing.s2;
|
||||
alignment: center;
|
||||
|
||||
Text {
|
||||
text: "🔧";
|
||||
font-size: 24px;
|
||||
horizontal-alignment: center;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "TUN 模式";
|
||||
font-size: Typography.sizes.xs;
|
||||
color: Theme.colors.foreground;
|
||||
horizontal-alignment: center;
|
||||
}
|
||||
}
|
||||
|
||||
TouchArea {
|
||||
clicked => {
|
||||
root.toggle-setting("tun-mode", true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Allow LAN
|
||||
Rectangle {
|
||||
horizontal-stretch: 1;
|
||||
height: 80px;
|
||||
background: Theme.colors.muted;
|
||||
border-radius: SpacingSystem.radius.md;
|
||||
|
||||
VerticalLayout {
|
||||
padding: SpacingSystem.spacing.s3;
|
||||
spacing: SpacingSystem.spacing.s2;
|
||||
alignment: center;
|
||||
|
||||
Text {
|
||||
text: "📡";
|
||||
font-size: 24px;
|
||||
horizontal-alignment: center;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "允许局域网";
|
||||
font-size: Typography.sizes.xs;
|
||||
color: Theme.colors.foreground;
|
||||
horizontal-alignment: center;
|
||||
}
|
||||
}
|
||||
|
||||
TouchArea {
|
||||
clicked => {
|
||||
root.toggle-setting("allow-lan", true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
text: root.ip;
|
||||
color: Theme.colors.foreground;
|
||||
font-size: Typography.sizes.sm;
|
||||
font-weight: Typography.weights.medium;
|
||||
}
|
||||
}
|
||||
|
||||
VerticalLayout {
|
||||
: SpacingSystem.spacing.s1;
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: SpacingSystem.spacing.s1;
|
||||
|
||||
Text {
|
||||
text: "🌐";
|
||||
font-size: Typography.sizes.xs;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "所属地址";
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: Typography.sizes.xs;
|
||||
font-weight: Typography.weights.semibold;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
text: root.region;
|
||||
color: Theme.colors.foreground;
|
||||
font-size: Typography.sizes.sm;
|
||||
font-weight: Typography.weights.medium;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Latency
|
||||
VerticalLayout {
|
||||
spacing: SpacingSystem.spacing.s2;
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: SpacingSystem.spacing.s1;
|
||||
|
||||
Text {
|
||||
text: "✓";
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: Typography.sizes.xs;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "延迟";
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: Typography.sizes.xs;
|
||||
font-weight: Typography.weights.semibold;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
|
||||
GridLayout {
|
||||
spacing: SpacingSystem.spacing.s2;
|
||||
Row {
|
||||
LatencyItem { service: "Github"; latency: "122ms"; }
|
||||
LatencyItem { service: "Google"; latency: "45ms"; }
|
||||
LatencyItem { service: "Youtube"; latency: "67ms"; }
|
||||
LatencyItem { service: "Twitter"; latency: "89ms"; }
|
||||
}
|
||||
Row {
|
||||
LatencyItem { serviceetflix"; latency: "34ms"; }
|
||||
LatencyItem { service: "Steam"; latency: "156ms"; }
|
||||
LatencyItem { service: "Spotify"; latency: "78ms"; }
|
||||
LatencyItem { service: "Discord"; latency: "92ms"; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component LatencyItem {
|
||||
in property <string> service: "";
|
||||
in property <string> latency: "";
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: SpacingSystem.spacing.s1;
|
||||
|
||||
Text {
|
||||
text: root.service;
|
||||
color: Theme.colors.muted-foreg;
|
||||
font-size: Typography.sizes.xs;
|
||||
font-weight: Typography.weights.medium;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: root.latency;
|
||||
color: Theme.colors.foreground;
|
||||
font-size: Typography.sizes.xs;
|
||||
font-weight: Typography.weights.semibold;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component SettingsCard {
|
||||
in property <bool> tun-enabled: false;
|
||||
in property <bool> proxy-enabled: false;
|
||||
in property <int> port: 7890;
|
||||
|
||||
callback tun-toggled(bool);
|
||||
callback proxy-toggled(bool);
|
||||
callback port-clicked();
|
||||
|
||||
Card {
|
||||
VerticalLayout {
|
||||
spacing: 0;
|
||||
|
||||
// TUN Mode
|
||||
SettingItem {
|
||||
label: "Tun 模式";
|
||||
|
||||
Switch {
|
||||
checked: root.tun-enabled;
|
||||
toggled(checked) => {
|
||||
root.tun-toggled(checked);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
height: 1px;
|
||||
background: Theme.colors.border.transparentize(0.6);
|
||||
}
|
||||
|
||||
// System Proxy
|
||||
SettingItem {
|
||||
label: "系统代理";
|
||||
|
||||
Switch {
|
||||
checked: root.proxy-enabled;
|
||||
toggled(checked) => {
|
||||
root.proxy-toggled(checked);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
height: 1px;
|
||||
background: Theme.colors.border.transparentize(0.6);
|
||||
}
|
||||
|
||||
// Proxy Port
|
||||
SettingItem {
|
||||
label: "代理端口";
|
||||
|
||||
But text: root.port;
|
||||
variant: "link";
|
||||
|
||||
clicked => {
|
||||
root.port-clicked();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component SettingItem {
|
||||
in property <string> label: "";
|
||||
|
||||
height: 40px;
|
||||
|
||||
HorizontalLayout {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
spacing: SpacingSystem.spacing.s3;
|
||||
alignment: space-between;
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: SpacingSystem.spacing.s2;
|
||||
|
||||
Text {
|
||||
text: "✓";
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: Typography.sizes.sm;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: root.label;
|
||||
color: Theme.colors.foreground;
|
||||
font-size: Typography.sizes.sm;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
|
||||
@children
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,170 +1,209 @@
|
||||
import { Theme, SpacingSystem, Typography } from "../theme/theme.slint";
|
||||
import { Tabs, TabItem, TabContent } from "../components/tabs.slint";
|
||||
// Logs Page - Display Mihomo and App logs with tabs
|
||||
import { Theme, Typography, SpacingSystem } from "../theme/theme.slint";
|
||||
import { Empty } from "../components/empty.slint";
|
||||
import { LogEntry } from "../types.slint";
|
||||
|
||||
export struct LogEntry {
|
||||
time: string,
|
||||
level: string, // "debug", "info", "warn", "error"
|
||||
message: string,
|
||||
}
|
||||
export component LogsPage inherits Rectangle {
|
||||
in property <[LogEntry]> mihomo-logs;
|
||||
in property <[LogEntry]> app-logs;
|
||||
in-out property <string> active-tab: "mihomo";
|
||||
|
||||
callback load-logs(string /* type: mihomo/app */);
|
||||
|
||||
background: Theme.colors.background;
|
||||
|
||||
export component LogsPage {
|
||||
in property <[LogEntry]> mihomo-logs: [];
|
||||
in property <[LogEntry]> app-logs: [];
|
||||
in-out property <int> current-tab: 0;
|
||||
|
||||
callback tab-changed(int);
|
||||
|
||||
VerticalLayout {
|
||||
padding: SpacingSystem.spacing.s4;
|
||||
spacing: SpacingSystem.spacing.s4;
|
||||
|
||||
// Header with Tabs
|
||||
HorizontalLayout {
|
||||
spacing: SpacingSystem.spacing.s3;
|
||||
alignment: space-between;
|
||||
|
||||
Text {
|
||||
text: "Logs";
|
||||
color: Theme.colors.foreground;
|
||||
font-size: Typography.sizes.xl;
|
||||
font-weight: Typography.weights.bold;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
horizontal-stretch: 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Tabs
|
||||
Tabs {
|
||||
tabs: [
|
||||
{ title: "Mihomo Logs" },
|
||||
{ title: "App Logs" },
|
||||
];
|
||||
current-index: root.current-tab;
|
||||
|
||||
tab-changed(index) => {
|
||||
root.current-tab = index;
|
||||
root.tab-changed(index);
|
||||
}
|
||||
|
||||
// Mihomo Logs
|
||||
TabContent {
|
||||
index: 0;
|
||||
current-index: root.current-tab;
|
||||
|
||||
LogView {
|
||||
logs: root.mihomo-logs;
|
||||
}
|
||||
}
|
||||
|
||||
// App Logs
|
||||
TabContent {
|
||||
index: 1;
|
||||
current-index: root.current-tab;
|
||||
|
||||
LogView {
|
||||
logs: root.app-logs;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Fixed header with tabs
|
||||
Rectangle {
|
||||
height: 48px;
|
||||
background: Theme.colors.background;
|
||||
|
||||
component LogView {
|
||||
in property <[LogEntry]> logs: [];
|
||||
|
||||
Rectangle {
|
||||
background: Theme.colors.background;
|
||||
border-radius: SpacingSystem.radius.md;
|
||||
border-width: 1px;
|
||||
border-color: Theme.colors.border;
|
||||
|
||||
ScrollView {
|
||||
VerticalLayout {
|
||||
padding: SpacingSystem.spacing.s2;
|
||||
spacing: 0;
|
||||
|
||||
for log[index] in root.logs: LogRow {
|
||||
log-entry: log;
|
||||
odd: Math.mod(index, 2) == 1;
|
||||
HorizontalLayout {
|
||||
padding: SpacingSystem.spacing.s4;
|
||||
spacing: SpacingSystem.spacing.s4;
|
||||
alignment: space-between;
|
||||
|
||||
Text {
|
||||
text: "Logs";
|
||||
font-size: Typography.sizes.xl;
|
||||
font-weight: Typography.weights.bold;
|
||||
color: Theme.colors.foreground;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
|
||||
// Tabs
|
||||
HorizontalLayout {
|
||||
spacing: SpacingSystem.spacing.s1;
|
||||
|
||||
Rectangle {
|
||||
width: 120px;
|
||||
height: 32px;
|
||||
background: active-tab == "mihomo" ? Theme.colors.primary : Theme.colors.muted;
|
||||
border-radius: SpacingSystem.radius.md;
|
||||
|
||||
TouchArea {
|
||||
clicked => {
|
||||
active-tab = "mihomo";
|
||||
root.load-logs("mihomo");
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Mihomo Logs";
|
||||
font-size: Typography.sizes.sm;
|
||||
font-weight: Typography.weights.medium;
|
||||
color: active-tab == "mihomo" ? Theme.colors.primary-foreground : Theme.colors.muted-foreground;
|
||||
horizontal-alignment: center;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 100px;
|
||||
height: 32px;
|
||||
background: active-tab == "app" ? Theme.colors.primary : Theme.colors.muted;
|
||||
border-radius: SpacingSystem.radius.md;
|
||||
|
||||
TouchArea {
|
||||
clicked => {
|
||||
active-tab = "app";
|
||||
root.load-logs("app");
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "App Logs";
|
||||
font-size: Typography.sizes.sm;
|
||||
font-weight: Typography.weights.medium;
|
||||
color: active-tab == "app" ? Theme.colors.primary-foreground : Theme.colors.muted-foreground;
|
||||
horizontal-alignment: center;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Scrollable log content
|
||||
Flickable {
|
||||
viewport-height: log-content.preferred-height + SpacingSystem.spacing.s4 * 2;
|
||||
|
||||
log-content := VerticalLayout {
|
||||
padding: SpacingSystem.spacing.s4;
|
||||
spacing: 0px;
|
||||
|
||||
// Mihomo logs
|
||||
if active-tab == "mihomo": VerticalLayout {
|
||||
spacing: 0px;
|
||||
|
||||
if mihomo-logs.length == 0: Empty {
|
||||
height: 300px;
|
||||
icon: "📝";
|
||||
title: "No Logs";
|
||||
description: "Mihomo logs will appear here";
|
||||
}
|
||||
|
||||
for log[index] in mihomo-logs: Rectangle {
|
||||
height: 24px;
|
||||
background: mod(index, 2) == 0 ? Theme.colors.muted : transparent;
|
||||
|
||||
HorizontalLayout {
|
||||
padding-left: SpacingSystem.spacing.s2;
|
||||
padding-right: SpacingSystem.spacing.s2;
|
||||
spacing: SpacingSystem.spacing.s2;
|
||||
|
||||
// Time
|
||||
Text {
|
||||
text: log.time;
|
||||
font-size: Typography.sizes.xs;
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-family: "monospace";
|
||||
vertical-alignment: center;
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
// Level
|
||||
Text {
|
||||
text: log.level;
|
||||
font-size: Typography.sizes.xs;
|
||||
font-weight: Typography.weights.bold;
|
||||
color: log.level == "error" ? #ef4444 :
|
||||
log.level == "warn" ? #f59e0b :
|
||||
log.level == "info" ? #3b82f6 : Theme.colors.muted-foreground;
|
||||
font-family: "monospace";
|
||||
vertical-alignment: center;
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
// Message
|
||||
Text {
|
||||
text: log.message;
|
||||
font-size: Typography.sizes.xs;
|
||||
color: Theme.colors.foreground;
|
||||
font-family: "monospace";
|
||||
vertical-alignment: center;
|
||||
overflow: elide;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// App logs
|
||||
if active-tab == "app": VerticalLayout {
|
||||
spacing: 0px;
|
||||
|
||||
if app-logs.length == 0: Empty {
|
||||
height: 300px;
|
||||
icon: "📝";
|
||||
title: "No Logs";
|
||||
description: "App logs will appear here";
|
||||
}
|
||||
|
||||
for log[index] in app-logs: Rectangle {
|
||||
height: 24px;
|
||||
background: mod(index, 2) == 0 ? Theme.colors.muted : transparent;
|
||||
|
||||
HorizontalLayout {
|
||||
padding-left: SpacingSystem.spacing.s2;
|
||||
padding-right: SpacingSystem.spacing.s2;
|
||||
spacing: SpacingSystem.spacing.s2;
|
||||
|
||||
// Time
|
||||
Text {
|
||||
text: log.time;
|
||||
font-size: Typography.sizes.xs;
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-family: "monospace";
|
||||
vertical-alignment: center;
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
// Level
|
||||
Text {
|
||||
text: log.level;
|
||||
font-size: Typography.sizes.xs;
|
||||
font-weight: Typography.weights.bold;
|
||||
color: log.level == "error" ? #ef4444 :
|
||||
log.level == "warn" ? #f59e0b :
|
||||
log.level == "info" ? #3b82f6 : Theme.colors.muted-foreground;
|
||||
font-family: "monospace";
|
||||
vertical-alignment: center;
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
// Message
|
||||
Text {
|
||||
text: log.message;
|
||||
font-size: Typography.sizes.xs;
|
||||
color: Theme.colors.foreground;
|
||||
font-family: "monospace";
|
||||
vertical-alignment: center;
|
||||
overflow: elide;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component LogRow {
|
||||
in property <LogEntry> log-entry;
|
||||
in property ool> odd: false;
|
||||
|
||||
private property <bool> hovered: false;
|
||||
|
||||
private property <color> level-color: log-entry.level == "error" ? #ef4444 :
|
||||
log-entry.level == "warn" ? #eab308 :
|
||||
log-entry.level == "info" ? #3b82f6 :
|
||||
log-entry.level == "debug" ? #6b7280 :
|
||||
Theme.colors.muted-foreground;
|
||||
|
||||
height: 24px;
|
||||
|
||||
states [
|
||||
hovered when root.hovered: {
|
||||
container.background: Theme.colors.muted.transparentize(0.5);
|
||||
}
|
||||
odd when root.odd && !root.hovered: {
|
||||
container.background: Theme.colors.muted.transparentize(0.8);
|
||||
}
|
||||
]
|
||||
|
||||
container := Rectangle {
|
||||
background: transparent;
|
||||
border-radius: SpacingSystem.radius.sm;
|
||||
|
||||
animate background { duration: 150ms; }
|
||||
|
||||
HorizontalLayout {
|
||||
padding-left: SpacingSystem.spacing.s2;
|
||||
padding-right: SpacingSystem.spacing.s2;
|
||||
spacing: SpacingSystem.spacing.s2;
|
||||
|
||||
// Time
|
||||
Text {
|
||||
text: root.log-entry.time;
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: 11px;
|
||||
font-family: "monospace";
|
||||
vertical-alignment: center;
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
// Level
|
||||
Text {
|
||||
text: root.log-entry.level;
|
||||
color: root.level-color;
|
||||
font-size: 11px;
|
||||
font-family: "monospace";
|
||||
font-weight: Typography.weights.bold;
|
||||
vertical-alignment: center;
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
// Message
|
||||
Text {
|
||||
text: root.log-entry.message;
|
||||
color: Theme.colors.foreground;
|
||||
font-size: 11px;
|
||||
font-family: "ospace";
|
||||
vertical-alignment: center;
|
||||
overflow: elide;
|
||||
}
|
||||
}
|
||||
|
||||
touch := TouchArea {
|
||||
moved => {
|
||||
root.hovered = self.has-hover;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,203 +1,225 @@
|
||||
import { Theme, SpacingSystem, Typography } from "../theme/theme.slint";
|
||||
import { Card } from "../components/card.slint";
|
||||
// Profiles Page - Configuration profiles management
|
||||
import { Theme, Typography, SpacingSystem } from "../theme/theme.slint";
|
||||
import { Card, CardHeader, CardTitle, CardContent, CardFooter } from "../components/card.slint";
|
||||
import { Button } from "../components/button.slint";
|
||||
import { Badge } from "../components/badge.slint";
|
||||
import { Progress } from "../components/progress.slint";
|
||||
import { Empty } from "../components/empty.slint";
|
||||
import { Profile } from "../types.slint";
|
||||
|
||||
export struct ProfileData {
|
||||
id: string,
|
||||
name: string,
|
||||
type: string, // "local" or "remote"
|
||||
description: string,
|
||||
usage: float, // For remote profiles
|
||||
total: float, // For remote profiles
|
||||
updated: string,
|
||||
}
|
||||
export component ProfilesPage inherits Rectangle {
|
||||
in property <[Profile]> profiles;
|
||||
|
||||
export component ProfilesPage {
|
||||
in property <[ProfileData]> profiles: [];
|
||||
in property <string> selected-profile-id: "";
|
||||
|
||||
callback load-profiles();
|
||||
callback select-profile(string /* profile-id */);
|
||||
callback update-profile(string /* profile-id */);
|
||||
callback delete-profile(string /* profile-id */);
|
||||
callback add-profile();
|
||||
callback refresh-all();
|
||||
callback select-profile(string);
|
||||
callback edit-profile(string);
|
||||
callback delete-profile(string);
|
||||
callback refresh-profile(string);
|
||||
|
||||
VerticalLayout {
|
||||
padding: SpacingSystem.spacing.s4;
|
||||
spacing: SpacingSystem.spacing.s4;
|
||||
|
||||
// Header
|
||||
HorizontalLayout {
|
||||
spacing: SpacingSystem.spacing.s3;
|
||||
alignment: space-between;
|
||||
|
||||
Text {
|
||||
text: "Profiles";
|
||||
color: Theme.colors.foreground;
|
||||
font-size: Typography.sizes.xl;
|
||||
font-weight: Typography.weights.bold;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: SpacingSystem.spacing.s2;
|
||||
|
||||
Button {
|
||||
text: "+";
|
||||
variant: "ghost";
|
||||
|
||||
clicked => {
|
||||
root.add-profile();
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "↻";
|
||||
variant: "ghost";
|
||||
|
||||
clicked => {
|
||||
root.refresh-all();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Profile Grid
|
||||
ScrollView {
|
||||
GridLayout {
|
||||
spacing: SpacingSystem.spacing.s3;
|
||||
|
||||
for profile in root.profiles: ProfileItem {
|
||||
profile-data: profile;
|
||||
selected: profile.id == root.selected-profile-id;
|
||||
|
||||
clicked => {
|
||||
root.select-profile(profile.id);
|
||||
}
|
||||
|
||||
refresh-clicked => {
|
||||
root.refresh-profile(profile.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component ProfileItem {
|
||||
in property <ProfileData> profile-data;
|
||||
in property <bool> selected: false;
|
||||
|
||||
callback clicked();
|
||||
callback refresh-clicked();
|
||||
|
||||
private property <bool> hovered: false;
|
||||
|
||||
min-width: 288px; // 18rem
|
||||
height: 96px;
|
||||
|
||||
states [
|
||||
selected when root.selected: {
|
||||
container.border-color: Theme.colors.primary.transparentize(0.5);
|
||||
container.background: Theme.colors.accent.transparentize(0.4);
|
||||
}
|
||||
]
|
||||
|
||||
container := Card {
|
||||
border-width: 2px;
|
||||
|
||||
VerticalLa spacing: SpacingSystem.spacing.s2;
|
||||
|
||||
// Header
|
||||
background: Theme.colors.background;
|
||||
|
||||
VerticalLayout {
|
||||
// Fixed header
|
||||
Rectangle {
|
||||
height: 48px;
|
||||
background: Theme.colors.background;
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: SpacingSystem.spacing.s2;
|
||||
padding: SpacingSystem.spacing.s4;
|
||||
spacing: SpacingSystem.spacing.s4;
|
||||
alignment: space-between;
|
||||
|
||||
|
||||
Text {
|
||||
text: "Profiles";
|
||||
font-size: Typography.sizes.xl;
|
||||
font-weight: Typography.weights.bold;
|
||||
color: Theme.colors.foreground;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
|
||||
// Action buttons
|
||||
HorizontalLayout {
|
||||
spacing: SpacingSystem.spacing.s2;
|
||||
|
||||
Text {
|
||||
text: root.profile-data.type == "remote" ? "☁" : "📄";
|
||||
font-size: Typography.sizes.lg;
|
||||
vertical-alignment: center;
|
||||
|
||||
Button {
|
||||
text: "➕";
|
||||
variant: "ghost";
|
||||
size: "sm";
|
||||
|
||||
clicked => {
|
||||
root.add-profile();
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
text: root.profile-data.name;
|
||||
color: Theme.colors.foreground;
|
||||
font-size: Typography.sizes.base;
|
||||
font-weight: Typography.weights.semibold;
|
||||
vertical-alignment: center;
|
||||
|
||||
Button {
|
||||
text: "🔄";
|
||||
variant: "ghost";
|
||||
size: "sm";
|
||||
|
||||
clicked => {
|
||||
root.load-profiles();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if root.profile-data.type == "remote": Button {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
text: "↻";
|
||||
variant: "ghost";
|
||||
|
||||
clicked => {
|
||||
root.refresh-clicked();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Description
|
||||
Text {
|
||||
text: root.profile-data.description;
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: Typography.sizes.xs;
|
||||
overflow: elide;
|
||||
}
|
||||
|
||||
// Progress bar for remote profiles
|
||||
if root.profile-data.type == "remote": VerticalLayout {
|
||||
spacing: SpacingSystem.spacing.s1;
|
||||
|
||||
Progress {
|
||||
value: (root.profile-data.usage / root.profile-data.total) * 100;
|
||||
}
|
||||
|
||||
HorizontalLayout {
|
||||
alignment: space-between;
|
||||
|
||||
Text {
|
||||
text: "Updated: " + root.profile-data.updated;
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: root.profile-data.usage + " / " + root.profile-data.total + " GB";
|
||||
color: Theme.colors.oreground;
|
||||
font-size: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Local profile info
|
||||
if root.profile-data.type == "local": HorizontalLayout {
|
||||
alignment: space-between;
|
||||
|
||||
Text {
|
||||
text: "Local Profile";
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
touch := TouchArea {
|
||||
clicked => {
|
||||
root.clicked();
|
||||
}
|
||||
|
||||
moved => {
|
||||
root.hovered = self.has-hover;
|
||||
|
||||
// Scrollable content
|
||||
Flickable {
|
||||
viewport-height: content-layout.preferred-height + SpacingSystem.spacing.s4 * 2;
|
||||
|
||||
content-layout := VerticalLayout {
|
||||
padding: SpacingSystem.spacing.s4;
|
||||
spacing: SpacingSystem.spacing.s4;
|
||||
|
||||
// Empty state
|
||||
if profiles.length == 0: Empty {
|
||||
height: 300px;
|
||||
icon: "📋";
|
||||
title: "No Profiles";
|
||||
description: "Click + to add a new profile";
|
||||
}
|
||||
|
||||
// Profile list
|
||||
for profile in profiles: Card {
|
||||
hoverable: true;
|
||||
|
||||
CardHeader {
|
||||
HorizontalLayout {
|
||||
spacing: SpacingSystem.spacing.s2;
|
||||
alignment: space-between;
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: SpacingSystem.spacing.s2;
|
||||
|
||||
Text {
|
||||
text: "☁";
|
||||
font-size: Typography.sizes.xl;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
|
||||
VerticalLayout {
|
||||
spacing: SpacingSystem.spacing.s1;
|
||||
|
||||
CardTitle {
|
||||
text: profile.name;
|
||||
}
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: SpacingSystem.spacing.s1;
|
||||
|
||||
Badge {
|
||||
text: profile.type;
|
||||
variant: "outline";
|
||||
}
|
||||
|
||||
if profile.selected: Badge {
|
||||
text: "Active";
|
||||
variant: "default";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "🔄";
|
||||
variant: "ghost";
|
||||
size: "sm";
|
||||
|
||||
clicked => {
|
||||
root.update-profile(profile.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CardContent {
|
||||
VerticalLayout {
|
||||
spacing: SpacingSystem.spacing.s2;
|
||||
|
||||
// Usage
|
||||
HorizontalLayout {
|
||||
spacing: SpacingSystem.spacing.s2;
|
||||
alignment: space-between;
|
||||
|
||||
Text {
|
||||
text: "Usage:";
|
||||
font-size: Typography.sizes.xs;
|
||||
color: Theme.colors.muted-foreground;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: profile.usage;
|
||||
font-size: Typography.sizes.xs;
|
||||
color: Theme.colors.foreground;
|
||||
font-weight: Typography.weights.medium;
|
||||
}
|
||||
}
|
||||
|
||||
// Expire date
|
||||
HorizontalLayout {
|
||||
spacing: SpacingSystem.spacing.s2;
|
||||
alignment: space-between;
|
||||
|
||||
Text {
|
||||
text: "Expires:";
|
||||
font-size: Typography.sizes.xs;
|
||||
color: Theme.colors.muted-foreground;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: profile.expire-at;
|
||||
font-size: Typography.sizes.xs;
|
||||
color: Theme.colors.foreground;
|
||||
font-weight: Typography.weights.medium;
|
||||
}
|
||||
}
|
||||
|
||||
// Updated date
|
||||
HorizontalLayout {
|
||||
spacing: SpacingSystem.spacing.s2;
|
||||
alignment: space-between;
|
||||
|
||||
Text {
|
||||
text: "Updated:";
|
||||
font-size: Typography.sizes.xs;
|
||||
color: Theme.colors.muted-foreground;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: profile.updated-at;
|
||||
font-size: Typography.sizes.xs;
|
||||
color: Theme.colors.muted-foreground;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CardFooter {
|
||||
HorizontalLayout {
|
||||
spacing: SpacingSystem.spacing.s2;
|
||||
|
||||
if !profile.selected: Button {
|
||||
text: "Select";
|
||||
variant: "default";
|
||||
size: "sm";
|
||||
|
||||
clicked => {
|
||||
root.select-profile(profile.id);
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Delete";
|
||||
variant: "destructive";
|
||||
size: "sm";
|
||||
|
||||
clicked => {
|
||||
root.delete-profile(profile.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,90 +1,274 @@
|
||||
import { Theme, SpacingSystem, Typography } from "../theme/theme.slint";
|
||||
import { Tabs, TabItem, TabContent } from "../components/tabs.slint";
|
||||
import { Accordion, AccordionItemData, AccordionContent } from "../components/accordion.slint";
|
||||
// Proxies Page - Proxy nodes management with tabs and groups
|
||||
import { Theme, Typography, SpacingSystem } from "../theme/theme.slint";
|
||||
import { Item } from "../components/item.slint";
|
||||
import { Badge } from "../components/badge.slint";
|
||||
import { Button } from "../components/button.slint";
|
||||
import { Empty } from "../components/empty.slint";
|
||||
import { Container } from "../layouts/container.slint";
|
||||
import { ProxyGroup, ProxyNode } from "../types.slint";
|
||||
|
||||
export struct ProxyNode {
|
||||
name: string,
|
||||
type: string,
|
||||
latency: string,
|
||||
}
|
||||
export component ProxiesPage inherits Rectangle {
|
||||
in property <[ProxyGroup]> proxy-groups;
|
||||
in-out property <string> proxy-mode: "rule"; // "rule" | "global" | "direct"
|
||||
|
||||
export struct ProxyGroup {
|
||||
name: string,
|
||||
nodes: [ProxyNode],
|
||||
expanded: bool,
|
||||
}
|
||||
callback load-proxy-groups(string /* mode */);
|
||||
callback select-proxy(string /* group-name */, string /* node-name */);
|
||||
callback test-proxy-delay(string /* node-name */);
|
||||
|
||||
background: Theme.colors.background;
|
||||
|
||||
export component ProxiesPage {
|
||||
in-out property <[ProxyGroup]> proxy-groups: [];
|
||||
in-out property <int> current-mode: 0; // 0=Rule, 1=Global, 2=Direct
|
||||
in property <string> selected-proxy: "";
|
||||
|
||||
callback mode-changed(int);
|
||||
callback group-toggled(int, bool);
|
||||
callback proxy-selected(string);
|
||||
|
||||
VerticalLayout {
|
||||
padding: SpacingSystem.spacing.s4;
|
||||
spacing: SpacingSystem.spacing.s4;
|
||||
|
||||
// Header with Tabs
|
||||
HorizontalLayout {
|
||||
spacing: SpacingSystem.spacing.s3;
|
||||
alignment: space-between;
|
||||
|
||||
Text {
|
||||
text: "Proxies";
|
||||
color: Theme.colors.foreground;
|
||||
font-size: Typography.sizes.xl;
|
||||
font-weight: Typography.weights.bold;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
horizontal-stretch: 1;
|
||||
// Fixed header with tabs
|
||||
Rectangle {
|
||||
height: 48px;
|
||||
background: Theme.colors.background;
|
||||
|
||||
HorizontalLayout {
|
||||
padding: SpacingSystem.spacing.s4;
|
||||
spacing: SpacingSystem.spacing.s4;
|
||||
alignment: space-between;
|
||||
|
||||
Text {
|
||||
text: "Proxies";
|
||||
font-size: Typography.sizes.xl;
|
||||
font-weight: Typography.weights.bold;
|
||||
color: Theme.colors.foreground;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
|
||||
// Mode tabs
|
||||
HorizontalLayout {
|
||||
spacing: SpacingSystem.spacing.s1;
|
||||
|
||||
Rectangle {
|
||||
width: 80px;
|
||||
height: 32px;
|
||||
background: proxy-mode == "rule" ? Theme.colors.primary : Theme.colors.muted;
|
||||
border-radius: SpacingSystem.radius.md;
|
||||
|
||||
TouchArea {
|
||||
clicked => {
|
||||
proxy-mode = "rule";
|
||||
root.load-proxy-groups("rule");
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Rule";
|
||||
font-size: Typography.sizes.sm;
|
||||
font-weight: Typography.weights.medium;
|
||||
color: proxy-mode == "rule" ? Theme.colors.primary-foreground : Theme.colors.muted-foreground;
|
||||
horizontal-alignment: center;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 80px;
|
||||
height: 32px;
|
||||
background: proxy-mode == "global" ? Theme.colors.primary : Theme.colors.muted;
|
||||
border-radius: SpacingSystem.radius.md;
|
||||
|
||||
TouchArea {
|
||||
clicked => {
|
||||
proxy-mode = "global";
|
||||
root.load-proxy-groups("global");
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Global";
|
||||
font-size: Typography.sizes.sm;
|
||||
font-weight: Typography.weights.medium;
|
||||
color: proxy-mode == "global" ? Theme.colors.primary-foreground : Theme.colors.muted-foreground;
|
||||
horizontal-alignment: center;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 80px;
|
||||
height: 32px;
|
||||
background: proxy-mode == "direct" ? Theme.colors.primary : Theme.colors.muted;
|
||||
border-radius: SpacingSystem.radius.md;
|
||||
|
||||
TouchArea {
|
||||
clicked => {
|
||||
proxy-mode = "direct";
|
||||
root.load-proxy-groups("direct");
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Direct";
|
||||
font-size: Typography.sizes.sm;
|
||||
font-weight: Typography.weights.medium;
|
||||
color: proxy-mode == "direct" ? Theme.colors.primary-foreground : Theme.colors.muted-foreground;
|
||||
horizontal-alignment: center;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tabs
|
||||
Tabs {
|
||||
tabs: [
|
||||
{ title: "Rule" },
|
||||
{ title: "Global" },
|
||||
{ title: "Direct" },
|
||||
];
|
||||
current-index: root.current-mode;
|
||||
|
||||
tab-changed(index) => {
|
||||
root.current-mode = index;
|
||||
root.mode-changed(index);
|
||||
}
|
||||
|
||||
// Tab Content
|
||||
TabContent {
|
||||
index: 0;
|
||||
current-index: root.current-mode;
|
||||
|
||||
ScrollView {
|
||||
|
||||
// Scrollable content
|
||||
Flickable {
|
||||
viewport-height: content-layout.preferred-height + SpacingSystem.spacing.s4 * 2;
|
||||
|
||||
content-layout := VerticalLayout {
|
||||
padding: SpacingSystem.spacing.s4;
|
||||
spacing: SpacingSystem.spacing.s4;
|
||||
|
||||
// Empty state
|
||||
if proxy-groups.length == 0: Empty {
|
||||
height: 300px;
|
||||
icon: "🌍";
|
||||
title: "No Groups";
|
||||
description: "Proxy groups will appear here";
|
||||
}
|
||||
|
||||
// Proxy groups
|
||||
for group in proxy-groups: Container {
|
||||
VerticalLayout {
|
||||
padding: SpacingSystem.spacing.s4;
|
||||
spacing: SpacingSystem.spacing.s3;
|
||||
|
||||
Accordion {
|
||||
items: root.proxy-groups.map(|g| {
|
||||
return { title: g.name, expanded: g.expanded };
|
||||
});
|
||||
|
||||
item-toggled(index, expanded) => {
|
||||
root.group-toggled(index, expanded);
|
||||
|
||||
// Group header
|
||||
HorizontalLayout {
|
||||
spacing: SpacingSystem.spacing.s3;
|
||||
alignment: space-between;
|
||||
|
||||
VerticalLayout {
|
||||
spacing: SpacingSystem.spacing.s1;
|
||||
|
||||
Text {
|
||||
text: group.name;
|
||||
font-size: Typography.sizes.base;
|
||||
font-weight: Typography.weights.semibold;
|
||||
color: Theme.colors.foreground;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Type: " + group.type + " | Current: " + group.now;
|
||||
font-size: Typography.sizes.xs;
|
||||
color: Theme.colors.muted-foreground;
|
||||
}
|
||||
}
|
||||
|
||||
for group[group-index] in root.proxy-groups: AccordionContent {
|
||||
ProxyGroupContent {
|
||||
nodes: group.nodes;
|
||||
selected-proxy: root.selected-proxy;
|
||||
|
||||
node-selected(name) => {
|
||||
root.proxy-selected(name);
|
||||
|
||||
Text {
|
||||
text: group.nodes.length + " nodes";
|
||||
font-size: Typography.sizes.xs;
|
||||
color: Theme.colors.muted-foreground;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
|
||||
// Divider
|
||||
Rectangle {
|
||||
height: 1px;
|
||||
background: Theme.colors.border;
|
||||
}
|
||||
|
||||
// Proxy nodes
|
||||
VerticalLayout {
|
||||
spacing: SpacingSystem.spacing.s2;
|
||||
|
||||
for node in group.nodes: Rectangle {
|
||||
height: 60px;
|
||||
background: node.name == group.now ? Theme.colors.accent : transparent;
|
||||
border-radius: SpacingSystem.radius.md;
|
||||
border-width: 1px;
|
||||
border-color: Theme.colors.border;
|
||||
|
||||
HorizontalLayout {
|
||||
padding: SpacingSystem.spacing.s3;
|
||||
spacing: SpacingSystem.spacing.s3;
|
||||
alignment: space-between;
|
||||
|
||||
// Node info
|
||||
HorizontalLayout {
|
||||
spacing: SpacingSystem.spacing.s3;
|
||||
|
||||
// Status indicator
|
||||
Rectangle {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 4px;
|
||||
background: node.delay >= 0 && node.delay < 200 ? #22c55e :
|
||||
node.delay >= 200 && node.delay < 500 ? #f59e0b :
|
||||
node.delay >= 500 ? #ef4444 : Theme.colors.muted;
|
||||
}
|
||||
|
||||
// Node details
|
||||
VerticalLayout {
|
||||
spacing: SpacingSystem.spacing.s1;
|
||||
|
||||
Text {
|
||||
text: node.name;
|
||||
font-size: Typography.sizes.sm;
|
||||
font-weight: Typography.weights.medium;
|
||||
color: Theme.colors.foreground;
|
||||
}
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: SpacingSystem.spacing.s2;
|
||||
|
||||
Badge {
|
||||
text: node.type;
|
||||
variant: "outline";
|
||||
}
|
||||
|
||||
if node.udp: Badge {
|
||||
text: "UDP";
|
||||
variant: "outline";
|
||||
}
|
||||
|
||||
if node.delay >= 0: Text {
|
||||
text: node.delay + "ms";
|
||||
font-size: Typography.sizes.xs;
|
||||
color: Theme.colors.muted-foreground;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Actions
|
||||
HorizontalLayout {
|
||||
spacing: SpacingSystem.spacing.s2;
|
||||
|
||||
Button {
|
||||
text: "🔍";
|
||||
variant: "ghost";
|
||||
size: "sm";
|
||||
|
||||
clicked => {
|
||||
root.test-proxy-delay(node.name);
|
||||
}
|
||||
}
|
||||
|
||||
if node.name != group.now: Button {
|
||||
text: "Select";
|
||||
variant: "outline";
|
||||
size: "sm";
|
||||
|
||||
clicked => {
|
||||
root.select-proxy(group.name, node.name);
|
||||
}
|
||||
}
|
||||
|
||||
if node.name == group.now: Badge {
|
||||
text: "Active";
|
||||
variant: "default";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TouchArea {
|
||||
clicked => {
|
||||
if node.name != group.now {
|
||||
root.select-proxy(group.name, node.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -92,135 +276,6 @@ export component ProxiesPage {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TabContent {
|
||||
index: 1;
|
||||
current-index: root.current-mode;
|
||||
|
||||
Text {
|
||||
text: "Global mode - All traffic goes through selected proxy";
|
||||
color: Theme.colors.muted-foreground;
|
||||
horizontal-alignment: center;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
|
||||
TabContent {
|
||||
index: 2;
|
||||
current-index: root.current-mode;
|
||||
|
||||
Text {
|
||||
text: "Direct mode - All traffic goes direct";
|
||||
color: Theme.colors.muted-foreground;
|
||||
horizontal-alignment: center;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component ProxyGroupContent {
|
||||
in property <[ProxyNode]> nodes: [];
|
||||
in property <string> selected-proxy: "";
|
||||
|
||||
callback node-selected(string);
|
||||
|
||||
GridLayout {
|
||||
spacing: SpacingSystem.spacing.s3;
|
||||
|
||||
for node[index] in root.nodes: ProxyNodeItem {
|
||||
node-data: node;
|
||||
selected: node.name == root.selected-proxy;
|
||||
|
||||
clicked => {
|
||||
root.node-selected(node.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component ProxyNodeItem {
|
||||
in property <ProxyNode> node-data;
|
||||
in property <bool> selected: false;
|
||||
|
||||
callback clicked();
|
||||
|
||||
private property <bool> hovered: false;
|
||||
|
||||
min-width: 200px;
|
||||
height: 60px;
|
||||
|
||||
states [
|
||||
selected when root.selected: {
|
||||
container.border-color: Theme.coloimary;
|
||||
container.background: Theme.colors.primary.transparentize(0.9);
|
||||
}
|
||||
hovered when root.hovered && !root.selected: {
|
||||
container.background: Theme.colors.muted;
|
||||
}
|
||||
]
|
||||
|
||||
container := Rectangle {
|
||||
background: transparent;
|
||||
border-radius: SpacingSystem.radius.md;
|
||||
border-width: 1px;
|
||||
border-color: Theme.colors.border;
|
||||
|
||||
animate background { duration: 200ms; }
|
||||
animate border-color { duration: 200ms; }
|
||||
|
||||
HorizontalLayout {
|
||||
padding: SpacingSystem.spacing.s3;
|
||||
spacing: SpacingSystem.spacing.s3;
|
||||
alignment: space-between;
|
||||
|
||||
VerticalLayout {
|
||||
spacing: SpacingSystem.spacing.s1;
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: SpacingSystem.spacing.s2;
|
||||
|
||||
Text {
|
||||
text: "✓";
|
||||
color: root.selected ? Theme.colors.primary : Theme.colors.muted-foreground;
|
||||
font-size: Typography.sizes.sm;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: root.node-data.name;
|
||||
color: Theme.colors.foreground;
|
||||
font-size: Typography.sizes.sm;
|
||||
font-weight: Typography.weights.medium;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
text: root.node-data.type;
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: Typography.sizes.xs;
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
text: root.node-data.latency;
|
||||
color: Theme.colors.primary;
|
||||
font-size: Typography.sizes.sm;
|
||||
font-weight: Typography.weights.semibold;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
|
||||
touch := TouchArea {
|
||||
clicked => {
|
||||
root.clicked();
|
||||
}
|
||||
|
||||
moved => {
|
||||
root.hovered = self.has-hover;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,336 +1,272 @@
|
||||
import { Theme, SpacingSystem, Typography } from "../theme/theme.slint";
|
||||
import { Card } from "../components/card.slint";
|
||||
// Settings Page - App and Clash settings
|
||||
import { Theme, Typography, SpacingSystem } from "../theme/theme.slint";
|
||||
import { Container } from "../layouts/container.slint";
|
||||
import { Switch } from "../components/switch.slint";
|
||||
import { Button } from "../components/button.slint";
|
||||
import { Select, SelectOption } from "../components/select.slint";
|
||||
import { Separator, Orientation } from "../components/separator.slint";
|
||||
import { SettingItem } from "../types.slint";
|
||||
|
||||
export component SettingsPage inherits Rectangle {
|
||||
in property <[SettingItem]> app-settings;
|
||||
in property <[SettingItem]> clash-settings;
|
||||
|
||||
callback toggle-setting(string /* key */, bool /* value */);
|
||||
callback load-settings();
|
||||
|
||||
background: Theme.colors.background;
|
||||
|
||||
export component SettingsPage {
|
||||
// App Settings
|
||||
in property <bool> auto-start: false;
|
||||
in property <bool> silent-start: false;
|
||||
in property <bool> clash-core: false;
|
||||
|
||||
// Clash Settings
|
||||
in property <bool> tun-mode: false;
|
||||
in property <bool> allow-lan: false;
|
||||
in property <bool> ipv6: false;
|
||||
in property <bool> unified-delay: false;
|
||||
in property <int> log-level-index: 1;
|
||||
in property <int> proxy-port: 7890;
|
||||
|
||||
// Misc
|
||||
in property <string> app-dir: "C:/Program Files/Clash";
|
||||
in property <string> config-dir: "C:/Users/User/.config/clash";
|
||||
in property <string> core-dir: "C:/Program Files/Clash/core";
|
||||
in property <string> app-version: "1.0.0";
|
||||
|
||||
callback toggle-auto-start(bool);
|
||||
callback toggle-silent-start(bool);
|
||||
callback toggle-clash-core(bool);
|
||||
callback toggle-tun-mode(bool);
|
||||
callback toggle-allow-lan(bool);
|
||||
callback toggle-ipv6(bool);
|
||||
callback toggle-unified-delay(bool);
|
||||
callback log-level-changed(int);
|
||||
callback open-port-settings();
|
||||
callback open-tun-config();
|
||||
callback open-directory(string);
|
||||
|
||||
VerticalLayout {
|
||||
padding: SpacingSystem.spacing.s4;
|
||||
spacing: SpacingSystem.spacing.s4;
|
||||
|
||||
// Header
|
||||
Text {
|
||||
text: "Settings";
|
||||
color: Theme.colors.foreground;
|
||||
font-size: Typography.sizes.xl;
|
||||
font-weight: Typography.weights.bold;
|
||||
}
|
||||
|
||||
ScrollView {
|
||||
VerticalLayout {
|
||||
spacing: SpacingSystem.spacing.s3;
|
||||
|
||||
// App Settings Section
|
||||
Card {
|
||||
VerticalLayout {
|
||||
spacing: SpacingSystem.spacing.s3;
|
||||
|
||||
Text {
|
||||
text: "App Settings";
|
||||
color: Theme.colors.foreground;
|
||||
font-size: Typography.sizes.base;
|
||||
font-weight: Typography.weights.semibold;
|
||||
}
|
||||
|
||||
SettingRow {
|
||||
icon: "🚀";
|
||||
label: "Auto Start";
|
||||
|
||||
Switch {
|
||||
checked: root.auto-start;
|
||||
toggled(checked) => {
|
||||
root.toggle-auto-start(checked);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Separator { orientation: Orientation.horizontal; }
|
||||
|
||||
SettingRow {
|
||||
icon: "🔇";
|
||||
label: "Silent Start";
|
||||
|
||||
Switch {
|
||||
checked: root.silent-start;
|
||||
toggled(checked) => {
|
||||
root.toggle-silent-start(checked);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Separator { orientation: Orientation.horizontal; }
|
||||
|
||||
SettingRow {
|
||||
icon: "⚙";
|
||||
label: "Clash Core";
|
||||
|
||||
Switch {
|
||||
checked: root.clash-core;
|
||||
toggled(checked) => {
|
||||
root.toggle-clash-core(checked);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clash Settings Section
|
||||
Card {
|
||||
VerticalLayout {
|
||||
spacing: SpacingSystem.spacing.s3;
|
||||
|
||||
Text {
|
||||
text: "Clash Settings";
|
||||
color: Theme.colors.foreground;
|
||||
font-size: Typography.sizes.base;
|
||||
font-weight: Typography.weights.semibold;
|
||||
}
|
||||
|
||||
SettingRow {
|
||||
icon: "🌐";
|
||||
label: "TUN Mode";
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: SpacingSystem.spacing.s2;
|
||||
|
||||
Switch
|
||||
checked: root.tun-mode;
|
||||
toggled(checked) => {
|
||||
root.toggle-tun-mode(checked);
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
text: "⚙";
|
||||
variant: "ghost";
|
||||
|
||||
clicked => {
|
||||
root.open-tun-config();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Separator { orientation: Orientation.horizontal; }
|
||||
|
||||
SettingRow {
|
||||
icon: "📡";
|
||||
label: "Allow LAN";
|
||||
|
||||
Switch {
|
||||
checked: root.allow-lan;
|
||||
toggled(checked) => {
|
||||
root.toggle-allow-lan(checked);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Separator { orientation: Orientation.horizontal; }
|
||||
|
||||
SettingRow {
|
||||
icon: "🌍";
|
||||
label: "IPv6";
|
||||
|
||||
Switch {
|
||||
checked: root.ipv6;
|
||||
toggled(checked) => {
|
||||
root.toggle-ipv6(checked);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Separator { orientation: Orientation.horizontal; }
|
||||
|
||||
SettingRow {
|
||||
icon: "⏱";
|
||||
label: "Unified Delay";
|
||||
|
||||
Switch {
|
||||
checked: root.unified-delay;
|
||||
toggled(checked) => {
|
||||
root.toggle-unified-delay(checked);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Separator { orientation: Orientation.horizontal; }
|
||||
|
||||
SettingRow {
|
||||
icon: "📝";
|
||||
label: "Log Level";
|
||||
|
||||
Select {
|
||||
options: [
|
||||
{ label: "Debug", valueg" },
|
||||
{ label: "Info", value: "info" },
|
||||
{ label: "Warn", value: "warn" },
|
||||
{ label: "Error", value: "error" },
|
||||
];
|
||||
selected-index: root.log-level-index;
|
||||
|
||||
selected(index, value) => {
|
||||
root.log-level-changed(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Separator { orientation: Orientation.horizontal; }
|
||||
|
||||
SettingRow {
|
||||
icon: "🔌";
|
||||
label: "Proxy Port";
|
||||
|
||||
Button {
|
||||
text: root.proxy-port;
|
||||
variant: "link";
|
||||
|
||||
clicked => {
|
||||
root.open-port-settings();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Miscellaneous Section
|
||||
Card {
|
||||
VerticalLayout {
|
||||
spacing: SpacingSystem.spacing.s3;
|
||||
|
||||
Text {
|
||||
text: "Miscellaneous";
|
||||
color: Theme.colors.foreground;
|
||||
font-size: Typography.sizes.base;
|
||||
font-weight: Typography.weights.semibold;
|
||||
}
|
||||
|
||||
SettingRow {
|
||||
icon: "📁";
|
||||
l: "App Directory";
|
||||
|
||||
Button {
|
||||
text: "Open";
|
||||
variant: "link";
|
||||
|
||||
clicked => {
|
||||
root.open-directory(root.app-dir);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Separator { orientation: Orientation.horizontal; }
|
||||
|
||||
SettingRow {
|
||||
icon: "📁";
|
||||
label: "Config Directory";
|
||||
|
||||
Button {
|
||||
text: "Open";
|
||||
variant: "link";
|
||||
|
||||
clicked => {
|
||||
root.open-directory(root.config-dir);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Separator { orientation: Orientation.horizontal; }
|
||||
|
||||
SettingRow {
|
||||
icon: "📁";
|
||||
label: "Core Directory";
|
||||
|
||||
Button {
|
||||
text: "Open";
|
||||
variant: "link";
|
||||
|
||||
clicked => {
|
||||
root.open-directory(root.core-dir);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Separator { orientation: Orientation.horizontal; }
|
||||
|
||||
SettingRow {
|
||||
icon: "ℹ";
|
||||
label: "App Version";
|
||||
|
||||
Text {
|
||||
text: root.app-version;
|
||||
color: Theme.colors.muted-foreground;
|
||||
font-size: Typography.sizes.sm;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Fixed header
|
||||
Rectangle {
|
||||
height: 48px;
|
||||
background: Theme.colors.background;
|
||||
|
||||
component SettingRow {
|
||||
in property <string> icon: "";
|
||||
in property <string> label: "";
|
||||
|
||||
height: 40px;
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: SpacingSystem.spacing.s3;
|
||||
alignment: space-between;
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: SpacingSystem.spacing.s2;
|
||||
|
||||
Text {
|
||||
text: root.icon;
|
||||
font-size: Typography.sizes.base;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: root.label;
|
||||
color: Theme.colors.foreground;
|
||||
font-size: Typography.sizes.sm;
|
||||
vertical-alignment: center;
|
||||
HorizontalLayout {
|
||||
padding: SpacingSystem.spacing.s4;
|
||||
|
||||
Text {
|
||||
text: "Settings";
|
||||
font-size: Typography.sizes.xl;
|
||||
font-weight: Typography.weights.bold;
|
||||
color: Theme.colors.foreground;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Scrollable content
|
||||
Flickable {
|
||||
viewport-height: content-layout.preferred-height + SpacingSystem.spacing.s4 * 2;
|
||||
|
||||
content-layout := VerticalLayout {
|
||||
padding: SpacingSystem.spacing.s4;
|
||||
spacing: SpacingSystem.spacing.s4;
|
||||
|
||||
// App Settings Section
|
||||
Container {
|
||||
VerticalLayout {
|
||||
padding: SpacingSystem.spacing.s4;
|
||||
spacing: SpacingSystem.spacing.s3;
|
||||
|
||||
// Section title
|
||||
Text {
|
||||
text: "App 设置";
|
||||
font-size: Typography.sizes.base;
|
||||
font-weight: Typography.weights.semibold;
|
||||
color: Theme.colors.foreground;
|
||||
}
|
||||
|
||||
// App settings list
|
||||
for setting in app-settings: Rectangle {
|
||||
height: 48px;
|
||||
|
||||
HorizontalLayout {
|
||||
padding-top: SpacingSystem.spacing.s2;
|
||||
padding-bottom: SpacingSystem.spacing.s2;
|
||||
spacing: SpacingSystem.spacing.s3;
|
||||
alignment: space-between;
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: SpacingSystem.spacing.s3;
|
||||
|
||||
// Icon
|
||||
Text {
|
||||
text: "✓";
|
||||
font-size: Typography.sizes.base;
|
||||
color: Theme.colors.primary;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
|
||||
// Label
|
||||
Text {
|
||||
text: setting.label;
|
||||
font-size: Typography.sizes.sm;
|
||||
color: Theme.colors.foreground;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
|
||||
// Control based on type
|
||||
if setting.type == "switch": Switch {
|
||||
checked: setting.enabled;
|
||||
|
||||
toggled => {
|
||||
root.toggle-setting(setting.key, self.checked);
|
||||
}
|
||||
}
|
||||
|
||||
if setting.type == "button": Button {
|
||||
text: setting.value;
|
||||
variant: "link";
|
||||
size: "sm";
|
||||
}
|
||||
|
||||
if setting.type == "text": Text {
|
||||
text: setting.value;
|
||||
font-size: Typography.sizes.sm;
|
||||
color: Theme.colors.muted-foreground;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clash Settings Section
|
||||
Container {
|
||||
VerticalLayout {
|
||||
padding: SpacingSystem.spacing.s4;
|
||||
spacing: SpacingSystem.spacing.s3;
|
||||
|
||||
// Section title
|
||||
Text {
|
||||
text: "Clash 设置";
|
||||
font-size: Typography.sizes.base;
|
||||
font-weight: Typography.weights.semibold;
|
||||
color: Theme.colors.foreground;
|
||||
}
|
||||
|
||||
// Clash settings list
|
||||
for setting in clash-settings: Rectangle {
|
||||
height: 48px;
|
||||
|
||||
HorizontalLayout {
|
||||
padding-top: SpacingSystem.spacing.s2;
|
||||
padding-bottom: SpacingSystem.spacing.s2;
|
||||
spacing: SpacingSystem.spacing.s3;
|
||||
alignment: space-between;
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: SpacingSystem.spacing.s3;
|
||||
|
||||
// Icon
|
||||
Text {
|
||||
text: "⚙";
|
||||
font-size: Typography.sizes.base;
|
||||
color: Theme.colors.primary;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
|
||||
// Label
|
||||
Text {
|
||||
text: setting.label;
|
||||
font-size: Typography.sizes.sm;
|
||||
color: Theme.colors.foreground;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
|
||||
// Control based on type
|
||||
if setting.type == "switch": Switch {
|
||||
checked: setting.enabled;
|
||||
|
||||
toggled => {
|
||||
root.toggle-setting(setting.key, self.checked);
|
||||
}
|
||||
}
|
||||
|
||||
if setting.type == "button": Button {
|
||||
text: setting.value;
|
||||
variant: "link";
|
||||
size: "sm";
|
||||
}
|
||||
|
||||
if setting.type == "text": Text {
|
||||
text: setting.value;
|
||||
font-size: Typography.sizes.sm;
|
||||
color: Theme.colors.muted-foreground;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Miscellaneous Section
|
||||
Container {
|
||||
VerticalLayout {
|
||||
padding: SpacingSystem.spacing.s4;
|
||||
spacing: SpacingSystem.spacing.s3;
|
||||
|
||||
// Section title
|
||||
Text {
|
||||
text: "杂项";
|
||||
font-size: Typography.sizes.base;
|
||||
font-weight: Typography.weights.semibold;
|
||||
color: Theme.colors.foreground;
|
||||
}
|
||||
|
||||
// Misc items
|
||||
Rectangle {
|
||||
height: 48px;
|
||||
|
||||
HorizontalLayout {
|
||||
padding-top: SpacingSystem.spacing.s2;
|
||||
padding-bottom: SpacingSystem.spacing.s2;
|
||||
spacing: SpacingSystem.spacing.s3;
|
||||
alignment: space-between;
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: SpacingSystem.spacing.s3;
|
||||
|
||||
Text {
|
||||
text: "📁";
|
||||
font-size: Typography.sizes.base;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "App 目录";
|
||||
font-size: Typography.sizes.sm;
|
||||
color: Theme.colors.foreground;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "打开";
|
||||
variant: "link";
|
||||
size: "sm";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
height: 48px;
|
||||
|
||||
HorizontalLayout {
|
||||
padding-top: SpacingSystem.spacing.s2;
|
||||
padding-bottom: SpacingSystem.spacing.s2;
|
||||
spacing: SpacingSystem.spacing.s3;
|
||||
alignment: space-between;
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: SpacingSystem.spacing.s3;
|
||||
|
||||
Text {
|
||||
text: "📋";
|
||||
font-size: Typography.sizes.base;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "App 版本";
|
||||
font-size: Typography.sizes.sm;
|
||||
color: Theme.colors.foreground;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "v1.0.0";
|
||||
font-size: Typography.sizes.sm;
|
||||
color: Theme.colors.muted-foreground;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@children
|
||||
}
|
||||
}
|
||||
|
||||
57
ui/types.slint
Normal file
57
ui/types.slint
Normal file
@@ -0,0 +1,57 @@
|
||||
// Data structure definitions for the proxy management application
|
||||
|
||||
// 配置文件信息
|
||||
export struct Profile {
|
||||
id: string,
|
||||
name: string,
|
||||
url: string,
|
||||
type: string, // "remote" | "local"
|
||||
updated-at: string,
|
||||
usage: string, // "1.26GB / 100GB"
|
||||
expire-at: string,
|
||||
selected: bool,
|
||||
}
|
||||
|
||||
// 代理节点
|
||||
export struct ProxyNode {
|
||||
name: string,
|
||||
type: string, // "Vmess", "Shadowsocks", etc.
|
||||
delay: int, // 延迟 ms,-1 表示未测试
|
||||
udp: bool,
|
||||
}
|
||||
|
||||
// 代理组
|
||||
export struct ProxyGroup {
|
||||
name: string,
|
||||
type: string, // "Selector", "URLTest", etc.
|
||||
now: string, // 当前选中的节点
|
||||
nodes: [ProxyNode],
|
||||
}
|
||||
|
||||
// 连接信息
|
||||
export struct Connection {
|
||||
id: string,
|
||||
host: string,
|
||||
process: string,
|
||||
rule: string,
|
||||
chains: string,
|
||||
upload: string,
|
||||
download: string,
|
||||
time: string,
|
||||
}
|
||||
|
||||
// 日志条目
|
||||
export struct LogEntry {
|
||||
time: string,
|
||||
level: string, // "debug", "info", "warn", "error"
|
||||
message: string,
|
||||
}
|
||||
|
||||
// 设置项
|
||||
export struct SettingItem {
|
||||
key: string,
|
||||
label: string,
|
||||
value: string,
|
||||
type: string, // "switch", "select", "button", "text"
|
||||
enabled: bool,
|
||||
}
|
||||
Reference in New Issue
Block a user