From 3cee927674579fd4b21149fffefeb6e1d9fa9cf5 Mon Sep 17 00:00:00 2001 From: Michel Weststrate Date: Tue, 5 Nov 2019 09:13:31 -0800 Subject: [PATCH] Introduce favorite plugins Summary: This diff lands improved sidebar navigation. The old functionality to order plugins based on last-recently-used, and cropping at 5 items has been removed. Instead, items can be starred and their position will be fixed. Together with the app switcher introduced this should lead to a cleaner, stabler, and more customizable UI. Reviewed By: jknoxville Differential Revision: D18299401 fbshipit-source-id: 29b7eb3a4130933c637f7c81834558bf738d5bf0 --- src/Client.tsx | 32 --- src/chrome/ErrorBar.tsx | 10 +- src/chrome/MainSidebar.tsx | 424 ++++++++++++++++++------------- src/reducers/connections.tsx | 99 +++----- src/reducers/index.tsx | 2 +- src/ui/components/Glyph.tsx | 9 +- src/ui/components/StarButton.tsx | 62 +++++ src/ui/index.tsx | 1 + src/utils/icons.js | 12 +- 9 files changed, 373 insertions(+), 278 deletions(-) create mode 100644 src/ui/components/StarButton.tsx diff --git a/src/Client.tsx b/src/Client.tsx index a1402f14c..10b3ff609 100644 --- a/src/Client.tsx +++ b/src/Client.tsx @@ -22,7 +22,6 @@ import {registerPlugins} from './reducers/plugins'; import createTableNativePlugin from './plugins/TableNativePlugin'; import EventEmitter from 'events'; import invariant from 'invariant'; -import {Responder} from 'rsocket-types/ReactiveSocketTypes'; type Plugins = Array; @@ -98,11 +97,6 @@ const handleError = ( } }; -export const MAX_MINIMUM_PLUGINS = 5; -export const SHOW_REMAINING_PLUGIN_IF_LESS_THAN = 3; -export const SAVED_PLUGINS_COUNT = - MAX_MINIMUM_PLUGINS + SHOW_REMAINING_PLUGIN_IF_LESS_THAN; - export default class Client extends EventEmitter { app: App | undefined; connected: boolean; @@ -111,8 +105,6 @@ export default class Client extends EventEmitter { sdkVersion: number; messageIdCounter: number; plugins: Plugins; - lessPlugins: Plugins | undefined; - showAllPlugins: boolean; connection: RSocketClientSocket | null | undefined; store: Store; activePlugins: Set; @@ -146,7 +138,6 @@ export default class Client extends EventEmitter { super(); this.connected = true; this.plugins = plugins ? plugins : []; - this.showAllPlugins = false; this.connection = conn; this.id = id; this.query = query; @@ -188,29 +179,6 @@ export default class Client extends EventEmitter { } } - /// Sort plugins by LRU order stored in lessPlugins; if not, sort by alphabet - byClientLRU( - pluginsCount: number, - a: typeof FlipperPlugin, - b: typeof FlipperPlugin, - ): number { - // Sanity check - if (this.lessPlugins != null) { - const showPluginsCount = - pluginsCount >= MAX_MINIMUM_PLUGINS + SHOW_REMAINING_PLUGIN_IF_LESS_THAN - ? MAX_MINIMUM_PLUGINS - : pluginsCount; - let idxA = this.lessPlugins.indexOf(a.id); - idxA = idxA < 0 || idxA >= showPluginsCount ? showPluginsCount : idxA; - let idxB = this.lessPlugins.indexOf(b.id); - idxB = idxB < 0 || idxB >= showPluginsCount ? showPluginsCount : idxB; - if (idxA !== idxB) { - return idxA > idxB ? 1 : -1; - } - } - return (a.title || a.id) > (b.title || b.id) ? 1 : -1; - } - /* All clients should have a corresponding Device in the store. However, clients can connect before a device is registered, so wait a while for the device to be registered if it isn't already. */ diff --git a/src/chrome/ErrorBar.tsx b/src/chrome/ErrorBar.tsx index 69c79799c..35b74c5e9 100644 --- a/src/chrome/ErrorBar.tsx +++ b/src/chrome/ErrorBar.tsx @@ -7,7 +7,7 @@ * @format */ -import {styled, colors} from 'flipper'; +import {styled, colors, Glyph} from 'flipper'; import React, {useState, memo} from 'react'; import {connect} from 'react-redux'; import {FlipperError, dismissError} from '../reducers/connections'; @@ -51,7 +51,13 @@ const ErrorBar = memo(function ErrorBar(props: Props) { setCollapsed(c => !c)} title="Show / hide errors"> - {collapsed ? `▼ ${errorCount}` : '▲'} + + {collapsed && errorCount} ); diff --git a/src/chrome/MainSidebar.tsx b/src/chrome/MainSidebar.tsx index eb14a3f9c..18cc14e1e 100644 --- a/src/chrome/MainSidebar.tsx +++ b/src/chrome/MainSidebar.tsx @@ -29,12 +29,13 @@ import { FlipperDevicePlugin, LoadingIndicator, Button, + StarButton, } from 'flipper'; import React, {Component, PureComponent, Fragment} from 'react'; import NotificationsHub from '../NotificationsHub'; import { selectPlugin, - showMoreOrLessPlugins, + starPlugin, StaticView, setStaticView, } from '../reducers/connections'; @@ -42,13 +43,12 @@ import {setActiveSheet} from '../reducers/application'; import UserAccount from './UserAccount'; import {connect} from 'react-redux'; import {BackgroundColorProperty} from 'csstype'; -import { - MAX_MINIMUM_PLUGINS, - SHOW_REMAINING_PLUGIN_IF_LESS_THAN, -} from '../Client'; import {StyledOtherComponent} from 'create-emotion-styled'; import SupportRequestFormManager from '../fb-stubs/SupportRequestFormManager'; +type FlipperPlugins = (typeof FlipperPlugin)[]; +type PluginsByCategory = [string, FlipperPlugins][]; + const ListItem = styled('div')(({active}: {active?: boolean}) => ({ paddingLeft: 10, display: 'flex', @@ -64,19 +64,18 @@ const ListItem = styled('div')(({active}: {active?: boolean}) => ({ }, })); -const SidebarHeader = styled(FlexBox)({ - display: 'block', - alignItems: 'center', - padding: 3, - color: colors.macOSSidebarSectionTitle, - fontSize: 11, - fontWeight: 500, - marginLeft: 7, - textOverflow: 'ellipsis', +const SidebarButton = styled(Button)(({small}: {small?: boolean}) => ({ + fontWeight: 'bold', + fontSize: small ? 11 : 14, + width: '100%', overflow: 'hidden', - whiteSpace: 'nowrap', - flexShrink: 0, -}); + marginTop: small ? 0 : 20, + pointer: 'cursor', + border: 'none', + background: 'none', + padding: 0, + justifyContent: 'left', +})); const PluginShape = styled(FlexBox)( ({backgroundColor}: {backgroundColor?: BackgroundColorProperty}) => ({ @@ -130,23 +129,6 @@ const Plugins = styled(FlexColumn)({ overflow: 'auto', }); -const PluginDebugger = styled(FlexBox)({ - color: colors.blackAlpha50, - alignItems: 'center', - padding: 10, - flexShrink: 0, - whiteSpace: 'nowrap', - overflow: 'hidden', - textOverflow: 'ellipsis', -}); - -const PluginShowMoreOrLess = styled(ListItem)({ - color: colors.blue, - fontSize: 10, - lineHeight: '10px', - paddingBottom: 5, -}); - function PluginIcon({ isActive, backgroundColor, @@ -170,9 +152,13 @@ class PluginSidebarListItem extends Component<{ isActive: boolean; plugin: typeof FlipperBasePlugin; app?: string | null | undefined; + helpRef?: any; + provided?: any; + onFavorite?: () => void; + starred?: boolean; }> { render() { - const {isActive, plugin} = this.props; + const {isActive, plugin, onFavorite, starred} = this.props; const app = this.props.app || 'Facebook'; let iconColor: string | undefined = (brandColors as any)[app]; @@ -201,6 +187,9 @@ class PluginSidebarListItem extends Component<{ color={colors.white} /> {plugin.title || plugin.id} + {starred !== undefined && ( + + )} ); } @@ -230,6 +219,7 @@ type StateFromProps = { staticView: StaticView; selectedPlugin: string | null | undefined; selectedApp: string | null | undefined; + userStarredPlugins: Store['connections']['userStarredPlugins']; clients: Array; uninitializedClients: Array<{ client: UninitializedClient; @@ -248,16 +238,21 @@ type DispatchFromProps = { }) => void; setActiveSheet: (activeSheet: ActiveSheet) => void; setStaticView: (payload: StaticView) => void; - showMoreOrLessPlugins: (payload: string) => void; + starPlugin: typeof starPlugin; }; type Props = OwnProps & StateFromProps & DispatchFromProps; -type State = {showSupportForm: boolean; selectedClientIndex: number}; +type State = { + showSupportForm: boolean; + selectedClientIndex: number; + showAllPlugins: boolean; +}; class MainSidebar extends PureComponent { state: State = { showSupportForm: GK.get('flipper_support_requests'), // Not to be confused with selectedApp prop, this one only used to remember the client drowdown selector selectedClientIndex: 0, + showAllPlugins: false, }; static getDerivedStateFromProps(props: Props, state: State) { if ( @@ -297,11 +292,6 @@ class MainSidebar extends PureComponent { const client: Client | null = clients[this.state.selectedClientIndex] || null; - const byPluginNameOrId = ( - a: typeof FlipperBasePlugin, - b: typeof FlipperBasePlugin, - ) => ((a.title || a.id) > (b.title || b.id) ? 1 : -1); - return ( { process.platform === 'darwin' && windowIsFocused ? 'transparent' : '' }> - {!GK.get('flipper_disable_notifications') && ( - - selectPlugin({ - selectedPlugin: 'notifications', - selectedApp: null, - deepLinkPayload: null, - }) - }> - 0 - ? NotificationsHub.icon || 'bell' - : 'bell-null' - } - isActive={selectedPlugin === NotificationsHub.id} - /> - - {NotificationsHub.title} - - - )} - {this.state.showSupportForm && ( - setStaticView(SupportRequestFormManager)}> - - - Litho Support Request - - - )} {selectedDevice && ( - {selectedDevice.title} + + {selectedDevice.title} + )} {selectedDevice && Array.from(this.props.devicePlugins.values()) .filter(plugin => plugin.supportsDevice(selectedDevice)) - .sort(byPluginNameOrId) + .sort(sortPluginsByName) .map((plugin: typeof FlipperDevicePlugin) => ( { plugin={plugin} /> ))} - - + }))}> + {clients.length === 0 ? ( + '(Not connected to app)' + ) : this.state.selectedClientIndex >= clients.length ? ( + '(Select app)' + ) : ( + <> + {client.query.app} + {clients.length > 1 && ( + + )} + + )} + {this.renderClientPlugins(client)} {uninitializedClients.map(entry => ( - - {entry.client.appName} + + {entry.client.appName} {entry.errorMessage ? ( ) : ( )} - + ))} - + selectPlugin({ + selectedPlugin: 'notifications', + selectedApp: null, + deepLinkPayload: null, + }) + } + style={{ + borderTop: `1px solid ${colors.blackAlpha10}`, + }}> + 0 + ? NotificationsHub.icon || 'bell' + : 'bell-null' + } + isActive={selectedPlugin === NotificationsHub.id} + /> + + {NotificationsHub.title} + + + )} + {this.state.showSupportForm && ( + setStaticView(SupportRequestFormManager)}> + + + Litho Support Request + + + )} + this.props.setActiveSheet(ACTIVE_SHEET_PLUGINS)}> - -  Manage Plugins... - + Manage Plugins + {config.showLogin && } ); } + renderPluginsByCategory( + client: Client, + plugins: FlipperPlugins, + starred: boolean, + onFavorite: (pluginId: string) => void, + ) { + const {selectedPlugin, selectedApp, selectPlugin} = this.props; + return groupPluginsByCategory(plugins).map(([category, plugins]) => ( + + {category && ( + + {category} + + )} + {plugins.map(plugin => ( + + selectPlugin({ + selectedPlugin: plugin.id, + selectedApp: client.id, + deepLinkPayload: null, + }) + } + plugin={plugin} + app={client.query.app} + onFavorite={() => onFavorite(plugin.id)} + starred={starred} + /> + ))} + + )); + } + renderClientPlugins(client: Client | null) { if (!client) { return null; } - const {selectedPlugin, selectedApp, selectPlugin} = this.props; - const plugins = Array.from(this.props.clientPlugins.values()).filter( - (p: typeof FlipperPlugin) => client.plugins.indexOf(p.id) > -1, + const onFavorite = (plugin: string) => { + this.props.starPlugin({ + selectedApp: client.id, + selectedPlugin: plugin, + }); + }; + const allPlugins = Array.from(this.props.clientPlugins.values()); + const favoritePlugins: FlipperPlugins = getFavoritePlugins( + client, + allPlugins, + this.props.userStarredPlugins, + true, ); - - const minShowPluginsCount = - plugins.length < MAX_MINIMUM_PLUGINS + SHOW_REMAINING_PLUGIN_IF_LESS_THAN - ? plugins.length - : MAX_MINIMUM_PLUGINS; - return ( - - {groupPluginsByCategory( - plugins - .sort((a: typeof FlipperPlugin, b: typeof FlipperPlugin) => - client.byClientLRU(plugins.length, a, b), - ) - .slice( - 0, - client.showAllPlugins - ? client.plugins.length - : minShowPluginsCount, - ), - ).map(([category, plugins]) => ( - - {category && ( - - {category} - + <> + {favoritePlugins.length === 0 ? ( + +
+ Star some plugins! +
+
+
+ ) : ( + <> + {this.renderPluginsByCategory( + client, + favoritePlugins, + true, + onFavorite, )} - {plugins.map(plugin => ( - + - selectPlugin({ - selectedPlugin: plugin.id, - selectedApp: client.id, - deepLinkPayload: null, - }) - } - plugin={plugin} - app={client.query.app} - /> - ))} -
- ))} - {plugins.length > minShowPluginsCount && ( - this.props.showMoreOrLessPlugins(client.id)}> - {client.showAllPlugins ? 'Show less' : 'Show more'} - + this.setState(state => ({ + ...state, + showAllPlugins: !state.showAllPlugins, + })) + }> + {this.state.showAllPlugins ? 'Show less' : 'Show more'} + + + + )} -
+
+ {this.state.showAllPlugins || favoritePlugins.length === 0 + ? this.renderPluginsByCategory( + client, + getFavoritePlugins( + client, + allPlugins, + this.props.userStarredPlugins, + false, + ), + false, + onFavorite, + ) + : null} +
+ ); } } -type PluginsByCategory = [string, (typeof FlipperPlugin)[]][]; +function getFavoritePlugins( + client: Client, + allPlugins: FlipperPlugins, + userStarredPlugins: Props['userStarredPlugins'], + favorite: boolean, +): FlipperPlugins { + const appName = client.id; + return allPlugins.filter(plugin => { + const idx = userStarredPlugins[appName] + ? userStarredPlugins[appName].indexOf(plugin.id) + : -1; + return idx === -1 ? !favorite : favorite; + }); +} -function groupPluginsByCategory( - plugins: (typeof FlipperPlugin)[], -): PluginsByCategory { - // Pre condition: plugins are already sorted globally - const byCategory: {[cat: string]: (typeof FlipperPlugin)[]} = {}; +function groupPluginsByCategory(plugins: FlipperPlugins): PluginsByCategory { + const sortedPlugins = plugins.slice().sort(sortPluginsByName); + const byCategory: {[cat: string]: FlipperPlugins} = {}; const res: PluginsByCategory = []; - plugins.forEach(plugin => { + sortedPlugins.forEach(plugin => { const category = plugin.category || ''; (byCategory[category] || (byCategory[category] = [])).push(plugin); }); @@ -512,6 +577,13 @@ function groupPluginsByCategory( return res; } +function sortPluginsByName( + a: typeof FlipperBasePlugin, + b: typeof FlipperBasePlugin, +): number { + return (a.title || a.id) > (b.title || b.id) ? 1 : -1; +} + export default connect( ({ application: {windowIsFocused}, @@ -519,6 +591,7 @@ export default connect( selectedDevice, selectedPlugin, selectedApp, + userStarredPlugins, clients, uninitializedClients, staticView, @@ -537,6 +610,7 @@ export default connect( staticView, selectedPlugin, selectedApp, + userStarredPlugins, clients, uninitializedClients, devicePlugins, @@ -546,6 +620,6 @@ export default connect( selectPlugin, setStaticView, setActiveSheet, - showMoreOrLessPlugins, + starPlugin, }, )(MainSidebar); diff --git a/src/reducers/connections.tsx b/src/reducers/connections.tsx index a74193137..9f1f8db8f 100644 --- a/src/reducers/connections.tsx +++ b/src/reducers/connections.tsx @@ -14,7 +14,6 @@ import {UninitializedClient} from '../UninitializedClient'; import {isEqual} from 'lodash'; import iosUtil from '../fb-stubs/iOSContainerUtility'; import {performance} from 'perf_hooks'; -import {SAVED_PLUGINS_COUNT} from '../Client'; import isHeadless from '../utils/isHeadless'; import {Actions} from '.'; const WelcomeScreen = isHeadless() @@ -43,7 +42,7 @@ export type State = { userPreferredDevice: null | string; userPreferredPlugin: null | string; userPreferredApp: null | string; - userLRUPlugins: {[key: string]: Array}; + userStarredPlugins: {[key: string]: Array}; errors: FlipperError[]; clients: Array; uninitializedClients: Array<{ @@ -116,11 +115,6 @@ export type Action = type: 'CLIENT_SETUP_ERROR'; payload: {client: UninitializedClient; error: FlipperError}; } - | { - type: 'CLIENT_SHOW_MORE_OR_LESS'; - payload: string; - } - | {type: 'CLEAR_LRU_PLUGINS_HISTORY'} | { type: 'SET_STATIC_VIEW'; payload: StaticView; @@ -128,6 +122,13 @@ export type Action = | { type: 'DISMISS_ERROR'; payload: number; + } + | { + type: 'STAR_PLUGIN'; + payload: { + selectedPlugin: string; + selectedApp: string; + }; }; const DEFAULT_PLUGIN = 'DeviceLogs'; @@ -141,7 +142,7 @@ const INITAL_STATE: State = { userPreferredDevice: null, userPreferredPlugin: null, userPreferredApp: null, - userLRUPlugins: {}, + userStarredPlugins: {}, errors: [], clients: [], uninitializedClients: [], @@ -259,45 +260,43 @@ const reducer = (state: State = INITAL_STATE, action: Actions): State => { } const userPreferredApp = selectedApp || state.userPreferredApp; - const selectedAppName = extractAppNameFromAppId(userPreferredApp); - // Need to recreate an array to make sure that it doesn't refer to the - // array that is showed in on the screen and the array that is kept for - // least recently used plugins reference - const LRUPlugins = [ - ...((selectedAppName && state.userLRUPlugins[selectedAppName]) || []), - ]; - const idxLRU = - (selectedPlugin && LRUPlugins.indexOf(selectedPlugin)) || -1; - if (idxLRU >= 0) { - LRUPlugins.splice(idxLRU, 1); - } - selectedPlugin && LRUPlugins.unshift(selectedPlugin); - LRUPlugins.splice(SAVED_PLUGINS_COUNT); return { ...state, ...payload, staticView: null, userPreferredApp: userPreferredApp, userPreferredPlugin: selectedPlugin, - userLRUPlugins: selectedAppName - ? { - ...state.userLRUPlugins, - [selectedAppName]: LRUPlugins, - } - : {...state.userLRUPlugins}, }; } + case 'STAR_PLUGIN': { + const {selectedPlugin, selectedApp} = action.payload; + const starredPluginsForApp = [ + ...(state.userStarredPlugins[selectedApp] || []), + ]; + const idx = starredPluginsForApp.indexOf(selectedPlugin); + if (idx === -1) { + starredPluginsForApp.push(selectedPlugin); + } else { + starredPluginsForApp.splice(idx, 1); + } + return { + ...state, + userStarredPlugins: { + ...state.userStarredPlugins, + [selectedApp]: starredPluginsForApp, + }, + }; + } + case 'SELECT_USER_PREFERRED_PLUGIN': { const {payload} = action; return {...state, userPreferredPlugin: payload}; } case 'NEW_CLIENT': { const {payload} = action; - const {userPreferredApp, userPreferredPlugin, userLRUPlugins} = state; + const {userPreferredApp, userPreferredPlugin} = state; let {selectedApp, selectedPlugin} = state; - const appName = extractAppNameFromAppId(payload.id); - payload.lessPlugins = (appName && userLRUPlugins[appName]) || []; if ( userPreferredApp && userPreferredPlugin && @@ -317,10 +316,6 @@ const reducer = (state: State = INITAL_STATE, action: Actions): State => { c.client.appName !== payload.query.app ); }), - userLRUPlugins: { - ...state.userLRUPlugins, - [payload.id]: payload.lessPlugins, - }, selectedApp, selectedPlugin, }; @@ -420,33 +415,6 @@ const reducer = (state: State = INITAL_STATE, action: Actions): State => { }), }; } - case 'CLIENT_SHOW_MORE_OR_LESS': { - const {payload} = action; - const appName = extractAppNameFromAppId(payload); - - return { - ...state, - clients: state.clients.map((client: Client) => { - if (appName && extractAppNameFromAppId(client.id) === appName) { - client.showAllPlugins = !client.showAllPlugins; - client.lessPlugins = state.userLRUPlugins[appName] || []; - } - return client; - }), - }; - } - case 'CLEAR_LRU_PLUGINS_HISTORY': { - const clearLRUPlugins: {[key: string]: Array} = {}; - Object.keys(state.userLRUPlugins).forEach((key: string) => { - if (key !== null) { - clearLRUPlugins[key] = []; - } - }); - return { - ...state, - userLRUPlugins: clearLRUPlugins, - }; - } case 'DISMISS_ERROR': { const errors = state.errors.slice(); errors.splice(action.payload, 1); @@ -531,8 +499,11 @@ export const selectPlugin = (payload: { payload, }); -export const showMoreOrLessPlugins = (payload: string): Action => ({ - type: 'CLIENT_SHOW_MORE_OR_LESS', +export const starPlugin = (payload: { + selectedPlugin: string; + selectedApp: string; +}): Action => ({ + type: 'STAR_PLUGIN', payload, }); diff --git a/src/reducers/index.tsx b/src/reducers/index.tsx index 138910d60..a30f1e14f 100644 --- a/src/reducers/index.tsx +++ b/src/reducers/index.tsx @@ -96,7 +96,7 @@ export default combineReducers({ 'userPreferredDevice', 'userPreferredPlugin', 'userPreferredApp', - 'userLRUPlugins', + 'userStarredPlugins', ], }, connections, diff --git a/src/ui/components/Glyph.tsx b/src/ui/components/Glyph.tsx index 89dd60def..556f7a6ea 100644 --- a/src/ui/components/Glyph.tsx +++ b/src/ui/components/Glyph.tsx @@ -43,12 +43,13 @@ function ColoredIcon( size?: number; className?: string; color?: string; + style?: React.CSSProperties; }, context: { glyphColor?: string; }, ) { - const {color = context.glyphColor, name, size = 16, src} = props; + const {color = context.glyphColor, name, size = 16, src, style} = props; const isBlack = color == null || @@ -63,6 +64,7 @@ function ColoredIcon( src={src} size={size} className={props.className} + style={style} /> ); } else { @@ -72,6 +74,7 @@ function ColoredIcon( size={size} src={src} className={props.className} + style={style} /> ); } @@ -87,9 +90,10 @@ export default class Glyph extends React.PureComponent<{ variant?: 'filled' | 'outline'; className?: string; color?: string; + style?: React.CSSProperties; }> { render() { - const {name, size = 16, variant, color, className} = this.props; + const {name, size = 16, variant, color, className, style} = this.props; return ( ); } diff --git a/src/ui/components/StarButton.tsx b/src/ui/components/StarButton.tsx new file mode 100644 index 000000000..01b2927e4 --- /dev/null +++ b/src/ui/components/StarButton.tsx @@ -0,0 +1,62 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + */ + +import React, {useState, useCallback} from 'react'; +import {colors} from './colors'; +import Glyph from './Glyph'; +import styled from 'react-emotion'; + +const DownscaledGlyph = styled(Glyph)({ + maskSize: '12px 12px', + WebkitMaskSize: '12px 12px', + height: 12, + width: 12, +}); + +export function StarButton({ + starred, + onStar, +}: { + starred: boolean; + onStar: () => void; +}) { + const [hovered, setHovered] = useState(false); + const handleClick = useCallback( + (e: React.MouseEvent) => { + e.stopPropagation(); + onStar(); + }, + [onStar], + ); + const handleMouseEnter = useCallback(setHovered.bind(null, true), []); + const handleMouseLeave = useCallback(setHovered.bind(null, false), []); + return ( + + ); +} diff --git a/src/ui/index.tsx b/src/ui/index.tsx index b9f6fc767..3385c7892 100644 --- a/src/ui/index.tsx +++ b/src/ui/index.tsx @@ -175,3 +175,4 @@ export {InspectorSidebar} from './components/elements-inspector/sidebar'; export {Console} from './components/console'; export {default as Sheet} from './components/Sheet'; +export {StarButton} from './components/StarButton'; diff --git a/src/utils/icons.js b/src/utils/icons.js index e56bf44fe..d06090f88 100644 --- a/src/utils/icons.js +++ b/src/utils/icons.js @@ -24,7 +24,8 @@ const ICONS = { 'caution-octagon': [16], 'caution-triangle': [16], 'chevron-down-outline': [10], - 'chevron-down': [8], + 'chevron-down': [8, 12], + 'chevron-up': [8, 12], 'chevron-right': [8], 'dots-3-circle-outline': [16], 'info-circle': [16], @@ -52,6 +53,8 @@ const ICONS = { rocket: [20], settings: [12], star: [16, 24], + 'star-slash': [16], + 'life-event-major': [16], target: [12, 16], tools: [20], }; @@ -77,7 +80,12 @@ function buildLocalIconPath(name, size, density) { // $FlowFixMe not using flow in this file function buildIconURL(name, size, density) { const icon = getIconPartsFromName(name); - const url = `https://external.xx.fbcdn.net/assets/?name=${icon.trimmedName}&variant=${icon.variant}&size=${size}&set=facebook_icons&density=${density}x`; + // eslint-disable-next-line prettier/prettier + const url = `https://external.xx.fbcdn.net/assets/?name=${ + icon.trimmedName + }&variant=${ + icon.variant + }&size=${size}&set=facebook_icons&density=${density}x`; if ( typeof window !== 'undefined' && (!ICONS[name] || !ICONS[name].includes(size))