Files
mail/ui/components/sidebar.slint

656 lines
35 KiB
Plaintext

import { ScrollFade } from "scroll-fade.slint";
import { ListView, VerticalBox } from "std-widgets.slint";
import { SidebarRow, SidebarRowKind } from "../models.slint";
import { SidebarItem, SidebarSectionHeader } from "sidebar-controls.slint";
import { SidebarStyle } from "sidebar-row.slint";
import { Button } from "button.slint";
import { IconButton, LineEdit } from "controls.slint";
import {
CollapsibleWorkspaceSidebar,
WorkspaceMiniHeading,
} from "workspace-layout.slint";
import { LayoutMetrics, MailIconPalette, MotionSettings } from "../design-system.slint";
import { TypeScale } from "../typography.slint";
export component MailSidebar inherits CollapsibleWorkspaceSidebar {
in property <bool> touch-mode: false;
in property <[SidebarRow]> rows: [];
in-out property <length> scroll-y: 0px;
in-out property <string> filter-query: "";
private property <bool> filter-open: root.filter-query != "";
in property <string> unified-count;
in property <string> selected-scope;
in property <bool> monochrome-icons: false;
in property <color> background-color;
in property <color> hover-background;
in property <color> selection-background;
in property <color> accent-color;
in property <color> title-color;
in property <color> body-color;
in property <color> muted-color;
callback select-scope(string);
callback compose;
callback manage-accounts;
callback toggle-folder(int, bool);
callback toggle-section(string, bool);
callback filter-folders(string);
callback request-create-folder(int, int, string);
callback request-create-label(int, string, int, string);
callback request-rename-folder(int, string);
callback request-delete-folder(int, string);
callback request-empty-trash(int);
callback request-rename-label(int, string, color, string, bool);
callback request-delete-label(int, string, string, bool, bool);
surface-color: root.background-color;
clip: true;
// Account rows hold a name and an address; both keep their 12px design
// and grow with the platform font.
private property <length> account-row-height: 10px
+ max(20px, account-name-probe.preferred-height)
+ max(15px, account-detail-probe.preferred-height);
account-name-probe := Text { visible: false; text: "Ag"; font-size: SidebarStyle.label-size; font-weight: SidebarStyle.selected-weight; }
account-detail-probe := Text { visible: false; text: "Ag"; font-size: TypeScale.caption; }
if !root.collapsed: VerticalBox {
x: LayoutMetrics.sidebar-inset;
y: LayoutMetrics.sidebar-action-top;
width: parent.width - 2 * LayoutMetrics.sidebar-inset;
height: parent.height - LayoutMetrics.sidebar-action-top;
padding: 0px;
spacing: 8px;
Button {
height: LayoutMetrics.sidebar-action-height;
horizontal-stretch: 1;
text: @tr("New email");
icon: @image-url("../icons/lucide/filled/compose.svg");
has-icon: true;
primary: true;
clicked => { root.compose(); }
}
Rectangle {
height: 32px;
horizontal-stretch: 1;
background: transparent;
Rectangle {
visible: !root.filter-open;
x: 0px;
width: parent.width - 36px;
height: parent.height;
border-radius: 6px;
background: find-hit.has-hover ? root.hover-background : transparent;
Image {
x: 13px - self.width / 2;
y: (parent.height - self.height) / 2;
width: LayoutMetrics.icon-size;
height: LayoutMetrics.icon-size;
source: @image-url("../icons/lucide/outline/search.svg");
colorize: root.muted-color;
image-fit: contain;
accessible-role: none;
}
Text {
x: 28px;
width: parent.width - 28px;
height: parent.height;
text: @tr("Find folder or label");
color: root.muted-color;
font-size: TypeScale.caption;
vertical-alignment: center;
overflow: elide;
}
find-hit := TouchArea {
accessible-role: button;
accessible-label: @tr("Find folder or label");
accessible-action-default => {
root.filter-open = true;
filter-field.focus();
}
clicked => {
root.filter-open = true;
filter-field.focus();
}
mouse-cursor: pointer;
}
}
filter-field := LineEdit {
visible: root.filter-open;
x: 0px;
width: parent.width - 36px;
height: parent.height;
text <=> root.filter-query;
placeholder-text: @tr("Find folder or label");
input-label: @tr("Find folder or label");
show-leading-icon: true;
leading-icon: @image-url("../icons/lucide/outline/search.svg");
leading-icon-color: root.muted-color;
trailing-content-width: 32px;
edited(value) => { root.filter-folders(value); }
}
IconButton {
visible: root.filter-open;
x: parent.width - 68px;
width: 32px;
height: 32px;
icon: @image-url("../icons/lucide/outline/x.svg");
icon-size: 14px;
icon-color: root.muted-color;
hover-background: root.hover-background;
tooltip-text: @tr("Clear folder filter");
clicked => {
root.filter-query = "";
root.filter-open = false;
root.filter-folders("");
}
}
IconButton {
x: parent.width - 32px;
width: 32px;
height: 32px;
icon: @image-url("../icons/lucide/outline/plus.svg");
icon-size: 16px;
icon-color: root.muted-color;
hover-background: root.hover-background;
tooltip-text: @tr("Add account");
clicked => { root.manage-accounts(); }
}
}
Rectangle {
vertical-stretch: 1;
width: parent.width;
background: transparent;
mailbox-scroll := ListView {
// Keep a direct ListView repeater; Slint does not
// virtualize through a wrapper or inherited ListView.
content-width: self.visible-width;
horizontal-scrollbar-policy: always-off;
x: 0px;
y: 0px;
width: parent.width;
height: parent.height;
content-y <=> root.scroll-y;
mouse-drag-pan-enabled: root.touch-mode;
// The repeater must be a direct ListView child: nesting it in
// a layout disables virtualization. Rust supplies only expanded rows.
for entry in root.rows: Rectangle {
private property <bool> reveal-ready: !entry.reveal_row;
width: max(0px, mailbox-scroll.visible-width - LayoutMetrics.sidebar-scrollbar-gutter);
height: !entry.show_row || (entry.reveal_row && !self.reveal-ready && MotionSettings.enabled) ? 0px
: entry.kind == SidebarRowKind.unified-heading ? 21px
: entry.kind == SidebarRowKind.accounts-heading ? 25px
: entry.kind == SidebarRowKind.account ? (entry.mailbox.detail == "" ? 35px : root.account-row-height)
: entry.kind == SidebarRowKind.unified-section || entry.kind == SidebarRowKind.unified-inbox ? 35px
: entry.kind == SidebarRowKind.folder && entry.mailbox.label == "Inbox" ? 35px
: 31px;
background: transparent;
clip: true;
animate height { duration: 230ms; easing: spring(0); enabled: MotionSettings.enabled; }
Timer {
interval: 16ms;
running: entry.reveal_row && !parent.reveal-ready && MotionSettings.enabled;
triggered => { parent.reveal-ready = true; }
}
if entry.kind == SidebarRowKind.unified-heading: WorkspaceMiniHeading {
x: 0px;
y: 0px;
width: parent.width;
label: @tr("ALL ACCOUNTS");
label-color: root.muted-color;
}
if entry.kind == SidebarRowKind.unified-section: SidebarSectionHeader {
x: 0px;
interactive: entry.show_row;
y: 0px;
width: parent.width;
label: @tr("All Inboxes");
icon: @image-url("../icons/lucide/filled/stack.svg");
sidebar-background: root.background-color;
hover-background: root.hover-background;
body-color: root.title-color;
muted-color: root.muted-color;
icon-color: root.monochrome-icons ? root.body-color : MailIconPalette.inbox;
open: entry.open;
toggled(open) => { root.toggle-section(entry.key, open); }
}
if entry.kind == SidebarRowKind.unified-inbox: SidebarItem {
x: 0px;
interactive: entry.show_row;
y: 0px;
width: parent.width;
label: @tr("Inbox");
count: root.unified-count;
scope: "Unified Inbox";
account-id: 0;
folder-id: -1;
mail-drop-enabled: true;
icon: @image-url("../icons/lucide/filled/inbox-flectar.svg");
selected: root.selected-scope == "Unified Inbox";
row-height: 32px;
sidebar-background: root.background-color;
selection-background: root.selection-background;
accent-color: root.accent-color;
body-color: root.body-color;
muted-color: root.muted-color;
icon-color: root.monochrome-icons ? (root.selected-scope == "Unified Inbox" ? root.accent-color : root.muted-color) : MailIconPalette.inbox;
clicked(scope) => { root.select-scope(scope); }
}
if entry.kind == SidebarRowKind.unified-mailbox: SidebarItem {
x: 0px;
interactive: entry.show_row;
y: 0px;
width: parent.width;
label: entry.mailbox.label;
count: entry.mailbox.count;
scope: entry.mailbox.scope;
account-id: entry.mailbox.account_id;
can-empty-trash: entry.mailbox.scope == "Unified Trash";
folder-id: entry.mailbox.folder_id;
mail-drop-enabled: entry.mailbox.scope == "Unified Starred"
|| entry.mailbox.scope == "Unified Archive"
|| entry.mailbox.scope == "Unified Spam"
|| entry.mailbox.scope == "Unified Trash";
mail-drop-copies: entry.mailbox.scope == "Unified Starred";
icon: entry.mailbox.scope == "Unified Starred" ? @image-url("../icons/lucide/filled/star.svg")
: entry.mailbox.scope == "Unified Sent" ? @image-url("../icons/lucide/filled/paper-airplane.svg")
: entry.mailbox.scope == "Unified Spam" ? @image-url("../icons/lucide/filled/alert.svg")
: entry.mailbox.scope == "Unified Trash" ? @image-url("../icons/lucide/filled/trash.svg")
: entry.mailbox.scope == "Unified Archive" ? @image-url("../icons/lucide/filled/archive.svg")
: @image-url("../icons/lucide/filled/draft.svg");
selected: entry.mailbox.scope == root.selected-scope;
sidebar-background: root.background-color;
selection-background: root.selection-background;
accent-color: root.accent-color;
body-color: root.body-color;
muted-color: root.muted-color;
icon-color: root.monochrome-icons ? (entry.mailbox.scope == root.selected-scope ? root.accent-color : root.muted-color)
: entry.mailbox.scope == "Unified Starred" ? MailIconPalette.starred
: entry.mailbox.scope == "Unified Sent" ? MailIconPalette.sent
: entry.mailbox.scope == "Unified Spam" ? MailIconPalette.spam
: entry.mailbox.scope == "Unified Trash" ? MailIconPalette.trash
: entry.mailbox.scope == "Unified Archive" ? MailIconPalette.archive
: MailIconPalette.drafts;
clicked(scope) => { root.select-scope(scope); }
empty-trash(account-id) => { root.request-empty-trash(account-id); }
}
if entry.kind == SidebarRowKind.categories-section: SidebarSectionHeader {
x: 0px;
interactive: entry.show_row;
y: 0px;
width: parent.width;
label: @tr("Categories");
label-font-weight: 500;
folder-row-style: true;
sidebar-background: root.background-color;
hover-background: root.hover-background;
body-color: root.body-color;
muted-color: root.muted-color;
icon: @image-url("../icons/lucide/filled/tag.svg");
icon-color: root.monochrome-icons ? root.body-color : MailIconPalette.inbox;
open: entry.open;
toggled(open) => { root.toggle-section(entry.key, open); }
}
if entry.kind == SidebarRowKind.important: SidebarItem {
x: 0px;
interactive: entry.show_row;
y: 0px;
width: parent.width;
label: @tr("Important");
left-padding: SidebarStyle.icon-left + SidebarStyle.branch-indent;
scope: "Important";
mail-drop-enabled: true;
icon: @image-url("../icons/lucide/filled/tag.svg");
selected: root.selected-scope == "Important";
sidebar-background: root.background-color;
selection-background: root.selection-background;
accent-color: root.accent-color;
body-color: root.body-color;
muted-color: root.muted-color;
icon-color: root.monochrome-icons ? (root.selected-scope == "Important" ? root.accent-color : root.muted-color) : MailIconPalette.inbox;
clicked(scope) => { root.select-scope(scope); }
}
if entry.kind == SidebarRowKind.other: SidebarItem {
x: 0px;
interactive: entry.show_row;
y: 0px;
width: parent.width;
label: @tr("Other");
left-padding: SidebarStyle.icon-left + SidebarStyle.branch-indent;
scope: "Other";
mail-drop-enabled: true;
icon: @image-url("../icons/lucide/filled/tag.svg");
selected: root.selected-scope == "Other";
sidebar-background: root.background-color;
selection-background: root.selection-background;
accent-color: root.accent-color;
body-color: root.body-color;
muted-color: root.muted-color;
icon-color: root.monochrome-icons ? (root.selected-scope == "Other" ? root.accent-color : root.muted-color) : MailIconPalette.other;
clicked(scope) => { root.select-scope(scope); }
}
if entry.kind == SidebarRowKind.category: SidebarItem {
x: 0px;
interactive: entry.show_row;
y: 0px;
width: parent.width;
label: entry.label.name;
label-has-emoji: entry.label.name_has_emoji;
left-padding: SidebarStyle.icon-left + SidebarStyle.branch-indent;
scope: "Category:" + entry.label.id;
mail-drop-enabled: true;
icon: @image-url("../icons/lucide/filled/tag.svg");
selected: root.selected-scope == "Category:" + entry.label.id;
sidebar-background: root.background-color;
selection-background: root.selection-background;
accent-color: root.accent-color;
body-color: root.body-color;
muted-color: root.muted-color;
icon-color: root.monochrome-icons
? (root.selected-scope == "Category:" + entry.label.id ? root.accent-color : root.muted-color)
: entry.label.color;
clicked(scope) => { root.select-scope(scope); }
}
if entry.kind == SidebarRowKind.global-labels-section: SidebarSectionHeader {
x: 0px;
interactive: entry.show_row;
y: 0px;
width: parent.width;
label: @tr("Labels");
label-font-weight: 500;
folder-row-style: true;
sidebar-background: root.background-color;
hover-background: root.hover-background;
body-color: root.body-color;
muted-color: root.muted-color;
icon: @image-url("../icons/lucide/filled/tag.svg");
icon-color: root.monochrome-icons ? root.body-color : MailIconPalette.folders;
open: entry.open;
toggled(open) => { root.toggle-section(entry.key, open); }
}
if entry.kind == SidebarRowKind.global-label: SidebarItem {
x: 0px;
interactive: entry.show_row;
y: 0px;
width: parent.width;
label: entry.label.name;
accessible-name: @tr("Global label {}", entry.label.name);
label-has-emoji: entry.label.name_has_emoji;
left-padding: SidebarStyle.icon-left + SidebarStyle.branch-indent;
label-id: entry.label.id;
editable-color: entry.label.color;
scope: "GlobalLabel:" + entry.label.id;
label-manageable: true;
label-is-global: true;
mail-drop-enabled: true;
mail-drop-copies: true;
icon: @image-url("../icons/lucide/filled/tag.svg");
selected: root.selected-scope == "GlobalLabel:" + entry.label.id;
sidebar-background: root.background-color;
selection-background: root.selection-background;
accent-color: root.accent-color;
body-color: root.body-color;
muted-color: root.muted-color;
icon-color: root.monochrome-icons
? (root.selected-scope == "GlobalLabel:" + entry.label.id ? root.accent-color : root.muted-color)
: entry.label.color;
clicked(scope) => { root.select-scope(scope); }
rename-label(id, name, color, account, global) => { root.request-rename-label(id, name, color, account, global); }
delete-label(id, name, account, global, has-children) => {
root.request-delete-label(id, name, account, global, has-children);
}
}
if entry.kind == SidebarRowKind.accounts-heading: WorkspaceMiniHeading {
x: 0px;
y: 0px;
width: parent.width;
height: 22px;
label: @tr("ACCOUNTS");
label-color: root.muted-color;
vertical-alignment: bottom;
}
if entry.kind == SidebarRowKind.account: SidebarSectionHeader {
x: 0px;
interactive: entry.show_row;
y: 0px;
width: parent.width;
label: entry.mailbox.label;
detail: entry.mailbox.detail;
avatar: entry.mailbox.avatar;
avatar-image: entry.mailbox.avatar_image;
has-avatar-image: entry.mailbox.has_avatar;
has-avatar: true;
sidebar-background: root.background-color;
hover-background: root.hover-background;
body-color: root.title-color;
muted-color: root.muted-color;
icon-color: root.body-color;
open: entry.open;
toggled(open) => { root.toggle-section(entry.key, open); }
}
if entry.kind == SidebarRowKind.folder: SidebarItem {
x: 0px;
interactive: entry.show_row;
y: 0px;
width: parent.width;
label: entry.mailbox.label;
label-has-emoji: entry.mailbox.label_has_emoji;
count: entry.mailbox.count;
scope: entry.mailbox.scope;
account-id: entry.mailbox.account_id;
folder-id: entry.mailbox.folder_id;
has-children: entry.mailbox.has_children;
expanded: entry.mailbox.expanded;
folder-menu-enabled: entry.mailbox.folder_id >= 0
&& !(entry.mailbox.is_standard && entry.mailbox.label == "Trash")
&& (entry.mailbox.can_create_children
|| (!entry.mailbox.is_standard && entry.mailbox.can_rename)
|| (!entry.mailbox.is_standard && entry.mailbox.can_delete));
can-create-child: entry.mailbox.can_create_children;
can-rename: !entry.mailbox.is_standard && entry.mailbox.can_rename;
can-delete: !entry.mailbox.is_standard && entry.mailbox.can_delete;
can-empty-trash: entry.mailbox.is_standard
&& entry.mailbox.label == "Trash"
&& entry.mailbox.folder_id >= 0;
mail-drop-enabled: entry.mailbox.is_selectable
&& ((entry.mailbox.folder_id >= 0
&& entry.mailbox.label != "Sent"
&& entry.mailbox.label != "Drafts")
|| entry.mailbox.label == "Starred");
mail-drop-copies: entry.mailbox.label == "Starred";
left-padding: SidebarStyle.icon-left + entry.mailbox.depth * SidebarStyle.branch-indent;
icon: entry.mailbox.label == "Inbox" ? @image-url("../icons/lucide/filled/inbox-flectar.svg")
: entry.mailbox.label == "Starred" ? @image-url("../icons/lucide/filled/star.svg")
: entry.mailbox.label == "Sent" ? @image-url("../icons/lucide/filled/paper-airplane.svg")
: entry.mailbox.label == "Archive" ? @image-url("../icons/lucide/filled/archive.svg")
: entry.mailbox.label == "Spam" ? @image-url("../icons/lucide/filled/alert.svg")
: entry.mailbox.label == "Trash" ? @image-url("../icons/lucide/filled/trash.svg")
: entry.mailbox.label == "Drafts" ? @image-url("../icons/lucide/filled/draft.svg")
: entry.mailbox.scope == root.selected-scope ? @image-url("../icons/lucide/filled/folder-open.svg")
: @image-url("../icons/lucide/filled/file-directory.svg");
selected: entry.mailbox.scope == root.selected-scope;
row-height: entry.mailbox.label == "Inbox" ? 32px : 28px;
sidebar-background: root.background-color;
selection-background: root.selection-background;
accent-color: root.accent-color;
body-color: root.body-color;
muted-color: root.muted-color;
icon-color: root.monochrome-icons ? (entry.mailbox.scope == root.selected-scope ? root.accent-color : root.muted-color)
: entry.mailbox.label == "Inbox" ? MailIconPalette.inbox
: entry.mailbox.label == "Starred" ? MailIconPalette.starred
: entry.mailbox.label == "Sent" ? MailIconPalette.sent
: entry.mailbox.label == "Archive" ? MailIconPalette.archive
: entry.mailbox.label == "Spam" ? MailIconPalette.spam
: entry.mailbox.label == "Trash" ? MailIconPalette.trash
: entry.mailbox.label == "Drafts" ? MailIconPalette.drafts
: entry.mailbox.has_custom_color ? entry.mailbox.color
: MailIconPalette.folders;
clicked(scope) => {
if (entry.mailbox.is_selectable || entry.mailbox.is_standard) {
root.select-scope(scope);
} else if (entry.mailbox.has_children) {
root.toggle-folder(entry.mailbox.folder_id, !entry.mailbox.expanded);
}
}
toggled(folder-id, expanded) => { root.toggle-folder(folder-id, expanded); }
new-subfolder(account-id, folder-id, name) => {
root.request-create-folder(account-id, folder-id, name);
}
rename-folder(folder-id, name) => { root.request-rename-folder(folder-id, name); }
delete-folder(folder-id, name) => { root.request-delete-folder(folder-id, name); }
empty-trash(account-id) => { root.request-empty-trash(account-id); }
}
if entry.kind == SidebarRowKind.account-labels-section: SidebarSectionHeader {
x: 0px;
interactive: entry.show_row;
y: 0px;
width: parent.width;
label: @tr("Labels");
label-font-weight: 500;
folder-row-style: true;
sidebar-background: root.background-color;
hover-background: root.hover-background;
body-color: root.body-color;
muted-color: root.muted-color;
icon: @image-url("../icons/lucide/filled/tag.svg");
icon-color: root.monochrome-icons ? root.body-color : MailIconPalette.folders;
open: entry.open;
toggled(open) => { root.toggle-section(entry.key, open); }
}
if entry.kind == SidebarRowKind.account-label: SidebarItem {
x: 0px;
interactive: entry.show_row;
y: 0px;
width: parent.width;
label: entry.label.display_name;
label-path: entry.label.name;
accessible-name: @tr("{} label, {} account", entry.label.name, entry.label.account_name);
label-has-emoji: entry.label.name_has_emoji;
left-padding: SidebarStyle.icon-left + SidebarStyle.branch-indent
+ entry.label.depth * SidebarStyle.branch-indent;
label-id: entry.label.id;
has-children: entry.label.has_children;
expanded: entry.label.expanded;
label-tree-node: true;
editable-color: entry.label.color;
scope: "AccountLabel:" + entry.label.id;
account-id: entry.label.account_id;
label-manageable: true;
label-can-create-child: entry.label.can_create_children;
label-account-name: entry.label.account_name;
mail-drop-enabled: true;
mail-drop-copies: true;
icon: @image-url("../icons/lucide/filled/tag.svg");
selected: root.selected-scope == "AccountLabel:" + entry.label.id;
sidebar-background: root.background-color;
selection-background: root.selection-background;
accent-color: root.accent-color;
body-color: root.body-color;
muted-color: root.muted-color;
icon-color: root.monochrome-icons
? (root.selected-scope == "AccountLabel:" + entry.label.id ? root.accent-color : root.muted-color)
: entry.label.color;
clicked(scope) => { root.select-scope(scope); }
toggled(label-id, expanded) => { root.toggle-section(entry.key, expanded); }
new-child-label(account-id, label-id, path, account) => {
root.request-create-label(account-id, account, label-id, path);
}
rename-label(id, name, color, account, global) => { root.request-rename-label(id, name, color, account, global); }
delete-label(id, name, account, global, has-children) => {
root.request-delete-label(id, name, account, global, has-children);
}
}
if entry.kind == SidebarRowKind.new-folder: SidebarItem {
x: 0px;
interactive: entry.show_row;
y: 0px;
width: parent.width;
label: @tr("New folder");
scope: "";
icon: @image-url("../icons/lucide/outline/plus.svg");
sidebar-background: root.background-color;
selection-background: root.selection-background;
accent-color: root.accent-color;
body-color: root.body-color;
muted-color: root.muted-color;
icon-color: root.muted-color;
clicked(scope) => { root.request-create-folder(entry.mailbox.account_id, -1, ""); }
}
if entry.kind == SidebarRowKind.new-label: SidebarItem {
x: 0px;
interactive: entry.show_row;
y: 0px;
width: parent.width;
label: @tr("New label");
scope: "";
left-padding: SidebarStyle.icon-left + SidebarStyle.branch-indent;
icon: @image-url("../icons/lucide/outline/plus.svg");
sidebar-background: root.background-color;
selection-background: root.selection-background;
accent-color: root.accent-color;
body-color: root.body-color;
muted-color: root.muted-color;
icon-color: root.muted-color;
clicked(scope) => { root.request-create-label(entry.mailbox.account_id, entry.mailbox.context, -1, ""); }
}
}
}
if root.filter-query != "" && root.rows.length == 0 : Text {
x: 12px;
y: 12px;
width: parent.width - 24px;
text: @tr("No folders found");
color: root.muted-color;
font-size: TypeScale.caption;
horizontal-alignment: center;
wrap: word-wrap;
}
ScrollFade {
x: mailbox-scroll.x;
y: mailbox-scroll.y;
width: mailbox-scroll.visible-width;
height: mailbox-scroll.visible-height;
surface-color: root.background-color;
content-y: mailbox-scroll.content-y;
content-height: mailbox-scroll.content-height;
visible-height: mailbox-scroll.visible-height;
}
}
}
}