Improved project creation validation

No longer requires project name to be part of the project path.
main
Alex Peterson 5 years ago committed by GitHub
parent afe20906db
commit a3e73948c5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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."));
}
else
{
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."));
}
}
QDir path(QDir::toNativeSeparators(m_projectPath->lineEdit()->text() + "/" + m_projectName->lineEdit()->text()));
if (path.exists() && !path.isEmpty())
bool projectNameIsValid = true;
if (m_projectName->lineEdit()->text().isEmpty())
{
projectPathIsValid = false;
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,

Loading…
Cancel
Save