Wire playlist webui

This commit is contained in:
sangwoo.ko
2022-07-19 15:01:15 +09:00
parent 6ade5825aa
commit 60f912d93d
42 changed files with 1133 additions and 39 deletions
+34 -1
View File
@@ -23,6 +23,7 @@
#include "brave/components/debounce/common/features.h"
#include "brave/components/ipfs/buildflags/buildflags.h"
#include "brave/components/ntp_background_images/browser/features.h"
#include "brave/components/playlist/buildflags/buildflags.h"
#include "brave/components/skus/browser/skus_utils.h"
#include "brave/components/skus/common/features.h"
#include "brave/components/speedreader/common/buildflags.h"
@@ -43,6 +44,12 @@
#include "brave/components/ipfs/features.h"
#endif
#if BUILDFLAG(ENABLE_PLAYLIST)
#include "brave/components/playlist/features.h"
#include "chrome/common/channel_info.h"
#include "components/version_info/version_info.h"
#endif
using brave_shields::features::kBraveAdblockCnameUncloaking;
using brave_shields::features::kBraveAdblockCollapseBlockedElements;
using brave_shields::features::kBraveAdblockCookieListDefault;
@@ -321,6 +328,9 @@ constexpr char kRestrictWebSocketsPoolName[] = "Restrict WebSockets pool";
constexpr char kRestrictWebSocketsPoolDescription[] =
"Limits simultaneous active WebSockets connections per eTLD+1";
constexpr char kPlaylistName[] = "Playlist";
constexpr char kPlaylistDescription[] = "Enables Playlist";
} // namespace
} // namespace flag_descriptions
@@ -449,6 +459,28 @@ constexpr char kRestrictWebSocketsPoolDescription[] =
#define BRAVE_TRANSLATE_GO_FEATURE_ENTRIES
#endif // BUILDFLAG(ENABLE_BRAVE_TRANSLATE_GO)
#if BUILDFLAG(ENABLE_PLAYLIST)
uint16_t AllowForDevVersion(uint16_t os) {
if (auto channel = chrome::GetChannel();
channel == version_info::Channel::STABLE ||
channel == version_info::Channel::BETA) {
return 0;
}
return os;
}
#define PLAYLIST_FEATURE_ENTRIES \
{"playlist", \
flag_descriptions::kPlaylistName, \
flag_descriptions::kPlaylistDescription, \
AllowForDevVersion( \
flags_ui::kOsMac | flags_ui::kOsWin | flags_ui::kOsLinux), \
FEATURE_VALUE_TYPE(playlist::features::kPlaylist)},
#else
#define PLAYLIST_FEATURE_ENTRIES
#endif
#define BRAVE_ABOUT_FLAGS_FEATURE_ENTRIES \
{"use-dev-updater-url", \
flag_descriptions::kUseDevUpdaterUrlName, \
@@ -582,4 +614,5 @@ constexpr char kRestrictWebSocketsPoolDescription[] =
BRAVE_SKU_SDK_FEATURE_ENTRIES \
SPEEDREADER_FEATURE_ENTRIES \
BRAVE_TRANSLATE_GO_FEATURE_ENTRIES \
BRAVE_FEDERATED_FEATURE_ENTRIES
BRAVE_FEDERATED_FEATURE_ENTRIES \
PLAYLIST_FEATURE_ENTRIES
+13
View File
@@ -65,6 +65,8 @@
#include "brave/components/ftx/browser/buildflags/buildflags.h"
#include "brave/components/gemini/browser/buildflags/buildflags.h"
#include "brave/components/ipfs/buildflags/buildflags.h"
#include "brave/components/playlist/buildflags/buildflags.h"
#include "brave/components/playlist/features.h"
#include "brave/components/sidebar/buildflags/buildflags.h"
#include "brave/components/skus/common/skus_sdk.mojom.h"
#include "brave/components/speedreader/common/buildflags.h"
@@ -213,6 +215,10 @@ using extensions::ChromeContentBrowserClientExtensionsPart;
#include "brave/browser/brave_ads/brave_ads_host.h"
#endif // BUILDFLAG(IS_ANDROID)
#if BUILDFLAG(ENABLE_PLAYLIST)
#include "brave/browser/ui/webui/playlist_ui.h"
#endif // BUILDFLAG(ENABLE_PLAYLIST)
namespace {
bool HandleURLReverseOverrideRewrite(GURL* url,
@@ -592,6 +598,13 @@ void BraveContentBrowserClient::RegisterBrowserInterfaceBindersForFrame(
}
#endif
#if BUILDFLAG(ENABLE_PLAYLIST)
if (base::FeatureList::IsEnabled(playlist::features::kPlaylist)) {
chrome::internal::RegisterWebUIControllerInterfaceBinder<
playlist::mojom::PageHandlerFactory, playlist::PlaylistUI>(map);
}
#endif
#if !BUILDFLAG(IS_ANDROID)
chrome::internal::RegisterWebUIControllerInterfaceBinder<
brave_new_tab_page::mojom::PageHandlerFactory, BraveNewTabUI>(map);
@@ -166,7 +166,8 @@ void BraveShieldsWebContentsObserver::DispatchBlockedEventForWebContents(
return;
auto* shields_data_ctrlr =
brave_shields::BraveShieldsDataController::FromWebContents(web_contents);
DCHECK(shields_data_ctrlr);
// |shields_data_ctrlr| can be null if the |web_contents| is generated in
// component layer - We don't attach any tab helpers in this case.
if (!shields_data_ctrlr)
return;
shields_data_ctrlr->HandleItemBlocked(block_type, subresource);
+16
View File
@@ -5,6 +5,7 @@ import("//brave/components/brave_wayback_machine/buildflags/buildflags.gni")
import("//brave/components/ftx/browser/buildflags/buildflags.gni")
import("//brave/components/ipfs/buildflags/buildflags.gni")
import("//brave/components/ntp_background_images/buildflags/buildflags.gni")
import("//brave/components/playlist/buildflags/buildflags.gni")
import("//brave/components/sidebar/buildflags/buildflags.gni")
import("//brave/components/speedreader/common/buildflags.gni")
import("//brave/components/tor/buildflags/buildflags.gni")
@@ -308,6 +309,8 @@ source_set("ui") {
"//brave/components/ntp_background_images/common",
"//brave/components/ntp_widget_utils/browser",
"//brave/components/p3a:buildflags",
"//brave/components/playlist/buildflags",
"//brave/components/resources:static_resources",
"//brave/components/sidebar/buildflags",
"//brave/components/time_period_storage",
"//brave/components/tor",
@@ -658,6 +661,19 @@ source_set("ui") {
deps += [ "//brave/components/ftx/common" ]
}
if (enable_playlist) {
sources += [
"webui/playlist_page_handler.cc",
"webui/playlist_page_handler.h",
"webui/playlist_ui.cc",
"webui/playlist_ui.h",
]
deps += [
"//brave/components/playlist/mojom",
"//brave/components/playlist/resources:generated_resources",
]
}
if (toolkit_views) {
deps += [
"//components/constrained_window",
@@ -21,6 +21,7 @@
#include "brave/components/constants/pref_names.h"
#include "brave/components/constants/webui_url_constants.h"
#include "brave/components/ipfs/buildflags/buildflags.h"
#include "brave/components/playlist/buildflags/buildflags.h"
#include "brave/components/sidebar/buildflags/buildflags.h"
#include "brave/components/tor/buildflags/buildflags.h"
#include "build/build_config.h"
@@ -55,6 +56,10 @@
#include "brave/components/ipfs/ipfs_utils.h"
#endif
#if BUILDFLAG(ENABLE_PLAYLIST)
#include "brave/browser/ui/webui/playlist_ui.h"
#endif
#if BUILDFLAG(ENABLE_TOR)
#include "brave/browser/ui/webui/tor_internals_ui.h"
#endif
@@ -86,7 +91,7 @@ WebUIController* NewWebUI(WebUI* web_ui, const GURL& url) {
} else if (host == kIPFSWebUIHost &&
ipfs::IpfsServiceFactory::IsIpfsEnabled(profile)) {
return new IPFSUI(web_ui, url.host());
#endif // BUILDFLAG(ENABLE_IPFS)
#endif
#if !BUILDFLAG(IS_ANDROID)
} else if (host == kWalletPageHost) {
if (brave_wallet::IsNativeWalletEnabled()) {
@@ -101,6 +106,10 @@ WebUIController* NewWebUI(WebUI* web_ui, const GURL& url) {
} else if (host == kWalletPanelHost) {
return new WalletPanelUI(web_ui);
#endif // BUILDFLAG(OS_ANDROID)
#if BUILDFLAG(ENABLE_PLAYLIST)
} else if (host == kPlaylistHost) {
return new playlist::PlaylistUI(web_ui, url.host());
#endif // BUILDFLAG(PLAYLIST_ENABLED)
} else if (host == kRewardsPageHost) {
return new BraveRewardsPageUI(web_ui, url.host());
} else if (host == kRewardsInternalsHost) {
@@ -169,6 +178,9 @@ WebUIFactoryFunction GetWebUIFactoryFunction(WebUI* web_ui, const GURL& url) {
#endif
#if BUILDFLAG(ENABLE_TOR)
url.host_piece() == kTorInternalsHost ||
#endif
#if BUILDFLAG(ENABLE_PLAYLIST)
url.host_piece() == kPlaylistHost ||
#endif
url.host_piece() == kWelcomeHost ||
url.host_piece() == chrome::kChromeUIWelcomeURL ||
@@ -213,6 +225,10 @@ WebUI::TypeID BraveWebUIControllerFactory::GetWebUIType(
return WebUI::kNoWebUI;
}
#endif // BUILDFLAG(IS_ANDROID)
#if BUILDFLAG(ENABLE_PLAYLIST)
if (playlist::PlaylistUI::ShouldBlockPlaylistWebUI(browser_context, url))
return WebUI::kNoWebUI;
#endif
WebUIFactoryFunction function = GetWebUIFactoryFunction(NULL, url);
if (function) {
return reinterpret_cast<WebUI::TypeID>(function);
+108
View File
@@ -0,0 +1,108 @@
/* Copyright (c) 2022 The Brave Authors. All rights reserved.
* 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 http://mozilla.org/MPL/2.0/. */
#include "brave/browser/ui/webui/playlist_page_handler.h"
#include <string>
#include <utility>
#include <vector>
#include "brave/browser/playlist/playlist_service_factory.h"
#include "brave/components/playlist/playlist_constants.h"
#include "chrome/browser/profiles/profile.h"
playlist::PlaylistService* GetPlaylistService(Profile* profile) {
return playlist::PlaylistServiceFactory::GetForBrowserContext(profile);
}
PlaylistPageHandler::PlaylistPageHandler(
Profile* profile,
mojo::PendingReceiver<playlist::mojom::PageHandler> pending_page_handler,
mojo::PendingRemote<playlist::mojom::Page> pending_page)
: profile_(profile),
page_(std::move(pending_page)),
handler_(this, std::move(pending_page_handler)) {
DCHECK(profile_);
observation_.Observe(GetPlaylistService(profile_));
}
PlaylistPageHandler::~PlaylistPageHandler() = default;
void PlaylistPageHandler::GetAllPlaylists(
PlaylistPageHandler::GetAllPlaylistsCallback callback) {
auto value = GetPlaylistService(profile_)->GetAllPlaylistItems();
DCHECK(value.is_list());
std::vector<mojo::StructPtr<playlist::mojom::Playlist>> playlists;
playlists.push_back(playlist::mojom::Playlist::New());
auto& playlist = playlists.back();
playlist->name = "default";
for (const auto& item : value.GetList()) {
auto* dict = item.GetIfDict();
DCHECK(dict);
DCHECK(dict->contains(playlist::kPlaylistItemIDKey));
DCHECK(dict->contains(playlist::kPlaylistItemTitleKey));
DCHECK(dict->contains(playlist::kPlaylistItemMediaFilePathKey));
DCHECK(dict->contains(playlist::kPlaylistItemThumbnailPathKey));
playlist->items.push_back(playlist::mojom::PlaylistItem::New(
*dict->FindString(playlist::kPlaylistItemIDKey) /* = id */,
*dict->FindString(playlist::kPlaylistItemTitleKey) /* = name */,
GURL(*dict->FindString(
playlist::kPlaylistItemMediaFilePathKey)) /* = media_path */,
GURL(*dict->FindString(
playlist::
kPlaylistItemThumbnailPathKey)) /* = ::GURL& thumbnail_path */,
*dict->FindBool(playlist::kPlaylistItemReadyKey) /* = ready */));
}
std::move(callback).Run(std::move(playlists));
}
void PlaylistPageHandler::GetPlaylist(
const std::string& id,
PlaylistPageHandler::GetPlaylistCallback callback) {
// TODO(sko) Get items for playlist with |id|.
auto value = GetPlaylistService(profile_)->GetAllPlaylistItems();
DCHECK(value.is_list());
playlist::mojom::Playlist playlist;
playlist.name = "default";
for (const auto& item : value.GetList()) {
auto* dict = item.GetIfDict();
DCHECK(dict);
DCHECK(dict->contains(playlist::kPlaylistItemIDKey));
DCHECK(dict->contains(playlist::kPlaylistItemTitleKey));
DCHECK(dict->contains(playlist::kPlaylistItemMediaFilePathKey));
DCHECK(dict->contains(playlist::kPlaylistItemThumbnailPathKey));
playlist.items.push_back(playlist::mojom::PlaylistItem::New(
*dict->FindString(playlist::kPlaylistItemIDKey) /* = id */,
*dict->FindString(playlist::kPlaylistItemTitleKey) /* = name */,
GURL(*dict->FindString(
playlist::kPlaylistItemMediaFilePathKey)) /* = media_path */,
GURL(*dict->FindString(
playlist::
kPlaylistItemThumbnailPathKey)) /* = ::GURL& thumbnail_path */,
*dict->FindBool(playlist::kPlaylistItemReadyKey) /* = ready */));
}
std::move(callback).Run(playlist.Clone());
}
void PlaylistPageHandler::AddMediaFilesFromPageToPlaylist(const std::string& id,
const GURL& url) {
GetPlaylistService(profile_)->RequestDownloadMediaFilesFromPage(id,
url.spec());
}
void PlaylistPageHandler::RemoveItemFromPlaylist(const std::string& playlist_id,
const std::string& item_id) {
GetPlaylistService(profile_)->RemoveItemFromPlaylist(playlist_id, item_id);
}
void PlaylistPageHandler::OnPlaylistItemStatusChanged(
const playlist::PlaylistItemChangeParams& params) {
page_->OnEvent(playlist::mojom::PlaylistEvent::kItemAdded);
}
+56
View File
@@ -0,0 +1,56 @@
/* Copyright (c) 2022 The Brave Authors. All rights reserved.
* 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 http://mozilla.org/MPL/2.0/. */
#ifndef BRAVE_BROWSER_UI_WEBUI_PLAYLIST_PAGE_HANDLER_H_
#define BRAVE_BROWSER_UI_WEBUI_PLAYLIST_PAGE_HANDLER_H_
#include <string>
#include "base/scoped_observation.h"
#include "brave/components/playlist/mojom/playlist.mojom.h"
#include "brave/components/playlist/playlist_service.h"
#include "brave/components/playlist/playlist_service_observer.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "mojo/public/cpp/bindings/remote.h"
class Profile;
class PlaylistPageHandler : public playlist::mojom::PageHandler,
public playlist::PlaylistServiceObserver {
public:
PlaylistPageHandler(
Profile* profile,
mojo::PendingReceiver<playlist::mojom::PageHandler> pending_page_handler,
mojo::PendingRemote<playlist::mojom::Page> pending_page);
~PlaylistPageHandler() override;
// playlist::mojom::PageHandler:
void GetAllPlaylists(
PlaylistPageHandler::GetAllPlaylistsCallback callback) override;
void GetPlaylist(const std::string& id,
PlaylistPageHandler::GetPlaylistCallback callback) override;
void AddMediaFilesFromPageToPlaylist(const std::string& playlist_id,
const GURL& url) override;
void RemoveItemFromPlaylist(const std::string& playlist_id,
const std::string& item_id) override;
// playlist::PlaylistServiceObserver
void OnPlaylistItemStatusChanged(
const playlist::PlaylistItemChangeParams& params) override;
private:
raw_ptr<Profile> profile_ = nullptr;
mojo::Remote<playlist::mojom::Page> page_;
mojo::Receiver<playlist::mojom::PageHandler> handler_;
base::ScopedObservation<playlist::PlaylistService,
playlist::PlaylistServiceObserver>
observation_{this};
};
#endif // BRAVE_BROWSER_UI_WEBUI_PLAYLIST_PAGE_HANDLER_H_
+60
View File
@@ -0,0 +1,60 @@
/* Copyright (c) 2021 The Brave Authors. All rights reserved.
* 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,
#include "chrome/browser/profiles/profile.h"
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "brave/browser/ui/webui/playlist_ui.h"
#include <utility>
#include "brave/browser/playlist/playlist_service_factory.h"
#include "brave/browser/ui/webui/brave_webui_source.h"
#include "brave/browser/ui/webui/playlist_page_handler.h"
#include "brave/components/constants/webui_url_constants.h"
#include "brave/components/playlist/resources/grit/playlist_generated_map.h"
#include "chrome/browser/profiles/profile.h"
#include "components/grit/brave_components_resources.h"
#include "url/gurl.h"
namespace playlist {
// static
bool PlaylistUI::ShouldBlockPlaylistWebUI(
content::BrowserContext* browser_context,
const GURL& url) {
if (url.host_piece() != kPlaylistHost)
return false;
return !PlaylistServiceFactory::IsPlaylistEnabled(browser_context);
}
PlaylistUI::PlaylistUI(content::WebUI* web_ui, const std::string& name)
: MojoWebUIController(web_ui) {
CreateAndAddWebUIDataSource(web_ui, name, kPlaylistGenerated,
kPlaylistGeneratedSize, IDR_PLAYLIST_HTML);
}
PlaylistUI::~PlaylistUI() = default;
void PlaylistUI::BindInterface(
mojo::PendingReceiver<playlist::mojom::PageHandlerFactory>
pending_receiver) {
if (page_factory_receiver_.is_bound())
page_factory_receiver_.reset();
page_factory_receiver_.Bind(std::move(pending_receiver));
}
void PlaylistUI::CreatePageHandler(
mojo::PendingRemote<playlist::mojom::Page> pending_page,
mojo::PendingReceiver<playlist::mojom::PageHandler> pending_page_handler) {
DCHECK(pending_page.is_valid());
page_handler_ = std::make_unique<PlaylistPageHandler>(
Profile::FromWebUI(web_ui()), std::move(pending_page_handler),
std::move(pending_page));
}
WEB_UI_CONTROLLER_TYPE_IMPL(PlaylistUI)
} // namespace playlist
+55
View File
@@ -0,0 +1,55 @@
/* Copyright (c) 2021 The Brave Authors. All rights reserved.
* 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 http://mozilla.org/MPL/2.0/. */
#ifndef BRAVE_BROWSER_UI_WEBUI_PLAYLIST_UI_H_
#define BRAVE_BROWSER_UI_WEBUI_PLAYLIST_UI_H_
#include <memory>
#include <string>
#include "brave/components/playlist/mojom/playlist.mojom.h"
#include "ui/webui/mojo_web_ui_controller.h"
namespace content {
class BrowserContext;
} // namespace content
class GURL;
class PlaylistPageHandler;
namespace playlist {
class PlaylistUI : public ui::MojoWebUIController,
public playlist::mojom::PageHandlerFactory {
public:
static bool ShouldBlockPlaylistWebUI(content::BrowserContext* browser_context,
const GURL& url);
PlaylistUI(content::WebUI* web_ui, const std::string& host);
~PlaylistUI() override;
PlaylistUI(const PlaylistUI&) = delete;
PlaylistUI& operator=(const PlaylistUI&) = delete;
void BindInterface(mojo::PendingReceiver<playlist::mojom::PageHandlerFactory>
pending_receiver);
// playlist::mojom::PageHandlerFactory:
void CreatePageHandler(
mojo::PendingRemote<playlist::mojom::Page> pending_page,
mojo::PendingReceiver<playlist::mojom::PageHandler> pending_page_handler)
override;
private:
std::unique_ptr<PlaylistPageHandler> page_handler_;
mojo::Receiver<playlist::mojom::PageHandlerFactory> page_factory_receiver_{
this};
WEB_UI_CONTROLLER_TYPE_DECL();
};
} // namespace playlist
#endif // BRAVE_BROWSER_UI_WEBUI_PLAYLIST_UI_H_
+2 -1
View File
@@ -162,7 +162,8 @@
"chrome://wallet/*",
"chrome://wallet-panel.top-chrome/*",
"chrome://brave-shields.top-chrome/*",
"chrome://rewards-panel.top-chrome/*"
"chrome://rewards-panel.top-chrome/*",
"chrome://playlist/*"
]
}],
"greaselion": [{
+1
View File
@@ -11,6 +11,7 @@ brave_common_web_compile_inputs = [
# see ../definitions/README.md.
rebase_path("../definitions/adBlock.d.ts"),
rebase_path("../definitions/ipfs.d.ts"),
rebase_path("../definitions/playlist.d.ts"),
rebase_path("../definitions/webcompatReporter.d.ts"),
rebase_path("../definitions/chromel.d.ts"),
rebase_path("../definitions/global.d.ts"),
@@ -43,3 +43,5 @@ const char kSidebarBookmarksHost[] = "sidebar-bookmarks.top-chrome";
const char kFederatedInternalsURL[] = "brave://federated-internals";
const char kFederatedInternalsHost[] = "federated-internals";
const char kContentFiltersPath[] = "shields/filters";
const char kPlaylistHost[] = "playlist";
const char kPlaylistURL[] = "chrome://playlist/";
@@ -45,5 +45,7 @@ extern const char kSidebarBookmarksHost[];
extern const char kFederatedInternalsURL[];
extern const char kFederatedInternalsHost[];
extern const char kContentFiltersPath[];
extern const char kPlaylistHost[];
extern const char kPlaylistURL[];
#endif // BRAVE_COMPONENTS_CONSTANTS_WEBUI_URL_CONSTANTS_H_
+15
View File
@@ -0,0 +1,15 @@
/* 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 http://mozilla.org/MPL/2.0/. */
import * as PlaylistMojo from 'gen/brave/components/playlist/mojom/playlist.mojom.m.js'
declare namespace Playlist {
export interface ApplicationState {
playlistData: State|undefined
}
export interface State {
lists : PlaylistMojo.Playlist[]
}
}
@@ -76,6 +76,59 @@ void MediaDetectorComponentManager::SetUseLocalScriptForTesting() {
// https://github.com/brave/brave-ios/blob/development/Client/Frontend/UserContent/UserScripts/Playlist.js
static const std::string kScript = R"-(
(function() {
function is_nan(value) {
return typeof value === "number" && value !== value;
}
function is_infinite(value) {
return typeof value === "number" && (value === Infinity || value === -Infinity);
}
function clamp_duration(value) {
if (is_nan(value)) {
return 0.0;
}
if (is_infinite(value)) {
return Number.MAX_VALUE;
}
return value;
}
// Algorithm:
// Generate a random number from 0 to 256
// Roll-Over clamp to the range [0, 15]
// If the index is 13, set it to 4.
// If the index is 17, clamp it to [0, 3]
// Subtract that number from 15 (XOR) and convert the result to hex.
function uuid_v4() {
// X >> 2 = X / 4 (integer division)
// AND-ing (15 >> 0) roll-over clamps to 15
// AND-ing (15 >> 2) roll-over clamps to 3
// So '8' digit is clamped to 3 (inclusive) and all others clamped to 15 (inclusive).
// 0 XOR 15 = 15
// 1 XOR 15 = 14
// 8 XOR 15 = 7
// So N XOR 15 = 15 - N
// UUID string format generated with array appending
// Results in "10000000-1000-4000-8000-100000000000".replace(...)
return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, (X) => {
return (X ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (X >> 2)))).toString(16);
});
}
function tagNode(node) {
if (node) {
if (!node.tagUUID) {
node.tagUUID = uuid_v4();
node.addEventListener('webkitpresentationmodechanged', (e) => e.stopPropagation(), true);
}
}
}
function getNodeSource(node, src, mimeType, thumbnail) {
var name = node.title;
if (name == null || typeof name == 'undefined' || name == "") {
@@ -102,14 +155,14 @@ void MediaDetectorComponentManager::SetUseLocalScriptForTesting() {
}
if (src && src !== "") {
// tagNode(node);
tagNode(node);
return {
"name": name,
"src": src,
"pageSrc": window.location.href,
"pageTitle": document.title,
"mimeType": mimeType,
//"duration": clamp_duration(node.duration),
"duration": clamp_duration(node.duration),
"detected": true,
"tagId": node.tagUUID,
thumbnail
@@ -119,14 +172,14 @@ void MediaDetectorComponentManager::SetUseLocalScriptForTesting() {
document.querySelectorAll('source').forEach(function(node) {
if (node.src !== "") {
if (node.closest('video') === target) {
// tagNode(target);
tagNode(target);
return {
"name": name,
"src": node.src,
"pageSrc": window.location.href,
"pageTitle": document.title,
"mimeType": mimeType,
//"duration": clamp_duration(target.duration),
"duration": clamp_duration(target.duration),
"detected": true,
"tagId": target.tagUUID,
thumbnail
@@ -141,7 +194,7 @@ void MediaDetectorComponentManager::SetUseLocalScriptForTesting() {
"pageSrc": window.location.href,
"pageTitle": document.title,
"mimeType": mimeType,
//"duration": clamp_duration(target.duration),
"duration": clamp_duration(target.duration),
"detected": true,
"tagId": target.tagUUID,
thumbnail
@@ -186,7 +239,6 @@ void MediaDetectorComponentManager::SetUseLocalScriptForTesting() {
return medias;
return videoElements;
})();
)-";
+14
View File
@@ -0,0 +1,14 @@
# Copyright (c) 2022 The Brave Authors. All rights reserved.
# 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 http://mozilla.org/MPL/2.0/.
import("//mojo/public/tools/bindings/mojom.gni")
mojom("mojom") {
sources = [ "playlist.mojom" ]
public_deps = [
"//mojo/public/mojom/base",
"//url/mojom:url_mojom_gurl",
]
}
+61
View File
@@ -0,0 +1,61 @@
// Copyright (c) 2022 The Brave Authors. All rights reserved.
// 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 http://mozilla.org/MPL/2.0/.
module playlist.mojom;
import "url/mojom/url.mojom";
enum PlaylistEvent {
kItemAdded,
kItemThumbnailReady,
kItemThumbnailFailed,
kItemReadyToPlay,
kItemDeleted,
kAllItemsDeleted,
kItemAborted,
};
struct Playlist {
string? id;
string name;
array<PlaylistItem> items;
};
struct PlaylistItem {
string id;
string name;
url.mojom.Url media_path;
url.mojom.Url thumbnail_path;
// When it's |ready|, |media_path| and |thumbnail_path| will be
// local path.
bool ready;
};
// Used by the WebUI page to bootstrap bidirectional communication.
interface PageHandlerFactory {
// The WebUI page's |BrowserProxy| singleton calls this method when the page
// is first initialized.
CreatePageHandler(pending_remote<Page> page,
pending_receiver<PageHandler> handler);
};
// Browser-side handler for requests from WebUI page.
interface PageHandler {
GetAllPlaylists() => (array<Playlist> playlists);
GetPlaylist(string id) => (Playlist? playlist);
AddMediaFilesFromPageToPlaylist(string playlist_id, url.mojom.Url url);
RemoveItemFromPlaylist(string playlist_id, string item_id);
};
// WebUI-side handler for requests from the browser.
interface Page {
OnEvent(PlaylistEvent event);
};
+62 -15
View File
@@ -27,8 +27,10 @@ namespace {
scoped_refptr<base::RefCountedMemory> ReadFileToString(
const base::FilePath& path) {
std::string contents;
if (!base::ReadFileToString(path, &contents))
if (!base::ReadFileToString(path, &contents)) {
VLOG(2) << __FUNCTION__ << " Failed to read " << path;
return nullptr;
}
return base::MakeRefCounted<base::RefCountedBytes>(
reinterpret_cast<const unsigned char*>(contents.c_str()),
@@ -43,7 +45,7 @@ PlaylistDataSource::PlaylistDataSource(PlaylistService* service)
PlaylistDataSource::~PlaylistDataSource() {}
std::string PlaylistDataSource::GetSource() {
return "playlist-image";
return "playlist-data";
}
void PlaylistDataSource::StartDataRequest(
@@ -54,33 +56,78 @@ void PlaylistDataSource::StartDataRequest(
std::move(got_data_callback).Run(nullptr);
return;
}
base::FilePath thumbnail_path;
if (!service_->GetThumbnailPath(URLDataSource::URLToRequestPath(url),
&thumbnail_path)) {
std::string path = URLDataSource::URLToRequestPath(url);
std::string id;
std::string type_string;
if (auto pos = path.find("/"); pos != std::string::npos) {
id = std::string(path.begin(), path.begin() + pos);
type_string = std::string(path.begin() + pos, path.end());
type_string.erase(std::remove(type_string.begin(), type_string.end(), '/'),
type_string.end());
} else {
NOTREACHED() << "path is not in expected form: /id/{thumbnail,media}/ vs "
<< path;
std::move(got_data_callback).Run(nullptr);
return;
}
GetThumbnailImageFile(thumbnail_path, std::move(got_data_callback));
base::FilePath data_path;
if (type_string == "thumbnail") {
if (!service_->GetThumbnailPath(id, &data_path)) {
std::move(got_data_callback).Run(nullptr);
return;
}
} else if (type_string == "media") {
if (!service_->GetMediaPath(id, &data_path)) {
std::move(got_data_callback).Run(nullptr);
return;
}
} else {
NOTREACHED() << "type is neither of {thumbnail,media}/ : " << type_string;
std::move(got_data_callback).Run(nullptr);
return;
}
GetDataFile(data_path, std::move(got_data_callback));
}
void PlaylistDataSource::GetThumbnailImageFile(
const base::FilePath& image_file_path,
GotDataCallback got_data_callback) {
void PlaylistDataSource::GetDataFile(const base::FilePath& data_path,
GotDataCallback got_data_callback) {
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE, base::MayBlock(),
base::BindOnce(&ReadFileToString, image_file_path),
base::BindOnce(&PlaylistDataSource::OnGotThumbnailImageFile,
FROM_HERE, base::MayBlock(), base::BindOnce(&ReadFileToString, data_path),
base::BindOnce(&PlaylistDataSource::OnGotDataFile,
weak_factory_.GetWeakPtr(), std::move(got_data_callback)));
}
void PlaylistDataSource::OnGotThumbnailImageFile(
void PlaylistDataSource::OnGotDataFile(
GotDataCallback got_data_callback,
scoped_refptr<base::RefCountedMemory> input) {
std::move(got_data_callback).Run(input);
}
std::string PlaylistDataSource::GetMimeType(const std::string&) {
return "image/jpg";
std::string PlaylistDataSource::GetMimeType(const std::string& path) {
std::string id;
std::string type_string;
if (auto pos = path.find("/"); pos != std::string::npos) {
id = std::string(path.begin(), path.begin() + pos);
type_string = std::string(path.begin() + pos, path.end());
type_string.erase(std::remove(type_string.begin(), type_string.end(), '/'),
type_string.end());
} else {
NOTREACHED() << "path is not in expected form: /id/{thumbnail,media}/ vs "
<< path;
return std::string();
}
if (type_string == "thumbnail")
return "image/jpeg";
// TODO(sko) Decide mime type based on the file extension.
if (type_string == "media")
return "video/mp4";
NOTREACHED() << "type is neither of {thumbnail,media}/ : " << type_string;
return std::string();
}
bool PlaylistDataSource::AllowCaching() {
+6 -6
View File
@@ -22,7 +22,7 @@ namespace playlist {
class PlaylistService;
// A URL data source for chrome://playlist-image/<playlist-id>
// A URL data source for chrome://playlist-data/<playlist-id>/{thumbnail,media}/
// resources, for use in webui pages that want to display downloaded
// playlist thumbnail images
class PlaylistDataSource : public content::URLDataSource {
@@ -37,14 +37,14 @@ class PlaylistDataSource : public content::URLDataSource {
void StartDataRequest(const GURL& url,
const content::WebContents::Getter& wc_getter,
GotDataCallback got_data_callback) override;
std::string GetMimeType(const std::string&) override;
std::string GetMimeType(const std::string& path) override;
bool AllowCaching() override;
private:
void GetThumbnailImageFile(const base::FilePath& image_file_path,
GotDataCallback got_data_callback);
void OnGotThumbnailImageFile(GotDataCallback got_data_callback,
scoped_refptr<base::RefCountedMemory> input);
void GetDataFile(const base::FilePath& data_path,
GotDataCallback got_data_callback);
void OnGotDataFile(GotDataCallback got_data_callback,
scoped_refptr<base::RefCountedMemory> input);
raw_ptr<PlaylistService> service_;
@@ -17,6 +17,7 @@
#include "content/public/common/isolated_world_ids.h"
#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
#include "third_party/re2/src/re2/re2.h"
#include "ui/base/page_transition_types.h"
namespace playlist {
@@ -68,6 +69,7 @@ void PlaylistDownloadRequestManager::CreateWebContents() {
// |web_contents_| is created on demand.
content::WebContents::CreateParams create_params(context_, nullptr);
web_contents_ = content::WebContents::Create(create_params);
Observe(web_contents_.get());
}
@@ -20,10 +20,9 @@ PlaylistMediaFileDownloadManager::PlaylistMediaFileDownloadManager(
Delegate* delegate,
const base::FilePath& base_dir)
: base_dir_(base_dir), delegate_(delegate) {
// TODO(pilgrim) dynamically set file extensions based on format
// (may require changes to youtubedown parser)
// TODO(pilgrim) dynamically set file extensions based on format.
media_file_downloader_ = std::make_unique<PlaylistMediaFileDownloader>(
this, context, FILE_PATH_LITERAL("media_file.mp4"));
this, context, kMediaFileName);
}
PlaylistMediaFileDownloadManager::~PlaylistMediaFileDownloadManager() = default;
@@ -108,7 +107,7 @@ void PlaylistMediaFileDownloadManager::OnMediaFileReady(
const std::string& media_file_path) {
VLOG(2) << __func__ << ": " << id << " is ready.";
delegate_->OnMediaFileReady(id, current_item_->media_file_path);
delegate_->OnMediaFileReady(id, media_file_path);
current_item_.reset();
@@ -40,6 +40,9 @@ class PlaylistMediaFileDownloadManager
virtual ~Delegate() {}
};
static constexpr base::FilePath::CharType kMediaFileName[] =
FILE_PATH_LITERAL("media_file.mp4");
PlaylistMediaFileDownloadManager(content::BrowserContext* context,
Delegate* delegate,
const base::FilePath& base_dir);
+21 -1
View File
@@ -85,6 +85,13 @@ void PlaylistService::RequestDownloadMediaFilesFromPage(
download_request_manager_->GetMediaFilesFromPage(url);
}
void PlaylistService::RemoveItemFromPlaylist(const std::string& playlist_id,
const std::string& item_id) {
VLOG(2) << __func__ << " " << playlist_id << " " << item_id;
// TODO(sko) consider |playlist_id|
DeletePlaylistItem(item_id);
}
void PlaylistService::OnPlaylistCreationParamsReady(
const PlaylistItemInfo& params) {
CreatePlaylistItem(params);
@@ -293,7 +300,7 @@ void PlaylistService::RemoveObserver(PlaylistServiceObserver* observer) {
void PlaylistService::OnMediaFileReady(const std::string& id,
const std::string& media_file_path) {
VLOG(2) << __func__ << ": " << id;
VLOG(2) << __func__ << ": " << id << " " << media_file_path;
DCHECK(IsValidPlaylistItem(id));
const base::Value* item_value_ptr =
@@ -369,6 +376,19 @@ bool PlaylistService::GetThumbnailPath(const std::string& id,
return true;
}
bool PlaylistService::GetMediaPath(const std::string& id,
base::FilePath* media_path) {
// TODO(sko) The extension of the media file can differ.
// We should iterate files in the directory and find the match.
*media_path = GetPlaylistItemDirPath(id).Append(
PlaylistMediaFileDownloadManager::kMediaFileName);
if (media_path->ReferencesParent()) {
media_path->clear();
return false;
}
return true;
}
base::SequencedTaskRunner* PlaylistService::task_runner() {
if (!task_runner_) {
task_runner_ = base::ThreadPool::CreateSequencedTaskRunner(
+4
View File
@@ -71,10 +71,14 @@ class PlaylistService : public KeyedService,
void RequestDownloadMediaFilesFromPage(const std::string& playlist_id,
const std::string& url);
void RemoveItemFromPlaylist(const std::string& playlist_id,
const std::string& item_id);
void AddObserver(PlaylistServiceObserver* observer);
void RemoveObserver(PlaylistServiceObserver* observer);
bool GetThumbnailPath(const std::string& id, base::FilePath* thumbnail_path);
bool GetMediaPath(const std::string& id, base::FilePath* media_path);
base::FilePath GetPlaylistItemDirPath(const std::string& id) const;
+26
View File
@@ -0,0 +1,26 @@
# Copyright (c) 2021 The Brave Authors. All rights reserved.
# 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 http://mozilla.org/MPL/2.0/.
import("//brave/components/common/typescript.gni")
transpile_web_ui("playlist_ui") {
entry_points = [ [
"playlist",
rebase_path("playlist.tsx"),
] ]
deps = [
"../mojom:mojom_js",
"//mojo/public/mojom/base",
]
resource_name = "playlist"
}
pack_web_resources("generated_resources") {
resource_name = "playlist"
output_dir = "$root_gen_dir/brave/components/playlist/resources"
deps = [ ":playlist_ui" ]
}
@@ -0,0 +1,13 @@
/* 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 http://mozilla.org/MPL/2.0/. */
import { action } from 'typesafe-actions'
import * as PlaylistMojo from 'gen/brave/components/playlist/mojom/playlist.mojom.m.js'
// Constants
import { types } from '../constants/playlist_types'
export const playlistLoaded = (playlist: PlaylistMojo.Playlist[]) =>
action(types.PLAYLIST_LOADED, playlist)
+66
View File
@@ -0,0 +1,66 @@
/* 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 http://mozilla.org/MPL/2.0/. */
import * as PlaylistMojo from 'gen/brave/components/playlist/mojom/playlist.mojom.m.js'
import { Url } from 'gen/url/mojom/url.mojom.m.js'
type PlaylistEventListener = (event: PlaylistMojo.PlaylistEvent) => void
interface API {
pageCallbackRouter: PlaylistMojo.PageCallbackRouter
pageHandler: PlaylistMojo.PageHandlerRemote
getAllPlaylists: () => Promise<{playlists: PlaylistMojo.Playlist[]}>
getPlaylist: (id: string) => Promise<{playlist?: PlaylistMojo.Playlist}>
addMediaFilesFromPageToPlaylist: (playlistId: string, url: string) => void
removeItemFromPlaylist: (playlistId: string, itemId: string) => void
addEventListener: (listener: PlaylistEventListener) => void
}
let playlistAPIInstance: API
class PlaylistAPIInstance implements API {
pageCallbackRouter = new PlaylistMojo.PageCallbackRouter()
pageHandler = new PlaylistMojo.PageHandlerRemote()
constructor () {
const factory = PlaylistMojo.PageHandlerFactory.getRemote()
factory.createPageHandler(
this.pageCallbackRouter.$.bindNewPipeAndPassRemote(),
this.pageHandler.$.bindNewPipeAndPassReceiver())
}
getAllPlaylists = async function ():
Promise<{playlists: PlaylistMojo.Playlist[]}> {
return this.pageHandler.getAllPlaylists()
}
getPlaylist = async function (id: string):
Promise<{playlist?: PlaylistMojo.Playlist}> {
return this.pageHandler.getPlaylist(id)
}
addEventListener (listener: PlaylistEventListener) {
this.pageCallbackRouter.onEvent.addListener(listener)
}
addMediaFilesFromPageToPlaylist (playlistId: string, url: string) {
let mojoUrl = new Url()
mojoUrl.url = url
this.pageHandler.addMediaFilesFromPageToPlaylist(
playlistId, mojoUrl)
}
removeItemFromPlaylist (playlistId: string, itemId: string) {
this.pageHandler.removeItemFromPlaylist(playlistId, itemId)
}
}
export function getPlaylistAPI (): API {
if (!playlistAPIInstance) {
playlistAPIInstance = new PlaylistAPIInstance()
}
return playlistAPIInstance
}
@@ -0,0 +1,22 @@
/* 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 http://mozilla.org/MPL/2.0/. */
import store from '../store'
import * as PlaylistActions from '../actions/playlist_action_creators'
import { bindActionCreators, Dispatch } from 'redux'
type Action = typeof PlaylistActions
let actions: Action
// returns actions bound to dispatch.
export function getAllActions () {
actions = actions || getActionsForDispatch(store.dispatch.bind(store))
return actions
}
export function getActionsForDispatch (dispatch: Dispatch) {
return bindActionCreators(PlaylistActions, dispatch)
}
@@ -0,0 +1,25 @@
// Copyright (c) 2022 The Brave Authors. All rights reserved.
// 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 http://mozilla.org/MPL/2.0/.
import { getAllActions } from './api/getAllActions'
import { getPlaylistAPI } from './api/api'
async function getInitialData () {
return getPlaylistAPI().getAllPlaylists()
}
export default function wireApiEventsToStore () {
// Get initial data and dispatch to store
getInitialData()
.then((initialData) => {
getAllActions().playlistLoaded(initialData.playlists)
// TODO: Add proper event listeners for changes to playlist
getPlaylistAPI().addEventListener((e) => {
getInitialData().then(data => { getAllActions().playlistLoaded(data.playlists) })
})
})
.catch(e => { console.error('New Tab Page fatal error:', e) })
}
@@ -0,0 +1,179 @@
/* 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 http://mozilla.org/MPL/2.0/. */
import * as React from 'react'
import { bindActionCreators, Dispatch } from 'redux'
import { connect } from 'react-redux'
import { Playlist } from 'components/definitions/playlist'
// Components for playlist
import { CloseCircleOIcon } from 'brave-ui/components/icons'
import Table, { Cell, Row } from 'brave-ui/components/dataTables/table'
import PlaylistItem from './playlistItem'
// Utils
import * as playlistActions from '../actions/playlist_action_creators'
import { getPlaylistAPI } from '../api/api'
import * as PlaylistMojo from 'gen/brave/components/playlist/mojom/playlist.mojom.m.js'
interface Props {
actions: any
playlistData: Playlist.State
}
interface State {
playlist?: PlaylistMojo.Playlist
experimentalUrl: string
}
export class PlaylistPage extends React.Component<Props, State> {
constructor (props: Props) {
super(props)
this.state = { experimentalUrl: '' }
this.onClickDownloadVideo = this.onClickDownloadVideo.bind(this)
}
get actions () {
return this.props.actions
}
getCurrentPlaylist = () => {
// TODO(sko): get current playlist index from store
return this.props.playlistData.lists.at(0)
}
getImgSrc = (itemId: string) => {
return 'chrome://playlist-data/' + itemId + '/thumbnail/'
}
getMediaSrc = (itemId: string) => {
return 'chrome://playlist-data/' + itemId + '/media'
}
get lazyButtonStyle () {
const lazyButtonStyle: any = {
alignItems: 'center',
WebkitAppearance: 'none',
width: '50px',
height: '50px',
display: 'flex',
borderRadius: '4px'
}
return lazyButtonStyle
}
getPlaylistHeader = (): Cell[] => {
return [
{ content: 'INDEX' },
{ content: 'NAME' },
{ content: 'STATUS' },
{ content: 'REMOVE' }
]
}
getPlaylistRows = (playlist?: any): Row[] | undefined => {
if (!playlist || !playlist.items) {
return
}
return playlist.items.map((item: PlaylistMojo.PlaylistItem, index: any): any => {
const cell: Row = {
content: [
{ content: (<div style={{ textAlign: 'center' }}>{index + 1}</div>) },
{
content: (
<PlaylistItem id={item.id} name={item.name} onClick={this.onClickItem.bind(this)}
thumbnailUrl={item.thumbnailPath.url.startsWith('http') ? item.thumbnailPath.url : this.getImgSrc(item.id)}/>
)
},
{ content: (<span>{item.ready ? 'Ready' : 'Downloading'}</span>) },
{ content: (<button style={this.lazyButtonStyle} onClick={this.onClickRemoveItemButton.bind(this, item.id)}><CloseCircleOIcon /></button>) }
]
}
return cell
})
}
onClickRemoveItemButton = (playlistItemId: string) => {
getPlaylistAPI().removeItemFromPlaylist('', playlistItemId)
}
get pageHasDownloadableVideo () {
return this.state.experimentalUrl.startsWith('https://www.youtube.com/watch')
}
onChangeExperimentalUrl = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
this.setState({ experimentalUrl: event.target.value })
}
onClickDownloadVideo = () => {
getPlaylistAPI().addMediaFilesFromPageToPlaylist('', this.state.experimentalUrl)
}
onClickItem = (itemId: string) => {
const item = this.getCurrentPlaylist()?.items.find((item: PlaylistMojo.PlaylistItem) => {
return item.id === itemId
})
if (!item || !item.ready) {
return
}
document.getElementById('player')?.setAttribute('src', this.getMediaSrc(itemId))
}
render () {
return (
<div id='playlistPage'>
<div style={{ minHeight: '600px', width: '1200px' }}>
<Table header={this.getPlaylistHeader()} rows={this.getPlaylistRows(this.getCurrentPlaylist())}>
YOUR PLAYLIST IS EMPTY
</Table>
</div>
<video id="player" controls autoPlay />
<div>
<h1>Experimental</h1>
<div>
<textarea
cols={100}
rows={10}
value={this.state.experimentalUrl}
onChange={this.onChangeExperimentalUrl}
/>
<div>
{
this.pageHasDownloadableVideo
? (
<div>
<h1>This page has a video you can download</h1>
<button onClick={this.onClickDownloadVideo}>Click here to download</button>
</div>
) : (
<h1>Nothing to see here. Put a proper YouTube link to see the magic</h1>
)
}
</div>
</div>
</div>
</div>
)
}
}
export const mapStateToProps = (state: Playlist.ApplicationState) => ({
playlistData: state.playlistData
})
export const mapDispatchToProps = (dispatch: Dispatch) => ({
actions: bindActionCreators(playlistActions, dispatch)
})
export default connect(
mapStateToProps,
mapDispatchToProps
)(PlaylistPage)
@@ -0,0 +1,31 @@
/* 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 http://mozilla.org/MPL/2.0/. */
import * as React from 'react'
interface Props {
id: string
name: string
thumbnailUrl: string
onClick: (id: string) => void
}
export default class PlaylistItem extends React.PureComponent<Props, {}> {
render () {
let { id, name, thumbnailUrl, onClick } = this.props
return (
<div>
<h3>{name}</h3>
<a href='#' onClick={() => { onClick(id) }}>
<img
style={{ maxWidth: '200px' }}
data-id={id}
src={thumbnailUrl}
/>
</a>
</div>
)
}
}
@@ -0,0 +1,9 @@
/* 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 http://mozilla.org/MPL/2.0/. */
export const enum types {
PLAYLIST_LOADED = '@@playlist/PLAYLIST_LOADED',
// TODO(sko) Need more actions for each events
}
@@ -0,0 +1,22 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>Brave Playlist</title>
<link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
<link rel="import" href="chrome://resources/html/cr.html">
<script src="chrome://resources/js/cr.js"></script>
<script src="chrome://resources/js/load_time_data.js"></script>
<script src="chrome://resources/js/util.js"></script>
<script src="chrome://resources/js/i18n_template_no_process.js"></script>
<script src="/playlist.bundle.js"></script>
<script src="/strings.js"></script>
<style>
#root { height: 100%; }
</style>
</head>
<body>
<div id="root"></div>
</body>
</html>
@@ -0,0 +1,43 @@
/* 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 http://mozilla.org/MPL/2.0/. */
import * as React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
// Components
import App from './components/app'
// Theme
import BraveCoreThemeProvider from '../../common/BraveCoreThemeProvider'
import Theme from 'brave-ui/theme/brave-default'
import DarkTheme from 'brave-ui/theme/brave-dark'
import wireApiEventsToStore from './apiEventsToStore'
// Utils
import store from './store'
function initialize () {
new Promise(resolve => chrome.braveTheme.getBraveThemeType(resolve))
.then((themeType: chrome.braveTheme.ThemeType) => {
render(
<Provider store={store}>
<BraveCoreThemeProvider
initialThemeType={themeType}
dark={DarkTheme}
light={Theme}
>
<App />
</BraveCoreThemeProvider>
</Provider>,
document.getElementById('root'))
})
.catch((error) => {
console.error('Problem mounting playlist', error)
})
}
wireApiEventsToStore()
document.addEventListener('DOMContentLoaded', initialize)
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<grit-part>
<if expr="enable_playlist">
<include name="IDR_PLAYLIST_HTML" file="../playlist/resources/playlist.html" type="BINDATA" />
</if>
</grit-part>
@@ -0,0 +1,13 @@
/* 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 http://mozilla.org/MPL/2.0/. */
import { combineReducers } from 'redux'
import { Playlist } from 'components/definitions/playlist'
// Utils
import playlistReducer from './playlist_reducer'
export default combineReducers<Playlist.ApplicationState>(
{ playlistData: playlistReducer })
@@ -0,0 +1,27 @@
/* 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 http://mozilla.org/MPL/2.0/. */
import { Playlist } from 'components/definitions/playlist'
import { Reducer } from 'redux'
import { types } from '../constants/playlist_types'
import * as PlaylistMojo from 'gen/brave/components/playlist/mojom/playlist.mojom.m.js'
const playlistReducer: Reducer<Playlist.State|undefined> =
(state: Playlist.State|undefined, action) => {
if (state === undefined) {
state = { lists: [] }
}
// TODO(sko) Handle each action properly.
switch (action.type) {
case types.PLAYLIST_LOADED:
state = { ...state, lists: [...action.payload as PlaylistMojo.Playlist[]] }
break
}
return state
}
export default playlistReducer
+10
View File
@@ -0,0 +1,10 @@
/* 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 http://mozilla.org/MPL/2.0/. */
import { createStore } from 'redux'
// Utils
import reducers from './reducers'
export default createStore(reducers)
@@ -0,0 +1,9 @@
{
"extends": "../../../tsconfig",
"include": [
"**/*.ts",
"**/*.tsx",
"**/*.d.ts",
"../../definitions/*.d.ts"
]
}
+10 -3
View File
@@ -3,6 +3,7 @@ import("//brave/components/brave_vpn/buildflags/buildflags.gni")
import("//brave/components/crypto_dot_com/browser/buildflags/buildflags.gni")
import("//brave/components/ftx/browser/buildflags/buildflags.gni")
import("//brave/components/ipfs/buildflags/buildflags.gni")
import("//brave/components/playlist/buildflags/buildflags.gni")
import("//brave/components/sidebar/buildflags/buildflags.gni")
import("//brave/components/speedreader/common/buildflags.gni")
import("//brave/components/tor/buildflags/buildflags.gni")
@@ -37,11 +38,12 @@ brave_grit("static_resources") {
}
defines = [
"enable_speedreader=$enable_speedreader",
"enable_ipfs=$enable_ipfs",
"crypto_dot_com_enabled=$crypto_dot_com_enabled",
"enable_ftx=$enable_ftx",
"enable_brave_vpn=$enable_brave_vpn",
"enable_ftx=$enable_ftx",
"enable_ipfs=$enable_ipfs",
"enable_playlist=$enable_playlist",
"enable_speedreader=$enable_speedreader",
]
output_dir = "$root_gen_dir/components"
@@ -112,6 +114,11 @@ repack("resources") {
}
}
if (enable_playlist) {
deps += [ "//brave/components/playlist/resources:generated_resources" ]
sources += [ "$root_gen_dir/brave/components/playlist/resources/playlist_generated.pak" ]
}
output = "$root_gen_dir/components/brave_components_resources.pak"
}
@@ -59,6 +59,7 @@
<include name="IDR_BRAVE_PRIVATE_TAB_TOR_IMG" file="../img/newtab/private-window-tor.svg" type="BINDATA" />
<part file="../brave_perf_predictor/resources/brave_perf_predictor_resources.grdp" />
<part file="../playlist/resources/playlist_resources.grdp" />
<part file="brave_blank_page_resources.grdp" />
<part file="brave_translate_resources.grdp" />
<part file="speedreader_resources.grdp" />
+4
View File
@@ -192,4 +192,8 @@
"META": {"sizes": {"includes": [20]}},
"includes": [59000]
},
"<(SHARED_INTERMEDIATE_DIR)/brave/web-ui-playlist/playlist.grd": {
"META": {"sizes": {"includes": [250]}},
"includes": [59020]
},
}