WIP: robustify scene change and initial scene for in game edition

This commit is contained in:
Florian Rival
2024-12-29 17:23:20 +01:00
parent 74401a1f9c
commit 9a31dd046c
14 changed files with 295 additions and 224 deletions

View File

@@ -169,6 +169,14 @@ struct PreviewExportOptions {
return *this;
}
/**
* \brief Set if the export is made for being edited in the editor.
*/
PreviewExportOptions &SetIsInGameEdition(bool enable) {
isInGameEdition = enable;
return *this;
}
/**
* \brief If set to a non zero value, the exported script URLs will have an
* extra search parameter added (with the given value) to ensure browser cache
@@ -291,6 +299,7 @@ struct PreviewExportOptions {
bool projectDataOnlyExport;
bool fullLoadingScreen;
bool isDevelopmentEnvironment;
bool isInGameEdition;
unsigned int nonRuntimeScriptsCacheBurst;
gd::String electronRemoteRequirePath;
gd::String gdevelopResourceToken;

View File

@@ -263,15 +263,24 @@ namespace gdjs {
} else if (data.command === 'hotReload') {
that._hotReloader.hotReload().then((logs) => {
that.sendHotReloaderLogs(logs);
// TODO: if fatal error, should probably reload. The editor should handle this
// as it knows the current scene to show.
});
} else if (data.command === 'requestSceneChange') {
} else if (data.command === 'requestSceneReplace') {
const sceneName = data.sceneName || null;
if (!sceneName) {
logger.warn('No scene name specified, requestSceneChange aborted');
logger.warn('No scene name specified, requestSceneReplace aborted');
return;
}
const currentScene = runtimeGame.getSceneStack().getCurrentScene();
if (currentScene && currentScene.getName() === sceneName) {
return;
}
runtimeGame.getSceneStack().replace(sceneName, true);
// TODO: if fatal error, should probably reload. The editor should handle this
// as it knows the current scene to show.
} else if (data.command === 'updateInstances') {
// TODO: do an update/partial hot reload of the instances
} else {

View File

@@ -45,8 +45,12 @@ namespace gdjs {
export type RuntimeGameOptions = {
/** if true, force fullscreen. */
forceFullscreen?: boolean;
/** if true, game is run as a preview launched from an editor. */
isPreview?: boolean;
/** if true, game is run for being edited from the editor. */
isInGameEdition?: boolean;
/** The name of the external layout to create in the scene at position 0;0. */
injectExternalLayout?: string;
/** Script files, used for hot-reloading. */

View File

