Skip to content

Commit debdb7a

Browse files
4ianGleb Volkov
andauthored
Add "Reload Project" menu action (#8501)
Co-authored-by: Gleb Volkov <glebusheg@playtika.com>
1 parent 66ed69c commit debdb7a

7 files changed

Lines changed: 126 additions & 8 deletions

File tree

newIDE/app/src/CommandPalette/CommandsList.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export type CommandName =
1717
| 'SAVE_PROJECT'
1818
| 'SAVE_PROJECT_AS'
1919
| 'CLOSE_PROJECT'
20+
| 'RELOAD_PROJECT'
2021
| 'EXPORT_GAME'
2122
| 'INVITE_COLLABORATORS'
2223
| 'OPEN_RECENT_PROJECT'
@@ -151,6 +152,10 @@ const commandsList: { [CommandName]: CommandMetadata } = {
151152
displayText: t`Close project`,
152153
handledByElectron: true,
153154
},
155+
RELOAD_PROJECT: {
156+
area: 'GENERAL',
157+
displayText: t`Reload project from disk/cloud (lose all changes)`,
158+
},
154159
EXPORT_GAME: {
155160
area: 'PROJECT',
156161
displayText: t`Export game`,

newIDE/app/src/KeyboardShortcuts/DefaultShortcuts.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ const defaultShortcuts: ShortcutMap = {
1717
SAVE_PROJECT: 'CmdOrCtrl+KeyS',
1818
SAVE_PROJECT_AS: 'CmdOrCtrl+Shift+KeyS',
1919
CLOSE_PROJECT: 'CmdOrCtrl+KeyW',
20+
RELOAD_PROJECT: '',
2021
EXPORT_GAME: 'CmdOrCtrl+Shift+KeyE',
2122
INVITE_COLLABORATORS: 'CmdOrCtrl+Shift+KeyI',
2223
OPEN_RECENT_PROJECT: '',

newIDE/app/src/MainFrame/MainFrameCommands.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ type CommandHandlers = {|
5858
onSaveProjectAs: () => void,
5959
onCloseApp: () => void,
6060
onCloseProject: () => Promise<void>,
61+
onReloadProject: () => Promise<void>,
6162
onExportGame: () => void,
6263
onInviteCollaborators: () => void,
6364
onOpenLayout: string => void,
@@ -150,6 +151,10 @@ const useMainFrameCommands = (handlers: CommandHandlers) => {
150151
handler: handlers.onCloseProject,
151152
});
152153

154+
useCommand('RELOAD_PROJECT', !!handlers.project, {
155+
handler: handlers.onReloadProject,
156+
});
157+
153158
useCommand('EXPORT_GAME', !!handlers.project, {
154159
handler: handlers.onExportGame,
155160
});

newIDE/app/src/MainFrame/index.js

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1176,14 +1176,14 @@ const MainFrame = (props: Props): React.MixedElement => {
11761176

11771177
// Read and apply project settings from gdevelop-settings.yaml if it exists
11781178
try {
1179-
const rawSettings = await readProjectSettings(
1179+
const parsedProjectSettings = await readProjectSettings(
11801180
updatedFileMetadata.fileIdentifier
11811181
);
1182-
if (rawSettings) {
1183-
applyProjectPreferences(rawSettings.preferences, preferences);
1182+
if (parsedProjectSettings) {
1183+
applyProjectPreferences(parsedProjectSettings, preferences);
11841184
setState(currentState => ({
11851185
...currentState,
1186-
toolbarButtons: rawSettings.toolbarButtons || [],
1186+
toolbarButtons: parsedProjectSettings.toolbarButtons || [],
11871187
}));
11881188
}
11891189
} catch (error) {
@@ -4311,6 +4311,41 @@ const MainFrame = (props: Props): React.MixedElement => {
43114311
[currentProject, hasUnsavedChanges, i18n, closeProject]
43124312
);
43134313

4314+
const reloadProject = React.useCallback(
4315+
async (): Promise<void> => {
4316+
if (!currentProject || !currentFileMetadata) return;
4317+
4318+
if (hasUnsavedChanges) {
4319+
const answer = Window.showConfirmDialog(
4320+
i18n._(
4321+
t`Reload the project? Any changes that have not been saved will be lost.`
4322+
)
4323+
);
4324+
if (!answer) return;
4325+
}
4326+
4327+
const storageProviderName = getStorageProvider().internalName;
4328+
await openFromFileMetadataWithStorageProvider(
4329+
{
4330+
fileMetadata: currentFileMetadata,
4331+
storageProviderName,
4332+
},
4333+
{
4334+
ignoreUnsavedChanges: true,
4335+
ignoreAutoSave: true,
4336+
}
4337+
);
4338+
},
4339+
[
4340+
currentProject,
4341+
currentFileMetadata,
4342+
hasUnsavedChanges,
4343+
i18n,
4344+
getStorageProvider,
4345+
openFromFileMetadataWithStorageProvider,
4346+
]
4347+
);
4348+
43144349
const endTutorial = React.useCallback(
43154350
async (shouldCloseProject?: boolean) => {
43164351
if (shouldCloseProject) {
@@ -4805,6 +4840,7 @@ const MainFrame = (props: Props): React.MixedElement => {
48054840
onCloseProject: async () => {
48064841
askToCloseProject();
48074842
},
4843+
onReloadProject: reloadProject,
48084844
onExportGame: () => {
48094845
openShareDialog('publish');
48104846
},

newIDE/app/src/Utils/ApplyProjectPreferences.js

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import {
33
type Preferences,
44
type ProjectSpecificPreferencesValues,
55
} from '../MainFrame/Preferences/PreferencesContext';
6+
import { type CommandName } from '../CommandPalette/CommandsList';
7+
import { type ParsedProjectSettings } from './ProjectSettingsReader';
68

79
/** Allowlist of preference keys that can be overridden per-project. */
810
const allowedPreferenceKeys: $ReadOnlyArray<
@@ -85,12 +87,13 @@ const normalizeDeprecatedInstructionWarning = (
8587
};
8688

8789
/**
88-
* Applies project-specific preferences from a gdevelop-settings.yaml file to the editor.
90+
* Applies project-specific preferences and shortcuts from a gdevelop-settings.yaml file to the editor.
8991
*/
9092
export const applyProjectPreferences = (
91-
rawPreferences: ?{ [string]: boolean | string | number },
93+
parsedProjectSettings: ParsedProjectSettings,
9294
preferences: Preferences
9395
): void => {
96+
const rawPreferences = parsedProjectSettings.preferences;
9497
if (rawPreferences) {
9598
const filtered = filterAllowedPreferences(rawPreferences);
9699
if (
@@ -106,4 +109,13 @@ export const applyProjectPreferences = (
106109
}
107110
preferences.setMultipleValues(filtered);
108111
}
112+
113+
if (parsedProjectSettings.shortcuts) {
114+
const shortcuts = parsedProjectSettings.shortcuts;
115+
for (const key of Object.keys(shortcuts)) {
116+
// $FlowFixMe[incompatible-call]
117+
const commandName: CommandName = (key: any);
118+
preferences.setShortcutForCommand(commandName, shortcuts[key]);
119+
}
120+
}
109121
};

newIDE/app/src/Utils/ProjectSettingsReader.js

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const path = optionalRequire('path');
1111
export type ParsedProjectSettings = {
1212
preferences?: { [string]: boolean | string | number },
1313
toolbarButtons?: Array<ToolbarButtonConfig>,
14+
shortcuts?: { [string]: string },
1415
};
1516

1617
const SETTINGS_FILE_NAME = 'gdevelop-settings.yaml';
@@ -126,10 +127,27 @@ export const readProjectSettings = async (
126127
}
127128
}
128129

130+
// Parse shortcuts section
131+
const rawShortcuts = SafeExtractor.extractObjectProperty(
132+
parsed,
133+
'shortcuts'
134+
);
135+
const shortcuts: { [string]: string } = {};
136+
if (rawShortcuts) {
137+
for (const key of Object.keys(rawShortcuts)) {
138+
const value = SafeExtractor.extractStringProperty(rawShortcuts, key);
139+
if (value !== null) {
140+
shortcuts[key] = value;
141+
}
142+
}
143+
}
144+
129145
console.info(
130146
`[ProjectSettingsReader] Loaded: ${
131147
Object.keys(preferences).length
132-
} preferences, ${toolbarButtons.length} buttons`
148+
} preferences, ${toolbarButtons.length} buttons, ${
149+
Object.keys(shortcuts).length
150+
} shortcuts`
133151
);
134152

135153
const result: ParsedProjectSettings = {};
@@ -139,6 +157,9 @@ export const readProjectSettings = async (
139157
if (toolbarButtons.length > 0) {
140158
result.toolbarButtons = toolbarButtons;
141159
}
160+
if (Object.keys(shortcuts).length > 0) {
161+
result.shortcuts = shortcuts;
162+
}
142163
return result;
143164
} catch (error) {
144165
console.error(

newIDE/app/src/Utils/ProjectSettingsReader.spec.js

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,14 +76,17 @@ preferences:
7676
// Step 4: Apply to preferences via setMultipleValues
7777
// $FlowFixMe[underconstrained-implicit-instantiation]
7878
const mockSetMultipleValues = jest.fn();
79+
// $FlowFixMe[underconstrained-implicit-instantiation]
80+
const mockSetShortcutForCommand = jest.fn();
7981
// $FlowFixMe[incompatible-type] - partial mock
8082
const mockPreferences: Preferences = {
8183
// $FlowFixMe[incompatible-type] - partial mock
8284
values: {},
8385
setMultipleValues: mockSetMultipleValues,
86+
setShortcutForCommand: mockSetShortcutForCommand,
8487
};
8588

86-
applyProjectPreferences(rawPreferences, mockPreferences);
89+
applyProjectPreferences({ preferences: rawPreferences }, mockPreferences);
8790

8891
expect(mockSetMultipleValues).toHaveBeenCalledWith({
8992
autosaveOnPreview: true,
@@ -99,6 +102,41 @@ preferences:
99102
});
100103
});
101104

105+
test('shortcuts from gdevelop-settings.yaml are properly applied to preferences', () => {
106+
// $FlowFixMe[underconstrained-implicit-instantiation]
107+
const mockSetMultipleValues = jest.fn();
108+
// $FlowFixMe[underconstrained-implicit-instantiation]
109+
const mockSetShortcutForCommand = jest.fn();
110+
// $FlowFixMe[incompatible-type] - partial mock
111+
const mockPreferences: Preferences = {
112+
// $FlowFixMe[incompatible-type] - partial mock
113+
values: {},
114+
setMultipleValues: mockSetMultipleValues,
115+
setShortcutForCommand: mockSetShortcutForCommand,
116+
};
117+
118+
applyProjectPreferences(
119+
{
120+
shortcuts: {
121+
RELOAD_PROJECT: 'CmdOrCtrl+Shift+R',
122+
OPEN_PROJECT_PROPERTIES: 'CmdOrCtrl+Shift+P',
123+
},
124+
},
125+
mockPreferences
126+
);
127+
128+
expect(mockSetShortcutForCommand).toHaveBeenCalledTimes(2);
129+
expect(mockSetShortcutForCommand).toHaveBeenCalledWith(
130+
'RELOAD_PROJECT',
131+
'CmdOrCtrl+Shift+R'
132+
);
133+
expect(mockSetShortcutForCommand).toHaveBeenCalledWith(
134+
'OPEN_PROJECT_PROPERTIES',
135+
'CmdOrCtrl+Shift+P'
136+
);
137+
expect(mockSetMultipleValues).not.toHaveBeenCalled();
138+
});
139+
102140
test('readProjectSettings would return null when preferences section is missing', () => {
103141
const yamlContent = `
104142
# Project settings without preferences

0 commit comments

Comments
 (0)