forked from Shinonome/dots-hyprland
Rearrange for tidier structure (#2212)
This commit is contained in:
@@ -0,0 +1,564 @@
|
||||
import qs
|
||||
import qs.services
|
||||
import qs.modules.common
|
||||
import qs.modules.common.widgets
|
||||
import qs.modules.common.functions
|
||||
import "./anime/"
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Qt5Compat.GraphicalEffects
|
||||
import Quickshell
|
||||
|
||||
Item {
|
||||
id: root
|
||||
property var inputField: tagInputField
|
||||
readonly property var responses: Booru.responses
|
||||
property string previewDownloadPath: Directories.booruPreviews
|
||||
property string downloadPath: Directories.booruDownloads
|
||||
property string nsfwPath: Directories.booruDownloadsNsfw
|
||||
property string commandPrefix: "/"
|
||||
property real scrollOnNewResponse: 100
|
||||
property int tagSuggestionDelay: 210
|
||||
property var suggestionQuery: ""
|
||||
property var suggestionList: []
|
||||
|
||||
Connections {
|
||||
target: Config
|
||||
function onReadyChanged() {
|
||||
if (Config.options.policies.weeb !== 0) {
|
||||
Quickshell.execDetached(["bash", "-c", `mkdir -p '${root.downloadPath}' && mkdir -p '${root.nsfwPath}'`])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: Booru
|
||||
function onTagSuggestion(query, suggestions) {
|
||||
root.suggestionQuery = query;
|
||||
root.suggestionList = suggestions;
|
||||
}
|
||||
}
|
||||
|
||||
property var allCommands: [
|
||||
{
|
||||
name: "mode",
|
||||
description: Translation.tr("Set the current API provider"),
|
||||
execute: (args) => {
|
||||
Booru.setProvider(args[0]);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "clear",
|
||||
description: Translation.tr("Clear the current list of images"),
|
||||
execute: () => {
|
||||
Booru.clearResponses();
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "next",
|
||||
description: Translation.tr("Get the next page of results"),
|
||||
execute: () => {
|
||||
if (root.responses.length > 0) {
|
||||
const lastResponse = root.responses[root.responses.length - 1];
|
||||
root.handleInput(`${lastResponse.tags.join(" ")} ${parseInt(lastResponse.page) + 1}`);
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "safe",
|
||||
description: Translation.tr("Disable NSFW content"),
|
||||
execute: () => {
|
||||
Persistent.states.booru.allowNsfw = false;
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "lewd",
|
||||
description: Translation.tr("Allow NSFW content"),
|
||||
execute: () => {
|
||||
Persistent.states.booru.allowNsfw = true;
|
||||
}
|
||||
},
|
||||
]
|
||||
|
||||
function handleInput(inputText) {
|
||||
if (inputText.startsWith(root.commandPrefix)) {
|
||||
// Handle special commands
|
||||
const command = inputText.split(" ")[0].substring(1);
|
||||
const args = inputText.split(" ").slice(1);
|
||||
const commandObj = root.allCommands.find(cmd => cmd.name === `${command}`);
|
||||
if (commandObj) {
|
||||
commandObj.execute(args);
|
||||
} else {
|
||||
Booru.addSystemMessage(Translation.tr("Unknown command: ") + command);
|
||||
}
|
||||
}
|
||||
else if (inputText.trim() == "+") {
|
||||
if (root.responses.length > 0) {
|
||||
const lastResponse = root.responses[root.responses.length - 1]
|
||||
root.handleInput(lastResponse.tags.join(" ") + ` ${parseInt(lastResponse.page) + 1}`);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Create tag list
|
||||
const tagList = inputText.split(/\s+/).filter(tag => tag.length > 0);
|
||||
let pageIndex = 1;
|
||||
for (let i = 0; i < tagList.length; ++i) { // Detect page number
|
||||
if (/^\d+$/.test(tagList[i])) {
|
||||
pageIndex = parseInt(tagList[i], 10);
|
||||
tagList.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
Booru.makeRequest(tagList, Persistent.states.booru.allowNsfw, Config.options.sidebar.booru.limit, pageIndex);
|
||||
}
|
||||
}
|
||||
|
||||
onFocusChanged: (focus) => {
|
||||
if (focus) {
|
||||
tagInputField.forceActiveFocus()
|
||||
}
|
||||
}
|
||||
|
||||
Keys.onPressed: (event) => {
|
||||
tagInputField.forceActiveFocus()
|
||||
if (event.modifiers === Qt.NoModifier) {
|
||||
if (event.key === Qt.Key_PageUp) {
|
||||
booruResponseListView.contentY = Math.max(0, booruResponseListView.contentY - booruResponseListView.height / 2)
|
||||
event.accepted = true
|
||||
} else if (event.key === Qt.Key_PageDown) {
|
||||
booruResponseListView.contentY = Math.min(booruResponseListView.contentHeight - booruResponseListView.height / 2, booruResponseListView.contentY + booruResponseListView.height / 2)
|
||||
event.accepted = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ColumnLayout {
|
||||
id: columnLayout
|
||||
anchors.fill: parent
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
layer.enabled: true
|
||||
layer.effect: OpacityMask {
|
||||
maskSource: Rectangle {
|
||||
width: swipeView.width
|
||||
height: swipeView.height
|
||||
radius: Appearance.rounding.small
|
||||
}
|
||||
}
|
||||
|
||||
ScrollEdgeFade {
|
||||
target: booruResponseListView
|
||||
vertical: true
|
||||
}
|
||||
|
||||
StyledListView { // Booru responses
|
||||
id: booruResponseListView
|
||||
anchors.fill: parent
|
||||
spacing: 10
|
||||
|
||||
touchpadScrollFactor: Config.options.interactions.scrolling.touchpadScrollFactor * 1.4
|
||||
mouseScrollFactor: Config.options.interactions.scrolling.mouseScrollFactor * 1.4
|
||||
|
||||
property int lastResponseLength: 0
|
||||
|
||||
model: ScriptModel {
|
||||
values: {
|
||||
if(root.responses.length > booruResponseListView.lastResponseLength) {
|
||||
if (booruResponseListView.lastResponseLength > 0 && root.responses[booruResponseListView.lastResponseLength].provider != "system")
|
||||
booruResponseListView.contentY = booruResponseListView.contentY + root.scrollOnNewResponse
|
||||
booruResponseListView.lastResponseLength = root.responses.length
|
||||
}
|
||||
return root.responses
|
||||
}
|
||||
}
|
||||
delegate: BooruResponse {
|
||||
responseData: modelData
|
||||
tagInputField: root.inputField
|
||||
previewDownloadPath: root.previewDownloadPath
|
||||
downloadPath: root.downloadPath
|
||||
nsfwPath: root.nsfwPath
|
||||
}
|
||||
}
|
||||
|
||||
PagePlaceholder {
|
||||
id: placeholderItem
|
||||
shown: root.responses.length === 0
|
||||
icon: "bookmark_heart"
|
||||
title: Translation.tr("Anime boorus")
|
||||
description: ""
|
||||
}
|
||||
|
||||
Item { // Queries awaiting response
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.margins: 10
|
||||
implicitHeight: pendingBackground.implicitHeight
|
||||
opacity: Booru.runningRequests > 0 ? 1 : 0
|
||||
visible: opacity > 0
|
||||
|
||||
Behavior on opacity {
|
||||
animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: pendingBackground
|
||||
color: Appearance.m3colors.m3inverseSurface
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
implicitHeight: pendingText.implicitHeight + 12 * 2
|
||||
radius: Appearance.rounding.verysmall
|
||||
|
||||
StyledText {
|
||||
id: pendingText
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.leftMargin: 12
|
||||
anchors.rightMargin: 12
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
font.pixelSize: Appearance.font.pixelSize.smaller
|
||||
color: Appearance.m3colors.m3inverseOnSurface
|
||||
wrapMode: Text.Wrap
|
||||
text: Translation.tr("%1 queries pending").arg(Booru.runningRequests)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DescriptionBox { // Tag suggestion description
|
||||
text: root.suggestionList[tagSuggestions.selectedIndex]?.description ?? ""
|
||||
showArrows: root.suggestionList.length > 1
|
||||
}
|
||||
|
||||
FlowButtonGroup { // Tag suggestions
|
||||
id: tagSuggestions
|
||||
visible: root.suggestionList.length > 0 && tagInputField.text.length > 0
|
||||
property int selectedIndex: 0
|
||||
Layout.fillWidth: true
|
||||
spacing: 5
|
||||
|
||||
Repeater {
|
||||
id: tagSuggestionRepeater
|
||||
model: {
|
||||
tagSuggestions.selectedIndex = 0
|
||||
return root.suggestionList.slice(0, 10)
|
||||
}
|
||||
delegate: ApiCommandButton {
|
||||
id: tagButton
|
||||
colBackground: tagSuggestions.selectedIndex === index ? Appearance.colors.colSecondaryContainerHover : Appearance.colors.colSecondaryContainer
|
||||
bounce: false
|
||||
contentItem: RowLayout {
|
||||
anchors.centerIn: parent
|
||||
spacing: 5
|
||||
StyledText {
|
||||
Layout.fillWidth: false
|
||||
font.pixelSize: Appearance.font.pixelSize.small
|
||||
color: Appearance.colors.colOnSecondaryContainer
|
||||
horizontalAlignment: Text.AlignRight
|
||||
text: modelData.displayName ?? modelData.name
|
||||
}
|
||||
StyledText {
|
||||
Layout.fillWidth: false
|
||||
visible: modelData.count !== undefined
|
||||
font.pixelSize: Appearance.font.pixelSize.smaller
|
||||
color: Appearance.colors.colOnSecondaryContainer
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
text: modelData.count ?? ""
|
||||
}
|
||||
}
|
||||
|
||||
onHoveredChanged: {
|
||||
if (tagButton.hovered) {
|
||||
tagSuggestions.selectedIndex = index;
|
||||
}
|
||||
}
|
||||
onClicked: {
|
||||
tagSuggestions.acceptTag(modelData.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function acceptTag(tag) {
|
||||
const words = tagInputField.text.trim().split(/\s+/);
|
||||
if (words.length > 0) {
|
||||
words[words.length - 1] = tag;
|
||||
} else {
|
||||
words.push(tag);
|
||||
}
|
||||
const updatedText = words.join(" ") + " ";
|
||||
tagInputField.text = updatedText;
|
||||
tagInputField.cursorPosition = tagInputField.text.length;
|
||||
tagInputField.forceActiveFocus();
|
||||
}
|
||||
|
||||
function acceptSelectedTag() {
|
||||
if (tagSuggestions.selectedIndex >= 0 && tagSuggestions.selectedIndex < tagSuggestionRepeater.count) {
|
||||
const tag = root.suggestionList[tagSuggestions.selectedIndex].name;
|
||||
tagSuggestions.acceptTag(tag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle { // Tag input area
|
||||
id: tagInputContainer
|
||||
property real columnSpacing: 5
|
||||
Layout.fillWidth: true
|
||||
radius: Appearance.rounding.small
|
||||
color: Appearance.colors.colLayer1
|
||||
implicitWidth: tagInputField.implicitWidth
|
||||
implicitHeight: Math.max(inputFieldRowLayout.implicitHeight + inputFieldRowLayout.anchors.topMargin
|
||||
+ commandButtonsRow.implicitHeight + commandButtonsRow.anchors.bottomMargin + columnSpacing, 45)
|
||||
clip: true
|
||||
border.color: Appearance.colors.colOutlineVariant
|
||||
border.width: 1
|
||||
|
||||
Behavior on implicitHeight {
|
||||
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
|
||||
}
|
||||
|
||||
RowLayout { // Input field and send button
|
||||
id: inputFieldRowLayout
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 5
|
||||
spacing: 0
|
||||
|
||||
StyledTextArea { // The actual TextArea
|
||||
id: tagInputField
|
||||
wrapMode: TextArea.Wrap
|
||||
Layout.fillWidth: true
|
||||
padding: 10
|
||||
color: activeFocus ? Appearance.m3colors.m3onSurface : Appearance.m3colors.m3onSurfaceVariant
|
||||
renderType: Text.NativeRendering
|
||||
placeholderText: Translation.tr('Enter tags, or "%1" for commands').arg(root.commandPrefix)
|
||||
|
||||
background: null
|
||||
|
||||
property Timer searchTimer: Timer { // Timer for tag suggestions
|
||||
interval: root.tagSuggestionDelay
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
const inputText = tagInputField.text
|
||||
const words = inputText.trim().split(/\s+/);
|
||||
if (words.length > 0) {
|
||||
Booru.triggerTagSearch(words[words.length - 1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onTextChanged: { // Handle tag suggestions
|
||||
if(tagInputField.text.length === 0) {
|
||||
root.suggestionQuery = ""
|
||||
root.suggestionList = []
|
||||
searchTimer.stop();
|
||||
return
|
||||
}
|
||||
if(tagInputField.text.startsWith(`${root.commandPrefix}mode`)) {
|
||||
root.suggestionQuery = tagInputField.text.split(" ")[1] ?? ""
|
||||
const providerResults = Fuzzy.go(root.suggestionQuery, Booru.providerList.map(provider => {
|
||||
return {
|
||||
name: Fuzzy.prepare(provider),
|
||||
obj: provider,
|
||||
}
|
||||
}), {
|
||||
all: true,
|
||||
key: "name"
|
||||
})
|
||||
root.suggestionList = providerResults.map(provider => {
|
||||
return {
|
||||
name: `${tagInputField.text.trim().split(" ").length == 1 ? (root.commandPrefix + "mode ") : ""}${provider.target}`,
|
||||
displayName: `${Booru.providers[provider.target].name}`,
|
||||
description: `${Booru.providers[provider.target].description}`,
|
||||
}
|
||||
})
|
||||
searchTimer.stop();
|
||||
return
|
||||
}
|
||||
if(tagInputField.text.startsWith(root.commandPrefix)) {
|
||||
root.suggestionQuery = tagInputField.text
|
||||
root.suggestionList = root.allCommands.filter(cmd => cmd.name.startsWith(tagInputField.text.substring(1))).map(cmd => {
|
||||
return {
|
||||
name: `${root.commandPrefix}${cmd.name}`,
|
||||
description: `${cmd.description}`,
|
||||
}
|
||||
})
|
||||
searchTimer.stop();
|
||||
return
|
||||
}
|
||||
searchTimer.restart();
|
||||
}
|
||||
|
||||
function accept() {
|
||||
root.handleInput(text)
|
||||
text = ""
|
||||
}
|
||||
|
||||
Keys.onPressed: (event) => {
|
||||
if (event.key === Qt.Key_Tab) {
|
||||
tagSuggestions.acceptSelectedTag();
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_Up) {
|
||||
tagSuggestions.selectedIndex = Math.max(0, tagSuggestions.selectedIndex - 1);
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_Down) {
|
||||
tagSuggestions.selectedIndex = Math.min(root.suggestionList.length - 1, tagSuggestions.selectedIndex + 1);
|
||||
event.accepted = true;
|
||||
} else if ((event.key === Qt.Key_Enter || event.key === Qt.Key_Return)) {
|
||||
if (event.modifiers & Qt.ShiftModifier) {
|
||||
// Insert newline
|
||||
tagInputField.insert(tagInputField.cursorPosition, "\n")
|
||||
event.accepted = true
|
||||
} else { // Accept text
|
||||
const inputText = tagInputField.text
|
||||
root.handleInput(inputText)
|
||||
tagInputField.clear()
|
||||
event.accepted = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RippleButton { // Send button
|
||||
id: sendButton
|
||||
Layout.alignment: Qt.AlignTop
|
||||
Layout.rightMargin: 5
|
||||
implicitWidth: 40
|
||||
implicitHeight: 40
|
||||
buttonRadius: Appearance.rounding.small
|
||||
enabled: tagInputField.text.length > 0
|
||||
toggled: enabled
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: sendButton.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
onClicked: {
|
||||
const inputText = tagInputField.text
|
||||
root.handleInput(inputText)
|
||||
tagInputField.clear()
|
||||
}
|
||||
}
|
||||
|
||||
contentItem: MaterialSymbol {
|
||||
anchors.centerIn: parent
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
iconSize: Appearance.font.pixelSize.larger
|
||||
// fill: sendButton.enabled ? 1 : 0
|
||||
color: sendButton.enabled ? Appearance.m3colors.m3onPrimary : Appearance.colors.colOnLayer2Disabled
|
||||
text: "send"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout { // Controls
|
||||
id: commandButtonsRow
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 5
|
||||
anchors.leftMargin: 5
|
||||
anchors.rightMargin: 5
|
||||
spacing: 5
|
||||
|
||||
property var commandsShown: [
|
||||
{
|
||||
name: "mode",
|
||||
sendDirectly: false,
|
||||
},
|
||||
{
|
||||
name: "clear",
|
||||
sendDirectly: true,
|
||||
},
|
||||
]
|
||||
|
||||
ApiInputBoxIndicator { // Tool indicator
|
||||
icon: "api"
|
||||
text: Booru.providers[Booru.currentProvider].name
|
||||
tooltipText: Translation.tr("Current API endpoint: %1\nSet it with %2mode PROVIDER")
|
||||
.arg(Booru.providers[Booru.currentProvider].url)
|
||||
.arg(root.commandPrefix)
|
||||
}
|
||||
|
||||
StyledText {
|
||||
font.pixelSize: Appearance.font.pixelSize.large
|
||||
color: Appearance.colors.colOnLayer1
|
||||
text: "•"
|
||||
}
|
||||
|
||||
MouseArea { // NSFW toggle
|
||||
visible: width > 0
|
||||
implicitWidth: switchesRow.implicitWidth
|
||||
Layout.fillHeight: true
|
||||
|
||||
hoverEnabled: true
|
||||
PointingHandInteraction {}
|
||||
onPressed: {
|
||||
nsfwSwitch.checked = !nsfwSwitch.checked
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: switchesRow
|
||||
spacing: 5
|
||||
anchors.centerIn: parent
|
||||
|
||||
StyledText {
|
||||
Layout.fillHeight: true
|
||||
Layout.leftMargin: 10
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
font.pixelSize: Appearance.font.pixelSize.smaller
|
||||
color: nsfwSwitch.enabled ? Appearance.colors.colOnLayer1 : Appearance.m3colors.m3outline
|
||||
text: Translation.tr("Allow NSFW")
|
||||
}
|
||||
StyledSwitch {
|
||||
id: nsfwSwitch
|
||||
enabled: Booru.currentProvider !== "zerochan"
|
||||
scale: 0.6
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
checked: (Persistent.states.booru.allowNsfw && Booru.currentProvider !== "zerochan")
|
||||
onCheckedChanged: {
|
||||
if (!nsfwSwitch.enabled) return;
|
||||
Persistent.states.booru.allowNsfw = checked;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Item { Layout.fillWidth: true }
|
||||
|
||||
ButtonGroup {
|
||||
padding: 0
|
||||
Repeater { // Command buttons
|
||||
id: commandRepeater
|
||||
model: commandButtonsRow.commandsShown
|
||||
delegate: ApiCommandButton {
|
||||
property string commandRepresentation: `${root.commandPrefix}${modelData.name}`
|
||||
buttonText: commandRepresentation
|
||||
colBackground: Appearance.colors.colLayer2
|
||||
|
||||
downAction: () => {
|
||||
if (modelData.sendDirectly) {
|
||||
root.handleInput(commandRepresentation)
|
||||
} else {
|
||||
tagInputField.text = commandRepresentation + " "
|
||||
tagInputField.cursorPosition = tagInputField.text.length
|
||||
tagInputField.forceActiveFocus()
|
||||
}
|
||||
if (modelData.name === "clear") {
|
||||
tagInputField.text = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user