Implement keyring #2
Generated
+10
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="GoImports">
|
||||||
|
<option name="excludedPackages">
|
||||||
|
<array>
|
||||||
|
<option value="golang.org/x/net/context" />
|
||||||
|
</array>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
Generated
+23
-23
@@ -2,29 +2,6 @@
|
|||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 4
|
version = 4
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "Tofu"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"anyhow",
|
|
||||||
"chrono",
|
|
||||||
"dirs",
|
|
||||||
"dotenvy",
|
|
||||||
"eyeball-im",
|
|
||||||
"futures-util",
|
|
||||||
"hex",
|
|
||||||
"matrix-sdk",
|
|
||||||
"matrix-sdk-ui",
|
|
||||||
"rand 0.10.3",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"slint",
|
|
||||||
"slint-build",
|
|
||||||
"tokio",
|
|
||||||
"tracing",
|
|
||||||
"tracing-subscriber",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ab_glyph"
|
name = "ab_glyph"
|
||||||
version = "0.2.32"
|
version = "0.2.32"
|
||||||
@@ -6879,6 +6856,29 @@ version = "1.13.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fd3ca314f692efd6c868f8408f53fe444634a845f96c028b97d35f6a1f79f0ee"
|
checksum = "fd3ca314f692efd6c868f8408f53fe444634a845f96c028b97d35f6a1f79f0ee"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tofu"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"chrono",
|
||||||
|
"dirs",
|
||||||
|
"dotenvy",
|
||||||
|
"eyeball-im",
|
||||||
|
"futures-util",
|
||||||
|
"hex",
|
||||||
|
"matrix-sdk",
|
||||||
|
"matrix-sdk-ui",
|
||||||
|
"rand 0.10.3",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"slint",
|
||||||
|
"slint-build",
|
||||||
|
"tokio",
|
||||||
|
"tracing",
|
||||||
|
"tracing-subscriber",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio"
|
name = "tokio"
|
||||||
version = "1.53.2"
|
version = "1.53.2"
|
||||||
|
|||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "Tofu"
|
name = "tofu"
|
||||||
authors = ["Inako"]
|
authors = ["Inako", "Thoq"]
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,151 @@
|
|||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: Tofu 0.1.0\n"
|
||||||
|
"POT-Creation-Date: 2026-10-09 21:50+0000\n"
|
||||||
|
"PO-Revision-Date: 2026-10-09 20:26+0000\n"
|
||||||
|
"Last-Translator: Automatically generated\n"
|
||||||
|
"Language-Team: none\n"
|
||||||
|
"Language: en_US\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
|
|
||||||
|
#. Reply preview: {0} is the sender, {1} the start of the replied-to text.
|
||||||
|
#: ui/components/message-list.slint:112
|
||||||
|
msgctxt "ChatMessage"
|
||||||
|
msgid "↳ {0} {1}"
|
||||||
|
msgstr "↳ {0} {1}"
|
||||||
|
|
||||||
|
#: ui/components/message-list.slint:179
|
||||||
|
msgctxt "ChatMessage"
|
||||||
|
msgid "(edited)"
|
||||||
|
msgstr "(edited)"
|
||||||
|
|
||||||
|
#. {} is the channel name; the leading "#" is the channel prefix.
|
||||||
|
#: ui/components/message-list.slint:274
|
||||||
|
msgctxt "RoomStart"
|
||||||
|
msgid "Welcome to #{}!"
|
||||||
|
msgstr "Welcome to #{}!"
|
||||||
|
|
||||||
|
#. {} is the channel name; the leading "#" is the channel prefix.
|
||||||
|
#: ui/components/message-list.slint:283
|
||||||
|
msgctxt "RoomStart"
|
||||||
|
msgid "This is the start of the #{} channel."
|
||||||
|
msgstr "This is the start of the #{} channel."
|
||||||
|
|
||||||
|
#: ui/components/message-list.slint:395
|
||||||
|
msgctxt "MessageList"
|
||||||
|
msgid "Loading history…"
|
||||||
|
msgstr "Loading history…"
|
||||||
|
|
||||||
|
#: ui/login.slint:35
|
||||||
|
msgctxt "LoginScreen"
|
||||||
|
msgid "Welcome back!"
|
||||||
|
msgstr "Welcome back!"
|
||||||
|
|
||||||
|
#: ui/login.slint:43
|
||||||
|
msgctxt "LoginScreen"
|
||||||
|
msgid "Sign in with your Matrix account"
|
||||||
|
msgstr "Sign in with your Matrix account"
|
||||||
|
|
||||||
|
#: ui/login.slint:54
|
||||||
|
msgctxt "LoginScreen"
|
||||||
|
msgid "HOMESERVER"
|
||||||
|
msgstr "HOMESERVER"
|
||||||
|
|
||||||
|
#: ui/login.slint:61
|
||||||
|
msgctxt "LoginScreen"
|
||||||
|
msgid "matrix.org"
|
||||||
|
msgstr "matrix.org"
|
||||||
|
|
||||||
|
#: ui/login.slint:70
|
||||||
|
msgctxt "LoginScreen"
|
||||||
|
msgid "USERNAME"
|
||||||
|
msgstr "USERNAME"
|
||||||
|
|
||||||
|
#: ui/login.slint:77
|
||||||
|
msgctxt "LoginScreen"
|
||||||
|
msgid "user (or @user:matrix.org)"
|
||||||
|
msgstr "user (or @user:matrix.org)"
|
||||||
|
|
||||||
|
#: ui/login.slint:86
|
||||||
|
msgctxt "LoginScreen"
|
||||||
|
msgid "PASSWORD"
|
||||||
|
msgstr "PASSWORD"
|
||||||
|
|
||||||
|
#: ui/login.slint:115
|
||||||
|
msgctxt "LoginScreen"
|
||||||
|
msgid "Signing in…"
|
||||||
|
msgstr "Signing in…"
|
||||||
|
|
||||||
|
#: ui/login.slint:115
|
||||||
|
msgctxt "LoginScreen"
|
||||||
|
msgid "Log In"
|
||||||
|
msgstr "Log In"
|
||||||
|
|
||||||
|
#: ui/main.slint:18 ui/main.slint:84
|
||||||
|
msgctxt "MainWindow"
|
||||||
|
msgid "Tofu"
|
||||||
|
msgstr "Tofu"
|
||||||
|
|
||||||
|
#. Heading of the sidebar in the Home view, which lists direct messages.
|
||||||
|
#: ui/main.slint:45 ui/main.slint:124
|
||||||
|
msgctxt "MainWindow"
|
||||||
|
msgid "Direct Messages"
|
||||||
|
msgstr "Direct Messages"
|
||||||
|
|
||||||
|
#: ui/main.slint:118
|
||||||
|
msgctxt "MainWindow"
|
||||||
|
msgid "Create / join a space"
|
||||||
|
msgstr "Create / join a space"
|
||||||
|
|
||||||
|
#: ui/main.slint:133
|
||||||
|
msgctxt "MainWindow"
|
||||||
|
msgid "Space settings"
|
||||||
|
msgstr "Space settings"
|
||||||
|
|
||||||
|
#: ui/main.slint:159
|
||||||
|
msgctxt "MainWindow"
|
||||||
|
msgid "Threads"
|
||||||
|
msgstr "Threads"
|
||||||
|
|
||||||
|
#: ui/main.slint:162
|
||||||
|
msgctxt "MainWindow"
|
||||||
|
msgid "Pinned messages"
|
||||||
|
msgstr "Pinned messages"
|
||||||
|
|
||||||
|
#: ui/main.slint:165
|
||||||
|
msgctxt "MainWindow"
|
||||||
|
msgid "Search"
|
||||||
|
msgstr "Search"
|
||||||
|
|
||||||
|
#. {} is the room name (or the person's name in a DM). Keep the "#" / "@".
|
||||||
|
#: ui/main.slint:207
|
||||||
|
msgctxt "MainWindow"
|
||||||
|
msgid "Message @{}"
|
||||||
|
msgstr "Message @{}"
|
||||||
|
|
||||||
|
#. {} is the room name (or the person's name in a DM). Keep the "#" / "@".
|
||||||
|
#: ui/main.slint:207
|
||||||
|
msgctxt "MainWindow"
|
||||||
|
msgid "Message #{}"
|
||||||
|
msgstr "Message #{}"
|
||||||
|
|
||||||
|
#. Banner above the message box. {} is the name of the person replied to.
|
||||||
|
#: ui/main.slint:209
|
||||||
|
msgctxt "MainWindow"
|
||||||
|
msgid "Editing message"
|
||||||
|
msgstr "Editing message"
|
||||||
|
|
||||||
|
#. Banner above the message box. {} is the name of the person replied to.
|
||||||
|
#: ui/main.slint:209
|
||||||
|
msgctxt "MainWindow"
|
||||||
|
msgid "Replying to {}"
|
||||||
|
msgstr "Replying to {}"
|
||||||
|
|
||||||
|
#: ui/main.slint:231
|
||||||
|
msgctxt "MainWindow"
|
||||||
|
msgid "File uploads"
|
||||||
|
msgstr "File uploads"
|
||||||
@@ -0,0 +1,151 @@
|
|||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: tofu 0.1.0\n"
|
||||||
|
"POT-Creation-Date: 2026-10-09 21:50+0000\n"
|
||||||
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
|
"Language-Team: LANGUAGE <[email protected]>\n"
|
||||||
|
"Language: \n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||||
|
|
||||||
|
#. Reply preview: {0} is the sender, {1} the start of the replied-to text.
|
||||||
|
#: ui/components/message-list.slint:112
|
||||||
|
msgctxt "ChatMessage"
|
||||||
|
msgid "↳ {0} {1}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: ui/components/message-list.slint:179
|
||||||
|
msgctxt "ChatMessage"
|
||||||
|
msgid "(edited)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. {} is the channel name; the leading "#" is the channel prefix.
|
||||||
|
#: ui/components/message-list.slint:274
|
||||||
|
msgctxt "RoomStart"
|
||||||
|
msgid "Welcome to #{}!"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. {} is the channel name; the leading "#" is the channel prefix.
|
||||||
|
#: ui/components/message-list.slint:283
|
||||||
|
msgctxt "RoomStart"
|
||||||
|
msgid "This is the start of the #{} channel."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: ui/components/message-list.slint:395
|
||||||
|
msgctxt "MessageList"
|
||||||
|
msgid "Loading history…"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: ui/login.slint:35
|
||||||
|
msgctxt "LoginScreen"
|
||||||
|
msgid "Welcome back!"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: ui/login.slint:43
|
||||||
|
msgctxt "LoginScreen"
|
||||||
|
msgid "Sign in with your Matrix account"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: ui/login.slint:54
|
||||||
|
msgctxt "LoginScreen"
|
||||||
|
msgid "HOMESERVER"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: ui/login.slint:61
|
||||||
|
msgctxt "LoginScreen"
|
||||||
|
msgid "matrix.org"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: ui/login.slint:70
|
||||||
|
msgctxt "LoginScreen"
|
||||||
|
msgid "USERNAME"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: ui/login.slint:77
|
||||||
|
msgctxt "LoginScreen"
|
||||||
|
msgid "user (or @user:matrix.org)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: ui/login.slint:86
|
||||||
|
msgctxt "LoginScreen"
|
||||||
|
msgid "PASSWORD"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: ui/login.slint:115
|
||||||
|
msgctxt "LoginScreen"
|
||||||
|
msgid "Signing in…"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: ui/login.slint:115
|
||||||
|
msgctxt "LoginScreen"
|
||||||
|
msgid "Log In"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: ui/main.slint:18 ui/main.slint:84
|
||||||
|
msgctxt "MainWindow"
|
||||||
|
msgid "Tofu"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. Heading of the sidebar in the Home view, which lists direct messages.
|
||||||
|
#: ui/main.slint:45 ui/main.slint:124
|
||||||
|
msgctxt "MainWindow"
|
||||||
|
msgid "Direct Messages"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: ui/main.slint:118
|
||||||
|
msgctxt "MainWindow"
|
||||||
|
msgid "Create / join a space"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: ui/main.slint:133
|
||||||
|
msgctxt "MainWindow"
|
||||||
|
msgid "Space settings"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: ui/main.slint:159
|
||||||
|
msgctxt "MainWindow"
|
||||||
|
msgid "Threads"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: ui/main.slint:162
|
||||||
|
msgctxt "MainWindow"
|
||||||
|
msgid "Pinned messages"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: ui/main.slint:165
|
||||||
|
msgctxt "MainWindow"
|
||||||
|
msgid "Search"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. {} is the room name (or the person's name in a DM). Keep the "#" / "@".
|
||||||
|
#: ui/main.slint:207
|
||||||
|
msgctxt "MainWindow"
|
||||||
|
msgid "Message @{}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. {} is the room name (or the person's name in a DM). Keep the "#" / "@".
|
||||||
|
#: ui/main.slint:207
|
||||||
|
msgctxt "MainWindow"
|
||||||
|
msgid "Message #{}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. Banner above the message box. {} is the name of the person replied to.
|
||||||
|
#: ui/main.slint:209
|
||||||
|
msgctxt "MainWindow"
|
||||||
|
msgid "Editing message"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. Banner above the message box. {} is the name of the person replied to.
|
||||||
|
#: ui/main.slint:209
|
||||||
|
msgctxt "MainWindow"
|
||||||
|
msgid "Replying to {}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: ui/main.slint:231
|
||||||
|
msgctxt "MainWindow"
|
||||||
|
msgid "File uploads"
|
||||||
|
msgstr ""
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
use slint_build::CompilerConfiguration;
|
use slint_build::CompilerConfiguration;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let config = CompilerConfiguration::new().with_style("fluent-dark".to_owned());
|
let config = CompilerConfiguration::new()
|
||||||
|
.with_style("fluent-dark".to_owned())
|
||||||
|
.with_bundled_translations("assets/translations");
|
||||||
slint_build::compile_with_config("ui/main.slint", config).expect("Failed to compile Slint UI");
|
slint_build::compile_with_config("ui/main.slint", config).expect("Failed to compile Slint UI");
|
||||||
}
|
}
|
||||||
Executable
+46
@@ -0,0 +1,46 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
cd "$(dirname "${BASH_SOURCE[0]}")/.."
|
||||||
|
|
||||||
|
# janky aaah
|
||||||
|
domain=$(sed -n '/^\[package\]/,/^\[/s/^name = "\(.*\)"$/\1/p' Cargo.toml)
|
||||||
|
version=$(sed -n '/^\[package\]/,/^\[/s/^version = "\(.*\)"$/\1/p' Cargo.toml)
|
||||||
|
[ -n "$domain" ] || { echo "error: could not find the package name in Cargo.toml" >&2; exit 1; }
|
||||||
|
dir=assets/translations
|
||||||
|
pot=$dir/$domain.pot
|
||||||
|
|
||||||
|
for tool in slint-tr-extractor msginit msgmerge msgcat; do
|
||||||
|
command -v "$tool" > /dev/null || { echo "error: '$tool' is not installed, see docs/TRANSLATIONS.md" >&2; exit 1; }
|
||||||
|
done
|
||||||
|
|
||||||
|
fresh=$(mktemp)
|
||||||
|
trap 'rm -f "$fresh"' EXIT
|
||||||
|
|
||||||
|
find ui -name '*.slint' | LC_ALL=C sort | xargs slint-tr-extractor --package-name "$domain" --package-version "$version" -o "$fresh"
|
||||||
|
|
||||||
|
msgcat --no-wrap "$fresh" > /dev/null || { echo "error: the extracted template is not valid, is a translator comment longer than 76 characters?" >&2; exit 1; }
|
||||||
|
|
||||||
|
if [ -f "$pot" ] && diff -q <(grep -v '^"POT-Creation-Date:' "$pot") <(grep -v '^"POT-Creation-Date:' "$fresh") > /dev/null; then
|
||||||
|
echo "$pot is up to date"
|
||||||
|
else
|
||||||
|
cp "$fresh" "$pot"
|
||||||
|
echo "updated $pot"
|
||||||
|
fi
|
||||||
|
|
||||||
|
for lang in "$@"; do
|
||||||
|
po=$dir/$lang/LC_MESSAGES/$domain.po
|
||||||
|
if [ -e "$po" ]; then
|
||||||
|
echo "$po already exists" >&2
|
||||||
|
else
|
||||||
|
mkdir -p "$(dirname "$po")"
|
||||||
|
msginit --no-translator --locale="$lang" --input="$pot" --output-file="$po"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
for po in "$dir"/*/LC_MESSAGES/"$domain".po; do
|
||||||
|
[ -e "$po" ] || continue
|
||||||
|
msgmerge --update --backup=none --quiet "$po" "$pot"
|
||||||
|
echo "merged into $po"
|
||||||
|
done
|
||||||
+70
-29
@@ -30,10 +30,11 @@ pub fn toast(ui: &MainWindow, text: &str) {
|
|||||||
|
|
||||||
fn typing_text(names: &[String]) -> String {
|
fn typing_text(names: &[String]) -> String {
|
||||||
match names {
|
match names {
|
||||||
|
//todo: translate
|
||||||
[] => String::new(),
|
[] => String::new(),
|
||||||
[a] => format!("{a} is typing..."),
|
[a] => format!("{a} is typing..."),
|
||||||
[a, b] => format!("{a} and {b} are typing..."),
|
[a, b] => format!("{a} and {b} are typing..."),
|
||||||
_ => "Several people are typing...".to_owned()
|
_ => "Several people are typing...".to_owned(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,7 +47,7 @@ pub fn apply(ui: &MainWindow, event: Event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Event::LoginBusy(busy) => {
|
Event::LoginBusy(busy) => {
|
||||||
ui.set_login_busy(busy)
|
ui.set_login_busy(busy);
|
||||||
}
|
}
|
||||||
|
|
||||||
Event::LoggedIn(user) => {
|
Event::LoggedIn(user) => {
|
||||||
@@ -58,16 +59,22 @@ pub fn apply(ui: &MainWindow, event: Event) {
|
|||||||
ui.set_screen(Screen::Main);
|
ui.set_screen(Screen::Main);
|
||||||
}
|
}
|
||||||
|
|
||||||
Event::Servers { servers, home_unread, home_mentions } => {
|
Event::Servers {
|
||||||
let items: Vec<ServerItem> = servers.into_iter()
|
servers,
|
||||||
|
home_unread,
|
||||||
|
home_mentions,
|
||||||
|
} => {
|
||||||
|
let items: Vec<ServerItem> = servers
|
||||||
|
.into_iter()
|
||||||
.map(|server| ServerItem {
|
.map(|server| ServerItem {
|
||||||
color: color_for(&server.id),
|
color: color_for(&server.id),
|
||||||
initial: initial_of(&server.name).into(),
|
initial: initial_of(&server.name).into(),
|
||||||
id: server.id.into(),
|
id: server.id.into(),
|
||||||
name: server.name.into(),
|
name: server.name.into(),
|
||||||
unread: server.unread,
|
unread: server.unread,
|
||||||
mentions: i32::try_from(server.mentions).unwrap_or(i32::MAX)
|
mentions: i32::try_from(server.mentions).unwrap_or(i32::MAX),
|
||||||
}).collect();
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
ui.set_servers(model_of(items));
|
ui.set_servers(model_of(items));
|
||||||
ui.set_home_unread(home_unread);
|
ui.set_home_unread(home_unread);
|
||||||
@@ -77,13 +84,18 @@ pub fn apply(ui: &MainWindow, event: Event) {
|
|||||||
Event::Channels { title, channels } => {
|
Event::Channels { title, channels } => {
|
||||||
let selected: String = ui.get_selected_room_id().to_string();
|
let selected: String = ui.get_selected_room_id().to_string();
|
||||||
|
|
||||||
let first_openable: Option<String> = channels.iter()
|
let first_openable: Option<String> = channels
|
||||||
.find(|channel| channel.kind != model::ChannelKind::Category && channel.kind != model::ChannelKind::Voice)
|
.iter()
|
||||||
|
.find(|channel| {
|
||||||
|
channel.kind != model::ChannelKind::Category
|
||||||
|
&& channel.kind != model::ChannelKind::Voice
|
||||||
|
})
|
||||||
.map(|channel| channel.id.clone());
|
.map(|channel| channel.id.clone());
|
||||||
|
|
||||||
let still_here: bool = channels.iter().any(|channel| channel.id == selected);
|
let still_here: bool = channels.iter().any(|channel| channel.id == selected);
|
||||||
|
|
||||||
let items: Vec<ChannelItem> = channels.into_iter()
|
let items: Vec<ChannelItem> = channels
|
||||||
|
.into_iter()
|
||||||
.map(|channel| ChannelItem {
|
.map(|channel| ChannelItem {
|
||||||
color: color_for(&channel.id),
|
color: color_for(&channel.id),
|
||||||
initial: initial_of(&channel.name).into(),
|
initial: initial_of(&channel.name).into(),
|
||||||
@@ -98,20 +110,18 @@ pub fn apply(ui: &MainWindow, event: Event) {
|
|||||||
unread: channel.unread,
|
unread: channel.unread,
|
||||||
mentions: i32::try_from(channel.mentions).unwrap_or(i32::MAX),
|
mentions: i32::try_from(channel.mentions).unwrap_or(i32::MAX),
|
||||||
joined: channel.joined,
|
joined: channel.joined,
|
||||||
is_dm: channel.is_dm
|
is_dm: channel.is_dm,
|
||||||
}).collect();
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
ui.set_sidebar_title(title.into());
|
ui.set_sidebar_title(title.into());
|
||||||
ui.set_channels(model_of(items));
|
ui.set_channels(model_of(items));
|
||||||
|
|
||||||
if !still_here {
|
if !still_here {
|
||||||
match first_openable {
|
if let Some(id) = first_openable {
|
||||||
Some(id) => {
|
|
||||||
ui.set_selected_room_id(id.clone().into());
|
ui.set_selected_room_id(id.clone().into());
|
||||||
ui.invoke_select_room(id.into());
|
ui.invoke_select_room(id.into());
|
||||||
}
|
} else {
|
||||||
|
|
||||||
None => {
|
|
||||||
ui.set_selected_room_id(SharedString::new());
|
ui.set_selected_room_id(SharedString::new());
|
||||||
ui.set_room_open(false);
|
ui.set_room_open(false);
|
||||||
ui.set_room_name(SharedString::new());
|
ui.set_room_name(SharedString::new());
|
||||||
@@ -121,7 +131,6 @@ pub fn apply(ui: &MainWindow, event: Event) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Event::RoomOpened(info) => {
|
Event::RoomOpened(info) => {
|
||||||
ui.set_room_name(info.name.into());
|
ui.set_room_name(info.name.into());
|
||||||
@@ -145,21 +154,23 @@ pub fn apply(ui: &MainWindow, event: Event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Event::Members(rows) => {
|
Event::Members(rows) => {
|
||||||
let items: Vec<MemberItem> = rows.into_iter()
|
let items: Vec<MemberItem> = rows
|
||||||
|
.into_iter()
|
||||||
.map(|member| MemberItem {
|
.map(|member| MemberItem {
|
||||||
color: color_for(&member.id),
|
color: color_for(&member.id),
|
||||||
initial: initial_of(&member.name).into(),
|
initial: initial_of(&member.name).into(),
|
||||||
id: member.id.into(),
|
id: member.id.into(),
|
||||||
name: member.name.into(),
|
name: member.name.into(),
|
||||||
is_header: member.is_header,
|
is_header: member.is_header,
|
||||||
}).collect();
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
ui.set_members(model_of(items));
|
ui.set_members(model_of(items));
|
||||||
}
|
}
|
||||||
|
|
||||||
Event::Typing(names) => ui.set_typing_text(typing_text(&names).into()),
|
Event::Typing(names) => ui.set_typing_text(typing_text(&names).into()),
|
||||||
Event::LoadingOlder(b) => ui.set_loading_older(b),
|
Event::LoadingOlder(b) => ui.set_loading_older(b),
|
||||||
Event::Toast(text) => toast(ui, &text)
|
Event::Toast(text) => toast(ui, &text),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,12 +202,15 @@ fn message_item(message: model::MessageRow) -> MessageItem {
|
|||||||
reply_sender: reply_sender.into(),
|
reply_sender: reply_sender.into(),
|
||||||
reply_body: reply_body.into(),
|
reply_body: reply_body.into(),
|
||||||
reactions: model_of(
|
reactions: model_of(
|
||||||
message.reactions.into_iter()
|
message
|
||||||
|
.reactions
|
||||||
|
.into_iter()
|
||||||
.map(|reaction| ReactionItem {
|
.map(|reaction| ReactionItem {
|
||||||
key: reaction.key.into(),
|
key: reaction.key.into(),
|
||||||
count: i32::try_from(reaction.count).unwrap_or(i32::MAX),
|
count: i32::try_from(reaction.count).unwrap_or(i32::MAX),
|
||||||
mine: reaction.mine,
|
mine: reaction.mine,
|
||||||
}).collect(),
|
})
|
||||||
|
.collect(),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -210,11 +224,14 @@ pub fn wire(ui: &MainWindow, tx: &UnboundedSender<Command>) {
|
|||||||
let tx = tx.clone();
|
let tx = tx.clone();
|
||||||
|
|
||||||
move |homeserver, username, password| {
|
move |homeserver, username, password| {
|
||||||
send(&tx, Command::Login {
|
send(
|
||||||
|
&tx,
|
||||||
|
Command::Login {
|
||||||
homeserver: homeserver.into(),
|
homeserver: homeserver.into(),
|
||||||
username: username.into(),
|
username: username.into(),
|
||||||
password: password.into(),
|
password: password.into(),
|
||||||
});
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -225,7 +242,12 @@ pub fn wire(ui: &MainWindow, tx: &UnboundedSender<Command>) {
|
|||||||
|
|
||||||
ui.on_select_server({
|
ui.on_select_server({
|
||||||
let tx = tx.clone();
|
let tx = tx.clone();
|
||||||
move |id| send(&tx, Command::SelectServer(Some(id.to_string()).filter(|s| !s.is_empty())))
|
move |id| {
|
||||||
|
send(
|
||||||
|
&tx,
|
||||||
|
Command::SelectServer(Some(id.to_string()).filter(|s| !s.is_empty())),
|
||||||
|
);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ui.on_select_room({
|
ui.on_select_room({
|
||||||
@@ -236,16 +258,27 @@ pub fn wire(ui: &MainWindow, tx: &UnboundedSender<Command>) {
|
|||||||
ui.on_send_message({
|
ui.on_send_message({
|
||||||
let tx = tx.clone();
|
let tx = tx.clone();
|
||||||
move |reply_to, text| {
|
move |reply_to, text| {
|
||||||
send(&tx, Command::Send {
|
send(
|
||||||
|
&tx,
|
||||||
|
Command::Send {
|
||||||
reply_to: Some(reply_to.to_string()).filter(|s| !s.is_empty()),
|
reply_to: Some(reply_to.to_string()).filter(|s| !s.is_empty()),
|
||||||
text: text.into(),
|
text: text.into(),
|
||||||
});
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ui.on_edit_message({
|
ui.on_edit_message({
|
||||||
let tx = tx.clone();
|
let tx = tx.clone();
|
||||||
move |id, text| send(&tx, Command::Edit { id: id.into(), text: text.into() })
|
move |id, text| {
|
||||||
|
send(
|
||||||
|
&tx,
|
||||||
|
Command::Edit {
|
||||||
|
id: id.into(),
|
||||||
|
text: text.into(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ui.on_delete_message({
|
ui.on_delete_message({
|
||||||
@@ -255,7 +288,15 @@ pub fn wire(ui: &MainWindow, tx: &UnboundedSender<Command>) {
|
|||||||
|
|
||||||
ui.on_toggle_reaction({
|
ui.on_toggle_reaction({
|
||||||
let tx = tx.clone();
|
let tx = tx.clone();
|
||||||
move |id, key| send(&tx, Command::ToggleReaction { id: id.into(), key: key.into() })
|
move |id, key| {
|
||||||
|
send(
|
||||||
|
&tx,
|
||||||
|
Command::ToggleReaction {
|
||||||
|
id: id.into(),
|
||||||
|
key: key.into(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ui.on_load_older({
|
ui.on_load_older({
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
mod backend;
|
mod backend;
|
||||||
mod model;
|
mod model;
|
||||||
pub mod bridge;
|
pub mod bridge;
|
||||||
|
mod helpers;
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use slint::Weak;
|
use slint::Weak;
|
||||||
|
|||||||
@@ -108,7 +108,8 @@ component ChatMessage inherits Rectangle {
|
|||||||
x: Theme.gutter;
|
x: Theme.gutter;
|
||||||
width: parent.width - Theme.gutter - 16px;
|
width: parent.width - Theme.gutter - 16px;
|
||||||
height: parent.height;
|
height: parent.height;
|
||||||
text: "↳ \{root.m.reply-sender} \{root.m.reply-body}";
|
// Reply preview: {0} is the sender, {1} the start of the replied-to text.
|
||||||
|
text: @tr("↳ {0} {1}", root.m.reply-sender, root.m.reply-body);
|
||||||
color: Theme.text-muted;
|
color: Theme.text-muted;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
vertical-alignment: center;
|
vertical-alignment: center;
|
||||||
@@ -175,7 +176,7 @@ component ChatMessage inherits Rectangle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if root.m.edited: Text {
|
if root.m.edited: Text {
|
||||||
text: "(edited)";
|
text: @tr("(edited)");
|
||||||
color: Theme.text-faint;
|
color: Theme.text-faint;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
}
|
}
|
||||||
@@ -269,7 +270,8 @@ component RoomStart inherits Rectangle {
|
|||||||
padding-top: 32px;
|
padding-top: 32px;
|
||||||
spacing: 6px;
|
spacing: 6px;
|
||||||
Text {
|
Text {
|
||||||
text: "Welcome to #\{root.room-name}!";
|
// {} is the channel name; the leading "#" is the channel prefix.
|
||||||
|
text: @tr("Welcome to #{}!", root.room-name);
|
||||||
color: Theme.text-header;
|
color: Theme.text-header;
|
||||||
font-size: 28px;
|
font-size: 28px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
@@ -277,7 +279,8 @@ component RoomStart inherits Rectangle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: "This is the start of the #\{root.room-name} channel.";
|
// {} is the channel name; the leading "#" is the channel prefix.
|
||||||
|
text: @tr("This is the start of the #{} channel.", root.room-name);
|
||||||
color: Theme.text-muted;
|
color: Theme.text-muted;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
wrap: word-wrap;
|
wrap: word-wrap;
|
||||||
@@ -389,7 +392,7 @@ export component MessageList inherits Rectangle {
|
|||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
background: Theme.popup-bg;
|
background: Theme.popup-bg;
|
||||||
Text {
|
Text {
|
||||||
text: "Loading history…";
|
text: @tr("Loading history…");
|
||||||
color: Theme.text-muted;
|
color: Theme.text-muted;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
horizontal-alignment: center;
|
horizontal-alignment: center;
|
||||||
|
|||||||
+8
-8
@@ -32,7 +32,7 @@ export component LoginScreen inherits Rectangle {
|
|||||||
spacing: 6px;
|
spacing: 6px;
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: "Welcome back!";
|
text: @tr("Welcome back!");
|
||||||
color: Theme.text-header;
|
color: Theme.text-header;
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
@@ -40,7 +40,7 @@ export component LoginScreen inherits Rectangle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: "Sign in with your Matrix account";
|
text: @tr("Sign in with your Matrix account");
|
||||||
color: Theme.text-muted;
|
color: Theme.text-muted;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
horizontal-alignment: center;
|
horizontal-alignment: center;
|
||||||
@@ -51,14 +51,14 @@ export component LoginScreen inherits Rectangle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: "HOMESERVER";
|
text: @tr("HOMESERVER");
|
||||||
color: Theme.text-muted;
|
color: Theme.text-muted;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
TextField {
|
TextField {
|
||||||
placeholder: "matrix.org";
|
placeholder: @tr("matrix.org");
|
||||||
text <=> root.homeserver;
|
text <=> root.homeserver;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,14 +67,14 @@ export component LoginScreen inherits Rectangle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: "USERNAME";
|
text: @tr("USERNAME");
|
||||||
color: Theme.text-muted;
|
color: Theme.text-muted;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
TextField {
|
TextField {
|
||||||
placeholder: "user (or @user:matrix.org)";
|
placeholder: @tr("user (or @user:matrix.org)");
|
||||||
text <=> root.username;
|
text <=> root.username;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,7 +83,7 @@ export component LoginScreen inherits Rectangle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: "PASSWORD";
|
text: @tr("PASSWORD");
|
||||||
color: Theme.text-muted;
|
color: Theme.text-muted;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
@@ -112,7 +112,7 @@ export component LoginScreen inherits Rectangle {
|
|||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
background: root.busy ? Theme.accent-hover : (login-touch.has-hover ? Theme.accent-hover : Theme.accent);
|
background: root.busy ? Theme.accent-hover : (login-touch.has-hover ? Theme.accent-hover : Theme.accent);
|
||||||
Text {
|
Text {
|
||||||
text: root.busy ? "Signing in…" : "Log In";
|
text: root.busy ? @tr("Signing in…") : @tr("Log In");
|
||||||
color: white;
|
color: white;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
|||||||
+20
-14
@@ -15,7 +15,7 @@ import { MemberList } from "components/member-list.slint";
|
|||||||
export { Screen, ServerItem, ChannelItem, MessageItem, MemberItem, ChannelKind, MessageKind, ReactionItem } from "types.slint";
|
export { Screen, ServerItem, ChannelItem, MessageItem, MemberItem, ChannelKind, MessageKind, ReactionItem } from "types.slint";
|
||||||
|
|
||||||
export component MainWindow inherits Window {
|
export component MainWindow inherits Window {
|
||||||
title: "Tofu";
|
title: @tr("Tofu");
|
||||||
min-width: 900px;
|
min-width: 900px;
|
||||||
min-height: 520px;
|
min-height: 520px;
|
||||||
preferred-width: 1280px;
|
preferred-width: 1280px;
|
||||||
@@ -42,7 +42,7 @@ export component MainWindow inherits Window {
|
|||||||
in property <[MessageItem]> messages;
|
in property <[MessageItem]> messages;
|
||||||
in property <[MemberItem]> members;
|
in property <[MemberItem]> members;
|
||||||
|
|
||||||
in property <string> sidebar-title: "Direct Messages";
|
in property <string> sidebar-title: @tr("Direct Messages");
|
||||||
in property <string> room-name;
|
in property <string> room-name;
|
||||||
in property <string> room-topic;
|
in property <string> room-topic;
|
||||||
in property <bool> room-is-dm;
|
in property <bool> room-is-dm;
|
||||||
@@ -81,7 +81,7 @@ export component MainWindow inherits Window {
|
|||||||
if root.screen == Screen.loading: Rectangle {
|
if root.screen == Screen.loading: Rectangle {
|
||||||
background: Theme.rail-bg;
|
background: Theme.rail-bg;
|
||||||
Text {
|
Text {
|
||||||
text: "Tofu";
|
text: @tr("Tofu");
|
||||||
color: Theme.text-muted;
|
color: Theme.text-muted;
|
||||||
font-size: 32px;
|
font-size: 32px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
@@ -115,12 +115,13 @@ export component MainWindow inherits Window {
|
|||||||
root.select-server(id);
|
root.select-server(id);
|
||||||
}
|
}
|
||||||
add-server => {
|
add-server => {
|
||||||
root.not-implemented("Create / join a space");
|
root.not-implemented(@tr("Create / join a space"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ChannelList {
|
ChannelList {
|
||||||
title: root.sidebar-title;
|
// Heading of the sidebar in the Home view, which lists direct messages.
|
||||||
|
title: root.selected-server-id == "" ? @tr("Direct Messages") : root.sidebar-title;
|
||||||
is-home: root.selected-server-id == "";
|
is-home: root.selected-server-id == "";
|
||||||
channels: root.channels;
|
channels: root.channels;
|
||||||
selected-id: root.selected-room-id;
|
selected-id: root.selected-room-id;
|
||||||
@@ -129,7 +130,7 @@ export component MainWindow inherits Window {
|
|||||||
root.select-room(id);
|
root.select-room(id);
|
||||||
}
|
}
|
||||||
open-server-menu => {
|
open-server-menu => {
|
||||||
root.not-implemented("Space settings");
|
root.not-implemented(@tr("Space settings"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -147,6 +148,7 @@ export component MainWindow inherits Window {
|
|||||||
|
|
||||||
// Right: chat
|
// Right: chat
|
||||||
VerticalLayout {
|
VerticalLayout {
|
||||||
|
width: root.width - Theme.rail-width - Theme.sidebar-width;
|
||||||
ChatHeader {
|
ChatHeader {
|
||||||
room-name: root.room-name;
|
room-name: root.room-name;
|
||||||
topic: root.room-topic;
|
topic: root.room-topic;
|
||||||
@@ -154,18 +156,21 @@ export component MainWindow inherits Window {
|
|||||||
encrypted: root.room-encrypted;
|
encrypted: root.room-encrypted;
|
||||||
show-members <=> root.show-members;
|
show-members <=> root.show-members;
|
||||||
open-threads => {
|
open-threads => {
|
||||||
root.not-implemented("Threads");
|
root.not-implemented(@tr("Threads"));
|
||||||
}
|
}
|
||||||
open-pins => {
|
open-pins => {
|
||||||
root.not-implemented("Pinned messages");
|
root.not-implemented(@tr("Pinned messages"));
|
||||||
}
|
}
|
||||||
open-search => {
|
open-search => {
|
||||||
root.not-implemented("Search");
|
root.not-implemented(@tr("Search"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
HorizontalLayout {
|
Rectangle {
|
||||||
|
vertical-stretch: 1;
|
||||||
VerticalLayout {
|
VerticalLayout {
|
||||||
|
width: parent.width - (root.show-members ? Theme.member-width : 0px);
|
||||||
|
height: parent.height;
|
||||||
MessageList {
|
MessageList {
|
||||||
messages: root.messages;
|
messages: root.messages;
|
||||||
room-name: root.room-name;
|
room-name: root.room-name;
|
||||||
@@ -198,8 +203,8 @@ export component MainWindow inherits Window {
|
|||||||
text-to-set: root.composer-text;
|
text-to-set: root.composer-text;
|
||||||
text-nonce: root.composer-text-nonce;
|
text-nonce: root.composer-text-nonce;
|
||||||
focus-nonce: root.composer-focus-nonce;
|
focus-nonce: root.composer-focus-nonce;
|
||||||
placeholder: root.room-is-dm ? "Message @\{root.room-name}" : "Message #\{root.room-name}";
|
placeholder: root.room-is-dm ? @tr("Message @{}", root.room-name) : @tr("Message #{}", root.room-name);
|
||||||
banner: root.editing-id != "" ? "Editing message" : (root.reply-to-id != "" ? "Replying to \{root.reply-to-name}" : "");
|
banner: root.editing-id != "" ? @tr("Editing message") : (root.reply-to-id != "" ? @tr("Replying to {}", root.reply-to-name) : "");
|
||||||
editing: root.editing-id != "";
|
editing: root.editing-id != "";
|
||||||
typing-text: root.typing-text;
|
typing-text: root.typing-text;
|
||||||
submit(text) => {
|
submit(text) => {
|
||||||
@@ -221,19 +226,20 @@ export component MainWindow inherits Window {
|
|||||||
root.typing();
|
root.typing();
|
||||||
}
|
}
|
||||||
attach => {
|
attach => {
|
||||||
root.not-implemented("File uploads");
|
root.not-implemented(@tr("File uploads"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if root.show-members: MemberList {
|
if root.show-members: MemberList {
|
||||||
|
x: parent.width - self.width;
|
||||||
|
height: parent.height;
|
||||||
members: root.members;
|
members: root.members;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transient banner for errors and "coming soon" notices.
|
|
||||||
if root.toast != "": Rectangle {
|
if root.toast != "": Rectangle {
|
||||||
width: min(480px, parent.width - 32px);
|
width: min(480px, parent.width - 32px);
|
||||||
height: 40px;
|
height: 40px;
|
||||||
|
|||||||
Reference in New Issue
Block a user