mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2025-12-07 22:15:38 -05:00
filebrowser: improved file browser
This commit is contained in:
214
Modals/FileBrowser/FileBrowserGridDelegate.qml
Normal file
214
Modals/FileBrowser/FileBrowserGridDelegate.qml
Normal file
@@ -0,0 +1,214 @@
|
||||
import QtQuick
|
||||
import QtQuick.Effects
|
||||
import qs.Common
|
||||
import qs.Widgets
|
||||
|
||||
StyledRect {
|
||||
id: delegateRoot
|
||||
|
||||
required property bool fileIsDir
|
||||
required property string filePath
|
||||
required property string fileName
|
||||
required property int index
|
||||
|
||||
property bool weMode: false
|
||||
property var iconSizes: [80, 120, 160, 200]
|
||||
property int iconSizeIndex: 1
|
||||
property int selectedIndex: -1
|
||||
property bool keyboardNavigationActive: false
|
||||
|
||||
signal itemClicked(int index, string path, string name, bool isDir)
|
||||
signal itemSelected(int index, string path, string name, bool isDir)
|
||||
|
||||
function isImageFile(fileName) {
|
||||
if (!fileName) {
|
||||
return false
|
||||
}
|
||||
const ext = fileName.toLowerCase().split('.').pop()
|
||||
return ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg'].includes(ext)
|
||||
}
|
||||
|
||||
function getFileIcon(fileName, isDir) {
|
||||
if (isDir) {
|
||||
return "folder"
|
||||
}
|
||||
if (!fileName) {
|
||||
return "description"
|
||||
}
|
||||
const ext = fileName.toLowerCase().split('.').pop()
|
||||
const iconMap = {
|
||||
"mp3": 'music_note',
|
||||
"wav": 'music_note',
|
||||
"flac": 'music_note',
|
||||
"ogg": 'music_note',
|
||||
"aac": 'music_note',
|
||||
"mp4": 'movie',
|
||||
"mkv": 'movie',
|
||||
"avi": 'movie',
|
||||
"mov": 'movie',
|
||||
"webm": 'movie',
|
||||
"flv": 'movie',
|
||||
"wmv": 'movie',
|
||||
"jpg": 'image',
|
||||
"jpeg": 'image',
|
||||
"png": 'image',
|
||||
"gif": 'image',
|
||||
"bmp": 'image',
|
||||
"webp": 'image',
|
||||
"svg": 'image',
|
||||
"pdf": 'picture_as_pdf',
|
||||
"zip": 'folder_zip',
|
||||
"rar": 'folder_zip',
|
||||
"7z": 'folder_zip',
|
||||
"tar": 'folder_zip',
|
||||
"gz": 'folder_zip',
|
||||
"bz2": 'folder_zip',
|
||||
"xz": 'folder_zip',
|
||||
"txt": 'description',
|
||||
"md": 'description',
|
||||
"doc": 'description',
|
||||
"docx": 'description',
|
||||
"odt": 'description',
|
||||
"rtf": 'description',
|
||||
"sh": 'terminal',
|
||||
"py": 'code',
|
||||
"js": 'code',
|
||||
"ts": 'code',
|
||||
"cpp": 'code',
|
||||
"c": 'code',
|
||||
"h": 'code',
|
||||
"java": 'code',
|
||||
"go": 'code',
|
||||
"rs": 'code',
|
||||
"php": 'code',
|
||||
"rb": 'code',
|
||||
"qml": 'code',
|
||||
"html": 'code',
|
||||
"css": 'code',
|
||||
"json": 'data_object',
|
||||
"xml": 'data_object',
|
||||
"yaml": 'data_object',
|
||||
"yml": 'data_object',
|
||||
"toml": 'data_object'
|
||||
}
|
||||
return iconMap[ext] || 'description'
|
||||
}
|
||||
|
||||
width: weMode ? 245 : iconSizes[iconSizeIndex] + 16
|
||||
height: weMode ? 205 : iconSizes[iconSizeIndex] + 48
|
||||
radius: Theme.cornerRadius
|
||||
color: {
|
||||
if (keyboardNavigationActive && delegateRoot.index === selectedIndex)
|
||||
return Theme.surfacePressed
|
||||
|
||||
return mouseArea.containsMouse ? Theme.surfaceContainerHigh : "transparent"
|
||||
}
|
||||
border.color: keyboardNavigationActive && delegateRoot.index === selectedIndex ? Theme.primary : "transparent"
|
||||
border.width: (keyboardNavigationActive && delegateRoot.index === selectedIndex) ? 2 : 0
|
||||
|
||||
Component.onCompleted: {
|
||||
if (keyboardNavigationActive && delegateRoot.index === selectedIndex)
|
||||
itemSelected(delegateRoot.index, delegateRoot.filePath, delegateRoot.fileName, delegateRoot.fileIsDir)
|
||||
}
|
||||
|
||||
onSelectedIndexChanged: {
|
||||
if (keyboardNavigationActive && selectedIndex === delegateRoot.index)
|
||||
itemSelected(delegateRoot.index, delegateRoot.filePath, delegateRoot.fileName, delegateRoot.fileIsDir)
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Item {
|
||||
width: weMode ? 225 : (iconSizes[iconSizeIndex] - 8)
|
||||
height: weMode ? 165 : (iconSizes[iconSizeIndex] - 8)
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
CachingImage {
|
||||
id: gridPreviewImage
|
||||
anchors.fill: parent
|
||||
anchors.margins: 2
|
||||
property var weExtensions: [".jpg", ".jpeg", ".png", ".webp", ".gif", ".bmp", ".tga"]
|
||||
property int weExtIndex: 0
|
||||
source: {
|
||||
if (weMode && delegateRoot.fileIsDir) {
|
||||
return "file://" + delegateRoot.filePath + "/preview" + weExtensions[weExtIndex]
|
||||
}
|
||||
return (!delegateRoot.fileIsDir && isImageFile(delegateRoot.fileName)) ? ("file://" + delegateRoot.filePath) : ""
|
||||
}
|
||||
onStatusChanged: {
|
||||
if (weMode && delegateRoot.fileIsDir && status === Image.Error) {
|
||||
if (weExtIndex < weExtensions.length - 1) {
|
||||
weExtIndex++
|
||||
source = "file://" + delegateRoot.filePath + "/preview" + weExtensions[weExtIndex]
|
||||
} else {
|
||||
source = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
maxCacheSize: weMode ? 225 : iconSizes[iconSizeIndex]
|
||||
visible: false
|
||||
}
|
||||
|
||||
MultiEffect {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 2
|
||||
source: gridPreviewImage
|
||||
maskEnabled: true
|
||||
maskSource: gridImageMask
|
||||
visible: gridPreviewImage.status === Image.Ready && ((!delegateRoot.fileIsDir && isImageFile(delegateRoot.fileName)) || (weMode && delegateRoot.fileIsDir))
|
||||
maskThresholdMin: 0.5
|
||||
maskSpreadAtMin: 1
|
||||
}
|
||||
|
||||
Item {
|
||||
id: gridImageMask
|
||||
anchors.fill: parent
|
||||
anchors.margins: 2
|
||||
layer.enabled: true
|
||||
layer.smooth: true
|
||||
visible: false
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: Theme.cornerRadius
|
||||
color: "black"
|
||||
antialiasing: true
|
||||
}
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: getFileIcon(delegateRoot.fileName, delegateRoot.fileIsDir)
|
||||
size: iconSizes[iconSizeIndex] * 0.45
|
||||
color: delegateRoot.fileIsDir ? Theme.primary : Theme.surfaceText
|
||||
visible: (!delegateRoot.fileIsDir && !isImageFile(delegateRoot.fileName)) || (delegateRoot.fileIsDir && !weMode)
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: delegateRoot.fileName || ""
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
width: delegateRoot.width - Theme.spacingM
|
||||
elide: Text.ElideRight
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
maximumLineCount: 2
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
itemClicked(delegateRoot.index, delegateRoot.filePath, delegateRoot.fileName, delegateRoot.fileIsDir)
|
||||
}
|
||||
}
|
||||
}
|
||||
219
Modals/FileBrowser/FileBrowserListDelegate.qml
Normal file
219
Modals/FileBrowser/FileBrowserListDelegate.qml
Normal file
@@ -0,0 +1,219 @@
|
||||
import QtQuick
|
||||
import QtQuick.Effects
|
||||
import qs.Common
|
||||
import qs.Widgets
|
||||
|
||||
StyledRect {
|
||||
id: listDelegateRoot
|
||||
|
||||
required property bool fileIsDir
|
||||
required property string filePath
|
||||
required property string fileName
|
||||
required property int index
|
||||
required property var fileModified
|
||||
required property int fileSize
|
||||
|
||||
property int selectedIndex: -1
|
||||
property bool keyboardNavigationActive: false
|
||||
|
||||
signal itemClicked(int index, string path, string name, bool isDir)
|
||||
signal itemSelected(int index, string path, string name, bool isDir)
|
||||
|
||||
function isImageFile(fileName) {
|
||||
if (!fileName) {
|
||||
return false
|
||||
}
|
||||
const ext = fileName.toLowerCase().split('.').pop()
|
||||
return ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg'].includes(ext)
|
||||
}
|
||||
|
||||
function getFileIcon(fileName, isDir) {
|
||||
if (isDir) {
|
||||
return "folder"
|
||||
}
|
||||
if (!fileName) {
|
||||
return "description"
|
||||
}
|
||||
const ext = fileName.toLowerCase().split('.').pop()
|
||||
const iconMap = {
|
||||
"mp3": 'music_note',
|
||||
"wav": 'music_note',
|
||||
"flac": 'music_note',
|
||||
"ogg": 'music_note',
|
||||
"aac": 'music_note',
|
||||
"mp4": 'movie',
|
||||
"mkv": 'movie',
|
||||
"avi": 'movie',
|
||||
"mov": 'movie',
|
||||
"webm": 'movie',
|
||||
"flv": 'movie',
|
||||
"wmv": 'movie',
|
||||
"jpg": 'image',
|
||||
"jpeg": 'image',
|
||||
"png": 'image',
|
||||
"gif": 'image',
|
||||
"bmp": 'image',
|
||||
"webp": 'image',
|
||||
"svg": 'image',
|
||||
"pdf": 'picture_as_pdf',
|
||||
"zip": 'folder_zip',
|
||||
"rar": 'folder_zip',
|
||||
"7z": 'folder_zip',
|
||||
"tar": 'folder_zip',
|
||||
"gz": 'folder_zip',
|
||||
"bz2": 'folder_zip',
|
||||
"xz": 'folder_zip',
|
||||
"txt": 'description',
|
||||
"md": 'description',
|
||||
"doc": 'description',
|
||||
"docx": 'description',
|
||||
"odt": 'description',
|
||||
"rtf": 'description',
|
||||
"sh": 'terminal',
|
||||
"py": 'code',
|
||||
"js": 'code',
|
||||
"ts": 'code',
|
||||
"cpp": 'code',
|
||||
"c": 'code',
|
||||
"h": 'code',
|
||||
"java": 'code',
|
||||
"go": 'code',
|
||||
"rs": 'code',
|
||||
"php": 'code',
|
||||
"rb": 'code',
|
||||
"qml": 'code',
|
||||
"html": 'code',
|
||||
"css": 'code',
|
||||
"json": 'data_object',
|
||||
"xml": 'data_object',
|
||||
"yaml": 'data_object',
|
||||
"yml": 'data_object',
|
||||
"toml": 'data_object'
|
||||
}
|
||||
return iconMap[ext] || 'description'
|
||||
}
|
||||
|
||||
function formatFileSize(size) {
|
||||
if (size < 1024)
|
||||
return size + " B"
|
||||
if (size < 1024 * 1024)
|
||||
return (size / 1024).toFixed(1) + " KB"
|
||||
if (size < 1024 * 1024 * 1024)
|
||||
return (size / (1024 * 1024)).toFixed(1) + " MB"
|
||||
return (size / (1024 * 1024 * 1024)).toFixed(1) + " GB"
|
||||
}
|
||||
|
||||
height: 44
|
||||
radius: Theme.cornerRadius
|
||||
color: {
|
||||
if (keyboardNavigationActive && listDelegateRoot.index === selectedIndex)
|
||||
return Theme.surfacePressed
|
||||
return listMouseArea.containsMouse ? Theme.surfaceContainerHigh : "transparent"
|
||||
}
|
||||
border.color: keyboardNavigationActive && listDelegateRoot.index === selectedIndex ? Theme.primary : "transparent"
|
||||
border.width: (keyboardNavigationActive && listDelegateRoot.index === selectedIndex) ? 2 : 0
|
||||
|
||||
Component.onCompleted: {
|
||||
if (keyboardNavigationActive && listDelegateRoot.index === selectedIndex)
|
||||
itemSelected(listDelegateRoot.index, listDelegateRoot.filePath, listDelegateRoot.fileName, listDelegateRoot.fileIsDir)
|
||||
}
|
||||
|
||||
onSelectedIndexChanged: {
|
||||
if (keyboardNavigationActive && selectedIndex === listDelegateRoot.index)
|
||||
itemSelected(listDelegateRoot.index, listDelegateRoot.filePath, listDelegateRoot.fileName, listDelegateRoot.fileIsDir)
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Item {
|
||||
width: 28
|
||||
height: 28
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
CachingImage {
|
||||
id: listPreviewImage
|
||||
anchors.fill: parent
|
||||
source: (!listDelegateRoot.fileIsDir && isImageFile(listDelegateRoot.fileName)) ? ("file://" + listDelegateRoot.filePath) : ""
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
maxCacheSize: 32
|
||||
visible: false
|
||||
}
|
||||
|
||||
MultiEffect {
|
||||
anchors.fill: parent
|
||||
source: listPreviewImage
|
||||
maskEnabled: true
|
||||
maskSource: listImageMask
|
||||
visible: listPreviewImage.status === Image.Ready && !listDelegateRoot.fileIsDir && isImageFile(listDelegateRoot.fileName)
|
||||
maskThresholdMin: 0.5
|
||||
maskSpreadAtMin: 1
|
||||
}
|
||||
|
||||
Item {
|
||||
id: listImageMask
|
||||
anchors.fill: parent
|
||||
layer.enabled: true
|
||||
layer.smooth: true
|
||||
visible: false
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: Theme.cornerRadius
|
||||
color: "black"
|
||||
antialiasing: true
|
||||
}
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: getFileIcon(listDelegateRoot.fileName, listDelegateRoot.fileIsDir)
|
||||
size: Theme.iconSize - 2
|
||||
color: listDelegateRoot.fileIsDir ? Theme.primary : Theme.surfaceText
|
||||
visible: listDelegateRoot.fileIsDir || !isImageFile(listDelegateRoot.fileName)
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: listDelegateRoot.fileName || ""
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
width: parent.width - 280
|
||||
elide: Text.ElideRight
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
maximumLineCount: 1
|
||||
clip: true
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: listDelegateRoot.fileIsDir ? "" : formatFileSize(listDelegateRoot.fileSize)
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceTextMedium
|
||||
width: 70
|
||||
horizontalAlignment: Text.AlignRight
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: Qt.formatDateTime(listDelegateRoot.fileModified, "MMM d, yyyy h:mm AP")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceTextMedium
|
||||
width: 140
|
||||
horizontalAlignment: Text.AlignRight
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: listMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
itemClicked(listDelegateRoot.index, listDelegateRoot.filePath, listDelegateRoot.fileName, listDelegateRoot.fileIsDir)
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
130
Modals/FileBrowser/FileBrowserNavigation.qml
Normal file
130
Modals/FileBrowser/FileBrowserNavigation.qml
Normal file
@@ -0,0 +1,130 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Widgets
|
||||
|
||||
Row {
|
||||
id: navigation
|
||||
|
||||
property string currentPath: ""
|
||||
property string homeDir: ""
|
||||
property bool backButtonFocused: false
|
||||
property bool keyboardNavigationActive: false
|
||||
property bool showSidebar: true
|
||||
property bool pathEditMode: false
|
||||
property bool pathInputHasFocus: false
|
||||
|
||||
signal navigateUp()
|
||||
signal navigateTo(string path)
|
||||
signal pathInputFocusChanged(bool hasFocus)
|
||||
|
||||
height: 40
|
||||
leftPadding: Theme.spacingM
|
||||
rightPadding: Theme.spacingM
|
||||
spacing: Theme.spacingS
|
||||
|
||||
StyledRect {
|
||||
width: 32
|
||||
height: 32
|
||||
radius: Theme.cornerRadius
|
||||
color: (backButtonMouseArea.containsMouse || (backButtonFocused && keyboardNavigationActive)) && currentPath !== homeDir ? Theme.surfaceVariant : "transparent"
|
||||
opacity: currentPath !== homeDir ? 1 : 0
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "arrow_back"
|
||||
size: Theme.iconSizeSmall
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: backButtonMouseArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: currentPath !== homeDir
|
||||
cursorShape: currentPath !== homeDir ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
enabled: currentPath !== homeDir
|
||||
onClicked: navigation.navigateUp()
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
width: Math.max(0, (parent?.width ?? 0) - 40 - Theme.spacingS - (showSidebar ? 0 : 80))
|
||||
height: 32
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
StyledRect {
|
||||
anchors.fill: parent
|
||||
radius: Theme.cornerRadius
|
||||
color: pathEditMode ? Theme.surfaceContainer : "transparent"
|
||||
border.color: pathEditMode ? Theme.primary : "transparent"
|
||||
border.width: pathEditMode ? 1 : 0
|
||||
visible: !pathEditMode
|
||||
|
||||
StyledText {
|
||||
id: pathDisplay
|
||||
text: currentPath.replace("file://", "")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
elide: Text.ElideMiddle
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
maximumLineCount: 1
|
||||
wrapMode: Text.NoWrap
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.IBeamCursor
|
||||
onClicked: {
|
||||
pathEditMode = true
|
||||
pathInput.text = currentPath.replace("file://", "")
|
||||
Qt.callLater(() => pathInput.forceActiveFocus())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankTextField {
|
||||
id: pathInput
|
||||
anchors.fill: parent
|
||||
visible: pathEditMode
|
||||
topPadding: Theme.spacingXS
|
||||
bottomPadding: Theme.spacingXS
|
||||
onAccepted: {
|
||||
const newPath = text.trim()
|
||||
if (newPath !== "") {
|
||||
navigation.navigateTo(newPath)
|
||||
}
|
||||
pathEditMode = false
|
||||
}
|
||||
Keys.onEscapePressed: {
|
||||
pathEditMode = false
|
||||
}
|
||||
Keys.onDownPressed: {
|
||||
pathEditMode = false
|
||||
}
|
||||
onActiveFocusChanged: {
|
||||
navigation.pathInputFocusChanged(activeFocus)
|
||||
if (!activeFocus && pathEditMode) {
|
||||
pathEditMode = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
spacing: Theme.spacingXS
|
||||
visible: !showSidebar
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
DankActionButton {
|
||||
circular: false
|
||||
iconName: "sort"
|
||||
iconSize: Theme.iconSize - 6
|
||||
iconColor: Theme.surfaceText
|
||||
}
|
||||
}
|
||||
}
|
||||
127
Modals/FileBrowser/FileBrowserOverwriteDialog.qml
Normal file
127
Modals/FileBrowser/FileBrowserOverwriteDialog.qml
Normal file
@@ -0,0 +1,127 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Widgets
|
||||
|
||||
Item {
|
||||
id: overwriteDialog
|
||||
|
||||
property bool showDialog: false
|
||||
property string pendingFilePath: ""
|
||||
|
||||
signal confirmed(string filePath)
|
||||
signal cancelled()
|
||||
|
||||
visible: showDialog
|
||||
focus: showDialog
|
||||
|
||||
Keys.onEscapePressed: {
|
||||
cancelled()
|
||||
}
|
||||
|
||||
Keys.onReturnPressed: {
|
||||
confirmed(pendingFilePath)
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Theme.shadowStrong
|
||||
opacity: 0.8
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
cancelled()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledRect {
|
||||
anchors.centerIn: parent
|
||||
width: 400
|
||||
height: 160
|
||||
color: Theme.surfaceContainer
|
||||
radius: Theme.cornerRadius
|
||||
border.color: Theme.outlineMedium
|
||||
border.width: 1
|
||||
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
width: parent.width - Theme.spacingL * 2
|
||||
spacing: Theme.spacingM
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("File Already Exists")
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("A file with this name already exists. Do you want to overwrite it?")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceTextMedium
|
||||
width: parent.width
|
||||
wrapMode: Text.WordWrap
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
spacing: Theme.spacingM
|
||||
|
||||
StyledRect {
|
||||
width: 80
|
||||
height: 36
|
||||
radius: Theme.cornerRadius
|
||||
color: cancelArea.containsMouse ? Theme.surfaceVariantHover : Theme.surfaceVariant
|
||||
border.color: Theme.outline
|
||||
border.width: 1
|
||||
|
||||
StyledText {
|
||||
anchors.centerIn: parent
|
||||
text: I18n.tr("Cancel")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: cancelArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
cancelled()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledRect {
|
||||
width: 90
|
||||
height: 36
|
||||
radius: Theme.cornerRadius
|
||||
color: overwriteArea.containsMouse ? Qt.darker(Theme.primary, 1.1) : Theme.primary
|
||||
|
||||
StyledText {
|
||||
anchors.centerIn: parent
|
||||
text: I18n.tr("Overwrite")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.background
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: overwriteArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
confirmed(pendingFilePath)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
74
Modals/FileBrowser/FileBrowserSaveRow.qml
Normal file
74
Modals/FileBrowser/FileBrowserSaveRow.qml
Normal file
@@ -0,0 +1,74 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Widgets
|
||||
|
||||
Row {
|
||||
id: saveRow
|
||||
|
||||
property bool saveMode: false
|
||||
property string defaultFileName: ""
|
||||
property string currentPath: ""
|
||||
|
||||
signal saveRequested(string filePath)
|
||||
|
||||
height: saveMode ? 40 : 0
|
||||
visible: saveMode
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankTextField {
|
||||
id: fileNameInput
|
||||
|
||||
width: parent.width - saveButton.width - Theme.spacingM
|
||||
height: 40
|
||||
text: defaultFileName
|
||||
placeholderText: I18n.tr("Enter filename...")
|
||||
ignoreLeftRightKeys: false
|
||||
focus: saveMode
|
||||
topPadding: Theme.spacingS
|
||||
bottomPadding: Theme.spacingS
|
||||
Component.onCompleted: {
|
||||
if (saveMode)
|
||||
Qt.callLater(() => {
|
||||
forceActiveFocus()
|
||||
})
|
||||
}
|
||||
onAccepted: {
|
||||
if (text.trim() !== "") {
|
||||
var basePath = currentPath.replace(/^file:\/\//, '')
|
||||
var fullPath = basePath + "/" + text.trim()
|
||||
fullPath = fullPath.replace(/\/+/g, '/')
|
||||
saveRequested(fullPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledRect {
|
||||
id: saveButton
|
||||
|
||||
width: 80
|
||||
height: 40
|
||||
color: fileNameInput.text.trim() !== "" ? Theme.primary : Theme.surfaceVariant
|
||||
radius: Theme.cornerRadius
|
||||
|
||||
StyledText {
|
||||
anchors.centerIn: parent
|
||||
text: I18n.tr("Save")
|
||||
color: fileNameInput.text.trim() !== "" ? Theme.primaryText : Theme.surfaceVariantText
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
}
|
||||
|
||||
StateLayer {
|
||||
stateColor: Theme.primary
|
||||
cornerRadius: Theme.cornerRadius
|
||||
enabled: fileNameInput.text.trim() !== ""
|
||||
onClicked: {
|
||||
if (fileNameInput.text.trim() !== "") {
|
||||
var basePath = currentPath.replace(/^file:\/\//, '')
|
||||
var fullPath = basePath + "/" + fileNameInput.text.trim()
|
||||
fullPath = fullPath.replace(/\/+/g, '/')
|
||||
saveRequested(fullPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
70
Modals/FileBrowser/FileBrowserSidebar.qml
Normal file
70
Modals/FileBrowser/FileBrowserSidebar.qml
Normal file
@@ -0,0 +1,70 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Widgets
|
||||
|
||||
StyledRect {
|
||||
id: sidebar
|
||||
|
||||
property var quickAccessLocations: []
|
||||
property string currentPath: ""
|
||||
signal locationSelected(string path)
|
||||
|
||||
width: 200
|
||||
color: Theme.surface
|
||||
clip: true
|
||||
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingS
|
||||
spacing: 4
|
||||
|
||||
StyledText {
|
||||
text: "Quick Access"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceTextMedium
|
||||
font.weight: Font.Medium
|
||||
leftPadding: Theme.spacingS
|
||||
bottomPadding: Theme.spacingXS
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: quickAccessLocations
|
||||
|
||||
StyledRect {
|
||||
width: parent?.width ?? 0
|
||||
height: 38
|
||||
radius: Theme.cornerRadius
|
||||
color: quickAccessMouseArea.containsMouse ? Theme.surfaceContainerHigh : (currentPath === modelData?.path ? Theme.surfacePressed : "transparent")
|
||||
|
||||
Row {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankIcon {
|
||||
name: modelData?.icon ?? ""
|
||||
size: Theme.iconSize - 2
|
||||
color: currentPath === modelData?.path ? Theme.primary : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: modelData?.name ?? ""
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: currentPath === modelData?.path ? Theme.primary : Theme.surfaceText
|
||||
font.weight: currentPath === modelData?.path ? Font.Medium : Font.Normal
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: quickAccessMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: locationSelected(modelData?.path ?? "")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
183
Modals/FileBrowser/FileBrowserSortMenu.qml
Normal file
183
Modals/FileBrowser/FileBrowserSortMenu.qml
Normal file
@@ -0,0 +1,183 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Widgets
|
||||
|
||||
StyledRect {
|
||||
id: sortMenu
|
||||
|
||||
property string sortBy: "name"
|
||||
property bool sortAscending: true
|
||||
|
||||
signal sortBySelected(string value)
|
||||
signal sortOrderSelected(bool ascending)
|
||||
|
||||
width: 200
|
||||
height: sortColumn.height + Theme.spacingM * 2
|
||||
color: Theme.surfaceContainer
|
||||
radius: Theme.cornerRadius
|
||||
border.color: Theme.outlineMedium
|
||||
border.width: 1
|
||||
visible: false
|
||||
z: 100
|
||||
|
||||
Column {
|
||||
id: sortColumn
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.margins: Theme.spacingM
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
StyledText {
|
||||
text: "Sort By"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceTextMedium
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: [{
|
||||
"name": "Name",
|
||||
"value": "name"
|
||||
}, {
|
||||
"name": "Size",
|
||||
"value": "size"
|
||||
}, {
|
||||
"name": "Modified",
|
||||
"value": "modified"
|
||||
}, {
|
||||
"name": "Type",
|
||||
"value": "type"
|
||||
}]
|
||||
|
||||
StyledRect {
|
||||
width: sortColumn?.width ?? 0
|
||||
height: 32
|
||||
radius: Theme.cornerRadius
|
||||
color: sortMouseArea.containsMouse ? Theme.surfaceVariant : (sortBy === modelData?.value ? Theme.surfacePressed : "transparent")
|
||||
|
||||
Row {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankIcon {
|
||||
name: sortBy === modelData?.value ? "check" : ""
|
||||
size: Theme.iconSizeSmall
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: sortBy === modelData?.value
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: modelData?.name ?? ""
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: sortBy === modelData?.value ? Theme.primary : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: sortMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
sortMenu.sortBySelected(modelData?.value ?? "name")
|
||||
sortMenu.visible = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledRect {
|
||||
width: sortColumn.width
|
||||
height: 1
|
||||
color: Theme.outline
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "Order"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceTextMedium
|
||||
font.weight: Font.Medium
|
||||
topPadding: Theme.spacingXS
|
||||
}
|
||||
|
||||
StyledRect {
|
||||
width: sortColumn?.width ?? 0
|
||||
height: 32
|
||||
radius: Theme.cornerRadius
|
||||
color: ascMouseArea.containsMouse ? Theme.surfaceVariant : (sortAscending ? Theme.surfacePressed : "transparent")
|
||||
|
||||
Row {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankIcon {
|
||||
name: "arrow_upward"
|
||||
size: Theme.iconSizeSmall
|
||||
color: sortAscending ? Theme.primary : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "Ascending"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: sortAscending ? Theme.primary : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: ascMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
sortMenu.sortOrderSelected(true)
|
||||
sortMenu.visible = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledRect {
|
||||
width: sortColumn?.width ?? 0
|
||||
height: 32
|
||||
radius: Theme.cornerRadius
|
||||
color: descMouseArea.containsMouse ? Theme.surfaceVariant : (!sortAscending ? Theme.surfacePressed : "transparent")
|
||||
|
||||
Row {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankIcon {
|
||||
name: "arrow_downward"
|
||||
size: Theme.iconSizeSmall
|
||||
color: !sortAscending ? Theme.primary : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "Descending"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: !sortAscending ? Theme.primary : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: descMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
sortMenu.sortOrderSelected(false)
|
||||
sortMenu.visible = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user