diff --git a/support/hololens/ServoApp/Bookmarks.cpp b/support/hololens/ServoApp/Bookmarks.cpp new file mode 100644 index 00000000000..a421dfe6ed6 --- /dev/null +++ b/support/hololens/ServoApp/Bookmarks.cpp @@ -0,0 +1,100 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#include "pch.h" +#include "Bookmarks.h" + +namespace winrt::servo { + +using namespace Windows::Storage; +using namespace Windows::UI::Core; +using namespace Windows::Foundation; +using namespace Windows::Data::Json; +using namespace Windows::ApplicationModel::Core; + +Bookmarks::Bookmarks() { + db = winrt::single_threaded_observable_vector(); + auto x = Concurrency::create_task([=] { + auto storageFolder = ApplicationData::Current().LocalFolder(); + auto bm_file = storageFolder.GetFileAsync(L"bookmarks.json").get(); + bool file_exist = + storageFolder.TryGetItemAsync(L"bookmarks.json").get() != nullptr; + if (file_exist) { + auto content = FileIO::ReadTextAsync(bm_file).get(); + JsonValue out = JsonValue::Parse(L"[]"); + if (!JsonValue::TryParse(content, out)) { + return; + } + auto list = out.GetArray(); + std::vector bookmarks; + for (auto value : list) { + auto obj = value.GetObject(); + auto name = obj.GetNamedString(L"name"); + auto url = obj.GetNamedString(L"url"); + bookmarks.push_back(box_value(ServoApp::Bookmark(url, name))); + } + auto dispatcher = CoreApplication::MainView().CoreWindow().Dispatcher(); + dispatcher.RunAsync(CoreDispatcherPriority::High, [=] { + db.ReplaceAll(bookmarks); + BuildIndex(); + }); + } + }); +} + +bool Bookmarks::Contains(const hstring &url) { return mIndex.count(url) > 0; } + +void Bookmarks::Set(hstring url, hstring title) { + auto bm = box_value(ServoApp::Bookmark(url, title)); + if (Contains(url)) { + auto index = mIndex.at(url); + db.SetAt(index, bm); + } else { + db.Append(bm); + } + InvalidateDB(); +} + +hstring Bookmarks::GetName(const hstring &url) { + auto index = mIndex.at(url); + ServoApp::Bookmark bm = unbox_value(db.GetAt(index)); + return bm.Name(); +} + +void Bookmarks::Delete(const hstring &url) { + auto index = mIndex.at(url); + db.RemoveAt(index); + InvalidateDB(); +} + +void Bookmarks::BuildIndex() { + mIndex.clear(); + int i = 0; + for (auto bm : db) { + auto url = unbox_value(bm).Url(); + mIndex.insert_or_assign(url, i++); + } +} + +void Bookmarks::InvalidateDB() { + BuildIndex(); + WriteSettings(); +} + +IAsyncAction Bookmarks::WriteSettings() { + auto storageFolder = ApplicationData::Current().LocalFolder(); + auto file = co_await storageFolder.CreateFileAsync( + L"bookmarks.json", CreationCollisionOption::ReplaceExisting); + JsonArray list; + for (auto boxed_bm : db) { + auto bm = unbox_value(boxed_bm); + JsonObject bookmark; + bookmark.Insert(L"name", JsonValue::CreateStringValue(bm.Name())); + bookmark.Insert(L"url", JsonValue::CreateStringValue(bm.Url())); + list.Append(bookmark); + } + FileIO::WriteTextAsync(file, list.Stringify()); +} + +} // namespace winrt::servo diff --git a/support/hololens/ServoApp/Bookmarks.h b/support/hololens/ServoApp/Bookmarks.h new file mode 100644 index 00000000000..040a3155f1f --- /dev/null +++ b/support/hololens/ServoApp/Bookmarks.h @@ -0,0 +1,38 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#pragma once + +#include "pch.h" +#include "Bookmark.g.h" + +namespace winrt::servo { + +using namespace winrt::Windows::Foundation; + +class Bookmarks { + +public: + Bookmarks(); + bool Contains(const hstring &url); + hstring GetName(const hstring &url); + void Set(hstring url, hstring title); + void Delete(const hstring &url); + const Collections::IObservableVector &TemplateSource() { + return db; + }; + +private: + IAsyncAction WriteSettings(); + void BuildIndex(); + void InvalidateDB(); + // Array of Bookmarks as defined in the IDL + // An IObservableMap would be better, but this is not supported in XAML+winrt: + // See https://github.com/microsoft/microsoft-ui-xaml/issues/1612 + Collections::IObservableVector db; + // ... so we have an additional map that link a url to the index in the array: + std::map mIndex; +}; + +} // namespace winrt::servo diff --git a/support/hololens/ServoApp/BrowserPage.cpp b/support/hololens/ServoApp/BrowserPage.cpp index 42282357834..a8417d8a759 100644 --- a/support/hololens/ServoApp/BrowserPage.cpp +++ b/support/hololens/ServoApp/BrowserPage.cpp @@ -6,6 +6,7 @@ #include "strutils.h" #include "BrowserPage.h" #include "BrowserPage.g.cpp" +#include "Bookmark.g.cpp" #include "ConsoleLog.g.cpp" #include "Devtools/Client.h" @@ -43,9 +44,19 @@ BrowserPage::BrowserPage() { } void BrowserPage::BindServoEvents() { - servoView().OnURLChanged( - [=](const auto &, hstring url) { urlTextbox().Text(url); }); - servoView().OnTitleChanged([=](const auto &, hstring title) {}); + servoView().OnURLChanged([=](const auto &, hstring url) { + mCurrentUrl = url; + urlTextbox().Text(url); + UpdateBookmarkPanel(); + }); + servoView().OnTitleChanged([=](const auto &, hstring title) { + if (title.size() > 0) { + mCurrentTitle = {title}; + } else { + mCurrentTitle = {}; + } + UpdateBookmarkPanel(); + }); servoView().OnHistoryChanged([=](bool back, bool forward) { backButton().IsEnabled(back); forwardButton().IsEnabled(forward); @@ -55,6 +66,8 @@ void BrowserPage::BindServoEvents() { CheckCrashReport(); }); servoView().OnLoadStarted([=] { + mCurrentUrl = {}; + mCurrentTitle = {}; urlbarLoadingIndicator().IsActive(true); transientLoadingIndicator().IsIndeterminate(true); reloadButton().IsEnabled(false); @@ -63,6 +76,7 @@ void BrowserPage::BindServoEvents() { stopButton().Visibility(Visibility::Visible); devtoolsButton().IsEnabled(true); CheckCrashReport(); + UpdateBookmarkPanel(); }); servoView().OnLoadEnded([=] { urlbarLoadingIndicator().IsActive(false); @@ -72,6 +86,22 @@ void BrowserPage::BindServoEvents() { stopButton().IsEnabled(false); stopButton().Visibility(Visibility::Collapsed); }); + bookmarkPanel().Opening([=](const auto &, const auto &) { + if (!mCurrentUrl.has_value()) { + return; + } + hstring url = *mCurrentUrl; + auto resourceLoader = ResourceLoader::GetForCurrentView(); + if (!mBookmarks.Contains(url)) { + auto label = resourceLoader.GetString(L"bookmarkPanel/addedTitle"); + bookmarkPanelLabel().Text(label); + mBookmarks.Set(url, bookmarkPanelTitle().Text()); + } else { + auto label = resourceLoader.GetString(L"bookmarkPanel/editTitle"); + bookmarkPanelLabel().Text(label); + } + bookmarkPanelTitle().SelectAll(); + }); servoView().OnCaptureGesturesStarted([=] { servoView().Focus(FocusState::Programmatic); navigationBar().IsHitTestVisible(false); @@ -80,9 +110,9 @@ void BrowserPage::BindServoEvents() { [=] { navigationBar().IsHitTestVisible(true); }); urlTextbox().GotFocus(std::bind(&BrowserPage::OnURLFocused, this, _1)); servoView().OnMediaSessionMetadata( - [=](hstring title, hstring artist, hstring album) {}); + [=](hstring /*title*/, hstring /*artist*/, hstring /*album*/) {}); servoView().OnMediaSessionPosition( - [=](double duration, double position, double rate) {}); + [=](double /*duration*/, double /*position*/, double /*rate*/) {}); servoView().OnMediaSessionPlaybackStateChange([=](const auto &, int state) { if (state == Servo::MediaSessionPlaybackState::None) { mediaControls().Visibility(Visibility::Collapsed); @@ -106,6 +136,10 @@ void BrowserPage::BindServoEvents() { [=](const auto &, const VisibilityChangedEventArgs &args) { servoView().ChangeVisibility(args.Visible()); }); + + auto obsBM = + mBookmarks.TemplateSource().as>(); + obsBM.VectorChanged(std::bind(&BrowserPage::OnBookmarkDBChanged, this)); } void BrowserPage::OnURLFocused(IInspectable const &) { @@ -426,6 +460,57 @@ void BrowserPage::OnDevtoolsButtonClicked(IInspectable const &, } } +void BrowserPage::OnBookmarkDBChanged() { + Dispatcher().RunAsync(CoreDispatcherPriority::High, + [=] { UpdateBookmarkPanel(); }); +} + +void BrowserPage::UpdateBookmarkPanel() { + if (mCurrentUrl.has_value()) { + bookmarkButton().IsEnabled(true); + if (mBookmarks.Contains(*mCurrentUrl)) { + bookmarkPanelIcon().Symbol(Controls::Symbol::SolidStar); + auto name = mBookmarks.GetName(*mCurrentUrl); + bookmarkPanelTitle().Text(name); + } else { + bookmarkPanelIcon().Symbol(Controls::Symbol::OutlineStar); + auto label = mCurrentTitle.value_or(*mCurrentUrl); + bookmarkPanelTitle().Text(label); + } + } else { + bookmarkButton().IsEnabled(false); + } + if (mBookmarks.TemplateSource().Size() == 0) { + bookmarkToolbar().Visibility(Visibility::Collapsed); + } else { + bookmarkToolbar().Visibility(Visibility::Visible); + } +} + +void BrowserPage::OnBookmarkEdited(IInspectable const &, + Input::KeyRoutedEventArgs const &e) { + if (e.Key() == Windows::System::VirtualKey::Enter) { + UpdateBookmark(); + } +} + +void BrowserPage::OnBookmarkClicked(IInspectable const &sender, + RoutedEventArgs const &) { + auto button = sender.as(); + auto url = winrt::unbox_value(button.Tag()); + servoView().LoadURIOrSearch(url); +} + +void BrowserPage::RemoveBookmark() { + mBookmarks.Delete(*mCurrentUrl); + bookmarkPanel().Hide(); +} + +void BrowserPage::UpdateBookmark() { + mBookmarks.Set(*mCurrentUrl, bookmarkPanelTitle().Text()); + bookmarkPanel().Hide(); +} + void BrowserPage::OnJSInputEdited(IInspectable const &, Input::KeyRoutedEventArgs const &e) { if (e.Key() == Windows::System::VirtualKey::Enter) { diff --git a/support/hololens/ServoApp/BrowserPage.h b/support/hololens/ServoApp/BrowserPage.h index 2a434d19354..a1e120955ac 100644 --- a/support/hololens/ServoApp/BrowserPage.h +++ b/support/hololens/ServoApp/BrowserPage.h @@ -5,12 +5,15 @@ #pragma once #include "BrowserPage.g.h" +#include "Bookmark.g.h" #include "ConsoleLog.g.h" #include "ServoControl/ServoControl.h" #include "Devtools/Client.h" +#include "Bookmarks.h" namespace winrt::ServoApp::implementation { +using namespace winrt::servo; using namespace winrt::Windows; using namespace winrt::Windows::Data::Json; using namespace winrt::Windows::Foundation; @@ -22,7 +25,7 @@ static const hstring FXR_SCHEME_SLASH_SLASH = L"fxr://"; static const hstring FXRMIN_SCHEME = L"fxrmin"; static const hstring FXRMIN_SCHEME_SLASH_SLASH = L"fxrmin://"; -struct BrowserPage : BrowserPageT, public servo::DevtoolsDelegate { +struct BrowserPage : BrowserPageT, public DevtoolsDelegate { public: BrowserPage(); @@ -32,6 +35,17 @@ public: void OnStopButtonClicked(IInspectable const &, RoutedEventArgs const &); void OnHomeButtonClicked(IInspectable const &, RoutedEventArgs const &); void OnDevtoolsButtonClicked(IInspectable const &, RoutedEventArgs const &); + void OnBookmarkClicked(IInspectable const &, RoutedEventArgs const &); + void OnUpdateBookmarkButtonClicked(IInspectable const &, + RoutedEventArgs const &) { + UpdateBookmark(); + }; + void OnRemoveBookmarkButtonClicked(IInspectable const &, + RoutedEventArgs const &) { + RemoveBookmark(); + }; + void OnBookmarkEdited(IInspectable const &, + Input::KeyRoutedEventArgs const &); void OnJSInputEdited(IInspectable const &, Input::KeyRoutedEventArgs const &); void OnURLEdited(IInspectable const &, Input::KeyRoutedEventArgs const &); void OnSeeAllPrefClicked(IInspectable const &, RoutedEventArgs const &); @@ -50,11 +64,15 @@ public: RoutedEventArgs const &); void OnPrefererenceSearchboxEdited(IInspectable const &, Input::KeyRoutedEventArgs const &); - void OnDevtoolsMessage(servo::DevtoolsMessageLevel, hstring, hstring); + void OnDevtoolsMessage(DevtoolsMessageLevel, hstring, hstring); void ClearConsole(); void OnDevtoolsDetached(); Collections::IObservableVector ConsoleLogs() { return mLogs; }; - void BuildPrefList(); + Collections::IObservableVector Bookmarks() { + return mBookmarks.TemplateSource(); + }; + void RemoveBookmark(); + void UpdateBookmark(); private: void SetTransientMode(bool); @@ -63,13 +81,19 @@ private: void BindServoEvents(); void ShowToolbox(); void HideToolbox(); + void BuildPrefList(); + void UpdateBookmarkPanel(); + void OnBookmarkDBChanged(); DevtoolsStatus mDevtoolsStatus = DevtoolsStatus::Stopped; unsigned int mDevtoolsPort = 0; hstring mDevtoolsToken; bool mPanicking = false; - std::unique_ptr mDevtoolsClient; + std::unique_ptr mDevtoolsClient; Collections::IObservableVector mLogs; std::map mPromotedPrefs; + std::optional mCurrentUrl; + std::optional mCurrentTitle; + servo::Bookmarks mBookmarks; }; struct ConsoleLog : ConsoleLogT { @@ -90,9 +114,21 @@ private: hstring mBody; }; +struct Bookmark : BookmarkT { +public: + Bookmark(hstring url, hstring name) : mName(name), mUrl(url){}; + hstring Name() { return mName; }; + hstring Url() { return mUrl; }; + +private: + hstring mName; + hstring mUrl; +}; + } // namespace winrt::ServoApp::implementation namespace winrt::ServoApp::factory_implementation { struct BrowserPage : BrowserPageT {}; struct ConsoleLog : ConsoleLogT {}; +struct Bookmark : BookmarkT {}; } // namespace winrt::ServoApp::factory_implementation diff --git a/support/hololens/ServoApp/BrowserPage.idl b/support/hololens/ServoApp/BrowserPage.idl index 107724b1616..4579d937a70 100644 --- a/support/hololens/ServoApp/BrowserPage.idl +++ b/support/hololens/ServoApp/BrowserPage.idl @@ -5,6 +5,7 @@ namespace ServoApp { BrowserPage(); Windows.Foundation.Collections.IObservableVector ConsoleLogs{ get; }; + Windows.Foundation.Collections.IObservableVector Bookmarks{ get; }; } runtimeclass ConsoleLog @@ -15,4 +16,11 @@ namespace ServoApp String Body{ get; }; String Source{ get; }; } + + runtimeclass Bookmark + { + Bookmark(String name, String url); + String Name{ get; }; + String Url{ get; }; + } } diff --git a/support/hololens/ServoApp/BrowserPage.xaml b/support/hololens/ServoApp/BrowserPage.xaml index 28d6871a3c9..2b1c6b0e121 100644 --- a/support/hololens/ServoApp/BrowserPage.xaml +++ b/support/hololens/ServoApp/BrowserPage.xaml @@ -83,11 +83,12 @@ - - - - - + + + + + + @@ -130,6 +131,24 @@ +