Initial Commit, Basic Navigation implemented

This commit is contained in:
Neshura 2024-06-20 00:14:19 +02:00
parent 60c3a2da87
commit d531356693
Signed by: Neshura
GPG key ID: 4E2D47B1374C297D
16 changed files with 6874 additions and 0 deletions

30
ui/appwindow.slint Normal file
View file

@ -0,0 +1,30 @@
import { Button, VerticalBox, HorizontalBox } from "std-widgets.slint";
import { SideBar } from "sidebar.slint";
import { AboutPage } from "pages/about_page.slint";
import { SettingsPage, SettingsPageInterface, JNCSettingsInterface } from "pages/settings_page.slint";
import { UploadPage, UploadPageInterface } from "pages/upload_page.slint";
export { SettingsPageInterface, JNCSettingsInterface, UploadPageInterface }
export component AppWindow inherits Window {
title: "Kavita Upload Tool";
preferred-width: 1024px;
preferred-height: 720px;
HorizontalLayout {
VerticalBox {
padding-right: 0px;
side-bar := SideBar {
title: "Kavita Upload Tool";
model: [
"Upload Tool",
"Settings",
"About"
];
}
}
if (side-bar.current-item == 0) : UploadPage {}
if (side-bar.current-item == 1) : SettingsPage {}
if (side-bar.current-item == 2) : AboutPage {}
}
}

13
ui/pages/about_page.slint Normal file
View file

@ -0,0 +1,13 @@
import { VerticalBox, Palette } from "std-widgets.slint";
export component AboutPage inherits VerticalBox {
Rectangle {
border-width: 1px;
border-radius: 4px;
border-color: Palette.border;
background: Palette.alternate-background;
Text {
text: "ToDo: Add Author Info and Link to Repository";
}
}
}

View file

@ -0,0 +1,131 @@
import { VerticalBox, Palette, TextEdit, HorizontalBox, ScrollView, LineEdit, Button } from "std-widgets.slint";
export enum LoginState {
LoggedIn,
LoggedOut,
Processing,
LoginError,
LoginTimeout
}
export global JNCSettingsInterface {
in-out property <LoginState> login_state: LoginState.LoggedOut;
in-out property <string> login_timeout;
in-out property <string> otp_code;
in-out property <int> series_count: 0;
in-out property <int> book_count: 0;
callback start_login();
callback logout();
callback refresh_library();
}
export global SettingsPageInterface {
}
component SettingsMember inherits Rectangle {
border-width: 1px;
border-radius: 4px;
border-color: Palette.border;
background: Palette.alternate-background;
VerticalBox {
alignment: start;
spacing: 4px;
@children
}
}
component StringSetting inherits HorizontalBox {
in property <string> name;
in property <string> placeholder: "" ;
in property <length> w: 100px;
in-out property <string> value;
alignment: start;
height: 24px;
Text {
font-size: 12px;
height: self.font-size + 8px;
vertical-alignment: center;
text: name;
}
LineEdit {
font-size: 12px;
preferred-width: w + 4px;
height: self.font-size + 8px;
placeholder-text: placeholder;
text <=> value;
}
}
export component SettingsPage inherits VerticalBox {
SettingsMember {
Text {
text: "Kavita Settings (sftp)";
}
StringSetting {
name: "Server:";
placeholder: "XXX.XXX.XXX.XXX";
value: "";
w: 120px;
}
StringSetting {
name: "Username:";
placeholder: "User";
value: "";
w: 80px;
}
}
SettingsMember {
Text {
text: "J-Novel Club Settings";
}
if JNCSettingsInterface.login_state == LoginState.LoggedOut : HorizontalLayout {
alignment: start;
Button {
text: "Log In";
clicked => {JNCSettingsInterface.start_login()}
}
}
if JNCSettingsInterface.login_state == LoginState.Processing : HorizontalBox {
otp := Text {
text: JNCSettingsInterface.otp_code;
}
Button {
text: "Copy";
clicked => {}
}
Text {
text: "Awaiting Login Confirmation. \{JNCSettingsInterface.login_timeout} until expiry.";
}
}
if JNCSettingsInterface.login_state == LoginState.LoggedIn : VerticalBox {
HorizontalBox {
Text {
text: "Logged In.";
}
Button {
text: "Log Out";
clicked => {JNCSettingsInterface.logout()}
}
}
HorizontalBox {
Text {
text: "Library: \{JNCSettingsInterface.series_count} Series, \{JNCSettingsInterface.book_count} Books loaded";
}
Button {
text: "refresh";
clicked => {JNCSettingsInterface.refresh_library()}
}
}
}
}
SettingsMember {
Text {
text: "Manual Parser Settings";
}
}
}

