Storybook: added ability to mark pages with their status

Closes: #16606
This commit is contained in:
Michał Cieślak 2024-10-30 12:19:53 +01:00 committed by Michał
parent 7854271c2e
commit 0c86fbf7b6
10 changed files with 275 additions and 56 deletions

View File

@ -8,7 +8,6 @@ import Qt.labs.settings 1.0
import StatusQ.Core.Theme 0.1 import StatusQ.Core.Theme 0.1
import Storybook 1.0 import Storybook 1.0
import utils 1.0
ApplicationWindow { ApplicationWindow {
id: root id: root
@ -181,6 +180,7 @@ ApplicationWindow {
model: pagesModel model: pagesModel
onPageSelected: root.currentPage = page onPageSelected: root.currentPage = page
onStatusClicked: statusStatsDialog.open()
} }
} }
} }
@ -287,17 +287,8 @@ ApplicationWindow {
} }
} }
Dialog { NoFigmaTokenDialog {
id: noFigmaTokenDialog id: noFigmaTokenDialog
anchors.centerIn: Overlay.overlay
title: "Figma token not set"
standardButtons: Dialog.Ok
Label {
text: "Please set Figma personal token in \"Settings\""
}
} }
FigmaLinksCache { FigmaLinksCache {
@ -310,24 +301,16 @@ ApplicationWindow {
id: inspectionWindow id: inspectionWindow
} }
Dialog { NothingToInspectDialog {
id: nothingToInspectDialog id: nothingToInspectDialog
anchors.centerIn: Overlay.overlay pageName: root.currentPage
width: contentItem.implicitWidth + leftPadding + rightPadding
title: "No items to inspect found"
standardButtons: Dialog.Ok
modal: true
contentItem: Label {
text: '
Tips:\n\
For inline components use naming convention of adding\n\
"Custom" at the begining (like Custom'+root.currentPage+')\n\
For popups set closePolicy to "Popup.NoAutoClose"\n\
'
} }
StatusStatisticsDialog {
id: statusStatsDialog
pagesModel: pagesModel
} }
Component { Component {

View File

@ -3,6 +3,7 @@ import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15 import QtQuick.Layouts 1.15
import QtQml 2.15 import QtQml 2.15
import StatusQ 0.1
import StatusQ.Core 0.1 import StatusQ.Core 0.1
import StatusQ.Core.Utils 0.1 import StatusQ.Core.Utils 0.1
import StatusQ.Controls 0.1 import StatusQ.Controls 0.1
@ -27,3 +28,4 @@ Item {
} }
// category: _ // category: _
// status: good

View File

@ -5,7 +5,6 @@
#include "directoryfileswatcher.h" #include "directoryfileswatcher.h"
namespace { namespace {
const auto categoryUncategorized QStringLiteral("Uncategorized"); const auto categoryUncategorized QStringLiteral("Uncategorized");
} }
@ -35,29 +34,9 @@ PagesModelItem PagesModel::readMetadata(const QString& path)
file.open(QIODevice::ReadOnly); file.open(QIODevice::ReadOnly);
QByteArray content = file.readAll(); QByteArray content = file.readAll();
static QRegularExpression categoryRegex( item.category = extractCategory(content);
"^//(\\s)*category:(.+)$", QRegularExpression::MultilineOption); item.status = extractStatus(content);
item.figmaLinks = extractFigmaLinks(content);
QRegularExpressionMatch categoryMatch = categoryRegex.match(content);
QString category = categoryMatch.hasMatch()
? categoryMatch.captured(2).trimmed() : categoryUncategorized;
item.category = category.size() ? category : categoryUncategorized;
static QRegularExpression figmaRegex(
"^(\\/\\/\\s*)((?:https:\\/\\/)?(?:www\\.)?figma\\.com\\/.*)$",
QRegularExpression::MultilineOption);
QRegularExpressionMatchIterator i = figmaRegex.globalMatch(content);
QStringList links;
while (i.hasNext()) {
QRegularExpressionMatch match = i.next();
QString link = match.captured(2);
links << link;
}
item.figmaLinks = links;
return item; return item;
} }
@ -75,6 +54,55 @@ QList<PagesModelItem> PagesModel::readMetadata(const QStringList &paths)
return metadata; return metadata;
} }
QString PagesModel::extractCategory(const QByteArray& content)
{
static QRegularExpression categoryRegex(
"^//(\\s)*category:(.+)$", QRegularExpression::MultilineOption);
QRegularExpressionMatch categoryMatch = categoryRegex.match(content);
QString category = categoryMatch.hasMatch()
? categoryMatch.captured(2).trimmed() : categoryUncategorized;
return category.isEmpty() ? categoryUncategorized : category;
}
PagesModel::Status PagesModel::extractStatus(const QByteArray& content)
{
static QRegularExpression statusRegex(
"^//(\\s)*status:(.+)$", QRegularExpression::MultilineOption);
QRegularExpressionMatch statusMatch = statusRegex.match(content);
QString status = statusMatch.hasMatch()
? statusMatch.captured(2).trimmed() : "";
if (status == QStringLiteral("bad"))
return Bad;
if (status == QStringLiteral("decent"))
return Decent;
if (status == QStringLiteral("good"))
return Good;
return Uncategorized;
}
QStringList PagesModel::extractFigmaLinks(const QByteArray& content)
{
static QRegularExpression figmaRegex(
"^(\\/\\/\\s*)((?:https:\\/\\/)?(?:www\\.)?figma\\.com\\/.*)$",
QRegularExpression::MultilineOption);
QRegularExpressionMatchIterator i = figmaRegex.globalMatch(content);
QStringList links;
while (i.hasNext()) {
QRegularExpressionMatch match = i.next();
QString link = match.captured(2);
links << link;
}
return links;
}
void PagesModel::onPagesChanged(const QStringList& added, void PagesModel::onPagesChanged(const QStringList& added,
const QStringList& removed, const QStringList& removed,
const QStringList& changed) const QStringList& changed)
@ -106,10 +134,11 @@ void PagesModel::onPagesChanged(const QStringList& added,
PagesModelItem metadata = readMetadata(path); PagesModelItem metadata = readMetadata(path);
setFigmaLinks(metadata.title, metadata.figmaLinks); setFigmaLinks(metadata.title, metadata.figmaLinks);
if (previous.category != metadata.category) { // For simplicity category and status change is handled by removing and
// For simplicity category change is handled by removing and
// adding item. In the future it can be changed to regular dataChanged // adding item. In the future it can be changed to regular dataChanged
// event and handled properly in upstream models like SectionSDecoratorModel. // event and handled properly in upstream models like SectionSDecoratorModel.
if (previous.category != metadata.category
|| previous.status != metadata.status) {
beginRemoveRows({}, index, index); beginRemoveRows({}, index, index);
m_items.removeAt(index); m_items.removeAt(index);
endRemoveRows(); endRemoveRows();
@ -135,6 +164,7 @@ QHash<int, QByteArray> PagesModel::roleNames() const
static const QHash<int,QByteArray> roles { static const QHash<int,QByteArray> roles {
{ TitleRole, QByteArrayLiteral("title") }, { TitleRole, QByteArrayLiteral("title") },
{ CategoryRole, QByteArrayLiteral("category") }, { CategoryRole, QByteArrayLiteral("category") },
{ StatusRole, QByteArrayLiteral("status") },
{ FigmaRole, QByteArrayLiteral("figma") } { FigmaRole, QByteArrayLiteral("figma") }
}; };
@ -157,6 +187,9 @@ QVariant PagesModel::data(const QModelIndex &index, int role) const
if (role == CategoryRole) if (role == CategoryRole)
return m_items.at(index.row()).category; return m_items.at(index.row()).category;
if (role == StatusRole)
return m_items.at(index.row()).status;
if (role == FigmaRole) { if (role == FigmaRole) {
auto title = m_items.at(index.row()).title; auto title = m_items.at(index.row()).title;
auto it = m_figmaSubmodels.find(title); auto it = m_figmaSubmodels.find(title);

View File

@ -12,6 +12,7 @@ struct PagesModelItem {
QDateTime lastModified; QDateTime lastModified;
QString title; QString title;
QString category; QString category;
int status = 0;
QStringList figmaLinks; QStringList figmaLinks;
}; };
@ -24,14 +25,23 @@ public:
enum Roles { enum Roles {
TitleRole = Qt::UserRole + 1, TitleRole = Qt::UserRole + 1,
CategoryRole, CategoryRole,
StatusRole,
FigmaRole FigmaRole
}; };
enum Status : int {
Uncategorized = 0,
Bad,
Decent,
Good
};
Q_ENUM(Status)
QHash<int, QByteArray> roleNames() const override; QHash<int, QByteArray> roleNames() const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role) const override; QVariant data(const QModelIndex &index, int role) const override;
private: private:
void onPagesChanged(const QStringList& added, const QStringList& removed, void onPagesChanged(const QStringList& added, const QStringList& removed,
const QStringList& changed); const QStringList& changed);
@ -44,6 +54,10 @@ private:
static void readMetadata(PagesModelItem &item); static void readMetadata(PagesModelItem &item);
static void readMetadata(QList<PagesModelItem> &items); static void readMetadata(QList<PagesModelItem> &items);
static QString extractCategory(const QByteArray& content);
static PagesModel::Status extractStatus(const QByteArray& content);
static QStringList extractFigmaLinks(const QByteArray& content);
void setFigmaLinks(const QString& title, const QStringList& links); void setFigmaLinks(const QString& title, const QStringList& links);
QString m_path; QString m_path;

View File

@ -13,6 +13,7 @@ ColumnLayout {
property alias currentPage: pagesList.currentPage property alias currentPage: pagesList.currentPage
signal pageSelected(string page) signal pageSelected(string page)
signal statusClicked
SortFilterProxyModel { SortFilterProxyModel {
id: filteredModel id: filteredModel
@ -96,5 +97,6 @@ ColumnLayout {
onPageSelected: root.pageSelected(page) onPageSelected: root.pageSelected(page)
onSectionClicked: sectionsModel.flipFolding(index) onSectionClicked: sectionsModel.flipFolding(index)
onStatusClicked: root.statusClicked()
} }
} }

View File

@ -0,0 +1,13 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
Dialog {
anchors.centerIn: Overlay.overlay
title: "Figma token not set"
standardButtons: Dialog.Ok
Label {
text: "Please set Figma personal token in \"Settings\""
}
}

View File

@ -0,0 +1,24 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
Dialog {
id: root
property string pageName
anchors.centerIn: Overlay.overlay
width: contentItem.implicitWidth + leftPadding + rightPadding
title: "No items to inspect found"
standardButtons: Dialog.Ok
modal: true
contentItem: Label {
text: '
Tips:\n\
For inline components use naming convention of adding\n\
"Custom" at the begining (like Custom'+root.pageName+')\n\
For popups set closePolicy to "Popup.NoAutoClose"\n\
'
}
}

View File

@ -1,5 +1,7 @@
import QtQuick 2.14 import QtQuick 2.15
import QtQuick.Controls 2.14 import QtQuick.Controls 2.15
import Storybook 1.0
ListView { ListView {
id: root id: root
@ -10,6 +12,7 @@ ListView {
signal pageSelected(string page) signal pageSelected(string page)
signal sectionClicked(int index) signal sectionClicked(int index)
signal statusClicked
readonly property string foldedPrefix: "▶ " readonly property string foldedPrefix: "▶ "
readonly property string unfoldedPrefix: "▼ " readonly property string unfoldedPrefix: "▼ "
@ -27,6 +30,35 @@ ListView {
"text/uri-list": `file:${pagesFolder}/${model.title}Page.qml` "text/uri-list": `file:${pagesFolder}/${model.title}Page.qml`
} }
indicator: Rectangle {
visible: !model.isSection
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: parent.leftPadding / 2
width: 6
height: 6
radius: 3
color: {
if (model.status === PagesModel.Good)
return "green"
if (model.status === PagesModel.Decent)
return "orange"
if (model.status === PagesModel.Bad)
return "red"
return "gray"
}
MouseArea {
anchors.fill: parent
onClicked: root.statusClicked()
}
}
MouseArea { MouseArea {
id: dragArea id: dragArea
anchors.fill: parent anchors.fill: parent

View File

@ -0,0 +1,113 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import StatusQ 0.1
import Storybook 1.0
import utils 1.0
Dialog {
id: root
required property var pagesModel
readonly property string contributingMdLink:
"https://github.com/status-im/status-desktop/blob/master/" +
"CONTRIBUTING.md#page-classification"
anchors.centerIn: Overlay.overlay
width: 420
title: "Page status statistics"
standardButtons: Dialog.Ok
modal: true
QtObject {
id: d
readonly property int total: root.pagesModel.ModelCount.count
function percent(val) {
return (val / total * 100).toFixed(2)
}
}
contentItem: ColumnLayout {
Label {
Layout.bottomMargin: 20
text: `Total number of pages: ${d.total}`
}
Repeater {
model: [
{ status: "good", color: "green" },
{ status: "decent", color: "orange" },
{ status: "bad", color: "red" },
{ status: "uncategorized", color: "gray" }
]
Row {
spacing: 10
Rectangle {
readonly property int size: label.height - 2
width: size
height: size
radius: size / 2
color: modelData.color
}
Label {
id: label
readonly property string status: modelData.status
readonly property int count: statusAggregator[status]
text: `${status}: ${count} (${d.percent(count)}%)`
}
}
}
Label {
Layout.topMargin: 20
text: `For details check <a href="${root.contributingMdLink}">CONTRIBUTING.md</a>`
onLinkActivated: Qt.openUrlExternally(link)
}
}
FunctionAggregator {
id: statusAggregator
readonly property int bad: value.bad
readonly property int decent: value.decent
readonly property int good: value.good
readonly property int uncategorized: value.uncategorized
model: root.pagesModel
roleName: "status"
initialValue: ({
bad: 0,
decent: 0,
good: 0,
uncategorized: 0
})
aggregateFunction: (aggr, value) => {
let { bad, decent, good, uncategorized } = aggr
switch (value) {
case PagesModel.Bad: bad++; break
case PagesModel.Decent: decent++; break
case PagesModel.Good: good++; break
default: uncategorized++
}
return { bad, decent, good, uncategorized }
}
}
}

View File

@ -21,6 +21,8 @@ LimitProxyModel 1.0 LimitProxyModel.qml
Logs 1.0 Logs.qml Logs 1.0 Logs.qml
LogsAndControlsPanel 1.0 LogsAndControlsPanel.qml LogsAndControlsPanel 1.0 LogsAndControlsPanel.qml
LogsView 1.0 LogsView.qml LogsView 1.0 LogsView.qml
NoFigmaTokenDialog 1.0 NoFigmaTokenDialog.qml
NothingToInspectDialog 1.0 NothingToInspectDialog.qml
PageToolBar 1.0 PageToolBar.qml PageToolBar 1.0 PageToolBar.qml
PagesList 1.0 PagesList.qml PagesList 1.0 PagesList.qml
PopupBackground 1.0 PopupBackground.qml PopupBackground 1.0 PopupBackground.qml
@ -28,6 +30,7 @@ RadioButtonFlowSelector 1.0 RadioButtonFlowSelector.qml
SettingsLayout 1.0 SettingsLayout.qml SettingsLayout 1.0 SettingsLayout.qml
SingleItemProxyModel 1.0 SingleItemProxyModel.qml SingleItemProxyModel 1.0 SingleItemProxyModel.qml
SourceCodeBox 1.0 SourceCodeBox.qml SourceCodeBox 1.0 SourceCodeBox.qml
StatusStatisticsDialog 1.0 StatusStatisticsDialog.qml
TestRunnerController 1.0 TestRunnerController.qml TestRunnerController 1.0 TestRunnerController.qml
TestRunnerControls 1.0 TestRunnerControls.qml TestRunnerControls 1.0 TestRunnerControls.qml
singleton FigmaUtils 1.0 FigmaUtils.qml singleton FigmaUtils 1.0 FigmaUtils.qml