@@ -3843,6 +3843,7 @@ interface PreviewExportOptions {
[Ref] PreviewExportOptions SetNativeMobileApp(boolean enable);
[Ref] PreviewExportOptions SetFullLoadingScreen(boolean enable);
[Ref] PreviewExportOptions SetIsDevelopmentEnvironment(boolean enable);
[Ref] PreviewExportOptions SetIsInGameEdition(boolean enable);
[Ref] PreviewExportOptions SetNonRuntimeScriptsCacheBurst(unsigned long value);
[Ref] PreviewExportOptions SetElectronRemoteRequirePath([Const] DOMString electronRemoteRequirePath);
[Ref] PreviewExportOptions SetGDevelopResourceToken([Const] DOMString gdevelopResourceToken);

View File

@@ -2850,6 +2850,7 @@ export class PreviewExportOptions extends EmscriptenObject {
setNativeMobileApp(enable: boolean): PreviewExportOptions;
setFullLoadingScreen(enable: boolean): PreviewExportOptions;
setIsDevelopmentEnvironment(enable: boolean): PreviewExportOptions;
setIsInGameEdition(enable: boolean): PreviewExportOptions;
setNonRuntimeScriptsCacheBurst(value: number): PreviewExportOptions;
setElectronRemoteRequirePath(electronRemoteRequirePath: string): PreviewExportOptions;
setGDevelopResourceToken(gdevelopResourceToken: string): PreviewExportOptions;

View File

@@ -13,6 +13,7 @@ declare class gdPreviewExportOptions {
setNativeMobileApp(enable: boolean): gdPreviewExportOptions;
setFullLoadingScreen(enable: boolean): gdPreviewExportOptions;
setIsDevelopmentEnvironment(enable: boolean): gdPreviewExportOptions;
setIsInGameEdition(enable: boolean): gdPreviewExportOptions;
setNonRuntimeScriptsCacheBurst(value: number): gdPreviewExportOptions;
setElectronRemoteRequirePath(electronRemoteRequirePath: string): gdPreviewExportOptions;
setGDevelopResourceToken(gdevelopResourceToken: string): gdPreviewExportOptions;

View File

@@ -2,7 +2,7 @@
import * as React from 'react';
import { type PreviewDebuggerServer } from '../ExportAndShare/PreviewLauncher.flow';
type SwitchToPreviewOptions = {|
type AttachToPreviewOptions = {|
previewIndexHtmlLocation: string,
|};
@@ -10,14 +10,14 @@ type SwitchToSceneEditionOptions = {|
sceneName: string,
|};
let onSwitchToPreview: null | (SwitchToPreviewOptions => void) = null;
let onAttachToPreview: null | (AttachToPreviewOptions => void) = null;
let onSwitchToSceneEdition: null | (SwitchToSceneEditionOptions => void) = null;
export const switchToPreview = ({
export const attachToPreview = ({
previewIndexHtmlLocation,
}: SwitchToPreviewOptions) => {
if (!onSwitchToPreview) throw new Error('No EmbeddedGameFrame registered.');
onSwitchToPreview({ previewIndexHtmlLocation });
}: AttachToPreviewOptions) => {
if (!onAttachToPreview) throw new Error('No EmbeddedGameFrame registered.');
onAttachToPreview({ previewIndexHtmlLocation });
};
export const switchToSceneEdition = ({
@@ -30,36 +30,53 @@ export const switchToSceneEdition = ({
type Props = {|
previewDebuggerServer: PreviewDebuggerServer | null,
onLaunchPreviewForInGameEdition: ({| sceneName: string |}) => void,
|};
export const EmbeddedGameFrame = ({ previewDebuggerServer }: Props) => {
export const EmbeddedGameFrame = ({
previewDebuggerServer,
onLaunchPreviewForInGameEdition,
}: Props) => {
const [
previewIndexHtmlLocation,
setPreviewIndexHtmlLocation,
] = React.useState<string>('');
const iframeRef = React.useRef<HTMLIFrameElement | null>(null);
// TODO: display a loader when the preview is being loaded.
React.useEffect(() => {
// TODO: use a real context for this?
onSwitchToPreview = (options: SwitchToPreviewOptions) => {
setPreviewIndexHtmlLocation(options.previewIndexHtmlLocation);
if (iframeRef.current) {
iframeRef.current.contentWindow.focus();
}
};
onSwitchToSceneEdition = (options: SwitchToSceneEditionOptions) => {
if (!previewDebuggerServer) return;
React.useEffect(
() => {
// TODO: use a real context for this?
onAttachToPreview = (options: AttachToPreviewOptions) => {
setPreviewIndexHtmlLocation(options.previewIndexHtmlLocation);
if (iframeRef.current) {
iframeRef.current.contentWindow.focus();
}
};
onSwitchToSceneEdition = (options: SwitchToSceneEditionOptions) => {
if (!previewDebuggerServer) return;
console.log('TODO: switch to scene edition', options);
previewDebuggerServer.getExistingDebuggerIds().forEach(debuggerId => {
previewDebuggerServer.sendMessage(debuggerId, {
command: 'requestSceneChange',
sceneName: options.sceneName,
});
});
};
}, [previewDebuggerServer]);
const { sceneName } = options;
if (!previewIndexHtmlLocation) {
console.info('Launching preview for embedded game.');
onLaunchPreviewForInGameEdition({ sceneName });
} else {
console.info(`Switching previews to scene "${sceneName}".`);
previewDebuggerServer.getExistingDebuggerIds().forEach(debuggerId => {
previewDebuggerServer.sendMessage(debuggerId, {
command: 'requestSceneReplace',
sceneName,
});
});
}
};
},
[
previewDebuggerServer,
previewIndexHtmlLocation,
onLaunchPreviewForInGameEdition,
]
);
return (
<div style={{ position: 'absolute', top: 0, left: 0, right: 0, bottom: 0 }}>

View File

@@ -20,7 +20,7 @@ import { displayBlackLoadingScreenOrThrow } from '../../../Utils/BrowserExternal
import { getGDevelopResourceJwtToken } from '../../../Utils/GDevelopServices/Project';
import { isNativeMobileApp } from '../../../Utils/Platform';
import { getIDEVersionWithHash } from '../../../Version';
import { switchToPreview } from '../../../EmbeddedGame/EmbeddedGameFrame';
import { attachToPreview } from '../../../EmbeddedGame/EmbeddedGameFrame';
const gd: libGDevelop = global.gd;
type State = {|
@@ -159,6 +159,7 @@ export default class BrowserS3PreviewLauncher extends React.Component<
);
previewExportOptions.setLayoutName(layout.getName());
previewExportOptions.setIsDevelopmentEnvironment(Window.isDev());
previewExportOptions.setIsInGameEdition(previewOptions.isForInGameEdition);
if (externalLayout) {
previewExportOptions.setExternalLayoutName(externalLayout.getName());
}
@@ -221,9 +222,11 @@ export default class BrowserS3PreviewLauncher extends React.Component<
// Upload any file that must be exported for the preview.
await browserS3FileSystem.uploadPendingObjects();
switchToPreview({
previewIndexHtmlLocation: outputDir + '/index.html',
});
if (previewOptions.isForInGameEdition) {
attachToPreview({
previewIndexHtmlLocation: outputDir + '/index.html',
});
}
// Change the HTML file displayed by the preview window so that it starts loading
// the game.

View File

@@ -22,7 +22,7 @@ import {
} from './LocalPreviewDebuggerServer';
import Window from '../../../Utils/Window';
import { getIDEVersionWithHash } from '../../../Version';
import { switchToPreview } from '../../../EmbeddedGame/EmbeddedGameFrame';
import { attachToPreview } from '../../../EmbeddedGame/EmbeddedGameFrame';
const electron = optionalRequire('electron');
const path = optionalRequire('path');
const ipcRenderer = electron ? electron.ipcRenderer : null;
@@ -48,6 +48,28 @@ type State = {|
captureOptions: ?CaptureOptions,
|};
const prepareExporter = async (): Promise<{|
outputDir: string,
exporter: gdjsExporter,
gdjsRoot: string,
|}> => {
const { gdjsRoot } = await findGDJS();
console.info('GDJS found in ', gdjsRoot);
const localFileSystem = new LocalFileSystem({
downloadUrlsToLocalFiles: false,
});
const fileSystem = assignIn(new gd.AbstractFileSystemJS(), localFileSystem);
const outputDir = path.join(fileSystem.getTempDir(), 'preview');
const exporter = new gd.Exporter(fileSystem, gdjsRoot);
return {
outputDir,
exporter,
gdjsRoot,
};
};
export default class LocalPreviewLauncher extends React.Component<
PreviewLauncherProps,
State
@@ -172,171 +194,138 @@ export default class LocalPreviewLauncher extends React.Component<
);
};
_prepareExporter = (): Promise<{|
outputDir: string,
exporter: gdjsExporter,
gdjsRoot: string,
|}> => {
return findGDJS().then(({ gdjsRoot }) => {
console.info('GDJS found in ', gdjsRoot);
const localFileSystem = new LocalFileSystem({
downloadUrlsToLocalFiles: false,
});
const fileSystem = assignIn(
new gd.AbstractFileSystemJS(),
localFileSystem
);
const outputDir = path.join(fileSystem.getTempDir(), 'preview');
const exporter = new gd.Exporter(fileSystem, gdjsRoot);
return {
outputDir,
exporter,
gdjsRoot,
};
});
};
launchPreview = (previewOptions: PreviewOptions): Promise<any> => {
launchPreview = async (previewOptions: PreviewOptions): Promise<any> => {
const { project, layout, externalLayout } = previewOptions;
// Start the debugger server for previews. Even if not used,
// useful if the user opens the Debugger editor later, or want to
// hot reload.
return this.getPreviewDebuggerServer()
.startServer()
.catch(err => {
// Ignore any error when running the debugger server - the preview
// can still work without it.
console.error(
'Unable to start the Debugger Server for the preview:',
err
);
})
.then(() => this._prepareExporter())
.then(({ outputDir, exporter, gdjsRoot }) => {
timeFunction(
() => {
const previewExportOptions = new gd.PreviewExportOptions(
project,
outputDir
);
previewExportOptions.setIsDevelopmentEnvironment(Window.isDev());
previewExportOptions.setLayoutName(layout.getName());
if (externalLayout) {
previewExportOptions.setExternalLayoutName(
externalLayout.getName()
);
}
try {
await this.getPreviewDebuggerServer().startServer();
} catch (err) {
console.error(
'Unable to start the Debugger Server for the preview:',
err
);
}
const previewDebuggerServerAddress = getDebuggerServerAddress();
if (previewDebuggerServerAddress) {
previewExportOptions.useWebsocketDebuggerClientWithServerAddress(
previewDebuggerServerAddress.address,
'' + previewDebuggerServerAddress.port
);
}
const { outputDir, exporter, gdjsRoot } = await prepareExporter();
const includeFileHashs = this.props.getIncludeFileHashs();
for (const includeFile in includeFileHashs) {
const hash = includeFileHashs[includeFile];
previewExportOptions.setIncludeFileHash(includeFile, hash);
}
var previewStartTime = performance.now();
// Give the preview the path to the "@electron/remote" module of the editor,
// as this is required by some features and we've not removed dependency
// on "@electron/remote" yet.
previewExportOptions.setElectronRemoteRequirePath(
path.join(
gdjsRoot,
'../preview_node_modules',
'@electron/remote',
'renderer/index.js'
)
);
const previewExportOptions = new gd.PreviewExportOptions(
project,
outputDir
);
previewExportOptions.setIsDevelopmentEnvironment(Window.isDev());
previewExportOptions.setLayoutName(layout.getName());
previewExportOptions.setIsInGameEdition(previewOptions.isForInGameEdition);
if (externalLayout) {
previewExportOptions.setExternalLayoutName(externalLayout.getName());
}
const debuggerIds = this.getPreviewDebuggerServer().getExistingDebuggerIds();
const shouldHotReload =
previewOptions.hotReload && !!debuggerIds.length;
const previewDebuggerServerAddress = getDebuggerServerAddress();
if (previewDebuggerServerAddress) {
previewExportOptions.useWebsocketDebuggerClientWithServerAddress(
previewDebuggerServerAddress.address,
'' + previewDebuggerServerAddress.port
);
}
previewExportOptions.setProjectDataOnlyExport(
// Only export project data if asked and if a hot-reloading is being done.
shouldHotReload && previewOptions.projectDataOnlyExport
);
const includeFileHashs = this.props.getIncludeFileHashs();
for (const includeFile in includeFileHashs) {
const hash = includeFileHashs[includeFile];
previewExportOptions.setIncludeFileHash(includeFile, hash);
}
previewExportOptions.setFullLoadingScreen(
previewOptions.fullLoadingScreen
);
previewExportOptions.setGDevelopVersionWithHash(
getIDEVersionWithHash()
);
previewExportOptions.setCrashReportUploadLevel(
this.props.crashReportUploadLevel
);
previewExportOptions.setPreviewContext(this.props.previewContext);
previewExportOptions.setProjectTemplateSlug(
project.getTemplateSlug()
);
previewExportOptions.setSourceGameId(this.props.sourceGameId);
// Give the preview the path to the "@electron/remote" module of the editor,
// as this is required by some features and we've not removed dependency
// on "@electron/remote" yet.
previewExportOptions.setElectronRemoteRequirePath(
path.join(
gdjsRoot,
'../preview_node_modules',
'@electron/remote',
'renderer/index.js'
)
);
if (previewOptions.fallbackAuthor) {
previewExportOptions.setFallbackAuthor(
previewOptions.fallbackAuthor.id,
previewOptions.fallbackAuthor.username
);
}
if (previewOptions.authenticatedPlayer) {
previewExportOptions.setAuthenticatedPlayer(
previewOptions.authenticatedPlayer.playerId,
previewOptions.authenticatedPlayer.playerUsername,
previewOptions.authenticatedPlayer.playerToken
);
}
if (previewOptions.captureOptions) {
if (previewOptions.captureOptions.screenshots) {
previewOptions.captureOptions.screenshots.forEach(
screenshot => {
previewExportOptions.addScreenshotCapture(
screenshot.delayTimeInSeconds,
screenshot.signedUrl,
screenshot.publicUrl
);
}
);
}
}
const debuggerIds = this.getPreviewDebuggerServer().getExistingDebuggerIds();
const shouldHotReload = previewOptions.hotReload && !!debuggerIds.length;
exporter.exportProjectForPixiPreview(previewExportOptions);
previewExportOptions.delete();
exporter.delete();
previewExportOptions.setProjectDataOnlyExport(
// Only export project data if asked and if a hot-reloading is being done.
shouldHotReload && previewOptions.projectDataOnlyExport
);
if (shouldHotReload) {
debuggerIds.forEach(debuggerId => {
this.getPreviewDebuggerServer().sendMessage(debuggerId, {
command: 'hotReload',
});
});
previewExportOptions.setFullLoadingScreen(previewOptions.fullLoadingScreen);
previewExportOptions.setGDevelopVersionWithHash(getIDEVersionWithHash());
previewExportOptions.setCrashReportUploadLevel(
this.props.crashReportUploadLevel
);
previewExportOptions.setPreviewContext(this.props.previewContext);
previewExportOptions.setProjectTemplateSlug(project.getTemplateSlug());
previewExportOptions.setSourceGameId(this.props.sourceGameId);
if (
this.state.hotReloadsCount % 16 === 0 &&
this._hotReloadSubscriptionChecker
) {
this._hotReloadSubscriptionChecker.checkUserHasSubscription();
}
this.setState(state => ({
hotReloadsCount: state.hotReloadsCount + 1,
}));
} else {
switchToPreview({
previewIndexHtmlLocation: `file://${outputDir}/index.html`,
});
this._openPreviewWindow(project, outputDir, previewOptions);
}
},
time => console.info(`Preview took ${time}ms`)
);
if (previewOptions.fallbackAuthor) {
previewExportOptions.setFallbackAuthor(
previewOptions.fallbackAuthor.id,
previewOptions.fallbackAuthor.username
);
}
if (previewOptions.authenticatedPlayer) {
previewExportOptions.setAuthenticatedPlayer(
previewOptions.authenticatedPlayer.playerId,
previewOptions.authenticatedPlayer.playerUsername,
previewOptions.authenticatedPlayer.playerToken
);
}
if (previewOptions.captureOptions) {
if (previewOptions.captureOptions.screenshots) {
previewOptions.captureOptions.screenshots.forEach(screenshot => {
previewExportOptions.addScreenshotCapture(
screenshot.delayTimeInSeconds,
screenshot.signedUrl,
screenshot.publicUrl
);
});
}
}
exporter.exportProjectForPixiPreview(previewExportOptions);
previewExportOptions.delete();
exporter.delete();
if (shouldHotReload) {
debuggerIds.forEach(debuggerId => {
this.getPreviewDebuggerServer().sendMessage(debuggerId, {
command: 'hotReload',
});
});
if (
this.state.hotReloadsCount % 16 === 0 &&
this._hotReloadSubscriptionChecker
) {
this._hotReloadSubscriptionChecker.checkUserHasSubscription();
}
this.setState(state => ({
hotReloadsCount: state.hotReloadsCount + 1,
}));
} else {
if (previewOptions.isForInGameEdition) {
attachToPreview({
previewIndexHtmlLocation: `file://${outputDir}/index.html`,
});
}
if (previewOptions.numberOfWindows >= 1) {
this._openPreviewWindow(project, outputDir, previewOptions);
}
}
const previewStopTime = performance.now();
console.info(`Preview took ${previewStopTime - previewStartTime}ms`);
};
getPreviewDebuggerServer() {

View File

@@ -15,6 +15,7 @@ export type LaunchPreviewOptions = {
fullLoadingScreen?: boolean,
forceDiagnosticReport?: boolean,
numberOfWindows?: number,
isForInGameEdition?: {forcedSceneName: string},
launchCaptureOptions?: LaunchCaptureOptions,
};
export type CaptureOptions = {|
@@ -40,6 +41,7 @@ export type PreviewOptions = {|
playerToken: string,
},
numberOfWindows: number,
isForInGameEdition: boolean,
getIsMenuBarHiddenInPreview: () => boolean,
getIsAlwaysOnTopInPreview: () => boolean,
captureOptions: CaptureOptions,

View File

@@ -87,21 +87,21 @@ const PreviewAndShareButtons = React.memo<PreviewAndShareButtonsProps>(
click: async () => {
await onPreviewWithoutHotReload({ numberOfWindows: 2 });
},
enabled: isPreviewEnabled && !hasPreviewsRunning,
enabled: isPreviewEnabled,
},
{
label: i18n._(t`3 previews in 3 windows`),
click: async () => {
onPreviewWithoutHotReload({ numberOfWindows: 3 });
},
enabled: isPreviewEnabled && !hasPreviewsRunning,
enabled: isPreviewEnabled,
},
{
label: i18n._(t`4 previews in 4 windows`),
click: async () => {
onPreviewWithoutHotReload({ numberOfWindows: 4 });
},
enabled: isPreviewEnabled && !hasPreviewsRunning,
enabled: isPreviewEnabled,
},
],
},

View File

@@ -621,7 +621,6 @@ const MainFrame = (props: Props) => {
<ExtensionIcon />
) : null;
// Scene editors can have an embedded game, so they redefine manually
// which components can have clicks/touches.
const removePointerEvents = kind === 'layout';
@@ -1614,6 +1613,7 @@ const MainFrame = (props: Props) => {
fullLoadingScreen,
forceDiagnosticReport,
launchCaptureOptions,
isForInGameEdition,
}: LaunchPreviewOptions) => {
if (!currentProject) return;
if (currentProject.getLayoutsCount() === 0) return;
@@ -1624,10 +1624,14 @@ const MainFrame = (props: Props) => {
setPreviewLoading(true);
notifyPreviewOrExportWillStart(state.editorTabs);
const layoutName = previewState.isPreviewOverriden
const layoutName = isForInGameEdition
? isForInGameEdition.forcedSceneName
: previewState.isPreviewOverriden
? previewState.overridenPreviewLayoutName
: previewState.previewLayoutName;
const externalLayoutName = previewState.isPreviewOverriden
const externalLayoutName = isForInGameEdition
? null
: previewState.isPreviewOverriden
? previewState.overridenPreviewExternalLayoutName
: previewState.previewExternalLayoutName;
@@ -1679,23 +1683,25 @@ const MainFrame = (props: Props) => {
authenticatedPlayer,
getIsMenuBarHiddenInPreview: preferences.getIsMenuBarHiddenInPreview,
getIsAlwaysOnTopInPreview: preferences.getIsAlwaysOnTopInPreview,
numberOfWindows: numberOfWindows || 1,
numberOfWindows: numberOfWindows === undefined ? 1 : numberOfWindows,
isForInGameEdition: !!isForInGameEdition,
captureOptions,
onCaptureFinished,
});
setPreviewLoading(false);
sendPreviewStarted({
quickCustomizationGameId:
quickCustomizationDialogOpenedFromGameId || null,
networkPreview: !!networkPreview,
hotReload: !!hotReload,
projectDataOnlyExport: !!projectDataOnlyExport,
fullLoadingScreen: !!fullLoadingScreen,
numberOfWindows: numberOfWindows || 1,
forceDiagnosticReport: !!forceDiagnosticReport,
previewLaunchDuration: Date.now() - startTime,
});
if (!isForInGameEdition)
sendPreviewStarted({
quickCustomizationGameId:
quickCustomizationDialogOpenedFromGameId || null,
networkPreview: !!networkPreview,
hotReload: !!hotReload,
projectDataOnlyExport: !!projectDataOnlyExport,
fullLoadingScreen: !!fullLoadingScreen,
numberOfWindows: numberOfWindows || 1,
forceDiagnosticReport: !!forceDiagnosticReport,
previewLaunchDuration: Date.now() - startTime,
});
if (inAppTutorialOrchestratorRef.current) {
inAppTutorialOrchestratorRef.current.onPreviewLaunch();
@@ -1779,6 +1785,21 @@ const MainFrame = (props: Props) => {
[launchPreview]
);
const onLaunchPreviewForInGameEdition = React.useCallback(
({ sceneName }: {| sceneName: string |}) => {
launchPreview({
networkPreview: false,
hotReload: false,
forceDiagnosticReport: false,
isForInGameEdition: {
forcedSceneName: sceneName,
},
numberOfWindows: 0,
});
},
[launchPreview]
);
const launchQuickCustomizationPreview = React.useCallback(
() =>
launchPreview({
@@ -3577,7 +3598,30 @@ const MainFrame = (props: Props) => {
'main-frame' /* The root styling, done in CSS to read some CSS variables. */
}
>
<EmbeddedGameFrame previewDebuggerServer={previewDebuggerServer} />
{!!renderPreviewLauncher &&
renderPreviewLauncher(
{
crashReportUploadLevel:
preferences.values.previewCrashReportUploadLevel ||
'exclude-javascript-code-events',
previewContext: quickCustomizationDialogOpenedFromGameId
? 'preview-quick-customization'
: 'preview',
sourceGameId: quickCustomizationDialogOpenedFromGameId || '',
getIncludeFileHashs:
eventsFunctionsExtensionsContext.getIncludeFileHashs,
onExport: () => openShareDialog('publish'),
onCaptureFinished,
},
(previewLauncher: ?PreviewLauncherInterface) => {
_previewLauncher.current = previewLauncher;
}
)}
<EmbeddedGameFrame
key={currentProject ? currentProject.ptr : 0}
previewDebuggerServer={previewDebuggerServer || null}
onLaunchPreviewForInGameEdition={onLaunchPreviewForInGameEdition}
/>
{!!renderMainMenu &&
renderMainMenu(
{ ...buildMainMenuProps, isApplicationTopLevelMenu: true },
@@ -3849,7 +3893,11 @@ const MainFrame = (props: Props) => {
<LoaderModal
show={showLoader}
progress={fileMetadataOpeningProgress}
message={loaderModalOpeningMessage || fileMetadataOpeningMessage}
message={
loaderModalOpeningMessage ||
fileMetadataOpeningMessage ||
(previewLoading ? t`Loading preview...` : null)
}
/>
<Snackbar
open={state.snackMessageOpen}
@@ -3872,25 +3920,6 @@ const MainFrame = (props: Props) => {
initialTab: shareDialogInitialTab,
gamesList,
})}
{!!renderPreviewLauncher &&
renderPreviewLauncher(
{
crashReportUploadLevel:
preferences.values.previewCrashReportUploadLevel ||
'exclude-javascript-code-events',
previewContext: quickCustomizationDialogOpenedFromGameId
? 'preview-quick-customization'
: 'preview',
sourceGameId: quickCustomizationDialogOpenedFromGameId || '',
getIncludeFileHashs:
eventsFunctionsExtensionsContext.getIncludeFileHashs,
onExport: () => openShareDialog('publish'),
onCaptureFinished,
},
(previewLauncher: ?PreviewLauncherInterface) => {
_previewLauncher.current = previewLauncher;
}
)}
{chooseResourceOptions && onResourceChosen && !!currentProject && (
<NewResourceDialog
project={currentProject}

View File

@@ -249,6 +249,11 @@ export default class SceneEditor extends React.Component<Props, State> {
this.resourceExternallyChangedCallbackId = registerOnResourceExternallyChangedCallback(
this.onResourceExternallyChanged.bind(this)
);
if (this.props.isActive) {
if (this.props.layout && this.state.gameEditorMode === 'embedded-game') {
switchToSceneEdition({ sceneName: this.props.layout.getName() });
}
}
}
componentWillUnmount() {
unregisterOnResourceExternallyChangedCallback(

View File

@@ -28,14 +28,15 @@ type Props = {|
progress?: ?number,
|};
const transitionDuration = { enter: 0, exit: 150 };
const transitionDuration = { enter: 400 };
const LoaderModal = ({ progress, message, show }: Props) => {
const isInfinite = progress === null || progress === undefined;
if (!show) return null;
return (
<I18n>
{({ i18n }) => (
<Dialog open={show} transitionDuration={transitionDuration}>
<Dialog open transitionDuration={transitionDuration}>
<DialogContent style={styles.dialogContent}>
<div
style={{