View file

@ -0,0 +1,58 @@
import { VerticalBox, Palette, TabWidget, HorizontalBox, Button } from "std-widgets.slint";
export global UploadPageInterface {
in-out property <bool> prev_button_enabled: false;
in-out property <bool> next_button_enabled: true;
pure callback paginate_tabs(int) -> int;
}
export component UploadPage inherits VerticalBox {
Rectangle {
border-width: 1px;
border-radius: 4px;
border-color: Palette.border;
background: Palette.alternate-background;
VerticalLayout {
property <int> current_tab: UploadPageInterface.paginate_tabs(tabs.current-index);
tabs := TabWidget {
Tab {
title: "Parser";
Text {
text: "Parser Settings here";
}
}
Tab {
title: "Metadata";
Text {
text: "Metadata manipulation here";
}
}
Tab {
title: "Upload";
Text {
text: "Upload Stuff here";
}
}
}
HorizontalBox {
previous := Button {
text: "Previous";
enabled: UploadPageInterface.prev_button_enabled;
clicked => {tabs.current-index -= 1}
}
Text {
text: "\{current_tab + 1}/3";
vertical-alignment: center;
horizontal-alignment: center;
}
next := Button {
text: "Next";
enabled: UploadPageInterface.next_button_enabled;
clicked => {tabs.current-index += 1}
}
}
}
}
}

129
ui/sidebar.slint Normal file
View file

@ -0,0 +1,129 @@
import { HorizontalBox, VerticalBox, Palette } from "std-widgets.slint";
component SideBarItem inherits Rectangle {
in property <bool> selected;
in property <bool> has-focus;
in-out property <string> text <=> label.text;
callback clicked <=> touch.clicked;
min-height: l.preferred-height;
states [
pressed when touch.pressed : {
state.opacity: 0.8;
}
hover when touch.has-hover : {
state.opacity: 0.6;
}
selected when root.selected : {
state.opacity: 1;
}
focused when root.has-focus : {
state.opacity: 0.8;
}
]
state := Rectangle {
opacity: 0;
background: Palette.background;
animate opacity { duration: 150ms; }
}
l := HorizontalBox {
y: (parent.height - self.height) / 2;
spacing: 0px;
label := Text {
vertical-alignment: center;
}
}
touch := TouchArea {
width: 100%;
height: 100%;
}
}
export component SideBar inherits Rectangle {
in property <[string]> model: [];
in property <string> title <=> label.text;
out property <int> current-item: 0;
out property <int> current-focused: fs.has-focus ? fs.focused-tab : -1; // The currently focused tab
width: 180px;
forward-focus: fs;
accessible-role: tab;
accessible-delegate-focus: root.current-focused >= 0 ? root.current-focused : root.current-item;
Rectangle {
background: Palette.alternate-background;
border-width: 1px;
border-radius: 4px;
border-color: Palette.border;
fs := FocusScope {
key-pressed(event) => {
if (event.text == "\n") {
root.current-item = root.current-focused;
return accept;
}
if (event.text == Key.UpArrow) {
self.focused-tab = Math.max(self.focused-tab - 1, 0);
return accept;
}
if (event.text == Key.DownArrow) {
self.focused-tab = Math.min(self.focused-tab + 1, root.model.length - 1);
return accept;
}
return reject;
}
key-released(event) => {
if (event.text == " ") {
root.current-item = root.current-focused;
return accept;
}
return reject;
}
property <int> focused-tab: 0;
x: 0;
width: 0; // Do not react on clicks
}
}
VerticalBox {
padding-left: 0px;
padding-right: 0px;
alignment: start;
label := Text {
font-size: 16px;
horizontal-alignment: center;
}
navigation := VerticalLayout {
alignment: start;
vertical-stretch: 0;
for item[index] in root.model : SideBarItem {
clicked => { root.current-item = index; }
has-focus: index == root.current-focused;
text: item;
selected: index == root.current-item;
}
}
VerticalLayout {
bottom := VerticalBox {
padding-top: 0px;
padding-bottom: 0px;
@children
}
}
}
}