qs: add notecard

This commit is contained in:
2026-03-06 15:09:25 +01:00
parent ca514ac2fa
commit f9facdd61b
8 changed files with 499 additions and 35 deletions
@@ -4,10 +4,10 @@ import QtQuick.Layouts
import Quickshell
import Quickshell.Bluetooth
import Quickshell.Wayland
import qs.Constants
import qs.Services
import qs.Components
import qs.Constants
import qs.Modules.Sidebar.Misc
import qs.Services
ColumnLayout {
spacing: Style.marginM
@@ -119,7 +119,6 @@ ColumnLayout {
ColumnLayout {
Layout.alignment: Qt.AlignHCenter
spacing: Style.marginL
visible: {
if (!BluetoothService.adapter || !BluetoothService.adapter.discovering || !Bluetooth.devices)
return false;
@@ -138,7 +137,7 @@ ColumnLayout {
}
UText {
text: "Pairing Mode"
text: "Scanning for devices..."
pointSize: Style.fontSizeM
color: Colors.mOnSurfaceVariant
Layout.alignment: Qt.AlignHCenter
@@ -8,7 +8,7 @@ import qs.Services
UBox {
id: root
property string currentPanel: "bluetooth" // "bluetooth", "wifi"
property string currentPanel: ShellState.leftSiderbarTab // "bluetooth", "wifi"
implicitHeight: contentLoader.implicitHeight + toggleGroup.implicitHeight + Style.marginXS * 2 + Style.marginS * 2
@@ -21,13 +21,14 @@ UBox {
Layout.fillWidth: true
Rectangle {
// border.color: Colors.mOutline
id: toggleGroup
Layout.preferredWidth: Style.baseWidgetSize * 2.8
Layout.preferredHeight: Style.baseWidgetSize
radius: Math.min(Style.radiusS, height / 2)
color: Colors.mSurface
// border.color: Colors.mOutline
Row {
anchors.fill: parent
@@ -41,18 +42,6 @@ UBox {
radius: Math.min(Style.radiusS, height / 2)
color: root.currentPanel === "bluetooth" ? Colors.mPrimary : "transparent"
Behavior on width {
NumberAnimation {
duration: 250
easing.type: Easing.OutCubic
}
}
Behavior on color {
ColorAnimation {
duration: 200
}
}
UIcon {
anchors.centerIn: parent
iconName: "bluetooth"
@@ -63,14 +52,32 @@ UBox {
ColorAnimation {
duration: 200
}
}
}
MouseArea {
anchors.fill: parent
onClicked: root.currentPanel = "bluetooth"
onClicked: ShellState.leftSiderbarTab = "bluetooth"
cursorShape: Qt.PointingHandCursor
}
Behavior on width {
NumberAnimation {
duration: 250
easing.type: Easing.OutCubic
}
}
Behavior on color {
ColorAnimation {
duration: 200
}
}
}
Rectangle {
@@ -81,18 +88,6 @@ UBox {
radius: Math.min(Style.radiusS, height / 2)
color: root.currentPanel === "wifi" ? Colors.mPrimary : "transparent"
Behavior on width {
NumberAnimation {
duration: 250
easing.type: Easing.OutCubic
}
}
Behavior on color {
ColorAnimation {
duration: 200
}
}
UIcon {
anchors.centerIn: parent
iconName: "wifi"
@@ -103,16 +98,36 @@ UBox {
ColorAnimation {
duration: 200
}
}
}
MouseArea {
anchors.fill: parent
onClicked: root.currentPanel = "wifi"
onClicked: ShellState.leftSiderbarTab = "wifi"
cursorShape: Qt.PointingHandCursor
}
Behavior on width {
NumberAnimation {
duration: 250
easing.type: Easing.OutCubic
}
}
Behavior on color {
ColorAnimation {
duration: 200
}
}
}
}
}
Item {
@@ -147,7 +162,9 @@ UBox {
}
colorFg: Colors.mGreen
}
}
}
Component {
@@ -171,8 +188,11 @@ UBox {
onClicked: NetworkService.scan()
colorFg: Colors.mGreen
}
}
}
}
}
@@ -186,7 +206,6 @@ UBox {
Layout.fillWidth: true
Layout.fillHeight: true
sourceComponent: currentPanel === "bluetooth" ? bluetoothComponent : wifiComponent
Component {
@@ -196,6 +215,7 @@ UBox {
anchors.fill: parent
anchors.margins: Style.marginS
}
}
Component {
@@ -205,7 +225,11 @@ UBox {
anchors.fill: parent
anchors.margins: Style.marginS
}
}
}
}
}
@@ -0,0 +1,160 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import qs.Components
import qs.Constants
import qs.Services
import qs.Utils
Rectangle {
id: root
property real calculatedHeight: headerBox.implicitHeight + scrollView.implicitHeight + Style.marginL * 2 + Style.marginM
property real contentPreferredHeight: Math.min(root.height, Math.ceil(calculatedHeight))
property real layoutWidth: Math.max(1, root.width - Style.marginL * 2)
color: "transparent"
ColumnLayout {
id: mainColumn
anchors.fill: parent
spacing: Style.marginM
// Header
UBox {
id: headerBox
Layout.fillWidth: true
implicitHeight: header.implicitHeight + Style.marginM * 2
ColumnLayout {
id: header
anchors.fill: parent
anchors.margins: Style.marginM
spacing: Style.marginM
RowLayout {
Layout.fillWidth: true
UIcon {
iconName: "notes"
iconSize: Style.fontSizeXXL
color: Colors.mPrimary
}
UText {
text: "Notes" + " (" + NotesService.notesModel.count + ")"
pointSize: Style.fontSizeL
font.weight: Style.fontWeightBold
color: Colors.mOnSurface
Layout.fillWidth: true
}
UIconButton {
iconName: "plus"
baseSize: Style.baseWidgetSize * 0.8
colorFg: Colors.mGreen
onClicked: NotesService.createNote()
}
}
}
}
Item {
Layout.fillWidth: true
Layout.fillHeight: true
NScrollView {
id: scrollView
anchors.fill: parent
implicitHeight: notesColumn.implicitHeight
ColumnLayout {
id: notesColumn
width: scrollView.width
spacing: Style.marginM
Repeater {
model: NotesService.notesModel
delegate: UBox {
property color accentColor: Colors.cavaList[model.colorIdx % Colors.cavaList.length]
width: notesColumn.width
implicitHeight: noteLayout.implicitHeight + Style.marginM * 2
Rectangle {
anchors.left: parent.left
anchors.top: parent.top
anchors.bottom: parent.bottom
width: Style.marginS
color: parent.accentColor
radius: Style.radiusS
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton
onClicked: NotesService.openNote(model.noteId)
}
FileView {
id: fileView
path: NotesService.notesDir + "/" + model.noteId + ".txt"
watchChanges: true
onFileChanged: reload()
}
RowLayout {
id: noteLayout
anchors.fill: parent
anchors.margins: Style.marginM
anchors.leftMargin: Style.marginM + Style.marginS
UText {
Layout.fillWidth: true
text: {
var t = fileView.text();
if (!t)
return "(empty note)";
return t.trim().split('\n').slice(0, 5).join('\n');
}
wrapMode: Text.Wrap
elide: Text.ElideRight
maximumLineCount: 5
}
UIconButton {
iconName: "trash"
baseSize: Style.baseWidgetSize * 0.8
colorFg: Colors.mError
onClicked: NotesService.deleteNote(model.noteId)
}
}
}
}
}
}
}
}
}
@@ -491,7 +491,7 @@ Rectangle {
readonly property real swipeDismissThreshold: Math.max(110, width * 0.3)
readonly property int removeAnimationDuration: Style.animationNormal
readonly property int notificationTextFormat: notificationDelegate.isExpanded ? Text.MarkdownText : Text.StyledText
readonly property real actionButtonSize: Style.baseWidgetSize * 0.7
readonly property real actionButtonSize: Style.baseWidgetSize * 0.8
readonly property real buttonClusterWidth: notificationDelegate.actionButtonSize * 2 + Style.marginXS
readonly property real iconSize: 40
// Parse actions safely
@@ -0,0 +1,144 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import qs.Components
import qs.Constants
import qs.Services
Item {
id: root
property string currentPanel: ShellState.rightSiderbarTab // "notifications", "notes"
ColumnLayout {
anchors.fill: parent
spacing: Style.marginM
Rectangle {
id: toggleGroup
Layout.alignment: Qt.AlignHCenter
Layout.preferredWidth: Style.baseWidgetSize * 2.8
Layout.preferredHeight: Style.baseWidgetSize
radius: Math.min(Style.radiusS, height / 2)
color: Colors.mSurface
Row {
anchors.fill: parent
spacing: Style.marginS / 2
Rectangle {
id: btnNotifications
width: root.currentPanel === "notifications" ? (parent.width - parent.spacing) * 0.65 : (parent.width - parent.spacing) * 0.35
height: parent.height
radius: Math.min(Style.radiusS, height / 2)
color: root.currentPanel === "notifications" ? Colors.mPrimary : "transparent"
UIcon {
anchors.centerIn: parent
iconName: "bell"
iconSize: Style.fontSizeL
color: root.currentPanel === "notifications" ? Colors.mOnPrimary : Colors.mOnSurface
Behavior on color {
ColorAnimation {
duration: 200
}
}
}
MouseArea {
anchors.fill: parent
onClicked: ShellState.rightSiderbarTab = "notifications"
cursorShape: Qt.PointingHandCursor
}
Behavior on width {
NumberAnimation {
duration: 250
easing.type: Easing.OutCubic
}
}
Behavior on color {
ColorAnimation {
duration: 200
}
}
}
Rectangle {
id: btnNotes
width: root.currentPanel === "notes" ? (parent.width - parent.spacing) * 0.65 : (parent.width - parent.spacing) * 0.35
height: parent.height
radius: Math.min(Style.radiusS, height / 2)
color: root.currentPanel === "notes" ? Colors.mPrimary : "transparent"
UIcon {
anchors.centerIn: parent
iconName: "notes"
iconSize: Style.fontSizeL
color: root.currentPanel === "notes" ? Colors.mOnPrimary : Colors.mOnSurface
Behavior on color {
ColorAnimation {
duration: 200
}
}
}
MouseArea {
anchors.fill: parent
onClicked: ShellState.rightSiderbarTab = "notes"
cursorShape: Qt.PointingHandCursor
}
Behavior on width {
NumberAnimation {
duration: 250
easing.type: Easing.OutCubic
}
}
Behavior on color {
ColorAnimation {
duration: 200
}
}
}
}
}
Item {
Layout.fillWidth: true
Layout.fillHeight: true
NotificationHistoryCard {
anchors.fill: parent
visible: root.currentPanel === "notifications"
}
NoteCard {
anchors.fill: parent
visible: root.currentPanel === "notes"
}
}
}
}
@@ -69,7 +69,7 @@ Variants {
Layout.fillWidth: true
}
NotificationHistoryCard {
NotificationNoteToggleCard {
Layout.fillWidth: true
Layout.fillHeight: true
}
@@ -0,0 +1,131 @@
import QtQuick
import Quickshell
import Quickshell.Io
import qs.Constants
import qs.Utils
pragma Singleton
Singleton {
id: root
property string notesDir: Paths.cacheDir + "/notes"
property var notes: []
property ListModel notesModel
function loadNotes() {
listProcess.running = true;
}
function createNote() {
var id = new Date().getTime().toString();
var filePath = notesDir + "/" + id + ".txt";
// Random color index from 0 to 7
var colorIdx = Math.floor(Math.random() * 8);
createProcess.command = ["sh", "-c", "mkdir -p " + notesDir + " && echo 'New Note' > " + filePath + " && echo " + colorIdx + " > " + filePath + ".color"];
createProcess.running = true;
}
function deleteNote(id) {
var filePath = notesDir + "/" + id + ".txt";
var colorPath = notesDir + "/" + id + ".txt.color";
deleteProcess.command = ["rm", "-f", filePath, colorPath];
deleteProcess.running = true;
}
function openNote(id) {
var filePath = notesDir + "/" + id + ".txt";
openProcess.command = ["gnome-text-editor", filePath];
openProcess.running = true;
}
Component.onCompleted: {
loadNotes();
}
Process {
id: openProcess
}
Process {
id: createProcess
onExited: root.loadNotes()
}
Process {
id: deleteProcess
onExited: root.loadNotes()
}
Process {
id: listProcess
command: ["sh", "-c", "mkdir -p " + notesDir + " && ls -1 " + notesDir + " | grep '\\.txt$' || true"]
stdout: StdioCollector {
id: listCollector
onStreamFinished: {
var files = listCollector.text.split('\n');
notesModel.clear();
for (var i = 0; i < files.length; i++) {
if (files[i] === "")
continue;
var id = files[i].replace(".txt", "");
var contentFile = notesDir + "/" + files[i];
var colorFile = notesDir + "/" + files[i] + ".color";
// create an intermediate reader process
readProcessComponent.createObject(root, {
"noteId": id,
"contentFile": contentFile,
"colorFile": colorFile
}).run();
}
}
}
}
Component {
id: readProcessComponent
Process {
id: p
property string noteId
property string contentFile
property string colorFile
function run() {
running = true;
}
command: ["sh", "-c", "cat " + colorFile + " 2>/dev/null || echo 0; head -n 5 " + contentFile]
stdout: StdioCollector {
id: readCollector
onStreamFinished: {
var lines = readCollector.text.split('\n');
var colorIdx = parseInt(lines[0] || "0");
lines.shift();
var contentLines = lines.join('\n').trim();
notesModel.append({
"noteId": p.noteId,
"title": contentLines,
"colorIdx": colorIdx
});
p.destroy();
}
}
}
}
notesModel: ListModel {
}
}
@@ -13,6 +13,8 @@ Singleton {
property alias notificationsState: adapter.notificationsState
property alias lyricsState: adapter.lyricsState
property alias sunsetState: adapter.sunsetState
property alias leftSiderbarTab: adapter.leftSiderbarTab
property alias rightSiderbarTab: adapter.rightSiderbarTab
function save() {
saveTimer.restart();
@@ -21,6 +23,8 @@ Singleton {
onNotificationsStateChanged: save()
onLyricsStateChanged: save()
onSunsetStateChanged: save()
onLeftSiderbarTabChanged: save()
onRightSiderbarTabChanged: save()
Component.onCompleted: {
stateFileView.path = stateFile;
}
@@ -51,6 +55,8 @@ Singleton {
property var sunsetState: ({
"enabled": true
})
property string leftSiderbarTab: "bluetooth"
property string rightSiderbarTab: "notes"
}
}