diff --git a/packages/design-system/src/components/landingPage/index.tsx b/packages/design-system/src/components/landingPage/index.tsx index aac96d9c7..d79a4e4cf 100644 --- a/packages/design-system/src/components/landingPage/index.tsx +++ b/packages/design-system/src/components/landingPage/index.tsx @@ -37,6 +37,7 @@ interface LandingPageProps { contentPanel?: ReactNode; iframeBorderClass?: string; extraClasses?: string; + showQuickLinks?: boolean; } const LandingPage = ({ @@ -47,12 +48,13 @@ const LandingPage = ({ children, extraClasses, contentPanel, + showQuickLinks = true, }: LandingPageProps) => { const [loading, setLoading] = useState(iframeSrc ? true : false); const [open, setOpen] = useState(true); return ( -
+
{loading && }
)} {psInfoKey && } - {contentPanel &&
{contentPanel}
} + {contentPanel && <>{contentPanel}}
{children && ( @@ -114,7 +116,7 @@ const LandingPage = ({
)}
- + {showQuickLinks && } ); diff --git a/packages/design-system/src/components/sidebar/collapsedSidebar.tsx b/packages/design-system/src/components/sidebar/collapsedSidebar.tsx index 18f0bf046..3813151ea 100644 --- a/packages/design-system/src/components/sidebar/collapsedSidebar.tsx +++ b/packages/design-system/src/components/sidebar/collapsedSidebar.tsx @@ -23,7 +23,7 @@ import classNames from 'classnames'; * Internal dependencies. */ import { MenuOpenIcon } from '../../icons'; -import { useSidebar } from './useSidebar'; +import { useSidebar, type SidebarItemValue } from './useSidebar'; const CollapsedSidebar = () => { const { @@ -31,11 +31,15 @@ const CollapsedSidebar = () => { updateSelectedItemKey, currentSelectedItemKey, toggleSidebarCollapse, + sidebarItems, + selectedItemKey, } = useSidebar(({ state, actions }) => ({ collapsedSidebarItems: state.collapsedSidebarItems, updateSelectedItemKey: actions.updateSelectedItemKey, currentSelectedItemKey: state.currentItemKey, toggleSidebarCollapse: actions.toggleSidebarCollapse, + sidebarItems: state.sidebarItems, + selectedItemKey: state.selectedItemKey, })); const handleFooterElementClick = useCallback( @@ -54,13 +58,48 @@ const CollapsedSidebar = () => { 'flex flex-col justify-between items-center p-2 w-full h-full' )} > - +
+ + {Object.keys(sidebarItems).map((itemKey) => { + if ( + Object.keys(collapsedSidebarItems?.footerElements || {}).includes( + itemKey + ) + ) { + return null; + } + + const sidebarItem = sidebarItems[itemKey] as SidebarItemValue; + const Icon = sidebarItem.icon ? sidebarItem.icon.Element : null; + const props = sidebarItem?.icon?.props || {}; + const title = + typeof sidebarItem.title === 'function' + ? sidebarItem.title() + : sidebarItem.title; + const isCurrent = itemKey === selectedItemKey; + const buttonClassNames = classNames({ + 'cursor-auto opacity-70 dark:opacity-40': isCurrent, + 'hover:opacity-60': !isCurrent, + }); + + return ( + + ); + })} +
{Object.keys(collapsedSidebarItems?.footerElements || {}).map((key) => { const Icon = collapsedSidebarItems?.footerElements[key].icon.Element; @@ -71,21 +110,24 @@ const CollapsedSidebar = () => { const props = collapsedSidebarItems?.footerElements[key].icon.props; const title = collapsedSidebarItems?.footerElements[key].title; + const isCurrent = key === currentSelectedItemKey; return ( ); })} diff --git a/packages/design-system/src/components/sidebar/useSidebar/constants.ts b/packages/design-system/src/components/sidebar/useSidebar/constants.ts index 9097882ec..4b1507c6e 100644 --- a/packages/design-system/src/components/sidebar/useSidebar/constants.ts +++ b/packages/design-system/src/components/sidebar/useSidebar/constants.ts @@ -15,6 +15,7 @@ */ export enum SIDEBAR_ITEMS_KEYS { + DASHBOARD = 'dashboard', COOKIES = 'cookies', COOKIES_WITH_ISSUES = 'cookie-issues', PRIVACY_SANDBOX = 'privacy-sandbox', diff --git a/packages/design-system/src/icons/dashboard-white.svg b/packages/design-system/src/icons/dashboard-white.svg new file mode 100644 index 000000000..8eda7fc0b --- /dev/null +++ b/packages/design-system/src/icons/dashboard-white.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/design-system/src/icons/dashboard.svg b/packages/design-system/src/icons/dashboard.svg new file mode 100644 index 000000000..ee7f4eb98 --- /dev/null +++ b/packages/design-system/src/icons/dashboard.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/design-system/src/icons/external-link-black.svg b/packages/design-system/src/icons/external-link-black.svg new file mode 100644 index 000000000..43209f757 --- /dev/null +++ b/packages/design-system/src/icons/external-link-black.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/design-system/src/icons/index.tsx b/packages/design-system/src/icons/index.tsx index 2abd7058b..2f4ba9b83 100644 --- a/packages/design-system/src/icons/index.tsx +++ b/packages/design-system/src/icons/index.tsx @@ -73,6 +73,9 @@ export { default as GreenTick } from './green-tick-icon.svg'; export { default as ChevronDown } from './chevron-down.svg'; export { default as WarningBare } from './warning.svg'; export { default as GroupsIcon } from './groups.svg'; +export { default as ExternalLinkBlack } from './external-link-black.svg'; +export { default as DashboardIcon } from './dashboard.svg'; +export { default as DashboardIconWhite } from './dashboard-white.svg'; export { default as WikiIcon } from './wiki.svg'; export { default as WikiIconWhite } from './wiki-white.svg'; export { default as ExternalLinkIcon } from './external-link.svg'; diff --git a/packages/design-system/src/icons/privacy-sandbox-colored.svg b/packages/design-system/src/icons/privacy-sandbox-colored.svg index 52a3c540d..c80948657 100644 --- a/packages/design-system/src/icons/privacy-sandbox-colored.svg +++ b/packages/design-system/src/icons/privacy-sandbox-colored.svg @@ -1,6 +1,6 @@ - + @@ -13,7 +13,7 @@ - + diff --git a/packages/extension/src/view/devtools/app.tsx b/packages/extension/src/view/devtools/app.tsx index 4c9a6ec2e..bfb7c0c26 100644 --- a/packages/extension/src/view/devtools/app.tsx +++ b/packages/extension/src/view/devtools/app.tsx @@ -60,9 +60,12 @@ const App: React.FC = () => { } const data = await chrome.storage.session.get(); + const syncData = await chrome.storage.sync.get(); if (data?.['selectedSidebarItem#' + tabId]) { setDefaultSelectedItemKey(data['selectedSidebarItem#' + tabId]); + } else if (syncData?.psLandingPageViewed) { + setDefaultSelectedItemKey(SIDEBAR_ITEMS_KEYS.DASHBOARD); } if (data?.['sidebarCollapsedState#' + tabId]) { diff --git a/packages/extension/src/view/devtools/components/cookies/index.tsx b/packages/extension/src/view/devtools/components/cookies/index.tsx index f8acb3372..dbcfb002b 100644 --- a/packages/extension/src/view/devtools/components/cookies/index.tsx +++ b/packages/extension/src/view/devtools/components/cookies/index.tsx @@ -29,6 +29,7 @@ import { I18n } from '@google-psat/i18n'; * Internal dependencies. */ import { useCookie, useSettings } from '../../stateProviders'; +import useCanShowAnalyzeTabButton from '../../hooks/useCanShowAnalyzeTabButton'; import CookiesListing from './cookiesListing'; import AssembledCookiesLanding from './cookieLanding'; @@ -50,6 +51,7 @@ const Cookies = ({ setFilteredCookies }: CookiesProps) => { tabToRead: state.tabToRead, changeListeningToThisTab: actions.changeListeningToThisTab, })); + const canShowAnalyzeTabButton = useCanShowAnalyzeTabButton(); const { allowedNumberOfTabs } = useSettings(({ state }) => ({ allowedNumberOfTabs: state.allowedNumberOfTabs, @@ -70,13 +72,7 @@ const Cookies = ({ setFilteredCookies }: CookiesProps) => { ); } - if ( - (tabToRead && - isCurrentTabBeingListenedTo && - allowedNumberOfTabs && - allowedNumberOfTabs === 'single') || - (allowedNumberOfTabs && allowedNumberOfTabs === 'unlimited') - ) { + if (canShowAnalyzeTabButton) { return (
{ + const navigateTo = useSidebar(({ actions }) => actions.updateSelectedItemKey); + const { tabFrames } = useCookie(({ state }) => ({ + tabFrames: state.tabFrames, + })); + const canShowAnalyzeTabButton = useCanShowAnalyzeTabButton(); + + const handleButtonClick = useCallback( + (event: React.MouseEvent, sidebarKey: string) => { + event.preventDefault(); + event.stopPropagation(); + + const firstFrame = + Object.keys(tabFrames || {})?.[0] || SIDEBAR_ITEMS_KEYS.COOKIES; + + navigateTo(sidebarKey === 'FIRST_COOKIE_TABLE' ? firstFrame : sidebarKey); + }, + [navigateTo, tabFrames] + ); + + useEffect(() => { + if (canShowAnalyzeTabButton && FEATURE_LIST[0].buttons.length === 1) { + FEATURE_LIST[0].buttons.push({ + name: 'Cookies Table', + sidebarKey: 'FIRST_COOKIE_TABLE', + }); + } + }, [canShowAnalyzeTabButton]); + + return ( +
+
+
+

Pinned

+
+ {PINNED_ITEMS.map((item) => { + const Icon = item.icon; + + return ( +
navigateTo(item.sidebarKey)} + > + + {item.name} +
+ ); + })} +
+
+
+

Features

+
+ {FEATURE_LIST.map((item) => { + const Icon = item.icon; + const headingClasses = classNames( + 'text-sm', + item?.colorClasses?.heading ? item?.colorClasses?.heading : '' + ); + + return ( +
navigateTo(item.sidebarKey)} + > +
+ +

{item.name}

+
+

{item.description}

+
+ {item.buttons && + item.buttons.map((button) => ( + + ))} +
+
+ ); + })} +
+
+
+
+ ); +}; + +export default ContentPanel; diff --git a/packages/extension/src/view/devtools/components/dashboard/index.tsx b/packages/extension/src/view/devtools/components/dashboard/index.tsx new file mode 100644 index 000000000..17ac6819c --- /dev/null +++ b/packages/extension/src/view/devtools/components/dashboard/index.tsx @@ -0,0 +1,37 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * External dependencies. + */ +import React from 'react'; +import { LandingPage } from '@google-psat/design-system'; + +/** + * Internal dependencies. + */ +import ContentPanel from './contentPanel'; + +const Dashboard = () => { + return ( + } + showQuickLinks={false} + /> + ); +}; + +export default Dashboard; diff --git a/packages/extension/src/view/devtools/components/index.ts b/packages/extension/src/view/devtools/components/index.ts index 6e8d45f6c..38e097d10 100644 --- a/packages/extension/src/view/devtools/components/index.ts +++ b/packages/extension/src/view/devtools/components/index.ts @@ -21,3 +21,4 @@ export { default as PrivacySandbox } from './privacySandbox'; export { default as Wiki } from './wiki'; export { default as Settings } from './settings'; export { default as Layout } from './layout'; +export { default as Dashboard } from './dashboard'; diff --git a/packages/extension/src/view/devtools/components/layout.tsx b/packages/extension/src/view/devtools/components/layout.tsx index 7244f5170..b56d8d089 100644 --- a/packages/extension/src/view/devtools/components/layout.tsx +++ b/packages/extension/src/view/devtools/components/layout.tsx @@ -257,7 +257,7 @@ const Layout = ({ setSidebarData }: LayoutProps) => {
{settingsChanged && ( -
+
{ + const navigateTo = useSidebar(({ actions }) => actions.updateSelectedItemKey); + + return ( +
+
+
+ +

+ Protecting your privacy online +

+

+ The Privacy Sandbox initiative aims to create technologies that both + protect people's privacy online and give companies and + developers tools to build thriving digital businesses. The Privacy + Sandbox reduces cross-site and cross-app tracking while helping to + keep online content and services free for all. +

+
+ + Learn About Privacy Sandbox + + + +
+
+
+
+ ); +}; + +export default ContentPanel; diff --git a/packages/extension/src/view/devtools/components/privacySandbox/index.tsx b/packages/extension/src/view/devtools/components/privacySandbox/index.tsx index f8188f316..dbbe4f405 100644 --- a/packages/extension/src/view/devtools/components/privacySandbox/index.tsx +++ b/packages/extension/src/view/devtools/components/privacySandbox/index.tsx @@ -16,18 +16,26 @@ /** * External dependencies. */ -import React from 'react'; +import React, { useEffect } from 'react'; import { LandingPage } from '@google-psat/design-system'; -const PrivacySandbox = () => ( -
- -
-); +/** + * Internal dependencies. + */ +import ContentPanel from './contentPanel'; + +const PrivacySandbox = () => { + useEffect(() => { + (async () => { + await chrome.storage.sync.set({ + psLandingPageViewed: true, + }); + })(); + }, []); + + return ( + } /> + ); +}; export default PrivacySandbox; diff --git a/packages/extension/src/view/devtools/hooks/useCanShowAnalyzeTabButton.ts b/packages/extension/src/view/devtools/hooks/useCanShowAnalyzeTabButton.ts new file mode 100644 index 000000000..785c0ca53 --- /dev/null +++ b/packages/extension/src/view/devtools/hooks/useCanShowAnalyzeTabButton.ts @@ -0,0 +1,40 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * Internal dependencies. + */ +import { useCookie, useSettings } from '../stateProviders'; + +const useCanShowAnalyzeTabButton = () => { + const { isCurrentTabBeingListenedTo, tabToRead } = useCookie(({ state }) => ({ + isCurrentTabBeingListenedTo: state.isCurrentTabBeingListenedTo, + tabToRead: state.tabToRead, + })); + + const { allowedNumberOfTabs } = useSettings(({ state }) => ({ + allowedNumberOfTabs: state.allowedNumberOfTabs, + })); + + return ( + (tabToRead && + isCurrentTabBeingListenedTo && + allowedNumberOfTabs && + allowedNumberOfTabs === 'single') || + (allowedNumberOfTabs && allowedNumberOfTabs === 'unlimited') + ); +}; + +export default useCanShowAnalyzeTabButton; diff --git a/packages/extension/src/view/devtools/tabs.ts b/packages/extension/src/view/devtools/tabs.ts index 93725407b..07c8ef9c2 100644 --- a/packages/extension/src/view/devtools/tabs.ts +++ b/packages/extension/src/view/devtools/tabs.ts @@ -42,6 +42,8 @@ import { type SidebarItems, SIDEBAR_ITEMS_KEYS, GroupsIcon, + DashboardIcon, + DashboardIconWhite, WikiIcon, WikiIconWhite, type CollapsedSidebarItems, @@ -71,6 +73,7 @@ import { Settings, ProtectedAudience, PrivateAggregation, + Dashboard, } from './components'; const TABS: SidebarItems = { @@ -258,6 +261,20 @@ const TABS: SidebarItems = { }, }, }, + [SIDEBAR_ITEMS_KEYS.DASHBOARD]: { + title: () => 'Dashboard', + panel: { + Element: Dashboard, + }, + icon: { + Element: DashboardIcon, + }, + selectedIcon: { + Element: DashboardIconWhite, + }, + dropdownOpen: false, + children: {}, + }, [SIDEBAR_ITEMS_KEYS.WIKI]: { title: () => I18n.getMessage('wiki'), panel: { diff --git a/tailwind.config.cjs b/tailwind.config.cjs index a48825793..24de9173d 100644 --- a/tailwind.config.cjs +++ b/tailwind.config.cjs @@ -216,6 +216,7 @@ module.exports = { 'selection-green-dark': '#74d47e', 'selection-yellow-light': '#8b8f18', 'selection-yellow-dark': '#dbdb48', + 'cultured-grey': '#F7F7F7', }, borderColor: { ...colors, @@ -233,6 +234,7 @@ module.exports = { 'baby-blue-eyes': '#A8C7FA', 'leaf-green-dark': '#87DFB2', sapphire: '#0B57D0', + 'dark-grey': '#AAAAAA', }, colors: { ...colors,