feat(Storybook): Possibility to modify Figma links directly from the app

Closes: #8450
This commit is contained in:
Michał Cieślak 2022-11-29 11:51:19 +01:00 committed by Michał
parent 4c1370f7ef
commit cda881af46
12 changed files with 422 additions and 37 deletions

View File

@ -49,6 +49,10 @@ ListModel {
title: "MembersSelector"
section: "Components"
}
ListElement {
title: "ImagesGridView"
section: "Components"
}
ListElement {
title: "BrowserSettings"
section: "Settings"

View File

@ -1,26 +1,15 @@
{
"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"
"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"
],
"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"
],
"CommunityProfilePopupInviteFriendsPanel": [
"https://www.figma.com/file/17fc13UBFvInrLgNUKJJg5/Kuba%E2%8E%9CDesktop?node-id=2927%3A343592",
@ -31,13 +20,6 @@
"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",
@ -47,13 +29,31 @@
"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"
"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"
],
"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"
],
"LoginView": [
"https://www.figma.com/file/17fc13UBFvInrLgNUKJJg5/Kuba%E2%8E%9CDesktop?node-id=1080%3A313192"
],
"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"
],
"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"
]
}

View File

@ -5,6 +5,7 @@
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QSaveFile>
QMap<QString, QStringList> FigmaIO::read(const QString &file)
{
@ -47,3 +48,28 @@ QMap<QString, QStringList> FigmaIO::read(const QString &file)
return mapping;
}
void FigmaIO::write(const QString &file, const QMap<QString, QStringList> &map)
{
QJsonObject rootObject;
std::for_each(map.constKeyValueBegin(), map.constKeyValueEnd(),
[&rootObject](auto entry) {
const auto& [key, links] = entry;
rootObject.insert(key, QJsonArray::fromStringList(links));
});
QSaveFile saveFile(file);
if (!saveFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
qWarning() << "FigmaIO::write - failed to open file:" << file;
return;
}
QJsonDocument doc(rootObject);
saveFile.write(doc.toJson());
bool commitResult = saveFile.commit();
if (!commitResult)
qWarning() << "FigmaIO::write - failed to write to file:" << file;
}

View File

@ -6,4 +6,5 @@ class FigmaIO
{
public:
static QMap<QString, QStringList> read(const QString &file);
static void write(const QString &file, const QMap<QString, QStringList> &map);
};

View File

