Skip to content

Commit

Permalink
When duplicating a project, ask for the new name and if link with gam…
Browse files Browse the repository at this point in the history
…e should be kept (#7253)

Don't show in changelog
  • Loading branch information
AlexandreSi authored Dec 19, 2024
1 parent 88a2060 commit 16d94b5
Show file tree
Hide file tree
Showing 13 changed files with 272 additions and 126 deletions.
5 changes: 4 additions & 1 deletion newIDE/app/src/GameDashboard/GameDashboardCard.js
Original file line number Diff line number Diff line change
Expand Up @@ -487,7 +487,10 @@ const GameDashboardCard = ({
// If there are multiple projects, suggest opening the game dashboard.
actions.push({
label: i18n._(t`See all projects`),
click: game ? () => onOpenGameManager({ game }) : undefined,
click: game
? () =>
onOpenGameManager({ game, widgetToScrollTo: 'projects' })
: undefined,
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ const ProjectFileList = ({
}: Props) => {
const projectFiles = useProjectsListFor(game);
const contextMenu = React.useRef<?ContextMenuInterface>(null);
const [loadingProjectId, setLoadingProjectId] = React.useState<?string>(null);
const { removeRecentProjectFile } = React.useContext(PreferencesContext);
const authenticatedUser = React.useContext(AuthenticatedUserContext);
const {
Expand Down Expand Up @@ -141,6 +142,18 @@ const ProjectFileList = ({
[removeRecentProjectFile, showConfirmation]
);

const onWillDeleteCloudProject = React.useCallback(
async (i18n, file: FileMetadataAndStorageProviderName) => {
setLoadingProjectId(file.fileMetadata.fileIdentifier);
try {
await onDeleteCloudProject(i18n, file);
} finally {
setLoadingProjectId(null);
}
},
[onDeleteCloudProject]
);

const buildContextMenu = (
i18n: I18nType,
file: ?FileMetadataAndStorageProviderName
Expand All @@ -155,7 +168,7 @@ const ProjectFileList = ({
{ type: 'separator' },
{
label: i18n._(t`Delete`),
click: () => onDeleteCloudProject(i18n, file),
click: () => onWillDeleteCloudProject(i18n, file),
}
);
} else if (file.storageProviderName === 'LocalFile') {
Expand Down Expand Up @@ -226,7 +239,7 @@ const ProjectFileList = ({
</ListItem>
))
) : projectFiles.length > 0 ? (
<Line>
<Line noMargin>
<Column noMargin expand>
{!isMobile && (
<Line justifyContent="space-between">
Expand All @@ -253,7 +266,10 @@ const ProjectFileList = ({
key={file.fileMetadata.fileIdentifier}
file={file}
onOpenContextMenu={openContextMenu}
isLoading={disabled}
disabled={disabled}
isLoading={
file.fileMetadata.fileIdentifier === loadingProjectId
}
currentFileMetadata={currentFileMetadata}
storageProviders={storageProviders}
isWindowSizeMediumOrLarger={!isMobile}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ type ProjectFileListItemProps = {|
storageProviders: Array<StorageProvider>,
onOpenProject: (file: FileMetadataAndStorageProviderName) => Promise<void>,
isWindowSizeMediumOrLarger: boolean,
disabled: boolean,
isLoading: boolean,
onOpenContextMenu: (
event: ClientCoordinates,
Expand All @@ -76,6 +77,7 @@ export const ProjectFileListItem = ({
storageProviders,
onOpenProject,
isWindowSizeMediumOrLarger,
disabled,
isLoading,
onOpenContextMenu,
}: ProjectFileListItemProps) => {
Expand All @@ -86,12 +88,20 @@ export const ProjectFileListItem = ({
file.storageProviderName
);

const onWillOpenContextMenu = React.useCallback(
(event: ClientCoordinates, file: FileMetadataAndStorageProviderName) => {
if (disabled) return;
onOpenContextMenu(event, file);
},
[disabled, onOpenContextMenu]
);

const longTouchForContextMenuProps = useLongTouch(
React.useCallback(
event => {
onOpenContextMenu(event, file);
onWillOpenContextMenu(event, file);
},
[onOpenContextMenu, file]
[onWillOpenContextMenu, file]
)
);
return (
Expand All @@ -102,10 +112,11 @@ export const ProjectFileListItem = ({
button
key={file.fileMetadata.fileIdentifier}
onClick={() => {
if (disabled) return;
onOpenProject(file);
}}
style={styles.listItem}
onContextMenu={event => onOpenContextMenu(event, file)}
onContextMenu={event => onWillOpenContextMenu(event, file)}
{...longTouchForContextMenuProps}
>
{isWindowSizeMediumOrLarger ? (
Expand Down Expand Up @@ -158,7 +169,7 @@ export const ProjectFileListItem = ({
onClick={event => {
// prevent triggering the click on the list item.
event.stopPropagation();
onOpenContextMenu(event, file);
onWillOpenContextMenu(event, file);
}}
>
<ThreeDotsMenu />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@ const CreateSection = ({
const isCurrentProjectOpened =
!!project &&
!!currentFileMetadata &&
fileMetadata.gameId === currentFileMetadata.gameId;
fileMetadata.fileIdentifier === currentFileMetadata.fileIdentifier;

if (isCurrentProjectOpened) {
const result = await showConfirmation({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ const TeamMemberProjectsView = ({
file={file}
currentFileMetadata={currentFileMetadata}
key={file.fileMetadata.fileIdentifier}
disabled={false}
isLoading={false}
onOpenContextMenu={openContextMenu}
onOpenProject={onOpenRecentFile}
Expand Down
38 changes: 36 additions & 2 deletions newIDE/app/src/MainFrame/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ import {
type StorageProviderOperations,
type FileMetadata,
type SaveAsLocation,
type SaveAsOptions,
type FileMetadataAndStorageProviderName,
type ResourcesActionsMenuBuilder,
} from '../ProjectsStorage';
Expand Down Expand Up @@ -2604,15 +2605,27 @@ const MainFrame = (props: Props) => {
try {
let newSaveAsLocation: ?SaveAsLocation =
options && options.forcedSavedAsLocation;
let newSaveAsOptions: ?SaveAsOptions = null;
if (onChooseSaveProjectAsLocation && !newSaveAsLocation) {
const { saveAsLocation } = await onChooseSaveProjectAsLocation({
const {
saveAsLocation,
saveAsOptions,
} = await onChooseSaveProjectAsLocation({
project: currentProject,
fileMetadata: currentFileMetadata,
displayOptionToGenerateNewProjectUuid:
// No need to display the option if current file metadata doesn't have
// a gameId...
!!currentFileMetadata &&
!!currentFileMetadata.gameId &&
// ... or if the project is opened from a URL.
oldStorageProvider.internalName !== 'UrlStorageProvider',
});
if (!saveAsLocation) {
return; // Save as was cancelled.
}
newSaveAsLocation = saveAsLocation;
newSaveAsOptions = saveAsOptions;
}

if (canFileMetadataBeSafelySavedAs && currentFileMetadata) {
Expand All @@ -2627,6 +2640,21 @@ const MainFrame = (props: Props) => {
if (!canProjectBeSafelySavedAs) return;
}

let originalProjectUuid = null;
if (newSaveAsOptions && newSaveAsOptions.generateNewProjectUuid) {
originalProjectUuid = currentProject.getProjectUuid();
currentProject.resetProjectUuid();
}
let originalProjectName = null;
const newProjectName =
newSaveAsLocation && newSaveAsLocation.name
? newSaveAsLocation.name
: null;
if (newProjectName) {
originalProjectName = currentProject.getName();
currentProject.setName(newProjectName);
}

const { wasSaved, fileMetadata } = await onSaveProjectAs(
currentProject,
newSaveAsLocation,
Expand All @@ -2649,7 +2677,13 @@ const MainFrame = (props: Props) => {
}
);

if (!wasSaved) return; // Save was cancelled, don't do anything.
if (!wasSaved) {
_replaceSnackMessage(i18n._(t`An error occurred. Please try again.`));
if (originalProjectName) currentProject.setName(originalProjectName);
if (originalProjectUuid)
currentProject.setProjectUuid(originalProjectUuid);
return;
}

sealUnsavedChanges({ setCheckpointTime: true });
_replaceSnackMessage(i18n._(t`Project properly saved`));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
// @flow
import * as React from 'react';
import { type AuthenticatedUser } from '../../Profile/AuthenticatedUserContext';
import { type FileMetadata, type SaveAsLocation } from '..';
import { type FileMetadata, type SaveAsLocation, type SaveAsOptions } from '..';
import {
CLOUD_PROJECT_NAME_MAX_LENGTH,
commitVersion,
createCloudProject,
getCredentialsForCloudProject,
Expand All @@ -11,7 +12,6 @@ import {
import type { $AxiosError } from 'axios';
import type { MessageDescriptor } from '../../Utils/i18n/MessageDescriptor.flow';
import { serializeToJSON } from '../../Utils/Serializer';
import CloudSaveAsDialog from './CloudSaveAsDialog';
import { t } from '@lingui/macro';
import {
createZipWithSingleTextFile,
Expand All @@ -21,6 +21,7 @@ import ProjectCache from '../../Utils/ProjectCache';
import { getProjectCache } from './CloudProjectOpener';
import { retryIfFailed } from '../../Utils/RetryIfFailed';
import { extractGDevelopApiErrorStatusAndCode } from '../../Utils/GDevelopServices/Errors';
import SaveAsOptionsDialog from '../SaveAsOptionsDialog';

const zipProject = async (project: gdProject): Promise<[Blob, string]> => {
const projectJson = serializeToJSON(project);
Expand Down Expand Up @@ -184,38 +185,49 @@ export const generateOnChooseSaveProjectAsLocation = ({
|}) => async ({
project,
fileMetadata,
displayOptionToGenerateNewProjectUuid,
}: {|
project: gdProject,
fileMetadata: ?FileMetadata,
displayOptionToGenerateNewProjectUuid: boolean,
|}): Promise<{|
saveAsLocation: ?SaveAsLocation,
saveAsOptions: ?SaveAsOptions,
|}> => {
if (!authenticatedUser.authenticated) {
return { saveAsLocation: null };
return { saveAsLocation: null, saveAsOptions: null };
}

const name = await new Promise(resolve => {
const options = await new Promise(resolve => {
setDialog(() => (
<CloudSaveAsDialog
<SaveAsOptionsDialog
onCancel={() => {
closeDialog();
resolve(null);
}}
nameSuggestion={project.getName()}
onSave={(newName: string) => {
nameMaxLength={CLOUD_PROJECT_NAME_MAX_LENGTH}
nameSuggestion={
fileMetadata ? `${project.getName()} - Copy` : project.getName()
}
displayOptionToGenerateNewProjectUuid={
displayOptionToGenerateNewProjectUuid
}
onSave={options => {
closeDialog();
resolve(newName);
resolve(options);
}}
/>
));
});

if (!name) return { saveAsLocation: null }; // Save was cancelled.
if (!options) return { saveAsLocation: null, saveAsOptions: null }; // Save was cancelled.

return {
saveAsLocation: {
name,
gameId: project.getProjectUuid(),
name: options.name,
},
saveAsOptions: {
generateNewProjectUuid: options.generateNewProjectUuid,
},
};
};
Expand Down Expand Up @@ -243,7 +255,7 @@ export const generateOnSaveProjectAs = (
}
options.onStartSaving();

const gameId = saveAsLocation.gameId || project.getProjectUuid();
const gameId = project.getProjectUuid();

try {
// Create a new cloud project.
Expand Down

This file was deleted.

Loading

0 comments on commit 16d94b5

Please sign in to comment.