mirror of
https://github.com/end-4/dots-hyprland.git
synced 2026-06-06 07:19:27 -05:00
sidebar: translation widget
This commit is contained in:
@@ -90,6 +90,9 @@ Singleton {
|
||||
}
|
||||
|
||||
property QtObject sidebar: QtObject {
|
||||
property QtObject translator: QtObject {
|
||||
property int delay: 100 // Delay before sending request. Reduces (potential) rate limits and lag.
|
||||
}
|
||||
property QtObject booru: QtObject {
|
||||
property bool allowNsfw: false
|
||||
property string defaultProvider: "yandere"
|
||||
|
||||
@@ -16,8 +16,6 @@ import Quickshell.Hyprland
|
||||
Scope { // Scope
|
||||
id: root
|
||||
property int sidebarPadding: 15
|
||||
property var tabButtonList: [{"icon": "neurology", "name": qsTr("Intelligence")}, {"icon": "bookmark_heart", "name": qsTr("Anime")}]
|
||||
property int selectedTab: 0
|
||||
property bool detach: false
|
||||
property Component contentComponent: SidebarLeftContent {}
|
||||
property Item sidebarContent
|
||||
|
||||
@@ -14,9 +14,15 @@ import Quickshell.Wayland
|
||||
import Quickshell.Hyprland
|
||||
|
||||
Item {
|
||||
id: sidebarLeftBackground
|
||||
id: root
|
||||
required property var scopeRoot
|
||||
anchors.fill: parent
|
||||
property var tabButtonList: [
|
||||
{"icon": "neurology", "name": qsTr("Intelligence")},
|
||||
{"icon": "translate", "name": qsTr("Translator")},
|
||||
{"icon": "bookmark_heart", "name": qsTr("Anime")},
|
||||
]
|
||||
property int selectedTab: 0
|
||||
|
||||
function focusActiveItem() {
|
||||
swipeView.currentItem.forceActiveFocus()
|
||||
@@ -25,19 +31,19 @@ Item {
|
||||
Keys.onPressed: (event) => {
|
||||
if (event.modifiers === Qt.ControlModifier) {
|
||||
if (event.key === Qt.Key_PageDown) {
|
||||
scopeRoot.selectedTab = Math.min(scopeRoot.selectedTab + 1, scopeRoot.tabButtonList.length - 1)
|
||||
root.selectedTab = Math.min(root.selectedTab + 1, root.tabButtonList.length - 1)
|
||||
event.accepted = true;
|
||||
}
|
||||
else if (event.key === Qt.Key_PageUp) {
|
||||
scopeRoot.selectedTab = Math.max(scopeRoot.selectedTab - 1, 0)
|
||||
root.selectedTab = Math.max(root.selectedTab - 1, 0)
|
||||
event.accepted = true;
|
||||
}
|
||||
else if (event.key === Qt.Key_Tab) {
|
||||
scopeRoot.selectedTab = (scopeRoot.selectedTab + 1) % scopeRoot.tabButtonList.length;
|
||||
root.selectedTab = (root.selectedTab + 1) % root.tabButtonList.length;
|
||||
event.accepted = true;
|
||||
}
|
||||
else if (event.key === Qt.Key_Backtab) {
|
||||
scopeRoot.selectedTab = (scopeRoot.selectedTab - 1 + scopeRoot.tabButtonList.length) % scopeRoot.tabButtonList.length;
|
||||
root.selectedTab = (root.selectedTab - 1 + root.tabButtonList.length) % root.tabButtonList.length;
|
||||
event.accepted = true;
|
||||
}
|
||||
}
|
||||
@@ -51,10 +57,10 @@ Item {
|
||||
|
||||
PrimaryTabBar { // Tab strip
|
||||
id: tabBar
|
||||
tabButtonList: scopeRoot.tabButtonList
|
||||
externalTrackedTab: scopeRoot.selectedTab
|
||||
tabButtonList: root.tabButtonList
|
||||
externalTrackedTab: root.selectedTab
|
||||
function onCurrentIndexChanged(currentIndex) {
|
||||
scopeRoot.selectedTab = currentIndex
|
||||
root.selectedTab = currentIndex
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +74,7 @@ Item {
|
||||
currentIndex: tabBar.externalTrackedTab
|
||||
onCurrentIndexChanged: {
|
||||
tabBar.enableIndicatorAnimation = true
|
||||
scopeRoot.selectedTab = currentIndex
|
||||
root.selectedTab = currentIndex
|
||||
}
|
||||
|
||||
clip: true
|
||||
@@ -82,6 +88,7 @@ Item {
|
||||
}
|
||||
|
||||
AiChat {}
|
||||
Translator {}
|
||||
Anime {}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,229 @@
|
||||
import "root:/"
|
||||
import "root:/services"
|
||||
import "root:/modules/common"
|
||||
import "root:/modules/common/widgets"
|
||||
import "root:/modules/common/functions/string_utils.js" as StringUtils
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Hyprland
|
||||
|
||||
/**
|
||||
* Translator widget with the `trans` commandline tool.
|
||||
*/
|
||||
Item {
|
||||
id: root
|
||||
property var inputField: inputTextArea
|
||||
property var outputField: outputTextArea
|
||||
|
||||
property bool translationFor: false // Indicates if the translation is for an autocorrected text
|
||||
property string translatedText: ""
|
||||
|
||||
onFocusChanged: (focus) => {
|
||||
if (focus) {
|
||||
root.inputField.forceActiveFocus()
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: translateTimer
|
||||
interval: ConfigOptions.sidebar.translator.delay
|
||||
repeat: false
|
||||
onTriggered: () => {
|
||||
if (inputTextArea.text.trim().length > 0) {
|
||||
translateProc.running = false;
|
||||
translateProc.buffer = ""; // Clear the buffer
|
||||
translateProc.running = true; // Restart the process
|
||||
} else {
|
||||
outputTextArea.text = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: translateProc
|
||||
command: ["bash", "-c", `trans -no-theme -no-ansi '${StringUtils.shellSingleQuoteEscape(inputTextArea.text.trim())}'`]
|
||||
property string buffer: ""
|
||||
stdout: SplitParser {
|
||||
onRead: data => {
|
||||
translateProc.buffer += data + "\n";
|
||||
}
|
||||
}
|
||||
onExited: (exitCode, exitStatus) => {
|
||||
// 1. Split into sections by double newlines
|
||||
const sections = translateProc.buffer.trim().split(/\n\s*\n/);
|
||||
// console.log("BUFFER:", translateProc.buffer);
|
||||
// console.log("SECTIONS:", sections);
|
||||
|
||||
// 2. Extract relevant data
|
||||
root.translatedText = sections.length > 1 ? sections[1].trim() : "";
|
||||
root.outputField.text = root.translatedText;
|
||||
}
|
||||
}
|
||||
|
||||
Flickable {
|
||||
anchors.fill: parent
|
||||
contentHeight: contentColumn.implicitHeight
|
||||
|
||||
ColumnLayout {
|
||||
id: contentColumn
|
||||
anchors.fill: parent
|
||||
|
||||
Rectangle { // INPUT
|
||||
id: inputCanvas
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: Math.max(150, inputColumn.implicitHeight)
|
||||
color: Appearance.colors.colLayer1
|
||||
radius: Appearance.rounding.normal
|
||||
border.color: Appearance.m3colors.m3outlineVariant
|
||||
border.width: 1
|
||||
|
||||
ColumnLayout {
|
||||
id: inputColumn
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
|
||||
StyledTextArea { // Input area
|
||||
id: inputTextArea
|
||||
Layout.fillWidth: true
|
||||
placeholderText: qsTr("Enter text to translate...")
|
||||
wrapMode: TextEdit.Wrap
|
||||
textFormat: TextEdit.PlainText
|
||||
font.pixelSize: Appearance.font.pixelSize.small
|
||||
color: Appearance.colors.colOnLayer1
|
||||
padding: 15
|
||||
background: null
|
||||
onTextChanged: {
|
||||
if (inputTextArea.text.trim().length > 0) {
|
||||
translateTimer.restart();
|
||||
} else {
|
||||
outputTextArea.text = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item { Layout.fillHeight: true }
|
||||
|
||||
RowLayout { // Status row
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: 10
|
||||
spacing: 10
|
||||
|
||||
Text {
|
||||
Layout.leftMargin: 10
|
||||
text: qsTr("%1 characters").arg(inputTextArea.text.length)
|
||||
color: Appearance.colors.colOnLayer1
|
||||
font.pixelSize: Appearance.font.pixelSize.smaller
|
||||
}
|
||||
Item { Layout.fillWidth: true }
|
||||
ButtonGroup {
|
||||
GroupButton {
|
||||
id: pasteButton
|
||||
baseWidth: height
|
||||
buttonRadius: Appearance.rounding.small
|
||||
contentItem: MaterialSymbol {
|
||||
anchors.centerIn: parent
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
iconSize: Appearance.font.pixelSize.larger
|
||||
text: "content_paste"
|
||||
color: deleteButton.enabled ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext
|
||||
}
|
||||
onClicked: {
|
||||
root.inputField.text = Quickshell.clipboardText
|
||||
}
|
||||
}
|
||||
GroupButton {
|
||||
id: deleteButton
|
||||
baseWidth: height
|
||||
buttonRadius: Appearance.rounding.small
|
||||
enabled: inputTextArea.text.length > 0
|
||||
contentItem: MaterialSymbol {
|
||||
anchors.centerIn: parent
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
iconSize: Appearance.font.pixelSize.larger
|
||||
text: "close"
|
||||
color: deleteButton.enabled ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext
|
||||
}
|
||||
onClicked: {
|
||||
root.inputField.text = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle { // OUTPUT
|
||||
id: outputCanvas
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: Math.max(150, outputColumn.implicitHeight)
|
||||
color: Appearance.m3colors.m3surfaceContainer
|
||||
radius: Appearance.rounding.normal
|
||||
|
||||
ColumnLayout { // Output column
|
||||
id: outputColumn
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
|
||||
StyledText { // Output area
|
||||
id: outputTextArea
|
||||
Layout.fillWidth: true
|
||||
property bool hasTranslation: (root.translatedText.trim().length > 0)
|
||||
wrapMode: TextEdit.Wrap
|
||||
font.pixelSize: Appearance.font.pixelSize.small
|
||||
color: hasTranslation ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext
|
||||
padding: 15
|
||||
text: hasTranslation ? root.translatedText : ""
|
||||
}
|
||||
Item { Layout.fillHeight: true }
|
||||
RowLayout { // Status row
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: 10
|
||||
spacing: 10
|
||||
Item { Layout.fillWidth: true }
|
||||
ButtonGroup {
|
||||
GroupButton {
|
||||
id: copyButton
|
||||
baseWidth: height
|
||||
buttonRadius: Appearance.rounding.small
|
||||
enabled: root.outputField.text.trim().length > 0
|
||||
contentItem: MaterialSymbol {
|
||||
anchors.centerIn: parent
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
iconSize: Appearance.font.pixelSize.larger
|
||||
text: "content_copy"
|
||||
color: deleteButton.enabled ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext
|
||||
}
|
||||
onClicked: {
|
||||
Quickshell.clipboardText = root.outputField.text
|
||||
}
|
||||
}
|
||||
GroupButton {
|
||||
id: searchButton
|
||||
baseWidth: height
|
||||
buttonRadius: Appearance.rounding.small
|
||||
enabled: root.outputField.text.trim().length > 0
|
||||
contentItem: MaterialSymbol {
|
||||
anchors.centerIn: parent
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
iconSize: Appearance.font.pixelSize.larger
|
||||
text: "travel_explore"
|
||||
color: deleteButton.enabled ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext
|
||||
}
|
||||
onClicked: {
|
||||
let url = ConfigOptions.search.engineBaseUrl + root.outputField.text;
|
||||
for (let site of ConfigOptions.search.excludedSites) {
|
||||
url += ` -site:${site}`;
|
||||
}
|
||||
Qt.openUrlExternally(url);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,5 +14,6 @@ depends=(
|
||||
nm-connection-editor
|
||||
quickshell
|
||||
swww
|
||||
translate-shell
|
||||
wlogout
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user