@ -38,6 +38,74 @@ FigmaLinks* FigmaLinksSource::getFigmaLinks() const
return m_figmaLinks;
}
void FigmaLinksSource::remove(const QString &key, const QList<int> &indexes)
{
if (m_filePath.isEmpty()) {
qWarning("FigmaLinksSource::remove - file path is not set!");
return;
}
QMap<QString, QStringList> linksMap;
if (m_figmaLinks)
linksMap = m_figmaLinks->getLinksMap();
auto it = linksMap.find(key);
if (it == linksMap.end()) {
qWarning("FigmaLinksSource::remove - provided key doesn't exist!");
return;
}
if (indexes.isEmpty())
return;
auto indexesSorted = indexes;
std::sort(indexesSorted.begin(), indexesSorted.end());
if (std::adjacent_find(indexesSorted.cbegin(), indexesSorted.cend())
!= indexesSorted.cend()) {
qWarning("FigmaLinksSource::remove - provided indexes list contains duplicates!");
return;
}
auto& linksList = it.value();
if (indexesSorted.first() < 0 || indexesSorted.last() >= linksList.size()) {
qWarning("FigmaLinksSource::remove - at least one provided index is out of range!");
return;
}
if (linksList.size() == indexesSorted.size()) {
linksMap.erase(it);
} else {
std::for_each(std::crbegin(indexesSorted), std::crend(indexesSorted),
[&linksList](int idx) {
linksList.removeAt(idx);
});
}
FigmaIO::write(m_filePath.path(), linksMap);
}
void FigmaLinksSource::append(const QString &key, const QList<QString> &links)
{
QMap<QString, QStringList> linksMap;
if (m_filePath.isEmpty()) {
qWarning("FigmaLinksSource::append - file path is not set!");
return;
}
if (m_figmaLinks)
linksMap = m_figmaLinks->getLinksMap();
linksMap[key].append(links);
FigmaIO::write(m_filePath.path(), linksMap);
}
void FigmaLinksSource::updateFigmaLinks(const QMap<QString, QStringList>& map)
{
FigmaLinks *mapping = new FigmaLinks(map, this);

View File

@ -19,6 +19,9 @@ public:
void setFilePath(const QUrl& path);
FigmaLinks* getFigmaLinks() const;
Q_INVOKABLE void remove(const QString &key, const QList<int> &indexes);
Q_INVOKABLE void append(const QString &key, const QList<QString> &links);
signals:
void filePathChanged();
void figmaLinksChanged();

View File

@ -161,9 +161,9 @@ ApplicationWindow {
return
}
const window = figmaWindow.createObject(root, {
figmaWindow.createObject(root, {
figmaModel: currentPageModelItem.object.figma,
title: currentPageModelItem.object.title + " - Figma"
pageTitle: currentPageModelItem.object.title
})
}
}
@ -215,14 +215,20 @@ ApplicationWindow {
id: figmaWindow
FigmaPreviewWindow {
property string pageTitle
property alias figmaModel: figmaImagesProxyModel.sourceModel
title: pageTitle + " - Figma"
model: FigmaImagesProxyModel {
id: figmaImagesProxyModel
figmaLinksCache: figmaImageLinksCache
}
onRemoveLinksRequested: figmaLinksSource.remove(pageTitle, indexes)
onAppendLinksRequested: figmaLinksSource.append(pageTitle, links)
onClosing: Qt.callLater(destroy)
}
}

View File

@ -0,0 +1,111 @@
import QtQuick 2.14
import QtQuick.Controls 2.14
import QtQuick.Layouts 1.14
import Storybook 1.0
Pane {
id: root
ListModel {
id: imagesModel
}
Instantiator {
model: 20
delegate: Rectangle {
parent: root
width: 150
height: 150
color: 'whitesmoke'
border.width: 1
visible: false
Label {
anchors.centerIn: parent
text: "image " + index
font.pixelSize: 20
}
Image {
id: keepUrlAlive
visible: false
}
Component.onCompleted: {
imagesModel.append({
imageLink: "",
rawLink: `raw link ${index}`
})
Qt.callLater(grabToImage, imageResult => {
keepUrlAlive.source = imageResult.url
imagesModel.setProperty(index, "imageLink",
Qt.resolvedUrl(imageResult.url))
})
}
}
}
ColumnLayout {
anchors.fill: parent
RowLayout {
Layout.fillWidth: true
CheckBox {
id: selectableCheckBox
Layout.alignment: Qt.AlignVCenter
text: "selectable"
}
ToolSeparator {
Layout.alignment: Qt.AlignVCenter
}
Label {
id: selectionText
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
property string selectionAsString: ""
text: `selected indexes: [${selectionAsString}]`
Connections {
target: grid.selection
function onSelectionChanged() {
const indexes = grid.selection.selectedIndexes
const rows = indexes.map(idx => idx.row)
selectionText.selectionAsString = rows.join(", ")
}
}
}
Button {
text: "Clear selection"
onClicked: grid.selection.clear()
}
}
ImagesGridView {
id: grid
Layout.fillWidth: true
Layout.fillHeight: true
selectable: selectableCheckBox.checked
clip: true
model: imagesModel
}
}
}

View File

@ -1,5 +1,6 @@
import QtQuick 2.14
import QtQuick.Controls 2.14
import QtQuick.Layouts 1.14
ApplicationWindow {
id: root
@ -10,6 +11,13 @@ ApplicationWindow {
property var model
signal removeLinksRequested(var indexes)
signal appendLinksRequested(var links)
readonly property var urlRegex:
/^https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_\+.~#?&\/=]*)$/
SwipeView {
id: topSwipeView
@ -18,13 +26,50 @@ ApplicationWindow {
orientation: Qt.Vertical
interactive: false
ImagesGridView {
clip: true
model: root.model
Page {
ImagesGridView {
id: grid
onClicked: {
imagesSwipeView.setCurrentIndex(index)
topSwipeView.incrementCurrentIndex()
anchors.fill: parent
clip: true
model: root.model
onClicked: {
imagesSwipeView.setCurrentIndex(index)
topSwipeView.incrementCurrentIndex()
}
}
footer: ToolBar {
RowLayout {
anchors.fill: parent
Button {
id: removeButton
readonly property int selectionCount:
grid.selection.selectedIndexes.length
text: "Remove selected"
+ (enabled ? ` (${selectionCount})` : "")
enabled: grid.selection.hasSelection
onClicked: removeConfirmDialog.open()
}
ToolSeparator {}
Button {
text: "Add new links"
onClicked: addNewLinksDialog.open()
}
Item {
Layout.fillWidth: true
}
}
}
}
@ -61,4 +106,81 @@ ApplicationWindow {
}
}
}
Dialog {
id: removeConfirmDialog
readonly property var selected: grid.selection.selectedIndexes
anchors.centerIn: Overlay.overlay
title: "Links removal"
standardButtons: Dialog.Ok | Dialog.Cancel
Label {
text: "Are you sure that you want to remove "
+ removeButton.selectionCount + " link(s)?"
}
onAccepted: root.removeLinksRequested(selected.map(idx => idx.row))
onSelectedChanged: close()
}
Dialog {
id: addNewLinksDialog
anchors.centerIn: Overlay.overlay
title: "Add new Figma links"
standardButtons: Dialog.Save | Dialog.Cancel
width: parent.width * 0.8
height: parent.height * 0.4
GroupBox {
anchors.fill: parent
title: "Figma links, 1 per line"
ScrollView {
id: scrollView
anchors.fill: parent
clip: true
contentHeight: linksTextEdit.implicitHeight
contentWidth: linksTextEdit.implicitWidth
TextEdit {
id: linksTextEdit
property var links: []
width: scrollView.width
height: scrollView.height
font.pixelSize: 13
selectByMouse: true
onTextChanged: {
const allLines = text.split("\n")
const nonEmptyLines = allLines.filter(
line => line.trim().length > 0)
const trimmed = nonEmptyLines.map(line => line.trim())
links = trimmed.every(line => root.urlRegex.test(line))
? trimmed : []
}
}
}
}
onClosed: Qt.callLater(linksTextEdit.clear)
onAccepted: root.appendLinksRequested(linksTextEdit.links)
Component.onCompleted: {
standardButton(Dialog.Save).enabled
= Qt.binding(() => linksTextEdit.links.length > 0)
}
}
}

View File

@ -1,5 +1,6 @@
import QtQuick 2.14
import QtQuick.Controls 2.14
import QtQml.Models 2.14
GridView {
id: root
@ -9,6 +10,15 @@ GridView {
signal clicked(int index)
property bool selectable: true
readonly property alias selection: selectionModel
ItemSelectionModel {
id: selectionModel
model: root.model
}
delegate: Item {
width: root.cellWidth
height: root.cellHeight
@ -50,6 +60,21 @@ GridView {
ToolTip.visible: hovered
ToolTip.text: model.rawLink
}
CheckBox {
visible: root.selectable
anchors.top: parent.top
anchors.right: parent.right
checked: {
selectionModel.selection
return selectionModel.isSelected(root.model.index(index, 0))
}
onToggled: selectionModel.select(root.model.index(index, 0),
ItemSelectionModel.Toggle)
}
}
}
}

View File

@ -30,7 +30,6 @@ ToolBar {
ToolButton {
id: openFigmaButton
enabled: root.figmaPagesCount
text: `Figma designs (${root.figmaPagesCount})`
onClicked: root.figmaPreviewClicked()

View File

@ -4,6 +4,7 @@
#include <QStringListModel>
#include "figmadecoratormodel.h"
#include "figmaio.h"
#include "figmalinks.h"
#include "figmalinksmodel.h"
#include "figmalinkssource.h"
@ -66,6 +67,25 @@ class FigmaDecoratorModelTest: public QObject
Q_OBJECT
private slots:
void figmaIOTest() {
QTemporaryFile file;
QVERIFY(file.open());
file.close();
const auto readEmpty = FigmaIO::read(file.fileName());
QCOMPARE(readEmpty, {});
const QMap<QString, QStringList> content = {
{ "k_1", { "l_1", "l_2"}},
{ "k_2", { "l_3", "l_4"}}
};
FigmaIO::write(file.fileName(), content);
const auto readContent = FigmaIO::read(file.fileName());
QCOMPARE(readContent, content);
}
void readingFigmaFileTest() {
FigmaLinksSource figmaLinksSource;