diff --git a/storybook/PagesModel.qml b/storybook/PagesModel.qml index c3131d2ac..d4702d9a3 100644 --- a/storybook/PagesModel.qml +++ b/storybook/PagesModel.qml @@ -49,6 +49,10 @@ ListModel { title: "MembersSelector" section: "Components" } + ListElement { + title: "ImagesGridView" + section: "Components" + } ListElement { title: "BrowserSettings" section: "Settings" diff --git a/storybook/figma.json b/storybook/figma.json index 66f27c020..5a1f6b3eb 100644 --- a/storybook/figma.json +++ b/storybook/figma.json @@ -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" ] } diff --git a/storybook/figmaio.cpp b/storybook/figmaio.cpp index 23f350400..83edb7a48 100644 --- a/storybook/figmaio.cpp +++ b/storybook/figmaio.cpp @@ -5,6 +5,7 @@ #include #include #include +#include QMap FigmaIO::read(const QString &file) { @@ -47,3 +48,28 @@ QMap FigmaIO::read(const QString &file) return mapping; } + +void FigmaIO::write(const QString &file, const QMap &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; +} diff --git a/storybook/figmaio.h b/storybook/figmaio.h index d7e65fed2..a653f705e 100644 --- a/storybook/figmaio.h +++ b/storybook/figmaio.h @@ -6,4 +6,5 @@ class FigmaIO { public: static QMap read(const QString &file); + static void write(const QString &file, const QMap &map); }; diff --git a/storybook/figmalinkssource.cpp b/storybook/figmalinkssource.cpp index 547e790da..797a33443 100644 --- a/storybook/figmalinkssource.cpp +++ b/storybook/figmalinkssource.cpp @@ -38,6 +38,74 @@ FigmaLinks* FigmaLinksSource::getFigmaLinks() const return m_figmaLinks; } +void FigmaLinksSource::remove(const QString &key, const QList &indexes) +{ + if (m_filePath.isEmpty()) { + qWarning("FigmaLinksSource::remove - file path is not set!"); + return; + } + + QMap 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 &links) +{ + QMap 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& map) { FigmaLinks *mapping = new FigmaLinks(map, this); diff --git a/storybook/figmalinkssource.h b/storybook/figmalinkssource.h index cc91e03e5..20437e54c 100644 --- a/storybook/figmalinkssource.h +++ b/storybook/figmalinkssource.h @@ -19,6 +19,9 @@ public: void setFilePath(const QUrl& path); FigmaLinks* getFigmaLinks() const; + Q_INVOKABLE void remove(const QString &key, const QList &indexes); + Q_INVOKABLE void append(const QString &key, const QList &links); + signals: void filePathChanged(); void figmaLinksChanged(); diff --git a/storybook/main.qml b/storybook/main.qml index 72863e377..d09383b3c 100644 --- a/storybook/main.qml +++ b/storybook/main.qml @@ -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) } } diff --git a/storybook/pages/ImagesGridViewPage.qml b/storybook/pages/ImagesGridViewPage.qml new file mode 100644 index 000000000..f2683f13c --- /dev/null +++ b/storybook/pages/ImagesGridViewPage.qml @@ -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 + } + } +} diff --git a/storybook/src/Storybook/FigmaPreviewWindow.qml b/storybook/src/Storybook/FigmaPreviewWindow.qml index bfeae5191..12ce15065 100644 --- a/storybook/src/Storybook/FigmaPreviewWindow.qml +++ b/storybook/src/Storybook/FigmaPreviewWindow.qml @@ -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) + } + } } diff --git a/storybook/src/Storybook/ImagesGridView.qml b/storybook/src/Storybook/ImagesGridView.qml index a59b70753..f314bea8b 100644 --- a/storybook/src/Storybook/ImagesGridView.qml +++ b/storybook/src/Storybook/ImagesGridView.qml @@ -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) + } } } } diff --git a/storybook/src/Storybook/PageToolBar.qml b/storybook/src/Storybook/PageToolBar.qml index 7ebf9bdd2..4cac97cf2 100644 --- a/storybook/src/Storybook/PageToolBar.qml +++ b/storybook/src/Storybook/PageToolBar.qml @@ -30,7 +30,6 @@ ToolBar { ToolButton { id: openFigmaButton - enabled: root.figmaPagesCount text: `Figma designs (${root.figmaPagesCount})` onClicked: root.figmaPreviewClicked() diff --git a/storybook/tests/tst_FigmaDecoratorModel.cpp b/storybook/tests/tst_FigmaDecoratorModel.cpp index 7145bd26e..318b4696a 100644 --- a/storybook/tests/tst_FigmaDecoratorModel.cpp +++ b/storybook/tests/tst_FigmaDecoratorModel.cpp @@ -4,6 +4,7 @@ #include #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 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;