Compare commits

...

7 Commits

Author SHA1 Message Date
AlexandreSi
67acc82775 Apply scroll position after the filters panel was closed to avoid scroll offset 2023-03-10 15:00:06 +01:00
AlexandreSi
933cc2f3ed Solve inconsistent state issue 2023-03-10 14:35:25 +01:00
AlexandreSi
a1500ba01f Add comments 2023-03-10 11:29:37 +01:00
AlexandreSi
b114a3ff18 Use objects 2023-03-10 11:28:24 +01:00
AlexandreSi
88e14c655e Deactivate shortcuts handled by Electron when not focused on main window 2023-03-10 11:06:10 +01:00
AlexandreSi
abb36e62a2 Remove interception of input events in preview window 2023-03-10 10:35:55 +01:00
AlexandreSi
47751d2677 Intercept Cmd+W shortcut in preview in order to close preview 2023-03-09 16:45:29 +01:00
9 changed files with 205 additions and 72 deletions

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -112,8 +112,8 @@ export const AssetStoreContext = React.createContext<AssetStoreState>({
},
navigationState: {
getCurrentPage: () => assetStoreHomePageState,
backToPreviousPage: () => {},
openHome: () => {},
backToPreviousPage: () => assetStoreHomePageState,
openHome: () => assetStoreHomePageState,
clearHistory: () => {},
openSearchResultPage: () => {},
openTagPage: tag => {},

View File

@@ -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 => {

View File

@@ -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>

View File

@@ -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(
() => {

View File

@@ -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,

View File

@@ -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 => {