forked from qt-creator/qt-creator
QmlDesigner: Implement adding and removing asset folders
Also corrected assets view margins and few relevants tweaks. Task-number: QDS-5795 Change-Id: Ieeb68584bcb261422f48ec1a865f510a00c251f5 Reviewed-by: Miikka Heikkinen <miikka.heikkinen@qt.io> Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
This commit is contained in:
@@ -36,7 +36,9 @@ Item {
|
|||||||
|
|
||||||
property var selectedAssets: ({})
|
property var selectedAssets: ({})
|
||||||
property int allExpandedState: 0
|
property int allExpandedState: 0
|
||||||
property string delFilePath: ""
|
property string contextFilePath: ""
|
||||||
|
property var contextDir: undefined
|
||||||
|
property bool isDirContextMenu: false
|
||||||
|
|
||||||
DropArea {
|
DropArea {
|
||||||
id: dropArea
|
id: dropArea
|
||||||
@@ -67,6 +69,19 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MouseArea { // right clicking the empty area of the view
|
||||||
|
anchors.fill: parent
|
||||||
|
acceptedButtons: Qt.RightButton
|
||||||
|
onClicked: {
|
||||||
|
if (!assetsModel.isEmpty) {
|
||||||
|
contextFilePath = ""
|
||||||
|
contextDir = assetsModel.rootDir()
|
||||||
|
isDirContextMenu = false
|
||||||
|
contextMenu.popup()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// called from C++ to close context menu on focus out
|
// called from C++ to close context menu on focus out
|
||||||
function handleViewFocusOut()
|
function handleViewFocusOut()
|
||||||
{
|
{
|
||||||
@@ -75,9 +90,139 @@ Item {
|
|||||||
selectedAssetsChanged()
|
selectedAssetsChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Dialog {
|
||||||
|
id: newFolderDialog
|
||||||
|
|
||||||
|
title: qsTr("Create new folder")
|
||||||
|
anchors.centerIn: parent
|
||||||
|
closePolicy: Popup.CloseOnEscape
|
||||||
|
modal: true
|
||||||
|
|
||||||
|
contentItem: Column {
|
||||||
|
spacing: 2
|
||||||
|
|
||||||
|
Row {
|
||||||
|
Text {
|
||||||
|
text: qsTr("Folder Name: ")
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
color: StudioTheme.Values.themeTextColor
|
||||||
|
}
|
||||||
|
|
||||||
|
StudioControls.TextField {
|
||||||
|
id: folderName
|
||||||
|
|
||||||
|
actionIndicator.visible: false
|
||||||
|
translationIndicator.visible: false
|
||||||
|
|
||||||
|
Keys.onEnterPressed: btnCreate.onClicked()
|
||||||
|
Keys.onReturnPressed: btnCreate.onClicked()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: qsTr("Folder Name cannot be empty.")
|
||||||
|
color: "#ff0000"
|
||||||
|
anchors.right: parent.right
|
||||||
|
visible: folderName.text === ""
|
||||||
|
}
|
||||||
|
|
||||||
|
Item { // spacer
|
||||||
|
width: 1
|
||||||
|
height: 20
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
anchors.right: parent.right
|
||||||
|
|
||||||
|
Button {
|
||||||
|
id: btnCreate
|
||||||
|
|
||||||
|
text: qsTr("Create")
|
||||||
|
enabled: folderName.text !== ""
|
||||||
|
onClicked: {
|
||||||
|
assetsModel.addNewFolder(contextDir.dirPath + '/' + folderName.text)
|
||||||
|
newFolderDialog.accept()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
text: qsTr("Cancel")
|
||||||
|
onClicked: newFolderDialog.reject()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onOpened: {
|
||||||
|
folderName.text = "New folder"
|
||||||
|
folderName.selectAll()
|
||||||
|
folderName.forceActiveFocus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Dialog {
|
||||||
|
id: confirmDeleteFolderDialog
|
||||||
|
|
||||||
|
title: qsTr("Folder not empty")
|
||||||
|
anchors.centerIn: parent
|
||||||
|
closePolicy: Popup.CloseOnEscape
|
||||||
|
implicitWidth: 300
|
||||||
|
modal: true
|
||||||
|
|
||||||
|
contentItem: Column {
|
||||||
|
spacing: 20
|
||||||
|
width: parent.width
|
||||||
|
|
||||||
|
Text {
|
||||||
|
id: folderNotEmpty
|
||||||
|
|
||||||
|
text: qsTr("Folder '%1' is not empty. Are you sure you want to delete it?")
|
||||||
|
.arg(contextDir ? contextDir.dirName : "")
|
||||||
|
color: StudioTheme.Values.themeTextColor
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
width: confirmDeleteFolderDialog.width
|
||||||
|
leftPadding: 10
|
||||||
|
rightPadding: 10
|
||||||
|
|
||||||
|
Keys.onEnterPressed: btnDelete.onClicked()
|
||||||
|
Keys.onReturnPressed: btnDelete.onClicked()
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: qsTr("If the folder has assets in use, deleting it might cause the project to not work correctly.")
|
||||||
|
color: StudioTheme.Values.themeTextColor
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
width: confirmDeleteFolderDialog.width
|
||||||
|
leftPadding: 10
|
||||||
|
rightPadding: 10
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
anchors.right: parent.right
|
||||||
|
Button {
|
||||||
|
id: btnDelete
|
||||||
|
|
||||||
|
text: qsTr("Delete")
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
assetsModel.deleteFolder(contextDir.dirPath)
|
||||||
|
confirmDeleteFolderDialog.accept()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
text: qsTr("Cancel")
|
||||||
|
onClicked: confirmDeleteFolderDialog.reject()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onOpened: folderNotEmpty.forceActiveFocus()
|
||||||
|
}
|
||||||
|
|
||||||
ScrollView { // TODO: experiment using ListView instead of ScrollView + Column
|
ScrollView { // TODO: experiment using ListView instead of ScrollView + Column
|
||||||
id: assetsView
|
id: assetsView
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
interactive: assetsView.verticalScrollBarVisible
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
StudioControls.Menu {
|
StudioControls.Menu {
|
||||||
@@ -86,7 +231,7 @@ Item {
|
|||||||
StudioControls.MenuItem {
|
StudioControls.MenuItem {
|
||||||
text: qsTr("Expand All")
|
text: qsTr("Expand All")
|
||||||
enabled: allExpandedState !== 1
|
enabled: allExpandedState !== 1
|
||||||
visible: !delFilePath
|
visible: isDirContextMenu
|
||||||
height: visible ? implicitHeight : 0
|
height: visible ? implicitHeight : 0
|
||||||
onTriggered: assetsModel.toggleExpandAll(true)
|
onTriggered: assetsModel.toggleExpandAll(true)
|
||||||
}
|
}
|
||||||
@@ -94,22 +239,51 @@ Item {
|
|||||||
StudioControls.MenuItem {
|
StudioControls.MenuItem {
|
||||||
text: qsTr("Collapse All")
|
text: qsTr("Collapse All")
|
||||||
enabled: allExpandedState !== 2
|
enabled: allExpandedState !== 2
|
||||||
visible: !delFilePath
|
visible: isDirContextMenu
|
||||||
height: visible ? implicitHeight : 0
|
height: visible ? implicitHeight : 0
|
||||||
onTriggered: assetsModel.toggleExpandAll(false)
|
onTriggered: assetsModel.toggleExpandAll(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
StudioControls.MenuSeparator {
|
||||||
|
visible: isDirContextMenu
|
||||||
|
height: visible ? StudioTheme.Values.border : 0
|
||||||
|
}
|
||||||
|
|
||||||
StudioControls.MenuItem {
|
StudioControls.MenuItem {
|
||||||
text: qsTr("Delete File")
|
text: qsTr("Delete File")
|
||||||
visible: delFilePath
|
visible: contextFilePath
|
||||||
height: visible ? implicitHeight : 0
|
height: visible ? implicitHeight : 0
|
||||||
onTriggered: assetsModel.removeFile(delFilePath)
|
onTriggered: assetsModel.deleteFile(contextFilePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
StudioControls.MenuSeparator {
|
||||||
|
visible: contextFilePath
|
||||||
|
height: visible ? StudioTheme.Values.border : 0
|
||||||
|
}
|
||||||
|
|
||||||
|
StudioControls.MenuItem {
|
||||||
|
text: qsTr("New Folder")
|
||||||
|
onTriggered: newFolderDialog.open()
|
||||||
|
}
|
||||||
|
|
||||||
|
StudioControls.MenuItem {
|
||||||
|
text: qsTr("Delete Folder")
|
||||||
|
visible: isDirContextMenu
|
||||||
|
height: visible ? implicitHeight : 0
|
||||||
|
onTriggered: {
|
||||||
|
var dirEmpty = !(contextDir.dirsModel && contextDir.dirsModel.rowCount() > 0)
|
||||||
|
&& !(contextDir.filesModel && contextDir.filesModel.rowCount() > 0);
|
||||||
|
|
||||||
|
if (dirEmpty)
|
||||||
|
assetsModel.deleteFolder(contextDir.dirPath)
|
||||||
|
else
|
||||||
|
confirmDeleteFolderDialog.open()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
spacing: 2
|
|
||||||
Repeater {
|
Repeater {
|
||||||
model: assetsModel // context property
|
model: assetsModel // context property
|
||||||
delegate: dirSection
|
delegate: dirSection
|
||||||
@@ -120,31 +294,35 @@ Item {
|
|||||||
|
|
||||||
Section {
|
Section {
|
||||||
width: assetsView.width -
|
width: assetsView.width -
|
||||||
(assetsView.verticalScrollBarVisible ? assetsView.verticalThickness : 0)
|
(assetsView.verticalScrollBarVisible ? assetsView.verticalThickness : 0) - 5
|
||||||
caption: dirName
|
caption: dirName
|
||||||
sectionHeight: 30
|
sectionHeight: 30
|
||||||
sectionFontSize: 15
|
sectionFontSize: 15
|
||||||
levelShift: 20
|
|
||||||
leftPadding: 0
|
leftPadding: 0
|
||||||
|
topPadding: dirDepth > 0 ? 5 : 0
|
||||||
|
bottomPadding: 0
|
||||||
hideHeader: dirDepth === 0
|
hideHeader: dirDepth === 0
|
||||||
showLeftBorder: true
|
showLeftBorder: dirDepth > 0
|
||||||
expanded: dirExpanded
|
expanded: dirExpanded
|
||||||
visible: dirVisible
|
visible: !assetsModel.isEmpty && dirVisible
|
||||||
expandOnClick: false
|
expandOnClick: false
|
||||||
useDefaulContextMenu: false
|
useDefaulContextMenu: false
|
||||||
|
|
||||||
onToggleExpand: {
|
onToggleExpand: {
|
||||||
dirExpanded = !dirExpanded
|
dirExpanded = !dirExpanded
|
||||||
}
|
}
|
||||||
|
|
||||||
onShowContextMenu: {
|
onShowContextMenu: {
|
||||||
delFilePath = ""
|
contextFilePath = ""
|
||||||
|
contextDir = model
|
||||||
|
isDirContextMenu = true
|
||||||
allExpandedState = assetsModel.getAllExpandedState()
|
allExpandedState = assetsModel.getAllExpandedState()
|
||||||
contextMenu.popup()
|
contextMenu.popup()
|
||||||
}
|
}
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
spacing: 5
|
spacing: 5
|
||||||
leftPadding: 15
|
leftPadding: 5
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
model: dirsModel
|
model: dirsModel
|
||||||
@@ -155,6 +333,25 @@ Item {
|
|||||||
model: filesModel
|
model: filesModel
|
||||||
delegate: fileSection
|
delegate: fileSection
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: qsTr("Empty folder")
|
||||||
|
color: StudioTheme.Values.themeTextColorDisabled
|
||||||
|
font.pixelSize: 12
|
||||||
|
visible: !(dirsModel && dirsModel.rowCount() > 0)
|
||||||
|
&& !(filesModel && filesModel.rowCount() > 0)
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
acceptedButtons: Qt.RightButton
|
||||||
|
onClicked: {
|
||||||
|
contextFilePath = ""
|
||||||
|
contextDir = model
|
||||||
|
isDirContextMenu = true
|
||||||
|
contextMenu.popup()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -222,8 +419,11 @@ Item {
|
|||||||
if (currFileSelected)
|
if (currFileSelected)
|
||||||
rootView.startDragAsset(selectedAssetsArr, mapToGlobal(mouse.x, mouse.y))
|
rootView.startDragAsset(selectedAssetsArr, mapToGlobal(mouse.x, mouse.y))
|
||||||
} else {
|
} else {
|
||||||
delFilePath = filePath
|
contextFilePath = filePath
|
||||||
|
contextDir = model.fileDir
|
||||||
|
|
||||||
tooltipBackend.hideTooltip()
|
tooltipBackend.hideTooltip()
|
||||||
|
isDirContextMenu = false
|
||||||
contextMenu.popup()
|
contextMenu.popup()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -263,7 +463,7 @@ Item {
|
|||||||
// Placeholder when the assets panel is empty
|
// Placeholder when the assets panel is empty
|
||||||
Column {
|
Column {
|
||||||
id: colNoAssets
|
id: colNoAssets
|
||||||
visible: assetsModel.isEmpty
|
visible: assetsModel.isEmpty && !rootView.searchActive
|
||||||
|
|
||||||
spacing: 20
|
spacing: 20
|
||||||
x: 20
|
x: 20
|
||||||
@@ -307,4 +507,13 @@ Item {
|
|||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.WordWrap
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: qsTr("No match found.")
|
||||||
|
x: 20
|
||||||
|
y: 10
|
||||||
|
color: StudioTheme.Values.themeTextColor
|
||||||
|
font.pixelSize: 12
|
||||||
|
visible: assetsModel.isEmpty && rootView.searchActive
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -173,7 +173,7 @@ Item {
|
|||||||
id: leftBorder
|
id: leftBorder
|
||||||
visible: false
|
visible: false
|
||||||
width: 1
|
width: 1
|
||||||
height: parent.height - 15
|
height: parent.height - bottomPadding
|
||||||
color: header.color
|
color: header.color
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -34,6 +34,7 @@ ItemLibraryAssetsFilesModel::ItemLibraryAssetsFilesModel(QObject *parent)
|
|||||||
// add roles
|
// add roles
|
||||||
m_roleNames.insert(FileNameRole, "fileName");
|
m_roleNames.insert(FileNameRole, "fileName");
|
||||||
m_roleNames.insert(FilePathRole, "filePath");
|
m_roleNames.insert(FilePathRole, "filePath");
|
||||||
|
m_roleNames.insert(FileDirRole, "fileDir");
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariant ItemLibraryAssetsFilesModel::data(const QModelIndex &index, int role) const
|
QVariant ItemLibraryAssetsFilesModel::data(const QModelIndex &index, int role) const
|
||||||
@@ -49,6 +50,9 @@ QVariant ItemLibraryAssetsFilesModel::data(const QModelIndex &index, int role) c
|
|||||||
if (role == FilePathRole)
|
if (role == FilePathRole)
|
||||||
return m_files[index.row()];
|
return m_files[index.row()];
|
||||||
|
|
||||||
|
if (role == FileDirRole)
|
||||||
|
return QVariant::fromValue(parent());
|
||||||
|
|
||||||
qWarning() << Q_FUNC_INFO << "Invalid role requested: " << QString::number(role);
|
qWarning() << Q_FUNC_INFO << "Invalid role requested: " << QString::number(role);
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
@@ -44,7 +44,8 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
enum Roles {FileNameRole = Qt::UserRole + 1,
|
enum Roles {FileNameRole = Qt::UserRole + 1,
|
||||||
FilePathRole};
|
FilePathRole,
|
||||||
|
FileDirRole};
|
||||||
|
|
||||||
QStringList m_files;
|
QStringList m_files;
|
||||||
QHash<int, QByteArray> m_roleNames;
|
QHash<int, QByteArray> m_roleNames;
|
||||||
|
@@ -99,7 +99,7 @@ void ItemLibraryAssetsModel::toggleExpandAll(bool expand)
|
|||||||
endResetModel();
|
endResetModel();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ItemLibraryAssetsModel::removeFile(const QString &filePath)
|
void ItemLibraryAssetsModel::deleteFile(const QString &filePath)
|
||||||
{
|
{
|
||||||
bool askBeforeDelete = DesignerSettings::getValue(
|
bool askBeforeDelete = DesignerSettings::getValue(
|
||||||
DesignerSettingsKey::ASK_BEFORE_DELETING_ASSET).toBool();
|
DesignerSettingsKey::ASK_BEFORE_DELETING_ASSET).toBool();
|
||||||
@@ -134,6 +134,52 @@ void ItemLibraryAssetsModel::removeFile(const QString &filePath)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ItemLibraryAssetsModel::addNewFolder(const QString &folderPath)
|
||||||
|
{
|
||||||
|
QString iterPath = folderPath;
|
||||||
|
QRegularExpression rgx("\\d+$"); // matches a number at the end of a string
|
||||||
|
QDir dir{folderPath};
|
||||||
|
|
||||||
|
while (dir.exists()) {
|
||||||
|
// if the folder name ends with a number, increment it
|
||||||
|
QRegularExpressionMatch match = rgx.match(iterPath);
|
||||||
|
if (match.hasMatch()) { // ends with a number
|
||||||
|
QString numStr = match.captured(0);
|
||||||
|
int num = match.captured(0).toInt();
|
||||||
|
|
||||||
|
// get number of padding zeros, ex: for "005" = 2
|
||||||
|
int nPaddingZeros = 0;
|
||||||
|
for (; nPaddingZeros < numStr.size() && numStr[nPaddingZeros] == '0'; ++nPaddingZeros);
|
||||||
|
|
||||||
|
++num;
|
||||||
|
|
||||||
|
// if the incremented number's digits increased, decrease the padding zeros
|
||||||
|
if (std::fmod(std::log10(num), 1.0) == 0)
|
||||||
|
--nPaddingZeros;
|
||||||
|
|
||||||
|
iterPath = folderPath.mid(0, match.capturedStart())
|
||||||
|
+ QString('0').repeated(nPaddingZeros)
|
||||||
|
+ QString::number(num);
|
||||||
|
} else {
|
||||||
|
iterPath = folderPath + '1';
|
||||||
|
}
|
||||||
|
|
||||||
|
dir.setPath(iterPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
dir.mkpath(iterPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ItemLibraryAssetsModel::deleteFolder(const QString &folderPath)
|
||||||
|
{
|
||||||
|
QDir{folderPath}.removeRecursively();
|
||||||
|
}
|
||||||
|
|
||||||
|
QObject *ItemLibraryAssetsModel::rootDir() const
|
||||||
|
{
|
||||||
|
return m_assetsDir;
|
||||||
|
}
|
||||||
|
|
||||||
const QStringList &ItemLibraryAssetsModel::supportedImageSuffixes()
|
const QStringList &ItemLibraryAssetsModel::supportedImageSuffixes()
|
||||||
{
|
{
|
||||||
static QStringList retList;
|
static QStringList retList;
|
||||||
@@ -270,7 +316,7 @@ void ItemLibraryAssetsModel::setRootPath(const QString &path)
|
|||||||
isEmpty &= parseDirRecursive(assetsDir, currDepth + 1);
|
isEmpty &= parseDirRecursive(assetsDir, currDepth + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isEmpty)
|
if (!m_searchText.isEmpty() && isEmpty)
|
||||||
currAssetsDir->setDirVisible(false);
|
currAssetsDir->setDirVisible(false);
|
||||||
|
|
||||||
return isEmpty;
|
return isEmpty;
|
||||||
|
@@ -83,7 +83,10 @@ public:
|
|||||||
|
|
||||||
Q_INVOKABLE void toggleExpandAll(bool expand);
|
Q_INVOKABLE void toggleExpandAll(bool expand);
|
||||||
Q_INVOKABLE DirExpandState getAllExpandedState() const;
|
Q_INVOKABLE DirExpandState getAllExpandedState() const;
|
||||||
Q_INVOKABLE void removeFile(const QString &filePath);
|
Q_INVOKABLE void deleteFile(const QString &filePath);
|
||||||
|
Q_INVOKABLE void addNewFolder(const QString &folderPath);
|
||||||
|
Q_INVOKABLE void deleteFolder(const QString &folderPath);
|
||||||
|
Q_INVOKABLE QObject *rootDir() const;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void isEmptyChanged();
|
void isEmptyChanged();
|
||||||
|
@@ -504,8 +504,14 @@ bool DocumentManager::belongsToQmakeProject()
|
|||||||
Utils::FilePath DocumentManager::currentResourcePath()
|
Utils::FilePath DocumentManager::currentResourcePath()
|
||||||
{
|
{
|
||||||
Utils::FilePath resourcePath = currentProjectDirPath();
|
Utils::FilePath resourcePath = currentProjectDirPath();
|
||||||
|
|
||||||
if (resourcePath.isEmpty())
|
if (resourcePath.isEmpty())
|
||||||
return currentFilePath().absolutePath();
|
return currentFilePath().absolutePath();
|
||||||
|
|
||||||
|
FilePath contentFilePath = resourcePath.pathAppended("content");
|
||||||
|
if (contentFilePath.exists())
|
||||||
|
return contentFilePath;
|
||||||
|
|
||||||
return resourcePath;
|
return resourcePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user