feat(Storybook): Figma links loaded from json file

Closes: #8187
This commit is contained in:
Michał Cieślak 2022-11-25 16:41:50 +01:00 committed by Michał
parent 10684c22a0
commit 8e39d761dc
22 changed files with 863 additions and 166 deletions

View File

@ -28,21 +28,34 @@ add_executable(
main.cpp
cachecleaner.cpp cachecleaner.h
directorieswatcher.cpp directorieswatcher.h
figmadecoratormodel.cpp figmadecoratormodel.h
figmaio.cpp figmaio.h
figmalinks.cpp figmalinks.h
figmalinksmodel.cpp figmalinksmodel.h
figmalinkssource.cpp figmalinkssource.h
modelutils.cpp modelutils.h
sectionsdecoratormodel.cpp sectionsdecoratormodel.h
${QML_FILES} main.qml PagesModel.qml
${JS_FILES}
figma.json
)
target_compile_definitions(${PROJECT_NAME}
PRIVATE QML_IMPORT_ROOT="${CMAKE_CURRENT_LIST_DIR}")
target_link_libraries(
${PROJECT_NAME} PRIVATE Qt5::Core Qt5::Quick Qt5::QuickControls2 SortFilterProxyModel)
enable_testing()
add_executable(SectionsDecoratorModelTest tests/tst_SectionsDecoratorModel.cpp sectionsdecoratormodel.cpp)
add_executable(SectionsDecoratorModelTest tests/tst_SectionsDecoratorModel.cpp sectionsdecoratormodel.cpp modelutils.cpp)
add_test(NAME SectionsDecoratorModelTest COMMAND SectionsDecoratorModelTest)
target_link_libraries(SectionsDecoratorModelTest PRIVATE Qt5::Test)
add_executable(FigmaDecoratorModelTest tests/tst_FigmaDecoratorModel.cpp figmadecoratormodel.cpp figmalinkssource.cpp
figmalinks.cpp figmaio.cpp modelutils.cpp figmalinksmodel.cpp)
add_test(NAME FigmaModelTest COMMAND FigmaModelTest)
target_link_libraries(FigmaDecoratorModelTest PRIVATE Qt5::Test Qt5::Qml)
list(APPEND QML_DIRS "${CMAKE_SOURCE_DIR}/../ui/StatusQ/src")
list(APPEND QML_DIRS "${CMAKE_SOURCE_DIR}/../ui/app")
list(APPEND QML_DIRS "${CMAKE_SOURCE_DIR}/../ui/imports")

View File

