sidebar: the garden's micro-view — Lens service, vault card in the right panel
The vault card lives where notifications were — header with sync pill (clean/ahead/behind/dirty), today's note entry, five freshest notes, cold start and push rows — over the notification list. Lens.qml owns souveraine-lens the way Face owns the rig: one socket, event lines up, no vault access of its own. Kiyoshi's slash grammar and tool registry are noted for the fuller app; the micro view only opens and plants.
This commit is contained in:
parent
658217e72e
commit
cb7dd73d2b
5 changed files with 543 additions and 0 deletions
|
|
@ -63,6 +63,7 @@ modules/souveraine/windowSheet/PowerMenu.qml souveraine/modules/souveraine/windo
|
||||||
modules/souveraine/windowSheet/PowerOptionRow.qml souveraine/modules/souveraine/windowSheet/PowerOptionRow.qml
|
modules/souveraine/windowSheet/PowerOptionRow.qml souveraine/modules/souveraine/windowSheet/PowerOptionRow.qml
|
||||||
modules/souveraine/windowSheet/qmldir souveraine/modules/souveraine/windowSheet/qmldir
|
modules/souveraine/windowSheet/qmldir souveraine/modules/souveraine/windowSheet/qmldir
|
||||||
services/Face.qml souveraine/services/Face.qml
|
services/Face.qml souveraine/services/Face.qml
|
||||||
|
services/Lens.qml souveraine/services/Lens.qml
|
||||||
services/FingerprintPreview.qml souveraine/services/FingerprintPreview.qml
|
services/FingerprintPreview.qml souveraine/services/FingerprintPreview.qml
|
||||||
services/ViewtopControl.qml souveraine/services/ViewtopControl.qml
|
services/ViewtopControl.qml souveraine/services/ViewtopControl.qml
|
||||||
services/ZoneTransition.qml souveraine/services/ZoneTransition.qml
|
services/ZoneTransition.qml souveraine/services/ZoneTransition.qml
|
||||||
|
|
@ -93,6 +94,8 @@ modules/ii/sidebarLeft/AiChat.qml souveraine/modules/ii/sidebarLeft/AiCh
|
||||||
modules/ii/sidebarRight/SidebarRight.qml souveraine/modules/ii/sidebarRight/SidebarRight.qml
|
modules/ii/sidebarRight/SidebarRight.qml souveraine/modules/ii/sidebarRight/SidebarRight.qml
|
||||||
modules/ii/sidebarRight/SidebarRightContent.qml souveraine/modules/ii/sidebarRight/SidebarRightContent.qml
|
modules/ii/sidebarRight/SidebarRightContent.qml souveraine/modules/ii/sidebarRight/SidebarRightContent.qml
|
||||||
modules/ii/sidebarRight/QuickSliders.qml souveraine/modules/ii/sidebarRight/QuickSliders.qml
|
modules/ii/sidebarRight/QuickSliders.qml souveraine/modules/ii/sidebarRight/QuickSliders.qml
|
||||||
|
modules/ii/sidebarRight/CenterWidgetGroup.qml souveraine/modules/ii/sidebarRight/CenterWidgetGroup.qml
|
||||||
|
modules/ii/sidebarRight/lens/LensWidget.qml souveraine/modules/ii/sidebarRight/lens/LensWidget.qml
|
||||||
modules/ii/sidebarRight/volumeMixer/VolumeDialogContent.qml souveraine/modules/ii/sidebarRight/volumeMixer/VolumeDialogContent.qml
|
modules/ii/sidebarRight/volumeMixer/VolumeDialogContent.qml souveraine/modules/ii/sidebarRight/volumeMixer/VolumeDialogContent.qml
|
||||||
modules/ii/bar/UtilButtons.qml souveraine/modules/ii/bar/UtilButtons.qml
|
modules/ii/bar/UtilButtons.qml souveraine/modules/ii/bar/UtilButtons.qml
|
||||||
modules/common/Config.qml souveraine/modules/common/Config.qml
|
modules/common/Config.qml souveraine/modules/common/Config.qml
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
import qs.modules.common
|
||||||
|
import qs.modules.common.widgets
|
||||||
|
import qs.services
|
||||||
|
import qs.modules.ii.sidebarRight.notifications
|
||||||
|
import qs.modules.ii.sidebarRight.lens
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: root
|
||||||
|
clip: true
|
||||||
|
radius: Appearance.rounding.normal
|
||||||
|
color: Appearance.colors.colLayer1
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 5
|
||||||
|
spacing: 5
|
||||||
|
|
||||||
|
LensWidget {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.alignment: Qt.AlignTop
|
||||||
|
}
|
||||||
|
|
||||||
|
NotificationList {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
281
surfaces/quickshell/modules/ii/sidebarRight/lens/LensWidget.qml
Normal file
281
surfaces/quickshell/modules/ii/sidebarRight/lens/LensWidget.qml
Normal file
|
|
@ -0,0 +1,281 @@
|
||||||
|
// The garden's micro-view — the vault card in the right panel.
|
||||||
|
//
|
||||||
|
// The micro view is a window into the lens window, not a second editor:
|
||||||
|
// it shows the freshest notes, the sync truth, a daily note entry point
|
||||||
|
// and a quick capture — and every tap either opens the garden at that
|
||||||
|
// note or plants one in it. The full app is the lens window itself; this
|
||||||
|
// is the pulse.
|
||||||
|
import qs.services
|
||||||
|
import qs.modules.common
|
||||||
|
import qs.modules.common.widgets
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
implicitHeight: column.implicitHeight
|
||||||
|
property var shown: Lens.joined ? Lens.notes.slice(0, 5) : []
|
||||||
|
|
||||||
|
function dailyRel() {
|
||||||
|
return new Date().toISOString().slice(0, 10) + ".md";
|
||||||
|
}
|
||||||
|
|
||||||
|
function openDaily() {
|
||||||
|
if (!Lens.joined) {
|
||||||
|
Lens.join();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const rel = root.dailyRel();
|
||||||
|
const exists = Lens.notes.some(n => n.rel === rel);
|
||||||
|
if (exists)
|
||||||
|
Lens.openNote(rel);
|
||||||
|
else
|
||||||
|
Lens.createNote(rel.replace(/\.md$/, ""));
|
||||||
|
}
|
||||||
|
|
||||||
|
function relTime(mtime) {
|
||||||
|
const s = Math.max(0, Date.now() / 1000 - mtime);
|
||||||
|
if (s < 60) return Translation.tr("just now");
|
||||||
|
if (s < 3600) return Translation.tr("%1m ago").arg(Math.floor(s / 60));
|
||||||
|
if (s < 86400) return Translation.tr("%1h ago").arg(Math.floor(s / 3600));
|
||||||
|
if (s < 604800) return Translation.tr("%1d ago").arg(Math.floor(s / 86400));
|
||||||
|
return new Date(mtime * 1000).toLocaleDateString(Qt.locale(), "dd MMM");
|
||||||
|
}
|
||||||
|
|
||||||
|
onVisibleChanged: {
|
||||||
|
if (root.visible && Lens.joined)
|
||||||
|
Lens.requestState();
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: column
|
||||||
|
anchors.fill: parent
|
||||||
|
spacing: 4
|
||||||
|
|
||||||
|
// ── header ──
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 6
|
||||||
|
|
||||||
|
MaterialSymbol {
|
||||||
|
text: "hub"
|
||||||
|
iconSize: Appearance.font.pixelSize.larger
|
||||||
|
color: Appearance.colors.colPrimary
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
font.pixelSize: Appearance.font.pixelSize.larger
|
||||||
|
color: Appearance.colors.colOnLayer1
|
||||||
|
text: Translation.tr("Garden")
|
||||||
|
}
|
||||||
|
StyledText {
|
||||||
|
visible: Lens.joined && Lens.head.length > 0
|
||||||
|
font.pixelSize: Appearance.font.pixelSize.small
|
||||||
|
color: Appearance.colors.colOnLayer1Inactive
|
||||||
|
text: Lens.branch + " @" + Lens.head
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: syncPill
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
radius: 8
|
||||||
|
color: Appearance.colors.colLayer0
|
||||||
|
implicitHeight: pillText.implicitHeight + 6
|
||||||
|
implicitWidth: pillText.implicitWidth + 14
|
||||||
|
|
||||||
|
Row {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: 5
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
width: 7
|
||||||
|
height: 7
|
||||||
|
radius: width / 2
|
||||||
|
color: root.pillColor
|
||||||
|
}
|
||||||
|
StyledText {
|
||||||
|
id: pillText
|
||||||
|
font.pixelSize: Appearance.font.pixelSize.small
|
||||||
|
color: Appearance.colors.colOnLayer0
|
||||||
|
text: Lens.stateText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: 2
|
||||||
|
height: 1
|
||||||
|
color: Appearance.colors.colLayer0Border
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── the daily note ──
|
||||||
|
Item {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: 2
|
||||||
|
implicitHeight: dailyRow.implicitHeight
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
id: dailyRow
|
||||||
|
anchors.fill: parent
|
||||||
|
spacing: 6
|
||||||
|
|
||||||
|
MaterialSymbol {
|
||||||
|
text: "today"
|
||||||
|
iconSize: Appearance.font.pixelSize.normal
|
||||||
|
color: Appearance.colors.colOnLayer1Inactive
|
||||||
|
}
|
||||||
|
StyledText {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
font.pixelSize: Appearance.font.pixelSize.normal
|
||||||
|
color: Appearance.colors.colOnLayer1
|
||||||
|
text: Translation.tr("Today's note")
|
||||||
|
}
|
||||||
|
StyledText {
|
||||||
|
font.pixelSize: Appearance.font.pixelSize.small
|
||||||
|
color: Appearance.colors.colPrimary
|
||||||
|
text: Translation.tr("open")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: root.openDaily()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── fresh notes ──
|
||||||
|
Repeater {
|
||||||
|
model: root.shown
|
||||||
|
|
||||||
|
delegate: Item {
|
||||||
|
required property var modelData
|
||||||
|
required property int index
|
||||||
|
Layout.fillWidth: true
|
||||||
|
implicitHeight: noteRow.implicitHeight
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
id: noteRow
|
||||||
|
anchors.fill: parent
|
||||||
|
spacing: 6
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
width: 4
|
||||||
|
height: 4
|
||||||
|
radius: width / 2
|
||||||
|
color: index === 0 ? Appearance.colors.colPrimary : Appearance.colors.colOnLayer1Inactive
|
||||||
|
}
|
||||||
|
StyledText {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
elide: Text.ElideRight
|
||||||
|
font.pixelSize: Appearance.font.pixelSize.normal
|
||||||
|
color: Appearance.colors.colOnLayer1
|
||||||
|
text: modelData.title
|
||||||
|
}
|
||||||
|
StyledText {
|
||||||
|
font.pixelSize: Appearance.font.pixelSize.small
|
||||||
|
color: Appearance.colors.colOnLayer1Inactive
|
||||||
|
text: root.relTime(modelData.mtime)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: Lens.openNote(modelData.rel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── cold card ──
|
||||||
|
Item {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
visible: !Lens.joined
|
||||||
|
implicitHeight: coldRow.implicitHeight
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
id: coldRow
|
||||||
|
anchors.fill: parent
|
||||||
|
spacing: 6
|
||||||
|
|
||||||
|
MaterialSymbol {
|
||||||
|
text: "crop_free"
|
||||||
|
iconSize: Appearance.font.pixelSize.normal
|
||||||
|
color: Appearance.colors.colOnLayer1Inactive
|
||||||
|
}
|
||||||
|
StyledText {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
font.pixelSize: Appearance.font.pixelSize.normal
|
||||||
|
color: Appearance.colors.colOnLayer1Inactive
|
||||||
|
text: Translation.tr("The garden is closed")
|
||||||
|
}
|
||||||
|
StyledText {
|
||||||
|
font.pixelSize: Appearance.font.pixelSize.normal
|
||||||
|
color: Appearance.colors.colPrimary
|
||||||
|
text: Translation.tr("Open")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: Lens.join()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── push the truth ──
|
||||||
|
Item {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
visible: Lens.joined && (Lens.syncState === "ahead" || Lens.syncState === "diverged" || Lens.syncState === "dirty")
|
||||||
|
implicitHeight: pushRow.implicitHeight
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
id: pushRow
|
||||||
|
anchors.fill: parent
|
||||||
|
spacing: 6
|
||||||
|
|
||||||
|
MaterialSymbol {
|
||||||
|
text: "upload"
|
||||||
|
iconSize: Appearance.font.pixelSize.normal
|
||||||
|
color: Appearance.colors.colPrimary
|
||||||
|
}
|
||||||
|
StyledText {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
font.pixelSize: Appearance.font.pixelSize.normal
|
||||||
|
color: Appearance.colors.colOnLayer1
|
||||||
|
text: Translation.tr("The garden is ahead — push it")
|
||||||
|
}
|
||||||
|
StyledText {
|
||||||
|
font.pixelSize: Appearance.font.pixelSize.normal
|
||||||
|
color: Appearance.colors.colPrimary
|
||||||
|
text: Translation.tr("Push")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: Lens.syncNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pill color: one dot, one meaning.
|
||||||
|
readonly property color pillColor: {
|
||||||
|
if (!Lens.joined) return Appearance.colors.colOnLayer1Inactive;
|
||||||
|
switch (Lens.syncState) {
|
||||||
|
case "clean": return Appearance.colors.colPrimary;
|
||||||
|
case "dirty": return Appearance.m3colors.m3error;
|
||||||
|
case "ahead":
|
||||||
|
case "diverged": return Appearance.m3colors.m3tertiary;
|
||||||
|
case "behind": return Appearance.m3colors.m3error;
|
||||||
|
default: return Appearance.colors.colOnLayer1Inactive;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
228
surfaces/quickshell/services/Lens.qml
Normal file
228
surfaces/quickshell/services/Lens.qml
Normal file
|
|
@ -0,0 +1,228 @@
|
||||||
|
// The garden on the limb — the vault's micro-view service.
|
||||||
|
//
|
||||||
|
// One more process owned the Face way: this holds a `souveraine-lens`
|
||||||
|
// process with `--ipc` at a socket only the shell ever touches. The lens
|
||||||
|
// process has no connection to the server, and this service has no
|
||||||
|
// connection to the vault — the micro-state the widget renders arrives
|
||||||
|
// over the same line protocol the lens already speaks, one JSON object
|
||||||
|
// per line on stdout. The seam stays: nobody reads the vault but the lens
|
||||||
|
// and the user.
|
||||||
|
//
|
||||||
|
// `joined` is the whole state, as with the face. While it is false the
|
||||||
|
// garden window is not on glass and no webview holds memory; the widget
|
||||||
|
// shows a cold card until the user opens it.
|
||||||
|
pragma Singleton
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Io
|
||||||
|
import qs
|
||||||
|
import qs.modules.common
|
||||||
|
|
||||||
|
Singleton {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property bool joined: false
|
||||||
|
property string socketPath: Quickshell.env("XDG_RUNTIME_DIR") + "/souveraine/lens.sock"
|
||||||
|
property string vaultDir: Quickshell.env("HOME") + "/vault"
|
||||||
|
readonly property string vaultRel: vaultDir.replace(Quickshell.env("HOME"), "~")
|
||||||
|
|
||||||
|
// The micro-state, exactly as the lens reports it.
|
||||||
|
property var notes: []
|
||||||
|
property int noteCount: 0
|
||||||
|
property int dirty: 0
|
||||||
|
property int ahead: 0
|
||||||
|
property int behind: 0
|
||||||
|
property string head: ""
|
||||||
|
property string branch: ""
|
||||||
|
property bool hasRemote: true
|
||||||
|
property string lastError: ""
|
||||||
|
|
||||||
|
// cold until the first state answers; then one of clean / ahead /
|
||||||
|
// behind / diverged / dirty — computed, so there is one place the
|
||||||
|
// widget reads.
|
||||||
|
property string syncState: "cold"
|
||||||
|
readonly property string stateText: {
|
||||||
|
if (!root.joined) return "offline";
|
||||||
|
switch (root.syncState) {
|
||||||
|
case "clean": return root.noteCount + " notes · clean";
|
||||||
|
case "ahead": return root.noteCount + " notes · push " + root.ahead;
|
||||||
|
case "behind": return root.noteCount + " notes · pull " + root.behind;
|
||||||
|
case "diverged": return root.noteCount + " notes · push " + root.ahead + " pull " + root.behind;
|
||||||
|
case "dirty": return root.noteCount + " notes · " + root.dirty + " uncommitted";
|
||||||
|
default: return root.noteCount + " notes";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onJoinedChanged: {
|
||||||
|
if (root.joined)
|
||||||
|
root.requestState();
|
||||||
|
}
|
||||||
|
|
||||||
|
function join() {
|
||||||
|
if (root.joined)
|
||||||
|
return;
|
||||||
|
host.running = true;
|
||||||
|
root.joined = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function leave() {
|
||||||
|
if (!root.joined)
|
||||||
|
return;
|
||||||
|
root._send({ op: "quit" });
|
||||||
|
host.running = false;
|
||||||
|
root.joined = false;
|
||||||
|
root.syncState = "cold";
|
||||||
|
root.notes = [];
|
||||||
|
root.noteCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggle() {
|
||||||
|
if (root.joined)
|
||||||
|
root.leave();
|
||||||
|
else
|
||||||
|
root.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
function requestState() {
|
||||||
|
root._send({ op: "state" });
|
||||||
|
}
|
||||||
|
|
||||||
|
function openNote(rel) {
|
||||||
|
root._send({ op: "open", rel: rel });
|
||||||
|
}
|
||||||
|
|
||||||
|
function createNote(title) {
|
||||||
|
root._send({ op: "create", title: title });
|
||||||
|
}
|
||||||
|
|
||||||
|
// The page owns its sync flow; the shell only pulls the trigger.
|
||||||
|
function syncNow() {
|
||||||
|
root._eval("window.__lens.sync()");
|
||||||
|
}
|
||||||
|
|
||||||
|
function _eval(script) {
|
||||||
|
root._send({ op: "eval", script: script });
|
||||||
|
}
|
||||||
|
|
||||||
|
function _send(msg) {
|
||||||
|
if (sock.connected)
|
||||||
|
sock.write(JSON.stringify(msg) + "\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: host
|
||||||
|
command: ["souveraine-lens",
|
||||||
|
"--vault", root.vaultDir,
|
||||||
|
"--ipc", root.socketPath,
|
||||||
|
"--app-id", "org.souveraine.lens",
|
||||||
|
"--title", "Garden"]
|
||||||
|
stdout: SplitParser {
|
||||||
|
splitMarker: "\n"
|
||||||
|
onRead: line => {
|
||||||
|
let msg;
|
||||||
|
try {
|
||||||
|
msg = JSON.parse(line);
|
||||||
|
} catch (e) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (msg.event === "state" && msg.state) {
|
||||||
|
root.notes = msg.state.notes ?? [];
|
||||||
|
root.noteCount = msg.state.status?.note_count ?? root.notes.length;
|
||||||
|
root.dirty = msg.state.status?.dirty ?? 0;
|
||||||
|
root.ahead = msg.state.status?.ahead ?? 0;
|
||||||
|
root.behind = msg.state.status?.behind ?? 0;
|
||||||
|
root.head = msg.state.status?.head ?? "";
|
||||||
|
root.branch = msg.state.status?.branch ?? "";
|
||||||
|
root.hasRemote = (msg.state.status?.remote?.length ?? 0) > 0;
|
||||||
|
root.syncState = root.dirty > 0
|
||||||
|
? "dirty"
|
||||||
|
: root.ahead > 0 && root.behind > 0
|
||||||
|
? "diverged"
|
||||||
|
: root.ahead > 0 ? "ahead"
|
||||||
|
: root.behind > 0 ? "behind" : "clean";
|
||||||
|
}
|
||||||
|
else if (msg.event === "synced")
|
||||||
|
refreshTimer.restart();
|
||||||
|
else if (msg.event === "note_opened" || msg.event === "note_created" || msg.event === "note_saved")
|
||||||
|
refreshTimer.restart();
|
||||||
|
else if (msg.event === "console")
|
||||||
|
console.log("[lens]", msg.level ?? "", msg.text ?? "");
|
||||||
|
else
|
||||||
|
console.log("[lens]", line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onExited: {
|
||||||
|
root.joined = false;
|
||||||
|
root.syncState = "cold";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// One refresh after a burst of events, not one per line.
|
||||||
|
Timer {
|
||||||
|
id: refreshTimer
|
||||||
|
interval: 300
|
||||||
|
repeat: false
|
||||||
|
onTriggered: root.requestState()
|
||||||
|
}
|
||||||
|
|
||||||
|
// The garden's heartbeat while it is joined; cheap — one scan.
|
||||||
|
Timer {
|
||||||
|
id: pollTimer
|
||||||
|
running: root.joined
|
||||||
|
interval: 60000
|
||||||
|
repeat: true
|
||||||
|
triggeredOnStart: true
|
||||||
|
onTriggered: root.requestState()
|
||||||
|
}
|
||||||
|
|
||||||
|
Socket {
|
||||||
|
id: sock
|
||||||
|
path: root.socketPath
|
||||||
|
connected: root.joined
|
||||||
|
|
||||||
|
onConnectionStateChanged: {
|
||||||
|
if (sock.connected)
|
||||||
|
root.requestState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reachable by name: the agent summons the garden the same way it
|
||||||
|
// summons the face, without guessing.
|
||||||
|
IpcHandler {
|
||||||
|
target: "lens"
|
||||||
|
|
||||||
|
function open(rel: string): void {
|
||||||
|
if (!root.joined)
|
||||||
|
root.join();
|
||||||
|
root.openNote(rel);
|
||||||
|
}
|
||||||
|
|
||||||
|
function create(title: string): void {
|
||||||
|
if (!root.joined)
|
||||||
|
root.join();
|
||||||
|
root.createNote(title);
|
||||||
|
}
|
||||||
|
|
||||||
|
function sync(): void {
|
||||||
|
root.syncNow();
|
||||||
|
}
|
||||||
|
|
||||||
|
function join(): void {
|
||||||
|
root.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
function leave(): void {
|
||||||
|
root.leave();
|
||||||
|
}
|
||||||
|
|
||||||
|
function status(): string {
|
||||||
|
return JSON.stringify({
|
||||||
|
joined: root.joined,
|
||||||
|
syncState: root.syncState,
|
||||||
|
noteCount: root.noteCount,
|
||||||
|
head: root.head
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -35,6 +35,7 @@ singleton KeyringStorage 1.0 KeyringStorage.qml
|
||||||
singleton LatexRenderer 1.0 LatexRenderer.qml
|
singleton LatexRenderer 1.0 LatexRenderer.qml
|
||||||
singleton LauncherApps 1.0 LauncherApps.qml
|
singleton LauncherApps 1.0 LauncherApps.qml
|
||||||
singleton LauncherSearch 1.0 LauncherSearch.qml
|
singleton LauncherSearch 1.0 LauncherSearch.qml
|
||||||
|
singleton Lens 1.0 Lens.qml
|
||||||
singleton LockContentPolicy 1.0 LockContentPolicy.qml
|
singleton LockContentPolicy 1.0 LockContentPolicy.qml
|
||||||
singleton MaterialThemeLoader 1.0 MaterialThemeLoader.qml
|
singleton MaterialThemeLoader 1.0 MaterialThemeLoader.qml
|
||||||
singleton MprisController 1.0 MprisController.qml
|
singleton MprisController 1.0 MprisController.qml
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue