Merge pull request #4102 from nextcloud/feature/file-names-in-activity

Show only filenames in tray activity items, with full path in tooltip
This commit is contained in:
Matthieu Gallien 2022-01-14 11:02:07 +01:00 committed by GitHub
commit 075f33a272
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 122 additions and 69 deletions

View file

@ -21,6 +21,10 @@ MouseArea {
color: (parent.containsMouse ? Style.lightHover : "transparent") color: (parent.containsMouse ? Style.lightHover : "transparent")
} }
ToolTip.visible: containsMouse && displayLocation !== ""
ToolTip.delay: Qt.styleHints.mousePressAndHoldInterval
ToolTip.text: qsTr("In %1").arg(displayLocation)
RowLayout { RowLayout {
id: activityItem id: activityItem

View file

@ -61,11 +61,22 @@ public:
SyncFileItemType SyncFileItemType
}; };
struct RichSubjectParameter {
QString type; // Required
QString id; // Required
QString name; // Required
QString path; // Required (for files only)
QUrl link; // Optional (files only)
};
Type _type; Type _type;
qlonglong _id; qlonglong _id;
QString _fileAction; QString _fileAction;
QString _objectType; QString _objectType;
QString _subject; QString _subject;
QString _subjectRich;
QHash<QString, RichSubjectParameter> _subjectRichParameters;
QString _subjectDisplay;
QString _message; QString _message;
QString _folder; QString _folder;
QString _file; QString _file;

View file

@ -58,6 +58,7 @@ QHash<int, QByteArray> ActivityListModel::roleNames() const
roles[DisplayPathRole] = "displayPath"; roles[DisplayPathRole] = "displayPath";
roles[PathRole] = "path"; roles[PathRole] = "path";
roles[AbsolutePathRole] = "absolutePath"; roles[AbsolutePathRole] = "absolutePath";
roles[DisplayLocationRole] = "displayLocation";
roles[LinkRole] = "link"; roles[LinkRole] = "link";
roles[MessageRole] = "message"; roles[MessageRole] = "message";
roles[ActionRole] = "type"; roles[ActionRole] = "type";
@ -114,15 +115,40 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const
if (!ast && _accountState != ast.data()) if (!ast && _accountState != ast.data())
return QVariant(); return QVariant();
switch (role) { const auto getFilePath = [&]() {
case DisplayPathRole:
if (!a._file.isEmpty()) { if (!a._file.isEmpty()) {
auto folder = FolderMan::instance()->folder(a._folder); const auto folder = FolderMan::instance()->folder(a._folder);
QString relPath(a._file);
if (folder) { const QString relPath = folder ? folder->remotePath() + a._file : a._file;
relPath.prepend(folder->remotePath());
}
const auto localFiles = FolderMan::instance()->findFileInLocalFolders(relPath, ast->account()); const auto localFiles = FolderMan::instance()->findFileInLocalFolders(relPath, ast->account());
if (localFiles.isEmpty()) {
return QString();
}
// If this is an E2EE file or folder, pretend we got no path, hiding the share button which is what we want
if (folder) {
SyncJournalFileRecord rec;
folder->journalDb()->getFileRecord(a._file.mid(1), &rec);
if (rec.isValid() && (rec._isE2eEncrypted || !rec._e2eMangledName.isEmpty())) {
return QString();
}
}
return localFiles.constFirst();
}
return QString();
};
const auto getDisplayPath = [&a, &ast]() {
if (!a._file.isEmpty()) {
const auto folder = FolderMan::instance()->folder(a._folder);
QString relPath = folder ? folder->remotePath() + a._file : a._file;
const auto localFiles = FolderMan::instance()->findFileInLocalFolders(relPath, ast->account());
if (localFiles.count() > 0) { if (localFiles.count() > 0) {
if (relPath.startsWith('/') || relPath.startsWith('\\')) { if (relPath.startsWith('/') || relPath.startsWith('\\')) {
return relPath.remove(0, 1); return relPath.remove(0, 1);
@ -132,54 +158,22 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const
} }
} }
return QString(); return QString();
};
const auto displayLocation = [&]() {
const auto displayPath = QFileInfo(getDisplayPath()).path();
return displayPath == "." || displayPath == "/" ? QString() : displayPath;
};
switch (role) {
case DisplayPathRole:
return getDisplayPath();
case PathRole: case PathRole:
if (!a._file.isEmpty()) { return QUrl::fromLocalFile(QFileInfo(getFilePath()).path());
const auto folder = FolderMan::instance()->folder(a._folder); case AbsolutePathRole:
return getFilePath();
QString relPath(a._file); case DisplayLocationRole:
if (folder) { return displayLocation();
relPath.prepend(folder->remotePath());
}
// get relative path to the file so we can open it in the file manager
const auto localFiles = FolderMan::instance()->findFileInLocalFolders(QFileInfo(relPath).path(), ast->account());
if (localFiles.isEmpty()) {
return QString();
}
// If this is an E2EE file or folder, pretend we got no path, this leads to
// hiding the share button which is what we want
if (folder) {
SyncJournalFileRecord rec;
folder->journalDb()->getFileRecord(a._file.mid(1), &rec);
if (rec.isValid() && (rec._isE2eEncrypted || !rec._e2eMangledName.isEmpty())) {
return QString();
}
}
return QUrl::fromLocalFile(localFiles.constFirst());
}
return QString();
case AbsolutePathRole: {
const auto folder = FolderMan::instance()->folder(a._folder);
QString relPath(a._file);
if (!a._file.isEmpty()) {
if (folder) {
relPath.prepend(folder->remotePath());
}
const auto localFiles = FolderMan::instance()->findFileInLocalFolders(relPath, ast->account());
if (!localFiles.empty()) {
return localFiles.constFirst();
} else {
qWarning("File not local folders while processing absolute path request.");
return QString();
}
} else {
qWarning("Received an absolute path request for an activity without a file path.");
return QString();
}
}
case ActionsLinksRole: { case ActionsLinksRole: {
QList<QVariant> customList; QList<QVariant> customList;
foreach (ActivityLink activityLink, a._links) { foreach (ActivityLink activityLink, a._links) {
@ -242,7 +236,11 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const
} }
} }
case ActionTextRole: case ActionTextRole:
return a._subject; if(a._subjectDisplay.isEmpty()) {
return a._subject;
}
return a._subjectDisplay;
case ActionTextColorRole: case ActionTextColorRole:
return a._id == -1 ? QLatin1String("#808080") : QLatin1String("#222"); // FIXME: This is a temporary workaround for _showMoreActivitiesAvailableEntry return a._id == -1 ? QLatin1String("#808080") : QLatin1String("#222"); // FIXME: This is a temporary workaround for _showMoreActivitiesAvailableEntry
case MessageRole: case MessageRole:
@ -332,16 +330,53 @@ void ActivityListModel::activitiesReceived(const QJsonDocument &json, int status
Activity a; Activity a;
a._type = Activity::ActivityType; a._type = Activity::ActivityType;
a._objectType = json.value("object_type").toString(); a._objectType = json.value(QStringLiteral("object_type")).toString();
a._accName = ast->account()->displayName(); a._accName = ast->account()->displayName();
a._id = json.value("activity_id").toInt(); a._id = json.value(QStringLiteral("activity_id")).toInt();
a._fileAction = json.value("type").toString(); a._fileAction = json.value(QStringLiteral("type")).toString();
a._subject = json.value("subject").toString(); a._subject = json.value(QStringLiteral("subject")).toString();
a._message = json.value("message").toString(); a._message = json.value(QStringLiteral("message")).toString();
a._file = json.value("object_name").toString(); a._file = json.value(QStringLiteral("object_name")).toString();
a._link = QUrl(json.value("link").toString()); a._link = QUrl(json.value(QStringLiteral("link")).toString());
a._dateTime = QDateTime::fromString(json.value("datetime").toString(), Qt::ISODate); a._dateTime = QDateTime::fromString(json.value(QStringLiteral("datetime")).toString(), Qt::ISODate);
a._icon = json.value("icon").toString(); a._icon = json.value(QStringLiteral("icon")).toString();
auto richSubjectData = json.value(QStringLiteral("subject_rich")).toArray();
Q_ASSERT(richSubjectData.size() > 1);
if(richSubjectData.size() > 1) {
a._subjectRich = richSubjectData[0].toString();
auto parameters = richSubjectData[1].toObject();
const QRegularExpression subjectRichParameterRe(QStringLiteral("({[a-zA-Z0-9]*})"));
const QRegularExpression subjectRichParameterBracesRe(QStringLiteral("[{}]"));
for (auto i = parameters.begin(); i != parameters.end(); ++i) {
const auto parameterJsonObject = i.value().toObject();
const Activity::RichSubjectParameter parameter = {
parameterJsonObject.value(QStringLiteral("type")).toString(),
parameterJsonObject.value(QStringLiteral("id")).toString(),
parameterJsonObject.value(QStringLiteral("name")).toString(),
parameterJsonObject.contains(QStringLiteral("path")) ? parameterJsonObject.value(QStringLiteral("path")).toString() : QString(),
parameterJsonObject.contains(QStringLiteral("link")) ? QUrl(parameterJsonObject.value(QStringLiteral("link")).toString()) : QUrl(),
};
a._subjectRichParameters[i.key()] = parameter;
}
auto displayString = a._subjectRich;
auto i = subjectRichParameterRe.globalMatch(displayString);
while (i.hasNext()) {
const auto match = i.next();
auto word = match.captured(1);
word.remove(subjectRichParameterBracesRe);
Q_ASSERT(a._subjectRichParameters.contains(word));
displayString = displayString.replace(match.captured(1), a._subjectRichParameters[word].name);
}
a._subjectDisplay = displayString;
}
list.append(a); list.append(a);
_currentItem = list.last()._id; _currentItem = list.last()._id;

View file

@ -55,6 +55,7 @@ public:
DisplayPathRole, DisplayPathRole,
PathRole, PathRole,
AbsolutePathRole, AbsolutePathRole,
DisplayLocationRole, // Provides the display path to a file's parent folder, relative to Nextcloud root
LinkRole, LinkRole,
PointInTimeRole, PointInTimeRole,
AccountConnectedRole, AccountConnectedRole,

View file

@ -506,6 +506,8 @@ void User::processCompletedSyncItem(const Folder *folder, const SyncFileItemPtr
activity._folder = folder->alias(); activity._folder = folder->alias();
activity._fileAction = ""; activity._fileAction = "";
const auto fileName = QFileInfo(item->_originalFile).fileName();
if (item->_instruction == CSYNC_INSTRUCTION_REMOVE) { if (item->_instruction == CSYNC_INSTRUCTION_REMOVE) {
activity._fileAction = "file_deleted"; activity._fileAction = "file_deleted";
} else if (item->_instruction == CSYNC_INSTRUCTION_NEW) { } else if (item->_instruction == CSYNC_INSTRUCTION_NEW) {
@ -520,15 +522,15 @@ void User::processCompletedSyncItem(const Folder *folder, const SyncFileItemPtr
qCWarning(lcActivity) << "Item " << item->_file << " retrieved successfully."; qCWarning(lcActivity) << "Item " << item->_file << " retrieved successfully.";
if (item->_direction != SyncFileItem::Up) { if (item->_direction != SyncFileItem::Up) {
activity._message = tr("Synced %1").arg(item->_originalFile); activity._message = tr("Synced %1").arg(fileName);
} else if (activity._fileAction == "file_renamed") { } else if (activity._fileAction == "file_renamed") {
activity._message = tr("You renamed %1").arg(item->_originalFile); activity._message = tr("You renamed %1").arg(fileName);
} else if (activity._fileAction == "file_deleted") { } else if (activity._fileAction == "file_deleted") {
activity._message = tr("You deleted %1").arg(item->_originalFile); activity._message = tr("You deleted %1").arg(fileName);
} else if (activity._fileAction == "file_created") { } else if (activity._fileAction == "file_created") {
activity._message = tr("You created %1").arg(item->_originalFile); activity._message = tr("You created %1").arg(fileName);
} else { } else {
activity._message = tr("You changed %1").arg(item->_originalFile); activity._message = tr("You changed %1").arg(fileName);
} }
_activityModel->addSyncFileItemToActivityList(activity); _activityModel->addSyncFileItemToActivityList(activity);