mirror of
https://github.com/4ian/GDevelop.git
synced 2025-10-15 10:19:04 +00:00
Compare commits
7 Commits
experiment
...
close-prev
Author | SHA1 | Date | |
---|---|---|---|
![]() |
67acc82775 | ||
![]() |
933cc2f3ed | ||
![]() |
a1500ba01f | ||
![]() |
b114a3ff18 | ||
![]() |
88e14c655e | ||
![]() |
abb36e62a2 | ||
![]() |
47751d2677 |
@@ -1,5 +1,8 @@
|
||||
<html>
|
||||
<head>
|
||||
<!-- On desktop app, this title is used to know if the user is focused on the
|
||||
main window (IDE) or on another window (preview or external editor).
|
||||
Should this be changed, you should also change ElectronMainMenu.js -->
|
||||
<title>GDevelop Sound Effects Editor (Jfxr)</title>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
|
@@ -1,5 +1,8 @@
|
||||
<html>
|
||||
<head>
|
||||
<!-- On desktop app, this title is used to know if the user is focused on the
|
||||
main window (IDE) or on another window (preview or external editor).
|
||||
Should this be changed, you should also change ElectronMainMenu.js -->
|
||||
<title>GDevelop Image Editor (Piskel)</title>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
|
@@ -1,5 +1,8 @@
|
||||
<html>
|
||||
<head>
|
||||
<!-- On desktop app, this title is used to know if the user is focused on the
|
||||
main window (IDE) or on another window (preview or external editor).
|
||||
Should this be changed, you should also change ElectronMainMenu.js -->
|
||||
<title>GDevelop Dialogue Tree Editor (Yarn)</title>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
|
@@ -112,8 +112,8 @@ export const AssetStoreContext = React.createContext<AssetStoreState>({
|
||||
},
|
||||
navigationState: {
|
||||
getCurrentPage: () => assetStoreHomePageState,
|
||||
backToPreviousPage: () => {},
|
||||
openHome: () => {},
|
||||
backToPreviousPage: () => assetStoreHomePageState,
|
||||
openHome: () => assetStoreHomePageState,
|
||||
clearHistory: () => {},
|
||||
openSearchResultPage: () => {},
|
||||
openTagPage: tag => {},
|
||||
|
@@ -20,8 +20,8 @@ export type AssetStorePageState = {|
|
||||
|
||||
export type NavigationState = {|
|
||||
getCurrentPage: () => AssetStorePageState,
|
||||
backToPreviousPage: () => void,
|
||||
openHome: () => void,
|
||||
backToPreviousPage: () => AssetStorePageState,
|
||||
openHome: () => AssetStorePageState,
|
||||
clearHistory: () => void,
|
||||
openSearchResultPage: () => void,
|
||||
openTagPage: string => void,
|
||||
@@ -91,13 +91,21 @@ export const useNavigation = (): NavigationState => {
|
||||
getCurrentPage: () => previousPages[previousPages.length - 1],
|
||||
backToPreviousPage: () => {
|
||||
if (previousPages.length > 1) {
|
||||
const newPreviousPages = previousPages.slice(
|
||||
0,
|
||||
previousPages.length - 1
|
||||
);
|
||||
const newCurrentPage = newPreviousPages[newPreviousPages.length - 1];
|
||||
setHistory({
|
||||
previousPages: previousPages.slice(0, previousPages.length - 1),
|
||||
});
|
||||
return newCurrentPage;
|
||||
}
|
||||
return previousPages[0];
|
||||
},
|
||||
openHome: () => {
|
||||
setHistory({ previousPages: [assetStoreHomePageState] });
|
||||
return assetStoreHomePageState;
|
||||
},
|
||||
clearHistory: () => {
|
||||
setHistory(previousHistory => {
|
||||
|
@@ -104,17 +104,16 @@ export const AssetStore = React.forwardRef<Props, AssetStoreInterface>(
|
||||
assetFiltersState,
|
||||
assetPackRandomOrdering,
|
||||
} = React.useContext(AssetStoreContext);
|
||||
const currentPage = navigationState.getCurrentPage();
|
||||
const {
|
||||
openedAssetPack,
|
||||
openedAssetShortHeader,
|
||||
openedAssetCategory,
|
||||
openedPrivateAssetPackListingData,
|
||||
filtersState,
|
||||
} = navigationState.getCurrentPage();
|
||||
const isOnHomePage = isHomePage(navigationState.getCurrentPage());
|
||||
const isOnSearchResultPage = isSearchResultPage(
|
||||
navigationState.getCurrentPage()
|
||||
);
|
||||
} = currentPage;
|
||||
const isOnHomePage = isHomePage(currentPage);
|
||||
const isOnSearchResultPage = isSearchResultPage(currentPage);
|
||||
const searchBar = React.useRef<?SearchBarInterface>(null);
|
||||
const shouldAutofocusSearchbar = useShouldAutofocusInput();
|
||||
|
||||
@@ -152,14 +151,10 @@ export const AssetStore = React.forwardRef<Props, AssetStoreInterface>(
|
||||
const isAssetDetailLoading = React.useRef<boolean>(
|
||||
openedAssetShortHeader != null
|
||||
);
|
||||
const setScrollUpdateIsNeeded = React.useCallback(
|
||||
() => {
|
||||
hasAppliedSavedScrollPosition.current = false;
|
||||
isAssetDetailLoading.current =
|
||||
navigationState.getCurrentPage().openedAssetShortHeader != null;
|
||||
},
|
||||
[navigationState]
|
||||
);
|
||||
const setScrollUpdateIsNeeded = React.useCallback(page => {
|
||||
hasAppliedSavedScrollPosition.current = false;
|
||||
isAssetDetailLoading.current = page.openedAssetShortHeader !== null;
|
||||
}, []);
|
||||
|
||||
const canShowFiltersPanel =
|
||||
!openedAssetShortHeader && // Don't show filters on asset page.
|
||||
@@ -184,9 +179,9 @@ export const AssetStore = React.forwardRef<Props, AssetStoreInterface>(
|
||||
if (!scrollView) {
|
||||
return;
|
||||
}
|
||||
navigationState.getCurrentPage().scrollPosition = scrollView.getScrollPosition();
|
||||
currentPage.scrollPosition = scrollView.getScrollPosition();
|
||||
},
|
||||
[getScrollView, navigationState]
|
||||
[getScrollView, currentPage]
|
||||
);
|
||||
// This is also called when the asset detail page has loaded.
|
||||
const applyBackScrollPosition = React.useCallback(
|
||||
@@ -198,7 +193,7 @@ export const AssetStore = React.forwardRef<Props, AssetStoreInterface>(
|
||||
if (!scrollView) {
|
||||
return;
|
||||
}
|
||||
const scrollPosition = navigationState.getCurrentPage().scrollPosition;
|
||||
const scrollPosition = currentPage.scrollPosition;
|
||||
if (scrollPosition) scrollView.scrollToPosition(scrollPosition);
|
||||
// If no saved scroll position, force scroll to 0 in case the displayed component
|
||||
// is the same as the previous page so the scroll is naturally kept between pages
|
||||
@@ -206,22 +201,13 @@ export const AssetStore = React.forwardRef<Props, AssetStoreInterface>(
|
||||
else scrollView.scrollToPosition(0);
|
||||
hasAppliedSavedScrollPosition.current = true;
|
||||
},
|
||||
[getScrollView, navigationState]
|
||||
[getScrollView, currentPage]
|
||||
);
|
||||
|
||||
React.useImperativeHandle(ref, () => ({
|
||||
onClose: saveScrollPosition,
|
||||
}));
|
||||
|
||||
React.useLayoutEffect(
|
||||
() => {
|
||||
if (!isAssetDetailLoading.current) {
|
||||
applyBackScrollPosition();
|
||||
}
|
||||
},
|
||||
[applyBackScrollPosition]
|
||||
);
|
||||
|
||||
const onOpenDetails = React.useCallback(
|
||||
(assetShortHeader: AssetShortHeader) => {
|
||||
const assetPackName = openedAssetPack ? openedAssetPack.name : null;
|
||||
@@ -342,7 +328,6 @@ export const AssetStore = React.forwardRef<Props, AssetStoreInterface>(
|
||||
() => {
|
||||
if (!purchasingPrivateAssetPackListingData) return;
|
||||
// Ensure the user is not already on the pack page, to trigger the effect only once.
|
||||
const currentPage = navigationState.getCurrentPage();
|
||||
const isOnPrivatePackPage =
|
||||
currentPage.openedAssetPack &&
|
||||
currentPage.openedAssetPack.id &&
|
||||
@@ -365,6 +350,7 @@ export const AssetStore = React.forwardRef<Props, AssetStoreInterface>(
|
||||
receivedAssetPacks,
|
||||
purchasingPrivateAssetPackListingData,
|
||||
navigationState,
|
||||
currentPage,
|
||||
saveScrollPosition,
|
||||
setSearchText,
|
||||
openFiltersPanelIfAppropriate,
|
||||
@@ -443,14 +429,22 @@ export const AssetStore = React.forwardRef<Props, AssetStoreInterface>(
|
||||
|
||||
React.useLayoutEffect(
|
||||
() => {
|
||||
if (isOnHomePage) {
|
||||
clearAllFilters(assetFiltersState);
|
||||
setIsFiltersPanelOpen(false);
|
||||
}
|
||||
// When going back to the homepage from a page where the asset filters
|
||||
// were open, we must first close the panel and then apply the scroll position.
|
||||
const applyEffect = async () => {
|
||||
if (isOnHomePage) {
|
||||
clearAllFilters(assetFiltersState);
|
||||
await setIsFiltersPanelOpen(false);
|
||||
}
|
||||
if (!isAssetDetailLoading.current) {
|
||||
applyBackScrollPosition();
|
||||
}
|
||||
};
|
||||
applyEffect();
|
||||
},
|
||||
// assetFiltersState is not stable, so don't list it.
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[isOnHomePage]
|
||||
[isOnHomePage, applyBackScrollPosition]
|
||||
);
|
||||
|
||||
const privateAssetPackFromSameCreator: ?Array<PrivateAssetPackListingData> = React.useMemo(
|
||||
@@ -483,8 +477,8 @@ export const AssetStore = React.forwardRef<Props, AssetStoreInterface>(
|
||||
tooltip={t`Back to discover`}
|
||||
onClick={() => {
|
||||
setSearchText('');
|
||||
navigationState.openHome();
|
||||
setScrollUpdateIsNeeded();
|
||||
const page = navigationState.openHome();
|
||||
setScrollUpdateIsNeeded(page);
|
||||
clearAllFilters(assetFiltersState);
|
||||
setIsFiltersPanelOpen(false);
|
||||
}}
|
||||
@@ -538,8 +532,8 @@ export const AssetStore = React.forwardRef<Props, AssetStoreInterface>(
|
||||
label={<Trans>Back</Trans>}
|
||||
primary={false}
|
||||
onClick={() => {
|
||||
navigationState.backToPreviousPage();
|
||||
setScrollUpdateIsNeeded();
|
||||
const page = navigationState.backToPreviousPage();
|
||||
setScrollUpdateIsNeeded(page);
|
||||
}}
|
||||
/>
|
||||
</Column>
|
||||
|
@@ -9,18 +9,58 @@ import {
|
||||
type MainMenuEvent,
|
||||
} from './MainMenu';
|
||||
const electron = optionalRequire('electron');
|
||||
const remote = optionalRequire('@electron/remote');
|
||||
const app = remote ? remote.app : null;
|
||||
const ipcRenderer = electron ? electron.ipcRenderer : null;
|
||||
|
||||
// Custom hook to register and deregister IPC listener
|
||||
const useIPCEventListener = (ipcEvent: MainMenuEvent, func) => {
|
||||
const useIPCEventListener = ({
|
||||
ipcEvent,
|
||||
callback,
|
||||
shouldApply,
|
||||
}: {
|
||||
ipcEvent: MainMenuEvent,
|
||||
callback: Function,
|
||||
shouldApply: boolean,
|
||||
}) => {
|
||||
React.useEffect(
|
||||
() => {
|
||||
if (!ipcRenderer) return;
|
||||
const handler = (event, ...eventArgs) => func(...eventArgs);
|
||||
if (!ipcRenderer || !shouldApply) return;
|
||||
|
||||
const handler = (event, ...eventArgs) => callback(...eventArgs);
|
||||
ipcRenderer.on(ipcEvent, handler);
|
||||
return () => ipcRenderer.removeListener(ipcEvent, handler);
|
||||
},
|
||||
[ipcEvent, func]
|
||||
[ipcEvent, callback, shouldApply]
|
||||
);
|
||||
};
|
||||
|
||||
const useAppEventListener = ({
|
||||
event,
|
||||
callback,
|
||||
}: {
|
||||
event: string,
|
||||
callback: Function,
|
||||
}) => {
|
||||
React.useEffect(
|
||||
() => {
|
||||
if (!app) return;
|
||||
const handler = (event, ...eventArgs) => callback(...eventArgs);
|
||||
app.on(event, handler);
|
||||
return () => app.removeListener(event, handler);
|
||||
},
|
||||
[event, callback]
|
||||
);
|
||||
};
|
||||
|
||||
const isMainWindow = (windowTitle: string): boolean => {
|
||||
if (!windowTitle) return false;
|
||||
const lowercaseTitle = windowTitle.toLowerCase();
|
||||
return (
|
||||
lowercaseTitle.startsWith('gdevelop') &&
|
||||
lowercaseTitle !== 'gdevelop dialogue tree editor (yarn)' &&
|
||||
lowercaseTitle !== 'gdevelop sound effects editor (jfxr)' &&
|
||||
lowercaseTitle !== 'gdevelop image editor (piskel)'
|
||||
);
|
||||
};
|
||||
|
||||
@@ -36,36 +76,115 @@ const ElectronMainMenu = ({
|
||||
|}) => {
|
||||
const { i18n, project, recentProjectFiles, shortcutMap } = props;
|
||||
const language = i18n.language;
|
||||
const [
|
||||
isFocusedOnMainWindow,
|
||||
setIsFocusedOnMainWindow,
|
||||
] = React.useState<boolean>(true);
|
||||
useAppEventListener({
|
||||
event: 'browser-window-focus',
|
||||
callback: window => {
|
||||
setIsFocusedOnMainWindow(isMainWindow(window.title));
|
||||
},
|
||||
});
|
||||
useAppEventListener({
|
||||
event: 'browser-window-blur',
|
||||
callback: window => {
|
||||
setIsFocusedOnMainWindow(!isMainWindow(window.title));
|
||||
},
|
||||
});
|
||||
|
||||
// We could use a for loop, but for safety let's write every hook one by
|
||||
// one to avoid any change at runtime which would break the rules of hooks.
|
||||
useIPCEventListener('main-menu-open', callbacks.onChooseProject);
|
||||
useIPCEventListener('main-menu-open-recent', callbacks.onOpenRecentFile);
|
||||
useIPCEventListener('main-menu-save', callbacks.onSaveProject);
|
||||
useIPCEventListener('main-menu-save-as', callbacks.onSaveProjectAs);
|
||||
useIPCEventListener('main-menu-close', callbacks.onCloseProject);
|
||||
useIPCEventListener('main-menu-close-app', callbacks.onCloseApp);
|
||||
useIPCEventListener('main-menu-export', callbacks.onExportProject);
|
||||
useIPCEventListener('main-menu-create-template', callbacks.onCreateProject);
|
||||
useIPCEventListener('main-menu-create-blank', callbacks.onCreateBlank);
|
||||
useIPCEventListener(
|
||||
'main-menu-open-project-manager',
|
||||
callbacks.onOpenProjectManager
|
||||
);
|
||||
useIPCEventListener('main-menu-open-home-page', callbacks.onOpenHomePage);
|
||||
useIPCEventListener('main-menu-open-debugger', callbacks.onOpenDebugger);
|
||||
useIPCEventListener('main-menu-open-about', callbacks.onOpenAbout);
|
||||
useIPCEventListener(
|
||||
'main-menu-open-preferences',
|
||||
callbacks.onOpenPreferences
|
||||
);
|
||||
useIPCEventListener('main-menu-open-language', callbacks.onOpenLanguage);
|
||||
useIPCEventListener('main-menu-open-profile', callbacks.onOpenProfile);
|
||||
useIPCEventListener(
|
||||
'main-menu-open-games-dashboard',
|
||||
callbacks.onOpenGamesDashboard
|
||||
);
|
||||
useIPCEventListener('update-status', callbacks.setElectronUpdateStatus);
|
||||
useIPCEventListener({
|
||||
ipcEvent: 'main-menu-open',
|
||||
callback: callbacks.onChooseProject,
|
||||
shouldApply: isFocusedOnMainWindow,
|
||||
});
|
||||
useIPCEventListener({
|
||||
ipcEvent: 'main-menu-open-recent',
|
||||
callback: callbacks.onOpenRecentFile,
|
||||
shouldApply: isFocusedOnMainWindow,
|
||||
});
|
||||
useIPCEventListener({
|
||||
ipcEvent: 'main-menu-save',
|
||||
callback: callbacks.onSaveProject,
|
||||
shouldApply: isFocusedOnMainWindow,
|
||||
});
|
||||
useIPCEventListener({
|
||||
ipcEvent: 'main-menu-save-as',
|
||||
callback: callbacks.onSaveProjectAs,
|
||||
shouldApply: isFocusedOnMainWindow,
|
||||
});
|
||||
useIPCEventListener({
|
||||
ipcEvent: 'main-menu-close',
|
||||
callback: callbacks.onCloseProject,
|
||||
shouldApply: isFocusedOnMainWindow,
|
||||
});
|
||||
useIPCEventListener({
|
||||
ipcEvent: 'main-menu-close-app',
|
||||
callback: callbacks.onCloseApp,
|
||||
shouldApply: isFocusedOnMainWindow,
|
||||
});
|
||||
useIPCEventListener({
|
||||
ipcEvent: 'main-menu-export',
|
||||
callback: callbacks.onExportProject,
|
||||
shouldApply: isFocusedOnMainWindow,
|
||||
});
|
||||
useIPCEventListener({
|
||||
ipcEvent: 'main-menu-create-template',
|
||||
callback: callbacks.onCreateProject,
|
||||
shouldApply: isFocusedOnMainWindow,
|
||||
});
|
||||
useIPCEventListener({
|
||||
ipcEvent: 'main-menu-create-blank',
|
||||
callback: callbacks.onCreateBlank,
|
||||
shouldApply: isFocusedOnMainWindow,
|
||||
});
|
||||
useIPCEventListener({
|
||||
ipcEvent: 'main-menu-open-project-manager',
|
||||
callback: callbacks.onOpenProjectManager,
|
||||
shouldApply: isFocusedOnMainWindow,
|
||||
});
|
||||
useIPCEventListener({
|
||||
ipcEvent: 'main-menu-open-home-page',
|
||||
callback: callbacks.onOpenHomePage,
|
||||
shouldApply: isFocusedOnMainWindow,
|
||||
});
|
||||
useIPCEventListener({
|
||||
ipcEvent: 'main-menu-open-debugger',
|
||||
callback: callbacks.onOpenDebugger,
|
||||
shouldApply: isFocusedOnMainWindow,
|
||||
});
|
||||
useIPCEventListener({
|
||||
ipcEvent: 'main-menu-open-about',
|
||||
callback: callbacks.onOpenAbout,
|
||||
shouldApply: isFocusedOnMainWindow,
|
||||
});
|
||||
useIPCEventListener({
|
||||
ipcEvent: 'main-menu-open-preferences',
|
||||
callback: callbacks.onOpenPreferences,
|
||||
shouldApply: isFocusedOnMainWindow,
|
||||
});
|
||||
useIPCEventListener({
|
||||
ipcEvent: 'main-menu-open-language',
|
||||
callback: callbacks.onOpenLanguage,
|
||||
shouldApply: isFocusedOnMainWindow,
|
||||
});
|
||||
useIPCEventListener({
|
||||
ipcEvent: 'main-menu-open-profile',
|
||||
callback: callbacks.onOpenProfile,
|
||||
shouldApply: isFocusedOnMainWindow,
|
||||
});
|
||||
useIPCEventListener({
|
||||
ipcEvent: 'main-menu-open-games-dashboard',
|
||||
callback: callbacks.onOpenGamesDashboard,
|
||||
shouldApply: isFocusedOnMainWindow,
|
||||
});
|
||||
useIPCEventListener({
|
||||
ipcEvent: 'update-status',
|
||||
callback: callbacks.setElectronUpdateStatus,
|
||||
shouldApply: true, // Keep logic around app update even if on preview window
|
||||
});
|
||||
|
||||
React.useEffect(
|
||||
() => {
|
||||
|
@@ -42,6 +42,9 @@ const ProjectTitlebar = React.memo<Props>(
|
||||
React.useEffect(
|
||||
() => {
|
||||
const title = [
|
||||
// On desktop app, this title is used to know if the user is focused on the
|
||||
// main window (IDE) or on another window (preview or external editor).
|
||||
// Should this be changed, you should also change ElectronMainMenu.js
|
||||
'GDevelop 5',
|
||||
projectIdentifier ? `${projectIdentifier}${suffix}` : '',
|
||||
storageProviderName,
|
||||
|
@@ -227,7 +227,7 @@ app.on('ready', function() {
|
||||
}, 300)
|
||||
).then(
|
||||
() => {
|
||||
log.info('Local file upload succesfully done');
|
||||
log.info('Local file upload successfully done');
|
||||
event.sender.send('local-file-upload-done', null);
|
||||
},
|
||||
uploadError => {
|
||||
|
Reference in New Issue
Block a user