227 lines
7.0 KiB
Plaintext
227 lines
7.0 KiB
Plaintext
import { Theme, SpacingSystem, Typography } from "../theme/theme.slint";
|
|
import { Tabs, TabItem, TabContent } from "../components/tabs.slint";
|
|
import { Accordion, AccordionItemData, AccordionContent } from "../components/accordion.slint";
|
|
import { Item } from "../components/item.slint";
|
|
|
|
export struct ProxyNode {
|
|
name: string,
|
|
type: string,
|
|
latency: string,
|
|
}
|
|
|
|
export struct ProxyGroup {
|
|
name: string,
|
|
nodes: [ProxyNode],
|
|
expanded: bool,
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
// 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 {
|
|
VerticalLayout {
|
|
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);
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
}
|