diff --git a/components/brave_new_tab_ui/actions/new_tab_actions.ts b/components/brave_new_tab_ui/actions/new_tab_actions.ts index b4f601dd7221..835209e43fcf 100644 --- a/components/brave_new_tab_ui/actions/new_tab_actions.ts +++ b/components/brave_new_tab_ui/actions/new_tab_actions.ts @@ -82,8 +82,12 @@ export const onPromotionFinish = (result: NewTab.RewardsResult, promotion: NewTa promotion }) -export const setCurrentStackWidget = (widgetId: NewTab.StackWidget) => action(types.SET_CURRENT_STACK_WIDGET, { - widgetId +export const removeStackWidget = (widget: NewTab.StackWidget) => action(types.REMOVE_STACK_WIDGET, { + widget +}) + +export const setForegroundStackWidget = (widget: NewTab.StackWidget) => action(types.SET_FOREGROUND_STACK_WIDGET, { + widget }) export const onBinanceUserTLD = (userTLD: NewTab.BinanceTLD) => action(types.ON_BINANCE_USER_TLD, { diff --git a/components/brave_new_tab_ui/apiEventsToStore.ts b/components/brave_new_tab_ui/apiEventsToStore.ts index 774fc82a42e6..69fe32e322a8 100644 --- a/components/brave_new_tab_ui/apiEventsToStore.ts +++ b/components/brave_new_tab_ui/apiEventsToStore.ts @@ -72,7 +72,7 @@ function binanceInitData () { getBinanceBlackList() .then(({ isSupportedRegion, onlyAnonWallet }) => { if (onlyAnonWallet || !isSupportedRegion) { - getActions().setCurrentStackWidget('rewards') + getActions().removeStackWidget('binance') } getActions().setOnlyAnonWallet(onlyAnonWallet) getActions().setBinanceSupported(isSupportedRegion && !onlyAnonWallet) diff --git a/components/brave_new_tab_ui/constants/new_tab_types.ts b/components/brave_new_tab_ui/constants/new_tab_types.ts index 3530b7dfd28c..880f70d42944 100644 --- a/components/brave_new_tab_ui/constants/new_tab_types.ts +++ b/components/brave_new_tab_ui/constants/new_tab_types.ts @@ -24,7 +24,6 @@ export const enum types { SET_INITIAL_REWARDS_DATA = '@@newtab/SET_INITIAL_REWARDS_DATA', SET_PRE_INITIAL_REWARDS_DATA = '@@newtab/SET_PRE_INITIAL_REWARDS_DATA', ON_WIDGET_POSITION_CHANGED = '@@newtab/ON_WIDGET_POSITION_CHANGED', - SET_CURRENT_STACK_WIDGET = '@@newtab/SET_CURRENT_STACK_WIDGET', SET_ONLY_ANON_WALLET = '@@newtab/SET_ONLY_ANON_WALLET', // Binance Widget ON_BINANCE_USER_TLD = '@@newtab/ON_BINANCE_USER_TLD', @@ -49,7 +48,9 @@ export const enum types { ON_CONVERTABLE_ASSETS = '@@newtab/ON_CONVERTABLE_ASSETS', SET_DISCONNECT_IN_PROGRESS = '@@newtab/SET_DISCONNECT_IN_PROGRESS', SET_AUTH_INVALID = '@@newtab/SET_AUTH_INVALID', - SET_SELECTED_VIEW = '@@newtab/SET_SELECTED_VIEW' + SET_SELECTED_VIEW = '@@newtab/SET_SELECTED_VIEW', + REMOVE_STACK_WIDGET = '@@newtab/REMOVE_STACK_WIDGET', + SET_FOREGROUND_STACK_WIDGET = '@@newtab/SET_FOREGROUND_STACK_WIDGET' } export type DismissBrandedWallpaperNotificationPayload = { diff --git a/components/brave_new_tab_ui/containers/newTab/index.tsx b/components/brave_new_tab_ui/containers/newTab/index.tsx index de984bc9d21f..0b9e6abf2621 100644 --- a/components/brave_new_tab_ui/containers/newTab/index.tsx +++ b/components/brave_new_tab_ui/containers/newTab/index.tsx @@ -99,13 +99,6 @@ class NewTabPage extends React.Component { if (GetShouldShowBrandedWallpaperNotification(this.props)) { this.trackBrandedWallpaperNotificationAutoDismiss() } - - // Migratory check in the event that rewards is off - // when receiving the upgrade with the Binance widget. - const { showRewards } = this.props.newTabData - if (!showRewards) { - this.props.actions.setCurrentStackWidget('binance') - } } componentDidUpdate (prevProps: Props) { @@ -136,13 +129,13 @@ class NewTabPage extends React.Component { const { showRewards, showBinance } = this.props.newTabData if (!oldShowRewards && showRewards) { - this.props.actions.setCurrentStackWidget('rewards') + this.props.actions.setForegroundStackWidget('rewards') } else if (!oldShowBinance && showBinance) { - this.props.actions.setCurrentStackWidget('binance') + this.props.actions.setForegroundStackWidget('binance') } else if (oldShowRewards && !showRewards) { - this.props.actions.setCurrentStackWidget('binance') + this.props.actions.removeStackWidget('rewards') } else if (oldShowBinance && !showBinance) { - this.props.actions.setCurrentStackWidget('rewards') + this.props.actions.removeStackWidget('binance') } } @@ -207,36 +200,24 @@ class NewTabPage extends React.Component { } toggleShowRewards = () => { - const { - currentStackWidget, - showBinance, - showRewards - } = this.props.newTabData - - if (currentStackWidget === 'rewards' && showRewards) { - this.props.actions.setCurrentStackWidget('binance') - } else if (!showBinance) { - this.props.actions.setCurrentStackWidget('rewards') - } else if (!showRewards) { - this.props.actions.setCurrentStackWidget('rewards') + const { showRewards } = this.props.newTabData + + if (showRewards) { + this.removeStackWidget('rewards') + } else { + this.setForegroundStackWidget('rewards') } this.props.saveShowRewards(!showRewards) } toggleShowBinance = () => { - const { - currentStackWidget, - showBinance, - showRewards - } = this.props.newTabData - - if (currentStackWidget === 'binance' && showBinance) { - this.props.actions.setCurrentStackWidget('rewards') - } else if (!showRewards) { - this.props.actions.setCurrentStackWidget('binance') - } else if (!showBinance) { - this.props.actions.setCurrentStackWidget('binance') + const { showBinance } = this.props.newTabData + + if (showBinance) { + this.removeStackWidget('binance') + } else { + this.setForegroundStackWidget('binance') } this.props.saveShowBinance(!showBinance) @@ -351,8 +332,12 @@ class NewTabPage extends React.Component { this.setState({ showSettingsMenu: !this.state.showSettingsMenu }) } - toggleStackWidget = (widgetId: NewTab.StackWidget) => { - this.props.actions.setCurrentStackWidget(widgetId) + setForegroundStackWidget = (widget: NewTab.StackWidget) => { + this.props.actions.setForegroundStackWidget(widget) + } + + removeStackWidget = (widget: NewTab.StackWidget) => { + this.props.actions.removeStackWidget(widget) } setInitialAmount = (amount: string) => { @@ -481,30 +466,31 @@ class NewTabPage extends React.Component { } getCryptoContent () { - const { currentStackWidget } = this.props.newTabData + const { widgetStackOrder } = this.props.newTabData + const renderLookup = { + 'rewards': this.renderRewardsWidget.bind(this), + 'binance': this.renderBinanceWidget.bind(this) + } return ( <> - { - currentStackWidget === 'rewards' - ? <> - {this.renderBinanceWidget(false)} - {this.renderRewardsWidget(true)} - - : <> - {this.renderRewardsWidget(false)} - {this.renderBinanceWidget(true)} - - } + {widgetStackOrder.map((widget: NewTab.StackWidget, i: number) => { + const isForeground = i === widgetStackOrder.length - 1 + return ( +
+ {renderLookup[widget](isForeground)} +
+ ) + })} ) } renderCryptoContent () { const { newTabData } = this.props - const { showRewards, showBinance } = newTabData + const { widgetStackOrder } = newTabData - if (!showRewards && !showBinance) { + if (!widgetStackOrder.length) { return null } @@ -542,7 +528,7 @@ class NewTabPage extends React.Component { preventFocus={false} hideWidget={this.toggleShowRewards} showContent={showContent} - onShowContent={this.toggleStackWidget.bind(this, 'rewards')} + onShowContent={this.setForegroundStackWidget.bind(this, 'rewards')} onCreateWallet={this.createWallet} onEnableAds={this.enableAds} onEnableRewards={this.enableRewards} @@ -592,7 +578,7 @@ class NewTabPage extends React.Component { onValidAuthCode={this.onValidAuthCode} onBuyCrypto={this.buyCrypto} onBinanceUserTLD={this.onBinanceUserTLD} - onShowContent={this.toggleStackWidget.bind(this, 'binance')} + onShowContent={this.setForegroundStackWidget.bind(this, 'binance')} onSetInitialAmount={this.setInitialAmount} onSetInitialAsset={this.setInitialAsset} onSetInitialFiat={this.setInitialFiat} diff --git a/components/brave_new_tab_ui/reducers/new_tab_reducer.ts b/components/brave_new_tab_ui/reducers/new_tab_reducer.ts index 98e47cf0d996..dc4adb3d094a 100644 --- a/components/brave_new_tab_ui/reducers/new_tab_reducer.ts +++ b/components/brave_new_tab_ui/reducers/new_tab_reducer.ts @@ -75,6 +75,11 @@ export const newTabReducer: Reducer = (state: NewTab.S } } }) + + if (state.currentStackWidget) { + state = storage.migrateStackWidgetSettings(state) + } + break case types.NEW_TAB_STATS_UPDATED: @@ -309,12 +314,42 @@ export const newTabReducer: Reducer = (state: NewTab.S } break - case types.SET_CURRENT_STACK_WIDGET: - const widgetId: NewTab.StackWidget = payload.widgetId + case types.REMOVE_STACK_WIDGET: + const widget: NewTab.StackWidget = payload.widget + let { removedStackWidgets, widgetStackOrder } = state + + if (!widgetStackOrder.length) { + break + } + + widgetStackOrder = widgetStackOrder.filter((curWidget: NewTab.StackWidget) => { + return curWidget !== widget + }) + + if (!removedStackWidgets.includes(widget)) { + removedStackWidgets.push(widget) + } + + state = { + ...state, + removedStackWidgets, + widgetStackOrder + } + break + + case types.SET_FOREGROUND_STACK_WIDGET: + const frontWidget: NewTab.StackWidget = payload.widget + let newWidgetStackOrder = state.widgetStackOrder + + newWidgetStackOrder = newWidgetStackOrder.filter((widget: NewTab.StackWidget) => { + return widget !== frontWidget + }) + + newWidgetStackOrder.push(frontWidget) state = { ...state, - currentStackWidget: widgetId + widgetStackOrder: newWidgetStackOrder } break diff --git a/components/brave_new_tab_ui/storage/new_tab_storage.ts b/components/brave_new_tab_ui/storage/new_tab_storage.ts index ab52a4bfab43..407d8d8e1ab1 100644 --- a/components/brave_new_tab_ui/storage/new_tab_storage.ts +++ b/components/brave_new_tab_ui/storage/new_tab_storage.ts @@ -50,7 +50,10 @@ export const defaultState: NewTab.State = { walletCreateFailed: false, walletCorrupted: false }, - currentStackWidget: 'rewards', + currentStackWidget: '', + removedStackWidgets: [], + // Order is ascending, with last entry being in the foreground + widgetStackOrder: ['binance', 'rewards'], binanceState: { userTLD: 'com', initialFiat: 'USD', @@ -85,6 +88,72 @@ if (chrome.extension.inIncognitoContext) { defaultState.isQwant = window.loadTimeData.getBoolean('isQwant') } +// For users upgrading to the new list based widget stack state, +// a list in the current format will need to be generated based on their +// previous configuration. +const getMigratedWidgetOrder = (state: NewTab.State) => { + const { + showRewards, + showBinance, + currentStackWidget + } = state + + if (!showRewards && !showBinance) { + return { + widgetStackOrder: [], + removedStackWidgets: ['rewards', 'binance'] + } + } + + if (showRewards && !showBinance) { + return { + widgetStackOrder: ['rewards'], + removedStackWidgets: ['binance'] + } + } + + if (!showRewards && showBinance) { + return { + widgetStackOrder: ['binance'], + removedStackWidgets: ['rewards'] + } + } + + const widgetStackOrder = [] + const nonCurrentWidget = currentStackWidget === 'rewards' + ? 'binance' + : 'rewards' + + widgetStackOrder.push(currentStackWidget) + widgetStackOrder.unshift(nonCurrentWidget) + + return { + widgetStackOrder, + removedStackWidgets: [] + } +} + +export const migrateStackWidgetSettings = (state: NewTab.State) => { + // Migrating to the new stack widget data format + const { widgetStackOrder, removedStackWidgets } = getMigratedWidgetOrder(state) + state.widgetStackOrder = widgetStackOrder as NewTab.StackWidget[] + state.removedStackWidgets = removedStackWidgets as NewTab.StackWidget[] + state.currentStackWidget = '' + + // Ensure any new stack widgets introduced are put behind + // the others, and not re-added unecessarily if removed + // at one point. + const defaultWidgets = defaultState.widgetStackOrder + defaultWidgets.map((widget: NewTab.StackWidget) => { + if (!state.widgetStackOrder.includes(widget) && + !state.removedStackWidgets.includes(widget)) { + state.widgetStackOrder.unshift(widget) + } + }) + + return state +} + const cleanData = (state: NewTab.State) => { // We need to disable linter as we defined in d.ts that this values are number, // but we need this check to covert from old version to a new one @@ -127,7 +196,8 @@ export const debouncedSave = debounce((data: NewTab.State) => { showEmptyPage: data.showEmptyPage, rewardsState: data.rewardsState, binanceState: data.binanceState, - currentStackWidget: data.currentStackWidget + removedStackWidgets: data.removedStackWidgets, + widgetStackOrder: data.widgetStackOrder } window.localStorage.setItem(keyName, JSON.stringify(dataToSave)) } diff --git a/components/definitions/newTab.d.ts b/components/definitions/newTab.d.ts index 7ed3fecc2a66..949b109922b1 100644 --- a/components/definitions/newTab.d.ts +++ b/components/definitions/newTab.d.ts @@ -72,7 +72,7 @@ declare namespace NewTab { url: string } - export type StackWidget = 'rewards' | 'binance' + export type StackWidget = 'rewards' | 'binance' | '' export interface LegacyState { pinnedTopSites: Site[] @@ -92,13 +92,14 @@ declare namespace NewTab { export interface RewardsState { rewardsState: RewardsWidgetState - currentStackWidget: StackWidget } export interface PersistentState { showEmptyPage: boolean rewardsState: RewardsWidgetState currentStackWidget: StackWidget + removedStackWidgets: StackWidget[] + widgetStackOrder: StackWidget[] binanceState: BinanceWidgetState } diff --git a/components/test/brave_new_tab_ui/reducers/new_tab_reducer_test.ts b/components/test/brave_new_tab_ui/reducers/new_tab_reducer_test.ts index c7bc5d584bae..7f2f0d04eaa1 100644 --- a/components/test/brave_new_tab_ui/reducers/new_tab_reducer_test.ts +++ b/components/test/brave_new_tab_ui/reducers/new_tab_reducer_test.ts @@ -7,6 +7,7 @@ import newTabReducer from '../../../brave_new_tab_ui/reducers/new_tab_reducer' // API import * as storage from '../../../brave_new_tab_ui/storage/new_tab_storage' +import { types } from '../../../brave_new_tab_ui/constants/new_tab_types' describe('newTabReducer', () => { @@ -86,4 +87,113 @@ describe('newTabReducer', () => { // TODO }) }) + describe('widget stack state maintenance', () => { + describe('REMOVE_STACK_WIDGET', () => { + it('does not modify an empty stack', () => { + const assertion = newTabReducer({ + ...storage.defaultState, + widgetStackOrder: [] + }, { + type: types.REMOVE_STACK_WIDGET, + payload: { + widget: 'rewards' + } + }) + const expectedState = { + ...storage.defaultState, + widgetStackOrder: [] + } + expect(assertion).toEqual(expectedState) + }) + + it('removes widget and updates removed widgets', () => { + const assertion = newTabReducer({ + ...storage.defaultState, + widgetStackOrder: ['binance', 'rewards'] + }, { + type: types.REMOVE_STACK_WIDGET, + payload: { + widget: 'rewards' + } + }) + const expectedState = { + ...storage.defaultState, + widgetStackOrder: ['binance'], + removedStackWidgets: ['rewards'] + } + expect(assertion).toEqual(expectedState) + }) + + it('removes widget and does not re-update removed widgets', () => { + const assertion = newTabReducer({ + ...storage.defaultState, + widgetStackOrder: ['rewards', 'binance'], + removedStackWidgets: ['rewards'] + }, { + type: types.REMOVE_STACK_WIDGET, + payload: { + widget: 'rewards' + } + }) + const expectedState = { + ...storage.defaultState, + widgetStackOrder: ['binance'], + removedStackWidgets: ['rewards'] + } + expect(assertion).toEqual(expectedState) + }) + }) + describe('SET_FOREGROUND_STACK_WIDGET', () => { + it('adds widget if it is not in the stack and sets it to the foreground', () => { + const assertion = newTabReducer({ + ...storage.defaultState, + widgetStackOrder: ['rewards'] + }, { + type: types.SET_FOREGROUND_STACK_WIDGET, + payload: { + widget: 'binance' + } + }) + const expectedState = { + ...storage.defaultState, + widgetStackOrder: ['rewards', 'binance'] + } + expect(assertion).toEqual(expectedState) + }) + + it('sets a widget to the foreground if it is in the stack', () => { + const assertion = newTabReducer({ + ...storage.defaultState, + widgetStackOrder: ['binance', 'rewards'] + }, { + type: types.SET_FOREGROUND_STACK_WIDGET, + payload: { + widget: 'binance' + } + }) + const expectedState = { + ...storage.defaultState, + widgetStackOrder: ['rewards', 'binance'] + } + expect(assertion).toEqual(expectedState) + }) + + it('does not re-add a widget', () => { + const assertion = newTabReducer({ + ...storage.defaultState, + widgetStackOrder: ['binance', 'rewards'] + }, { + type: types.SET_FOREGROUND_STACK_WIDGET, + payload: { + widget: 'rewards' + } + }) + const expectedState = { + ...storage.defaultState, + widgetStackOrder: ['binance', 'rewards'] + } + expect(assertion).toEqual(expectedState) + }) + }) + }) }) diff --git a/components/test/brave_new_tab_ui/storage/new_tab_storage_test.ts b/components/test/brave_new_tab_ui/storage/new_tab_storage_test.ts new file mode 100644 index 000000000000..1eb67c8a4a77 --- /dev/null +++ b/components/test/brave_new_tab_ui/storage/new_tab_storage_test.ts @@ -0,0 +1,94 @@ +/* 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 { defaultState, migrateStackWidgetSettings } from '../../../brave_new_tab_ui/storage/new_tab_storage' + +describe('new tab storage', () => { + describe('cleanData', () => { + it('migrates users who had all stack widgets hidden', () => { + // showRewards and showBinance are false in default state + // currentStackWidget will still be set in the event both + // are hidden, as that did not drive visibility previously + const initialState = { + ...defaultState, + currentStackWidget: 'rewards' + } + const expectedState = { + ...defaultState, + currentStackWidget: '', + widgetStackOrder: [], + removedStackWidgets: ['rewards', 'binance'] + } + expect(migrateStackWidgetSettings(initialState)).toEqual(expectedState) + }) + }) + + it('migrates users who were showing rewards and hiding binance', () => { + const initialState = { + ...defaultState, + currentStackWidget: 'rewards', + showRewards: true + } + const expectedState = { + ...defaultState, + showRewards: true, + currentStackWidget: '', + widgetStackOrder: ['rewards'], + removedStackWidgets: ['binance'] + } + expect(migrateStackWidgetSettings(initialState)).toEqual(expectedState) + }) + + it('migrates users who were showing binance and hiding rewards', () => { + const initialState = { + ...defaultState, + currentStackWidget: 'binance', + showBinance: true + } + const expectedState = { + ...defaultState, + showBinance: true, + currentStackWidget: '', + widgetStackOrder: ['binance'], + removedStackWidgets: ['rewards'] + } + expect(migrateStackWidgetSettings(initialState)).toEqual(expectedState) + }) + + it('preserves order and migrates users who were showing both widgets (Binance foreground)', () => { + const initialState = { + ...defaultState, + currentStackWidget: 'binance', + showBinance: true, + showRewards: true + } + const expectedState = { + ...defaultState, + showBinance: true, + showRewards: true, + currentStackWidget: '', + widgetStackOrder: ['rewards', 'binance'], + removedStackWidgets: [] + } + expect(migrateStackWidgetSettings(initialState)).toEqual(expectedState) + }) + + it('preserves order and migrates users who were showing both widgets (Rewards foreground)', () => { + const initialState = { + ...defaultState, + currentStackWidget: 'rewards', + showBinance: true, + showRewards: true + } + const expectedState = { + ...defaultState, + showBinance: true, + showRewards: true, + currentStackWidget: '', + widgetStackOrder: ['binance', 'rewards'], + removedStackWidgets: [] + } + expect(migrateStackWidgetSettings(initialState)).toEqual(expectedState) + }) +})