Improved project creation validation
No longer requires project name to be part of the project path.
This commit is contained in:
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:40949893ed7009eeaa90b7ce6057cb6be9dfaf7b162e3c26ba9dadf985939d7d
|
||||
size 2038
|
||||
oid sha256:b9cd9d6f67440c193a85969ec5c082c6343e6d1fff3b6f209a0a6931eb22dd47
|
||||
size 2949
|
||||
|
||||
@@ -67,6 +67,15 @@ namespace O3DE::ProjectManager
|
||||
return ProjectManagerScreen::CreateProject;
|
||||
}
|
||||
|
||||
void CreateProjectCtrl::NotifyCurrentScreen()
|
||||
{
|
||||
ScreenWidget* currentScreen = reinterpret_cast<ScreenWidget*>(m_stack->currentWidget());
|
||||
if (currentScreen)
|
||||
{
|
||||
currentScreen->NotifyCurrentScreen();
|
||||
}
|
||||
}
|
||||
|
||||
void CreateProjectCtrl::HandleBackButton()
|
||||
{
|
||||
if (m_stack->currentIndex() > 0)
|
||||
@@ -110,6 +119,9 @@ namespace O3DE::ProjectManager
|
||||
auto result = PythonBindingsInterface::Get()->CreateProject(m_projectTemplatePath, m_projectInfo);
|
||||
if (result.IsSuccess())
|
||||
{
|
||||
// automatically register the project
|
||||
PythonBindingsInterface::Get()->AddProject(m_projectInfo.m_path);
|
||||
|
||||
// adding gems is not implemented yet because we don't know what targets to add or how to add them
|
||||
emit ChangeScreenRequest(ProjectManagerScreen::Projects);
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ namespace O3DE::ProjectManager
|
||||
explicit CreateProjectCtrl(QWidget* parent = nullptr);
|
||||
~CreateProjectCtrl() = default;
|
||||
ProjectManagerScreen GetScreenEnum() override;
|
||||
void NotifyCurrentScreen() override;
|
||||
|
||||
protected slots:
|
||||
void HandleBackButton();
|
||||
|
||||
@@ -78,6 +78,14 @@ namespace O3DE::ProjectManager
|
||||
m_errorLabel->setText(labelText);
|
||||
}
|
||||
|
||||
void FormLineEditWidget::setErrorLabelVisible(bool visible)
|
||||
{
|
||||
m_errorLabel->setVisible(visible);
|
||||
m_frame->setProperty("Valid", !visible);
|
||||
|
||||
refreshStyle();
|
||||
}
|
||||
|
||||
QLineEdit* FormLineEditWidget::lineEdit() const
|
||||
{
|
||||
return m_lineEdit;
|
||||
|
||||
@@ -39,6 +39,7 @@ namespace O3DE::ProjectManager
|
||||
|
||||
//! Set the error message for to display when invalid.
|
||||
void setErrorLabelText(const QString& labelText);
|
||||
void setErrorLabelVisible(bool visible);
|
||||
|
||||
//! Returns a pointer to the underlying LineEdit.
|
||||
QLineEdit* lineEdit() const;
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include <FormLineEditWidget.h>
|
||||
#include <FormBrowseEditWidget.h>
|
||||
#include <PathValidator.h>
|
||||
#include <EngineInfo.h>
|
||||
|
||||
#include <QVBoxLayout>
|
||||
#include <QHBoxLayout>
|
||||
@@ -49,16 +50,16 @@ namespace O3DE::ProjectManager
|
||||
vLayout->setContentsMargins(0,0,0,0);
|
||||
vLayout->setAlignment(Qt::AlignTop);
|
||||
{
|
||||
m_projectName = new FormLineEditWidget(tr("Project name"), tr("New Project"), this);
|
||||
m_projectName->setErrorLabelText(
|
||||
tr("A project with this name already exists at this location. Please choose a new name or location."));
|
||||
const QString defaultName{ "NewProject" };
|
||||
const QString defaultPath = QDir::toNativeSeparators(GetDefaultProjectPath() + "/" + defaultName);
|
||||
|
||||
m_projectName = new FormLineEditWidget(tr("Project name"), defaultName, this);
|
||||
connect(m_projectName->lineEdit(), &QLineEdit::textChanged, this, &NewProjectSettingsScreen::ValidateProjectPath);
|
||||
vLayout->addWidget(m_projectName);
|
||||
|
||||
m_projectPath =
|
||||
new FormBrowseEditWidget(tr("Project Location"), QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), this);
|
||||
m_projectPath = new FormBrowseEditWidget(tr("Project Location"), defaultPath, this);
|
||||
m_projectPath->lineEdit()->setReadOnly(true);
|
||||
m_projectPath->setErrorLabelText(tr("Please provide a valid path to a folder that exists"));
|
||||
m_projectPath->lineEdit()->setValidator(new PathValidator(PathValidator::PathMode::ExistingFolder, this));
|
||||
connect(m_projectPath->lineEdit(), &QLineEdit::textChanged, this, &NewProjectSettingsScreen::ValidateProjectPath);
|
||||
vLayout->addWidget(m_projectPath);
|
||||
|
||||
// if we don't use a QFrame we cannot "contain" the widgets inside and move them around
|
||||
@@ -112,17 +113,41 @@ namespace O3DE::ProjectManager
|
||||
this->setLayout(hLayout);
|
||||
}
|
||||
|
||||
QString NewProjectSettingsScreen::GetDefaultProjectPath()
|
||||
{
|
||||
QString defaultPath = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
|
||||
AZ::Outcome<EngineInfo> engineInfoResult = PythonBindingsInterface::Get()->GetEngineInfo();
|
||||
if (engineInfoResult.IsSuccess())
|
||||
{
|
||||
QDir path(QDir::toNativeSeparators(engineInfoResult.GetValue().m_defaultProjectsFolder));
|
||||
if (path.exists())
|
||||
{
|
||||
defaultPath = path.absolutePath();
|
||||
}
|
||||
}
|
||||
return defaultPath;
|
||||
}
|
||||
|
||||
ProjectManagerScreen NewProjectSettingsScreen::GetScreenEnum()
|
||||
{
|
||||
return ProjectManagerScreen::NewProjectSettings;
|
||||
}
|
||||
|
||||
void NewProjectSettingsScreen::ValidateProjectPath()
|
||||
{
|
||||
Validate();
|
||||
}
|
||||
|
||||
void NewProjectSettingsScreen::NotifyCurrentScreen()
|
||||
{
|
||||
Validate();
|
||||
}
|
||||
|
||||
ProjectInfo NewProjectSettingsScreen::GetProjectInfo()
|
||||
{
|
||||
ProjectInfo projectInfo;
|
||||
projectInfo.m_projectName = m_projectName->lineEdit()->text();
|
||||
projectInfo.m_path = QDir::toNativeSeparators(m_projectPath->lineEdit()->text() + "/" + projectInfo.m_projectName);
|
||||
projectInfo.m_path = m_projectPath->lineEdit()->text();
|
||||
return projectInfo;
|
||||
}
|
||||
|
||||
@@ -133,24 +158,44 @@ namespace O3DE::ProjectManager
|
||||
|
||||
bool NewProjectSettingsScreen::Validate()
|
||||
{
|
||||
bool projectNameIsValid = true;
|
||||
if (m_projectName->lineEdit()->text().isEmpty())
|
||||
{
|
||||
projectNameIsValid = false;
|
||||
}
|
||||
|
||||
bool projectPathIsValid = true;
|
||||
if (m_projectPath->lineEdit()->text().isEmpty())
|
||||
{
|
||||
projectPathIsValid = false;
|
||||
m_projectPath->setErrorLabelText(tr("Please provide a valid location."));
|
||||
}
|
||||
|
||||
QDir path(QDir::toNativeSeparators(m_projectPath->lineEdit()->text() + "/" + m_projectName->lineEdit()->text()));
|
||||
if (path.exists() && !path.isEmpty())
|
||||
else
|
||||
{
|
||||
projectPathIsValid = false;
|
||||
QDir path(m_projectPath->lineEdit()->text());
|
||||
if (path.exists() && !path.isEmpty())
|
||||
{
|
||||
projectPathIsValid = false;
|
||||
m_projectPath->setErrorLabelText(tr("This folder exists and isn't empty. Please choose a different location."));
|
||||
}
|
||||
}
|
||||
|
||||
bool projectNameIsValid = true;
|
||||
if (m_projectName->lineEdit()->text().isEmpty())
|
||||
{
|
||||
projectNameIsValid = false;
|
||||
m_projectName->setErrorLabelText(tr("Please provide a project name."));
|
||||
}
|
||||
else
|
||||
{
|
||||
// this validation should roughly match the utils.validate_identifier which the cli
|
||||
// uses to validate project names
|
||||
QRegExp validProjectNameRegex("[A-Za-z][A-Za-z0-9_-]{0,63}");
|
||||
const bool result = validProjectNameRegex.exactMatch(m_projectName->lineEdit()->text());
|
||||
if (!result)
|
||||
{
|
||||
projectNameIsValid = false;
|
||||
m_projectName->setErrorLabelText(tr("Project names must start with a letter and consist of up to 64 letter, number, '_' or '-' characters"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
m_projectName->setErrorLabelVisible(!projectNameIsValid);
|
||||
m_projectPath->setErrorLabelVisible(!projectPathIsValid);
|
||||
return projectNameIsValid && projectPathIsValid;
|
||||
}
|
||||
} // namespace O3DE::ProjectManager
|
||||
|
||||
@@ -36,10 +36,15 @@ namespace O3DE::ProjectManager
|
||||
|
||||
bool Validate();
|
||||
|
||||
void NotifyCurrentScreen() override;
|
||||
|
||||
protected slots:
|
||||
void HandleBrowseButton();
|
||||
void ValidateProjectPath();
|
||||
|
||||
private:
|
||||
QString GetDefaultProjectPath();
|
||||
|
||||
FormLineEditWidget* m_projectName;
|
||||
FormBrowseEditWidget* m_projectPath;
|
||||
QButtonGroup* m_projectTemplateButtonGroup;
|
||||
|
||||
@@ -341,6 +341,16 @@ namespace O3DE::ProjectManager
|
||||
}
|
||||
else
|
||||
{
|
||||
// refresh the projects content by re-creating it for now
|
||||
if (m_projectsContent)
|
||||
{
|
||||
m_stack->removeWidget(m_projectsContent);
|
||||
m_projectsContent->deleteLater();
|
||||
}
|
||||
|
||||
m_projectsContent = CreateProjectsContent();
|
||||
|
||||
m_stack->addWidget(m_projectsContent);
|
||||
m_stack->setCurrentWidget(m_projectsContent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -513,10 +513,15 @@ namespace O3DE::ProjectManager
|
||||
{
|
||||
ProjectInfo createdProjectInfo;
|
||||
bool result = ExecuteWithLock([&] {
|
||||
|
||||
pybind11::str projectPath = projectInfo.m_path.toStdString();
|
||||
pybind11::str projectName = projectInfo.m_projectName.toStdString();
|
||||
pybind11::str templatePath = projectTemplatePath.toStdString();
|
||||
auto createProjectResult = m_engineTemplate.attr("create_project")(projectPath, templatePath);
|
||||
|
||||
auto createProjectResult = m_engineTemplate.attr("create_project")(
|
||||
projectPath,
|
||||
projectName,
|
||||
templatePath
|
||||
);
|
||||
if (createProjectResult.cast<int>() == 0)
|
||||
{
|
||||
createdProjectInfo = ProjectInfoFromPath(projectPath);
|
||||
|
||||
@@ -136,6 +136,7 @@ namespace O3DE::ProjectManager
|
||||
{
|
||||
shouldRestoreCurrentScreen = true;
|
||||
}
|
||||
int tabIndex = GetScreenTabIndex(screen);
|
||||
|
||||
// Delete old screen if it exists to start fresh
|
||||
DeleteScreen(screen);
|
||||
@@ -144,11 +145,19 @@ namespace O3DE::ProjectManager
|
||||
ScreenWidget* newScreen = BuildScreen(this, screen);
|
||||
if (newScreen->IsTab())
|
||||
{
|
||||
m_tabWidget->addTab(newScreen, newScreen->GetTabText());
|
||||
if (tabIndex > -1)
|
||||
{
|
||||
m_tabWidget->insertTab(tabIndex, newScreen, newScreen->GetTabText());
|
||||
}
|
||||
else
|
||||
{
|
||||
m_tabWidget->addTab(newScreen, newScreen->GetTabText());
|
||||
}
|
||||
if (shouldRestoreCurrentScreen)
|
||||
{
|
||||
m_tabWidget->setCurrentWidget(newScreen);
|
||||
m_screenStack->setCurrentWidget(m_tabWidget);
|
||||
newScreen->NotifyCurrentScreen();
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -157,6 +166,7 @@ namespace O3DE::ProjectManager
|
||||
if (shouldRestoreCurrentScreen)
|
||||
{
|
||||
m_screenStack->setCurrentWidget(newScreen);
|
||||
newScreen->NotifyCurrentScreen();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,4 +229,19 @@ namespace O3DE::ProjectManager
|
||||
screen->NotifyCurrentScreen();
|
||||
}
|
||||
}
|
||||
|
||||
int ScreensCtrl::GetScreenTabIndex(ProjectManagerScreen screen)
|
||||
{
|
||||
const auto iter = m_screenMap.find(screen);
|
||||
if (iter != m_screenMap.end())
|
||||
{
|
||||
ScreenWidget* screenWidget = iter.value();
|
||||
if (screenWidget->IsTab())
|
||||
{
|
||||
return m_tabWidget->indexOf(screenWidget);
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
} // namespace O3DE::ProjectManager
|
||||
|
||||
@@ -51,6 +51,8 @@ namespace O3DE::ProjectManager
|
||||
void TabChanged(int index);
|
||||
|
||||
private:
|
||||
int GetScreenTabIndex(ProjectManagerScreen screen);
|
||||
|
||||
QStackedWidget* m_screenStack;
|
||||
QHash<ProjectManagerScreen, ScreenWidget*> m_screenMap;
|
||||
QStack<ProjectManagerScreen> m_screenVisitOrder;
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:40949893ed7009eeaa90b7ce6057cb6be9dfaf7b162e3c26ba9dadf985939d7d
|
||||
size 2038
|
||||
oid sha256:4a5881b8d6cfbc4ceefb14ab96844484fe19407ee030824768f9fcce2f729d35
|
||||
size 2949
|
||||
|
||||
@@ -1279,6 +1279,7 @@ def create_from_template(destination_path: str,
|
||||
|
||||
|
||||
def create_project(project_path: str,
|
||||
project_name: str = None,
|
||||
template_path: str = None,
|
||||
template_name: str = None,
|
||||
project_restricted_path: str = None,
|
||||
@@ -1297,6 +1298,7 @@ def create_project(project_path: str,
|
||||
Template instantiation specialization that makes all default assumptions for a Project template instantiation,
|
||||
reducing the effort needed in instancing a project
|
||||
:param project_path: the project path, can be absolute or relative to default projects path
|
||||
:param project_name: the project name, defaults to project_path basename if not provided
|
||||
:param template_path: the path to the template you want to instance, can be absolute or relative to default templates path
|
||||
:param template_name: the name the registered template you want to instance, defaults to DefaultProject, resolves template_path
|
||||
:param project_restricted_path: path to the projects restricted folder, can be absolute or relative to the restricted='projects'
|
||||
@@ -1489,12 +1491,17 @@ def create_project(project_path: str,
|
||||
elif not os.path.isdir(project_path):
|
||||
os.makedirs(project_path)
|
||||
|
||||
# project name is now the last component of the project_path
|
||||
project_name = os.path.basename(project_path)
|
||||
if not project_name:
|
||||
# project name is now the last component of the project_path
|
||||
project_name = os.path.basename(project_path)
|
||||
|
||||
if not utils.validate_identifier(project_name):
|
||||
logger.error(f'Project name must be fewer than 64 characters, contain only alphanumeric, "_" or "-" characters, and start with a letter. {project_name}')
|
||||
return 1
|
||||
|
||||
# project name cannot be the same as a restricted platform name
|
||||
if project_name in restricted_platforms:
|
||||
logger.error(f'Project path cannot be a restricted name. {project_name}')
|
||||
logger.error(f'Project name cannot be a restricted name. {project_name}')
|
||||
return 1
|
||||
|
||||
# project restricted name
|
||||
@@ -2079,6 +2086,7 @@ def _run_create_from_template(args: argparse) -> int:
|
||||
|
||||
def _run_create_project(args: argparse) -> int:
|
||||
return create_project(args.project_path,
|
||||
args.project_name,
|
||||
args.template_path,
|
||||
args.template_name,
|
||||
args.project_restricted_path,
|
||||
@@ -2262,10 +2270,15 @@ def add_args(subparsers) -> None:
|
||||
# creation of a project from a template (like create from template but makes project assumptions)
|
||||
create_project_subparser = subparsers.add_parser('create-project')
|
||||
create_project_subparser.add_argument('-pp', '--project-path', type=str, required=True,
|
||||
help='The name of the project you wish to create from the template,'
|
||||
help='The location of the project you wish to create from the template,'
|
||||
' can be an absolute path or dev root relative.'
|
||||
' Ex. C:/o3de/TestProject'
|
||||
' TestProject = <project_name>')
|
||||
' TestProject = <project_name> if --project-name not provided')
|
||||
create_project_subparser.add_argument('-pn', '--project-name', type=str, required=False,
|
||||
help='The name of the project you wish to use, must be alphanumeric, '
|
||||
' and can contain _ and - characters.'
|
||||
' If no name is provided, will use last component of project path.'
|
||||
' Ex. New_Project-123')
|
||||
|
||||
group = create_project_subparser.add_mutually_exclusive_group(required=False)
|
||||
group.add_argument('-tp', '--template-path', type=str, required=False,
|
||||
|
||||
Reference in New Issue
Block a user