import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell import Quickshell.Io import qs.Commons import qs.Ui import "Model.js" as Model Panel { id: root moduleName: "omarchy.dropbox" ipcTarget: "omarchy.dropbox" manageIpc: false property string focusSection: "login" property int fileIndex: 0 property bool cursorActive: false property int phraseIndex: 0 readonly property var activePhrases: [ "Filing files", "Distributing data", "Shuffling folders", "Boxing bytes", "Sorting stuff", "Syncing secrets", "Packing packets", "Moving memories", "Wrangling revisions", "Cataloging chaos" ] readonly property string heroPhraseText: activePhrases[phraseIndex % activePhrases.length] readonly property color foreground: bar ? bar.foreground : Color.foreground readonly property color urgent: bar ? bar.urgent : Color.urgent readonly property color dim: Qt.darker(foreground, 1.55) readonly property string fontFamily: bar ? bar.fontFamily : Style.font.family readonly property color iconColor: dropbox.authenticated ? foreground : dim readonly property color barIconColor: dropbox.authenticated ? barForeground : Qt.darker(barForeground, 1.55) function ensureCursor() { if (!dropbox.authenticated) { focusSection = "login" fileIndex = 0 return } if (dropbox.files.length === 0) { focusSection = "header" fileIndex = 0 return } if (focusSection !== "files") focusSection = "files" if (fileIndex >= dropbox.files.length) fileIndex = Math.max(0, dropbox.files.length - 1) if (fileIndex < 0) fileIndex = 0 } function moveCursor(dx, dy) { cursorActive = true ensureCursor() if (focusSection === "files" && dy !== 0) { fileIndex = Math.max(0, Math.min(dropbox.files.length - 1, fileIndex + dy)) scrollCursorIntoView() } } function activateCursor() { ensureCursor() if (focusSection === "login") dropbox.login() else if (focusSection === "files") dropbox.openFile(selectedFile()) } function selectedFile() { if (dropbox.files.length === 0) return null return dropbox.files[Math.max(0, Math.min(fileIndex, dropbox.files.length - 1))] } function setFileCursor(index) { cursorActive = true focusSection = "files" fileIndex = index scrollCursorIntoView() } function scrollItemIntoView(item) { if (!panelFlick || !item) return Qt.callLater(function() { if (!item) return var margin = Style.space(6) var point = item.mapToItem(panelFlick.contentItem, 0, 0) var top = point.y var bottom = top + item.height var viewTop = panelFlick.contentY var viewBottom = viewTop + panelFlick.height var maxY = Math.max(0, panelFlick.contentHeight - panelFlick.height) if (top < viewTop + margin) panelFlick.contentY = Math.max(0, top - margin) else if (bottom > viewBottom - margin) panelFlick.contentY = Math.min(maxY, bottom + margin - panelFlick.height) }) } function scrollCursorIntoView() { if (focusSection === "files" && fileColumn && fileIndex >= 0 && fileIndex < fileColumn.children.length) { scrollItemIntoView(fileColumn.children[fileIndex]) } } implicitWidth: button.implicitWidth implicitHeight: button.implicitHeight onOpenedChanged: if (opened) { cursorActive = false if (panelFlick) panelFlick.contentY = 0 dropbox.refresh() Qt.callLater(function() { keyCatcher.forceActiveFocus() }) } onFileIndexChanged: scrollCursorIntoView() Service { id: dropbox settings: root.settings omarchyPath: root.bar ? root.bar.omarchyPath : Quickshell.env("OMARCHY_PATH") } Connections { target: dropbox function onAuthenticatedChanged() { root.ensureCursor() } function onFilesChanged() { root.ensureCursor() } } IpcHandler { target: root.ipcTarget function open(): void { root.open() } function close(): void { root.close() } function show(): void { root.open() } function hide(): void { root.close() } function toggle(): void { root.toggle() } function refresh(): string { dropbox.refresh(); return "ok" } function login(): string { dropbox.login(); return "ok" } function status(): string { return dropbox.statusText } } Item { id: button anchors.fill: parent implicitWidth: root.bar && root.bar.vertical ? root.bar.barSize : Style.space(32) implicitHeight: root.bar && root.bar.vertical ? Style.space(26) : (root.bar ? root.bar.barSize : Style.space(26)) property var registeredBar: null function triggerPress(buttonCode) { if (buttonCode === Qt.RightButton) dropbox.refresh() else if (buttonCode === Qt.MiddleButton) dropbox.login() else root.toggle() } function syncClickRegistration() { if (registeredBar && registeredBar.unregisterClickTarget) registeredBar.unregisterClickTarget(button) registeredBar = root.bar if (registeredBar && registeredBar.registerClickTarget) registeredBar.registerClickTarget(button) } Component.onCompleted: syncClickRegistration() Component.onDestruction: if (registeredBar && registeredBar.unregisterClickTarget) registeredBar.unregisterClickTarget(button) Connections { target: root function onBarChanged() { button.syncClickRegistration() } } DropboxIcon { anchors.centerIn: parent iconSize: Style.space(12) color: root.barIconColor opacity: dropbox.authenticated ? 1.0 : 0.6 } MouseArea { anchors.fill: parent acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton hoverEnabled: true cursorShape: Qt.PointingHandCursor onClicked: function(mouse) { button.triggerPress(mouse.button) } } } KeyboardPanel { id: panel anchorItem: button owner: root bar: root.bar open: root.opened focusTarget: keyCatcher contentWidth: panel.fittedContentWidth(Style.space(380)) contentHeight: panel.fittedContentHeight(column.implicitHeight, Style.space(560)) PanelKeyCatcher { id: keyCatcher anchors.fill: parent onMoveRequested: function(dx, dy) { if (!root.cursorActive) { root.cursorActive = true; return } root.moveCursor(dx, dy) } onActivateRequested: if (root.cursorActive) root.activateCursor() onCloseRequested: root.close() onTabRequested: function(direction) { root.switchPanel(direction) } onTextKey: function(t) { if (t === "r" || t === "R") dropbox.refresh() else if (t === "l" || t === "L") dropbox.login() } Flickable { id: panelFlick anchors.fill: parent contentWidth: width contentHeight: column.implicitHeight clip: true boundsBehavior: Flickable.StopAtBounds flickableDirection: Flickable.VerticalFlick ScrollBar.vertical: ScrollBar { policy: ScrollBar.AsNeeded } Column { id: column width: panelFlick.width spacing: Style.space(12) PanelHero { id: hero visible: dropbox.authenticated width: parent.width title: "Dropbox" meta: root.heroPhraseText foreground: root.foreground fontFamily: root.fontFamily iconOpacity: 1.0 iconComponent: Component { DropboxIcon { iconSize: Style.font.display color: root.iconColor } } } Text { visible: dropbox.actionStatus !== "" || dropbox.lastError !== "" width: parent.width text: dropbox.actionStatus !== "" ? dropbox.actionStatus : dropbox.lastError color: dropbox.lastError !== "" && dropbox.actionStatus === "" ? root.urgent : root.dim font.family: root.fontFamily font.pixelSize: Style.font.bodySmall wrapMode: Text.WordWrap } LoginButton { visible: !dropbox.authenticated width: parent.width } Column { visible: dropbox.authenticated width: parent.width spacing: Style.spacing.labelGap Column { width: parent.width spacing: Style.spacing.labelGap InfoPair { label: "Stored"; value: Model.usageText(dropbox.usedBytes, dropbox.quotaBytes, dropbox.quotaKnown) } } } PanelSeparator { visible: dropbox.authenticated foreground: root.foreground } Column { visible: dropbox.authenticated width: parent.width spacing: Style.space(10) PanelSectionHeader { text: "RECENT FILES" foreground: root.foreground fontFamily: root.fontFamily } Text { visible: dropbox.files.length === 0 width: parent.width text: "No synced files found." color: root.dim font.family: root.fontFamily font.pixelSize: Style.font.body horizontalAlignment: Text.AlignHCenter } Column { id: fileColumn visible: dropbox.files.length > 0 width: parent.width spacing: Style.space(6) Repeater { model: dropbox.files FileRow { required property var modelData required property int index width: fileColumn.width file: modelData rowIndex: index } } } } } } } } Timer { id: phraseTimer interval: 2800 running: root.opened && dropbox.authenticated repeat: true onTriggered: phraseSwap.restart() } SequentialAnimation { id: phraseSwap PropertyAnimation { target: hero; property: "metaOpacity" to: 0.0; duration: 180; easing.type: Easing.OutQuad } ScriptAction { script: root.phraseIndex = (root.phraseIndex + 1) % root.activePhrases.length } PropertyAnimation { target: hero; property: "metaOpacity" to: 1.0; duration: 260; easing.type: Easing.InQuad } } component LoginButton: CursorSurface { id: loginButton hasCursor: root.cursorActive && root.focusSection === "login" foreground: root.foreground implicitHeight: loginRow.implicitHeight + Style.spacing.rowPaddingX MouseArea { anchors.fill: parent hoverEnabled: true cursorShape: dropbox.installed && !dropbox.busy ? Qt.PointingHandCursor : Qt.ArrowCursor enabled: dropbox.installed && !dropbox.busy onEntered: { root.cursorActive = true root.focusSection = "login" } onClicked: dropbox.login() } RowLayout { id: loginRow anchors.left: parent.left anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter anchors.leftMargin: Style.space(10) anchors.rightMargin: Style.space(10) spacing: Style.space(8) Text { text: "" color: root.foreground font.family: root.fontFamily font.pixelSize: Style.font.heading Layout.alignment: Qt.AlignVCenter } ColumnLayout { Layout.fillWidth: true spacing: Style.space(1) Text { Layout.fillWidth: true text: dropbox.installed ? "Login to Dropbox" : "Dropbox CLI is not installed" color: root.foreground font.family: root.fontFamily font.pixelSize: Style.font.body elide: Text.ElideRight } Text { Layout.fillWidth: true text: dropbox.installed ? "Start the authentication flow" : "Install Dropbox from the service menu" color: root.dim font.family: root.fontFamily font.pixelSize: Style.font.caption elide: Text.ElideRight } } PanelActionButton { iconText: "󰌋" foreground: root.foreground fontFamily: root.fontFamily enabled: dropbox.installed && !dropbox.busy Layout.alignment: Qt.AlignVCenter onClicked: dropbox.login() } } } component FileRow: CursorSurface { id: fileRow property var file: null property int rowIndex: 0 readonly property string fileName: file ? String(file.name || "Untitled") : "Untitled" hasCursor: root.cursorActive && root.focusSection === "files" && root.fileIndex === rowIndex foreground: root.foreground implicitHeight: fileContent.implicitHeight + Style.spacing.rowPaddingX MouseArea { anchors.fill: parent hoverEnabled: true cursorShape: Qt.PointingHandCursor onEntered: root.setFileCursor(fileRow.rowIndex) onClicked: dropbox.openFile(fileRow.file) } RowLayout { anchors.left: parent.left anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter anchors.leftMargin: Style.space(10) anchors.rightMargin: Style.space(10) spacing: Style.space(8) Text { text: Model.fileGlyph(fileRow.fileName) color: root.foreground font.family: root.fontFamily font.pixelSize: Style.font.icon Layout.alignment: Qt.AlignVCenter } ColumnLayout { id: fileContent Layout.fillWidth: true spacing: Style.space(1) Text { Layout.fillWidth: true text: fileRow.fileName color: root.foreground font.family: root.fontFamily font.pixelSize: Style.font.body elide: Text.ElideRight } Text { Layout.fillWidth: true text: Model.fileMeta(fileRow.file) color: root.dim font.family: root.fontFamily font.pixelSize: Style.font.caption elide: Text.ElideRight } } } } component InfoPair: Row { property string label: "" property string value: "" width: parent.width spacing: Style.space(8) InfoLabel { text: label } Item { width: Math.max(0, parent.width - parent.children[0].implicitWidth - parent.children[2].implicitWidth - parent.spacing * 2); height: 1 } InfoValue { text: value } } component InfoLabel: Text { color: root.foreground opacity: 0.6 font.family: root.fontFamily font.pixelSize: Style.font.bodySmall } component InfoValue: Text { color: root.foreground font.family: root.fontFamily font.pixelSize: Style.font.bodySmall elide: Text.ElideRight } }