This includes temporary AI-generated frontend scaffolding, none of this will be used in production. It was just used during development for the backend.
156 lines
4.7 KiB
Plaintext
156 lines
4.7 KiB
Plaintext
/// TEMPORARY AI GENERATED FRONTEND \\\
|
|
/// NOT TO BE USED IN PRODUCTION \\\
|
|
|
|
import { Theme } from "../theme.slint";
|
|
import { Icons } from "../icons.slint";
|
|
import { Icon } from "icon.slint";
|
|
import { ToolButton } from "tool-button.slint";
|
|
|
|
// Message box. Enter sends, Shift+Enter inserts a newline (same as Discord).
|
|
// The component is deliberately stateless about *what* is being sent: the
|
|
// parent owns the reply/edit state and just tells us what banner to show.
|
|
export component Composer inherits Rectangle {
|
|
in property <string> placeholder;
|
|
in property <string> banner; // "Replying to Alice" / "Editing message"; "" hides it
|
|
in property <bool> editing;
|
|
in property <string> typing-text; // "Bob is typing…"
|
|
callback submit(string);
|
|
callback cancel-banner();
|
|
callback clear-text();
|
|
callback typing();
|
|
callback attach();
|
|
// Parent -> composer requests, as properties so the composer can live inside an `if`.
|
|
in property <string> text-to-set;
|
|
in property <int> text-nonce; // bump to replace the input text with `text-to-set` (and focus)
|
|
in property <int> focus-nonce; // bump to just focus the input
|
|
changed text-nonce => {
|
|
input.text = root.text-to-set;
|
|
input.focus();
|
|
}
|
|
changed focus-nonce => {
|
|
input.focus();
|
|
}
|
|
|
|
// Grows with content up to ~8 lines, like Discord.
|
|
property <length> input-height: min(max(input.preferred-height, 24px), 180px);
|
|
|
|
height: (root.banner != "" ? 32px : 0px) + input-height + 24px + 24px;
|
|
background: Theme.chat-bg;
|
|
|
|
// Reply / edit banner sits above the input and shares its rounded top.
|
|
if root.banner != "": Rectangle {
|
|
x: 16px;
|
|
y: 0px;
|
|
width: parent.width - 32px;
|
|
height: 32px + 8px;
|
|
background: Theme.sidebar-bg;
|
|
border-top-left-radius: 8px;
|
|
border-top-right-radius: 8px;
|
|
|
|
Text {
|
|
x: 12px;
|
|
width: parent.width - 60px;
|
|
height: 32px;
|
|
text: root.banner;
|
|
color: Theme.text-muted;
|
|
font-size: 13px;
|
|
vertical-alignment: center;
|
|
overflow: elide;
|
|
}
|
|
|
|
ToolButton {
|
|
glyph: Icons.close;
|
|
x: parent.width - self.width - 6px;
|
|
y: 2px;
|
|
clicked => {
|
|
root.cancel-banner();
|
|
}
|
|
}
|
|
}
|
|
|
|
box := Rectangle {
|
|
x: 16px;
|
|
y: (root.banner != "" ? 32px : 0px);
|
|
width: parent.width - 32px;
|
|
height: root.input-height + 24px;
|
|
background: Theme.input-bg;
|
|
border-radius: root.banner != "" ? 0px : 8px;
|
|
border-bottom-left-radius: 8px;
|
|
border-bottom-right-radius: 8px;
|
|
|
|
ToolButton {
|
|
glyph: Icons.plus-circle;
|
|
x: 6px;
|
|
y: parent.height - self.height - 10px;
|
|
clicked => {
|
|
root.attach();
|
|
}
|
|
}
|
|
|
|
FocusScope {
|
|
x: 44px;
|
|
y: 12px;
|
|
width: parent.width - 44px - 52px;
|
|
height: root.input-height;
|
|
|
|
// Intercept Enter before the TextInput turns it into a newline.
|
|
capture-key-pressed(event) => {
|
|
if (event.text == Key.Return && !event.modifiers.shift) {
|
|
if (input.text != "") {
|
|
root.submit(input.text);
|
|
input.text = "";
|
|
}
|
|
return accept;
|
|
}
|
|
return reject;
|
|
}
|
|
|
|
input := TextInput {
|
|
width: parent.width;
|
|
height: parent.height;
|
|
single-line: false;
|
|
wrap: word-wrap;
|
|
color: Theme.text-normal;
|
|
font-size: 15px;
|
|
selection-background-color: Theme.accent;
|
|
edited => {
|
|
root.typing();
|
|
}
|
|
}
|
|
}
|
|
|
|
if input.text == "": Text {
|
|
x: 44px;
|
|
y: 12px;
|
|
width: parent.width - 100px;
|
|
height: 24px;
|
|
text: root.placeholder;
|
|
color: Theme.text-faint;
|
|
font-size: 15px;
|
|
vertical-alignment: center;
|
|
overflow: elide;
|
|
}
|
|
|
|
ToolButton {
|
|
glyph: Icons.smile;
|
|
x: parent.width - self.width - 8px;
|
|
y: parent.height - self.height - 10px;
|
|
// TODO: emoji picker
|
|
}
|
|
}
|
|
|
|
// Typing indicator, under the box.
|
|
Text {
|
|
x: 20px;
|
|
y: parent.height - 22px;
|
|
width: parent.width - 40px;
|
|
height: 18px;
|
|
text: root.typing-text;
|
|
color: Theme.text-muted;
|
|
font-size: 12px;
|
|
font-weight: 600;
|
|
vertical-alignment: center;
|
|
overflow: elide;
|
|
}
|
|
}
|