From 2e4e1a37328d8392a7af571d5b740a4b307c8051 Mon Sep 17 00:00:00 2001 From: Alexey Martemyanov Date: Mon, 5 Feb 2024 18:02:09 +0600 Subject: [PATCH] Drop bookmarks storyboard (#2148) Task/Issue URL: https://app.asana.com/0/1199230911884351/1206472717181422/f --- DuckDuckGo.xcodeproj/project.pbxproj | 52 +- .../Extensions/NSPopUpButtonExtension.swift | 69 -- ...BookmarkListTreeControllerDataSource.swift | 8 +- .../Bookmarks/Model/BookmarkManager.swift | 2 + .../Model/BookmarkOutlineViewDataSource.swift | 9 +- .../Model/BookmarkSidebarTreeController.swift | 2 +- DuckDuckGo/Bookmarks/Model/PseudoFolder.swift | 7 +- .../View/BookmarkListViewController.swift | 361 ++++++++-- ...okmarkManagementDetailViewController.swift | 285 ++++++-- ...kmarkManagementSidebarViewController.swift | 203 ++++-- ...ookmarkManagementSplitViewController.swift | 90 ++- .../View/BookmarkOutlineCellView.swift | 165 +++++ .../View/BookmarkOutlineViewCell.swift | 74 -- .../View/BookmarkOutlineViewCell.xib | 61 -- .../View/BookmarkTableCellView.swift | 2 +- .../Bookmarks/View/Bookmarks.storyboard | 659 ------------------ .../Bookmarks/View/BookmarksOutlineView.swift | 12 +- .../View/BrowserTabSelectionDelegate.swift | 2 +- .../Extensions/NSMenuItemExtension.swift | 7 +- DuckDuckGo/Common/Localizables/UserText.swift | 8 +- .../View/AppKit/NSSavePanelExtension.swift | 27 +- .../Common/View/AppKit/NibLoadable.swift | 46 -- .../View/SwiftUI/NSPathControlView.swift | 2 +- DuckDuckGo/Localizable.xcstrings | 72 +- .../Model/PreferencesSidebarModel.swift | 4 + .../View/PreferencesViewController.swift | 12 +- .../Tab/View/BrowserTabViewController.swift | 13 +- .../BookmarkOutlineViewDataSourceTests.swift | 16 +- .../BookmarkSidebarTreeControllerTests.swift | 2 +- .../HomePage/Mocks/MockBookmarkManager.swift | 4 + 30 files changed, 1046 insertions(+), 1230 deletions(-) delete mode 100644 DuckDuckGo/Bookmarks/Extensions/NSPopUpButtonExtension.swift create mode 100644 DuckDuckGo/Bookmarks/View/BookmarkOutlineCellView.swift delete mode 100644 DuckDuckGo/Bookmarks/View/BookmarkOutlineViewCell.swift delete mode 100644 DuckDuckGo/Bookmarks/View/BookmarkOutlineViewCell.xib delete mode 100644 DuckDuckGo/Bookmarks/View/Bookmarks.storyboard delete mode 100644 DuckDuckGo/Common/View/AppKit/NibLoadable.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index a713030d43..815602f158 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -195,10 +195,9 @@ 3706FAAD293F65D500E42796 /* BadgeNotificationAnimationModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3184AC6C288F29D800C35E4B /* BadgeNotificationAnimationModel.swift */; }; 3706FAAE293F65D500E42796 /* HyperLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 857FFEBF27D239DC00415E7A /* HyperLink.swift */; }; 3706FAAF293F65D500E42796 /* PasteboardWriting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92929A26670D2A00AD2C21 /* PasteboardWriting.swift */; }; - 3706FAB0293F65D500E42796 /* BookmarkOutlineViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92928726670D1600AD2C21 /* BookmarkOutlineViewCell.swift */; }; + 3706FAB0293F65D500E42796 /* BookmarkOutlineCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92928726670D1600AD2C21 /* BookmarkOutlineCellView.swift */; }; 3706FAB1293F65D500E42796 /* UnprotectedDomains.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = B604085A274B8CA300680351 /* UnprotectedDomains.xcdatamodeld */; }; 3706FAB2293F65D500E42796 /* TabInstrumentation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB88B4F25B7BA2B006F6B06 /* TabInstrumentation.swift */; }; - 3706FAB4293F65D500E42796 /* NSPopUpButtonExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9292D62667124000AD2C21 /* NSPopUpButtonExtension.swift */; }; 3706FAB5293F65D500E42796 /* ConfigurationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85D33F1125C82EB3002B91A6 /* ConfigurationManager.swift */; }; 3706FAB6293F65D500E42796 /* YoutubePlayerUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31F28C4C28C8EEC500119F70 /* YoutubePlayerUserScript.swift */; }; 3706FAB7293F65D500E42796 /* PixelParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A9E48326146AAB0067D1B9 /* PixelParameters.swift */; }; @@ -544,7 +543,6 @@ 3706FC4A293F65D500E42796 /* LocalStatisticsStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B69B50392726A12500758A2B /* LocalStatisticsStore.swift */; }; 3706FC4B293F65D500E42796 /* BackForwardListItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B689ECD426C247DB006FB0C5 /* BackForwardListItem.swift */; }; 3706FC4E293F65D500E42796 /* AtbAndVariantCleanup.swift in Sources */ = {isa = PBXBuildFile; fileRef = B69B50562727D16900758A2B /* AtbAndVariantCleanup.swift */; }; - 3706FC4F293F65D500E42796 /* NibLoadable.swift in Sources */ = {isa = PBXBuildFile; fileRef = B693953C26F04BE70015B914 /* NibLoadable.swift */; }; 3706FC50293F65D500E42796 /* FeedbackWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA3D531427A1ED9300074EC1 /* FeedbackWindow.swift */; }; 3706FC51293F65D500E42796 /* RecentlyVisitedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85F0FF1227CFAB04001C7C6E /* RecentlyVisitedView.swift */; }; 3706FC52293F65D500E42796 /* MouseOverAnimationButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA7EB6DE27E7C57D00036718 /* MouseOverAnimationButton.swift */; }; @@ -648,7 +646,6 @@ 3706FCCA293F65D500E42796 /* FirePopoverCollectionViewHeader.xib in Resources */ = {isa = PBXBuildFile; fileRef = AAE246F5270A3D3000BEEAEE /* FirePopoverCollectionViewHeader.xib */; }; 3706FCCC293F65D500E42796 /* TabBar.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AA80EC7B256C46AA007083E7 /* TabBar.storyboard */; }; 3706FCCD293F65D500E42796 /* shield-dot.json in Resources */ = {isa = PBXBuildFile; fileRef = AA34396B2754D4E300B241FA /* shield-dot.json */; }; - 3706FCCF293F65D500E42796 /* Bookmarks.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AAC5E4C625D6A6E8007F5990 /* Bookmarks.storyboard */; }; 3706FCD0293F65D500E42796 /* BookmarksBarCollectionViewItem.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4BE53369286912D40019DBFD /* BookmarksBarCollectionViewItem.xib */; }; 3706FCD1293F65D500E42796 /* PrivacyDashboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B6FA893C269C423100588ECD /* PrivacyDashboard.storyboard */; }; 3706FCD2293F65D500E42796 /* shield.json in Resources */ = {isa = PBXBuildFile; fileRef = AA34396A2754D4E200B241FA /* shield.json */; }; @@ -660,7 +657,6 @@ 3706FCDB293F65D500E42796 /* Feedback.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AA3863C427A1E28F00749AB5 /* Feedback.storyboard */; }; 3706FCDE293F65D500E42796 /* HomePageAssets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 85AC7AD827BD625000FFB69B /* HomePageAssets.xcassets */; }; 3706FCDF293F65D500E42796 /* shield-mouse-over.json in Resources */ = {isa = PBXBuildFile; fileRef = AA7EB6E627E8809D00036718 /* shield-mouse-over.json */; }; - 3706FCE0293F65D500E42796 /* BookmarkOutlineViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B92928826670D1600AD2C21 /* BookmarkOutlineViewCell.xib */; }; 3706FCE1293F65D500E42796 /* PermissionAuthorization.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B64C84DD2692D7400048FEBE /* PermissionAuthorization.storyboard */; }; 3706FCE2293F65D500E42796 /* dark-trackers-3.json in Resources */ = {isa = PBXBuildFile; fileRef = AA3439772754D55100B241FA /* dark-trackers-3.json */; }; 3706FCE3293F65D500E42796 /* dark-trackers-2.json in Resources */ = {isa = PBXBuildFile; fileRef = AA3439722754D55100B241FA /* dark-trackers-2.json */; }; @@ -1236,8 +1232,7 @@ 4B8D9062276D1D880078DB17 /* LocaleExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8D9061276D1D880078DB17 /* LocaleExtension.swift */; }; 4B92928B26670D1700AD2C21 /* BookmarksOutlineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92928526670D1600AD2C21 /* BookmarksOutlineView.swift */; }; 4B92928C26670D1700AD2C21 /* OutlineSeparatorViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92928626670D1600AD2C21 /* OutlineSeparatorViewCell.swift */; }; - 4B92928D26670D1700AD2C21 /* BookmarkOutlineViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92928726670D1600AD2C21 /* BookmarkOutlineViewCell.swift */; }; - 4B92928E26670D1700AD2C21 /* BookmarkOutlineViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B92928826670D1600AD2C21 /* BookmarkOutlineViewCell.xib */; }; + 4B92928D26670D1700AD2C21 /* BookmarkOutlineCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92928726670D1600AD2C21 /* BookmarkOutlineCellView.swift */; }; 4B92928F26670D1700AD2C21 /* BookmarkTableCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92928926670D1700AD2C21 /* BookmarkTableCellView.swift */; }; 4B92929B26670D2A00AD2C21 /* BookmarkOutlineViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92929126670D2A00AD2C21 /* BookmarkOutlineViewDataSource.swift */; }; 4B92929C26670D2A00AD2C21 /* PasteboardFolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92929226670D2A00AD2C21 /* PasteboardFolder.swift */; }; @@ -1268,7 +1263,6 @@ 4B9292D32667123700AD2C21 /* AddBookmarkModalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9292CB2667123700AD2C21 /* AddBookmarkModalView.swift */; }; 4B9292D42667123700AD2C21 /* BookmarkListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9292CC2667123700AD2C21 /* BookmarkListViewController.swift */; }; 4B9292D52667123700AD2C21 /* BookmarkManagementDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9292CD2667123700AD2C21 /* BookmarkManagementDetailViewController.swift */; }; - 4B9292D72667124000AD2C21 /* NSPopUpButtonExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9292D62667124000AD2C21 /* NSPopUpButtonExtension.swift */; }; 4B9292D92667124B00AD2C21 /* BookmarkListTreeControllerDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9292D82667124B00AD2C21 /* BookmarkListTreeControllerDataSource.swift */; }; 4B9292DB2667125D00AD2C21 /* ContextualMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9292DA2667125D00AD2C21 /* ContextualMenu.swift */; }; 4B9579212AC687170062CA31 /* HardwareModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9579202AC687170062CA31 /* HardwareModel.swift */; }; @@ -1332,10 +1326,9 @@ 4B95797F2AC7AE700062CA31 /* HyperLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 857FFEBF27D239DC00415E7A /* HyperLink.swift */; }; 4B9579802AC7AE700062CA31 /* SyncDataProviders.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37445F982A1566420029F789 /* SyncDataProviders.swift */; }; 4B9579812AC7AE700062CA31 /* PasteboardWriting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92929A26670D2A00AD2C21 /* PasteboardWriting.swift */; }; - 4B9579822AC7AE700062CA31 /* BookmarkOutlineViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92928726670D1600AD2C21 /* BookmarkOutlineViewCell.swift */; }; + 4B9579822AC7AE700062CA31 /* BookmarkOutlineCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92928726670D1600AD2C21 /* BookmarkOutlineCellView.swift */; }; 4B9579832AC7AE700062CA31 /* UnprotectedDomains.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = B604085A274B8CA300680351 /* UnprotectedDomains.xcdatamodeld */; }; 4B9579842AC7AE700062CA31 /* TabInstrumentation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB88B4F25B7BA2B006F6B06 /* TabInstrumentation.swift */; }; - 4B9579862AC7AE700062CA31 /* NSPopUpButtonExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9292D62667124000AD2C21 /* NSPopUpButtonExtension.swift */; }; 4B9579872AC7AE700062CA31 /* ConfigurationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85D33F1125C82EB3002B91A6 /* ConfigurationManager.swift */; }; 4B9579882AC7AE700062CA31 /* YoutubePlayerUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31F28C4C28C8EEC500119F70 /* YoutubePlayerUserScript.swift */; }; 4B9579892AC7AE700062CA31 /* PixelParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A9E48326146AAB0067D1B9 /* PixelParameters.swift */; }; @@ -1794,7 +1787,6 @@ 4B957B632AC7AE700062CA31 /* LocalStatisticsStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B69B50392726A12500758A2B /* LocalStatisticsStore.swift */; }; 4B957B642AC7AE700062CA31 /* BackForwardListItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B689ECD426C247DB006FB0C5 /* BackForwardListItem.swift */; }; 4B957B672AC7AE700062CA31 /* AtbAndVariantCleanup.swift in Sources */ = {isa = PBXBuildFile; fileRef = B69B50562727D16900758A2B /* AtbAndVariantCleanup.swift */; }; - 4B957B682AC7AE700062CA31 /* NibLoadable.swift in Sources */ = {isa = PBXBuildFile; fileRef = B693953C26F04BE70015B914 /* NibLoadable.swift */; }; 4B957B692AC7AE700062CA31 /* FeedbackWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA3D531427A1ED9300074EC1 /* FeedbackWindow.swift */; }; 4B957B6A2AC7AE700062CA31 /* WorkspaceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6685E3E29A606190043D2EE /* WorkspaceProtocol.swift */; }; 4B957B6B2AC7AE700062CA31 /* RecentlyVisitedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85F0FF1227CFAB04001C7C6E /* RecentlyVisitedView.swift */; }; @@ -1943,7 +1935,6 @@ 4B957C062AC7AE700062CA31 /* FirePopoverCollectionViewHeader.xib in Resources */ = {isa = PBXBuildFile; fileRef = AAE246F5270A3D3000BEEAEE /* FirePopoverCollectionViewHeader.xib */; }; 4B957C072AC7AE700062CA31 /* TabBar.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AA80EC7B256C46AA007083E7 /* TabBar.storyboard */; }; 4B957C082AC7AE700062CA31 /* shield-dot.json in Resources */ = {isa = PBXBuildFile; fileRef = AA34396B2754D4E300B241FA /* shield-dot.json */; }; - 4B957C0A2AC7AE700062CA31 /* Bookmarks.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AAC5E4C625D6A6E8007F5990 /* Bookmarks.storyboard */; }; 4B957C0B2AC7AE700062CA31 /* BookmarksBarCollectionViewItem.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4BE53369286912D40019DBFD /* BookmarksBarCollectionViewItem.xib */; }; 4B957C0C2AC7AE700062CA31 /* PrivacyDashboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B6FA893C269C423100588ECD /* PrivacyDashboard.storyboard */; }; 4B957C0D2AC7AE700062CA31 /* shield.json in Resources */ = {isa = PBXBuildFile; fileRef = AA34396A2754D4E200B241FA /* shield.json */; }; @@ -1955,7 +1946,6 @@ 4B957C142AC7AE700062CA31 /* Feedback.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AA3863C427A1E28F00749AB5 /* Feedback.storyboard */; }; 4B957C172AC7AE700062CA31 /* HomePageAssets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 85AC7AD827BD625000FFB69B /* HomePageAssets.xcassets */; }; 4B957C182AC7AE700062CA31 /* shield-mouse-over.json in Resources */ = {isa = PBXBuildFile; fileRef = AA7EB6E627E8809D00036718 /* shield-mouse-over.json */; }; - 4B957C192AC7AE700062CA31 /* BookmarkOutlineViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B92928826670D1600AD2C21 /* BookmarkOutlineViewCell.xib */; }; 4B957C1A2AC7AE700062CA31 /* PermissionAuthorization.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B64C84DD2692D7400048FEBE /* PermissionAuthorization.storyboard */; }; 4B957C1B2AC7AE700062CA31 /* dark-trackers-3.json in Resources */ = {isa = PBXBuildFile; fileRef = AA3439772754D55100B241FA /* dark-trackers-3.json */; }; 4B957C1C2AC7AE700062CA31 /* dark-trackers-2.json in Resources */ = {isa = PBXBuildFile; fileRef = AA3439722754D55100B241FA /* dark-trackers-2.json */; }; @@ -2514,7 +2504,6 @@ AAC30A2C268F1ECD00D2D9CD /* CrashReportSender.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAC30A2B268F1ECD00D2D9CD /* CrashReportSender.swift */; }; AAC30A2E268F1EE300D2D9CD /* CrashReportPromptPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAC30A2D268F1EE300D2D9CD /* CrashReportPromptPresenter.swift */; }; AAC5E4C725D6A6E8007F5990 /* AddBookmarkPopover.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAC5E4C425D6A6E8007F5990 /* AddBookmarkPopover.swift */; }; - AAC5E4C925D6A6E8007F5990 /* Bookmarks.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AAC5E4C625D6A6E8007F5990 /* Bookmarks.storyboard */; }; AAC5E4D025D6A709007F5990 /* Bookmark.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAC5E4CD25D6A709007F5990 /* Bookmark.swift */; }; AAC5E4D125D6A709007F5990 /* BookmarkManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAC5E4CE25D6A709007F5990 /* BookmarkManager.swift */; }; AAC5E4D225D6A709007F5990 /* BookmarkList.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAC5E4CF25D6A709007F5990 /* BookmarkList.swift */; }; @@ -2803,7 +2792,6 @@ B690152C2ACBF4DA00AD0BAB /* MenuPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = B690152B2ACBF4DA00AD0BAB /* MenuPreview.swift */; }; B690152D2ACBF4DA00AD0BAB /* MenuPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = B690152B2ACBF4DA00AD0BAB /* MenuPreview.swift */; }; B690152F2ACBF4DA00AD0BAB /* MenuPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = B690152B2ACBF4DA00AD0BAB /* MenuPreview.swift */; }; - B693954A26F04BEB0015B914 /* NibLoadable.swift in Sources */ = {isa = PBXBuildFile; fileRef = B693953C26F04BE70015B914 /* NibLoadable.swift */; }; B693954B26F04BEB0015B914 /* MouseOverView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B693953D26F04BE70015B914 /* MouseOverView.swift */; }; B693954C26F04BEB0015B914 /* FocusRingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B693953E26F04BE70015B914 /* FocusRingView.swift */; }; B693954E26F04BEB0015B914 /* LoadingProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B693954026F04BE80015B914 /* LoadingProgressView.swift */; }; @@ -3580,8 +3568,7 @@ 4B8F52402A18326600BE7131 /* NetworkProtectionTunnelController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkProtectionTunnelController.swift; sourceTree = ""; }; 4B92928526670D1600AD2C21 /* BookmarksOutlineView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookmarksOutlineView.swift; sourceTree = ""; }; 4B92928626670D1600AD2C21 /* OutlineSeparatorViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OutlineSeparatorViewCell.swift; sourceTree = ""; }; - 4B92928726670D1600AD2C21 /* BookmarkOutlineViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookmarkOutlineViewCell.swift; sourceTree = ""; }; - 4B92928826670D1600AD2C21 /* BookmarkOutlineViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = BookmarkOutlineViewCell.xib; sourceTree = ""; }; + 4B92928726670D1600AD2C21 /* BookmarkOutlineCellView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookmarkOutlineCellView.swift; sourceTree = ""; }; 4B92928926670D1700AD2C21 /* BookmarkTableCellView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookmarkTableCellView.swift; sourceTree = ""; }; 4B92929126670D2A00AD2C21 /* BookmarkOutlineViewDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookmarkOutlineViewDataSource.swift; sourceTree = ""; }; 4B92929226670D2A00AD2C21 /* PasteboardFolder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasteboardFolder.swift; sourceTree = ""; }; @@ -3612,7 +3599,6 @@ 4B9292CB2667123700AD2C21 /* AddBookmarkModalView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddBookmarkModalView.swift; sourceTree = ""; }; 4B9292CC2667123700AD2C21 /* BookmarkListViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookmarkListViewController.swift; sourceTree = ""; }; 4B9292CD2667123700AD2C21 /* BookmarkManagementDetailViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookmarkManagementDetailViewController.swift; sourceTree = ""; }; - 4B9292D62667124000AD2C21 /* NSPopUpButtonExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSPopUpButtonExtension.swift; sourceTree = ""; }; 4B9292D82667124B00AD2C21 /* BookmarkListTreeControllerDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookmarkListTreeControllerDataSource.swift; sourceTree = ""; }; 4B9292DA2667125D00AD2C21 /* ContextualMenu.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContextualMenu.swift; sourceTree = ""; }; 4B9579202AC687170062CA31 /* HardwareModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HardwareModel.swift; sourceTree = ""; }; @@ -4039,7 +4025,6 @@ AAC30A2B268F1ECD00D2D9CD /* CrashReportSender.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrashReportSender.swift; sourceTree = ""; }; AAC30A2D268F1EE300D2D9CD /* CrashReportPromptPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrashReportPromptPresenter.swift; sourceTree = ""; }; AAC5E4C425D6A6E8007F5990 /* AddBookmarkPopover.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddBookmarkPopover.swift; sourceTree = ""; }; - AAC5E4C625D6A6E8007F5990 /* Bookmarks.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Bookmarks.storyboard; sourceTree = ""; }; AAC5E4CD25D6A709007F5990 /* Bookmark.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Bookmark.swift; sourceTree = ""; }; AAC5E4CE25D6A709007F5990 /* BookmarkManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookmarkManager.swift; sourceTree = ""; }; AAC5E4CF25D6A709007F5990 /* BookmarkList.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookmarkList.swift; sourceTree = ""; }; @@ -4223,7 +4208,6 @@ B68C92C0274E3EF4002AC6B0 /* PopUpWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopUpWindow.swift; sourceTree = ""; }; B68C92C32750EF76002AC6B0 /* PixelDataRecord.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PixelDataRecord.swift; sourceTree = ""; }; B690152B2ACBF4DA00AD0BAB /* MenuPreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuPreview.swift; sourceTree = ""; }; - B693953C26F04BE70015B914 /* NibLoadable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NibLoadable.swift; sourceTree = ""; }; B693953D26F04BE70015B914 /* MouseOverView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MouseOverView.swift; sourceTree = ""; }; B693953E26F04BE70015B914 /* FocusRingView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FocusRingView.swift; sourceTree = ""; }; B693954026F04BE80015B914 /* LoadingProgressView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoadingProgressView.swift; sourceTree = ""; }; @@ -5666,7 +5650,6 @@ 4B9292AD26670F5300AD2C21 /* Extensions */ = { isa = PBXGroup; children = ( - 4B9292D62667124000AD2C21 /* NSPopUpButtonExtension.swift */, 4B9292AE26670F5300AD2C21 /* NSOutlineViewExtensions.swift */, B6C0BB6629AEFF8100AE8E3C /* BookmarkExtension.swift */, ); @@ -6228,7 +6211,6 @@ AA7EB6DE27E7C57D00036718 /* MouseOverAnimationButton.swift */, B693953D26F04BE70015B914 /* MouseOverView.swift */, 4B379C2327BDE1B0008A968E /* FlatButton.swift */, - B693953C26F04BE70015B914 /* NibLoadable.swift */, B693954726F04BEA0015B914 /* NSSavePanelExtension.swift */, B693954126F04BE80015B914 /* PaddedImageButton.swift */, B693954026F04BE80015B914 /* LoadingProgressView.swift */, @@ -7300,19 +7282,17 @@ 7BEC20402B0F505F00243D3E /* AddBookmarkPopoverView.swift */, 4B9292CB2667123700AD2C21 /* AddBookmarkModalView.swift */, 4B9292CA2667123700AD2C21 /* AddBookmarkFolderModalView.swift */, - 4B9292CC2667123700AD2C21 /* BookmarkListViewController.swift */, B69A14F92B4D705D00B9417D /* BookmarkFolderPicker.swift */, + 4B9292CC2667123700AD2C21 /* BookmarkListViewController.swift */, 4B9292CD2667123700AD2C21 /* BookmarkManagementDetailViewController.swift */, 4B9292C72667123700AD2C21 /* BookmarkManagementSidebarViewController.swift */, 4B9292C82667123700AD2C21 /* BookmarkManagementSplitViewController.swift */, 4B9292C92667123700AD2C21 /* BookmarkTableRowView.swift */, - 4B9292C62667123700AD2C21 /* BrowserTabSelectionDelegate.swift */, - 4B92928726670D1600AD2C21 /* BookmarkOutlineViewCell.swift */, - 4B92928826670D1600AD2C21 /* BookmarkOutlineViewCell.xib */, + 4B92928726670D1600AD2C21 /* BookmarkOutlineCellView.swift */, 4B92928526670D1600AD2C21 /* BookmarksOutlineView.swift */, 4B92928926670D1700AD2C21 /* BookmarkTableCellView.swift */, + 4B9292C62667123700AD2C21 /* BrowserTabSelectionDelegate.swift */, 4B92928626670D1600AD2C21 /* OutlineSeparatorViewCell.swift */, - AAC5E4C625D6A6E8007F5990 /* Bookmarks.storyboard */, 4B0511B3262CAA5A00F6079C /* RoundedSelectionRowView.swift */, ); path = View; @@ -8800,7 +8780,6 @@ 3706FCCA293F65D500E42796 /* FirePopoverCollectionViewHeader.xib in Resources */, 3706FCCC293F65D500E42796 /* TabBar.storyboard in Resources */, 3706FCCD293F65D500E42796 /* shield-dot.json in Resources */, - 3706FCCF293F65D500E42796 /* Bookmarks.storyboard in Resources */, 3706FCD0293F65D500E42796 /* BookmarksBarCollectionViewItem.xib in Resources */, 3706FCD1293F65D500E42796 /* PrivacyDashboard.storyboard in Resources */, 3706FCD2293F65D500E42796 /* shield.json in Resources */, @@ -8813,7 +8792,6 @@ B658BAB72B0F848D00D1F2C7 /* Localizable.xcstrings in Resources */, 3706FCDE293F65D500E42796 /* HomePageAssets.xcassets in Resources */, 3706FCDF293F65D500E42796 /* shield-mouse-over.json in Resources */, - 3706FCE0293F65D500E42796 /* BookmarkOutlineViewCell.xib in Resources */, 3706FCE1293F65D500E42796 /* PermissionAuthorization.storyboard in Resources */, 3706FCE2293F65D500E42796 /* dark-trackers-3.json in Resources */, 3706FCE3293F65D500E42796 /* dark-trackers-2.json in Resources */, @@ -8930,7 +8908,6 @@ 4B957C062AC7AE700062CA31 /* FirePopoverCollectionViewHeader.xib in Resources */, 4B957C072AC7AE700062CA31 /* TabBar.storyboard in Resources */, 4B957C082AC7AE700062CA31 /* shield-dot.json in Resources */, - 4B957C0A2AC7AE700062CA31 /* Bookmarks.storyboard in Resources */, 4B957C0B2AC7AE700062CA31 /* BookmarksBarCollectionViewItem.xib in Resources */, 4B957C0C2AC7AE700062CA31 /* PrivacyDashboard.storyboard in Resources */, 4B957C0D2AC7AE700062CA31 /* shield.json in Resources */, @@ -8943,7 +8920,6 @@ B658BAB92B0F849100D1F2C7 /* Localizable.xcstrings in Resources */, 4B957C172AC7AE700062CA31 /* HomePageAssets.xcassets in Resources */, 4B957C182AC7AE700062CA31 /* shield-mouse-over.json in Resources */, - 4B957C192AC7AE700062CA31 /* BookmarkOutlineViewCell.xib in Resources */, 4B957C1A2AC7AE700062CA31 /* PermissionAuthorization.storyboard in Resources */, 4B957C1B2AC7AE700062CA31 /* dark-trackers-3.json in Resources */, 4B957C1C2AC7AE700062CA31 /* dark-trackers-2.json in Resources */, @@ -9029,7 +9005,6 @@ AAE246F6270A3D3000BEEAEE /* FirePopoverCollectionViewHeader.xib in Resources */, AA80EC79256C46AA007083E7 /* TabBar.storyboard in Resources */, AA34396D2754D4E300B241FA /* shield-dot.json in Resources */, - AAC5E4C925D6A6E8007F5990 /* Bookmarks.storyboard in Resources */, 4BE5336B286912D40019DBFD /* BookmarksBarCollectionViewItem.xib in Resources */, B6FA893D269C423100588ECD /* PrivacyDashboard.storyboard in Resources */, AA34396C2754D4E300B241FA /* shield.json in Resources */, @@ -9042,7 +9017,6 @@ B658BAB62B0F845D00D1F2C7 /* Localizable.xcstrings in Resources */, 85AC7AD927BD625000FFB69B /* HomePageAssets.xcassets in Resources */, AA7EB6E727E8809D00036718 /* shield-mouse-over.json in Resources */, - 4B92928E26670D1700AD2C21 /* BookmarkOutlineViewCell.xib in Resources */, B64C84DE2692D7400048FEBE /* PermissionAuthorization.storyboard in Resources */, AA34397D2754D55100B241FA /* dark-trackers-3.json in Resources */, AA3439782754D55100B241FA /* dark-trackers-2.json in Resources */, @@ -9467,11 +9441,10 @@ 3706FAAD293F65D500E42796 /* BadgeNotificationAnimationModel.swift in Sources */, 3706FAAE293F65D500E42796 /* HyperLink.swift in Sources */, 3706FAAF293F65D500E42796 /* PasteboardWriting.swift in Sources */, - 3706FAB0293F65D500E42796 /* BookmarkOutlineViewCell.swift in Sources */, + 3706FAB0293F65D500E42796 /* BookmarkOutlineCellView.swift in Sources */, 3706FAB1293F65D500E42796 /* UnprotectedDomains.xcdatamodeld in Sources */, 85393C872A6FF1B600F11EB3 /* BookmarksBarAppearance.swift in Sources */, 3706FAB2293F65D500E42796 /* TabInstrumentation.swift in Sources */, - 3706FAB4293F65D500E42796 /* NSPopUpButtonExtension.swift in Sources */, 3706FAB5293F65D500E42796 /* ConfigurationManager.swift in Sources */, 3706FAB6293F65D500E42796 /* YoutubePlayerUserScript.swift in Sources */, 1D8057C92A83CB3C00F4FED6 /* SupportedOsChecker.swift in Sources */, @@ -10009,7 +9982,6 @@ 31267C6A2B640C4B00FEF811 /* DataBrokerProtectionFeatureDisabler.swift in Sources */, 3707C723294B5D2900682A9F /* URLSessionExtension.swift in Sources */, 3706FC4E293F65D500E42796 /* AtbAndVariantCleanup.swift in Sources */, - 3706FC4F293F65D500E42796 /* NibLoadable.swift in Sources */, 3706FC50293F65D500E42796 /* FeedbackWindow.swift in Sources */, 3706FC51293F65D500E42796 /* RecentlyVisitedView.swift in Sources */, B645D8F729FA95440024461F /* WKProcessPoolExtension.swift in Sources */, @@ -10624,10 +10596,9 @@ 4B95797F2AC7AE700062CA31 /* HyperLink.swift in Sources */, 4B9579802AC7AE700062CA31 /* SyncDataProviders.swift in Sources */, 4B9579812AC7AE700062CA31 /* PasteboardWriting.swift in Sources */, - 4B9579822AC7AE700062CA31 /* BookmarkOutlineViewCell.swift in Sources */, + 4B9579822AC7AE700062CA31 /* BookmarkOutlineCellView.swift in Sources */, 4B9579832AC7AE700062CA31 /* UnprotectedDomains.xcdatamodeld in Sources */, 4B9579842AC7AE700062CA31 /* TabInstrumentation.swift in Sources */, - 4B9579862AC7AE700062CA31 /* NSPopUpButtonExtension.swift in Sources */, 4B9579872AC7AE700062CA31 /* ConfigurationManager.swift in Sources */, 4B9579882AC7AE700062CA31 /* YoutubePlayerUserScript.swift in Sources */, 4B9579892AC7AE700062CA31 /* PixelParameters.swift in Sources */, @@ -11150,7 +11121,6 @@ 4B957B632AC7AE700062CA31 /* LocalStatisticsStore.swift in Sources */, 4B957B642AC7AE700062CA31 /* BackForwardListItem.swift in Sources */, 4B957B672AC7AE700062CA31 /* AtbAndVariantCleanup.swift in Sources */, - 4B957B682AC7AE700062CA31 /* NibLoadable.swift in Sources */, 4B957B692AC7AE700062CA31 /* FeedbackWindow.swift in Sources */, 4B957B6A2AC7AE700062CA31 /* WorkspaceProtocol.swift in Sources */, 4B957B6B2AC7AE700062CA31 /* RecentlyVisitedView.swift in Sources */, @@ -11391,10 +11361,9 @@ 37445F992A1566420029F789 /* SyncDataProviders.swift in Sources */, 4B9292A426670D2A00AD2C21 /* PasteboardWriting.swift in Sources */, B6B4D1C52B0B3B5400C26286 /* DataImportReportModel.swift in Sources */, - 4B92928D26670D1700AD2C21 /* BookmarkOutlineViewCell.swift in Sources */, + 4B92928D26670D1700AD2C21 /* BookmarkOutlineCellView.swift in Sources */, B604085C274B8FBA00680351 /* UnprotectedDomains.xcdatamodeld in Sources */, 4BB88B5025B7BA2B006F6B06 /* TabInstrumentation.swift in Sources */, - 4B9292D72667124000AD2C21 /* NSPopUpButtonExtension.swift in Sources */, 85D33F1225C82EB3002B91A6 /* ConfigurationManager.swift in Sources */, 31F28C4F28C8EEC500119F70 /* YoutubePlayerUserScript.swift in Sources */, 4B41EDAE2B168AFF001EEDF4 /* VPNFeedbackFormViewController.swift in Sources */, @@ -11918,7 +11887,6 @@ B69B503F2726A12500758A2B /* LocalStatisticsStore.swift in Sources */, B689ECD526C247DB006FB0C5 /* BackForwardListItem.swift in Sources */, B69B50572727D16900758A2B /* AtbAndVariantCleanup.swift in Sources */, - B693954A26F04BEB0015B914 /* NibLoadable.swift in Sources */, AA3D531527A1ED9300074EC1 /* FeedbackWindow.swift in Sources */, B6685E3F29A606190043D2EE /* WorkspaceProtocol.swift in Sources */, B6ABC5962B4861D4008343B9 /* FocusableTextField.swift in Sources */, diff --git a/DuckDuckGo/Bookmarks/Extensions/NSPopUpButtonExtension.swift b/DuckDuckGo/Bookmarks/Extensions/NSPopUpButtonExtension.swift deleted file mode 100644 index 747feb9193..0000000000 --- a/DuckDuckGo/Bookmarks/Extensions/NSPopUpButtonExtension.swift +++ /dev/null @@ -1,69 +0,0 @@ -// -// NSPopUpButtonExtension.swift -// -// Copyright © 2021 DuckDuckGo. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -import AppKit -import Combine - -extension NSPopUpButton { - - var selectionPublisher: AnyPublisher { - NotificationCenter.default - .publisher(for: NSMenu.didSendActionNotification, object: menu) - .map { _ in self.indexOfSelectedItem } - .prepend(self.indexOfSelectedItem) - .eraseToAnyPublisher() - } - - func displayBrowserTabButtons(withSelectedTab tabType: Tab.TabContent) { - removeAllItems() - - var selectedTabIndex: Int? - - for (index, type) in Tab.TabContent.displayableTabTypes.enumerated() { - guard let tabTitle = type.title else { - assertionFailure("Attempted to display standard tab type in tab switcher") - return - } - - addItem(withTitle: tabTitle) - - if type == tabType { - selectedTabIndex = index - } - } - - selectItem(at: selectedTabIndex ?? 0) - } - - func select(tabType: Tab.TabContent) { - guard let title = tabType.title else { - return - } - - selectItem(withTitle: title) - } - - @discardableResult - func addItem(withTitle title: String, representedObject: Any?) -> NSMenuItem { - self.addItem(withTitle: title) - let item = self.item(at: self.numberOfItems - 1)! - item.representedObject = representedObject - return item - } - -} diff --git a/DuckDuckGo/Bookmarks/Model/BookmarkListTreeControllerDataSource.swift b/DuckDuckGo/Bookmarks/Model/BookmarkListTreeControllerDataSource.swift index f363ecd3f9..13b1e163df 100644 --- a/DuckDuckGo/Bookmarks/Model/BookmarkListTreeControllerDataSource.swift +++ b/DuckDuckGo/Bookmarks/Model/BookmarkListTreeControllerDataSource.swift @@ -20,6 +20,12 @@ import Foundation final class BookmarkListTreeControllerDataSource: BookmarkTreeControllerDataSource { + private let bookmarkManager: BookmarkManager + + init(bookmarkManager: BookmarkManager) { + self.bookmarkManager = bookmarkManager + } + func treeController(treeController: BookmarkTreeController, childNodesFor node: BookmarkNode) -> [BookmarkNode] { return node.isRoot ? childNodesForRootNode(node) : childNodes(node) } @@ -27,7 +33,7 @@ final class BookmarkListTreeControllerDataSource: BookmarkTreeControllerDataSour // MARK: - Private private func childNodesForRootNode(_ node: BookmarkNode) -> [BookmarkNode] { - let topLevelNodes = LocalBookmarkManager.shared.list?.topLevelEntities.compactMap { (item) -> BookmarkNode? in + let topLevelNodes = bookmarkManager.list?.topLevelEntities.compactMap { (item) -> BookmarkNode? in if let folder = item as? BookmarkFolder { let itemNode = node.createChildNode(item) itemNode.canHaveChildNodes = !folder.children.isEmpty diff --git a/DuckDuckGo/Bookmarks/Model/BookmarkManager.swift b/DuckDuckGo/Bookmarks/Model/BookmarkManager.swift index 54fe8dd783..ee19960da1 100644 --- a/DuckDuckGo/Bookmarks/Model/BookmarkManager.swift +++ b/DuckDuckGo/Bookmarks/Model/BookmarkManager.swift @@ -51,6 +51,8 @@ protocol BookmarkManager: AnyObject { var listPublisher: Published.Publisher { get } var list: BookmarkList? { get } + func requestSync() + } final class LocalBookmarkManager: BookmarkManager { diff --git a/DuckDuckGo/Bookmarks/Model/BookmarkOutlineViewDataSource.swift b/DuckDuckGo/Bookmarks/Model/BookmarkOutlineViewDataSource.swift index 11cce04493..edfcfa6e2e 100644 --- a/DuckDuckGo/Bookmarks/Model/BookmarkOutlineViewDataSource.swift +++ b/DuckDuckGo/Bookmarks/Model/BookmarkOutlineViewDataSource.swift @@ -41,7 +41,7 @@ final class BookmarkOutlineViewDataSource: NSObject, NSOutlineViewDataSource, NS init( contentMode: ContentMode, - bookmarkManager: BookmarkManager = LocalBookmarkManager.shared, + bookmarkManager: BookmarkManager, treeController: BookmarkTreeController, presentFaviconsFetcherOnboarding: (() -> Void)? = nil ) { @@ -117,11 +117,12 @@ final class BookmarkOutlineViewDataSource: NSObject, NSOutlineViewDataSource, NS } func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? { - guard let node = item as? BookmarkNode, - let cell = outlineView.makeView(withIdentifier: BookmarkOutlineViewCell.identifier, owner: self) as? BookmarkOutlineViewCell else { - assertionFailure("\(#file): Failed to create BookmarkOutlineViewCell or cast item to Node") + guard let node = item as? BookmarkNode else { + assertionFailure("\(#file): Failed to cast item to Node") return nil } + let cell = outlineView.makeView(withIdentifier: .init(BookmarkOutlineCellView.className()), owner: self) as? BookmarkOutlineCellView + ?? BookmarkOutlineCellView(identifier: .init(BookmarkOutlineCellView.className())) if let bookmark = node.representedObject as? Bookmark { cell.update(from: bookmark) diff --git a/DuckDuckGo/Bookmarks/Model/BookmarkSidebarTreeController.swift b/DuckDuckGo/Bookmarks/Model/BookmarkSidebarTreeController.swift index f8609a6917..06167f2356 100644 --- a/DuckDuckGo/Bookmarks/Model/BookmarkSidebarTreeController.swift +++ b/DuckDuckGo/Bookmarks/Model/BookmarkSidebarTreeController.swift @@ -26,7 +26,7 @@ final class BookmarkSidebarTreeController: BookmarkTreeControllerDataSource { private let bookmarkManager: BookmarkManager - init(bookmarkManager: BookmarkManager = LocalBookmarkManager.shared) { + init(bookmarkManager: BookmarkManager) { self.bookmarkManager = bookmarkManager } diff --git a/DuckDuckGo/Bookmarks/Model/PseudoFolder.swift b/DuckDuckGo/Bookmarks/Model/PseudoFolder.swift index 012882d93f..c3aed14be7 100644 --- a/DuckDuckGo/Bookmarks/Model/PseudoFolder.swift +++ b/DuckDuckGo/Bookmarks/Model/PseudoFolder.swift @@ -16,12 +16,13 @@ // limitations under the License. // +import AppKit import Foundation final class PseudoFolder: Equatable { - static let favorites = PseudoFolder(id: UUID().uuidString, name: UserText.favorites, icon: NSImage(named: "FavoriteFilledBorder")!) - static let bookmarks = PseudoFolder(id: UUID().uuidString, name: UserText.bookmarks, icon: NSImage(named: "Folder")!) + static let favorites = PseudoFolder(id: UUID().uuidString, name: UserText.favorites, icon: .favoriteFilledBorder) + static let bookmarks = PseudoFolder(id: UUID().uuidString, name: UserText.bookmarks, icon: .folder) let id: String let name: String @@ -38,7 +39,7 @@ final class PseudoFolder: Equatable { } static func == (lhs: PseudoFolder, rhs: PseudoFolder) -> Bool { - return lhs.name == rhs.name + return lhs.id == rhs.id } } diff --git a/DuckDuckGo/Bookmarks/View/BookmarkListViewController.swift b/DuckDuckGo/Bookmarks/View/BookmarkListViewController.swift index 8b8212446e..541ad955b6 100644 --- a/DuckDuckGo/Bookmarks/View/BookmarkListViewController.swift +++ b/DuckDuckGo/Bookmarks/View/BookmarkListViewController.swift @@ -28,44 +28,40 @@ protocol BookmarkListViewControllerDelegate: AnyObject { final class BookmarkListViewController: NSViewController { - private enum Constants { - static let storyboardName = "Bookmarks" - static let identifier = "BookmarkListViewController" - } - - static func create() -> BookmarkListViewController { - let storyboard = NSStoryboard(name: Constants.storyboardName, bundle: nil) - return storyboard.instantiateController(identifier: Constants.identifier) - } + static let preferredContentSize = CGSize(width: 420, height: 500) weak var delegate: BookmarkListViewControllerDelegate? var currentTabWebsite: WebsiteInfo? - @IBOutlet var outlineView: NSOutlineView! - @IBOutlet var contextMenu: NSMenu! - @IBOutlet var emptyState: NSView! - @IBOutlet var emptyStateTitle: NSTextField! - @IBOutlet var emptyStateMessage: NSTextField! + private lazy var titleTextField = NSTextField(string: UserText.bookmarks) - @IBOutlet var newBookmarkButton: NSButton! - @IBOutlet var newFolderButton: NSButton! - @IBOutlet var manageBookmarksButton: NSButton! + private lazy var stackView = NSStackView() + private lazy var newBookmarkButton = MouseOverButton(image: .addBookmark, target: self, action: #selector(newBookmarkButtonClicked)) + private lazy var newFolderButton = MouseOverButton(image: .addFolder, target: self, action: #selector(newFolderButtonClicked)) - private var cancellables = Set() - private var bookmarkManager: BookmarkManager = LocalBookmarkManager.shared - private let treeControllerDataSource = BookmarkListTreeControllerDataSource() + private lazy var buttonsDivider = NSBox() + private lazy var manageBookmarksButton = MouseOverButton(title: UserText.bookmarksManage, target: self, action: #selector(openManagementInterface)) + private lazy var boxDivider = NSBox() - private var mouseUpEventsMonitor: Any? - private var mouseDownEventsMonitor: Any? - private var appObserver: Any? + private lazy var scrollView = NSScrollView(frame: NSRect(x: 0, y: 0, width: 420, height: 408)) + private lazy var outlineView = BookmarksOutlineView(frame: scrollView.frame) - private lazy var treeController: BookmarkTreeController = { - return BookmarkTreeController(dataSource: treeControllerDataSource) - }() + private lazy var emptyState = NSView() + private lazy var emptyStateTitle = NSTextField() + private lazy var emptyStateMessage = NSTextField() + private lazy var emptyStateImageView = NSImageView(image: .bookmarksEmpty) + private lazy var importButton = NSButton(title: UserText.importBookmarksButtonTitle, target: self, action: #selector(onImportClicked)) + + private var cancellables = Set() + private let bookmarkManager: BookmarkManager + private let treeControllerDataSource: BookmarkListTreeControllerDataSource + + private lazy var treeController = BookmarkTreeController(dataSource: treeControllerDataSource) private lazy var dataSource: BookmarkOutlineViewDataSource = { BookmarkOutlineViewDataSource( contentMode: .bookmarksAndFolders, + bookmarkManager: bookmarkManager, treeController: treeController, presentFaviconsFetcherOnboarding: { [weak self] in guard let self, let window = self.view.window else { @@ -91,31 +87,235 @@ final class BookmarkListViewController: NSViewController { return .init(syncService: syncService, syncBookmarksAdapter: syncBookmarksAdapter) }() - override func viewDidLoad() { - super.viewDidLoad() + init(bookmarkManager: BookmarkManager = LocalBookmarkManager.shared) { + self.bookmarkManager = bookmarkManager + self.treeControllerDataSource = BookmarkListTreeControllerDataSource(bookmarkManager: bookmarkManager) + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("\(type(of: self)): Bad initializer") + } + + override func loadView() { // swiftlint:disable:this function_body_length + view = ColorView(frame: .zero, backgroundColor: .popoverBackground) + + view.addSubview(titleTextField) + view.addSubview(boxDivider) + view.addSubview(stackView) + view.addSubview(scrollView) + view.addSubview(emptyState) + + view.autoresizesSubviews = false + + titleTextField.isEditable = false + titleTextField.isBordered = false + titleTextField.drawsBackground = false + titleTextField.translatesAutoresizingMaskIntoConstraints = false + titleTextField.font = .systemFont(ofSize: 17) + titleTextField.textColor = .labelColor + + boxDivider.boxType = .separator + boxDivider.setContentHuggingPriority(.defaultHigh, for: .vertical) + boxDivider.translatesAutoresizingMaskIntoConstraints = false + + stackView.orientation = .horizontal + stackView.spacing = 4 + stackView.setHuggingPriority(.defaultHigh, for: .horizontal) + stackView.setHuggingPriority(.defaultHigh, for: .vertical) + stackView.translatesAutoresizingMaskIntoConstraints = false + stackView.addArrangedSubview(newBookmarkButton) + stackView.addArrangedSubview(newFolderButton) + stackView.addArrangedSubview(buttonsDivider) + stackView.addArrangedSubview(manageBookmarksButton) + + newBookmarkButton.bezelStyle = .shadowlessSquare + newBookmarkButton.cornerRadius = 4 + newBookmarkButton.normalTintColor = .button + newBookmarkButton.mouseDownColor = .buttonMouseDown + newBookmarkButton.mouseOverColor = .buttonMouseOver + newBookmarkButton.translatesAutoresizingMaskIntoConstraints = false + newBookmarkButton.toolTip = UserText.newBookmarkTooltip + + newFolderButton.bezelStyle = .shadowlessSquare + newFolderButton.cornerRadius = 4 + newFolderButton.normalTintColor = .button + newFolderButton.mouseDownColor = .buttonMouseDown + newFolderButton.mouseOverColor = .buttonMouseOver + newFolderButton.translatesAutoresizingMaskIntoConstraints = false + newFolderButton.toolTip = UserText.newFolderTooltip + + buttonsDivider.boxType = .separator + buttonsDivider.setContentHuggingPriority(.defaultHigh, for: .horizontal) + buttonsDivider.translatesAutoresizingMaskIntoConstraints = false - preferredContentSize = CGSize(width: 420, height: 500) + manageBookmarksButton.bezelStyle = .shadowlessSquare + manageBookmarksButton.cornerRadius = 4 + manageBookmarksButton.normalTintColor = .button + manageBookmarksButton.mouseDownColor = .buttonMouseDown + manageBookmarksButton.mouseOverColor = .buttonMouseOver + manageBookmarksButton.translatesAutoresizingMaskIntoConstraints = false + manageBookmarksButton.font = .systemFont(ofSize: 12) + manageBookmarksButton.toolTip = UserText.manageBookmarksTooltip + manageBookmarksButton.image = { + let image = NSImage.externalAppScheme + image.alignmentRect = NSRect(x: 0, y: 0, width: image.size.width + 6, height: image.size.height) + return image + }() + manageBookmarksButton.imagePosition = .imageLeading + manageBookmarksButton.imageHugsTitle = true - outlineView.register(BookmarkOutlineViewCell.nib, forIdentifier: BookmarkOutlineViewCell.identifier) + scrollView.borderType = .noBorder + scrollView.drawsBackground = false + scrollView.translatesAutoresizingMaskIntoConstraints = false + scrollView.hasHorizontalScroller = false + scrollView.usesPredominantAxisScrolling = false + scrollView.autohidesScrollers = true + scrollView.automaticallyAdjustsContentInsets = false + scrollView.scrollerInsets = NSEdgeInsets(top: 5, left: 0, bottom: 5, right: 0) + scrollView.contentInsets = NSEdgeInsets(top: 0, left: 0, bottom: 0, right: 12) + + let column = NSTableColumn() + column.width = scrollView.frame.width - 32 + outlineView.addTableColumn(column) + outlineView.translatesAutoresizingMaskIntoConstraints = true + outlineView.autoresizesOutlineColumn = false + outlineView.autoresizingMask = [.width, .height] + outlineView.headerView = nil + outlineView.allowsEmptySelection = false + outlineView.allowsExpansionToolTips = true + outlineView.allowsMultipleSelection = false + outlineView.backgroundColor = .clear + outlineView.indentationPerLevel = 13 + outlineView.rowHeight = 24 + outlineView.usesAutomaticRowHeights = true + outlineView.target = self + outlineView.action = #selector(handleClick) + outlineView.menu = NSMenu() + outlineView.menu!.delegate = self outlineView.dataSource = dataSource outlineView.delegate = dataSource + + let clipView = NSClipView(frame: scrollView.frame) + clipView.translatesAutoresizingMaskIntoConstraints = true + clipView.autoresizingMask = [.width, .height] + clipView.documentView = outlineView + clipView.drawsBackground = false + scrollView.contentView = clipView + + emptyState.addSubview(emptyStateImageView) + emptyState.addSubview(emptyStateTitle) + emptyState.addSubview(emptyStateMessage) + emptyState.addSubview(importButton) + + emptyState.isHidden = true + emptyState.translatesAutoresizingMaskIntoConstraints = false + + emptyStateTitle.translatesAutoresizingMaskIntoConstraints = false + emptyStateTitle.alignment = .center + emptyStateTitle.drawsBackground = false + emptyStateTitle.isBordered = false + emptyStateTitle.isEditable = false + emptyStateTitle.font = .systemFont(ofSize: 15, weight: .semibold) + emptyStateTitle.textColor = .labelColor + emptyStateTitle.attributedStringValue = NSAttributedString.make(UserText.bookmarksEmptyStateTitle, + lineHeight: 1.14, + kern: -0.23) + + emptyStateMessage.translatesAutoresizingMaskIntoConstraints = false + emptyStateMessage.alignment = .center + emptyStateMessage.drawsBackground = false + emptyStateMessage.isBordered = false + emptyStateMessage.isEditable = false + emptyStateMessage.font = .systemFont(ofSize: 13) + emptyStateMessage.textColor = .labelColor + emptyStateMessage.attributedStringValue = NSAttributedString.make(UserText.bookmarksEmptyStateMessage, + lineHeight: 1.05, + kern: -0.08) + + importButton.translatesAutoresizingMaskIntoConstraints = false + + setupLayout() + } + + private func setupLayout() { + titleTextField.setContentHuggingPriority(.defaultHigh, for: .vertical) + titleTextField.setContentHuggingPriority(.init(rawValue: 251), for: .horizontal) + titleTextField.topAnchor.constraint(equalTo: view.topAnchor, constant: 12).isActive = true + titleTextField.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 15).isActive = true + + newBookmarkButton.heightAnchor.constraint(equalToConstant: 28).isActive = true + newBookmarkButton.widthAnchor.constraint(equalToConstant: 28).isActive = true + + newFolderButton.heightAnchor.constraint(equalToConstant: 28).isActive = true + newFolderButton.widthAnchor.constraint(equalToConstant: 28).isActive = true + + buttonsDivider.widthAnchor.constraint(equalToConstant: 13).isActive = true + buttonsDivider.heightAnchor.constraint(equalToConstant: 18).isActive = true + + manageBookmarksButton.heightAnchor.constraint(equalToConstant: 28).isActive = true + let titleWidth = (manageBookmarksButton.title as NSString) + .size(withAttributes: [.font: manageBookmarksButton.font as Any]).width + let buttonWidth = manageBookmarksButton.image!.size.height + titleWidth + 18 + manageBookmarksButton.widthAnchor.constraint(equalToConstant: buttonWidth).isActive = true + + stackView.centerYAnchor.constraint(equalTo: titleTextField.centerYAnchor).isActive = true + view.trailingAnchor.constraint(equalTo: stackView.trailingAnchor, constant: 20).isActive = true + + boxDivider.topAnchor.constraint(equalTo: titleTextField.bottomAnchor, constant: 12).isActive = true + boxDivider.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true + view.trailingAnchor.constraint(equalTo: boxDivider.trailingAnchor).isActive = true + + scrollView.topAnchor.constraint(equalTo: boxDivider.bottomAnchor).isActive = true + scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true + scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true + scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true + + emptyState.topAnchor.constraint(equalTo: boxDivider.bottomAnchor).isActive = true + emptyState.centerXAnchor.constraint(equalTo: boxDivider.centerXAnchor).isActive = true + emptyState.widthAnchor.constraint(equalToConstant: 342).isActive = true + emptyState.heightAnchor.constraint(equalToConstant: 383).isActive = true + + emptyStateImageView.translatesAutoresizingMaskIntoConstraints = false + emptyStateImageView.setContentHuggingPriority(.init(rawValue: 251), for: .horizontal) + emptyStateImageView.setContentHuggingPriority(.init(rawValue: 251), for: .vertical) + emptyStateImageView.topAnchor.constraint(equalTo: emptyState.topAnchor, constant: 94.5).isActive = true + emptyStateImageView.widthAnchor.constraint(equalToConstant: 128).isActive = true + emptyStateImageView.heightAnchor.constraint(equalToConstant: 96).isActive = true + emptyStateImageView.centerXAnchor.constraint(equalTo: emptyState.centerXAnchor).isActive = true + + emptyStateTitle.setContentHuggingPriority(.defaultHigh, for: .vertical) + emptyStateTitle.setContentHuggingPriority(.init(rawValue: 251), for: .horizontal) + emptyStateTitle.topAnchor.constraint(equalTo: emptyStateImageView.bottomAnchor, constant: 8).isActive = true + emptyStateTitle.centerXAnchor.constraint(equalTo: emptyState.centerXAnchor).isActive = true + emptyStateTitle.widthAnchor.constraint(equalToConstant: 192).isActive = true + + emptyStateMessage.setContentHuggingPriority(.defaultHigh, for: .vertical) + emptyStateMessage.setContentHuggingPriority(.init(rawValue: 251), for: .horizontal) + emptyStateMessage.topAnchor.constraint(equalTo: emptyStateTitle.bottomAnchor, constant: 8).isActive = true + emptyStateMessage.centerXAnchor.constraint(equalTo: emptyState.centerXAnchor).isActive = true + + emptyStateMessage.widthAnchor.constraint(equalToConstant: 192).isActive = true + + importButton.topAnchor.constraint(equalTo: emptyStateMessage.bottomAnchor, constant: 8).isActive = true + importButton.centerXAnchor.constraint(equalTo: emptyState.centerXAnchor).isActive = true + } + + override func viewDidLoad() { + super.viewDidLoad() + + preferredContentSize = Self.preferredContentSize + outlineView.setDraggingSourceOperationMask([.move], forLocal: true) outlineView.registerForDraggedTypes([BookmarkPasteboardWriter.bookmarkUTIInternalType, FolderPasteboardWriter.folderUTIInternalType]) - LocalBookmarkManager.shared.listPublisher.receive(on: DispatchQueue.main).sink { [weak self] list in + bookmarkManager.listPublisher.receive(on: DispatchQueue.main).sink { [weak self] list in self?.reloadData() let isEmpty = list?.topLevelEntities.isEmpty ?? true self?.emptyState.isHidden = !isEmpty self?.outlineView.isHidden = isEmpty }.store(in: &cancellables) - - emptyStateTitle.attributedStringValue = NSAttributedString.make(emptyStateTitle.stringValue, lineHeight: 1.14, kern: -0.23) - emptyStateMessage.attributedStringValue = NSAttributedString.make(emptyStateMessage.stringValue, lineHeight: 1.05, kern: -0.08) - - newBookmarkButton.toolTip = UserText.newBookmarkTooltip - newFolderButton.toolTip = UserText.newFolderTooltip - setUpManageBookmarksButton() } override func viewWillAppear() { @@ -133,14 +333,14 @@ final class BookmarkListViewController: NSViewController { expandAndRestore(selectedNodes: selectedNodes) } - @IBAction func newBookmarkButtonClicked(_ sender: AnyObject) { + @objc func newBookmarkButtonClicked(_ sender: AnyObject) { delegate?.popover(shouldPreventClosure: true) AddBookmarkModalView(model: AddBookmarkModalViewModel(currentTabWebsite: currentTabWebsite) { [weak delegate] _ in delegate?.popover(shouldPreventClosure: false) }).show(in: parent?.view.window) } - @IBAction func newFolderButtonClicked(_ sender: AnyObject) { + @objc func newFolderButtonClicked(_ sender: AnyObject) { delegate?.popover(shouldPreventClosure: true) AddBookmarkFolderModalView() .show(in: parent?.view.window) { [weak delegate] in @@ -148,12 +348,12 @@ final class BookmarkListViewController: NSViewController { } } - @IBAction func openManagementInterface(_ sender: NSButton) { + @objc func openManagementInterface(_ sender: NSButton) { WindowControllersManager.shared.showBookmarksTab() delegate?.popoverShouldClose(self) } - @IBAction func handleClick(_ sender: NSOutlineView) { + @objc func handleClick(_ sender: NSOutlineView) { guard sender.clickedRow != -1 else { return } let item = sender.item(atRow: sender.clickedRow) @@ -170,7 +370,7 @@ final class BookmarkListViewController: NSViewController { } } - @IBAction func onImportClicked(_ sender: NSButton) { + @objc func onImportClicked(_ sender: NSButton) { DataImportView().show() } @@ -225,29 +425,6 @@ final class BookmarkListViewController: NSViewController { outlineView.selectRowIndexes(indexes, byExtendingSelection: false) } - private func setUpManageBookmarksButton() { - // Set up image - let image = NSImage(named: "ExternalAppScheme") - let imageSize = image?.size ?? .zero - let padding = 6.0 - let newRect = NSRect(x: 0.0, y: 0.0, width: imageSize.width + padding, height: imageSize.height) - image?.alignmentRect = newRect - - // Set up button - manageBookmarksButton.image = image - manageBookmarksButton.title = UserText.bookmarksManage - manageBookmarksButton.toolTip = UserText.manageBookmarksTooltip - manageBookmarksButton.font = NSFont.systemFont(ofSize: 12) - manageBookmarksButton.imagePosition = .imageLeading - manageBookmarksButton.imageHugsTitle = true - - // Set up constraints - let titleWidth = (manageBookmarksButton.title as NSString).size(withAttributes: [.font: manageBookmarksButton.font as Any]).width - let buttonWidth = imageSize.width + titleWidth + padding * 3 - manageBookmarksButton.translatesAutoresizingMaskIntoConstraints = false - let widthConstraint = NSLayoutConstraint(item: manageBookmarksButton!, attribute: NSLayoutConstraint.Attribute.width, relatedBy: NSLayoutConstraint.Relation.equal, toItem: nil, attribute: NSLayoutConstraint.Attribute.notAnAttribute, multiplier: 1, constant: buttonWidth) - NSLayoutConstraint.activate([widthConstraint]) - } } // MARK: - Menu Item Selectors @@ -317,7 +494,7 @@ extension BookmarkListViewController: BookmarkMenuItemSelectors { } bookmark.isFavorite.toggle() - LocalBookmarkManager.shared.update(bookmark: bookmark) + bookmarkManager.update(bookmark: bookmark) } func editBookmark(_ sender: NSMenuItem) { @@ -338,7 +515,7 @@ extension BookmarkListViewController: BookmarkMenuItemSelectors { return } - LocalBookmarkManager.shared.remove(bookmark: bookmark) + bookmarkManager.remove(bookmark: bookmark) } func deleteEntities(_ sender: NSMenuItem) { @@ -347,7 +524,7 @@ extension BookmarkListViewController: BookmarkMenuItemSelectors { return } - LocalBookmarkManager.shared.remove(objectsWithUUIDs: uuids) + bookmarkManager.remove(objectsWithUUIDs: uuids) } } @@ -378,7 +555,7 @@ extension BookmarkListViewController: FolderMenuItemSelectors { return } - LocalBookmarkManager.shared.remove(folder: folder) + bookmarkManager.remove(folder: folder) } func openInNewTabs(_ sender: NSMenuItem) { @@ -415,7 +592,7 @@ final class BookmarkListPopover: NSPopover { var viewController: BookmarkListViewController { contentViewController as! BookmarkListViewController } private func setupContentController() { - let controller = BookmarkListViewController.create() + let controller = BookmarkListViewController() controller.delegate = self contentViewController = controller } @@ -433,3 +610,45 @@ extension BookmarkListPopover: BookmarkListViewControllerDelegate { } } + +#if DEBUG +private let previewEmptyState = false +@available(macOS 14.0, *) +#Preview(traits: .fixedLayout(width: BookmarkListViewController.preferredContentSize.width, height: BookmarkListViewController.preferredContentSize.height)) { { + + let vc = BookmarkListViewController(bookmarkManager: { + let bkman = LocalBookmarkManager(bookmarkStore: BookmarkStoreMock(bookmarks: previewEmptyState ? [] : [ + BookmarkFolder(id: "1", title: "Folder 1", children: [ + BookmarkFolder(id: "2", title: "Nested Folder", children: [ + Bookmark(id: "b1", url: URL.duckDuckGo.absoluteString, title: "DuckDuckGo", isFavorite: false, parentFolderUUID: "2") + ]) + ]), + BookmarkFolder(id: "3", title: "Another Folder", children: [ + BookmarkFolder(id: "4", title: "Nested Folder", children: [ + BookmarkFolder(id: "5", title: "Another Nested Folder", children: [ + Bookmark(id: "b2", url: URL.duckDuckGo.absoluteString, title: "DuckDuckGo", isFavorite: false, parentFolderUUID: "5") + ]) + ]) + ]), + Bookmark(id: "b3", url: URL.duckDuckGo.absoluteString, title: "Bookmark 1", isFavorite: false, parentFolderUUID: ""), + Bookmark(id: "b4", url: URL.duckDuckGo.absoluteString, title: "Bookmark 2", isFavorite: false, parentFolderUUID: ""), + Bookmark(id: "b5", url: URL.duckDuckGo.absoluteString, title: "DuckDuckGo", isFavorite: false, parentFolderUUID: "") + ])) + bkman.loadBookmarks() + customAssertionFailure = { _, _, _ in } + + return bkman + }()) + + var c: AnyCancellable! + c = vc.publisher(for: \.view.window).sink { window in + window?.titlebarAppearsTransparent = true + window?.titleVisibility = .hidden + window?.styleMask = [] + withExtendedLifetime(c) {} + } + + return vc + +}() } +#endif diff --git a/DuckDuckGo/Bookmarks/View/BookmarkManagementDetailViewController.swift b/DuckDuckGo/Bookmarks/View/BookmarkManagementDetailViewController.swift index 2d31e59306..23dfd28251 100644 --- a/DuckDuckGo/Bookmarks/View/BookmarkManagementDetailViewController.swift +++ b/DuckDuckGo/Bookmarks/View/BookmarkManagementDetailViewController.swift @@ -33,20 +33,25 @@ private struct EditedBookmarkMetadata { final class BookmarkManagementDetailViewController: NSViewController, NSMenuItemValidation { fileprivate enum Constants { - static let bookmarkCellIdentifier = NSUserInterfaceItemIdentifier(rawValue: "BookmarksCellIdentifier") static let animationSpeed: TimeInterval = 0.3 } - @IBOutlet var tableView: NSTableView! - @IBOutlet var colorView: ColorView! - @IBOutlet var contextMenu: NSMenu! - @IBOutlet var emptyState: NSView! - @IBOutlet var emptyStateTitle: NSTextField! - @IBOutlet var emptyStateMessage: NSTextField! + private lazy var newBookmarkButton = MouseOverButton(title: " " + UserText.newBookmark, target: self, action: #selector(presentAddBookmarkModal)) + private lazy var newFolderButton = MouseOverButton(title: " " + UserText.newFolder, target: self, action: #selector(presentAddFolderModal)) + + private lazy var separator = NSBox() + private lazy var scrollView = NSScrollView() + private lazy var tableView = NSTableView() + + private lazy var emptyState = NSView() + private lazy var emptyStateImageView = NSImageView(image: .bookmarksEmpty) + private lazy var emptyStateTitle = NSTextField() + private lazy var emptyStateMessage = NSTextField() + private lazy var importButton = NSButton(title: UserText.importBookmarksButtonTitle, target: self, action: #selector(onImportClicked)) weak var delegate: BookmarkManagementDetailViewControllerDelegate? - private var bookmarkManager: BookmarkManager = LocalBookmarkManager.shared + private let bookmarkManager: BookmarkManager private var selectionState: BookmarkManagementSidebarViewController.SelectionState = .empty { didSet { editingBookmarkIndex = nil @@ -66,9 +71,9 @@ final class BookmarkManagementDetailViewController: NSViewController, NSMenuItem NSAppearance.withAppAppearance { if editingBookmarkIndex != nil { - colorView.animator().layer?.backgroundColor = NSColor.backgroundSecondaryColor.cgColor + view.animator().layer?.backgroundColor = NSColor.backgroundSecondaryColor.cgColor } else { - colorView.animator().layer?.backgroundColor = NSColor.interfaceBackgroundColor.cgColor + view.animator().layer?.backgroundColor = NSColor.interfaceBackgroundColor.cgColor } } } @@ -79,6 +84,175 @@ final class BookmarkManagementDetailViewController: NSViewController, NSMenuItem self.selectionState = selectionState } + init(bookmarkManager: BookmarkManager = LocalBookmarkManager.shared) { + self.bookmarkManager = bookmarkManager + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("\(type(of: self)): Bad initializer") + } + + // swiftlint:disable:next function_body_length + override func loadView() { + view = ColorView(frame: .zero, backgroundColor: .interfaceBackgroundColor) + view.translatesAutoresizingMaskIntoConstraints = false + + view.addSubview(separator) + view.addSubview(scrollView) + view.addSubview(emptyState) + view.addSubview(newBookmarkButton) + view.addSubview(newFolderButton) + + newBookmarkButton.bezelStyle = .shadowlessSquare + newBookmarkButton.cornerRadius = 4 + newBookmarkButton.normalTintColor = .button + newBookmarkButton.mouseDownColor = .buttonMouseDownColor + newBookmarkButton.mouseOverColor = .buttonMouseOverColor + newBookmarkButton.imageHugsTitle = true + newBookmarkButton.setContentHuggingPriority(.defaultHigh, for: .vertical) + newBookmarkButton.translatesAutoresizingMaskIntoConstraints = false + newBookmarkButton.alignment = .center + newBookmarkButton.font = .systemFont(ofSize: 13) + newBookmarkButton.image = .addBookmark + newBookmarkButton.imagePosition = .imageLeading + + newFolderButton.bezelStyle = .shadowlessSquare + newFolderButton.cornerRadius = 4 + newFolderButton.normalTintColor = .button + newFolderButton.mouseDownColor = .buttonMouseDownColor + newFolderButton.mouseOverColor = .buttonMouseOverColor + newFolderButton.imageHugsTitle = true + newFolderButton.setContentHuggingPriority(.defaultHigh, for: .vertical) + newFolderButton.translatesAutoresizingMaskIntoConstraints = false + newFolderButton.alignment = .center + newFolderButton.font = .systemFont(ofSize: 13) + newFolderButton.image = .addFolder + newFolderButton.imagePosition = .imageLeading + + emptyState.addSubview(emptyStateImageView) + emptyState.addSubview(emptyStateTitle) + emptyState.addSubview(emptyStateMessage) + emptyState.addSubview(importButton) + + emptyState.isHidden = true + emptyState.translatesAutoresizingMaskIntoConstraints = false + + emptyStateTitle.isEditable = false + emptyStateTitle.setContentHuggingPriority(.defaultHigh, for: .vertical) + emptyStateTitle.setContentHuggingPriority(.init(rawValue: 251), for: .horizontal) + emptyStateTitle.translatesAutoresizingMaskIntoConstraints = false + emptyStateTitle.alignment = .center + emptyStateTitle.drawsBackground = false + emptyStateTitle.isBordered = false + emptyStateTitle.font = .systemFont(ofSize: 15, weight: .semibold) + emptyStateTitle.textColor = .labelColor + emptyStateTitle.attributedStringValue = NSAttributedString.make(UserText.bookmarksEmptyStateTitle, + lineHeight: 1.14, + kern: -0.23) + + emptyStateMessage.isEditable = false + emptyStateMessage.setContentHuggingPriority(.defaultHigh, for: .vertical) + emptyStateMessage.setContentHuggingPriority(.init(rawValue: 251), for: .horizontal) + emptyStateMessage.translatesAutoresizingMaskIntoConstraints = false + emptyStateMessage.alignment = .center + emptyStateMessage.drawsBackground = false + emptyStateMessage.isBordered = false + emptyStateMessage.font = .systemFont(ofSize: 13) + emptyStateMessage.textColor = .labelColor + emptyStateMessage.attributedStringValue = NSAttributedString.make(UserText.bookmarksEmptyStateMessage, + lineHeight: 1.05, + kern: -0.08) + + emptyStateImageView.setContentHuggingPriority(.init(rawValue: 251), for: .horizontal) + emptyStateImageView.setContentHuggingPriority(.init(rawValue: 251), for: .vertical) + emptyStateImageView.translatesAutoresizingMaskIntoConstraints = false + emptyStateImageView.imageScaling = .scaleProportionallyDown + + scrollView.autohidesScrollers = true + scrollView.borderType = .noBorder + scrollView.translatesAutoresizingMaskIntoConstraints = false + scrollView.usesPredominantAxisScrolling = false + scrollView.automaticallyAdjustsContentInsets = false + scrollView.contentInsets = NSEdgeInsets(top: 22, left: 0, bottom: 22, right: 0) + scrollView.menu = NSMenu() + scrollView.menu!.delegate = self + + let clipView = NSClipView() + clipView.documentView = tableView + + clipView.autoresizingMask = [.width, .height] + clipView.backgroundColor = .clear + clipView.drawsBackground = false + clipView.frame = CGRect(x: 0, y: 0, width: 640, height: 601) + + tableView.addTableColumn(NSTableColumn()) + + tableView.headerView = nil + tableView.backgroundColor = .clear + tableView.setContentHuggingPriority(.defaultHigh, for: .vertical) + tableView.style = .plain + tableView.selectionHighlightStyle = .none + tableView.usesAutomaticRowHeights = true + tableView.action = #selector(handleClick) + tableView.doubleAction = #selector(handleDoubleClick) + tableView.delegate = self + tableView.dataSource = self + + scrollView.contentView = clipView + + separator.boxType = .separator + separator.setContentHuggingPriority(.defaultHigh, for: .vertical) + separator.translatesAutoresizingMaskIntoConstraints = false + setupLayout() + } + + private func setupLayout() { + newBookmarkButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 48).isActive = true + view.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor, constant: 48).isActive = true + separator.topAnchor.constraint(equalTo: newBookmarkButton.bottomAnchor, constant: 24).isActive = true + emptyState.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: 20).isActive = true + scrollView.topAnchor.constraint(equalTo: separator.bottomAnchor).isActive = true + + view.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true + view.trailingAnchor.constraint(greaterThanOrEqualTo: newFolderButton.trailingAnchor, constant: 20).isActive = true + view.trailingAnchor.constraint(equalTo: separator.trailingAnchor, constant: 58).isActive = true + newFolderButton.leadingAnchor.constraint(equalTo: newBookmarkButton.trailingAnchor, constant: 16).isActive = true + emptyState.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true + newFolderButton.centerYAnchor.constraint(equalTo: newBookmarkButton.centerYAnchor).isActive = true + separator.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 58).isActive = true + newBookmarkButton.topAnchor.constraint(equalTo: view.topAnchor, constant: 32).isActive = true + emptyState.topAnchor.constraint(greaterThanOrEqualTo: separator.bottomAnchor, constant: 8).isActive = true + scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 48).isActive = true + emptyState.centerXAnchor.constraint(equalTo: separator.centerXAnchor).isActive = true + + newBookmarkButton.heightAnchor.constraint(equalToConstant: 24).isActive = true + newBookmarkButton.widthAnchor.constraint(equalToConstant: 130).isActive = true + + newFolderButton.widthAnchor.constraint(equalToConstant: 110).isActive = true + newFolderButton.heightAnchor.constraint(equalToConstant: 24).isActive = true + + emptyStateMessage.centerXAnchor.constraint(equalTo: emptyState.centerXAnchor).isActive = true + + importButton.translatesAutoresizingMaskIntoConstraints = false + importButton.topAnchor.constraint(equalTo: emptyStateMessage.bottomAnchor, constant: 8).isActive = true + emptyState.heightAnchor.constraint(equalToConstant: 218).isActive = true + emptyStateMessage.topAnchor.constraint(equalTo: emptyStateTitle.bottomAnchor, constant: 8).isActive = true + importButton.centerXAnchor.constraint(equalTo: emptyState.centerXAnchor).isActive = true + emptyStateImageView.centerXAnchor.constraint(equalTo: emptyState.centerXAnchor).isActive = true + emptyState.widthAnchor.constraint(equalToConstant: 224).isActive = true + emptyStateImageView.topAnchor.constraint(equalTo: emptyState.topAnchor).isActive = true + emptyStateTitle.centerXAnchor.constraint(equalTo: emptyState.centerXAnchor).isActive = true + emptyStateTitle.topAnchor.constraint(equalTo: emptyStateImageView.bottomAnchor, constant: 8).isActive = true + + emptyStateMessage.widthAnchor.constraint(equalToConstant: 192).isActive = true + + emptyStateTitle.widthAnchor.constraint(equalToConstant: 192).isActive = true + + emptyStateImageView.widthAnchor.constraint(equalToConstant: 128).isActive = true + emptyStateImageView.heightAnchor.constraint(equalToConstant: 96).isActive = true + } + override func viewDidLoad() { super.viewDidLoad() @@ -87,11 +261,7 @@ final class BookmarkManagementDetailViewController: NSViewController, NSMenuItem FolderPasteboardWriter.folderUTIInternalType]) reloadData() - self.tableView.selectionHighlightStyle = .none - - emptyStateTitle.attributedStringValue = NSAttributedString.make(emptyStateTitle.stringValue, lineHeight: 1.14, kern: -0.23) - emptyStateMessage.attributedStringValue = NSAttributedString.make(emptyStateMessage.stringValue, lineHeight: 1.05, kern: -0.08) - } + } override func viewDidDisappear() { super.viewDidDisappear() @@ -122,11 +292,11 @@ final class BookmarkManagementDetailViewController: NSViewController, NSMenuItem tableView.scroll(scrollPosition) } - @IBAction func onImportClicked(_ sender: NSButton) { + @objc func onImportClicked(_ sender: NSButton) { DataImportView().show() } - @IBAction func handleDoubleClick(_ sender: NSTableView) { + @objc func handleDoubleClick(_ sender: NSTableView) { if sender.selectedRowIndexes.count > 1 { let entities = sender.selectedRowIndexes.map { fetchEntity(at: $0) } let bookmarks = entities.compactMap { $0 as? Bookmark } @@ -155,7 +325,7 @@ final class BookmarkManagementDetailViewController: NSViewController, NSMenuItem } } - @IBAction func handleClick(_ sender: NSTableView) { + @objc func handleClick(_ sender: NSTableView) { let index = sender.clickedRow if index != editingBookmarkIndex?.index { @@ -163,17 +333,17 @@ final class BookmarkManagementDetailViewController: NSViewController, NSMenuItem } } - @IBAction func presentAddBookmarkModal(_ sender: Any) { + @objc func presentAddBookmarkModal(_ sender: Any) { AddBookmarkModalView(model: AddBookmarkModalViewModel(parent: selectionState.folder)) .show(in: view.window) } - @IBAction func presentAddFolderModal(_ sender: Any) { + @objc func presentAddFolderModal(_ sender: Any) { AddBookmarkFolderModalView(model: AddBookmarkFolderModalViewModel(parent: selectionState.folder)) .show(in: view.window) } - @IBAction func delete(_ sender: AnyObject) { + @objc func delete(_ sender: AnyObject) { deleteSelectedItems() } @@ -235,11 +405,11 @@ final class BookmarkManagementDetailViewController: NSViewController, NSMenuItem private func totalRows() -> Int { switch selectionState { case .empty: - return LocalBookmarkManager.shared.list?.topLevelEntities.count ?? 0 + return bookmarkManager.list?.topLevelEntities.count ?? 0 case .folder(let folder): return folder.children.count case .favorites: - return LocalBookmarkManager.shared.list?.favoriteBookmarks.count ?? 0 + return bookmarkManager.list?.favoriteBookmarks.count ?? 0 } } @@ -287,8 +457,8 @@ extension BookmarkManagementDetailViewController: NSTableViewDelegate, NSTableVi func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { guard let entity = fetchEntity(at: row) else { return nil } - let cell = tableView.makeView(withIdentifier: Constants.bookmarkCellIdentifier, owner: nil) as? BookmarkTableCellView - ?? BookmarkTableCellView(identifier: Constants.bookmarkCellIdentifier) + let cell = tableView.makeView(withIdentifier: .init(BookmarkTableCellView.className()), owner: nil) as? BookmarkTableCellView + ?? BookmarkTableCellView(identifier: .init(BookmarkTableCellView.className())) cell.delegate = self @@ -374,20 +544,20 @@ extension BookmarkManagementDetailViewController: NSTableViewDelegate, NSTableVi } if let parent = fetchEntity(at: row) as? BookmarkFolder, dropOperation == .on { - LocalBookmarkManager.shared.add(objectsWithUUIDs: draggedItemIdentifiers, to: parent) { _ in } + bookmarkManager.add(objectsWithUUIDs: draggedItemIdentifiers, to: parent) { _ in } return true } else if let currentFolderUUID = selectionState.selectedFolderUUID { - LocalBookmarkManager.shared.move(objectUUIDs: draggedItemIdentifiers, - toIndex: row, - withinParentFolder: .parent(uuid: currentFolderUUID)) { _ in } + bookmarkManager.move(objectUUIDs: draggedItemIdentifiers, + toIndex: row, + withinParentFolder: .parent(uuid: currentFolderUUID)) { _ in } return true } else { if selectionState == .favorites { - LocalBookmarkManager.shared.moveFavorites(with: draggedItemIdentifiers, toIndex: row) { _ in } + bookmarkManager.moveFavorites(with: draggedItemIdentifiers, toIndex: row) { _ in } } else { - LocalBookmarkManager.shared.move(objectUUIDs: draggedItemIdentifiers, - toIndex: row, - withinParentFolder: .root) { _ in } + bookmarkManager.move(objectUUIDs: draggedItemIdentifiers, + toIndex: row, + withinParentFolder: .root) { _ in } } return true } @@ -396,22 +566,22 @@ extension BookmarkManagementDetailViewController: NSTableViewDelegate, NSTableVi private func fetchEntity(at row: Int) -> BaseBookmarkEntity? { switch selectionState { case .empty: - return LocalBookmarkManager.shared.list?.topLevelEntities[safe: row] + return bookmarkManager.list?.topLevelEntities[safe: row] case .folder(let folder): return folder.children[safe: row] case .favorites: - return LocalBookmarkManager.shared.list?.favoriteBookmarks[safe: row] + return bookmarkManager.list?.favoriteBookmarks[safe: row] } } private func index(for entity: Bookmark) -> Int? { switch selectionState { case .empty: - return LocalBookmarkManager.shared.list?.topLevelEntities.firstIndex(of: entity) + return bookmarkManager.list?.topLevelEntities.firstIndex(of: entity) case .folder(let folder): return folder.children.firstIndex(of: entity) case .favorites: - return LocalBookmarkManager.shared.list?.favoriteBookmarks.firstIndex(of: entity) + return bookmarkManager.list?.favoriteBookmarks.firstIndex(of: entity) } } @@ -497,7 +667,7 @@ extension BookmarkManagementDetailViewController: BookmarkTableCellViewDelegate } bookmark.isFavorite.toggle() - LocalBookmarkManager.shared.update(bookmark: bookmark) + bookmarkManager.update(bookmark: bookmark) } func bookmarkTableCellView(_ cell: BookmarkTableCellView, updatedBookmarkWithUUID uuid: String, newTitle: String, newUrl: String) { @@ -585,7 +755,7 @@ extension BookmarkManagementDetailViewController: FolderMenuItemSelectors { return } - LocalBookmarkManager.shared.remove(folder: folder) + bookmarkManager.remove(folder: folder) } func openInNewTabs(_ sender: NSMenuItem) { @@ -626,7 +796,7 @@ extension BookmarkManagementDetailViewController: BookmarkMenuItemSelectors { func toggleBookmarkAsFavorite(_ sender: NSMenuItem) { if let bookmark = sender.representedObject as? Bookmark { bookmark.isFavorite.toggle() - LocalBookmarkManager.shared.update(bookmark: bookmark) + bookmarkManager.update(bookmark: bookmark) } else if let bookmarks = sender.representedObject as? [Bookmark] { let bookmarkIdentifiers = bookmarks.map(\.id) bookmarkManager.update(objectsWithUUIDs: bookmarkIdentifiers, update: { entity in @@ -661,7 +831,7 @@ extension BookmarkManagementDetailViewController: BookmarkMenuItemSelectors { return } - LocalBookmarkManager.shared.remove(bookmark: bookmark) + bookmarkManager.remove(bookmark: bookmark) } func deleteEntities(_ sender: NSMenuItem) { @@ -676,7 +846,38 @@ extension BookmarkManagementDetailViewController: BookmarkMenuItemSelectors { return } - LocalBookmarkManager.shared.remove(objectsWithUUIDs: uuids) + bookmarkManager.remove(objectsWithUUIDs: uuids) } } + +#if DEBUG +@available(macOS 14.0, *) +#Preview(traits: .fixedLayout(width: 700, height: 660)) { + + return BookmarkManagementDetailViewController(bookmarkManager: { + let bkman = LocalBookmarkManager(bookmarkStore: BookmarkStoreMock(bookmarks: [ + BookmarkFolder(id: "1", title: "Folder 1", children: [ + BookmarkFolder(id: "2", title: "Nested Folder", children: [ + Bookmark(id: "b1", url: URL.duckDuckGo.absoluteString, title: "DuckDuckGo", isFavorite: false, parentFolderUUID: "2") + ]) + ]), + BookmarkFolder(id: "3", title: "Another Folder", children: [ + BookmarkFolder(id: "4", title: "Nested Folder", children: [ + BookmarkFolder(id: "5", title: "Another Nested Folder", children: [ + Bookmark(id: "b2", url: URL.duckDuckGo.absoluteString, title: "DuckDuckGo", isFavorite: false, parentFolderUUID: "5") + ]) + ]) + ]), + Bookmark(id: "b3", url: URL.duckDuckGo.absoluteString, title: "Bookmark 1", isFavorite: false, parentFolderUUID: ""), + Bookmark(id: "b4", url: URL.duckDuckGo.absoluteString, title: "Bookmark 2", isFavorite: false, parentFolderUUID: ""), + Bookmark(id: "b5", url: URL.duckDuckGo.absoluteString, title: "DuckDuckGo", isFavorite: false, parentFolderUUID: "") + ])) + bkman.loadBookmarks() + customAssertionFailure = { _, _, _ in } + + return bkman + }()) + +} +#endif diff --git a/DuckDuckGo/Bookmarks/View/BookmarkManagementSidebarViewController.swift b/DuckDuckGo/Bookmarks/View/BookmarkManagementSidebarViewController.swift index b0efe687c9..9a49cb04f4 100644 --- a/DuckDuckGo/Bookmarks/View/BookmarkManagementSidebarViewController.swift +++ b/DuckDuckGo/Bookmarks/View/BookmarkManagementSidebarViewController.swift @@ -16,13 +16,14 @@ // limitations under the License. // -import Foundation +import AppKit import Combine +import PreferencesViews protocol BookmarkManagementSidebarViewControllerDelegate: AnyObject { - func bookmarkManagementSidebarViewController(_ sidebarViewController: BookmarkManagementSidebarViewController, - enteredState state: BookmarkManagementSidebarViewController.SelectionState) + func sidebarSelectionStateDidChange(_ state: BookmarkManagementSidebarViewController.SelectionState) + func sidebarSelectedTabContentDidChange(_ content: Tab.TabContent) } @@ -42,24 +43,20 @@ final class BookmarkManagementSidebarViewController: NSViewController { } } - @IBOutlet var tabSwitcherButton: NSPopUpButton! - @IBOutlet var outlineView: NSOutlineView! - @IBOutlet var contextMenu: NSMenu! + private let bookmarkManager: BookmarkManager + private let treeControllerDataSource: BookmarkSidebarTreeController - weak var delegate: BookmarkManagementSidebarViewControllerDelegate? - - private let treeControllerDataSource = BookmarkSidebarTreeController() - - private lazy var treeController: BookmarkTreeController = { - return BookmarkTreeController(dataSource: treeControllerDataSource) - }() + private lazy var tabSwitcherButton = NSPopUpButton() + private lazy var scrollView = NSScrollView(frame: NSRect(x: 0, y: 0, width: 232, height: 410)) + private lazy var outlineView = BookmarksOutlineView(frame: scrollView.frame) - private lazy var dataSource: BookmarkOutlineViewDataSource = { - BookmarkOutlineViewDataSource(contentMode: .foldersOnly, treeController: treeController) - }() + private lazy var treeController = BookmarkTreeController(dataSource: treeControllerDataSource) + private lazy var dataSource = BookmarkOutlineViewDataSource(contentMode: .foldersOnly, bookmarkManager: bookmarkManager, treeController: treeController) private var cancellables = Set() + weak var delegate: BookmarkManagementSidebarViewControllerDelegate? + private var selectedNodes: [BookmarkNode] { if let nodes = outlineView.selectedItems as? [BookmarkNode] { return nodes @@ -67,57 +64,114 @@ final class BookmarkManagementSidebarViewController: NSViewController { return [BookmarkNode]() } - @IBAction func onDoubleClick(_ sender: NSOutlineView) { - guard let item = sender.item(atRow: sender.clickedRow) else { return } - if sender.isItemExpanded(item) { - sender.animator().collapseItem(item) - } else { - sender.animator().expandItem(item) - } + init(bookmarkManager: BookmarkManager = LocalBookmarkManager.shared) { + self.bookmarkManager = bookmarkManager + treeControllerDataSource = .init(bookmarkManager: bookmarkManager) + super.init(nibName: nil, bundle: nil) } - override func viewDidLoad() { - super.viewDidLoad() + required init?(coder: NSCoder) { + fatalError("\(type(of: self)): Bad initializer") + } + + override func loadView() { + view = ColorView(frame: .zero, backgroundColor: .interfaceBackground) + + view.addSubview(tabSwitcherButton) + view.addSubview(scrollView) + + tabSwitcherButton.translatesAutoresizingMaskIntoConstraints = false + tabSwitcherButton.font = PreferencesViews.Const.Fonts.popUpButton + tabSwitcherButton.setButtonType(.momentaryLight) + tabSwitcherButton.isBordered = false + tabSwitcherButton.target = self + tabSwitcherButton.action = #selector(selectedTabContentDidChange) + tabSwitcherButton.menu = NSMenu { + for content in Tab.TabContent.displayableTabTypes { + NSMenuItem(title: content.title!, representedObject: content) + } + } - outlineView.register(BookmarkOutlineViewCell.nib, forIdentifier: BookmarkOutlineViewCell.identifier) + scrollView.borderType = .noBorder + scrollView.drawsBackground = false + scrollView.translatesAutoresizingMaskIntoConstraints = false + scrollView.hasHorizontalScroller = false + scrollView.usesPredominantAxisScrolling = false + + let column = NSTableColumn() + column.width = scrollView.frame.width - 32 + outlineView.addTableColumn(column) + outlineView.translatesAutoresizingMaskIntoConstraints = true + outlineView.autoresizesOutlineColumn = false + outlineView.autoresizingMask = [.width, .height] + outlineView.headerView = nil + outlineView.allowsEmptySelection = false + outlineView.allowsExpansionToolTips = true + outlineView.allowsMultipleSelection = false + outlineView.backgroundColor = .clear + outlineView.indentationPerLevel = 13 + outlineView.rowHeight = 28 + outlineView.target = self + outlineView.doubleAction = #selector(onDoubleClick) + outlineView.menu = NSMenu() + outlineView.menu!.delegate = self outlineView.dataSource = dataSource outlineView.delegate = dataSource + + let clipView = NSClipView(frame: scrollView.frame) + clipView.translatesAutoresizingMaskIntoConstraints = true + clipView.autoresizingMask = [.width, .height] + clipView.documentView = outlineView + clipView.drawsBackground = false + scrollView.contentView = clipView + + setupLayout() + } + + private func setupLayout() { + tabSwitcherButton.topAnchor.constraint(equalTo: view.topAnchor, constant: 18).isActive = true + tabSwitcherButton.heightAnchor.constraint(equalToConstant: 60).isActive = true + tabSwitcherButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 23).isActive = true + view.trailingAnchor.constraint(equalTo: tabSwitcherButton.trailingAnchor, constant: 23).isActive = true + + scrollView.topAnchor.constraint(equalTo: tabSwitcherButton.bottomAnchor, constant: 12).isActive = true + view.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor, constant: 12).isActive = true + scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 12).isActive = true + view.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true + } + + override func viewDidLoad() { + super.viewDidLoad() outlineView.setDraggingSourceOperationMask([.move], forLocal: true) outlineView.registerForDraggedTypes([BookmarkPasteboardWriter.bookmarkUTIInternalType, FolderPasteboardWriter.folderUTIInternalType]) dataSource.$selectedFolders.sink { [weak self] selectedFolders in - guard let self = self else { return } - - switch selectedFolders.count { - case 0: + guard let self else { return } + guard let selectedFolder = selectedFolders.first else { if self.outlineView.selectedPseudoFolders == [PseudoFolder.favorites] { - self.delegate?.bookmarkManagementSidebarViewController(self, enteredState: .favorites) + self.delegate?.sidebarSelectionStateDidChange(.favorites) } else { - self.delegate?.bookmarkManagementSidebarViewController(self, enteredState: .empty) + self.delegate?.sidebarSelectionStateDidChange(.empty) } + return + } - case 1: - self.delegate?.bookmarkManagementSidebarViewController(self, enteredState: .folder(selectedFolders[0])) + self.delegate?.sidebarSelectionStateDidChange(.folder(selectedFolder)) - default: - assertionFailure("\(#file): Multi-select is not yet supported") - self.delegate?.bookmarkManagementSidebarViewController(self, enteredState: .empty) - } }.store(in: &cancellables) - LocalBookmarkManager.shared.listPublisher.receive(on: RunLoop.main).sink { [weak self] _ in + bookmarkManager.listPublisher.receive(on: RunLoop.main).sink { [weak self] _ in self?.reloadData() }.store(in: &cancellables) } override func viewWillAppear() { super.viewWillAppear() + tabSwitcherButton.select(tabSwitcherButton.itemArray.first(where: { $0.representedObject as? Tab.TabContent == .bookmarks })) reloadData() - tabSwitcherButton.select(tabType: .bookmarks) - - LocalBookmarkManager.shared.requestSync() + bookmarkManager.requestSync() } func select(folder: BookmarkFolder) { @@ -135,6 +189,25 @@ final class BookmarkManagementSidebarViewController: NSViewController { expandAndRestore(selectedNodes: selectedNodes) } + // MARK: Actions + + @objc func selectedTabContentDidChange(_ sender: NSPopUpButton) { + guard let content = sender.selectedItem?.representedObject as? Tab.TabContent else { + assertionFailure("Expected TabContent representedObject") + return + } + delegate?.sidebarSelectedTabContentDidChange(content) + } + + @objc func onDoubleClick(_ sender: NSOutlineView) { + guard let item = sender.item(atRow: sender.clickedRow) else { return } + if sender.isItemExpanded(item) { + sender.animator().collapseItem(item) + } else { + sender.animator().expandItem(item) + } + } + // MARK: NSOutlineView Configuration private func expandAndRestore(selectedNodes: [BookmarkNode]) { @@ -238,7 +311,7 @@ extension BookmarkManagementSidebarViewController: FolderMenuItemSelectors { return } - LocalBookmarkManager.shared.remove(folder: folder) + bookmarkManager.remove(folder: folder) } func openInNewTabs(_ sender: NSMenuItem) { @@ -253,3 +326,47 @@ extension BookmarkManagementSidebarViewController: FolderMenuItemSelectors { } } + +#if DEBUG +private let previewSize = NSSize(width: 400, height: 660) +@available(macOS 14.0, *) +#Preview(traits: .fixedLayout(width: previewSize.width, height: previewSize.height)) { { + + let vc = BookmarkManagementSidebarViewController(bookmarkManager: { + let bkman = LocalBookmarkManager(bookmarkStore: BookmarkStoreMock(bookmarks: [ + BookmarkFolder(id: "1", title: "Folder with a reasonably long name that would be clipped", children: [ + BookmarkFolder(id: "2", title: "Nested Folder", children: [ + ]) + ]), + BookmarkFolder(id: "3", title: "Another Folder", children: [ + BookmarkFolder(id: "4", title: "Nested Folder", children: [ + BookmarkFolder(id: "5", title: "Another Nested Folder", children: [ + BookmarkFolder(id: "a", title: "Another Nested Folder", children: [ + BookmarkFolder(id: "b", title: "Another Nested Folder", children: [ + BookmarkFolder(id: "c", title: "Another Nested Folder", children: [ + BookmarkFolder(id: "d", title: "Another Nested Folder", children: [ + Bookmark(id: "z1", url: "a:b", title: "a", isFavorite: false), + Bookmark(id: "z2", url: "a:b", title: "a", isFavorite: false), + Bookmark(id: "z3", url: "a:b", title: "a", isFavorite: false), + ]) + ]) + ]) + ]) + ]) + ]) + ]), + BookmarkFolder(id: "6", title: "Third Folder", children: []), + BookmarkFolder(id: "7", title: "Forth Folder", children: []), + BookmarkFolder(id: "8", title: "Fifth Folder", children: []), + Bookmark(id: "z", url: "a:b", title: "a", isFavorite: false) + ])) + bkman.loadBookmarks() + customAssertionFailure = { _, _, _ in } + + return bkman + }()) + vc.preferredContentSize = previewSize + return vc + +}()} +#endif diff --git a/DuckDuckGo/Bookmarks/View/BookmarkManagementSplitViewController.swift b/DuckDuckGo/Bookmarks/View/BookmarkManagementSplitViewController.swift index 8a260eb3cd..d861371b81 100644 --- a/DuckDuckGo/Bookmarks/View/BookmarkManagementSplitViewController.swift +++ b/DuckDuckGo/Bookmarks/View/BookmarkManagementSplitViewController.swift @@ -17,56 +17,62 @@ // import AppKit -import Combine final class BookmarkManagementSplitViewController: NSSplitViewController { - private enum Constants { - static let storyboardName = "Bookmarks" - static let identifier = "BookmarkManagementSplitViewController" - } + private let bookmarkManager: BookmarkManager + weak var delegate: BrowserTabSelectionDelegate? - static func create() -> BookmarkManagementSplitViewController { - let storyboard = NSStoryboard(name: Constants.storyboardName, bundle: nil) - return storyboard.instantiateController(identifier: Constants.identifier) - } + lazy var sidebarViewController: BookmarkManagementSidebarViewController = BookmarkManagementSidebarViewController(bookmarkManager: bookmarkManager) + lazy var detailViewController: BookmarkManagementDetailViewController = BookmarkManagementDetailViewController(bookmarkManager: bookmarkManager) - // swiftlint:disable force_cast - var sidebarViewController: BookmarkManagementSidebarViewController { - return splitViewItems[0].viewController as! BookmarkManagementSidebarViewController + init(bookmarkManager: BookmarkManager = LocalBookmarkManager.shared) { + self.bookmarkManager = bookmarkManager + super.init(nibName: nil, bundle: nil) } - var detailViewController: BookmarkManagementDetailViewController { - return splitViewItems[1].viewController as! BookmarkManagementDetailViewController + required init?(coder: NSCoder) { + fatalError("\(type(of: self)): Bad initializer") } - // swiftlint:enable force_cast - weak var delegate: BrowserTabSelectionDelegate? + override func loadView() { + title = UserText.bookmarks + + splitView.dividerStyle = .thin + splitView.isVertical = true + splitView.setValue(NSColor.divider, forKey: #keyPath(NSSplitView.dividerColor)) + + let sidebarViewItem = NSSplitViewItem(contentListWithViewController: sidebarViewController) + sidebarViewItem.minimumThickness = 256 + sidebarViewItem.maximumThickness = 256 + + addSplitViewItem(sidebarViewItem) - private var selectedTabCancellable: AnyCancellable? + let detailViewItem = NSSplitViewItem(viewController: detailViewController) + addSplitViewItem(detailViewItem) + + view = splitView + } override func viewDidLoad() { super.viewDidLoad() - splitView.setValue(NSColor(named: "DividerColor"), forKey: "dividerColor") sidebarViewController.delegate = self detailViewController.delegate = self - sidebarViewController.tabSwitcherButton.displayBrowserTabButtons(withSelectedTab: .bookmarks) - - selectedTabCancellable = sidebarViewController.tabSwitcherButton.selectionPublisher.dropFirst().sink { [weak self] index in - self?.delegate?.selectedTab(at: index) - } } } extension BookmarkManagementSplitViewController: BookmarkManagementSidebarViewControllerDelegate { - func bookmarkManagementSidebarViewController(_ sidebarViewController: BookmarkManagementSidebarViewController, - enteredState state: BookmarkManagementSidebarViewController.SelectionState) { + func sidebarSelectionStateDidChange(_ state: BookmarkManagementSidebarViewController.SelectionState) { detailViewController.update(selectionState: state) } + func sidebarSelectedTabContentDidChange(_ content: Tab.TabContent) { + delegate?.selectedTabContent(content) + } + } extension BookmarkManagementSplitViewController: BookmarkManagementDetailViewControllerDelegate { @@ -76,3 +82,37 @@ extension BookmarkManagementSplitViewController: BookmarkManagementDetailViewCon } } + +#if DEBUG +private let previewSize = NSSize(width: 700, height: 660) +@available(macOS 14.0, *) +#Preview(traits: .fixedLayout(width: previewSize.width, height: previewSize.height)) { { + + let vc = BookmarkManagementSplitViewController(bookmarkManager: { + let bkman = LocalBookmarkManager(bookmarkStore: BookmarkStoreMock(bookmarks: [ + BookmarkFolder(id: "1", title: "Folder 1", children: [ + BookmarkFolder(id: "2", title: "Nested Folder", children: [ + Bookmark(id: "b1", url: URL.duckDuckGo.absoluteString, title: "DuckDuckGo", isFavorite: false, parentFolderUUID: "2") + ]) + ]), + BookmarkFolder(id: "3", title: "Another Folder", children: [ + BookmarkFolder(id: "4", title: "Nested Folder", children: [ + BookmarkFolder(id: "5", title: "Another Nested Folder", children: [ + Bookmark(id: "b2", url: URL.duckDuckGo.absoluteString, title: "DuckDuckGo", isFavorite: false, parentFolderUUID: "5") + ]) + ]) + ]), + Bookmark(id: "b3", url: URL.duckDuckGo.absoluteString, title: "Bookmark 1", isFavorite: false, parentFolderUUID: ""), + Bookmark(id: "b4", url: URL.duckDuckGo.absoluteString, title: "Bookmark 2", isFavorite: false, parentFolderUUID: ""), + Bookmark(id: "b5", url: URL.duckDuckGo.absoluteString, title: "DuckDuckGo", isFavorite: false, parentFolderUUID: "") + ])) + bkman.loadBookmarks() + customAssertionFailure = { _, _, _ in } + + return bkman + }()) + vc.preferredContentSize = previewSize + return vc + +}() } +#endif diff --git a/DuckDuckGo/Bookmarks/View/BookmarkOutlineCellView.swift b/DuckDuckGo/Bookmarks/View/BookmarkOutlineCellView.swift new file mode 100644 index 0000000000..08e6c56953 --- /dev/null +++ b/DuckDuckGo/Bookmarks/View/BookmarkOutlineCellView.swift @@ -0,0 +1,165 @@ +// +// BookmarkOutlineCellView.swift +// +// Copyright © 2021 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import AppKit +import Foundation + +final class BookmarkOutlineCellView: NSTableCellView { + + private lazy var faviconImageView = NSImageView() + private lazy var titleLabel = NSTextField(string: "Bookmark/Folder") + private lazy var countLabel = NSTextField(string: "42") + + init(identifier: NSUserInterfaceItemIdentifier) { + super.init(frame: .zero) + setupUI() + } + + required init?(coder: NSCoder) { + fatalError("\(type(of: self)): Bad initializer") + } + + private func setupUI() { + addSubview(faviconImageView) + addSubview(titleLabel) + addSubview(countLabel) + + faviconImageView.translatesAutoresizingMaskIntoConstraints = false + faviconImageView.image = .bookmarkDefaultFavicon + faviconImageView.imageScaling = .scaleProportionallyDown + faviconImageView.wantsLayer = true + faviconImageView.layer?.cornerRadius = 2.0 + + titleLabel.translatesAutoresizingMaskIntoConstraints = false + titleLabel.isEditable = false + titleLabel.isBordered = false + titleLabel.isSelectable = false + titleLabel.drawsBackground = false + titleLabel.font = .systemFont(ofSize: 13) + titleLabel.textColor = .controlTextColor + titleLabel.lineBreakMode = .byTruncatingTail + + countLabel.translatesAutoresizingMaskIntoConstraints = false + countLabel.isEditable = false + countLabel.isBordered = false + countLabel.isSelectable = false + countLabel.drawsBackground = false + countLabel.font = .preferredFont(forTextStyle: .body) + countLabel.alignment = .right + countLabel.textColor = .blackWhite60 + countLabel.lineBreakMode = .byClipping + + setupLayout() + } + + private func setupLayout() { + faviconImageView.heightAnchor.constraint(equalToConstant: 16).isActive = true + faviconImageView.widthAnchor.constraint(equalToConstant: 16).isActive = true + faviconImageView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 5).isActive = true + faviconImageView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true + faviconImageView.setContentHuggingPriority(NSLayoutConstraint.Priority(rawValue: 251), for: .horizontal) + faviconImageView.setContentHuggingPriority(NSLayoutConstraint.Priority(rawValue: 251), for: .vertical) + + titleLabel.leadingAnchor.constraint(equalTo: faviconImageView.trailingAnchor, constant: 10).isActive = true + bottomAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 6).isActive = true + titleLabel.topAnchor.constraint(equalTo: topAnchor, constant: 6).isActive = true + titleLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) + titleLabel.setContentHuggingPriority(.defaultHigh, for: .vertical) + titleLabel.setContentHuggingPriority(.init(rawValue: 200), for: .horizontal) + + countLabel.centerYAnchor.constraint(equalTo: titleLabel.centerYAnchor).isActive = true + countLabel.leadingAnchor.constraint(equalTo: titleLabel.trailingAnchor, constant: 5).isActive = true + trailingAnchor.constraint(equalTo: countLabel.trailingAnchor).isActive = true + countLabel.setContentHuggingPriority(.defaultHigh, for: .vertical) + countLabel.setContentHuggingPriority(.required, for: .horizontal) + } + + func update(from bookmark: Bookmark) { + faviconImageView.image = bookmark.favicon(.small) ?? .bookmarkDefaultFavicon + titleLabel.stringValue = bookmark.title + countLabel.stringValue = "" + } + + func update(from folder: BookmarkFolder) { + faviconImageView.image = .folder + titleLabel.stringValue = folder.title + + let totalChildBookmarks = folder.totalChildBookmarks + if totalChildBookmarks > 0 { + countLabel.stringValue = String(totalChildBookmarks) + } else { + countLabel.stringValue = "" + } + } + + func update(from pseudoFolder: PseudoFolder) { + faviconImageView.image = pseudoFolder.icon + titleLabel.stringValue = pseudoFolder.name + countLabel.stringValue = pseudoFolder.count > 0 ? String(pseudoFolder.count) : "" + } + +} + +#if DEBUG +@available(macOS 14.0, *) +#Preview { + BookmarkOutlineCellView.PreviewView() +} + +extension BookmarkOutlineCellView { + final class PreviewView: NSView { + + init() { + super.init(frame: .zero) + wantsLayer = true + layer!.backgroundColor = NSColor.white.cgColor + + translatesAutoresizingMaskIntoConstraints = true + + let cells = [ + BookmarkOutlineCellView(identifier: .init("id")), + BookmarkOutlineCellView(identifier: .init("id")), + BookmarkOutlineCellView(identifier: .init("id")), + BookmarkOutlineCellView(identifier: .init("id")), + BookmarkOutlineCellView(identifier: .init("id")), + ] + + let stackView = NSStackView(views: cells as [NSView]) + stackView.orientation = .vertical + stackView.spacing = 1 + addAndLayout(stackView) + + cells[0].update(from: Bookmark(id: "1", url: "http://a.b", title: "DuckDuckGo", isFavorite: true)) + cells[1].update(from: BookmarkFolder(id: "2", title: "Bookmark Folder with a reasonably long name")) + cells[2].update(from: BookmarkFolder(id: "2", title: "Bookmark Folder with 42 bookmark children", children: Array(repeating: Bookmark(id: "2", url: "http://a.b", title: "DuckDuckGo", isFavorite: true), count: 42))) + PseudoFolder.favorites.count = 64 + cells[3].update(from: PseudoFolder.favorites) + PseudoFolder.bookmarks.count = 256 + cells[4].update(from: PseudoFolder.bookmarks) + + widthAnchor.constraint(equalToConstant: 258).isActive = true + heightAnchor.constraint(equalToConstant: CGFloat((28 + 1) * cells.count)).isActive = true + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + } +} +#endif diff --git a/DuckDuckGo/Bookmarks/View/BookmarkOutlineViewCell.swift b/DuckDuckGo/Bookmarks/View/BookmarkOutlineViewCell.swift deleted file mode 100644 index 0a747521d6..0000000000 --- a/DuckDuckGo/Bookmarks/View/BookmarkOutlineViewCell.swift +++ /dev/null @@ -1,74 +0,0 @@ -// -// BookmarkOutlineViewCell.swift -// -// Copyright © 2021 DuckDuckGo. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -import Foundation - -final class BookmarkOutlineViewCell: NSTableCellView { - - static let identifier = NSUserInterfaceItemIdentifier("BookmarkOutlineViewCell") - static let nib = NSNib(nibNamed: "BookmarkOutlineViewCell", bundle: Bundle.main) - - private static let defaultBookmarkFavicon = NSImage(named: "BookmarkDefaultFavicon") - - @IBOutlet var faviconImageView: NSImageView! { - didSet { - faviconImageView.wantsLayer = true - faviconImageView.layer?.cornerRadius = 2.0 - } - } - - @IBOutlet var titleLabel: NSTextField! - @IBOutlet var countLabel: NSTextField! - - override init(frame frameRect: NSRect) { - super.init(frame: frameRect) - commonInit() - } - - required init?(coder: NSCoder) { - super.init(coder: coder) - commonInit() - } - - private func commonInit() {} - - func update(from bookmark: Bookmark) { - faviconImageView.image = bookmark.favicon(.small) ?? Self.defaultBookmarkFavicon - titleLabel.stringValue = bookmark.title - countLabel.stringValue = "" - } - - func update(from folder: BookmarkFolder) { - faviconImageView.image = NSImage(named: "Folder") - titleLabel.stringValue = folder.title - countLabel.stringValue = "" - - let totalChildBookmarks = folder.totalChildBookmarks - if totalChildBookmarks > 0 { - countLabel.stringValue = "\(totalChildBookmarks)" - } else { - countLabel.stringValue = "" - } - } - - func update(from pseudoFolder: PseudoFolder) { - faviconImageView.image = pseudoFolder.icon - titleLabel.stringValue = pseudoFolder.name - countLabel.stringValue = pseudoFolder.count > 0 ? "\(pseudoFolder.count)" : "" - } -} diff --git a/DuckDuckGo/Bookmarks/View/BookmarkOutlineViewCell.xib b/DuckDuckGo/Bookmarks/View/BookmarkOutlineViewCell.xib deleted file mode 100644 index 5f156db026..0000000000 --- a/DuckDuckGo/Bookmarks/View/BookmarkOutlineViewCell.xib +++ /dev/null @@ -1,61 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/DuckDuckGo/Bookmarks/View/BookmarkTableCellView.swift b/DuckDuckGo/Bookmarks/View/BookmarkTableCellView.swift index 4dcdac0528..b9cd014e05 100644 --- a/DuckDuckGo/Bookmarks/View/BookmarkTableCellView.swift +++ b/DuckDuckGo/Bookmarks/View/BookmarkTableCellView.swift @@ -27,7 +27,7 @@ import Foundation } -final class BookmarkTableCellView: NSTableCellView, NibLoadable { +final class BookmarkTableCellView: NSTableCellView { private lazy var faviconImageView = NSImageView(image: .bookmark) diff --git a/DuckDuckGo/Bookmarks/View/Bookmarks.storyboard b/DuckDuckGo/Bookmarks/View/Bookmarks.storyboard deleted file mode 100644 index 03d9c83e5e..0000000000 --- a/DuckDuckGo/Bookmarks/View/Bookmarks.storyboard +++ /dev/null @@ -1,659 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/DuckDuckGo/Bookmarks/View/BookmarksOutlineView.swift b/DuckDuckGo/Bookmarks/View/BookmarksOutlineView.swift index a05c78bc6a..a79fb14f6f 100644 --- a/DuckDuckGo/Bookmarks/View/BookmarksOutlineView.swift +++ b/DuckDuckGo/Bookmarks/View/BookmarksOutlineView.swift @@ -50,13 +50,12 @@ final class BookmarksOutlineView: NSOutlineView { } } - override func awakeFromNib() { - guard let scrollView = enclosingScrollView else { fatalError() } + override func viewDidMoveToWindow() { + super.viewDidMoveToWindow() + guard let scrollView = enclosingScrollView else { return } + + let trackingArea = NSTrackingArea(rect: .zero, options: [.mouseMoved, .activeInKeyWindow, .inVisibleRect], owner: self, userInfo: nil) - let trackingArea = NSTrackingArea(rect: scrollView.frame, - options: [.mouseMoved, .activeInKeyWindow, .inVisibleRect], - owner: self, - userInfo: nil) scrollView.addTrackingArea(trackingArea) } @@ -69,4 +68,5 @@ final class BookmarksOutlineView: NSOutlineView { rowView.highlight = !(item?.representedObject is SpacerNode) lastRow = rowView } + } diff --git a/DuckDuckGo/Bookmarks/View/BrowserTabSelectionDelegate.swift b/DuckDuckGo/Bookmarks/View/BrowserTabSelectionDelegate.swift index d414bc187d..ee85a940d8 100644 --- a/DuckDuckGo/Bookmarks/View/BrowserTabSelectionDelegate.swift +++ b/DuckDuckGo/Bookmarks/View/BrowserTabSelectionDelegate.swift @@ -20,7 +20,7 @@ import Foundation protocol BrowserTabSelectionDelegate: AnyObject { - func selectedTab(at index: Int) + func selectedTabContent(_ content: Tab.TabContent) func selectedPreferencePane(_ identifier: PreferencePaneIdentifier) diff --git a/DuckDuckGo/Common/Extensions/NSMenuItemExtension.swift b/DuckDuckGo/Common/Extensions/NSMenuItemExtension.swift index 172ba5e471..bfa2e24e09 100644 --- a/DuckDuckGo/Common/Extensions/NSMenuItemExtension.swift +++ b/DuckDuckGo/Common/Extensions/NSMenuItemExtension.swift @@ -26,21 +26,22 @@ extension NSMenuItem { return item } - convenience init(title string: String, action selector: Selector? = nil, target: AnyObject? = nil, keyEquivalent: [KeyEquivalentElement] = [], representedObject: Any? = nil, items: [NSMenuItem]? = nil) { + convenience init(title string: String, action selector: Selector? = nil, target: AnyObject? = nil, keyEquivalent: [KeyEquivalentElement] = [], representedObject: Any? = nil, state: NSControl.StateValue = .off, items: [NSMenuItem]? = nil) { self.init(title: string, action: selector, keyEquivalent: keyEquivalent.charCode) if !keyEquivalent.modifierMask.isEmpty { self.keyEquivalentModifierMask = keyEquivalent.modifierMask } self.target = target self.representedObject = representedObject + self.state = state if let items { self.submenu = NSMenu(title: title, items: items) } } - convenience init(title string: String, action selector: Selector? = nil, target: AnyObject? = nil, keyEquivalent: [KeyEquivalentElement] = [], representedObject: Any? = nil, @MenuBuilder items: () -> [NSMenuItem]) { - self.init(title: string, action: selector, target: target, keyEquivalent: keyEquivalent, representedObject: representedObject, items: items()) + convenience init(title string: String, action selector: Selector? = nil, target: AnyObject? = nil, keyEquivalent: [KeyEquivalentElement] = [], representedObject: Any? = nil, state: NSControl.StateValue = .off, @MenuBuilder items: () -> [NSMenuItem]) { + self.init(title: string, action: selector, target: target, keyEquivalent: keyEquivalent, representedObject: representedObject, state: state, items: items()) } convenience init(action selector: Selector?) { diff --git a/DuckDuckGo/Common/Localizables/UserText.swift b/DuckDuckGo/Common/Localizables/UserText.swift index cbae1e8bd8..6826d01eaf 100644 --- a/DuckDuckGo/Common/Localizables/UserText.swift +++ b/DuckDuckGo/Common/Localizables/UserText.swift @@ -626,6 +626,7 @@ struct UserText { static let importLoginsPasswords = NSLocalizedString("import.logins.passwords", value: "Passwords", comment: "Title text for the Passwords import option") + static let importBookmarksButtonTitle = NSLocalizedString("bookmarks.import.button.title", value: "Import", comment: "Button text to open bookmark import dialog") static let initiateImport = NSLocalizedString("import.data.initiate", value: "Import", comment: "Button text for importing data") static let skipBookmarksImport = NSLocalizedString("import.data.skip.bookmarks", value: "Skip bookmarks", comment: "Button text to skip bookmarks manual import") static let skipPasswordsImport = NSLocalizedString("import.data.skip.passwords", value: "Skip passwords", comment: "Button text to skip bookmarks manual import") @@ -856,10 +857,6 @@ struct UserText { static let showNetworkProtectionShortcut = NSLocalizedString("pinning.show-netp-shortcut", value: "Show VPN Shortcut", comment: "Menu item for showing the NetP shortcut") static let hideNetworkProtectionShortcut = NSLocalizedString("pinning.hide-netp-shortcut", value: "Hide VPN Shortcut", comment: "Menu item for hiding the NetP shortcut") - static let showHomeShortcut = NSLocalizedString("pinning.show-home-shortcut", value: "Show Home Button", comment: "Menu item for showing the Home shortcut") - static let hideHomeShortcut = NSLocalizedString("pinning.hide-home-shortcut", value: "Hide Home Button", comment: "Menu item for hiding the Home shortcut") - static let showHomeButtonSettings = NSLocalizedString("settings.hide-home-shortcut", value: "Show Home Button in Toolbar", comment: "Settings Optionm to set Home Button visibility") - // MARK: - Tooltips static let autofillShortcutTooltip = NSLocalizedString("tooltip.autofill.shortcut", value: "Autofill", comment: "Tooltip for the autofill shortcut") @@ -877,6 +874,9 @@ struct UserText { static let manageBookmarksTooltip = NSLocalizedString("tooltip.bookmarks.manage-bookmarks", value: "Manage bookmarks", comment: "Tooltip for the Manage Bookmarks button") static let bookmarksManage = NSLocalizedString("bookmarks.manage", value: "Manage", comment: "Button for opening the bookmarks management interface") + static let bookmarksEmptyStateTitle = NSLocalizedString("bookmarks.empty.state.title", value: "No bookmarks yet", comment: "Title displayed in Bookmark Manager when there is no bookmarks yet") + static let bookmarksEmptyStateMessage = NSLocalizedString("bookmarks.empty.state.message", value: "If your bookmarks are saved in another browser, you can import them into DuckDuckGo.", comment: "Text displayed in Bookmark Manager when there is no bookmarks yet") + static let openDownloadsFolderTooltip = NSLocalizedString("tooltip.downloads.open-downloads-folder", value: "Open downloads folder", comment: "Tooltip for the Open Downloads Folder button") static let clearDownloadHistoryTooltip = NSLocalizedString("tooltip.downloads.clear-download-history", value: "Clear download history", comment: "Tooltip for the Clear Downloads button") diff --git a/DuckDuckGo/Common/View/AppKit/NSSavePanelExtension.swift b/DuckDuckGo/Common/View/AppKit/NSSavePanelExtension.swift index dae5d59af9..f8b1911ebe 100644 --- a/DuckDuckGo/Common/View/AppKit/NSSavePanelExtension.swift +++ b/DuckDuckGo/Common/View/AppKit/NSSavePanelExtension.swift @@ -42,19 +42,22 @@ extension NSSavePanel { popup.target = savePanel popup.action = #selector(NSSavePanel.fileTypePopUpSelectionDidChange(_:)) - for fileType in fileTypes { - let title: String - switch (fileType.localizedDescription, fileType.preferredFilenameExtension) { - case let (.some(description), .some(fileExtension)): - title = "\(description) (.\(fileExtension))" - case let (.some(description), .none): - title = description - case let (.none, .some(fileExtension)): - title = "." + fileExtension - case (.none, .none): - continue + popup.menu = NSMenu { + for fileType in fileTypes { + let title: String? = switch (fileType.localizedDescription, fileType.preferredFilenameExtension) { + case let (.some(description), .some(fileExtension)): + "\(description) (.\(fileExtension))" + case let (.some(description), .none): + description + case let (.none, .some(fileExtension)): + "." + fileExtension + case (.none, .none): + nil + } + if let title { + NSMenuItem(title: title, representedObject: fileType) + } } - popup.addItem(withTitle: title, representedObject: fileType) } } diff --git a/DuckDuckGo/Common/View/AppKit/NibLoadable.swift b/DuckDuckGo/Common/View/AppKit/NibLoadable.swift deleted file mode 100644 index e5c2a7b848..0000000000 --- a/DuckDuckGo/Common/View/AppKit/NibLoadable.swift +++ /dev/null @@ -1,46 +0,0 @@ -// -// NibLoadable.swift -// -// Copyright © 2021 DuckDuckGo. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -import Foundation - -protocol NibLoadable { - - static var nibName: String { get } - static func createFromNib(in bundle: Bundle) -> Self - -} - -extension NibLoadable where Self: NSView { - - static var nibName: String { - return String(describing: Self.self) - } - - static func createFromNib(in bundle: Bundle = Bundle.main) -> Self { - var objects: NSArray! - bundle.loadNibNamed(NSNib.Name(nibName), owner: self, topLevelObjects: &objects) - guard objects != nil else { - fatalError("NibLoadable: Could not load nib") - } - let views = objects.filter { $0 is Self } - - // swiftlint:disable force_cast - return views.last as! Self - // swiftlint:enable force_cast - } -} diff --git a/DuckDuckGo/Common/View/SwiftUI/NSPathControlView.swift b/DuckDuckGo/Common/View/SwiftUI/NSPathControlView.swift index b08a8ead58..a5906fce57 100644 --- a/DuckDuckGo/Common/View/SwiftUI/NSPathControlView.swift +++ b/DuckDuckGo/Common/View/SwiftUI/NSPathControlView.swift @@ -47,7 +47,7 @@ struct NSPathControlView: NSViewRepresentable { .publisher(for: \.effectiveAppearance) .sink { _ in NSAppearance.withAppAppearance { - newPathControl.layer?.borderColor = NSColor(named: "DividerColor")!.cgColor + newPathControl.layer?.borderColor = NSColor.divider.cgColor } } diff --git a/DuckDuckGo/Localizable.xcstrings b/DuckDuckGo/Localizable.xcstrings index 648355853b..3b079d0f19 100644 --- a/DuckDuckGo/Localizable.xcstrings +++ b/DuckDuckGo/Localizable.xcstrings @@ -1500,6 +1500,42 @@ } } }, + "bookmarks.empty.state.message" : { + "comment" : "Text displayed in Bookmark Manager when there is no bookmarks yet", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "If your bookmarks are saved in another browser, you can import them into DuckDuckGo." + } + } + } + }, + "bookmarks.empty.state.title" : { + "comment" : "Title displayed in Bookmark Manager when there is no bookmarks yet", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "No bookmarks yet" + } + } + } + }, + "bookmarks.import.button.title" : { + "comment" : "Button text to open bookmark import dialog", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Import" + } + } + } + }, "bookmarks.imported.from.folder" : { "comment" : "Name of the folder the imported bookmarks are saved into", "extractionState" : "extracted_with_value", @@ -6515,18 +6551,6 @@ } } }, - "pinning.hide-home-shortcut" : { - "comment" : "Menu item for hiding the Home shortcut", - "extractionState" : "extracted_with_value", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "Hide Home Button" - } - } - } - }, "pinning.hide-netp-shortcut" : { "comment" : "Menu item for hiding the NetP shortcut", "extractionState" : "extracted_with_value", @@ -6575,18 +6599,6 @@ } } }, - "pinning.show-home-shortcut" : { - "comment" : "Menu item for showing the Home shortcut", - "extractionState" : "extracted_with_value", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "Show Home Button" - } - } - } - }, "pinning.show-netp-shortcut" : { "comment" : "Menu item for showing the NetP shortcut", "extractionState" : "extracted_with_value", @@ -8312,18 +8324,6 @@ } } }, - "settings.hide-home-shortcut" : { - "comment" : "Settings Optionm to set Home Button visibility", - "extractionState" : "extracted_with_value", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "Show Home Button in Toolbar" - } - } - } - }, "share.menu.item" : { "comment" : "Menu item title", "extractionState" : "extracted_with_value", diff --git a/DuckDuckGo/Preferences/Model/PreferencesSidebarModel.swift b/DuckDuckGo/Preferences/Model/PreferencesSidebarModel.swift index 07e9451e73..de53764fec 100644 --- a/DuckDuckGo/Preferences/Model/PreferencesSidebarModel.swift +++ b/DuckDuckGo/Preferences/Model/PreferencesSidebarModel.swift @@ -29,6 +29,10 @@ final class PreferencesSidebarModel: ObservableObject { @Published var selectedTabIndex: Int = 0 @Published private(set) var selectedPane: PreferencePaneIdentifier = .general + var selectedTabContent: AnyPublisher { + $selectedTabIndex.map { [tabSwitcherTabs] in tabSwitcherTabs[$0] }.eraseToAnyPublisher() + } + // MARK: - Initializers init( diff --git a/DuckDuckGo/Preferences/View/PreferencesViewController.swift b/DuckDuckGo/Preferences/View/PreferencesViewController.swift index c48a995600..fa20ea57c6 100644 --- a/DuckDuckGo/Preferences/View/PreferencesViewController.swift +++ b/DuckDuckGo/Preferences/View/PreferencesViewController.swift @@ -31,7 +31,7 @@ final class PreferencesViewController: NSViewController { weak var delegate: BrowserTabSelectionDelegate? let model: PreferencesSidebarModel - private var selectedTabIndexCancellable: AnyCancellable? + private var selectedTabContentCancellable: AnyCancellable? private var selectedPreferencePaneCancellable: AnyCancellable? private var bitwardenManager: BWManagement = BWManager.shared @@ -64,10 +64,10 @@ final class PreferencesViewController: NSViewController { override func viewDidAppear() { super.viewDidAppear() - selectedTabIndexCancellable = model.$selectedTabIndex + selectedTabContentCancellable = model.selectedTabContent .dropFirst() - .sink { [weak self] index in - self?.delegate?.selectedTab(at: index) + .sink { [weak self] in + self?.delegate?.selectedTabContent($0) } selectedPreferencePaneCancellable = model.$selectedPane @@ -79,9 +79,7 @@ final class PreferencesViewController: NSViewController { override func viewWillDisappear() { super.viewWillDisappear() - selectedTabIndexCancellable?.cancel() - selectedTabIndexCancellable = nil - selectedPreferencePaneCancellable?.cancel() + selectedTabContentCancellable = nil selectedPreferencePaneCancellable = nil } } diff --git a/DuckDuckGo/Tab/View/BrowserTabViewController.swift b/DuckDuckGo/Tab/View/BrowserTabViewController.swift index fac37f7d10..2846d632d7 100644 --- a/DuckDuckGo/Tab/View/BrowserTabViewController.swift +++ b/DuckDuckGo/Tab/View/BrowserTabViewController.swift @@ -454,12 +454,6 @@ final class BrowserTabViewController: NSViewController { // MARK: - Browser Tabs - private func show(displayableTabAtIndex index: Int) { - // The tab switcher only displays displayable tab types. - tabCollectionViewModel.selectedTabViewModel?.tab.setContent(Tab.TabContent.displayableTabTypes[index]) - showTabContent(of: tabCollectionViewModel.selectedTabViewModel) - } - private func removeAllTabContent(includingWebView: Bool = true) { self.homePageView.removeFromSuperview() transientTabContentViewController?.removeCompletely() @@ -580,7 +574,7 @@ final class BrowserTabViewController: NSViewController { var bookmarksViewController: BookmarkManagementSplitViewController? private func bookmarksViewControllerCreatingIfNeeded() -> BookmarkManagementSplitViewController { return bookmarksViewController ?? { - let bookmarksViewController = BookmarkManagementSplitViewController.create() + let bookmarksViewController = BookmarkManagementSplitViewController() bookmarksViewController.delegate = self self.bookmarksViewController = bookmarksViewController return bookmarksViewController @@ -935,8 +929,9 @@ extension BrowserTabViewController: TabDownloadsDelegate { extension BrowserTabViewController: BrowserTabSelectionDelegate { - func selectedTab(at index: Int) { - show(displayableTabAtIndex: index) + func selectedTabContent(_ content: Tab.TabContent) { + tabCollectionViewModel.selectedTabViewModel?.tab.setContent(content) + showTabContent(of: tabCollectionViewModel.selectedTabViewModel) } func selectedPreferencePane(_ identifier: PreferencePaneIdentifier) { diff --git a/UnitTests/Bookmarks/Model/BookmarkOutlineViewDataSourceTests.swift b/UnitTests/Bookmarks/Model/BookmarkOutlineViewDataSourceTests.swift index 8650a423e7..8595ebdcc7 100644 --- a/UnitTests/Bookmarks/Model/BookmarkOutlineViewDataSourceTests.swift +++ b/UnitTests/Bookmarks/Model/BookmarkOutlineViewDataSourceTests.swift @@ -26,7 +26,7 @@ class BookmarkOutlineViewDataSourceTests: XCTestCase { let mockFolder = BookmarkFolder.mock let treeController = createTreeController(with: [mockFolder]) let mockFolderNode = treeController.node(representing: mockFolder)! - let dataSource = BookmarkOutlineViewDataSource(contentMode: .foldersOnly, treeController: treeController) + let dataSource = BookmarkOutlineViewDataSource(contentMode: .foldersOnly, bookmarkManager: LocalBookmarkManager(), treeController: treeController) let notification = Notification(name: NSOutlineView.itemDidExpandNotification, object: nil, userInfo: ["NSObject": mockFolderNode]) dataSource.outlineViewItemDidExpand(notification) @@ -38,7 +38,7 @@ class BookmarkOutlineViewDataSourceTests: XCTestCase { let mockFolder = BookmarkFolder.mock let treeController = createTreeController(with: [mockFolder]) let mockFolderNode = treeController.node(representing: mockFolder)! - let dataSource = BookmarkOutlineViewDataSource(contentMode: .foldersOnly, treeController: treeController) + let dataSource = BookmarkOutlineViewDataSource(contentMode: .foldersOnly, bookmarkManager: LocalBookmarkManager(), treeController: treeController) let expandNotification = Notification(name: NSOutlineView.itemDidExpandNotification, object: nil, userInfo: ["NSObject": mockFolderNode]) dataSource.outlineViewItemDidExpand(expandNotification) @@ -56,7 +56,7 @@ class BookmarkOutlineViewDataSourceTests: XCTestCase { let mockOutlineView = NSOutlineView(frame: .zero) let treeController = createTreeController(with: [mockFolder]) let mockFolderNode = treeController.node(representing: mockFolder)! - let dataSource = BookmarkOutlineViewDataSource(contentMode: .foldersOnly, treeController: treeController) + let dataSource = BookmarkOutlineViewDataSource(contentMode: .foldersOnly, bookmarkManager: LocalBookmarkManager(), treeController: treeController) let writer = dataSource.outlineView(mockOutlineView, pasteboardWriterForItem: mockFolderNode) as? FolderPasteboardWriter XCTAssertNotNil(writer) @@ -69,7 +69,7 @@ class BookmarkOutlineViewDataSourceTests: XCTestCase { let mockFolder = BookmarkFolder.mock let mockOutlineView = NSOutlineView(frame: .zero) let treeController = createTreeController(with: [mockFolder]) - let dataSource = BookmarkOutlineViewDataSource(contentMode: .foldersOnly, treeController: treeController) + let dataSource = BookmarkOutlineViewDataSource(contentMode: .foldersOnly, bookmarkManager: LocalBookmarkManager(), treeController: treeController) let spacerNode = BookmarkNode(representedObject: SpacerNode.blank, parent: nil) let writer = dataSource.outlineView(mockOutlineView, pasteboardWriterForItem: spacerNode) as? FolderPasteboardWriter @@ -88,7 +88,7 @@ class BookmarkOutlineViewDataSourceTests: XCTestCase { let treeDataSource = BookmarkSidebarTreeController(bookmarkManager: bookmarkManager) let treeController = BookmarkTreeController(dataSource: treeDataSource) let mockDestinationNode = treeController.node(representing: mockDestinationFolder)! - let dataSource = BookmarkOutlineViewDataSource(contentMode: .foldersOnly, treeController: treeController) + let dataSource = BookmarkOutlineViewDataSource(contentMode: .foldersOnly, bookmarkManager: bookmarkManager, treeController: treeController) let pasteboardBookmark = PasteboardBookmark(id: UUID().uuidString, url: "https://example.com", title: "Pasteboard Bookmark") let result = dataSource.validateDrop(for: [pasteboardBookmark], destination: mockDestinationNode) @@ -108,7 +108,7 @@ class BookmarkOutlineViewDataSourceTests: XCTestCase { let treeDataSource = BookmarkSidebarTreeController(bookmarkManager: bookmarkManager) let treeController = BookmarkTreeController(dataSource: treeDataSource) let mockDestinationNode = treeController.node(representing: mockDestinationFolder)! - let dataSource = BookmarkOutlineViewDataSource(contentMode: .foldersOnly, treeController: treeController) + let dataSource = BookmarkOutlineViewDataSource(contentMode: .foldersOnly, bookmarkManager: bookmarkManager, treeController: treeController) let pasteboardFolder = PasteboardFolder(id: UUID().uuidString, name: "Pasteboard Folder") let result = dataSource.validateDrop(for: [pasteboardFolder], destination: mockDestinationNode) @@ -127,7 +127,7 @@ class BookmarkOutlineViewDataSourceTests: XCTestCase { let treeDataSource = BookmarkSidebarTreeController(bookmarkManager: bookmarkManager) let treeController = BookmarkTreeController(dataSource: treeDataSource) - let dataSource = BookmarkOutlineViewDataSource(contentMode: .foldersOnly, treeController: treeController) + let dataSource = BookmarkOutlineViewDataSource(contentMode: .foldersOnly, bookmarkManager: bookmarkManager, treeController: treeController) let mockDestinationNode = treeController.node(representing: mockDestinationFolder)! let pasteboardFolder = PasteboardFolder(id: mockDestinationFolder.id, name: "Pasteboard Folder") @@ -149,7 +149,7 @@ class BookmarkOutlineViewDataSourceTests: XCTestCase { let treeDataSource = BookmarkSidebarTreeController(bookmarkManager: bookmarkManager) let treeController = BookmarkTreeController(dataSource: treeDataSource) - let dataSource = BookmarkOutlineViewDataSource(contentMode: .foldersOnly, treeController: treeController) + let dataSource = BookmarkOutlineViewDataSource(contentMode: .foldersOnly, bookmarkManager: bookmarkManager, treeController: treeController) let mockDestinationNode = treeController.node(representing: childFolder)! // Simulate dragging the root folder onto the child folder: diff --git a/UnitTests/Bookmarks/Model/BookmarkSidebarTreeControllerTests.swift b/UnitTests/Bookmarks/Model/BookmarkSidebarTreeControllerTests.swift index dd53767a4c..6c9e8c59da 100644 --- a/UnitTests/Bookmarks/Model/BookmarkSidebarTreeControllerTests.swift +++ b/UnitTests/Bookmarks/Model/BookmarkSidebarTreeControllerTests.swift @@ -23,7 +23,7 @@ import XCTest class BookmarkSidebarTreeControllerTests: XCTestCase { func testWhenBookmarkStoreHasNoFolders_ThenOnlyDefaultNodesAreReturned() { - let dataSource = BookmarkSidebarTreeController() + let dataSource = BookmarkSidebarTreeController(bookmarkManager: LocalBookmarkManager()) let treeController = BookmarkTreeController(dataSource: dataSource) let defaultNodes = treeController.rootNode.childNodes let representedObjects = defaultNodes.representedObjects() diff --git a/UnitTests/HomePage/Mocks/MockBookmarkManager.swift b/UnitTests/HomePage/Mocks/MockBookmarkManager.swift index 5f5d2f6513..c6a0840629 100644 --- a/UnitTests/HomePage/Mocks/MockBookmarkManager.swift +++ b/UnitTests/HomePage/Mocks/MockBookmarkManager.swift @@ -88,4 +88,8 @@ class MockBookmarkManager: BookmarkManager { @Published var list: BookmarkList? var listPublisher: Published.Publisher { $list } + + func requestSync() { + } + }