Enhance link actions in Playlist (#20184)
* Enhance link actions in Playlist * Clicking title on Mini player will open the Playlist folder of current item * Add close button to Mini player so that users can unload playlist * Clicking favicon will open a new tab with original page * Add Context menu labeled 'View original page', which works just as clicking favicon
This commit is contained in:
@@ -1255,6 +1255,9 @@ Or change later at <ph name="SETTINGS_EXTENIONS_LINK">$2<ex>brave://settings/ext
|
||||
<message name="IDS_PLAYLIST_CONTEXT_MENU_REMOVE_FROM_PLAYLIST" desc="Context menu item on Playlist item to remove it from the folder">
|
||||
Remove from playlist
|
||||
</message>
|
||||
<message name="IDS_PLAYLIST_CONTEXT_MENU_VIEW_ORIGINAL_PAGE" desc="Context menu item on Playlist item to open the source page in a new tab">
|
||||
View original page
|
||||
</message>
|
||||
</if> <!-- enable_playlist_webui -->
|
||||
<!--Add new items to the appropriate sections above -->
|
||||
</messages>
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
|
||||
#include "brave/browser/ui/views/side_panel/playlist/playlist_contents_wrapper.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "brave/browser/ui/views/side_panel/playlist/playlist_side_panel_coordinator.h"
|
||||
#include "chrome/browser/picture_in_picture/picture_in_picture_window_manager.h"
|
||||
#include "chrome/browser/ui/exclusive_access/exclusive_access_manager.h"
|
||||
@@ -129,6 +131,19 @@ void PlaylistContentsWrapper::ExitPictureInPicture() {
|
||||
PictureInPictureWindowManager::GetInstance()->ExitPictureInPicture();
|
||||
}
|
||||
|
||||
void PlaylistContentsWrapper::AddNewContents(
|
||||
content::WebContents* source,
|
||||
std::unique_ptr<content::WebContents> new_contents,
|
||||
const GURL& target_url,
|
||||
WindowOpenDisposition disposition,
|
||||
const blink::mojom::WindowFeatures& window_features,
|
||||
bool user_gesture,
|
||||
bool* was_blocked) {
|
||||
static_cast<WebContentsDelegate*>(browser_view_->browser())
|
||||
->AddNewContents(source, std::move(new_contents), target_url, disposition,
|
||||
window_features, user_gesture, was_blocked);
|
||||
}
|
||||
|
||||
bool PlaylistContentsWrapper::IsFullscreenForPlaylist() const {
|
||||
if (auto* fullscreen_tab_helper = FullscreenWithinTabHelper::FromWebContents(
|
||||
const_cast<PlaylistContentsWrapper*>(this)->web_contents())) {
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
#ifndef BRAVE_BROWSER_UI_VIEWS_SIDE_PANEL_PLAYLIST_PLAYLIST_CONTENTS_WRAPPER_H_
|
||||
#define BRAVE_BROWSER_UI_VIEWS_SIDE_PANEL_PLAYLIST_PLAYLIST_CONTENTS_WRAPPER_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "base/scoped_observation.h"
|
||||
#include "brave/browser/ui/webui/playlist_ui.h"
|
||||
#include "chrome/browser/ui/exclusive_access/fullscreen_observer.h"
|
||||
@@ -47,6 +49,14 @@ class PlaylistContentsWrapper
|
||||
content::WebContents* web_contents) override;
|
||||
void ExitPictureInPicture() override;
|
||||
|
||||
void AddNewContents(content::WebContents* source,
|
||||
std::unique_ptr<content::WebContents> new_contents,
|
||||
const GURL& target_url,
|
||||
WindowOpenDisposition disposition,
|
||||
const blink::mojom::WindowFeatures& window_features,
|
||||
bool user_gesture,
|
||||
bool* was_blocked) override;
|
||||
|
||||
// FullscreenObserver:
|
||||
void OnFullscreenStateChanged() override;
|
||||
|
||||
|
||||
@@ -50,6 +50,8 @@ void AddLocalizedStrings(content::WebUIDataSource* source) {
|
||||
IDS_PLAYLIST_CONTEXT_MENU_RENAME_PLAYLIST},
|
||||
{"bravePlaylistContextMenuDeletePlaylist",
|
||||
IDS_PLAYLIST_CONTEXT_MENU_DELETE_PLAYLIST},
|
||||
{"bravePlaylistContextMenuViewOriginalPage",
|
||||
IDS_PLAYLIST_CONTEXT_MENU_VIEW_ORIGINAL_PAGE},
|
||||
};
|
||||
|
||||
for (const auto& [name, id] : kLocalizedStrings) {
|
||||
|
||||
@@ -34,3 +34,5 @@ export const playPreviousItem = () => action(types.PLAYER_PLAY_PREVIOUS_ITEM)
|
||||
export const toggleShuffle = () => action(types.PLAYER_TOGGLE_SHUFFLE)
|
||||
|
||||
export const toggleAutoPlay = () => action(types.PLAYER_TOGGLE_AUTO_PLAY)
|
||||
|
||||
export const unloadPlaylist = () => action(types.UNLOAD_PLAYLIST)
|
||||
|
||||
@@ -30,6 +30,9 @@ export type PlayerMessagePayload =
|
||||
| ({
|
||||
actionType: types.SELECTED_PLAYLIST_UPDATED
|
||||
} & SelectedPlaylistUpdatedPayload)
|
||||
| ({
|
||||
actionType: types.UNLOAD_PLAYLIST
|
||||
})
|
||||
|
||||
export default function postMessageToPlayer (payload: PlayerMessagePayload) {
|
||||
const playerWindow = getPlayerWindow()
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
// 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/.
|
||||
|
||||
import { PlaylistItem } from 'gen/brave/components/playlist/common/mojom/playlist.mojom.m'
|
||||
import {
|
||||
Playlist,
|
||||
PlaylistItem
|
||||
} from 'gen/brave/components/playlist/common/mojom/playlist.mojom.m'
|
||||
import { types } from '../constants/playlist_types'
|
||||
import { PlayerState } from '../reducers/states'
|
||||
export { types as PlaylistTypes } from '../constants/playlist_types'
|
||||
@@ -16,6 +19,14 @@ type LastPlayedPositionChangedPayload = {
|
||||
data: PlaylistItem
|
||||
}
|
||||
|
||||
type GoBackToCurrentFolderPayload = {
|
||||
data: { currentList: Playlist, currentItem: PlaylistItem }
|
||||
}
|
||||
|
||||
type OpenSourcePagePayload = {
|
||||
data: PlaylistItem
|
||||
}
|
||||
|
||||
export type PlayerEventsPayload =
|
||||
| ({
|
||||
type: types.PLAYLIST_PLAYER_STATE_CHANGED
|
||||
@@ -23,6 +34,12 @@ export type PlayerEventsPayload =
|
||||
| ({
|
||||
type: types.PLAYLIST_LAST_PLAYED_POSITION_OF_CURRENT_ITEM_CHANGED
|
||||
} & LastPlayedPositionChangedPayload)
|
||||
| ({
|
||||
type: types.PLAYLIST_GO_BACK_TO_CURRENTLY_PLAYING_FOLDER
|
||||
} & GoBackToCurrentFolderPayload)
|
||||
| ({
|
||||
type: types.PLAYLIST_OPEN_SOURCE_PAGE
|
||||
} & OpenSourcePagePayload)
|
||||
|
||||
export function notifyEventsToTopFrame (playerState: PlayerEventsPayload) {
|
||||
if (location.protocol.startsWith('chrome-untrusted:')) {
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
useLastPlayerState,
|
||||
usePlaylistEditMode
|
||||
} from '../reducers/states'
|
||||
import { useHistorySynchronization } from '../playerEventSink'
|
||||
|
||||
const HeaderWrapper = styled.header<{ isPlaylistPlayerPage: boolean }>`
|
||||
position: sticky;
|
||||
@@ -28,6 +29,8 @@ const HeaderWrapper = styled.header<{ isPlaylistPlayerPage: boolean }>`
|
||||
`
|
||||
|
||||
export default function App () {
|
||||
useHistorySynchronization()
|
||||
|
||||
const lastPlayerState = useLastPlayerState()
|
||||
const editMode = usePlaylistEditMode()
|
||||
|
||||
|
||||
@@ -5,9 +5,12 @@
|
||||
|
||||
import * as React from 'react'
|
||||
import { useSelector } from 'react-redux'
|
||||
import styled from 'styled-components'
|
||||
import styled, { css } from 'styled-components'
|
||||
|
||||
import { PlaylistItem } from 'gen/brave/components/playlist/common/mojom/playlist.mojom.m.js'
|
||||
import {
|
||||
Playlist,
|
||||
PlaylistItem
|
||||
} from 'gen/brave/components/playlist/common/mojom/playlist.mojom.m.js'
|
||||
|
||||
import { color, font, radius, spacing } from '@brave/leo/tokens/css'
|
||||
|
||||
@@ -88,21 +91,31 @@ const FaviconAndTitle = styled.div`
|
||||
gap: ${spacing.m};
|
||||
`
|
||||
|
||||
const StyledFavicon = styled.img`
|
||||
const StyledFavicon = styled.img<{ clickable: boolean }>`
|
||||
padding: 3px;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border-radius: ${radius.s};
|
||||
border: 1px solid rgba(0, 0, 0, 0.05);
|
||||
background: ${color.white};
|
||||
${p =>
|
||||
p.clickable &&
|
||||
css`
|
||||
cursor: pointer;
|
||||
`}
|
||||
`
|
||||
|
||||
const StyledTitle = styled.div`
|
||||
const StyledTitle = styled.div<{ clickable: boolean }>`
|
||||
color: ${color.text.primary};
|
||||
font: ${font.primary.large.semibold};
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
${p =>
|
||||
p.clickable &&
|
||||
css`
|
||||
cursor: pointer;
|
||||
`}
|
||||
`
|
||||
|
||||
const StyledPlayerControls = styled(PlayerControls)`
|
||||
@@ -130,6 +143,9 @@ export default function Player () {
|
||||
const currentItem = useSelector<ApplicationState, PlaylistItem | undefined>(
|
||||
applicationState => applicationState.playerState?.currentItem
|
||||
)
|
||||
const currentList = useSelector<ApplicationState, Playlist | undefined>(
|
||||
applicationState => applicationState.playerState?.currentList
|
||||
)
|
||||
|
||||
const autoPlayEnabled = useAutoPlayEnabled()
|
||||
|
||||
@@ -168,8 +184,29 @@ export default function Player () {
|
||||
currentItem?.id &&
|
||||
`chrome-untrusted://playlist-data/${currentItem.id}/favicon`
|
||||
}
|
||||
clickable={!!currentItem}
|
||||
onClick={() => {
|
||||
if (currentItem) {
|
||||
notifyEventsToTopFrame({
|
||||
type: PlaylistTypes.PLAYLIST_OPEN_SOURCE_PAGE,
|
||||
data: currentItem
|
||||
})
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<StyledTitle>{currentItem?.name}</StyledTitle>
|
||||
<StyledTitle
|
||||
clickable={!!(currentList && currentItem)}
|
||||
onClick={() => {
|
||||
if (currentList && currentItem) {
|
||||
notifyEventsToTopFrame({
|
||||
type: PlaylistTypes.PLAYLIST_GO_BACK_TO_CURRENTLY_PLAYING_FOLDER,
|
||||
data: { currentList, currentItem }
|
||||
})
|
||||
}
|
||||
}}
|
||||
>
|
||||
{currentItem?.name}
|
||||
</StyledTitle>
|
||||
</FaviconAndTitle>
|
||||
<PlayerSeeker videoElement={videoElement} />
|
||||
<StyledPlayerControls videoElement={videoElement} />
|
||||
|
||||
@@ -331,6 +331,14 @@ export function PlaylistItem ({
|
||||
iconName: 'trash',
|
||||
onClick: () =>
|
||||
getPlaylistAPI().removeItemFromPlaylist(playlist.id!, id)
|
||||
},
|
||||
{
|
||||
name: getLocalizedString(
|
||||
'bravePlaylistContextMenuViewOriginalPage'
|
||||
),
|
||||
iconName: 'link-normal',
|
||||
onClick: () =>
|
||||
window.open(item.pageSource.url, '_blank', 'noopener noreferrer')
|
||||
}
|
||||
]}
|
||||
onShowMenu={() => setShowingMenu(true)}
|
||||
|
||||
@@ -6,20 +6,24 @@
|
||||
import * as React from 'react'
|
||||
import styled, { css } from 'styled-components'
|
||||
|
||||
import { color, effect } from '@brave/leo/tokens/css'
|
||||
import { color, effect, spacing } from '@brave/leo/tokens/css'
|
||||
import Button from '@brave/leo/react/button'
|
||||
import Icon from '@brave/leo/react/icon'
|
||||
|
||||
import { playlistControlsAreaHeight } from '../constants/style'
|
||||
import postMessageToPlayer from '../api/playerApi'
|
||||
import { types } from '../constants/player_types'
|
||||
|
||||
interface Props {
|
||||
visible: boolean
|
||||
isMiniPlayer: boolean
|
||||
}
|
||||
|
||||
const StyledVideoFrame = styled.iframe<Props>`
|
||||
${playlistControlsAreaHeight}
|
||||
|
||||
const VideoFrameContainer = styled.div<Props>`
|
||||
position: relative;
|
||||
width: 100vw;
|
||||
border: none;
|
||||
|
||||
${playlistControlsAreaHeight}
|
||||
${p =>
|
||||
p.isMiniPlayer
|
||||
? css`
|
||||
@@ -27,7 +31,6 @@ const StyledVideoFrame = styled.iframe<Props>`
|
||||
bottom: 0;
|
||||
height: var(--player-controls-area-height);
|
||||
z-index: 1;
|
||||
border-top: 1px solid ${color.divider.subtle};
|
||||
`
|
||||
: css`
|
||||
// 16:9 aspect ratio for video and fixed height for the controls area
|
||||
@@ -43,19 +46,54 @@ const StyledVideoFrame = styled.iframe<Props>`
|
||||
`}
|
||||
`
|
||||
|
||||
export default function VideoFrame (props: Props) {
|
||||
const StyledCloseButton = styled(Button)`
|
||||
position: absolute;
|
||||
margin: ${spacing.s};
|
||||
right: 0;
|
||||
`
|
||||
|
||||
function CloseButton () {
|
||||
return (
|
||||
<StyledVideoFrame
|
||||
id='player'
|
||||
src={
|
||||
location.protocol === 'chrome-untrusted:'
|
||||
? 'chrome-untrusted://playlist-player'
|
||||
: 'iframe.html?id=playlist-components--video-player'
|
||||
}
|
||||
allow='autoplay; fullscreen;'
|
||||
scrolling='no'
|
||||
sandbox='allow-scripts allow-same-origin'
|
||||
{...props}
|
||||
/>
|
||||
<StyledCloseButton
|
||||
kind='plain-faint'
|
||||
size='tiny'
|
||||
onClick={() => {
|
||||
postMessageToPlayer({ actionType: types.UNLOAD_PLAYLIST })
|
||||
}}
|
||||
>
|
||||
<Icon name='close'></Icon>
|
||||
</StyledCloseButton>
|
||||
)
|
||||
}
|
||||
|
||||
const StyledVideoFrame = styled.iframe<Pick<Props, 'isMiniPlayer'>>`
|
||||
position: absolute;
|
||||
width: 100vw;
|
||||
height: 100%;
|
||||
border: none;
|
||||
${p =>
|
||||
p.isMiniPlayer &&
|
||||
css`
|
||||
border-top: 1px solid ${color.divider.subtle};
|
||||
`}
|
||||
`
|
||||
|
||||
export default function VideoFrame (props: Props) {
|
||||
return (
|
||||
<VideoFrameContainer {...props}>
|
||||
<StyledVideoFrame
|
||||
id='player'
|
||||
src={
|
||||
location.protocol === 'chrome-untrusted:'
|
||||
? 'chrome-untrusted://playlist-player'
|
||||
: 'iframe.html?id=playlist-components--video-player'
|
||||
}
|
||||
allow='autoplay; fullscreen;'
|
||||
scrolling='no'
|
||||
sandbox='allow-scripts allow-same-origin'
|
||||
isMiniPlayer={props.isMiniPlayer}
|
||||
/>
|
||||
{props.isMiniPlayer && <CloseButton />}
|
||||
</VideoFrameContainer>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -19,5 +19,7 @@ export enum types {
|
||||
|
||||
PLAYER_TOGGLE_AUTO_PLAY = '@@playlist/PLAYER_TOGGLE_AUTO_PLAY',
|
||||
|
||||
SELECTED_PLAYLIST_UPDATED = '@@playlist/SELECTED_PLAYLIST_UPDATED'
|
||||
SELECTED_PLAYLIST_UPDATED = '@@playlist/SELECTED_PLAYLIST_UPDATED',
|
||||
|
||||
UNLOAD_PLAYLIST = '@@playlist/UNLOAD_PLAYLIST'
|
||||
}
|
||||
|
||||
@@ -14,6 +14,10 @@ export const enum types {
|
||||
|
||||
PLAYLIST_SET_EDIT_MODE = '@@playlist/PLAYLIST_EDIT_MODE',
|
||||
|
||||
PLAYLIST_LAST_PLAYED_POSITION_OF_CURRENT_ITEM_CHANGED = '@@playlist/PLAYLIST_LAST_PLAYED_POSITION_CHANGED'
|
||||
PLAYLIST_LAST_PLAYED_POSITION_OF_CURRENT_ITEM_CHANGED = '@@playlist/PLAYLIST_LAST_PLAYED_POSITION_CHANGED',
|
||||
|
||||
PLAYLIST_GO_BACK_TO_CURRENTLY_PLAYING_FOLDER = '@@playlist/PLAYLIST_GO_BACK_TO_CURRENTLY_PLAYING_FOLDER',
|
||||
|
||||
PLAYLIST_OPEN_SOURCE_PAGE= '@@playlist/PLAYLIST_OPEN_SOURCE_PAGE'
|
||||
// TODO(sko) Need more actions for each events
|
||||
}
|
||||
|
||||
@@ -17,6 +17,10 @@ export function handlePlayerMessage (payload: PlayerMessagePayload) {
|
||||
getPlayerActions().selectedPlaylistUpdated(payload)
|
||||
break
|
||||
}
|
||||
case types.UNLOAD_PLAYLIST: {
|
||||
getPlayerActions().unloadPlaylist()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,10 +3,15 @@
|
||||
// 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/.
|
||||
|
||||
import { useHistory } from 'react-router'
|
||||
import { getPlaylistAPI } from './api/api'
|
||||
import { getPlaylistActions } from './api/getPlaylistActions'
|
||||
import { PlayerEventsPayload } from './api/playerEventsNotifier'
|
||||
import { types } from './constants/playlist_types'
|
||||
import { History } from 'history'
|
||||
import { useEffect } from 'react'
|
||||
|
||||
let history: History | undefined
|
||||
|
||||
function handlePlayerEvents (payload: PlayerEventsPayload) {
|
||||
switch (payload.type) {
|
||||
@@ -20,9 +25,33 @@ function handlePlayerEvents (payload: PlayerEventsPayload) {
|
||||
payload.data.lastPlayedPosition
|
||||
)
|
||||
break
|
||||
|
||||
case types.PLAYLIST_GO_BACK_TO_CURRENTLY_PLAYING_FOLDER: {
|
||||
if (history) {
|
||||
const {currentList, currentItem } = payload.data
|
||||
history.push(`/playlist/${currentList.id}#${currentItem.id}`)
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case types.PLAYLIST_OPEN_SOURCE_PAGE: {
|
||||
window.open(
|
||||
payload.data.pageSource.url,
|
||||
'_blank',
|
||||
'noopener,noreferrer'
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function useHistorySynchronization () {
|
||||
const h = useHistory()
|
||||
useEffect(() => {
|
||||
history = h
|
||||
return () => history = undefined
|
||||
}, [h])
|
||||
}
|
||||
|
||||
// Used to mirror state of Player from Playlist side.
|
||||
export default function startReceivingPlayerEvents () {
|
||||
window.onmessage = e => {
|
||||
@@ -34,3 +63,4 @@ export default function startReceivingPlayerEvents () {
|
||||
handlePlayerEvents(e.data)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ const playerReducer: Reducer<PlayerState | undefined> = (
|
||||
state: PlayerState | undefined,
|
||||
action
|
||||
) => {
|
||||
if (state === undefined) {
|
||||
if (state === undefined || action.type === types.UNLOAD_PLAYLIST) {
|
||||
state = {
|
||||
currentList: undefined,
|
||||
itemsInOrder: undefined,
|
||||
|
||||
@@ -16,6 +16,7 @@ export type Message =
|
||||
| 'bravePlaylistContextMenuRemoveFromPlaylist'
|
||||
| 'bravePlaylistContextMenuRenamePlaylist'
|
||||
| 'bravePlaylistContextMenuDeletePlaylist'
|
||||
| 'bravePlaylistContextMenuViewOriginalPage'
|
||||
|
||||
export function getLocalizedString (message: Message) {
|
||||
return getLocale(message)
|
||||
|
||||
Reference in New Issue
Block a user