Handle removing the top toolbar when accessing a bundle via a web-link

This commit is contained in:
Clément Pasteau
2025-09-26 17:06:27 +02:00
parent ff8697ed71
commit c6bc916a62
12 changed files with 163 additions and 94 deletions

View File

@@ -52,6 +52,9 @@ export type RenderEditorContainerProps = {|
storageProvider: StorageProvider,
setToolbar: (?React.Node) => void,
setGamesPlatformFrameShown: ({| shown: boolean, isMobile: boolean |}) => void,
setTabsTitleBarAndEditorToolbarVisibility: (
visibility: 'visible' | 'hidden' | 'removed'
) => void,
// Some optional extra props to pass to the rendered editor
extraEditorProps: ?EditorContainerExtraProps,

View File

@@ -133,7 +133,7 @@ const HomePageMenuBar = ({
borderTop: `1px solid ${gdevelopTheme.home.separator.color}`,
}}
>
<Toolbar height={homepageMobileMenuHeight}>
<Toolbar height={homepageMobileMenuHeight} visibility="visible">
<ToolbarGroup spaceOut>
{tabsToDisplay.map(({ label, tab, getIcon, id }) => {
const isActive = activeTab === tab;

View File

@@ -38,6 +38,7 @@ import {
getBundleListingDataFromUserFriendlySlug,
} from '../../../../AssetStore/AssetStoreUtils';
import useAlertDialog from '../../../../UI/Alert/useAlertDialog';
import { useResponsiveWindowSize } from '../../../../UI/Responsive/ResponsiveWindowMeasurer';
type Props = {|
selectInAppTutorial: (tutorialId: string) => void,
@@ -88,6 +89,7 @@ type Props = {|
initialBundleUserFriendlySlug: ?string,
initialBundleCategory: ?string,
clearInitialBundleValues: () => void,
onTabsTitleBarAndEditorToolbarRemoved: boolean => void,
|};
const LearnSection = ({
@@ -118,6 +120,7 @@ const LearnSection = ({
initialBundleUserFriendlySlug,
initialBundleCategory,
clearInitialBundleValues,
onTabsTitleBarAndEditorToolbarRemoved,
}: Props) => {
const { fetchTutorials } = React.useContext(TutorialContext);
const { fetchBundles, bundleListingDatas } = React.useContext(
@@ -125,6 +128,7 @@ const LearnSection = ({
);
const { navigateToRoute } = React.useContext(RouterContext);
const { showAlert } = useAlertDialog();
const { isMobile } = useResponsiveWindowSize();
const [
selectedBundleListingData,
@@ -143,9 +147,10 @@ const LearnSection = ({
priceValue: priceForUsageType && priceForUsageType.value,
priceCurrency: priceForUsageType && priceForUsageType.currency,
});
onTabsTitleBarAndEditorToolbarRemoved(false);
setSelectedBundleListingData(bundleListingData);
},
[setSelectedBundleListingData]
[setSelectedBundleListingData, onTabsTitleBarAndEditorToolbarRemoved]
);
// When the bundles are loaded,
@@ -193,6 +198,11 @@ const LearnSection = ({
priceValue: priceForUsageType && priceForUsageType.value,
priceCurrency: priceForUsageType && priceForUsageType.currency,
});
// We are coming from a web link, let's hide the top navigation of the app,
// so the user can focus on the bundle information.
if (isMobile) {
onTabsTitleBarAndEditorToolbarRemoved(true);
}
setSelectedBundleListingData(bundleListingData);
},
[
@@ -201,6 +211,8 @@ const LearnSection = ({
initialBundleUserFriendlySlug,
initialBundleCategory,
clearInitialBundleValues,
onTabsTitleBarAndEditorToolbarRemoved,
isMobile,
]
);
@@ -226,9 +238,10 @@ const LearnSection = ({
});
}
onTabsTitleBarAndEditorToolbarRemoved(false);
onSelectCourse(courseId);
},
[onSelectCourse, courses]
[onSelectCourse, courses, onTabsTitleBarAndEditorToolbarRemoved]
);
React.useEffect(
@@ -259,6 +272,7 @@ const LearnSection = ({
course={course}
courseChapters={courseChapters}
onBack={() => {
onTabsTitleBarAndEditorToolbarRemoved(false);
onSelectCourse(null);
}}
onOpenTemplateFromCourseChapter={onOpenTemplateFromCourseChapter}
@@ -281,7 +295,10 @@ const LearnSection = ({
return (
<BundleInformationPage
bundleListingData={selectedBundleListingData}
onBack={() => setSelectedBundleListingData(null)}
onBack={() => {
onTabsTitleBarAndEditorToolbarRemoved(false);
setSelectedBundleListingData(null);
}}
getSubscriptionPlansWithPricingSystems={
getSubscriptionPlansWithPricingSystems
}

View File

@@ -109,6 +109,9 @@ type Props = {|
project: ?gdProject,
setToolbar: (?React.Node) => void,
setGamesPlatformFrameShown: ({| shown: boolean, isMobile: boolean |}) => void,
setTabsTitleBarAndEditorToolbarVisibility: (
visibility: 'visible' | 'hidden' | 'removed'
) => void,
storageProviders: Array<StorageProvider>,
// Games
@@ -200,6 +203,7 @@ export const HomePage = React.memo<Props>(
onCreateProjectFromExample,
setToolbar,
setGamesPlatformFrameShown,
setTabsTitleBarAndEditorToolbarVisibility,
selectInAppTutorial,
onOpenPreferences,
onOpenAbout,
@@ -281,6 +285,10 @@ export const HomePage = React.memo<Props>(
includeLegacy: false,
});
const [
isRemovingTabsTitleBarAndEditorToolbarTemporarily,
setIsRemovingTabsTitleBarAndEditorToolbarTemporarily,
] = React.useState<boolean>(false);
const { isMobile } = useResponsiveWindowSize();
const {
values: { showCreateSectionByDefault },
@@ -295,6 +303,10 @@ export const HomePage = React.memo<Props>(
: 'learn';
const [activeTab, setActiveTab] = React.useState<HomeTab>(initialTab);
const onTabChange = (tab: HomeTab) => {
setActiveTab(tab);
setIsRemovingTabsTitleBarAndEditorToolbarTemporarily(false);
};
const { setInitialPackUserFriendlySlug } = React.useContext(
AssetStoreContext
@@ -331,7 +343,7 @@ export const HomePage = React.memo<Props>(
if (!requestedTab) return;
setActiveTab(requestedTab);
onTabChange(requestedTab);
if (requestedTab === 'shop') {
if (routeArguments['asset-pack']) {
setInitialPackUserFriendlySlug(routeArguments['asset-pack']);
@@ -482,16 +494,21 @@ export const HomePage = React.memo<Props>(
if (activeTab === 'play') {
setGamesPlatformFrameShown({ shown: true, isMobile });
} else {
if (isRemovingTabsTitleBarAndEditorToolbarTemporarily) {
return;
}
setGamesPlatformFrameShown({ shown: false, isMobile });
updateToolbar();
}
// Ensure we show it again when the tab changes.
return () => {
setGamesPlatformFrameShown({ shown: false, isMobile });
};
},
[updateToolbar, activeTab, setGamesPlatformFrameShown, isMobile]
[
updateToolbar,
activeTab,
isActive,
setGamesPlatformFrameShown,
isMobile,
isRemovingTabsTitleBarAndEditorToolbarTemporarily,
]
);
const forceUpdateEditor = React.useCallback(() => {
@@ -653,6 +670,14 @@ export const HomePage = React.memo<Props>(
initialBundleUserFriendlySlugForLearn
}
initialBundleCategory={initialBundleCategoryForLearn}
onTabsTitleBarAndEditorToolbarRemoved={removed => {
setIsRemovingTabsTitleBarAndEditorToolbarTemporarily(
removed
);
setTabsTitleBarAndEditorToolbarVisibility(
removed ? 'removed' : 'visible'
);
}}
/>
)}
{activeTab === 'play' && (
@@ -671,7 +696,7 @@ export const HomePage = React.memo<Props>(
onExtensionInstalled={onExtensionInstalled}
onCourseOpen={(courseId: string) => {
onSelectCourse(courseId);
setActiveTab('learn');
onTabChange('learn');
}}
receivedCourses={
courses
@@ -694,7 +719,7 @@ export const HomePage = React.memo<Props>(
currentFileMetadata={fileMetadata}
onOpenTeachingResources={() => {
setLearnCategory('education-curriculum');
setActiveTab('learn');
onTabChange('learn');
}}
/>
) : (
@@ -711,7 +736,7 @@ export const HomePage = React.memo<Props>(
</div>
<HomePageMenu
activeTab={activeTab}
setActiveTab={setActiveTab}
setActiveTab={onTabChange}
onOpenPreferences={onOpenPreferences}
onOpenAbout={onOpenAbout}
/>
@@ -738,6 +763,9 @@ export const renderHomePageContainer = (
projectItemName={props.projectItemName}
setToolbar={props.setToolbar}
setGamesPlatformFrameShown={props.setGamesPlatformFrameShown}
setTabsTitleBarAndEditorToolbarVisibility={
props.setTabsTitleBarAndEditorToolbarVisibility
}
canOpen={props.canOpen}
onChooseProject={props.onChooseProject}
onOpenRecentFile={props.onOpenRecentFile}

View File

@@ -321,14 +321,16 @@ const EditorTabsPane = React.forwardRef<Props, {||}>((props, ref) => {
const containerRef = React.useRef<?HTMLDivElement>(null);
const [
tabsTitleBarAndEditorToolbarHidden,
setTabsTitleBarAndEditorToolbarHidden,
] = React.useState(false);
tabsTitleBarAndEditorToolbarVisibility,
setTabsTitleBarAndEditorToolbarVisibility,
] = React.useState<'visible' | 'hidden' | 'removed'>('visible');
const onSetGamesPlatformFrameShown = React.useCallback(
({ shown, isMobile }: {| shown: boolean, isMobile: boolean |}) => {
onSetPointerEventsNone(shown);
setTabsTitleBarAndEditorToolbarHidden(shown && isMobile);
setTabsTitleBarAndEditorToolbarVisibility(
shown && isMobile ? 'hidden' : 'visible'
);
},
[onSetPointerEventsNone]
);
@@ -485,7 +487,7 @@ const EditorTabsPane = React.forwardRef<Props, {||}>((props, ref) => {
isLeftMostPane={isLeftMostPane}
isRightMostPane={isRightMostPane}
displayMenuIcon={paneIdentifier === 'center'}
hidden={tabsTitleBarAndEditorToolbarHidden}
visibility={tabsTitleBarAndEditorToolbarVisibility}
toggleProjectManager={toggleProjectManager}
renderTabs={(onEditorTabHovered, onEditorTabClosing) => (
<DraggableEditorTabs
@@ -526,7 +528,7 @@ const EditorTabsPane = React.forwardRef<Props, {||}>((props, ref) => {
)}
<Toolbar
ref={toolbarRef}
hidden={tabsTitleBarAndEditorToolbarHidden}
visibility={tabsTitleBarAndEditorToolbarVisibility}
showProjectButtons={
!['start page', 'debugger', 'ask-ai', null].includes(
currentTab ? currentTab.key : null
@@ -584,6 +586,7 @@ const EditorTabsPane = React.forwardRef<Props, {||}>((props, ref) => {
setToolbar: editorToolbar =>
setEditorToolbar(editorToolbar, isCurrentTab),
setGamesPlatformFrameShown: onSetGamesPlatformFrameShown,
setTabsTitleBarAndEditorToolbarVisibility: setTabsTitleBarAndEditorToolbarVisibility,
projectItemName: editorTab.projectItemName,
setPreviewedLayout,
onOpenAskAi,

View File

@@ -45,7 +45,7 @@ const styles = {
};
type TabsTitlebarProps = {|
hidden: boolean,
visibility: 'visible' | 'hidden' | 'removed',
toggleProjectManager: () => void,
renderTabs: (
onEditorTabHovered: (?EditorTab, {| isLabelTruncated: boolean |}) => void,
@@ -104,7 +104,7 @@ const useIsAskAiIconAnimated = (shouldDisplayAskAi: boolean) => {
*/
export default function TabsTitlebar({
toggleProjectManager,
hidden,
visibility,
renderTabs,
isLeftMostPane,
isRightMostPane,
@@ -190,52 +190,54 @@ export default function TabsTitlebar({
const isAskAiIconAnimated = useIsAskAiIconAnimated(shouldDisplayAskAi);
return (
<div
style={{
...styles.container,
backgroundColor: 'transparent',
// Hiding the titlebar should still keep its position in the layout to avoid layout shifts:
visibility: hidden ? 'hidden' : 'visible',
pointerEvents: hidden ? undefined : 'all',
}}
className={WINDOW_DRAGGABLE_PART_CLASS_NAME}
>
{isLeftMostPane && <TitleBarLeftSafeMargins />}
{displayMenuIcon && (
<IconButton
size="small"
// Even if not in the toolbar, keep this ID for backward compatibility for tutorials.
id="main-toolbar-project-manager-button"
// The whole bar is draggable, so prevent the icon to be draggable,
// as it can affect the ability to open the menu.
className={WINDOW_NON_DRAGGABLE_PART_CLASS_NAME}
style={styles.menuIcon}
color="default"
onClick={toggleProjectManager}
>
<MenuIcon />
</IconButton>
)}
{renderTabs(onEditorTabHovered, onEditorTabClosing)}
{shouldDisplayAskAi ? (
<div
style={styles.askAiContainer}
className={WINDOW_NON_DRAGGABLE_PART_CLASS_NAME}
>
<TextButton
icon={<RobotIcon size={16} rotating={isAskAiIconAnimated} />}
label={'Ask AI'}
onClick={onAskAiClicked}
visibility !== 'removed' && (
<div
style={{
...styles.container,
backgroundColor: 'transparent',
// Hiding the titlebar should still keep its position in the layout to avoid layout shifts:
visibility,
pointerEvents: visibility === 'hidden' ? undefined : 'all',
}}
className={WINDOW_DRAGGABLE_PART_CLASS_NAME}
>
{isLeftMostPane && <TitleBarLeftSafeMargins />}
{displayMenuIcon && (
<IconButton
size="small"
// Even if not in the toolbar, keep this ID for backward compatibility for tutorials.
id="main-toolbar-project-manager-button"
// The whole bar is draggable, so prevent the icon to be draggable,
// as it can affect the ability to open the menu.
className={WINDOW_NON_DRAGGABLE_PART_CLASS_NAME}
style={styles.menuIcon}
color="default"
onClick={toggleProjectManager}
>
<MenuIcon />
</IconButton>
)}
{renderTabs(onEditorTabHovered, onEditorTabClosing)}
{shouldDisplayAskAi ? (
<div
style={styles.askAiContainer}
className={WINDOW_NON_DRAGGABLE_PART_CLASS_NAME}
>
<TextButton
icon={<RobotIcon size={16} rotating={isAskAiIconAnimated} />}
label={'Ask AI'}
onClick={onAskAiClicked}
/>
</div>
) : null}
{isRightMostPane && <TitleBarRightSafeMargins />}
{tooltipData && (
<TabsTitlebarTooltip
anchorElement={tooltipData.element}
editorTab={tooltipData.editorTab}
/>
</div>
) : null}
{isRightMostPane && <TitleBarRightSafeMargins />}
{tooltipData && (
<TabsTitlebarTooltip
anchorElement={tooltipData.element}
editorTab={tooltipData.editorTab}
/>
)}
</div>
)}
</div>
)
);
}

View File

@@ -23,7 +23,7 @@ export type MainFrameToolbarProps = {|
checkedOutVersionStatus?: ?OpenedVersionStatus,
onQuitVersionHistory: () => Promise<void>,
canQuitVersionHistory: boolean,
hidden: boolean,
visibility: 'visible' | 'hidden' | 'removed',
...PreviewAndShareButtonsProps,
|};
@@ -101,7 +101,10 @@ export default React.forwardRef<MainFrameToolbarProps, ToolbarInterface>(
);
return (
<Toolbar borderBottomColor={borderBottomColor} hidden={props.hidden}>
<Toolbar
borderBottomColor={borderBottomColor}
visibility={props.visibility}
>
{props.showProjectButtons ? (
<>
<LeftButtonsToolbarGroup

View File

@@ -72,7 +72,11 @@ const editors = {
const BottomToolbar = React.memo<Props>((props: Props) => {
return (
<Paper background="medium" square style={styles.container}>
<Toolbar height={toolbarHeight} paddingBottom={toolbarPaddingBottom}>
<Toolbar
height={toolbarHeight}
paddingBottom={toolbarPaddingBottom}
visibility="visible"
>
<ToolbarGroup spaceOut>
{Object.keys(editors).map(editorId => {
const { icon, buttonId } = editors[editorId];

View File

@@ -7,7 +7,7 @@ type ToolbarProps = {|
height?: number,
borderBottomColor?: ?string,
paddingBottom?: number,
hidden?: boolean,
visibility: 'visible' | 'hidden' | 'removed',
|};
const styles = {
@@ -28,31 +28,33 @@ export const Toolbar = React.memo<ToolbarProps>(
borderBottomColor,
height = 40,
paddingBottom,
hidden,
visibility,
}: ToolbarProps) => {
const gdevelopTheme = React.useContext(GDevelopThemeContext);
return (
<div
className="almost-invisible-scrollbar"
style={{
...styles.toolbar,
backgroundColor: gdevelopTheme.toolbar.backgroundColor,
height,
borderBottom: borderBottomColor
? `2px solid ${borderBottomColor}`
: undefined,
...(paddingBottom ? { paddingBottom } : undefined),
visibility !== 'removed' && (
<div
className="almost-invisible-scrollbar"
style={{
...styles.toolbar,
backgroundColor: gdevelopTheme.toolbar.backgroundColor,
height,
borderBottom: borderBottomColor
? `2px solid ${borderBottomColor}`
: undefined,
...(paddingBottom ? { paddingBottom } : undefined),
// Hiding the titlebar should still keep its position in the layout to avoid layout shifts:
visibility: hidden ? 'hidden' : 'visible',
// Use content-visibility as we know the exact height of the toolbar, so the
// content can be entirely skipped when hidden:
contentVisibility: hidden ? 'hidden' : 'visible',
pointerEvents: hidden ? undefined : 'all',
}}
>
{children}
</div>
// Hiding the titlebar should still keep its position in the layout to avoid layout shifts:
visibility,
// Use content-visibility as we know the exact height of the toolbar, so the
// content can be entirely skipped when hidden:
contentVisibility: visibility === 'hidden' ? 'hidden' : 'visible',
pointerEvents: visibility === 'hidden' ? undefined : 'all',
}}
>
{children}
</div>
)
);
}
);

View File

@@ -95,6 +95,7 @@ const WrappedHomePage = ({
projectItemName={null}
setToolbar={() => {}}
setGamesPlatformFrameShown={() => {}}
setTabsTitleBarAndEditorToolbarVisibility={() => {}}
canOpen={true}
storageProviders={[CloudStorageProvider]}
onChooseProject={() => action('onChooseProject')()}

View File

@@ -80,6 +80,7 @@ export const Default = () => (
initialBundleCategory={null}
initialBundleUserFriendlySlug={null}
clearInitialBundleValues={() => {}}
onTabsTitleBarAndEditorToolbarRemoved={() => {}}
/>
</TutorialContext.Provider>
</PreferencesContext.Provider>
@@ -131,6 +132,7 @@ export const NotAuthenticated = () => (
initialBundleCategory={null}
initialBundleUserFriendlySlug={null}
clearInitialBundleValues={() => {}}
onTabsTitleBarAndEditorToolbarRemoved={() => {}}
/>
</TutorialContext.Provider>
</PreferencesContext.Provider>
@@ -184,6 +186,7 @@ export const EducationSubscriber = () => (
initialBundleCategory={null}
initialBundleUserFriendlySlug={null}
clearInitialBundleValues={() => {}}
onTabsTitleBarAndEditorToolbarRemoved={() => {}}
/>
</TutorialContext.Provider>
</PreferencesContext.Provider>
@@ -237,6 +240,7 @@ export const EducationTeacher = () => (
initialBundleCategory={null}
initialBundleUserFriendlySlug={null}
clearInitialBundleValues={() => {}}
onTabsTitleBarAndEditorToolbarRemoved={() => {}}
/>
</TutorialContext.Provider>
</PreferencesContext.Provider>
@@ -284,6 +288,7 @@ export const LoadingTutorials = () => (
initialBundleCategory={null}
initialBundleUserFriendlySlug={null}
clearInitialBundleValues={() => {}}
onTabsTitleBarAndEditorToolbarRemoved={() => {}}
/>
</TutorialContext.Provider>
</PreferencesContext.Provider>
@@ -330,6 +335,7 @@ export const LoadingCourses = () => (
initialBundleCategory={null}
initialBundleUserFriendlySlug={null}
clearInitialBundleValues={() => {}}
onTabsTitleBarAndEditorToolbarRemoved={() => {}}
/>
</TutorialContext.Provider>
</PreferencesContext.Provider>

View File

@@ -46,7 +46,7 @@ const defaultProps: MainFrameToolbarProps = {
showProjectButtons: true,
openShareDialog: () => {},
isSharingEnabled: true,
hidden: false,
visibility: 'visible',
onPreviewWithoutHotReload: async () => {},
onOpenDebugger: () => {},