20260619
This commit is contained in:
589
ui/welcome.slint
Normal file
589
ui/welcome.slint
Normal file
@@ -0,0 +1,589 @@
|
||||
import { Theme } from "theme.slint";
|
||||
import { PrimaryButton } from "widgets.slint";
|
||||
|
||||
export struct SessionInfo {
|
||||
id: string,
|
||||
name: string,
|
||||
group: string,
|
||||
host: string,
|
||||
port: int,
|
||||
user: string,
|
||||
auth: string, // "password" | "key"
|
||||
kind: string, // "ssh" | "serial" | "telnet"
|
||||
last-used: string, // ISO timestamp or "never"
|
||||
}
|
||||
|
||||
export struct ConnectionFolderInfo {
|
||||
name: string,
|
||||
expanded: bool,
|
||||
}
|
||||
|
||||
// --- SessionRow ------------------------------------------------------------
|
||||
component SessionRow inherits Rectangle {
|
||||
in property <SessionInfo> session;
|
||||
in property <[string]> folder-names;
|
||||
in property <bool> debug-ids: false;
|
||||
callback connect();
|
||||
callback edit();
|
||||
callback remove();
|
||||
callback move-folder(string);
|
||||
|
||||
height: 34px;
|
||||
border-radius: Theme.radius-sm;
|
||||
background: touch.has-hover ? Theme.bg-hover : transparent;
|
||||
animate background { duration: 120ms; }
|
||||
|
||||
// Remember where the right-click landed so the popup can appear there.
|
||||
property <length> ctx-x;
|
||||
property <length> ctx-y;
|
||||
|
||||
touch := TouchArea {
|
||||
mouse-cursor: pointer;
|
||||
clicked => { root.connect(); }
|
||||
pointer-event(e) => {
|
||||
if (e.kind == PointerEventKind.down && e.button == PointerEventButton.right) {
|
||||
ctx-x = self.mouse-x;
|
||||
ctx-y = self.mouse-y;
|
||||
ctx-menu.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Right-click context menu -----------------------------------------------
|
||||
ctx-menu := PopupWindow {
|
||||
x: ctx-x;
|
||||
y: ctx-y;
|
||||
width: 142px;
|
||||
height: 112px + root.folder-names.length * 28px;
|
||||
|
||||
Rectangle {
|
||||
background: Theme.bg-panel;
|
||||
border-radius: Theme.radius-sm;
|
||||
border-width: 1px;
|
||||
border-color: Theme.border-strong;
|
||||
drop-shadow-blur: 10px;
|
||||
drop-shadow-color: #00000050;
|
||||
|
||||
VerticalLayout {
|
||||
padding: 4px;
|
||||
spacing: 2px;
|
||||
|
||||
// Edit
|
||||
Rectangle {
|
||||
height: 28px;
|
||||
border-radius: Theme.radius-sm;
|
||||
background: edit-ta.has-hover ? Theme.bg-hover : transparent;
|
||||
edit-ta := TouchArea {
|
||||
mouse-cursor: pointer;
|
||||
clicked => { root.edit(); }
|
||||
}
|
||||
HorizontalLayout {
|
||||
height: parent.height;
|
||||
padding-left: 10px;
|
||||
spacing: 8px;
|
||||
alignment: center;
|
||||
Text { width: 16px; height: parent.height; text: "\u{E3C9}"; font-family: "Material Icons"; color: Theme.text-secondary; font-size: Theme.fs-sm; horizontal-alignment: center; vertical-alignment: center; }
|
||||
Text { text: @tr("Edit"); color: Theme.text-primary; font-size: Theme.fs-md; vertical-alignment: center; horizontal-stretch: 1; }
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle { height: 1px; background: Theme.border-subtle; }
|
||||
|
||||
// Move to category
|
||||
Rectangle {
|
||||
height: 28px;
|
||||
border-radius: Theme.radius-sm;
|
||||
background: transparent;
|
||||
HorizontalLayout {
|
||||
height: parent.height;
|
||||
padding-left: 10px;
|
||||
spacing: 8px;
|
||||
alignment: center;
|
||||
Text { width: 16px; height: parent.height; text: "\u{E2C8}"; font-family: "Material Icons"; color: Theme.text-secondary; font-size: Theme.fs-sm; horizontal-alignment: center; vertical-alignment: center; }
|
||||
Text { text: @tr("Move to category"); color: Theme.text-primary; font-size: Theme.fs-md; vertical-alignment: center; horizontal-stretch: 1; }
|
||||
}
|
||||
}
|
||||
|
||||
for folder in root.folder-names : Rectangle {
|
||||
height: 26px;
|
||||
border-radius: Theme.radius-sm;
|
||||
background: folder == root.session.group ? Theme.bg-active : (folder-ta.has-hover ? Theme.bg-hover : transparent);
|
||||
folder-ta := TouchArea {
|
||||
mouse-cursor: pointer;
|
||||
clicked => { root.move-folder(folder); }
|
||||
}
|
||||
Text {
|
||||
x: 24px;
|
||||
width: parent.width - 34px;
|
||||
height: parent.height;
|
||||
text: folder;
|
||||
color: Theme.text-primary;
|
||||
font-size: Theme.fs-sm;
|
||||
vertical-alignment: center;
|
||||
overflow: elide;
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle { height: 1px; background: Theme.border-subtle; }
|
||||
|
||||
// Delete (red tint)
|
||||
Rectangle {
|
||||
height: 28px;
|
||||
border-radius: Theme.radius-sm;
|
||||
background: del-ta.has-hover ? #cc333320 : transparent;
|
||||
del-ta := TouchArea {
|
||||
mouse-cursor: pointer;
|
||||
clicked => { root.remove(); }
|
||||
}
|
||||
HorizontalLayout {
|
||||
height: parent.height;
|
||||
padding-left: 10px;
|
||||
spacing: 8px;
|
||||
alignment: center;
|
||||
Text { width: 16px; height: parent.height; text: "\u{E872}"; font-family: "Material Icons"; color: #cc3333; font-size: Theme.fs-sm; horizontal-alignment: center; vertical-alignment: center; }
|
||||
Text { text: @tr("Delete"); color: #cc3333; font-size: Theme.fs-md; vertical-alignment: center; horizontal-stretch: 1; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Row content ------------------------------------------------------------
|
||||
HorizontalLayout {
|
||||
width: parent.width;
|
||||
height: parent.height;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
spacing: 8px;
|
||||
|
||||
Rectangle {
|
||||
width: 24px;
|
||||
height: parent.height;
|
||||
Text {
|
||||
width: parent.width;
|
||||
height: parent.height;
|
||||
text: "\u{2514}";
|
||||
color: Theme.text-muted;
|
||||
font-size: Theme.fs-sm;
|
||||
horizontal-alignment: right;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 18px;
|
||||
height: parent.height;
|
||||
|
||||
Rectangle {
|
||||
y: (parent.height - self.height) / 2;
|
||||
width: parent.width;
|
||||
height: 18px;
|
||||
border-radius: 3px;
|
||||
background: Theme.bg-elevated;
|
||||
Text {
|
||||
width: parent.width;
|
||||
height: parent.height;
|
||||
text: session.kind == "serial" ? "\u{E1B1}"
|
||||
: session.kind == "telnet" ? "\u{E8AC}"
|
||||
: "\u{E875}";
|
||||
font-family: "Material Icons";
|
||||
color: Theme.text-secondary;
|
||||
font-size: Theme.fs-sm;
|
||||
horizontal-alignment: center;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
height: parent.height;
|
||||
text: session.name;
|
||||
color: Theme.text-primary;
|
||||
font-size: Theme.fs-md;
|
||||
vertical-alignment: center;
|
||||
width: 170px;
|
||||
overflow: elide;
|
||||
}
|
||||
Rectangle {
|
||||
width: 52px;
|
||||
height: parent.height;
|
||||
|
||||
Rectangle {
|
||||
y: (parent.height - self.height) / 2;
|
||||
width: parent.width;
|
||||
height: 20px;
|
||||
border-radius: Theme.radius-sm;
|
||||
background: Theme.bg-elevated;
|
||||
Text {
|
||||
width: parent.width;
|
||||
height: parent.height;
|
||||
text: session.kind == "serial" ? "SERIAL"
|
||||
: session.kind == "telnet" ? "TELNET"
|
||||
: "SSH";
|
||||
color: Theme.text-secondary;
|
||||
font-size: Theme.fs-xs;
|
||||
horizontal-alignment: center;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
Text {
|
||||
height: parent.height;
|
||||
text: session.kind == "serial"
|
||||
? session.host
|
||||
: session.host + ":" + session.port;
|
||||
color: Theme.text-secondary;
|
||||
font-size: Theme.fs-sm;
|
||||
vertical-alignment: center;
|
||||
width: 220px;
|
||||
overflow: elide;
|
||||
}
|
||||
Text {
|
||||
height: parent.height;
|
||||
text: session.user;
|
||||
color: Theme.text-secondary;
|
||||
font-size: Theme.fs-sm;
|
||||
vertical-alignment: center;
|
||||
horizontal-stretch: 1;
|
||||
overflow: elide;
|
||||
}
|
||||
if root.debug-ids : Text {
|
||||
height: parent.height;
|
||||
text: "[session-row]";
|
||||
color: Theme.warning;
|
||||
font-size: Theme.fs-xs;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component FolderSessionSlot inherits Rectangle {
|
||||
in property <string> folder-name;
|
||||
in property <bool> folder-expanded: true;
|
||||
in property <SessionInfo> session;
|
||||
in property <[string]> folder-names;
|
||||
in property <bool> debug-ids: false;
|
||||
callback connect-session(string);
|
||||
callback edit-session(string);
|
||||
callback remove-session(string);
|
||||
callback move-session-folder(string, string);
|
||||
|
||||
height: root.folder-expanded && session.group == root.folder-name ? 34px : 0px;
|
||||
visible: root.folder-expanded && session.group == root.folder-name;
|
||||
|
||||
if root.folder-expanded && session.group == root.folder-name : SessionRow {
|
||||
width: parent.width;
|
||||
height: parent.height;
|
||||
session: root.session;
|
||||
folder-names: root.folder-names;
|
||||
debug-ids: root.debug-ids;
|
||||
connect => { root.connect-session(root.session.id); }
|
||||
edit => { root.edit-session(root.session.id); }
|
||||
remove => { root.remove-session(root.session.id); }
|
||||
move-folder(folder) => { root.move-session-folder(root.session.id, folder); }
|
||||
}
|
||||
}
|
||||
|
||||
component FolderSection inherits VerticalLayout {
|
||||
in property <string> folder-name;
|
||||
in property <bool> expanded: true;
|
||||
in property <[SessionInfo]> sessions;
|
||||
in property <[string]> folder-names;
|
||||
in property <bool> debug-ids: false;
|
||||
callback connect-session(string);
|
||||
callback edit-session(string);
|
||||
callback remove-session(string);
|
||||
callback rename-folder(string);
|
||||
callback delete-folder(string);
|
||||
callback new-folder();
|
||||
callback toggle-folder(string);
|
||||
callback move-session-folder(string, string);
|
||||
|
||||
spacing: 2px;
|
||||
|
||||
Rectangle {
|
||||
width: parent.width;
|
||||
height: 28px;
|
||||
border-radius: Theme.radius-sm;
|
||||
background: header-ta.has-hover ? Theme.bg-hover : transparent;
|
||||
property <length> ctx-x;
|
||||
property <length> ctx-y;
|
||||
|
||||
header-ta := TouchArea {
|
||||
mouse-cursor: pointer;
|
||||
pointer-event(e) => {
|
||||
if (e.kind == PointerEventKind.down && e.button == PointerEventButton.right) {
|
||||
ctx-x = self.mouse-x;
|
||||
ctx-y = self.mouse-y;
|
||||
folder-menu.show();
|
||||
} else if (e.kind == PointerEventKind.down && e.button == PointerEventButton.left) {
|
||||
root.toggle-folder(root.folder-name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
folder-menu := PopupWindow {
|
||||
x: ctx-x;
|
||||
y: ctx-y;
|
||||
width: 132px;
|
||||
height: 108px;
|
||||
Rectangle {
|
||||
background: Theme.bg-panel;
|
||||
border-radius: Theme.radius-sm;
|
||||
border-width: 1px;
|
||||
border-color: Theme.border-strong;
|
||||
drop-shadow-blur: 10px;
|
||||
drop-shadow-color: #00000050;
|
||||
VerticalLayout {
|
||||
padding: 4px;
|
||||
spacing: 2px;
|
||||
|
||||
Rectangle {
|
||||
height: 28px;
|
||||
border-radius: Theme.radius-sm;
|
||||
background: new-folder-ta.has-hover ? Theme.bg-hover : transparent;
|
||||
new-folder-ta := TouchArea {
|
||||
mouse-cursor: pointer;
|
||||
clicked => { root.new-folder(); }
|
||||
}
|
||||
Text { x: 10px; width: parent.width - 20px; height: parent.height; text: @tr("New category"); color: Theme.text-primary; font-size: Theme.fs-md; vertical-alignment: center; }
|
||||
}
|
||||
Rectangle {
|
||||
height: 28px;
|
||||
border-radius: Theme.radius-sm;
|
||||
background: rename-folder-ta.has-hover ? Theme.bg-hover : transparent;
|
||||
rename-folder-ta := TouchArea {
|
||||
mouse-cursor: pointer;
|
||||
clicked => { root.rename-folder(root.folder-name); }
|
||||
}
|
||||
Text { x: 10px; width: parent.width - 20px; height: parent.height; text: @tr("Rename"); color: Theme.text-primary; font-size: Theme.fs-md; vertical-alignment: center; }
|
||||
}
|
||||
Rectangle {
|
||||
height: 28px;
|
||||
border-radius: Theme.radius-sm;
|
||||
background: delete-folder-ta.has-hover ? #cc333320 : transparent;
|
||||
delete-folder-ta := TouchArea {
|
||||
mouse-cursor: pointer;
|
||||
clicked => { root.delete-folder(root.folder-name); }
|
||||
}
|
||||
Text { x: 10px; width: parent.width - 20px; height: parent.height; text: @tr("Delete"); color: #cc3333; font-size: Theme.fs-md; vertical-alignment: center; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HorizontalLayout {
|
||||
width: parent.width;
|
||||
height: parent.height;
|
||||
spacing: 8px;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
Rectangle {
|
||||
width: 24px;
|
||||
height: parent.height;
|
||||
Text {
|
||||
width: parent.width;
|
||||
height: parent.height;
|
||||
text: root.expanded ? "\u{E5CF}" : "\u{E5CC}";
|
||||
font-family: "Material Icons";
|
||||
color: Theme.text-secondary;
|
||||
font-size: 16px;
|
||||
horizontal-alignment: center;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
Text {
|
||||
height: parent.height;
|
||||
text: root.folder-name;
|
||||
color: Theme.text-secondary;
|
||||
font-size: Theme.fs-sm;
|
||||
font-weight: 600;
|
||||
vertical-alignment: center;
|
||||
horizontal-stretch: 1;
|
||||
overflow: elide;
|
||||
}
|
||||
if root.debug-ids : Text {
|
||||
height: parent.height;
|
||||
text: "[folder-section]";
|
||||
color: Theme.warning;
|
||||
font-size: Theme.fs-xs;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for session in root.sessions : FolderSessionSlot {
|
||||
width: parent.width;
|
||||
folder-name: root.folder-name;
|
||||
folder-expanded: root.expanded;
|
||||
session: session;
|
||||
folder-names: root.folder-names;
|
||||
debug-ids: root.debug-ids;
|
||||
connect-session(id) => { root.connect-session(id); }
|
||||
edit-session(id) => { root.edit-session(id); }
|
||||
remove-session(id) => { root.remove-session(id); }
|
||||
move-session-folder(id, folder) => { root.move-session-folder(id, folder); }
|
||||
}
|
||||
}
|
||||
|
||||
// --- Welcome ---------------------------------------------------------------
|
||||
export component Welcome inherits Rectangle {
|
||||
in property <bool> app-background-active: false;
|
||||
in property <float> ui-opacity: 0.72;
|
||||
in property <[SessionInfo]> sessions;
|
||||
in property <[ConnectionFolderInfo]> folders;
|
||||
in property <[string]> all-folder-names;
|
||||
in property <string> import-hint; // result text after an import
|
||||
in property <bool> debug-ids: false;
|
||||
callback new-session();
|
||||
callback new-folder();
|
||||
callback rename-folder(string);
|
||||
callback delete-folder(string);
|
||||
callback toggle-folder(string);
|
||||
callback move-session-folder(string, string);
|
||||
callback import-ssh-config();
|
||||
callback connect-session(string);
|
||||
callback edit-session(string);
|
||||
callback remove-session(string);
|
||||
|
||||
background: root.app-background-active ? Theme.bg-root.with-alpha(root.ui-opacity) : Theme.bg-root;
|
||||
clip: true;
|
||||
|
||||
Flickable {
|
||||
width: parent.width;
|
||||
height: parent.height;
|
||||
viewport-width: self.width;
|
||||
viewport-height: max(self.height, content.preferred-height);
|
||||
|
||||
content := VerticalLayout {
|
||||
width: root.width;
|
||||
padding: 24px;
|
||||
spacing: 20px;
|
||||
|
||||
VerticalLayout {
|
||||
spacing: 4px;
|
||||
Text {
|
||||
text: "meatshell";
|
||||
color: Theme.text-primary;
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
}
|
||||
Text {
|
||||
text: @tr("meatshell — a lightweight Rust + Slint SSH client by 'lil meatball'");
|
||||
color: Theme.text-secondary;
|
||||
font-size: Theme.fs-md;
|
||||
wrap: word-wrap;
|
||||
}
|
||||
}
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: 8px;
|
||||
Rectangle { horizontal-stretch: 1; }
|
||||
|
||||
PrimaryButton {
|
||||
text: @tr("New SSH");
|
||||
clicked => { root.new-session(); }
|
||||
}
|
||||
}
|
||||
|
||||
// Connection management
|
||||
Rectangle {
|
||||
background: Theme.bg-panel;
|
||||
border-radius: Theme.radius-md;
|
||||
border-width: 1px;
|
||||
border-color: Theme.border-subtle;
|
||||
height: max(240px, min(560px, root.height - 190px));
|
||||
|
||||
VerticalLayout {
|
||||
padding: 16px;
|
||||
spacing: 12px;
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: 8px;
|
||||
Text {
|
||||
text: @tr("Connection manager");
|
||||
color: Theme.text-primary;
|
||||
font-size: Theme.fs-lg;
|
||||
font-weight: 600;
|
||||
vertical-alignment: center;
|
||||
horizontal-stretch: 1;
|
||||
}
|
||||
// Result of the last "Import ~/.ssh/config" (triggered from
|
||||
// the settings menu), e.g. "imported 3".
|
||||
Text {
|
||||
text: root.import-hint;
|
||||
color: Theme.text-muted;
|
||||
font-size: Theme.fs-sm;
|
||||
vertical-alignment: center;
|
||||
overflow: elide;
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
vertical-stretch: 1;
|
||||
clip: true;
|
||||
background: transparent;
|
||||
|
||||
if sessions.length == 0 : Rectangle {
|
||||
width: parent.width;
|
||||
height: parent.height;
|
||||
background: Theme.bg-root;
|
||||
border-radius: Theme.radius-sm;
|
||||
border-width: 1px;
|
||||
border-color: Theme.border-subtle;
|
||||
|
||||
VerticalLayout {
|
||||
alignment: center;
|
||||
spacing: 6px;
|
||||
Text {
|
||||
text: @tr("No sessions yet");
|
||||
color: Theme.text-secondary;
|
||||
font-size: Theme.fs-md;
|
||||
horizontal-alignment: center;
|
||||
}
|
||||
Text {
|
||||
text: @tr("Use the plus button above to add SSH, Serial, or Telnet");
|
||||
color: Theme.text-muted;
|
||||
font-size: Theme.fs-sm;
|
||||
horizontal-alignment: center;
|
||||
wrap: word-wrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if root.folders.length > 0 : Flickable {
|
||||
width: parent.width;
|
||||
height: parent.height;
|
||||
viewport-width: self.width;
|
||||
viewport-height: max(self.height, folder-list.preferred-height);
|
||||
|
||||
folder-list := VerticalLayout {
|
||||
width: parent.width;
|
||||
spacing: 4px;
|
||||
for folder in root.folders : FolderSection {
|
||||
width: parent.width;
|
||||
folder-name: folder.name;
|
||||
expanded: folder.expanded;
|
||||
sessions: root.sessions;
|
||||
folder-names: root.all-folder-names;
|
||||
debug-ids: root.debug-ids;
|
||||
connect-session(id) => { root.connect-session(id); }
|
||||
edit-session(id) => { root.edit-session(id); }
|
||||
remove-session(id) => { root.remove-session(id); }
|
||||
rename-folder(name) => { root.rename-folder(name); }
|
||||
delete-folder(name) => { root.delete-folder(name); }
|
||||
new-folder() => { root.new-folder(); }
|
||||
toggle-folder(name) => { root.toggle-folder(name); }
|
||||
move-session-folder(id, folder) => { root.move-session-folder(id, folder); }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Filler
|
||||
Rectangle { vertical-stretch: 1; }
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user