use blobgroup for window chat interface + slim sidebar
C++ / fmt (pull_request) Successful in 4s
C++ / build (pull_request) Failing after 2m1s
C++ / clang-tidy (pull_request) Failing after 1m54s
JS/TS / fmt (pull_request) Failing after 7s
JS/TS / lint (pull_request) Successful in 7s
Python / static (pull_request) Successful in 29s
Rust / build (pull_request) Successful in 47s
Python / verify (pull_request) Successful in 1m35s
Rust / fmt (pull_request) Successful in 29s
Rust / clippy (pull_request) Successful in 51s
C++ / fmt (pull_request) Successful in 4s
C++ / build (pull_request) Failing after 2m1s
C++ / clang-tidy (pull_request) Failing after 1m54s
JS/TS / fmt (pull_request) Failing after 7s
JS/TS / lint (pull_request) Successful in 7s
Python / static (pull_request) Successful in 29s
Rust / build (pull_request) Successful in 47s
Python / verify (pull_request) Successful in 1m35s
Rust / fmt (pull_request) Successful in 29s
Rust / clippy (pull_request) Successful in 51s
This commit is contained in:
@@ -161,7 +161,7 @@ Item {
|
|||||||
scrollAnim.start();
|
scrollAnim.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
cacheBuffer: Math.max(height * 20, 0)
|
cacheBuffer: Math.max(height * 2, 0)
|
||||||
clip: true
|
clip: true
|
||||||
fadeAmount: 0.05
|
fadeAmount: 0.05
|
||||||
fadeThreshold: Tokens.padding.medium
|
fadeThreshold: Tokens.padding.medium
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ CustomClippingRect {
|
|||||||
|
|
||||||
property bool expanded: false
|
property bool expanded: false
|
||||||
property bool highlighted: false
|
property bool highlighted: false
|
||||||
|
property bool slim: false
|
||||||
required property ChatSession modelData
|
required property ChatSession modelData
|
||||||
|
|
||||||
signal clicked(content: ChatSession)
|
signal clicked(content: ChatSession)
|
||||||
@@ -20,6 +21,9 @@ CustomClippingRect {
|
|||||||
implicitHeight: {
|
implicitHeight: {
|
||||||
let h = 0;
|
let h = 0;
|
||||||
|
|
||||||
|
if (slim)
|
||||||
|
return chatIcon.implicitHeight + chatIcon.anchors.topMargin * 2;
|
||||||
|
|
||||||
h += infoContainer.implicitHeight;
|
h += infoContainer.implicitHeight;
|
||||||
|
|
||||||
if (expanded)
|
if (expanded)
|
||||||
@@ -85,6 +89,14 @@ CustomClippingRect {
|
|||||||
return h;
|
return h;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
opacity: root.slim ? 0 : 1
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
Anim {
|
||||||
|
type: Anim.DefaultEffects
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
CustomRect {
|
CustomRect {
|
||||||
id: title
|
id: title
|
||||||
|
|
||||||
@@ -246,7 +258,7 @@ CustomClippingRect {
|
|||||||
color: Colors.palette.m3onSurfaceVariant
|
color: Colors.palette.m3onSurfaceVariant
|
||||||
font.pointSize: Tokens.font.size.small
|
font.pointSize: Tokens.font.size.small
|
||||||
maximumLineCount: 1
|
maximumLineCount: 1
|
||||||
opacity: !root.expanded ? 1 : 0
|
opacity: !root.expanded && !root.slim ? 1 : 0
|
||||||
text: root.modelData.messagesModel.lastMessage?.activeGeneration.content.replace(/\s+/g, " ").trim() ?? qsTr("No messages yet")
|
text: root.modelData.messagesModel.lastMessage?.activeGeneration.content.replace(/\s+/g, " ").trim() ?? qsTr("No messages yet")
|
||||||
|
|
||||||
Behavior on y {
|
Behavior on y {
|
||||||
@@ -347,6 +359,16 @@ CustomClippingRect {
|
|||||||
radius: Tokens.rounding.full
|
radius: Tokens.rounding.full
|
||||||
color: Colors.layer(Colors.palette.m3surfaceContainerHighest, 3)
|
color: Colors.layer(Colors.palette.m3surfaceContainerHighest, 3)
|
||||||
|
|
||||||
|
opacity: root.slim ? 0 : 1
|
||||||
|
visible: opacity > 0
|
||||||
|
enabled: visible
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
Anim {
|
||||||
|
type: Anim.DefaultEffects
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
StateLayer {
|
StateLayer {
|
||||||
onClicked: root.expanded = !root.expanded
|
onClicked: root.expanded = !root.expanded
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ Item {
|
|||||||
property alias list: list
|
property alias list: list
|
||||||
property alias model: list.model
|
property alias model: list.model
|
||||||
property bool highlight: false
|
property bool highlight: false
|
||||||
|
property bool slim: false
|
||||||
|
|
||||||
signal deleteChatRequest(content: ChatSession)
|
signal deleteChatRequest(content: ChatSession)
|
||||||
signal loadChatRequest(content: ChatSession, index: int)
|
signal loadChatRequest(content: ChatSession, index: int)
|
||||||
@@ -79,6 +80,7 @@ Item {
|
|||||||
|
|
||||||
implicitWidth: ListView.view.width
|
implicitWidth: ListView.view.width
|
||||||
highlighted: root.highlight && ChatState.chatSession === modelData
|
highlighted: root.highlight && ChatState.chatSession === modelData
|
||||||
|
slim: root.slim
|
||||||
|
|
||||||
onHighlightedChanged: if (highlighted)
|
onHighlightedChanged: if (highlighted)
|
||||||
list.currentIndex = index
|
list.currentIndex = index
|
||||||
@@ -248,6 +250,14 @@ Item {
|
|||||||
font.pointSize: Math.round(18 * 1.2)
|
font.pointSize: Math.round(18 * 1.2)
|
||||||
icon: "add"
|
icon: "add"
|
||||||
isRound: ChatState.fabExpanded
|
isRound: ChatState.fabExpanded
|
||||||
|
opacity: root.slim ? 0 : 1
|
||||||
|
visible: opacity > 0
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
Anim {
|
||||||
|
type: Anim.DefaultEffects
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
label.transform: Rotation {
|
label.transform: Rotation {
|
||||||
origin.y: fabRoot.label.height / 2
|
origin.y: fabRoot.label.height / 2
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ Singleton {
|
|||||||
property bool inChat: false
|
property bool inChat: false
|
||||||
property bool fabExpanded: false
|
property bool fabExpanded: false
|
||||||
property bool isWindow: false
|
property bool isWindow: false
|
||||||
|
property bool windowIsWide: false
|
||||||
property ShellScreen screen
|
property ShellScreen screen
|
||||||
property bool sidebarVisible: Visibilities.getForActive().sidebar
|
property bool sidebarVisible: Visibilities.getForActive().sidebar
|
||||||
|
|
||||||
|
|||||||
@@ -6,127 +6,138 @@ import qs.Services
|
|||||||
import qs.Modules.Notifications.Sidebar.Chat
|
import qs.Modules.Notifications.Sidebar.Chat
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property int animOff
|
property int animOff
|
||||||
property Item currentItem
|
property Item currentItem
|
||||||
property bool loading: false
|
property bool noAnim: false
|
||||||
property int lastIdx: -1
|
property bool loading: false
|
||||||
|
property int lastIdx: -1
|
||||||
|
|
||||||
readonly property Component conversationComp: ChatContent {}
|
readonly property Component conversationComp: ChatContent {}
|
||||||
|
|
||||||
function loadConversation(chat): void {
|
signal requestClose
|
||||||
if (currentItem) {
|
|
||||||
currentItem.destroy();
|
|
||||||
currentItem = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!chat)
|
function loadConversation(chat): void {
|
||||||
return;
|
if (currentItem) {
|
||||||
|
currentItem.destroy();
|
||||||
|
currentItem = null;
|
||||||
|
}
|
||||||
|
|
||||||
root.loading = true;
|
if (!chat)
|
||||||
|
return;
|
||||||
|
|
||||||
const incubator = root.conversationComp.incubateObject(container, {
|
root.loading = true;
|
||||||
chatData: chat
|
|
||||||
});
|
|
||||||
|
|
||||||
const attach = () => {
|
const incubator = root.conversationComp.incubateObject(container, {
|
||||||
incubator.object.anchors.fill = container;
|
chatData: chat
|
||||||
currentItem = incubator.object;
|
});
|
||||||
root.loading = false;
|
|
||||||
enterAnim.start();
|
|
||||||
};
|
|
||||||
|
|
||||||
if (incubator.status === Component.Ready)
|
const attach = () => {
|
||||||
attach();
|
incubator.object.anchors.fill = container;
|
||||||
else
|
incubator.object.requestClose.connect(root.requestClose);
|
||||||
incubator.onStatusChanged = status => {
|
currentItem = incubator.object;
|
||||||
if (status === Component.Ready)
|
root.loading = false;
|
||||||
attach();
|
if (!noAnim)
|
||||||
};
|
enterAnim.start();
|
||||||
}
|
};
|
||||||
|
|
||||||
Item {
|
if (incubator.status === Component.Ready)
|
||||||
id: container
|
attach();
|
||||||
|
else
|
||||||
|
incubator.onStatusChanged = status => {
|
||||||
|
if (status === Component.Ready)
|
||||||
|
attach();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
anchors.fill: parent
|
Item {
|
||||||
layer.enabled: opacity < 1
|
id: container
|
||||||
objectName: "ConversationContainer"
|
|
||||||
|
|
||||||
Component.onCompleted: {
|
anchors.fill: parent
|
||||||
if (ChatState.chatSession)
|
layer.enabled: opacity < 1
|
||||||
root.loadConversation(ChatState.chatSession);
|
objectName: "ConversationContainer"
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LoadingIndicator {
|
Component.onCompleted: {
|
||||||
anchors.centerIn: parent
|
if (ChatState.chatSession)
|
||||||
implicitSize: Tokens.font.size.extraLarge * 4
|
root.loadConversation(ChatState.chatSession);
|
||||||
opacity: root.loading ? 1 : 0
|
}
|
||||||
visible: opacity > 0
|
}
|
||||||
|
|
||||||
Behavior on opacity {
|
LoadingIndicator {
|
||||||
Anim {}
|
anchors.centerIn: parent
|
||||||
}
|
implicitSize: Tokens.font.size.extraLarge * 4
|
||||||
}
|
opacity: root.loading ? 1 : 0
|
||||||
|
visible: opacity > 0
|
||||||
|
|
||||||
Connections {
|
Behavior on opacity {
|
||||||
function onChatSessionChanged(): void {
|
Anim {}
|
||||||
exitAnim.complete();
|
}
|
||||||
enterAnim.complete();
|
}
|
||||||
root.animOff = Tokens.padding.small * (ChatState.currentIdx > root.lastIdx ? 1 : -1);
|
|
||||||
root.lastIdx = ChatState.currentIdx;
|
|
||||||
exitAnim.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
target: ChatState
|
Connections {
|
||||||
}
|
function onChatSessionChanged(): void {
|
||||||
|
exitAnim.complete();
|
||||||
|
enterAnim.complete();
|
||||||
|
|
||||||
SequentialAnimation {
|
if (root.noAnim) {
|
||||||
id: exitAnim
|
root.loadConversation(ChatState.chatSession);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Anim {
|
root.animOff = Tokens.padding.small * (ChatState.currentIdx > root.lastIdx ? 1 : -1);
|
||||||
property: "opacity"
|
root.lastIdx = ChatState.currentIdx;
|
||||||
target: container
|
exitAnim.start();
|
||||||
to: 0
|
}
|
||||||
type: Anim.DefaultEffects
|
|
||||||
}
|
|
||||||
|
|
||||||
ScriptAction {
|
target: ChatState
|
||||||
script: root.loadConversation(ChatState.chatSession)
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SequentialAnimation {
|
SequentialAnimation {
|
||||||
id: enterAnim
|
id: exitAnim
|
||||||
|
|
||||||
PropertyAction {
|
Anim {
|
||||||
property: "topMargin"
|
property: "opacity"
|
||||||
target: container.anchors
|
target: container
|
||||||
value: root.animOff
|
to: 0
|
||||||
}
|
type: Anim.DefaultEffects
|
||||||
|
}
|
||||||
|
|
||||||
PropertyAction {
|
ScriptAction {
|
||||||
property: "bottomMargin"
|
script: root.loadConversation(ChatState.chatSession)
|
||||||
target: container.anchors
|
}
|
||||||
value: -root.animOff
|
}
|
||||||
}
|
|
||||||
|
|
||||||
ParallelAnimation {
|
SequentialAnimation {
|
||||||
Anim {
|
id: enterAnim
|
||||||
from: 0
|
|
||||||
property: "opacity"
|
|
||||||
target: container
|
|
||||||
to: 1
|
|
||||||
type: Anim.SlowEffects
|
|
||||||
}
|
|
||||||
|
|
||||||
Anim {
|
PropertyAction {
|
||||||
properties: "topMargin,bottomMargin"
|
property: "topMargin"
|
||||||
target: container.anchors
|
target: container.anchors
|
||||||
to: 0
|
value: root.animOff
|
||||||
type: Anim.SlowEffects
|
}
|
||||||
}
|
|
||||||
}
|
PropertyAction {
|
||||||
}
|
property: "bottomMargin"
|
||||||
|
target: container.anchors
|
||||||
|
value: -root.animOff
|
||||||
|
}
|
||||||
|
|
||||||
|
ParallelAnimation {
|
||||||
|
Anim {
|
||||||
|
from: 0
|
||||||
|
property: "opacity"
|
||||||
|
target: container
|
||||||
|
to: 1
|
||||||
|
type: Anim.SlowEffects
|
||||||
|
}
|
||||||
|
|
||||||
|
Anim {
|
||||||
|
properties: "topMargin,bottomMargin"
|
||||||
|
target: container.anchors
|
||||||
|
to: 0
|
||||||
|
type: Anim.SlowEffects
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,38 +4,65 @@ import QtQuick
|
|||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
import Quickshell
|
import Quickshell
|
||||||
|
import ZShell.Blobs
|
||||||
import ZShell.Config
|
import ZShell.Config
|
||||||
import ZShell.Llm
|
import ZShell.Llm
|
||||||
import qs.Components
|
import qs.Components
|
||||||
import qs.Modules.Notifications.Sidebar.Chat.Content
|
import qs.Modules.Notifications.Sidebar.Chat.Content
|
||||||
import qs.Services
|
import qs.Services
|
||||||
|
|
||||||
RowLayout {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property int breakpoint: 700
|
property int breakpoint: Math.round(Config.sidebar.sizes.width * 2) + Tokens.spacing.medium * 2 + Tokens.padding.medium * 3
|
||||||
|
property color blobColor: Colors.tPalette.m3surfaceContainerLow
|
||||||
|
readonly property int iconSize: Math.round(Tokens.font.size.extraLarge * (96 / 72)) + Tokens.padding.large * 2
|
||||||
property alias conversationModel: sidebar.model
|
property alias conversationModel: sidebar.model
|
||||||
|
property bool chatOpen: ChatState.chatSession !== null
|
||||||
|
|
||||||
readonly property bool isWide: root.width >= root.breakpoint
|
readonly property bool isWide: root.width >= root.breakpoint
|
||||||
property bool narrowShowsSidebar: true
|
|
||||||
|
readonly property bool showList: isWide || !chatOpen
|
||||||
|
readonly property bool showContent: !isWide && chatOpen
|
||||||
|
|
||||||
function openConversation(conv: ChatSession, index: int): void {
|
function openConversation(conv: ChatSession, index: int): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BlobGroup {
|
||||||
|
id: blobGroup
|
||||||
|
|
||||||
|
color: root.blobColor
|
||||||
|
smoothing: Tokens.rounding.medium
|
||||||
|
}
|
||||||
|
|
||||||
|
BlobInvertedRect {
|
||||||
|
anchors.fill: parent
|
||||||
|
borderBottom: Tokens.padding.small
|
||||||
|
borderLeft: sidebar.implicitWidth + sidebar.anchors.margins + Tokens.spacing.medium
|
||||||
|
borderRight: Tokens.padding.small
|
||||||
|
borderTop: Tokens.padding.small
|
||||||
|
group: blobGroup
|
||||||
|
opacity: root.blobColor.a
|
||||||
|
radius: Tokens.rounding.largeIncreased
|
||||||
|
}
|
||||||
|
|
||||||
ChatList {
|
ChatList {
|
||||||
id: sidebar
|
id: sidebar
|
||||||
|
|
||||||
Layout.fillHeight: true
|
anchors.top: parent.top
|
||||||
Layout.margins: Tokens.padding.medium
|
anchors.left: parent.left
|
||||||
Layout.rightMargin: Tokens.spacing.medium
|
anchors.bottom: parent.bottom
|
||||||
Layout.maximumWidth: Config.sidebar.sizes.width
|
anchors.margins: Tokens.padding.medium
|
||||||
Layout.preferredWidth: root.width / 4
|
implicitWidth: root.isWide ? Config.sidebar.sizes.width : root.iconSize
|
||||||
Layout.minimumWidth: Config.sidebar.sizes.width / 2
|
|
||||||
highlight: true
|
highlight: true
|
||||||
|
slim: !root.isWide
|
||||||
|
|
||||||
Behavior on implicitWidth {
|
Behavior on implicitWidth {
|
||||||
Anim {}
|
Anim {
|
||||||
|
type: Anim.DefaultEffects
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
model: ScriptModel {
|
model: ScriptModel {
|
||||||
values: Chat.chats.values
|
values: Chat.chats.values
|
||||||
}
|
}
|
||||||
@@ -46,9 +73,11 @@ RowLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onDeleteChatRequest: chat => {
|
onDeleteChatRequest: chat => {
|
||||||
|
if (chat === ChatState.chatSession) {
|
||||||
|
ChatState.chatSession = null;
|
||||||
|
ChatState.currentIdx = -1;
|
||||||
|
}
|
||||||
Chat.chats.remove(chat);
|
Chat.chats.remove(chat);
|
||||||
ChatState.currentIdx = -1;
|
|
||||||
ChatState.chatSession = null;
|
|
||||||
}
|
}
|
||||||
onNewChatRequest: {
|
onNewChatRequest: {
|
||||||
const data = Chat.chats.insert();
|
const data = Chat.chats.insert();
|
||||||
@@ -58,12 +87,34 @@ RowLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
Layout.margins: Tokens.padding.extraLarge
|
id: contentArea
|
||||||
Layout.leftMargin: Tokens.spacing.extraLarge
|
|
||||||
Layout.topMargin: Tokens.padding.large
|
property int leftMargin: sidebar.implicitWidth
|
||||||
Layout.preferredWidth: Config.sidebar.sizes.width * 2
|
|
||||||
Layout.fillWidth: true
|
z: 1
|
||||||
Layout.fillHeight: true
|
anchors.top: parent.top
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.margins: Tokens.padding.medium * 2
|
||||||
|
anchors.right: parent.right
|
||||||
|
implicitWidth: root.width - leftMargin - sidebar.anchors.margins - Tokens.spacing.medium * 2 - anchors.margins
|
||||||
|
|
||||||
|
states: State {
|
||||||
|
name: "wide"
|
||||||
|
when: !root.isWide
|
||||||
|
|
||||||
|
PropertyChanges {
|
||||||
|
contentArea.leftMargin: root.iconSize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
transitions: Transition {
|
||||||
|
to: "wide"
|
||||||
|
|
||||||
|
Anim {
|
||||||
|
type: Anim.DefaultEffects
|
||||||
|
property: "leftMargin"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ChatHost {
|
ChatHost {
|
||||||
id: convHost
|
id: convHost
|
||||||
@@ -71,10 +122,11 @@ RowLayout {
|
|||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
anchors.bottom: parent.bottom
|
anchors.bottom: parent.bottom
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
implicitWidth: Math.min(parent.width, 800)
|
implicitWidth: Math.max(Math.min(parent.width, 800), Config.sidebar.sizes.width)
|
||||||
clip: true
|
clip: true
|
||||||
|
|
||||||
onImplicitWidthChanged: console.log(implicitWidth)
|
onLoadingChanged: if (!loading)
|
||||||
|
root.chatOpen = true
|
||||||
}
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
@@ -102,6 +154,12 @@ RowLayout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Binding {
|
||||||
|
target: ChatState
|
||||||
|
property: "windowIsWide"
|
||||||
|
value: root.showList
|
||||||
|
}
|
||||||
|
|
||||||
Component {
|
Component {
|
||||||
id: conversationView
|
id: conversationView
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user