mirror of
https://github.com/4ian/GDevelop.git
synced 2025-10-15 10:19:04 +00:00
1718 lines
52 KiB
JavaScript
1718 lines
52 KiB
JavaScript
// @flow
|
|
import { Trans } from '@lingui/macro';
|
|
|
|
import * as React from 'react';
|
|
import './MainFrame.css';
|
|
import Drawer from '@material-ui/core/Drawer';
|
|
import Snackbar from '@material-ui/core/Snackbar';
|
|
import Toolbar from './Toolbar';
|
|
import ProjectTitlebar from './ProjectTitlebar';
|
|
import PreferencesDialog from './Preferences/PreferencesDialog';
|
|
import AboutDialog from './AboutDialog';
|
|
import ProjectManager from '../ProjectManager';
|
|
import PlatformSpecificAssetsDialog from '../PlatformSpecificAssetsEditor/PlatformSpecificAssetsDialog';
|
|
import LoaderModal from '../UI/LoaderModal';
|
|
import EditorBar from '../UI/EditorBar';
|
|
import CloseConfirmDialog from '../UI/CloseConfirmDialog';
|
|
import ProfileDialog from '../Profile/ProfileDialog';
|
|
import Window from '../Utils/Window';
|
|
import { showErrorBox } from '../UI/Messages/MessageBox';
|
|
import {
|
|
ClosableTabs,
|
|
ClosableTab,
|
|
TabContentContainer,
|
|
} from '../UI/ClosableTabs';
|
|
import {
|
|
getEditorTabsInitialState,
|
|
openEditorTab,
|
|
closeEditorTab,
|
|
closeOtherEditorTabs,
|
|
closeAllEditorTabs,
|
|
changeCurrentTab,
|
|
getEditors,
|
|
getCurrentTabIndex,
|
|
getCurrentTab,
|
|
closeProjectTabs,
|
|
closeLayoutTabs,
|
|
closeExternalLayoutTabs,
|
|
closeExternalEventsTabs,
|
|
closeEventsFunctionsExtensionTabs,
|
|
saveUiSettings,
|
|
type EditorTabsState,
|
|
type EditorTab,
|
|
getEventsFunctionsExtensionEditor,
|
|
} from './EditorTabsHandler';
|
|
import { watchPromiseInState } from '../Utils/WatchPromiseInState';
|
|
import { timeFunction } from '../Utils/TimeFunction';
|
|
import newNameGenerator from '../Utils/NewNameGenerator';
|
|
import HelpFinder from '../HelpFinder';
|
|
|
|
// Editors:
|
|
import DebuggerEditor from './Editors/DebuggerEditor';
|
|
import EventsEditor from './Editors/EventsEditor';
|
|
import ExternalEventsEditor from './Editors/ExternalEventsEditor';
|
|
import SceneEditor from './Editors/SceneEditor';
|
|
import ExternalLayoutEditor from './Editors/ExternalLayoutEditor';
|
|
import EventsFunctionsExtensionEditor from './Editors/EventsFunctionsExtensionEditor';
|
|
import StartPage from './Editors/StartPage';
|
|
import ResourcesEditor from './Editors/ResourcesEditor';
|
|
import ErrorBoundary from '../UI/ErrorBoundary';
|
|
import SubscriptionDialog from '../Profile/SubscriptionDialog';
|
|
import ResourcesLoader from '../ResourcesLoader/index';
|
|
import Authentification from '../Utils/GDevelopServices/Authentification';
|
|
import {
|
|
type PreviewLauncher,
|
|
type PreviewOptions,
|
|
} from '../Export/PreviewLauncher.flow';
|
|
import { type ResourceSource } from '../ResourcesList/ResourceSource.flow';
|
|
import { type ResourceExternalEditor } from '../ResourcesList/ResourceExternalEditor.flow';
|
|
import { type JsExtensionsLoader } from '../JsExtensionsLoader';
|
|
import { type EventsFunctionsExtensionsState } from '../EventsFunctionsExtensionsLoader/EventsFunctionsExtensionsContext';
|
|
import {
|
|
getUpdateNotificationTitle,
|
|
getUpdateNotificationBody,
|
|
type UpdateStatus,
|
|
} from './UpdaterTools';
|
|
import { showWarningBox } from '../UI/Messages/MessageBox';
|
|
import EmptyMessage from '../UI/EmptyMessage';
|
|
import ChangelogDialogContainer from './Changelog/ChangelogDialogContainer';
|
|
import { getNotNullTranslationFunction } from '../Utils/i18n/getTranslationFunction';
|
|
import { type I18n } from '@lingui/core';
|
|
import { t } from '@lingui/macro';
|
|
import LanguageDialog from './Preferences/LanguageDialog';
|
|
import PreferencesContext from './Preferences/PreferencesContext';
|
|
import { getFunctionNameFromType } from '../EventsFunctionsExtensionsLoader';
|
|
import { type ExportDialogWithoutExportsProps } from '../Export/ExportDialog';
|
|
import { getStartupTimesSummary } from '../Utils/StartupTimes';
|
|
import { type ProjectsStorageProps } from '../ProjectsStorage';
|
|
const GD_STARTUP_TIMES = global.GD_STARTUP_TIMES || [];
|
|
|
|
const gd = global.gd;
|
|
|
|
const styles = {
|
|
drawerContent: {
|
|
width: 320,
|
|
overflowX: 'hidden',
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
},
|
|
};
|
|
|
|
type State = {|
|
|
createDialogOpen: boolean,
|
|
exportDialogOpen: boolean,
|
|
introDialogOpen: boolean,
|
|
genericDialogOpen: boolean,
|
|
loadingProject: boolean,
|
|
previewLoading: boolean,
|
|
currentProject: ?gdProject,
|
|
projectManagerOpen: boolean,
|
|
editorTabs: EditorTabsState,
|
|
genericDialog: null,
|
|
snackMessage: string,
|
|
snackMessageOpen: boolean,
|
|
preferencesDialogOpen: boolean,
|
|
languageDialogOpen: boolean,
|
|
profileDialogOpen: boolean,
|
|
subscriptionDialogOpen: boolean,
|
|
updateStatus: UpdateStatus,
|
|
aboutDialogOpen: boolean,
|
|
platformSpecificAssetsDialogOpen: boolean,
|
|
helpFinderDialogOpen: boolean,
|
|
eventsFunctionsExtensionsError: ?Error,
|
|
|};
|
|
|
|
type Props = {
|
|
integratedEditor?: boolean,
|
|
introDialog?: React.Element<*>,
|
|
previewLauncher?: React.Element<PreviewLauncher>,
|
|
onEditObject?: gdObject => void,
|
|
projectsStorage: ProjectsStorageProps,
|
|
resourceSources: Array<ResourceSource>,
|
|
resourceExternalEditors: Array<ResourceExternalEditor>,
|
|
loading?: boolean,
|
|
requestUpdate?: () => void,
|
|
renderExportDialog?: ExportDialogWithoutExportsProps => React.Node,
|
|
createDialog?: React.Element<*>,
|
|
authentification: Authentification,
|
|
extensionsLoader?: JsExtensionsLoader,
|
|
initialPathsOrURLsToOpen: ?Array<string>,
|
|
eventsFunctionsExtensionsState: EventsFunctionsExtensionsState,
|
|
i18n: I18n,
|
|
};
|
|
|
|
class MainFrame extends React.Component<Props, State> {
|
|
state = {
|
|
createDialogOpen: false,
|
|
exportDialogOpen: false,
|
|
introDialogOpen: false,
|
|
genericDialogOpen: false,
|
|
loadingProject: false,
|
|
previewLoading: false,
|
|
currentProject: null,
|
|
projectManagerOpen: false,
|
|
editorTabs: getEditorTabsInitialState(),
|
|
genericDialog: null,
|
|
snackMessage: '',
|
|
snackMessageOpen: false,
|
|
preferencesDialogOpen: false,
|
|
languageDialogOpen: false,
|
|
profileDialogOpen: false,
|
|
subscriptionDialogOpen: false,
|
|
updateStatus: { message: '', status: 'unknown' },
|
|
aboutDialogOpen: false,
|
|
platformSpecificAssetsDialogOpen: false,
|
|
helpFinderDialogOpen: false,
|
|
eventsFunctionsExtensionsError: null,
|
|
};
|
|
toolbar = null;
|
|
_resourceSourceDialogs = {};
|
|
_previewLauncher: ?PreviewLauncher = null;
|
|
|
|
componentWillMount() {
|
|
if (!this.props.integratedEditor) this.openStartPage();
|
|
}
|
|
|
|
componentDidMount() {
|
|
GD_STARTUP_TIMES.push(['MainFrameComponentDidMount', performance.now()]);
|
|
|
|
const { initialPathsOrURLsToOpen } = this.props;
|
|
|
|
this._loadExtensions();
|
|
if (initialPathsOrURLsToOpen && initialPathsOrURLsToOpen[0]) {
|
|
this.openFromPathOrURL(initialPathsOrURLsToOpen[0], () =>
|
|
this.openSceneOrProjectManager()
|
|
);
|
|
} else if (this.props.introDialog && !Window.isDev())
|
|
this._openIntroDialog(true);
|
|
|
|
GD_STARTUP_TIMES.push([
|
|
'MainFrameComponentDidMountFinished',
|
|
performance.now(),
|
|
]);
|
|
console.info('Startup times:', getStartupTimesSummary());
|
|
}
|
|
|
|
_languageDidChange() {
|
|
// A change in the language will automatically be applied
|
|
// on all React components, as it's handled by GDI18nProvider.
|
|
// We still have this method that will be called when the language
|
|
// dialog is closed after a language change. We then reload GDevelop
|
|
// extensions so that they declare all objects/actions/condition/etc...
|
|
// using the new language.
|
|
gd.JsPlatform.get().reloadBuiltinExtensions();
|
|
this._loadExtensions();
|
|
}
|
|
|
|
_loadExtensions = () => {
|
|
const { extensionsLoader, i18n } = this.props;
|
|
if (!extensionsLoader) {
|
|
console.info(
|
|
'No extensions loader specified, skipping extensions loading.'
|
|
);
|
|
return;
|
|
}
|
|
|
|
extensionsLoader
|
|
.loadAllExtensions(getNotNullTranslationFunction(i18n))
|
|
.then(loadingResults => {
|
|
const successLoadingResults = loadingResults.filter(
|
|
loadingResult => !loadingResult.result.error
|
|
);
|
|
const failLoadingResults = loadingResults.filter(
|
|
loadingResult =>
|
|
loadingResult.result.error && !loadingResult.result.dangerous
|
|
);
|
|
const dangerousLoadingResults = loadingResults.filter(
|
|
loadingResult =>
|
|
loadingResult.result.error && loadingResult.result.dangerous
|
|
);
|
|
console.info(`Loaded ${successLoadingResults.length} JS extensions.`);
|
|
if (failLoadingResults.length) {
|
|
console.error(
|
|
`⚠️ Unable to load ${
|
|
failLoadingResults.length
|
|
} JS extensions. Please check these errors:`,
|
|
failLoadingResults
|
|
);
|
|
}
|
|
if (dangerousLoadingResults.length) {
|
|
console.error(
|
|
`💣 Dangerous exceptions while loading ${
|
|
dangerousLoadingResults.length
|
|
} JS extensions. 🔥 Please check these errors as they will CRASH GDevelop:`,
|
|
dangerousLoadingResults
|
|
);
|
|
}
|
|
});
|
|
};
|
|
|
|
loadFromSerializedProject = (
|
|
serializedProject: gdSerializerElement,
|
|
cb: Function
|
|
) => {
|
|
timeFunction(
|
|
() => {
|
|
const newProject = gd.ProjectHelper.createNewGDJSProject();
|
|
newProject.unserializeFrom(serializedProject);
|
|
|
|
this.closeProject(() => this.loadFromProject(newProject, cb));
|
|
},
|
|
time => console.info(`Unserialization took ${time} ms`)
|
|
);
|
|
};
|
|
|
|
loadFromProject = (project: gdProject, cb: Function) => {
|
|
const { eventsFunctionsExtensionsState } = this.props;
|
|
this.closeProject(() => {
|
|
// Make sure that the ResourcesLoader cache is emptied, so that
|
|
// the URL to a resource with a name in the old project is not re-used
|
|
// for another resource with the same name in the new project.
|
|
ResourcesLoader.burstAllUrlsCache();
|
|
// TODO: Pixi cache should also be burst
|
|
|
|
this.setState(
|
|
{
|
|
currentProject: project,
|
|
},
|
|
() => {
|
|
// Load all the EventsFunctionsExtension when the game is loaded. If they are modified,
|
|
// their editor will take care of reloading them.
|
|
eventsFunctionsExtensionsState.loadProjectEventsFunctionsExtensions(
|
|
this.state.currentProject
|
|
);
|
|
cb();
|
|
}
|
|
);
|
|
});
|
|
};
|
|
|
|
openFromPathOrURL = (url: string, cb: Function) => {
|
|
const { i18n, projectsStorage } = this.props;
|
|
|
|
const projectFilePath = url;
|
|
const autoSavePath = url + '.autosave';
|
|
if (
|
|
projectsStorage.shouldOpenAutosave &&
|
|
projectsStorage.shouldOpenAutosave(url, autoSavePath, true)
|
|
) {
|
|
//eslint-disable-next-line
|
|
const answer = confirm(
|
|
i18n._(
|
|
t`An autosave file (backup made automatically by GDevelop) that is newer than the project file exists. Would you like to load it instead?`
|
|
)
|
|
);
|
|
if (answer) url = autoSavePath;
|
|
}
|
|
|
|
projectsStorage.onOpen(url).then(
|
|
projectObject => {
|
|
this.setState({ loadingProject: true }, () =>
|
|
setTimeout(() => {
|
|
const serializedProject = gd.Serializer.fromJSObject(projectObject);
|
|
|
|
this.loadFromSerializedProject(serializedProject, () => {
|
|
serializedProject.delete();
|
|
|
|
if (this.state.currentProject) {
|
|
this.state.currentProject.setProjectFile(projectFilePath);
|
|
}
|
|
|
|
this.setState(
|
|
{
|
|
loadingProject: false,
|
|
},
|
|
cb
|
|
);
|
|
});
|
|
})
|
|
);
|
|
},
|
|
err => {
|
|
if (
|
|
projectsStorage.shouldOpenAutosave &&
|
|
projectsStorage.shouldOpenAutosave(
|
|
projectFilePath,
|
|
autoSavePath,
|
|
false
|
|
)
|
|
) {
|
|
//eslint-disable-next-line
|
|
const answer = confirm(
|
|
i18n._(
|
|
t`The project file appears to be malformed, but an autosave file exists (backup made automatically by GDevelop). Would you like to try to load it instead?`
|
|
)
|
|
);
|
|
if (answer) {
|
|
this.openFromPathOrURL(autoSavePath, () =>
|
|
this.openSceneOrProjectManager()
|
|
);
|
|
}
|
|
} else {
|
|
showErrorBox(
|
|
i18n._(
|
|
t`Unable to open this project. Check that the path/URL is correct, that you selected a file that is a game file created with GDevelop and that is was not removed.`
|
|
),
|
|
err
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
);
|
|
};
|
|
|
|
closeProject = (cb: Function) => {
|
|
const { currentProject } = this.state;
|
|
const { eventsFunctionsExtensionsState } = this.props;
|
|
if (!currentProject) return cb();
|
|
|
|
this.openProjectManager(false);
|
|
this.setState(
|
|
{
|
|
editorTabs: closeProjectTabs(this.state.editorTabs, currentProject),
|
|
},
|
|
() => {
|
|
eventsFunctionsExtensionsState.unloadProjectEventsFunctionsExtensions(
|
|
currentProject
|
|
);
|
|
currentProject.delete();
|
|
this.setState(
|
|
{
|
|
currentProject: null,
|
|
},
|
|
() => {
|
|
this.updateToolbar();
|
|
cb();
|
|
}
|
|
);
|
|
}
|
|
);
|
|
};
|
|
|
|
getSerializedElements = () => {
|
|
const editorTab = getCurrentTab(this.state.editorTabs);
|
|
if (!editorTab || !editorTab.editorRef) {
|
|
console.warn('No active editor or reference to the editor');
|
|
return {};
|
|
}
|
|
|
|
return editorTab.editorRef.getSerializedElements();
|
|
};
|
|
|
|
toggleProjectManager = () => {
|
|
if (!this.refs.toolbar)
|
|
this.setState({
|
|
projectManagerOpen: !this.state.projectManagerOpen,
|
|
});
|
|
};
|
|
|
|
openProjectManager = (open: boolean = true) => {
|
|
this.setState({
|
|
projectManagerOpen: open,
|
|
});
|
|
};
|
|
|
|
setEditorToolbar = (editorToolbar: any) => {
|
|
if (!this.toolbar) return;
|
|
|
|
this.toolbar.setEditorToolbar(editorToolbar);
|
|
};
|
|
|
|
addLayout = () => {
|
|
const { currentProject } = this.state;
|
|
if (!currentProject) return;
|
|
|
|
const name = newNameGenerator('NewScene', name =>
|
|
currentProject.hasLayoutNamed(name)
|
|
);
|
|
const newLayout = currentProject.insertNewLayout(
|
|
name,
|
|
currentProject.getLayoutsCount()
|
|
);
|
|
newLayout.updateBehaviorsSharedData(currentProject);
|
|
this.forceUpdate();
|
|
};
|
|
|
|
addExternalLayout = () => {
|
|
const { currentProject } = this.state;
|
|
if (!currentProject) return;
|
|
|
|
const name = newNameGenerator('NewExternalLayout', name =>
|
|
currentProject.hasExternalLayoutNamed(name)
|
|
);
|
|
currentProject.insertNewExternalLayout(
|
|
name,
|
|
currentProject.getExternalLayoutsCount()
|
|
);
|
|
this.forceUpdate();
|
|
};
|
|
|
|
addExternalEvents = () => {
|
|
const { currentProject } = this.state;
|
|
if (!currentProject) return;
|
|
|
|
const name = newNameGenerator('NewExternalEvents', name =>
|
|
currentProject.hasExternalEventsNamed(name)
|
|
);
|
|
currentProject.insertNewExternalEvents(
|
|
name,
|
|
currentProject.getExternalEventsCount()
|
|
);
|
|
this.forceUpdate();
|
|
};
|
|
|
|
addEventsFunctionsExtension = () => {
|
|
const { currentProject } = this.state;
|
|
if (!currentProject) return;
|
|
|
|
const name = newNameGenerator('NewExtension', name =>
|
|
currentProject.hasEventsFunctionsExtensionNamed(name)
|
|
);
|
|
currentProject.insertNewEventsFunctionsExtension(
|
|
name,
|
|
currentProject.getEventsFunctionsExtensionsCount()
|
|
);
|
|
this.forceUpdate();
|
|
};
|
|
|
|
deleteLayout = (layout: gdLayout) => {
|
|
const { currentProject } = this.state;
|
|
const { i18n } = this.props;
|
|
if (!currentProject) return;
|
|
|
|
//eslint-disable-next-line
|
|
const answer = confirm(
|
|
i18n._(
|
|
t`Are you sure you want to remove this scene? This can't be undone.`
|
|
)
|
|
);
|
|
if (!answer) return;
|
|
|
|
this.setState(
|
|
{
|
|
editorTabs: closeLayoutTabs(this.state.editorTabs, layout),
|
|
},
|
|
() => {
|
|
currentProject.removeLayout(layout.getName());
|
|
this.forceUpdate();
|
|
}
|
|
);
|
|
};
|
|
|
|
deleteExternalLayout = (externalLayout: gdExternalLayout) => {
|
|
const { currentProject } = this.state;
|
|
const { i18n } = this.props;
|
|
if (!currentProject) return;
|
|
|
|
//eslint-disable-next-line
|
|
const answer = confirm(
|
|
i18n._(
|
|
t`Are you sure you want to remove this external layout? This can't be undone.`
|
|
)
|
|
);
|
|
if (!answer) return;
|
|
|
|
this.setState(
|
|
{
|
|
editorTabs: closeExternalLayoutTabs(
|
|
this.state.editorTabs,
|
|
externalLayout
|
|
),
|
|
},
|
|
() => {
|
|
currentProject.removeExternalLayout(externalLayout.getName());
|
|
this.forceUpdate();
|
|
}
|
|
);
|
|
};
|
|
|
|
deleteExternalEvents = (externalEvents: gdExternalEvents) => {
|
|
const { currentProject } = this.state;
|
|
const { i18n } = this.props;
|
|
if (!currentProject) return;
|
|
|
|
//eslint-disable-next-line
|
|
const answer = confirm(
|
|
i18n._(
|
|
t`Are you sure you want to remove these external events? This can't be undone.`
|
|
)
|
|
);
|
|
if (!answer) return;
|
|
|
|
this.setState(
|
|
{
|
|
editorTabs: closeExternalEventsTabs(
|
|
this.state.editorTabs,
|
|
externalEvents
|
|
),
|
|
},
|
|
() => {
|
|
currentProject.removeExternalEvents(externalEvents.getName());
|
|
this.forceUpdate();
|
|
}
|
|
);
|
|
};
|
|
|
|
deleteEventsFunctionsExtension = (
|
|
externalLayout: gdEventsFunctionsExtension
|
|
) => {
|
|
const { currentProject } = this.state;
|
|
const { i18n, eventsFunctionsExtensionsState } = this.props;
|
|
if (!currentProject) return;
|
|
|
|
//eslint-disable-next-line
|
|
const answer = confirm(
|
|
i18n._(
|
|
t`Are you sure you want to remove this extension? This can't be undone.`
|
|
)
|
|
);
|
|
if (!answer) return;
|
|
|
|
this.setState(
|
|
{
|
|
editorTabs: closeEventsFunctionsExtensionTabs(
|
|
this.state.editorTabs,
|
|
externalLayout
|
|
),
|
|
},
|
|
() => {
|
|
currentProject.removeEventsFunctionsExtension(externalLayout.getName());
|
|
this.forceUpdate();
|
|
}
|
|
);
|
|
|
|
// Reload extensions to make sure the deleted extension is removed
|
|
// from the platform
|
|
eventsFunctionsExtensionsState.reloadProjectEventsFunctionsExtensions(
|
|
currentProject
|
|
);
|
|
};
|
|
|
|
renameLayout = (oldName: string, newName: string) => {
|
|
const { currentProject } = this.state;
|
|
const { i18n } = this.props;
|
|
if (!currentProject) return;
|
|
|
|
if (!currentProject.hasLayoutNamed(oldName) || newName === oldName) return;
|
|
|
|
if (newName === '') {
|
|
showWarningBox(
|
|
i18n._(t`This name cannot be empty, please enter a new name.`)
|
|
);
|
|
return;
|
|
}
|
|
|
|
if (currentProject.hasLayoutNamed(newName)) {
|
|
showWarningBox(i18n._(t`Another scene with this name already exists.`));
|
|
return;
|
|
}
|
|
|
|
const layout = currentProject.getLayout(oldName);
|
|
this.setState(
|
|
{
|
|
editorTabs: closeLayoutTabs(this.state.editorTabs, layout),
|
|
},
|
|
() => {
|
|
layout.setName(newName);
|
|
this.forceUpdate();
|
|
}
|
|
);
|
|
};
|
|
|
|
renameExternalLayout = (oldName: string, newName: string) => {
|
|
const { currentProject } = this.state;
|
|
const { i18n } = this.props;
|
|
if (!currentProject) return;
|
|
|
|
if (!currentProject.hasExternalLayoutNamed(oldName) || newName === oldName)
|
|
return;
|
|
|
|
if (newName === '') {
|
|
showWarningBox(
|
|
i18n._(t`This name cannot be empty, please enter a new name.`)
|
|
);
|
|
return;
|
|
}
|
|
|
|
if (currentProject.hasExternalLayoutNamed(newName)) {
|
|
showWarningBox(
|
|
i18n._(t`Another external layout with this name already exists.`)
|
|
);
|
|
return;
|
|
}
|
|
|
|
const externalLayout = currentProject.getExternalLayout(oldName);
|
|
this.setState(
|
|
{
|
|
editorTabs: closeExternalLayoutTabs(
|
|
this.state.editorTabs,
|
|
externalLayout
|
|
),
|
|
},
|
|
() => {
|
|
externalLayout.setName(newName);
|
|
this.forceUpdate();
|
|
}
|
|
);
|
|
};
|
|
|
|
renameExternalEvents = (oldName: string, newName: string) => {
|
|
const { currentProject } = this.state;
|
|
const { i18n } = this.props;
|
|
if (!currentProject) return;
|
|
|
|
if (!currentProject.hasExternalEventsNamed(oldName) || newName === oldName)
|
|
return;
|
|
|
|
if (newName === '') {
|
|
showWarningBox(
|
|
i18n._(t`This name cannot be empty, please enter a new name.`)
|
|
);
|
|
return;
|
|
}
|
|
|
|
if (currentProject.hasExternalEventsNamed(newName)) {
|
|
showWarningBox(
|
|
i18n._(t`Other external events with this name already exist.`)
|
|
);
|
|
return;
|
|
}
|
|
|
|
const externalEvents = currentProject.getExternalEvents(oldName);
|
|
this.setState(
|
|
{
|
|
editorTabs: closeExternalEventsTabs(
|
|
this.state.editorTabs,
|
|
externalEvents
|
|
),
|
|
},
|
|
() => {
|
|
externalEvents.setName(newName);
|
|
this.forceUpdate();
|
|
}
|
|
);
|
|
};
|
|
|
|
renameEventsFunctionsExtension = (oldName: string, newName: string) => {
|
|
const { currentProject } = this.state;
|
|
const { i18n } = this.props;
|
|
const { eventsFunctionsExtensionsState } = this.props;
|
|
if (!currentProject) return;
|
|
|
|
if (
|
|
!currentProject.hasEventsFunctionsExtensionNamed(oldName) ||
|
|
newName === oldName
|
|
)
|
|
return;
|
|
|
|
if (newName === '') {
|
|
showWarningBox(
|
|
i18n._(t`This name cannot be empty, please enter a new name.`)
|
|
);
|
|
return;
|
|
}
|
|
|
|
if (currentProject.hasEventsFunctionsExtensionNamed(newName)) {
|
|
showWarningBox(
|
|
i18n._(t`Another extension with this name already exists.`)
|
|
);
|
|
return;
|
|
}
|
|
|
|
if (!gd.Project.validateObjectName(newName)) {
|
|
showWarningBox(
|
|
i18n._(
|
|
t`This name contains forbidden characters: please only use alphanumeric characters (0-9, a-z) and underscores in your extension name.`
|
|
)
|
|
);
|
|
return;
|
|
}
|
|
|
|
const eventsFunctionsExtension = currentProject.getEventsFunctionsExtension(
|
|
oldName
|
|
);
|
|
this.setState(
|
|
{
|
|
editorTabs: closeEventsFunctionsExtensionTabs(
|
|
this.state.editorTabs,
|
|
eventsFunctionsExtension
|
|
),
|
|
},
|
|
() => {
|
|
// Refactor the project to update the instructions (and later expressions)
|
|
// of this extension:
|
|
gd.WholeProjectRefactorer.renameEventsFunctionsExtension(
|
|
currentProject,
|
|
eventsFunctionsExtension,
|
|
oldName,
|
|
newName
|
|
);
|
|
eventsFunctionsExtension.setName(newName);
|
|
eventsFunctionsExtensionsState.reloadProjectEventsFunctionsExtensions(
|
|
currentProject
|
|
);
|
|
|
|
this.forceUpdate();
|
|
}
|
|
);
|
|
};
|
|
|
|
_launchLayoutPreview = (
|
|
project: gdProject,
|
|
layout: gdLayout,
|
|
options: PreviewOptions
|
|
) =>
|
|
watchPromiseInState(this, 'previewLoading', () =>
|
|
this._handlePreviewResult(
|
|
this._previewLauncher &&
|
|
this._previewLauncher.launchLayoutPreview(project, layout, options)
|
|
)
|
|
);
|
|
|
|
_launchExternalLayoutPreview = (
|
|
project: gdProject,
|
|
layout: gdLayout,
|
|
externalLayout: gdExternalLayout,
|
|
options: PreviewOptions
|
|
) =>
|
|
watchPromiseInState(this, 'previewLoading', () =>
|
|
this._handlePreviewResult(
|
|
this._previewLauncher &&
|
|
this._previewLauncher.launchExternalLayoutPreview(
|
|
project,
|
|
layout,
|
|
externalLayout,
|
|
options
|
|
)
|
|
)
|
|
);
|
|
|
|
_handlePreviewResult = (previewPromise: ?Promise<any>): Promise<void> => {
|
|
if (!previewPromise) return Promise.reject();
|
|
const { i18n } = this.props;
|
|
|
|
return previewPromise.then(
|
|
(result: any) => {},
|
|
(err: any) => {
|
|
showErrorBox(i18n._(t`Unable to launch the preview!`), err);
|
|
}
|
|
);
|
|
};
|
|
|
|
openLayout = (
|
|
name: string,
|
|
{
|
|
openEventsEditor = true,
|
|
openSceneEditor = true,
|
|
}: { openEventsEditor: boolean, openSceneEditor: boolean } = {}
|
|
) => {
|
|
const { i18n, projectsStorage } = this.props;
|
|
const sceneEditorOptions = {
|
|
name,
|
|
renderEditor: ({ isActive, editorRef }) => (
|
|
<PreferencesContext.Consumer>
|
|
{({ values }) => (
|
|
<SceneEditor
|
|
project={this.state.currentProject}
|
|
layoutName={name}
|
|
setToolbar={this.setEditorToolbar}
|
|
onPreview={(project, layout, options) => {
|
|
this._launchLayoutPreview(project, layout, options);
|
|
if (
|
|
values.autosaveOnPreview &&
|
|
projectsStorage.onAutoSaveProject
|
|
) {
|
|
projectsStorage.onAutoSaveProject(project);
|
|
}
|
|
}}
|
|
showPreviewButton={!!this.props.previewLauncher}
|
|
showNetworkPreviewButton={
|
|
this._previewLauncher &&
|
|
this._previewLauncher.canDoNetworkPreview()
|
|
}
|
|
onOpenDebugger={this.openDebugger}
|
|
onEditObject={this.props.onEditObject}
|
|
resourceSources={this.props.resourceSources}
|
|
onChooseResource={this._onChooseResource}
|
|
resourceExternalEditors={this.props.resourceExternalEditors}
|
|
isActive={isActive}
|
|
ref={editorRef}
|
|
/>
|
|
)}
|
|
</PreferencesContext.Consumer>
|
|
),
|
|
key: 'layout ' + name,
|
|
};
|
|
const eventsEditorOptions = {
|
|
name: name + ' ' + i18n._(t`(Events)`),
|
|
renderEditor: ({ isActive, editorRef }) => (
|
|
<PreferencesContext.Consumer>
|
|
{({ values }) => (
|
|
<EventsEditor
|
|
project={this.state.currentProject}
|
|
layoutName={name}
|
|
setToolbar={this.setEditorToolbar}
|
|
onPreview={(project, layout, options) => {
|
|
this._launchLayoutPreview(project, layout, options);
|
|
if (
|
|
values.autosaveOnPreview &&
|
|
projectsStorage.onAutoSaveProject
|
|
) {
|
|
projectsStorage.onAutoSaveProject(project);
|
|
}
|
|
}}
|
|
showPreviewButton={!!this.props.previewLauncher}
|
|
showNetworkPreviewButton={
|
|
this._previewLauncher &&
|
|
this._previewLauncher.canDoNetworkPreview()
|
|
}
|
|
onOpenDebugger={this.openDebugger}
|
|
onOpenExternalEvents={this.openExternalEvents}
|
|
onOpenLayout={name =>
|
|
this.openLayout(name, {
|
|
openEventsEditor: true,
|
|
openSceneEditor: false,
|
|
})
|
|
}
|
|
resourceSources={this.props.resourceSources}
|
|
onChooseResource={this._onChooseResource}
|
|
resourceExternalEditors={this.props.resourceExternalEditors}
|
|
openInstructionOrExpression={this._openInstructionOrExpression}
|
|
onCreateEventsFunction={this._onCreateEventsFunction}
|
|
isActive={isActive}
|
|
ref={editorRef}
|
|
/>
|
|
)}
|
|
</PreferencesContext.Consumer>
|
|
),
|
|
key: 'layout events ' + name,
|
|
dontFocusTab: openSceneEditor,
|
|
};
|
|
|
|
const tabsWithSceneEditor = openSceneEditor
|
|
? openEditorTab(this.state.editorTabs, sceneEditorOptions)
|
|
: this.state.editorTabs;
|
|
const tabsWithSceneAndEventsEditors = openEventsEditor
|
|
? openEditorTab(tabsWithSceneEditor, eventsEditorOptions)
|
|
: tabsWithSceneEditor;
|
|
|
|
this.setState({ editorTabs: tabsWithSceneAndEventsEditors }, () =>
|
|
this.updateToolbar()
|
|
);
|
|
this.openProjectManager(false);
|
|
};
|
|
|
|
openExternalEvents = (name: string) => {
|
|
this.setState(
|
|
{
|
|
editorTabs: openEditorTab(this.state.editorTabs, {
|
|
name,
|
|
renderEditor: ({ isActive, editorRef }) => (
|
|
<ExternalEventsEditor
|
|
project={this.state.currentProject}
|
|
externalEventsName={name}
|
|
setToolbar={this.setEditorToolbar}
|
|
onOpenExternalEvents={this.openExternalEvents}
|
|
onOpenLayout={name =>
|
|
this.openLayout(name, {
|
|
openEventsEditor: true,
|
|
openSceneEditor: false,
|
|
})
|
|
}
|
|
resourceSources={this.props.resourceSources}
|
|
onChooseResource={this._onChooseResource}
|
|
resourceExternalEditors={this.props.resourceExternalEditors}
|
|
openInstructionOrExpression={this._openInstructionOrExpression}
|
|
onCreateEventsFunction={this._onCreateEventsFunction}
|
|
isActive={isActive}
|
|
ref={editorRef}
|
|
/>
|
|
),
|
|
key: 'external events ' + name,
|
|
}),
|
|
},
|
|
() => this.updateToolbar()
|
|
);
|
|
this.openProjectManager(false);
|
|
};
|
|
|
|
openExternalLayout = (name: string) => {
|
|
const { projectsStorage } = this.props;
|
|
this.setState(
|
|
{
|
|
editorTabs: openEditorTab(this.state.editorTabs, {
|
|
name,
|
|
renderEditor: ({ isActive, editorRef }) => (
|
|
<PreferencesContext.Consumer>
|
|
{({ values }) => (
|
|
<ExternalLayoutEditor
|
|
project={this.state.currentProject}
|
|
externalLayoutName={name}
|
|
setToolbar={this.setEditorToolbar}
|
|
onPreview={(project, layout, externalLayout, options) => {
|
|
this._launchExternalLayoutPreview(
|
|
project,
|
|
layout,
|
|
externalLayout,
|
|
options
|
|
);
|
|
if (
|
|
values.autosaveOnPreview &&
|
|
projectsStorage.onAutoSaveProject
|
|
) {
|
|
projectsStorage.onAutoSaveProject(project);
|
|
}
|
|
}}
|
|
showPreviewButton={!!this.props.previewLauncher}
|
|
showNetworkPreviewButton={
|
|
this._previewLauncher &&
|
|
this._previewLauncher.canDoNetworkPreview()
|
|
}
|
|
onOpenDebugger={this.openDebugger}
|
|
onEditObject={this.props.onEditObject}
|
|
resourceSources={this.props.resourceSources}
|
|
onChooseResource={this._onChooseResource}
|
|
resourceExternalEditors={this.props.resourceExternalEditors}
|
|
isActive={isActive}
|
|
ref={editorRef}
|
|
/>
|
|
)}
|
|
</PreferencesContext.Consumer>
|
|
),
|
|
key: 'external layout ' + name,
|
|
}),
|
|
},
|
|
() => this.updateToolbar()
|
|
);
|
|
this.openProjectManager(false);
|
|
};
|
|
|
|
openEventsFunctionsExtension = (
|
|
name: string,
|
|
initiallyFocusedFunctionName?: string,
|
|
initiallyFocusedBehaviorName?: ?string
|
|
) => {
|
|
const { eventsFunctionsExtensionsState } = this.props;
|
|
this.setState(
|
|
{
|
|
editorTabs: openEditorTab(this.state.editorTabs, {
|
|
name,
|
|
renderEditor: ({ isActive, editorRef }) => (
|
|
<EventsFunctionsExtensionEditor
|
|
project={this.state.currentProject}
|
|
eventsFunctionsExtensionName={name}
|
|
setToolbar={this.setEditorToolbar}
|
|
resourceSources={this.props.resourceSources}
|
|
onChooseResource={this._onChooseResource}
|
|
resourceExternalEditors={this.props.resourceExternalEditors}
|
|
isActive={isActive}
|
|
initiallyFocusedFunctionName={initiallyFocusedFunctionName}
|
|
initiallyFocusedBehaviorName={initiallyFocusedBehaviorName}
|
|
openInstructionOrExpression={this._openInstructionOrExpression}
|
|
onCreateEventsFunction={this._onCreateEventsFunction}
|
|
ref={editorRef}
|
|
onLoadEventsFunctionsExtensions={() => {
|
|
eventsFunctionsExtensionsState.loadProjectEventsFunctionsExtensions(
|
|
this.state.currentProject
|
|
);
|
|
}}
|
|
/>
|
|
),
|
|
key: 'events functions extension ' + name,
|
|
}),
|
|
},
|
|
() => this.updateToolbar()
|
|
);
|
|
this.openProjectManager(false);
|
|
};
|
|
|
|
openResources = () => {
|
|
const { i18n } = this.props;
|
|
this.setState(
|
|
{
|
|
editorTabs: openEditorTab(this.state.editorTabs, {
|
|
name: i18n._(t`Resources`),
|
|
renderEditor: ({ isActive, editorRef }) => (
|
|
<ResourcesEditor
|
|
project={this.state.currentProject}
|
|
setToolbar={this.setEditorToolbar}
|
|
onDeleteResource={(resource: gdResource, cb: boolean => void) => {
|
|
// TODO: Project wide refactoring of objects/events using the resource
|
|
cb(true);
|
|
}}
|
|
onRenameResource={(
|
|
resource: gdResource,
|
|
newName: string,
|
|
cb: boolean => void
|
|
) => {
|
|
// TODO: Project wide refactoring of objects/events using the resource
|
|
cb(true);
|
|
}}
|
|
isActive={isActive}
|
|
ref={editorRef}
|
|
onChooseResource={this._onChooseResource}
|
|
resourceSources={this.props.resourceSources}
|
|
/>
|
|
),
|
|
key: 'resources',
|
|
}),
|
|
},
|
|
() => this.updateToolbar()
|
|
);
|
|
};
|
|
|
|
openStartPage = () => {
|
|
const { i18n } = this.props;
|
|
this.setState(
|
|
{
|
|
editorTabs: openEditorTab(this.state.editorTabs, {
|
|
name: i18n._(t`Start Page`),
|
|
renderEditor: ({ isActive, editorRef }) => (
|
|
<StartPage
|
|
project={this.state.currentProject}
|
|
setToolbar={this.setEditorToolbar}
|
|
canOpen={!!this.props.projectsStorage.onOpenWithPicker}
|
|
onOpen={this.chooseProject}
|
|
onCreate={() => this.openCreateDialog()}
|
|
onOpenProjectManager={() => this.openProjectManager()}
|
|
onCloseProject={() => this.askToCloseProject()}
|
|
onOpenAboutDialog={() => this.openAboutDialog()}
|
|
onOpenHelpFinder={() => this.openHelpFinderDialog()}
|
|
onOpenLanguageDialog={() => this.openLanguage()}
|
|
isActive={isActive}
|
|
ref={editorRef}
|
|
/>
|
|
),
|
|
key: 'start page',
|
|
closable: false,
|
|
}),
|
|
},
|
|
() => this.updateToolbar()
|
|
);
|
|
};
|
|
|
|
openDebugger = () => {
|
|
const { i18n } = this.props;
|
|
this.setState(
|
|
{
|
|
editorTabs: openEditorTab(this.state.editorTabs, {
|
|
name: i18n._(t`Debugger`),
|
|
renderEditor: ({ isActive, editorRef }) => (
|
|
<DebuggerEditor
|
|
project={this.state.currentProject}
|
|
setToolbar={this.setEditorToolbar}
|
|
isActive={isActive}
|
|
ref={editorRef}
|
|
onChangeSubscription={() => this.openSubscription(true)}
|
|
/>
|
|
),
|
|
key: 'debugger',
|
|
}),
|
|
},
|
|
() => this.updateToolbar()
|
|
);
|
|
};
|
|
|
|
_openInstructionOrExpression = (
|
|
extension: gdPlatformExtension,
|
|
type: string
|
|
) => {
|
|
const { currentProject } = this.state;
|
|
if (!currentProject) return;
|
|
|
|
const extensionName = extension.getName();
|
|
if (currentProject.hasEventsFunctionsExtensionNamed(extensionName)) {
|
|
// It's an events functions extension, open the editor for it.
|
|
const eventsFunctionsExtension = currentProject.getEventsFunctionsExtension(
|
|
extensionName
|
|
);
|
|
const functionName = getFunctionNameFromType(type);
|
|
|
|
const foundTab = getEventsFunctionsExtensionEditor(
|
|
this.state.editorTabs,
|
|
eventsFunctionsExtension
|
|
);
|
|
if (foundTab) {
|
|
// Open the given function and focus the tab
|
|
foundTab.editor.selectEventsFunctionByName(
|
|
functionName.name,
|
|
functionName.behaviorName
|
|
);
|
|
this.setState(state => ({
|
|
editorTabs: changeCurrentTab(state.editorTabs, foundTab.tabIndex),
|
|
}));
|
|
} else {
|
|
// Open a new editor for the extension and the given function
|
|
this.openEventsFunctionsExtension(
|
|
extensionName,
|
|
functionName.name,
|
|
functionName.behaviorName
|
|
);
|
|
}
|
|
} else {
|
|
// It's not an events functions extension, we should not be here.
|
|
console.warn(
|
|
`Extension with name=${extensionName} can not be opened (no editor for this)`
|
|
);
|
|
}
|
|
};
|
|
|
|
_onCreateEventsFunction = (
|
|
extensionName: string,
|
|
eventsFunction: gdEventsFunction
|
|
) => {
|
|
const { currentProject } = this.state;
|
|
if (!currentProject) return;
|
|
const { eventsFunctionsExtensionsState } = this.props;
|
|
|
|
// Names are assumed to be already validated
|
|
const createNewExtension = !currentProject.hasEventsFunctionsExtensionNamed(
|
|
extensionName
|
|
);
|
|
const extension = createNewExtension
|
|
? currentProject.insertNewEventsFunctionsExtension(extensionName, 0)
|
|
: currentProject.getEventsFunctionsExtension(extensionName);
|
|
|
|
if (createNewExtension) {
|
|
extension.setFullName(extensionName);
|
|
extension.setDescription(
|
|
'Originally automatically extracted from events of the project'
|
|
);
|
|
}
|
|
|
|
extension.insertEventsFunction(eventsFunction, 0);
|
|
eventsFunctionsExtensionsState.loadProjectEventsFunctionsExtensions(
|
|
currentProject
|
|
);
|
|
};
|
|
|
|
openCreateDialog = (open: boolean = true) => {
|
|
this.setState({
|
|
createDialogOpen: open,
|
|
});
|
|
};
|
|
|
|
chooseProject = () => {
|
|
const { projectsStorage } = this.props;
|
|
if (!projectsStorage.onOpenWithPicker) return;
|
|
|
|
projectsStorage
|
|
.onOpenWithPicker()
|
|
.then(filepath => {
|
|
if (!filepath) return;
|
|
|
|
this.openFromPathOrURL(filepath, () =>
|
|
this.openSceneOrProjectManager()
|
|
);
|
|
})
|
|
.catch(() => {});
|
|
};
|
|
|
|
save = () => {
|
|
saveUiSettings(this.state.editorTabs);
|
|
|
|
const { currentProject } = this.state;
|
|
if (!currentProject) return;
|
|
const { i18n, projectsStorage } = this.props;
|
|
|
|
if (projectsStorage.onSaveProject) {
|
|
projectsStorage.onSaveProject(currentProject).then(
|
|
(saveDone: boolean) => {
|
|
if (saveDone) {
|
|
this._showSnackMessage(i18n._(t`Project properly saved`));
|
|
}
|
|
},
|
|
err => {
|
|
showErrorBox(
|
|
i18n._(
|
|
t`Unable to save the project! Please try again by choosing another location.`
|
|
),
|
|
err
|
|
);
|
|
}
|
|
);
|
|
}
|
|
};
|
|
|
|
saveAs = () => {
|
|
saveUiSettings(this.state.editorTabs);
|
|
|
|
const { currentProject } = this.state;
|
|
if (!currentProject) return;
|
|
const { i18n, projectsStorage } = this.props;
|
|
|
|
if (projectsStorage.onSaveProjectAs) {
|
|
projectsStorage.onSaveProjectAs(currentProject).then(
|
|
saveDone => {
|
|
if (saveDone)
|
|
this._showSnackMessage(i18n._(t`Project properly saved`));
|
|
},
|
|
err => {
|
|
showErrorBox(
|
|
i18n._(
|
|
t`Unable to save as the project! Please try again by choosing another location.`
|
|
),
|
|
err
|
|
);
|
|
}
|
|
);
|
|
}
|
|
};
|
|
|
|
askToCloseProject = (cb: ?Function) => {
|
|
if (!this.state.currentProject) return;
|
|
const { i18n } = this.props;
|
|
|
|
//eslint-disable-next-line
|
|
const answer = confirm(
|
|
i18n._(
|
|
t`Close the project? Any changes that have not been saved will be lost.`
|
|
)
|
|
);
|
|
if (!answer) return;
|
|
|
|
const noop = () => {};
|
|
this.closeProject(cb || noop);
|
|
};
|
|
|
|
openSceneOrProjectManager = () => {
|
|
const { currentProject } = this.state;
|
|
if (!currentProject) return;
|
|
|
|
if (currentProject.getLayoutsCount() === 1) {
|
|
this.openLayout(currentProject.getLayoutAt(0).getName(), {
|
|
openSceneEditor: true,
|
|
openEventsEditor: true,
|
|
});
|
|
} else {
|
|
this.openProjectManager();
|
|
}
|
|
};
|
|
|
|
openExportDialog = (open: boolean = true) => {
|
|
this.setState({
|
|
exportDialogOpen: open,
|
|
});
|
|
};
|
|
|
|
_openIntroDialog = (open: boolean = true) => {
|
|
this.setState({
|
|
introDialogOpen: open,
|
|
});
|
|
};
|
|
|
|
_openGenericDialog = (open: boolean = true) => {
|
|
this.setState({
|
|
genericDialogOpen: open,
|
|
genericDialog: null,
|
|
});
|
|
};
|
|
|
|
openPreferences = (open: boolean = true) => {
|
|
this.setState({
|
|
preferencesDialogOpen: open,
|
|
});
|
|
};
|
|
|
|
openLanguage = (open: boolean = true) => {
|
|
this.setState({
|
|
languageDialogOpen: open,
|
|
});
|
|
};
|
|
|
|
openProfile = (open: boolean = true) => {
|
|
this.setState({
|
|
profileDialogOpen: open,
|
|
});
|
|
};
|
|
|
|
openSubscription = (open: boolean = true) => {
|
|
this.setState({
|
|
subscriptionDialogOpen: open,
|
|
});
|
|
};
|
|
|
|
_onChangeEditorTab = (value: number) => {
|
|
this.setState(
|
|
{
|
|
editorTabs: changeCurrentTab(this.state.editorTabs, value),
|
|
},
|
|
() => this._onEditorTabActive(getCurrentTab(this.state.editorTabs))
|
|
);
|
|
};
|
|
|
|
_onEditorTabActive = (editorTab: EditorTab) => {
|
|
this.updateToolbar();
|
|
};
|
|
|
|
_onCloseEditorTab = (editorTab: EditorTab) => {
|
|
saveUiSettings(this.state.editorTabs);
|
|
this.setState(
|
|
{
|
|
editorTabs: closeEditorTab(this.state.editorTabs, editorTab),
|
|
},
|
|
() => this.updateToolbar()
|
|
);
|
|
};
|
|
|
|
_onCloseOtherEditorTabs = (editorTab: EditorTab) => {
|
|
saveUiSettings(this.state.editorTabs);
|
|
this.setState(
|
|
{
|
|
editorTabs: closeOtherEditorTabs(this.state.editorTabs, editorTab),
|
|
},
|
|
() => this.updateToolbar()
|
|
);
|
|
};
|
|
|
|
_onCloseAllEditorTabs = () => {
|
|
saveUiSettings(this.state.editorTabs);
|
|
this.setState(
|
|
{
|
|
editorTabs: closeAllEditorTabs(this.state.editorTabs),
|
|
},
|
|
() => this.updateToolbar()
|
|
);
|
|
};
|
|
|
|
_onChooseResource = (
|
|
sourceName: string,
|
|
multiSelection: boolean = true
|
|
): Promise<Array<any>> => {
|
|
const { currentProject } = this.state;
|
|
const resourceSourceDialog = this._resourceSourceDialogs[sourceName];
|
|
if (!resourceSourceDialog) return Promise.resolve([]);
|
|
|
|
return resourceSourceDialog.chooseResources(currentProject, multiSelection);
|
|
};
|
|
|
|
updateToolbar() {
|
|
const editorTab = getCurrentTab(this.state.editorTabs);
|
|
if (!editorTab || !editorTab.editorRef) {
|
|
this.setEditorToolbar(null);
|
|
return;
|
|
}
|
|
|
|
editorTab.editorRef.updateToolbar();
|
|
}
|
|
|
|
openAboutDialog = (open: boolean = true) => {
|
|
this.setState({
|
|
aboutDialogOpen: open,
|
|
});
|
|
};
|
|
|
|
openPlatformSpecificAssets = (open: boolean = true) => {
|
|
this.setState({
|
|
platformSpecificAssetsDialogOpen: open,
|
|
});
|
|
};
|
|
|
|
openHelpFinderDialog = (open: boolean = true) => {
|
|
this.setState({
|
|
helpFinderDialogOpen: open,
|
|
});
|
|
};
|
|
|
|
setUpdateStatus = (updateStatus: UpdateStatus) => {
|
|
this.setState({
|
|
updateStatus,
|
|
});
|
|
|
|
const notificationTitle = getUpdateNotificationTitle(updateStatus);
|
|
const notificationBody = getUpdateNotificationBody(updateStatus);
|
|
if (notificationTitle) {
|
|
const notification = new window.Notification(notificationTitle, {
|
|
body: notificationBody,
|
|
});
|
|
notification.onclick = () => this.openAboutDialog(true);
|
|
}
|
|
};
|
|
|
|
simulateUpdateDownloaded = () =>
|
|
this.setUpdateStatus({
|
|
status: 'update-downloaded',
|
|
message: 'update-downloaded',
|
|
info: {
|
|
releaseName: 'Fake update',
|
|
},
|
|
});
|
|
|
|
simulateUpdateAvailable = () =>
|
|
this.setUpdateStatus({
|
|
status: 'update-available',
|
|
message: 'Update available',
|
|
});
|
|
|
|
_showSnackMessage = (snackMessage: string) =>
|
|
this.setState({
|
|
snackMessage,
|
|
snackMessageOpen: true,
|
|
});
|
|
|
|
_closeSnackMessage = () =>
|
|
this.setState({
|
|
snackMessageOpen: false,
|
|
});
|
|
|
|
render() {
|
|
const {
|
|
currentProject,
|
|
genericDialog,
|
|
projectManagerOpen,
|
|
profileDialogOpen,
|
|
subscriptionDialogOpen,
|
|
updateStatus,
|
|
aboutDialogOpen,
|
|
helpFinderDialogOpen,
|
|
eventsFunctionsExtensionsError,
|
|
} = this.state;
|
|
const {
|
|
renderExportDialog,
|
|
createDialog,
|
|
introDialog,
|
|
resourceSources,
|
|
authentification,
|
|
previewLauncher,
|
|
resourceExternalEditors,
|
|
eventsFunctionsExtensionsState,
|
|
i18n,
|
|
} = this.props;
|
|
const showLoader =
|
|
this.state.loadingProject ||
|
|
this.state.previewLoading ||
|
|
this.props.loading;
|
|
|
|
return (
|
|
<div className="main-frame">
|
|
<ProjectTitlebar project={currentProject} />
|
|
<Drawer
|
|
open={projectManagerOpen}
|
|
PaperProps={{
|
|
style: styles.drawerContent,
|
|
}}
|
|
onClose={this.toggleProjectManager}
|
|
>
|
|
<EditorBar
|
|
title={currentProject ? currentProject.getName() : 'No project'}
|
|
displayRightCloseButton
|
|
onClose={this.toggleProjectManager}
|
|
/>
|
|
{currentProject && (
|
|
<ProjectManager
|
|
project={currentProject}
|
|
onOpenExternalEvents={this.openExternalEvents}
|
|
onOpenLayout={this.openLayout}
|
|
onOpenExternalLayout={this.openExternalLayout}
|
|
onOpenEventsFunctionsExtension={this.openEventsFunctionsExtension}
|
|
onAddLayout={this.addLayout}
|
|
onAddExternalLayout={this.addExternalLayout}
|
|
onAddEventsFunctionsExtension={this.addEventsFunctionsExtension}
|
|
onAddExternalEvents={this.addExternalEvents}
|
|
onDeleteLayout={this.deleteLayout}
|
|
onDeleteExternalLayout={this.deleteExternalLayout}
|
|
onDeleteEventsFunctionsExtension={
|
|
this.deleteEventsFunctionsExtension
|
|
}
|
|
onDeleteExternalEvents={this.deleteExternalEvents}
|
|
onRenameLayout={this.renameLayout}
|
|
onRenameExternalLayout={this.renameExternalLayout}
|
|
onRenameEventsFunctionsExtension={
|
|
this.renameEventsFunctionsExtension
|
|
}
|
|
onRenameExternalEvents={this.renameExternalEvents}
|
|
onSaveProject={this.save}
|
|
onCloseProject={this.askToCloseProject}
|
|
onExportProject={this.openExportDialog}
|
|
onOpenPreferences={() => this.openPreferences(true)}
|
|
onOpenResources={() => {
|
|
this.openResources();
|
|
this.openProjectManager(false);
|
|
}}
|
|
onOpenPlatformSpecificAssets={() =>
|
|
this.openPlatformSpecificAssets()
|
|
}
|
|
onChangeSubscription={() => this.openSubscription(true)}
|
|
eventsFunctionsExtensionsError={eventsFunctionsExtensionsError}
|
|
onReloadEventsFunctionsExtensions={() => {
|
|
// Check if load is sufficient
|
|
eventsFunctionsExtensionsState.reloadProjectEventsFunctionsExtensions(
|
|
currentProject
|
|
);
|
|
}}
|
|
freezeUpdate={!projectManagerOpen}
|
|
/>
|
|
)}
|
|
{!currentProject && (
|
|
<EmptyMessage>
|
|
<Trans>To begin, open or create a new project.</Trans>
|
|
</EmptyMessage>
|
|
)}
|
|
</Drawer>
|
|
<Toolbar
|
|
ref={toolbar => (this.toolbar = toolbar)}
|
|
showProjectIcons={!this.props.integratedEditor}
|
|
hasProject={!!this.state.currentProject}
|
|
toggleProjectManager={this.toggleProjectManager}
|
|
exportProject={() => this.openExportDialog(true)}
|
|
requestUpdate={this.props.requestUpdate}
|
|
simulateUpdateDownloaded={this.simulateUpdateDownloaded}
|
|
simulateUpdateAvailable={this.simulateUpdateAvailable}
|
|
/>
|
|
<ClosableTabs hideLabels={!!this.props.integratedEditor}>
|
|
{getEditors(this.state.editorTabs).map((editorTab, id) => {
|
|
const isCurrentTab =
|
|
getCurrentTabIndex(this.state.editorTabs) === id;
|
|
return (
|
|
<ClosableTab
|
|
label={editorTab.name}
|
|
key={editorTab.key}
|
|
active={isCurrentTab}
|
|
onClick={() => this._onChangeEditorTab(id)}
|
|
onClose={() => this._onCloseEditorTab(editorTab)}
|
|
onCloseOthers={() => this._onCloseOtherEditorTabs(editorTab)}
|
|
onCloseAll={this._onCloseAllEditorTabs}
|
|
onActivated={() => this._onEditorTabActive(editorTab)}
|
|
closable={editorTab.closable}
|
|
/>
|
|
);
|
|
})}
|
|
</ClosableTabs>
|
|
{getEditors(this.state.editorTabs).map((editorTab, id) => {
|
|
const isCurrentTab = getCurrentTabIndex(this.state.editorTabs) === id;
|
|
return (
|
|
<TabContentContainer key={editorTab.key} active={isCurrentTab}>
|
|
<ErrorBoundary>{editorTab.render(isCurrentTab)}</ErrorBoundary>
|
|
</TabContentContainer>
|
|
);
|
|
})}
|
|
<LoaderModal show={showLoader} />
|
|
<HelpFinder
|
|
open={helpFinderDialogOpen}
|
|
onClose={() => this.openHelpFinderDialog(false)}
|
|
/>
|
|
<Snackbar
|
|
open={this.state.snackMessageOpen}
|
|
autoHideDuration={3000}
|
|
onClose={this._closeSnackMessage}
|
|
ContentProps={{
|
|
'aria-describedby': 'snackbar-message',
|
|
}}
|
|
message={<span id="snackbar-message">{this.state.snackMessage}</span>}
|
|
/>
|
|
{!!renderExportDialog &&
|
|
this.state.exportDialogOpen &&
|
|
renderExportDialog({
|
|
onClose: () => this.openExportDialog(false),
|
|
onChangeSubscription: () => {
|
|
this.openExportDialog(false);
|
|
this.openSubscription(true);
|
|
},
|
|
project: this.state.currentProject,
|
|
authentification,
|
|
})}
|
|
{!!createDialog &&
|
|
React.cloneElement(createDialog, {
|
|
open: this.state.createDialogOpen,
|
|
onClose: () => this.openCreateDialog(false),
|
|
onOpen: filepath => {
|
|
this.openCreateDialog(false);
|
|
this.openFromPathOrURL(filepath, () =>
|
|
this.openSceneOrProjectManager()
|
|
);
|
|
},
|
|
onCreate: project => {
|
|
this.openCreateDialog(false);
|
|
this.loadFromProject(project, () =>
|
|
this.openSceneOrProjectManager()
|
|
);
|
|
},
|
|
})}
|
|
{!!introDialog &&
|
|
React.cloneElement(introDialog, {
|
|
open: this.state.introDialogOpen,
|
|
onClose: () => this._openIntroDialog(false),
|
|
})}
|
|
{!!this.state.currentProject &&
|
|
this.state.platformSpecificAssetsDialogOpen && (
|
|
<PlatformSpecificAssetsDialog
|
|
project={this.state.currentProject}
|
|
open
|
|
onApply={() => this.openPlatformSpecificAssets(false)}
|
|
onClose={() => this.openPlatformSpecificAssets(false)}
|
|
resourceSources={resourceSources}
|
|
onChooseResource={this._onChooseResource}
|
|
resourceExternalEditors={resourceExternalEditors}
|
|
/>
|
|
)}
|
|
{!!genericDialog &&
|
|
React.cloneElement(genericDialog, {
|
|
open: this.state.genericDialogOpen,
|
|
onClose: () => this._openGenericDialog(false),
|
|
})}
|
|
{!!previewLauncher &&
|
|
React.cloneElement(previewLauncher, {
|
|
ref: (previewLauncher: ?PreviewLauncher) =>
|
|
(this._previewLauncher = previewLauncher),
|
|
onExport: () => this.openExportDialog(true),
|
|
onChangeSubscription: () => this.openSubscription(true),
|
|
})}
|
|
{resourceSources.map(
|
|
(resourceSource, index): React.Node => {
|
|
const Component = resourceSource.component;
|
|
return (
|
|
<Component
|
|
key={resourceSource.name}
|
|
ref={dialog =>
|
|
(this._resourceSourceDialogs[resourceSource.name] = dialog)
|
|
}
|
|
i18n={i18n}
|
|
/>
|
|
);
|
|
}
|
|
)}
|
|
{profileDialogOpen && (
|
|
<ProfileDialog
|
|
open
|
|
onClose={() => this.openProfile(false)}
|
|
onChangeSubscription={() => this.openSubscription(true)}
|
|
/>
|
|
)}
|
|
{subscriptionDialogOpen && (
|
|
<SubscriptionDialog
|
|
onClose={() => {
|
|
this.openSubscription(false);
|
|
}}
|
|
open
|
|
/>
|
|
)}
|
|
{this.state.preferencesDialogOpen && (
|
|
<PreferencesDialog open onClose={() => this.openPreferences(false)} />
|
|
)}
|
|
{this.state.languageDialogOpen && (
|
|
<LanguageDialog
|
|
open
|
|
onClose={languageChanged => {
|
|
this.openLanguage(false);
|
|
if (languageChanged) {
|
|
this._languageDidChange();
|
|
}
|
|
}}
|
|
/>
|
|
)}
|
|
{aboutDialogOpen && (
|
|
<AboutDialog
|
|
open
|
|
onClose={() => this.openAboutDialog(false)}
|
|
updateStatus={updateStatus}
|
|
/>
|
|
)}
|
|
<CloseConfirmDialog shouldPrompt={!!this.state.currentProject} />
|
|
<ChangelogDialogContainer />
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
|
|
export default MainFrame;
|