feat: implement client backend
This includes temporary AI-generated frontend scaffolding, none of this will be used in production. It was just used during development for the backend.
This commit is contained in:
@@ -0,0 +1,155 @@
|
||||
/// 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user