Files
dms-chat/DaemonStatusSettings.qml
SalastilandClaude Sonnet 5 eeed38da6e Sneedchat reconnect/attachments, optimistic sends, scroll/video fixes, plugin status indicator
- Sneedchat: detect a reconnect that silently lands as a guest (server's
  own can_send permission flag) and force a session refresh + relogin;
  add an idle-read timeout so a dead websocket is actually noticed
- Sneedchat: multipart file upload support in http.rs, wired into
  sendMessage's attachment path (qu.ax-hosted images/video)
- Discord-style optimistic message sends: dim pending messages, resolve
  to the real server echo, turn red and offer Retry on failure/timeout
- MessageList: fix scroll-to-end/New-divider landing, anchored-scroll
  reliability during scrollbar drags, and BBCode [img][/img] tags
  merging into one URL when posted back-to-back
- MediaEmbed: real audio output device, working scrub-bar seek/pan,
  restart-from-start on a finished video
- DaemonStatusSettings: chatd connection indicator + build (git branch)
  badge for DMS's plugin manager pane
- BufferList: scope the "+" join-channel affordance to IRC accounts only
- DisplaySettingsPanel: new toggle for content-sniffing unrecognized
  media links

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-08-12 14:58:35 -04:00

202 lines
7.1 KiB
QML

import QtQuick
import QtQuick.Layouts
import Quickshell.Io
import qs.Common
import qs.Widgets
// Registered as plugin.json's "settings" key - the only thing shown in
// DMS's global Settings -> Plugins -> Dank Chat area, by explicit user
// direction (this pane started empty and gets exactly what's asked for,
// nothing assumed beyond it). Per-protocol chat settings (message
// filters, connection defaults, SASL, etc.) live in ChatSettings.qml
// instead, reachable from inside the chat panel's own three-dot menu -
// "is the background daemon actually running" is a different, always-
// relevant concern from any of that.
//
// DMS's own PluginsTab (see PluginListItem.qml's settingsLoader) injects
// `pluginService` into any loaded settings component that declares the
// property, the same way it does for every other plugin's "settings" -
// no manual embedding needed here, unlike ChatSettings.qml's in-panel
// case (which isn't reached through this normal path at all).
Item {
id: root
property var pluginService: null
property bool chatdConnected: false
function refreshStatus() {
root.chatdConnected = root.pluginService ? !!root.pluginService.getGlobalVar("dankChat", "chatdConnected", false) : false;
}
Component.onCompleted: {
root.refreshStatus();
root.checkGitBranch();
}
onPluginServiceChanged: {
root.refreshStatus();
root.checkGitBranch();
}
// Which checkout this running plugin is actually loaded from - see
// /home/ultra/.claude memory on the development-branch workflow:
// "master" is the stable build users should be running day to day,
// "development" is where bug fixes/new features land first. Read live
// via git rather than baked in at some build step, since this plugin
// has no such step at all (the QML is interpreted directly from
// whatever's currently checked out) - so this always reflects the
// real current state, including mid-session branch switches once the
// plugin's settings page is reopened.
readonly property string pluginDir: {
if (root.pluginService) {
const p = root.pluginService.getPluginPath("dankChat");
if (p)
return p.endsWith("/") ? p : p + "/";
}
// Fallback matches ChatDaemon.qml's own pluginDir - this settings
// pane's QML file lives in the plugin's own directory regardless of
// whether pluginService.getPluginPath() comes through, since the
// component is loaded directly off disk either way.
const dirUrl = Qt.resolvedUrl(".").toString();
return dirUrl.replace(/^file:\/\//, "");
}
property string gitBranch: ""
readonly property string buildLabel: {
if (root.gitBranch === "master")
return "Production";
if (root.gitBranch === "development")
return "Development";
if (root.gitBranch === "")
return "Unknown";
return root.gitBranch; // some other branch - show it verbatim rather than guessing
}
readonly property color buildLabelColor: {
if (root.gitBranch === "master")
return Theme.primary;
if (root.gitBranch === "development")
return Theme.warning;
return Theme.surfaceVariantText;
}
function checkGitBranch() {
if (root.pluginDir && !gitBranchProcess.running)
gitBranchProcess.running = true;
}
onPluginDirChanged: root.checkGitBranch()
Process {
id: gitBranchProcess
running: false
command: ["git", "-C", root.pluginDir, "rev-parse", "--abbrev-ref", "HEAD"]
stdout: StdioCollector {
onStreamFinished: root.gitBranch = (text || "").trim()
}
}
// getGlobalVar() is a plain function call, not a property read - QML's
// binding dependency tracking can't auto-refresh from that, so this
// has to explicitly re-poll on the one signal that means the value
// might have changed instead of binding chatdConnected directly.
Connections {
target: root.pluginService
enabled: root.pluginService !== null
function onGlobalVarChanged(changedPluginId, varName) {
if (changedPluginId === "dankChat" && varName === "chatdConnected")
root.refreshStatus();
}
}
implicitHeight: content.implicitHeight
height: implicitHeight
ColumnLayout {
id: content
width: parent.width
spacing: Theme.spacingM
StyledText {
Layout.fillWidth: true
text: "Chat Daemon"
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Medium
color: Theme.surfaceText
}
StyledText {
Layout.fillWidth: true
text: "The background process (chatd) that actually talks to IRC/XMPP servers on Dank Chat's behalf. If it's not running, no account can connect regardless of what's configured for it."
wrapMode: Text.WordWrap
color: Theme.surfaceVariantText
}
RowLayout {
Layout.fillWidth: true
spacing: Theme.spacingS
Rectangle {
width: 10
height: 10
radius: 5
color: root.chatdConnected ? Theme.primary : Theme.error
}
StyledText {
text: root.chatdConnected ? "Running" : "Not running"
color: Theme.surfaceText
font.weight: Font.Medium
}
}
Rectangle {
Layout.fillWidth: true
height: 1
color: Theme.outlineMedium
}
StyledText {
Layout.fillWidth: true
text: "Build"
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Medium
color: Theme.surfaceText
}
RowLayout {
Layout.fillWidth: true
spacing: Theme.spacingS
Rectangle {
radius: Theme.cornerRadius
color: Theme.withAlpha(root.buildLabelColor, 0.18)
implicitWidth: buildLabelText.implicitWidth + Theme.spacingM * 2
implicitHeight: buildLabelText.implicitHeight + Theme.spacingXS * 2
StyledText {
id: buildLabelText
anchors.centerIn: parent
text: root.buildLabel
color: root.buildLabelColor
font.weight: Font.Bold
font.pixelSize: Theme.fontSizeSmall
}
}
StyledText {
text: root.gitBranch ? ("(" + root.gitBranch + " branch)") : ""
visible: text !== ""
color: Theme.surfaceVariantText
font.pixelSize: Theme.fontSizeSmall
}
}
DankButton {
Layout.alignment: Qt.AlignLeft
text: "Restart daemon"
onClicked: {
if (root.pluginService)
root.pluginService.setGlobalVar("dankChat", "restartNonce", Date.now());
}
}
}
}