@ -3,39 +3,10 @@ import QtQuick 2.14
ListModel {
ListElement {
title: "ProfileDialogView"
figma: [
ListElement {
link: "https://www.figma.com/file/ibJOTPlNtIxESwS96vJb06/%F0%9F%91%A4-Profile-%7C-Desktop?node-id=733%3A12552"
},
ListElement {
link: "https://www.figma.com/file/ibJOTPlNtIxESwS96vJb06/%F0%9F%91%A4-Profile-%7C-Desktop?node-id=682%3A15078"
},
ListElement {
link: "https://www.figma.com/file/ibJOTPlNtIxESwS96vJb06/%F0%9F%91%A4-Profile-%7C-Desktop?node-id=682%3A17655"
},
ListElement {
link: "https://www.figma.com/file/ibJOTPlNtIxESwS96vJb06/%F0%9F%91%A4-Profile-%7C-Desktop?node-id=682%3A17087"
},
ListElement {
link: "https://www.figma.com/file/ibJOTPlNtIxESwS96vJb06/%F0%9F%91%A4-Profile-%7C-Desktop?node-id=4%3A23525"
},
ListElement {
link: "https://www.figma.com/file/ibJOTPlNtIxESwS96vJb06/%F0%9F%91%A4-Profile-%7C-Desktop?node-id=4%3A23932"
}
]
section: "Views"
}
ListElement {
title: "CommunitiesPortalLayout"
figma: [
ListElement {
link: "https://www.figma.com/file/17fc13UBFvInrLgNUKJJg5/Kuba%E2%8E%9CDesktop?node-id=8159%3A415655"
},
ListElement {
link: "https://www.figma.com/file/17fc13UBFvInrLgNUKJJg5/Kuba%E2%8E%9CDesktop?node-id=8159%3A415935"
}
]
section: "Views"
}
ListElement {
@ -44,67 +15,22 @@ ListModel {
}
ListElement {
title: "LoginView"
figma: [
ListElement {
link: "https://www.figma.com/file/17fc13UBFvInrLgNUKJJg5/Kuba%E2%8E%9CDesktop?node-id=1080%3A313192"
}
]
section: "Views"
}
ListElement {
title: "AboutView"
figma: [
ListElement {
link: "https://www.figma.com/file/idUoxN7OIW2Jpp3PMJ1Rl8/%E2%9A%99%EF%B8%8F-Settings-%7C-Desktop?node-id=1159%3A114479"
},
ListElement {
link: "https://www.figma.com/file/idUoxN7OIW2Jpp3PMJ1Rl8/%E2%9A%99%EF%B8%8F-Settings-%7C-Desktop?node-id=1684%3A127762"
}
]
section: "Views"
}
ListElement {
title: "StatusCommunityCard"
figma: [
ListElement {
link: "https://www.figma.com/file/17fc13UBFvInrLgNUKJJg5/Kuba%E2%8E%9CDesktop?node-id=8159%3A416159"
},
ListElement {
link: "https://www.figma.com/file/17fc13UBFvInrLgNUKJJg5/Kuba%E2%8E%9CDesktop?node-id=8159%3A416160"
}
]
section: "Panels"
}
ListElement {
title: "CommunityProfilePopupInviteFriendsPanel"
figma: [
ListElement {
link: "https://www.figma.com/file/17fc13UBFvInrLgNUKJJg5/Kuba%E2%8E%9CDesktop?node-id=2927%3A343592"
},
ListElement {
link: "https://www.figma.com/file/17fc13UBFvInrLgNUKJJg5/Kuba%E2%8E%9CDesktop?node-id=2990%3A353179"
},
ListElement {
link: "https://www.figma.com/file/17fc13UBFvInrLgNUKJJg5/Kuba%E2%8E%9CDesktop?node-id=2927%3A344073"
}
]
section: "Panels"
}
ListElement {
title: "CommunityProfilePopupInviteMessagePanel"
figma: [
ListElement {
link: "https://www.figma.com/file/17fc13UBFvInrLgNUKJJg5/Kuba%E2%8E%9CDesktop?node-id=4291%3A385536"
},
ListElement {
link: "https://www.figma.com/file/17fc13UBFvInrLgNUKJJg5/Kuba%E2%8E%9CDesktop?node-id=4295%3A385958"
}
]
section: "Panels"
}
ListElement {
@ -113,86 +39,22 @@ ListModel {
}
ListElement {
title: "InviteFriendsToCommunityPopup"
figma: [
ListElement {
link: "https://www.figma.com/file/17fc13UBFvInrLgNUKJJg5/Kuba%E2%8E%9CDesktop?node-id=2927%3A343592"
},
ListElement {
link: "https://www.figma.com/file/17fc13UBFvInrLgNUKJJg5/Kuba%E2%8E%9CDesktop?node-id=2990%3A353179"
},
ListElement {
link: "https://www.figma.com/file/17fc13UBFvInrLgNUKJJg5/Kuba%E2%8E%9CDesktop?node-id=2927%3A344073"
},
ListElement {
link: "https://www.figma.com/file/17fc13UBFvInrLgNUKJJg5/Kuba%E2%8E%9CDesktop?node-id=4291%3A385536"
},
ListElement {
link: "https://www.figma.com/file/17fc13UBFvInrLgNUKJJg5/Kuba%E2%8E%9CDesktop?node-id=4295%3A385958"
}
]
section: "Popups"
}
ListElement {
title: "CreateChannelPopup"
figma: [
ListElement {
link: "https://www.figma.com/file/17fc13UBFvInrLgNUKJJg5/Kuba%E2%8E%9CDesktop?node-id=2975%3A488608"
},
ListElement {
link: "https://www.figma.com/file/17fc13UBFvInrLgNUKJJg5/Kuba%E2%8E%9CDesktop?node-id=2975%3A488256"
},
ListElement {
link: "https://www.figma.com/file/17fc13UBFvInrLgNUKJJg5/Kuba%E2%8E%9CDesktop?node-id=2903%3A348301"
},
ListElement {
link: "https://www.figma.com/file/17fc13UBFvInrLgNUKJJg5/Kuba%E2%8E%9CDesktop?node-id=2975%3A488848"
},
ListElement {
link: "https://www.figma.com/file/17fc13UBFvInrLgNUKJJg5/Kuba%E2%8E%9CDesktop?node-id=2975%3A489237"
},
ListElement {
link: "https://www.figma.com/file/17fc13UBFvInrLgNUKJJg5/Kuba%E2%8E%9CDesktop?node-id=2975%3A489607"
},
ListElement {
link: "https://www.figma.com/file/17fc13UBFvInrLgNUKJJg5/Kuba%E2%8E%9CDesktop?node-id=2975%3A492910"
}
]
section: "Popups"
}
ListElement {
title: "MembersSelector"
section: "Components"
}
ListElement {
title: "BrowserSettings"
figma: [
ListElement {
link: "https://www.figma.com/file/idUoxN7OIW2Jpp3PMJ1Rl8/%E2%9A%99%EF%B8%8F-Settings-%7C-Desktop?node-id=448%3A36296"
},
ListElement {
link: "https://www.figma.com/file/idUoxN7OIW2Jpp3PMJ1Rl8/%E2%9A%99%EF%B8%8F-Settings-%7C-Desktop?node-id=1573%3A296338"
}
]
section: "Settings"
}
ListElement {
title: "LanguageCurrencySettings"
figma: [
ListElement {
link: "https://www.figma.com/file/idUoxN7OIW2Jpp3PMJ1Rl8/%E2%9A%99%EF%B8%8F-Settings-%7C-Desktop?node-id=701%3A74776"
},
ListElement {
link: "https://www.figma.com/file/idUoxN7OIW2Jpp3PMJ1Rl8/%E2%9A%99%EF%B8%8F-Settings-%7C-Desktop?node-id=1592%3A112840"
},
ListElement {
link: "https://www.figma.com/file/idUoxN7OIW2Jpp3PMJ1Rl8/%E2%9A%99%EF%B8%8F-Settings-%7C-Desktop?node-id=701%3A75345"
}
]
section: "Settings"
}
}

59
storybook/figma.json Normal file
View File

@ -0,0 +1,59 @@
{
"ProfileDialogView": [
"https://www.figma.com/file/ibJOTPlNtIxESwS96vJb06/%F0%9F%91%A4-Profile-%7C-Desktop?node-id=733%3A12552",
"https://www.figma.com/file/ibJOTPlNtIxESwS96vJb06/%F0%9F%91%A4-Profile-%7C-Desktop?node-id=682%3A15078",
"https://www.figma.com/file/ibJOTPlNtIxESwS96vJb06/%F0%9F%91%A4-Profile-%7C-Desktop?node-id=682%3A17655",
"https://www.figma.com/file/ibJOTPlNtIxESwS96vJb06/%F0%9F%91%A4-Profile-%7C-Desktop?node-id=682%3A17087",
"https://www.figma.com/file/ibJOTPlNtIxESwS96vJb06/%F0%9F%91%A4-Profile-%7C-Desktop?node-id=4%3A23525",
"https://www.figma.com/file/ibJOTPlNtIxESwS96vJb06/%F0%9F%91%A4-Profile-%7C-Desktop?node-id=4%3A23932"
],
"CommunitiesPortalLayout": [
"https://www.figma.com/file/17fc13UBFvInrLgNUKJJg5/Kuba%E2%8E%9CDesktop?node-id=8159%3A415655",
"https://www.figma.com/file/17fc13UBFvInrLgNUKJJg5/Kuba%E2%8E%9CDesktop?node-id=8159%3A415935"
],
"LoginView": [
"https://www.figma.com/file/17fc13UBFvInrLgNUKJJg5/Kuba%E2%8E%9CDesktop?node-id=1080%3A313192"
],
"AboutView": [
"https://www.figma.com/file/idUoxN7OIW2Jpp3PMJ1Rl8/%E2%9A%99%EF%B8%8F-Settings-%7C-Desktop?node-id=1159%3A114479",
"https://www.figma.com/file/idUoxN7OIW2Jpp3PMJ1Rl8/%E2%9A%99%EF%B8%8F-Settings-%7C-Desktop?node-id=1684%3A127762"
],
"StatusCommunityCard": [
"https://www.figma.com/file/17fc13UBFvInrLgNUKJJg5/Kuba%E2%8E%9CDesktop?node-id=8159%3A416159",
"https://www.figma.com/file/17fc13UBFvInrLgNUKJJg5/Kuba%E2%8E%9CDesktop?node-id=8159%3A416160"
],
"CommunityProfilePopupInviteFriendsPanel": [
"https://www.figma.com/file/17fc13UBFvInrLgNUKJJg5/Kuba%E2%8E%9CDesktop?node-id=2927%3A343592",
"https://www.figma.com/file/17fc13UBFvInrLgNUKJJg5/Kuba%E2%8E%9CDesktop?node-id=2990%3A353179",
"https://www.figma.com/file/17fc13UBFvInrLgNUKJJg5/Kuba%E2%8E%9CDesktop?node-id=2927%3A344073"
],
"CommunityProfilePopupInviteMessagePanel": [
"https://www.figma.com/file/17fc13UBFvInrLgNUKJJg5/Kuba%E2%8E%9CDesktop?node-id=4291%3A385536",
"https://www.figma.com/file/17fc13UBFvInrLgNUKJJg5/Kuba%E2%8E%9CDesktop?node-id=4295%3A385958"
],
"InviteFriendsToCommunityPopup": [
"https://www.figma.com/file/17fc13UBFvInrLgNUKJJg5/Kuba%E2%8E%9CDesktop?node-id=2927%3A343592",
"https://www.figma.com/file/17fc13UBFvInrLgNUKJJg5/Kuba%E2%8E%9CDesktop?node-id=2990%3A353179",
"https://www.figma.com/file/17fc13UBFvInrLgNUKJJg5/Kuba%E2%8E%9CDesktop?node-id=2927%3A344073",
"https://www.figma.com/file/17fc13UBFvInrLgNUKJJg5/Kuba%E2%8E%9CDesktop?node-id=4291%3A385536",
"https://www.figma.com/file/17fc13UBFvInrLgNUKJJg5/Kuba%E2%8E%9CDesktop?node-id=4295%3A385958"
],
"CreateChannelPopup": [
"https://www.figma.com/file/17fc13UBFvInrLgNUKJJg5/Kuba%E2%8E%9CDesktop?node-id=2975%3A488608",
"https://www.figma.com/file/17fc13UBFvInrLgNUKJJg5/Kuba%E2%8E%9CDesktop?node-id=2975%3A488256",
"https://www.figma.com/file/17fc13UBFvInrLgNUKJJg5/Kuba%E2%8E%9CDesktop?node-id=2903%3A348301",
"https://www.figma.com/file/17fc13UBFvInrLgNUKJJg5/Kuba%E2%8E%9CDesktop?node-id=2975%3A488848",
"https://www.figma.com/file/17fc13UBFvInrLgNUKJJg5/Kuba%E2%8E%9CDesktop?node-id=2975%3A489237",
"https://www.figma.com/file/17fc13UBFvInrLgNUKJJg5/Kuba%E2%8E%9CDesktop?node-id=2975%3A489607",
"https://www.figma.com/file/17fc13UBFvInrLgNUKJJg5/Kuba%E2%8E%9CDesktop?node-id=2975%3A492910"
],
"BrowserSettings": [
"https://www.figma.com/file/idUoxN7OIW2Jpp3PMJ1Rl8/%E2%9A%99%EF%B8%8F-Settings-%7C-Desktop?node-id=448%3A36296",
"https://www.figma.com/file/idUoxN7OIW2Jpp3PMJ1Rl8/%E2%9A%99%EF%B8%8F-Settings-%7C-Desktop?node-id=1573%3A296338"
],
"LanguageCurrencySettings": [
"https://www.figma.com/file/idUoxN7OIW2Jpp3PMJ1Rl8/%E2%9A%99%EF%B8%8F-Settings-%7C-Desktop?node-id=701%3A74776",
"https://www.figma.com/file/idUoxN7OIW2Jpp3PMJ1Rl8/%E2%9A%99%EF%B8%8F-Settings-%7C-Desktop?node-id=1592%3A112840",
"https://www.figma.com/file/idUoxN7OIW2Jpp3PMJ1Rl8/%E2%9A%99%EF%B8%8F-Settings-%7C-Desktop?node-id=701%3A75345"
]
}

View File

@ -0,0 +1,94 @@
#include "figmadecoratormodel.h"
#include "figmalinks.h"
#include "figmalinksmodel.h"
#include "modelutils.h"
FigmaDecoratorModel::FigmaDecoratorModel(QObject *parent)
: QIdentityProxyModel{parent}
{
}
QHash<int, QByteArray> FigmaDecoratorModel::roleNames() const
{
auto roles = QIdentityProxyModel::roleNames();
roles.insert(FigmaRole, QByteArrayLiteral("figma"));
return roles;
}
QVariant FigmaDecoratorModel::data(const QModelIndex &proxyIndex, int role) const
{
if (!checkIndex(proxyIndex, CheckIndexOption::IndexIsValid))
return {};
if (role == FigmaRole) {
static FigmaLinksModel empty({});
if (!m_titleRole)
return QVariant::fromValue(&empty);
const auto title = data(proxyIndex, m_titleRole.value()).toString();
auto it = m_submodels.find(title);
if (it == m_submodels.end()) {
QStringList links;
if (m_figmaLinks)
links = m_figmaLinks->getLinksMap().value(title, {});
auto linksModel = new FigmaLinksModel(
links, const_cast<FigmaDecoratorModel*>(this));
it = m_submodels.insert(title, linksModel);
}
return QVariant::fromValue(it.value());
}
return QIdentityProxyModel::data(proxyIndex, role);
}
FigmaLinks* FigmaDecoratorModel::getFigmaLinks() const
{
return m_figmaLinks;
}
void FigmaDecoratorModel::setFigmaLinks(FigmaLinks *figmaLinks)
{
if (figmaLinks == m_figmaLinks)
return;
m_figmaLinks = figmaLinks;
const auto& linksMap = m_figmaLinks
? m_figmaLinks->getLinksMap()
: QMap<QString, QStringList>{};
auto linksIt = linksMap.constBegin();
while (linksIt != linksMap.constEnd()) {
if (m_submodels.contains(linksIt.key()))
m_submodels.value(linksIt.key())->setContent(linksIt.value());
++linksIt;
}
auto submodelsIt = m_submodels.constBegin();
while (submodelsIt != m_submodels.constEnd()) {
if (!linksMap.contains(submodelsIt.key()))
submodelsIt.value()->setContent({});
++submodelsIt;
}
emit figmaLinksChanged();
}
void FigmaDecoratorModel::setSourceModel(QAbstractItemModel *sourceModel)
{
qDeleteAll(m_submodels);
m_submodels.clear();
m_titleRole = ModelUtils::findRole(QByteArrayLiteral("title"), sourceModel);
if(!m_titleRole)
qWarning("The source model is missing title role!");
QIdentityProxyModel::setSourceModel(sourceModel);
}

View File

@ -0,0 +1,33 @@
#pragma once
#include <QIdentityProxyModel>
class FigmaLinks;
class FigmaLinksModel;
class FigmaDecoratorModel : public QIdentityProxyModel
{
Q_OBJECT
Q_PROPERTY(FigmaLinks* figmaLinks READ getFigmaLinks
WRITE setFigmaLinks NOTIFY figmaLinksChanged)
public:
static constexpr auto FigmaRole = Qt::UserRole + 100;
explicit FigmaDecoratorModel(QObject *parent = nullptr);
QHash<int, QByteArray> roleNames() const override;
QVariant data(const QModelIndex &proxyIndex, int role) const override;
FigmaLinks* getFigmaLinks() const;
void setFigmaLinks(FigmaLinks *figmaLinks);
void setSourceModel(QAbstractItemModel *sourceModel) override;
signals:
void figmaLinksChanged();
private:
std::optional<int> m_titleRole;
FigmaLinks* m_figmaLinks = nullptr;
mutable QMap<QString, FigmaLinksModel*> m_submodels;
};

49
storybook/figmaio.cpp Normal file
View File

@ -0,0 +1,49 @@
#include "figmaio.h"
#include <QDebug>
#include <QFile>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
QMap<QString, QStringList> FigmaIO::read(const QString &file)
{
QFile f(file);
if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) {
qWarning() << "FigmaIO::read - failed to open file:" << file;
return {};
}
QJsonParseError error;
auto jsonDoc = QJsonDocument::fromJson(f.readAll(), &error);
if (error.error != QJsonParseError::NoError) {
qWarning() << "FigmaIO::read - error parsing json file:" << file
<< "->" << error.errorString();
return {};
}
QMap<QString, QStringList> mapping;
if (jsonDoc.isObject()) {
auto rootObject = jsonDoc.object();
auto i = rootObject.constBegin();
while (i != rootObject.constEnd()) {
QJsonValue val = i.value();
QJsonArray links = val.toArray();
QStringList linksList;
linksList.reserve(links.size());
for (const QJsonValue &link : qAsConst(links))
if (link.isString())
linksList << link.toString();
mapping.insert(i.key(), linksList);
++i;
}
}
return mapping;
}

9
storybook/figmaio.h Normal file
View File

@ -0,0 +1,9 @@
#pragma once
#include <QMap>
class FigmaIO
{
public:
static QMap<QString, QStringList> read(const QString &file);
};

11
storybook/figmalinks.cpp Normal file
View File

@ -0,0 +1,11 @@
#include "figmalinks.h"
FigmaLinks::FigmaLinks(const QMap<QString, QStringList>& linksMap, QObject *parent)
: m_linksMap{linksMap}, QObject{parent}
{
}
const QMap<QString, QStringList>& FigmaLinks::getLinksMap() const
{
return m_linksMap;
}

16
storybook/figmalinks.h Normal file
View File

@ -0,0 +1,16 @@
#pragma once
#include <QObject>
#include <QMap>
class FigmaLinks : public QObject
{
Q_OBJECT
public:
explicit FigmaLinks(const QMap<QString, QStringList>& mapping,
QObject *parent = nullptr);
const QMap<QString, QStringList>& getLinksMap() const;
private:
QMap<QString, QStringList> m_linksMap;
};

View File

@ -0,0 +1,42 @@
#include "figmalinksmodel.h"
FigmaLinksModel::FigmaLinksModel(const QStringList &links, QObject *parent)
: QAbstractListModel{parent}, m_links{links}
{
}
int FigmaLinksModel::rowCount(const QModelIndex &parent) const
{
return m_links.size();
}
QVariant FigmaLinksModel::data(const QModelIndex &index, int role) const
{
if (!checkIndex(index, CheckIndexOption::IndexIsValid))
return {};
const int row = index.row();
return m_links.at(row);
}
QHash<int, QByteArray> FigmaLinksModel::roleNames() const
{
static QHash<int, QByteArray> roles(
{{LinkRole, QByteArrayLiteral("link")}});
return roles;
}
void FigmaLinksModel::setContent(const QStringList &links)
{
if (m_links == links)
return;
const auto oldCount = m_links.size();
beginResetModel();
m_links = links;
endResetModel();
if (m_links.size() != oldCount)
emit countChanged();
}

View File

@ -0,0 +1,27 @@
#pragma once
#include <QAbstractListModel>
class FigmaLinksModel : public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
public:
static constexpr auto LinkRole = 0;
explicit FigmaLinksModel(const QStringList &links,
QObject *parent = nullptr);
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role) const override;
QHash<int, QByteArray> roleNames() const override;
void setContent(const QStringList &links);
signals:
void countChanged();
private:
QStringList m_links;
};

