Files
crossdesk/apps/desktop/src/gui/ui/message_panel.slint
T

250 lines
13 KiB
Plaintext

import { ScrollView } from "std-widgets.slint";
import { CompactButton, DialogSurface, IconButton, FontAwesomeIcons, ImGuiFontStyle } from "common.slint";
export struct AnnouncementItem {
id: string,
revision: string,
title: string,
date: string,
read: bool,
}
export global MessageStrings {
in-out property <string> messages: "Messages";
in-out property <string> unread: "Unread";
in-out property <string> read: "Read";
in-out property <string> empty: "No announcements";
in-out property <string> select-message: "Select an announcement to read";
in-out property <string> loading: "Loading…";
in-out property <string> read-save-error: "Unable to save reading status locally. Open the announcement again to retry.";
in-out property <string> close: "Close";
in-out property <string> delete-message: "Delete";
in-out property <string> delete-title: "Delete announcement";
in-out property <string> delete-confirmation: "Delete this announcement?";
in-out property <string> delete-error: "Unable to delete this announcement. Please try again.";
in-out property <string> cancel: "Cancel";
}
export component MessagePanel inherits DialogSurface {
in property <[AnnouncementItem]> items;
in property <int> unread-count;
in property <int> total;
in property <bool> loading;
in property <bool> failed;
in property <bool> read-save-failed;
in property <bool> delete-failed;
in property <bool> loaded;
in property <bool> connected;
in property <string> selected-id;
in property <string> selected-title;
in property <styled-text> selected-body;
in property <string> selected-date;
in property <bool> selected-read;
callback open-message(string, string);
callback open-link(string);
callback delete-message(string, string);
callback load-more;
callback close;
private property <string> notice-text: root.delete-failed ? MessageStrings.delete-error
: root.read-save-failed ? MessageStrings.read-save-error : "";
private property <length> content-top: title-bar.height + (root.notice-text == "" ? 14px : 50px);
private property <color> separator-color: #e6e8eb;
private property <string> pending-delete-id;
private property <string> pending-delete-revision;
function request-delete(id: string, revision: string) {
root.pending-delete-id = id;
root.pending-delete-revision = revision;
confirmation-keys.focus();
}
confirmation-keys := FocusScope {
width: 0px; height: 0px;
key-pressed(event) => {
if event.text == Key.Escape {
if root.pending-delete-id != "" { root.pending-delete-id = ""; }
else { root.close(); }
return accept;
}
return reject;
}
}
background: white;
border-radius: 7px;
TouchArea { }
title-bar := Rectangle {
x: 0px; y: 0px; width: parent.width; height: 21px;
background: #d3d3d3; border-radius: 7px;
Rectangle { y: parent.height - 7px; height: 7px; background: #d3d3d3; }
message-heading := Text {
x: 6px; width: parent.width - 100px; height: parent.height;
text: MessageStrings.messages;
font-size: ImGuiFontStyle.base; font-weight: 600; color: #202124;
vertical-alignment: center; overflow: elide;
}
if root.unread-count > 0: Rectangle {
x: message-heading.x + message-heading.preferred-width + 8px;
y: (parent.height - self.height) / 2; height: 16px;
width: max(16px, unread-count-label.preferred-width + 8px);
border-radius: 8px; background: #eaf2ff;
accessible-role: text;
accessible-label: MessageStrings.unread + " " + root.unread-count;
unread-count-label := Text {
text: "" + root.unread-count; color: #2563eb;
font-size: ImGuiFontStyle.small; font-weight: 600;
horizontal-alignment: center; vertical-alignment: center;
accessible-role: none;
}
}
if root.loading: Text {
x: parent.width - 150px; width: 110px; height: parent.height;
text: MessageStrings.loading; color: #61656c; font-size: ImGuiFontStyle.small;
horizontal-alignment: right; vertical-alignment: center;
}
IconButton {
x: parent.width - 25px; y: 2px; width: 19px; height: parent.height - 4px;
border-radius: 3px;
icon: FontAwesomeIcons.xmark; icon-size: ImGuiFontStyle.base;
icon-color: #202124;
hover-background: #bfc2c7; pressed-background: #b2b2b2;
accessible-role: button;
accessible-label: MessageStrings.close;
accessible-action-default => { root.close(); }
clicked => { root.close(); }
}
}
if root.notice-text != "": Rectangle {
x: 16px; y: title-bar.height + 10px; width: parent.width - 32px; height: 28px;
border-radius: 4px;
background: #fef2f2;
Text {
x: 8px; width: parent.width - 16px; height: parent.height;
text: root.notice-text; font-size: ImGuiFontStyle.body; overflow: elide;
color: #b3261e;
vertical-alignment: center;
}
}
announcement-list := Rectangle {
x: 16px; y: root.content-top; width: 168px; height: parent.height - self.y - 16px;
announcements-scroll := ScrollView {
width: parent.width; height: parent.height;
VerticalLayout {
width: announcements-scroll.width; spacing: 0px; padding: 0px;
padding-right: 8px;
alignment: start;
for item[index] in root.items: Rectangle {
height: 54px;
background: root.selected-id == item.id ? #eaf2ff : entry-touch.has-hover ? #f0f1f2 : transparent;
border-radius: 4px;
Text { x: 8px; y: 6px; width: parent.width - 32px; height: 18px; text: item.title; overflow: elide; font-size: ImGuiFontStyle.base; font-weight: item.read ? 400 : 600; color: #202124; vertical-alignment: center; }
if !item.read: Rectangle {
x: parent.width - 17px; y: 13px; width: 5px; height: 5px;
border-radius: 2.5px; background: #2563eb;
accessible-role: none;
}
Text { x: 8px; y: 28px; width: parent.width - 36px; height: 16px; text: item.date; color: #61656c; font-size: ImGuiFontStyle.small; overflow: elide; vertical-alignment: center; }
accessible-role: button;
accessible-label: item.title + " " + (item.read ? MessageStrings.read : MessageStrings.unread);
accessible-action-default => { root.open-message(item.id, item.revision); }
entry-touch := TouchArea { mouse-cursor: pointer; clicked => { root.open-message(item.id, item.revision); } }
IconButton {
x: parent.width - 24px; y: 26px; width: 20px; height: 20px;
icon: FontAwesomeIcons.xmark; icon-size: ImGuiFontStyle.small;
icon-color: #61656c; danger: true;
accessible-role: button;
accessible-label: MessageStrings.delete-message + " " + item.title;
accessible-action-default => { root.request-delete(item.id, item.revision); }
clicked => { root.request-delete(item.id, item.revision); }
}
if index < root.items.length - 1: Rectangle {
x: 8px; y: parent.height - 1px;
width: parent.width - 16px; height: 1px;
background: root.separator-color;
accessible-role: none;
}
}
}
}
}
if root.items.length == 0 && root.total == 0 && root.loaded && root.connected && !root.loading && !root.failed: Text {
x: 16px; y: root.content-top; width: parent.width - 32px; height: announcement-list.height;
text: MessageStrings.empty; wrap: word-wrap;
color: #61656c; font-size: ImGuiFontStyle.body;
horizontal-alignment: center; vertical-alignment: center;
}
content-divider := Rectangle {
x: announcement-list.x + announcement-list.width + 12px;
y: root.content-top; width: 1px; height: announcement-list.height;
background: root.separator-color;
visible: root.items.length > 0 || root.selected-id != "";
accessible-role: none;
}
announcement-details := Rectangle {
x: content-divider.x + content-divider.width + 12px;
y: root.content-top; width: parent.width - self.x - 16px; height: announcement-list.height;
if root.selected-id == "" && root.items.length > 0: Text { x: 8px; width: parent.width - 16px; text: MessageStrings.select-message; color: #61656c; font-size: ImGuiFontStyle.body; wrap: word-wrap; horizontal-alignment: center; vertical-alignment: center; }
if root.selected-id != "": details-scroll := ScrollView {
width: parent.width; height: parent.height;
VerticalLayout {
width: details-scroll.width; padding: 8px; padding-top: 6px; padding-right: 16px;
spacing: 12px; alignment: start;
VerticalLayout {
vertical-stretch: 0; spacing: 4px;
Text { vertical-stretch: 0; min-height: 18px; text: root.selected-title; wrap: word-wrap; font-size: ImGuiFontStyle.base; font-weight: 600; color: #202124; vertical-alignment: center; }
Text { vertical-stretch: 0; min-height: 16px; text: root.selected-date; accessible-label: root.selected-date + " · " + (root.selected-read ? MessageStrings.read : MessageStrings.unread); color: #61656c; font-size: ImGuiFontStyle.small; wrap: word-wrap; vertical-alignment: center; }
}
Rectangle { height: 1px; background: root.separator-color; }
StyledText {
vertical-stretch: 0;
text: root.selected-body;
default-color: #202124; default-font-size: ImGuiFontStyle.body;
link-color: #2563eb;
link-clicked(url) => { root.open-link(url); }
}
}
}
}
Timer {
interval: 100ms;
running: root.connected && !root.loading && !root.failed
&& root.pending-delete-id == "" && root.items.length > 0
&& root.items.length < root.total && announcements-scroll.viewport-height > 0px
&& announcements-scroll.viewport-height + announcements-scroll.viewport-y <= announcements-scroll.height + 80px;
triggered => { root.load-more(); }
}
if root.pending-delete-id != "": Rectangle {
background: #00000042; border-radius: root.border-radius; z: 30;
TouchArea { clicked => { confirmation-keys.focus(); } }
DialogSurface {
x: (parent.width - self.width) / 2;
y: (parent.height - self.height) / 2;
width: 230px; height: confirmation-content.preferred-height;
border-radius: root.border-radius;
TouchArea { clicked => { confirmation-keys.focus(); } }
confirmation-content := VerticalLayout {
padding: 16px;
padding-top: 26px;
padding-bottom: 26px;
spacing: 20px;
Text { text: MessageStrings.delete-confirmation; font-size: ImGuiFontStyle.base; color: #202124; wrap: word-wrap; horizontal-alignment: center; }
HorizontalLayout {
alignment: center; spacing: 8px;
cancel-delete-button := CompactButton {
width: 72px; text: MessageStrings.cancel;
accessible-role: button; accessible-label: MessageStrings.cancel;
accessible-action-default => { cancel-delete-button.clicked(); }
clicked => { root.pending-delete-id = ""; }
}
confirm-delete-button := CompactButton {
width: 72px; text: MessageStrings.delete-message; primary: true;
accessible-role: button; accessible-label: MessageStrings.delete-title;
accessible-action-default => { confirm-delete-button.clicked(); }
clicked => {
root.delete-message(root.pending-delete-id, root.pending-delete-revision);
root.pending-delete-id = "";
}
}
}
}
}
}
}