View File

@ -0,0 +1,71 @@
#include "figmalinkssource.h"
#include <QQmlEngine>
#include "figmaio.h"
#include "figmalinks.h"
FigmaLinksSource::FigmaLinksSource(QObject *parent)
: QObject{parent}
{
connect(&m_watcher, &QFileSystemWatcher::fileChanged,
this, [this](const QString &path) {
this->readFile();
if (!this->m_watcher.files().contains(path))
this->m_watcher.addPath(path);
});
}
const QUrl& FigmaLinksSource::getFilePath() const
{
return m_filePath;
}
void FigmaLinksSource::setFilePath(const QUrl& path)
{
if (path == m_filePath)
return;
m_filePath = path;
readFile();
setupWatcher();
emit filePathChanged();
}
FigmaLinks* FigmaLinksSource::getFigmaLinks() const
{
return m_figmaLinks;
}
void FigmaLinksSource::updateFigmaLinks(const QMap<QString, QStringList>& map)
{
FigmaLinks *mapping = new FigmaLinks(map, this);
if (m_figmaLinks && qjsEngine(m_figmaLinks)) {
m_figmaLinks->setParent(nullptr);
QQmlEngine::setObjectOwnership(m_figmaLinks, QQmlEngine::JavaScriptOwnership);
}
m_figmaLinks = mapping;
emit figmaLinksChanged();
}
void FigmaLinksSource::readFile()
{
QMap<QString, QStringList> figmaLinks = FigmaIO::read(m_filePath.path());
updateFigmaLinks(figmaLinks);
}
void FigmaLinksSource::setupWatcher()
{
auto currentlyWatched = m_watcher.files();
if (!currentlyWatched.isEmpty())
m_watcher.removePaths(currentlyWatched);
if (m_filePath.isEmpty())
return;
m_watcher.addPath(m_filePath.path());
}

View File

@ -0,0 +1,34 @@
#pragma once
#include <QObject>
#include <QFileSystemWatcher>
#include <QUrl>
class FigmaLinks;
class FigmaLinksSource : public QObject
{
Q_OBJECT
Q_PROPERTY(QUrl filePath READ getFilePath WRITE setFilePath NOTIFY filePathChanged)
Q_PROPERTY(FigmaLinks* figmaLinks READ getFigmaLinks NOTIFY figmaLinksChanged)
public:
explicit FigmaLinksSource(QObject *parent = nullptr);
const QUrl& getFilePath() const;
void setFilePath(const QUrl& path);
FigmaLinks* getFigmaLinks() const;
signals:
void filePathChanged();
void figmaLinksChanged();
private:
void updateFigmaLinks(const QMap<QString, QStringList>& map);
void readFile();
void setupWatcher();
FigmaLinks *m_figmaLinks = nullptr;
QUrl m_filePath;
QFileSystemWatcher m_watcher;
};

View File

@ -3,6 +3,9 @@
#include "cachecleaner.h"
#include "directorieswatcher.h"
#include "figmadecoratormodel.h"
#include "figmalinks.h"
#include "figmalinkssource.h"
#include "sectionsdecoratormodel.h"
int main(int argc, char *argv[])
@ -35,6 +38,9 @@ int main(int argc, char *argv[])
engine.addImportPath(path);
qmlRegisterType<SectionsDecoratorModel>("Storybook", 1, 0, "SectionsDecoratorModel");
qmlRegisterType<FigmaDecoratorModel>("Storybook", 1, 0, "FigmaDecoratorModel");
qmlRegisterType<FigmaLinksSource>("Storybook", 1, 0, "FigmaLinksSource");
qmlRegisterUncreatableType<FigmaLinks>("Storybook", 1, 0, "FigmaLinks", "");
auto watcherFactory = [additionalImportPaths](QQmlEngine*, QJSEngine*) {
auto watcher = new DirectoriesWatcher();

View File

@ -18,6 +18,23 @@ ApplicationWindow {
font.pixelSize: 13
PagesModel {
id: pagesModel
}
FigmaLinksSource {
id: figmaLinksSource
filePath: "figma.json"
}
FigmaDecoratorModel {
id: figmaModel
sourceModel: pagesModel
figmaLinks: figmaLinksSource.figmaLinks
}
HotReloader {
id: reloader
@ -26,10 +43,6 @@ ApplicationWindow {
onReloaded: hotReloaderControls.notifyReload()
}
PagesModel {
id: pagesModel
}
SplitView {
anchors.fill: parent
@ -125,13 +138,13 @@ ApplicationWindow {
title: `pages/${root.currentPage}Page.qml`
figmaPagesCount: currentPageModelItem.object
? currentPageModelItem.object.figmaCount : 0
? currentPageModelItem.object.figma.count : 0
Instantiator {
id: currentPageModelItem
model: SingleItemProxyModel {
sourceModel: pagesModel
sourceModel: figmaModel
roleName: "title"
value: root.currentPage
}
@ -139,7 +152,6 @@ ApplicationWindow {
delegate: QtObject {
readonly property string title: model.title
readonly property var figma: model.figma
readonly property int figmaCount: figma ? figma.count : 0
}
}

20
storybook/modelutils.cpp Normal file
View File

@ -0,0 +1,20 @@
#include "modelutils.h"
#include <QAbstractItemModel>
std::optional<int> ModelUtils::findRole(const QByteArray &role,
const QAbstractItemModel *model)
{
if (model == nullptr)
return std::nullopt;
const auto roleNames = model->roleNames();
auto it = std::find_if(roleNames.constKeyValueBegin(),
roleNames.constKeyValueEnd(), [&role](auto entry) {
return entry.second == role;
});
return it == roleNames.constKeyValueEnd()
? std::nullopt : std::make_optional((*it).first);
}

13
storybook/modelutils.h Normal file
View File

@ -0,0 +1,13 @@
#pragma once
#include <optional>
#include <QString>
class QAbstractItemModel;
struct ModelUtils
{
static std::optional<int> findRole(const QByteArray &role,
const QAbstractItemModel *model);
};

View File

@ -1,5 +1,7 @@
#include "sectionsdecoratormodel.h"
#include "modelutils.h"
#include <QScopeGuard>
SectionsDecoratorModel::SectionsDecoratorModel(QObject *parent)
@ -121,20 +123,6 @@ void SectionsDecoratorModel::calculateOffsets()
});
}
std::optional<int> SectionsDecoratorModel::findSectionRole() const
{
const auto roleNames = m_sourceModel->roleNames();
auto i = roleNames.constBegin();
while (i != roleNames.constEnd()) {
if (i.value() == QStringLiteral("section"))
return i.key();
++i;
}
return std::nullopt;
}
void SectionsDecoratorModel::initialize()
{
beginResetModel();
@ -142,7 +130,8 @@ void SectionsDecoratorModel::initialize()
m_rowsMetadata.clear();
const auto sectionRoleOpt = findSectionRole();
const auto sectionRoleOpt = ModelUtils::findRole(
QByteArrayLiteral("section"), m_sourceModel);
if (!sectionRoleOpt) {
qWarning("Section role not found!");

View File

@ -36,7 +36,6 @@ private:
int count = 0;
};
std::optional<int> findSectionRole() const;
void initialize();
void calculateOffsets();

View File

@ -1,30 +1,47 @@
import QtQuick 2.14
ListModel {
id: root
/* required */ property FigmaLinksCache figmaLinksCache
property alias sourceModel: d.model
readonly property Instantiator _d: Instantiator {
id: d
property int idCounter: 0
model: 0
delegate: QtObject {
id: delegate
property int uniqueId
Component.onCompleted: {
append({
rawLink: model.link,
imageLink: ""
imageLink: "",
uniqueId: d.idCounter
})
uniqueId = d.idCounter
d.idCounter++
figmaLinksCache.getImageUrl(model.link, link => {
if (delegate)
setProperty(model.index, "imageLink", link)
root.setProperty(model.index, "imageLink", link)
})
}
}
onObjectRemoved: console.warn("FigmaImagesProxyModel: removing items from the source model is not supported!")
onObjectRemoved: {
for (let i = 0; i < root.count; i++) {
if (root.get(i).uniqueId === object.uniqueId) {
root.remove(i)
break
}
}
}
}
}

View File

@ -17,7 +17,7 @@ ColumnLayout {
RoundButton {
text: "⬅"
enabled: root.currentIndex !== 0
enabled: root.currentIndex !== 0 && root.currentIndex !== -1
onClicked: root.left()
}
RoundButton {

View File

@ -0,0 +1,321 @@
#include <QSignalSpy>
#include <QTest>
#include <QTemporaryFile>
#include <QStringListModel>
#include "figmadecoratormodel.h"
#include "figmalinks.h"
#include "figmalinksmodel.h"
#include "figmalinkssource.h"
namespace {
auto constexpr sampleJson1 = R"(
{
"Component_1": [
"link_1", "link_2"
],
"Component_2": [
"link_3", "link_4"
]
}
)";
auto constexpr sampleJson2 = R"(
{
"Component_1": [
"link_1"
],
"Component_2": [
"link_3", "link_5"
]
}
)";
class TestSourceModel : public QAbstractListModel {
public:
static constexpr auto TitleRole = 0;
TestSourceModel(int count = 1) : m_count(count) {}
int rowCount(const QModelIndex &parent = QModelIndex()) const override {
return m_count;
}
QVariant data(const QModelIndex &index, int role) const override {
if (!index.isValid())
return {};
return QString("title_%1").arg(index.row());
}
QHash<int, QByteArray> roleNames() const override {
QHash<int, QByteArray> roles;
roles.insert(TitleRole, QByteArrayLiteral("title"));
return roles;
}
int m_count;
};
} // unnamed namespace
class FigmaDecoratorModelTest: public QObject
{
Q_OBJECT
private slots:
void readingFigmaFileTest() {
FigmaLinksSource figmaLinksSource;
QSignalSpy spy(&figmaLinksSource, &FigmaLinksSource::figmaLinksChanged);
QCOMPARE(figmaLinksSource.getFigmaLinks(), nullptr);
QTemporaryFile file;
if (file.open()) {
QTextStream stream(&file);
stream << sampleJson1;
}
figmaLinksSource.setFilePath(file.fileName());
QVERIFY(figmaLinksSource.getFigmaLinks() != nullptr);
const FigmaLinks *links = figmaLinksSource.getFigmaLinks();
QCOMPARE(links->getLinksMap(), (QMap<QString, QStringList> {
{{"Component_1"}, {"link_1", "link_2"}},
{{"Component_2"}, {"link_3", "link_4"}}}));
QCOMPARE(spy.count(), 1);
QTemporaryFile file2;
if (file2.open()) {
QTextStream stream(&file2);
stream << sampleJson2;
}
figmaLinksSource.setFilePath(file2.fileName());
QVERIFY(figmaLinksSource.getFigmaLinks() != nullptr);
const FigmaLinks *links2 = figmaLinksSource.getFigmaLinks();
QCOMPARE(links2->getLinksMap(), (QMap<QString, QStringList> {
{{"Component_1"}, {"link_1"}},
{{"Component_2"}, {"link_3", "link_5"}}}));
QCOMPARE(spy.count(), 2);
}
void readingAfterFigmaFileChangedTest() {
FigmaLinksSource figmaLinksSource;
QSignalSpy spy(&figmaLinksSource, &FigmaLinksSource::figmaLinksChanged);
QCOMPARE(figmaLinksSource.getFigmaLinks(), nullptr);
QTemporaryFile file;
if (file.open()) {
QTextStream stream(&file);
stream << sampleJson1;
}
figmaLinksSource.setFilePath(file.fileName());
QVERIFY(figmaLinksSource.getFigmaLinks() != nullptr);
const FigmaLinks *links = figmaLinksSource.getFigmaLinks();
QCOMPARE(links->getLinksMap(), (QMap<QString, QStringList> {
{{"Component_1"}, {"link_1", "link_2"}},
{{"Component_2"}, {"link_3", "link_4"}}}));
QCOMPARE(spy.count(), 1);
if (QFile f(file.fileName());
f.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
QTextStream stream(&f);
stream << sampleJson2;
}
QVERIFY(spy.wait());
QCOMPARE(spy.count(), 2);
const FigmaLinks *links2 = figmaLinksSource.getFigmaLinks();
QCOMPARE(links2->getLinksMap(), (QMap<QString, QStringList> {
{{"Component_1"}, {"link_1"}},
{{"Component_2"}, {"link_3", "link_5"}}}));
if (QFile f(file.fileName());
f.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
QTextStream stream(&f);
stream << sampleJson1;
}
QVERIFY(spy.wait());
QCOMPARE(spy.count(), 3);
const FigmaLinks *links3 = figmaLinksSource.getFigmaLinks();
QCOMPARE(links3->getLinksMap(), (QMap<QString, QStringList> {
{{"Component_1"}, {"link_1", "link_2"}},
{{"Component_2"}, {"link_3", "link_4"}}}));
}
void emptyFigmaModelTest() {
FigmaDecoratorModel model;
QCOMPARE(model.rowCount(), 0);
QVERIFY(model.roleNames().contains(FigmaDecoratorModel::FigmaRole));
QCOMPARE(model.roleNames().value(
FigmaDecoratorModel::FigmaRole), QStringLiteral("figma"));
}
void figmaModelWithoutSourceModel() {
FigmaLinks links({
{{"Component_1"}, {"link_1", "link_2"}},
{{"Component_2"}, {"link_3", "link_4"}}
});
FigmaDecoratorModel model;
model.setFigmaLinks(&links);
QCOMPARE(model.rowCount(), 0);
QVERIFY(model.roleNames().contains(FigmaDecoratorModel::FigmaRole));
QCOMPARE(model.roleNames().value(
FigmaDecoratorModel::FigmaRole), QStringLiteral("figma"));
}
void figmaModelWithIncompatibleSourceModel() {
QStringListModel stringsModel({"s1", "s2"});
FigmaDecoratorModel model;
QTest::ignoreMessage(QtWarningMsg,
"The source model is missing title role!");
model.setSourceModel(&stringsModel);
QCOMPARE(model.rowCount(), 2);
QVERIFY(model.roleNames().contains(FigmaDecoratorModel::FigmaRole));
QCOMPARE(model.roleNames().value(
FigmaDecoratorModel::FigmaRole), QStringLiteral("figma"));
QCOMPARE(model.data(
model.index(0, 0),
FigmaDecoratorModel::FigmaRole)
.value<QAbstractItemModel*>()->rowCount(), 0);
}
void figmaModelWithoutLinksTest() {
TestSourceModel sourceModel;
FigmaDecoratorModel model;
model.setSourceModel(&sourceModel);
QCOMPARE(model.rowCount(), 1);
QCOMPARE(model.roleNames().count(), 2);
QVERIFY(model.roleNames().contains(FigmaDecoratorModel::FigmaRole));
QCOMPARE(model.roleNames().value(
FigmaDecoratorModel::FigmaRole), QStringLiteral("figma"));
QCOMPARE(model.data(
model.index(0, 0),
FigmaDecoratorModel::FigmaRole)
.value<QAbstractItemModel*>()->rowCount(), 0);
}
void figmaModelTest() {
TestSourceModel sourceModel{2};
FigmaLinks links({
{{"title_0"}, {"link_1", "link_2"}},
{{"title_x"}, {"link_3", "link_4"}}
});
FigmaDecoratorModel model;
QSignalSpy spy(&model, &FigmaDecoratorModel::dataChanged);
model.setSourceModel(&sourceModel);
QCOMPARE(spy.size(), 0);
QCOMPARE(model.rowCount(), 2);
QCOMPARE(model.roleNames().count(), 2);
QVERIFY(model.roleNames().contains(FigmaDecoratorModel::FigmaRole));
QVERIFY(model.roleNames().contains(TestSourceModel::TitleRole));
QCOMPARE(model.roleNames().value(
FigmaDecoratorModel::FigmaRole), QStringLiteral("figma"));
QCOMPARE(model.roleNames().value(
TestSourceModel::TitleRole), QStringLiteral("title"));
QCOMPARE(model.data(model.index(0, 0),
TestSourceModel::TitleRole).toString(), "title_0");
QCOMPARE(model.data(model.index(1, 0),
TestSourceModel::TitleRole).toString(), "title_1");
auto figmaLinksModel1 = model.data(model.index(0, 0), FigmaDecoratorModel::FigmaRole)
.value<QAbstractItemModel*>();
QVERIFY(figmaLinksModel1 != nullptr);
QCOMPARE(figmaLinksModel1->rowCount(), 0);
auto figmaLinksModel2 = model.data(model.index(1, 0), FigmaDecoratorModel::FigmaRole)
.value<QAbstractItemModel*>();
QVERIFY(figmaLinksModel2 != nullptr);
QCOMPARE(figmaLinksModel2->rowCount(), 0);
QSignalSpy linksModelspy1(figmaLinksModel1,
&QAbstractItemModel::modelReset);
QSignalSpy linksModelspy2(figmaLinksModel2,
&QAbstractItemModel::modelReset);
model.setFigmaLinks(&links);
QCOMPARE(spy.size(), 0);
QCOMPARE(linksModelspy1.size(), 1);
QCOMPARE(linksModelspy2.size(), 0);
QCOMPARE(model.data(model.index(0, 0), FigmaDecoratorModel::FigmaRole)
.value<QAbstractItemModel*>(), figmaLinksModel1);
QCOMPARE(model.data(model.index(1, 0), FigmaDecoratorModel::FigmaRole)
.value<QAbstractItemModel*>(), figmaLinksModel2);
QCOMPARE(figmaLinksModel1->rowCount(), 2);
QCOMPARE(figmaLinksModel2->rowCount(), 0);
QCOMPARE(model.data(model.index(0, 0),
TestSourceModel::TitleRole).toString(), "title_0");
QCOMPARE(model.data(model.index(1, 0),
TestSourceModel::TitleRole).toString(), "title_1");
QCOMPARE(figmaLinksModel1->roleNames().size(), 1);
QCOMPARE(figmaLinksModel2->roleNames().size(), 1);
QCOMPARE(figmaLinksModel1->data(figmaLinksModel1->index(0, 0),
FigmaLinksModel::LinkRole).toString(), "link_1");
QCOMPARE(figmaLinksModel1->data(figmaLinksModel1->index(1, 0),
FigmaLinksModel::LinkRole).toString(), "link_2");
model.setFigmaLinks(nullptr);
QCOMPARE(spy.size(), 0);
QCOMPARE(linksModelspy1.size(), 2);
QCOMPARE(linksModelspy2.size(), 0);
QCOMPARE(model.data(model.index(0, 0), FigmaDecoratorModel::FigmaRole)
.value<QAbstractItemModel*>(), figmaLinksModel1);
QCOMPARE(model.data(model.index(1, 0), FigmaDecoratorModel::FigmaRole)
.value<QAbstractItemModel*>(), figmaLinksModel2);
QCOMPARE(figmaLinksModel1->rowCount(), 0);
QCOMPARE(figmaLinksModel2->rowCount(), 0);
}
};
QTEST_MAIN(FigmaDecoratorModelTest)
#include "tst_FigmaDecoratorModel.moc"