Skip to content

Commit

Permalink
Revert "Show spinner when opening/creating a project, take #2 (#6827)" (
Browse files Browse the repository at this point in the history
  • Loading branch information
farmaazon committed Jul 14, 2023
1 parent f691713 commit 853dd2f
Show file tree
Hide file tree
Showing 18 changed files with 339 additions and 259 deletions.
70 changes: 16 additions & 54 deletions app/gui/src/controller/ide.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@

use crate::prelude::*;

use crate::config::ProjectToOpen;

use double_representation::name::project;
use mockall::automock;
use parser::Parser;
Expand Down Expand Up @@ -104,7 +102,9 @@ impl StatusNotificationPublisher {
/// used internally in code.
#[derive(Copy, Clone, Debug)]
pub enum Notification {
/// User opened a new or existing project.
/// User created a new project. The new project is opened in IDE.
NewProjectCreated,
/// User opened an existing project.
ProjectOpened,
/// User closed the project.
ProjectClosed,
Expand All @@ -118,12 +118,10 @@ pub enum Notification {

// === Errors ===

/// Error raised when a project with given name or ID was not found.
#[allow(missing_docs)]
#[derive(Clone, Debug, Fail)]
#[fail(display = "Project '{}' was not found.", project)]
pub struct ProjectNotFound {
project: ProjectToOpen,
}
#[fail(display = "Project with name \"{}\" not found.", 0)]
struct ProjectNotFound(String);


// === Managing API ===
Expand All @@ -133,16 +131,11 @@ pub struct ProjectNotFound {
/// It is a separate trait, because those methods are not supported in some environments (see also
/// [`API::manage_projects`]).
pub trait ManagingProjectAPI {
/// Create a new project and open it in the IDE.
/// Create a new unnamed project and open it in the IDE.
///
/// `name` is an optional project name. It overrides the name of the template if given.
/// `template` is an optional project template name. Available template names are defined in
/// `lib/scala/pkg/src/main/scala/org/enso/pkg/Template.scala`.
fn create_new_project(
&self,
name: Option<String>,
template: Option<project::Template>,
) -> BoxFuture<FallibleResult>;
fn create_new_project(&self, template: Option<project::Template>) -> BoxFuture<FallibleResult>;

/// Return a list of existing projects.
fn list_projects(&self) -> BoxFuture<FallibleResult<Vec<ProjectMetadata>>>;
Expand All @@ -157,44 +150,18 @@ pub trait ManagingProjectAPI {
/// and then for the project opening.
fn open_project_by_name(&self, name: String) -> BoxFuture<FallibleResult> {
async move {
let project_id = self.find_project(&ProjectToOpen::Name(name.into())).await?;
self.open_project(project_id).await
}
.boxed_local()
}

/// Open a project by name or ID. If no project with the given name exists, it will be created.
fn open_or_create_project(&self, project_to_open: ProjectToOpen) -> BoxFuture<FallibleResult> {
async move {
match self.find_project(&project_to_open).await {
Ok(project_id) => self.open_project(project_id).await,
Err(error) =>
if let ProjectToOpen::Name(name) = project_to_open {
info!("Attempting to create project with name '{name}'.");
self.create_new_project(Some(name.to_string()), None).await
} else {
Err(error)
},
let projects = self.list_projects().await?;
let mut projects = projects.into_iter();
let project = projects.find(|project| project.name.as_ref() == name);
let uuid = project.map(|project| project.id);
if let Some(uuid) = uuid {
self.open_project(uuid).await
} else {
Err(ProjectNotFound(name).into())
}
}
.boxed_local()
}

/// Find a project by name or ID.
fn find_project<'a: 'c, 'b: 'c, 'c>(
&'a self,
project_to_open: &'b ProjectToOpen,
) -> BoxFuture<'c, FallibleResult<Uuid>> {
async move {
self.list_projects()
.await?
.into_iter()
.find(|project_metadata| project_to_open.matches(project_metadata))
.map(|metadata| metadata.id)
.ok_or_else(|| ProjectNotFound { project: project_to_open.clone() }.into())
}
.boxed_local()
}
}


Expand Down Expand Up @@ -226,11 +193,6 @@ pub trait API: Debug {
#[allow(clippy::needless_lifetimes)]
fn manage_projects<'a>(&'a self) -> FallibleResult<&'a dyn ManagingProjectAPI>;

/// Returns whether the Managing Project API is available.
fn can_manage_projects(&self) -> bool {
self.manage_projects().is_ok()
}

/// Return whether private entries should be visible in the component browser.
fn are_component_browser_private_entries_visible(&self) -> bool;

Expand Down
74 changes: 58 additions & 16 deletions app/gui/src/controller/ide/desktop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@

use crate::prelude::*;

use crate::config::ProjectToOpen;
use crate::controller::ide::ManagingProjectAPI;
use crate::controller::ide::Notification;
use crate::controller::ide::StatusNotificationPublisher;
use crate::controller::ide::API;
use crate::ide::initializer;

use double_representation::name::project;
use engine_protocol::project_manager;
Expand Down Expand Up @@ -47,16 +49,53 @@ pub struct Handle {
}

impl Handle {
/// Create IDE controller.
pub fn new(project_manager: Rc<dyn project_manager::API>) -> FallibleResult<Self> {
Ok(Self {
current_project: default(),
/// Create IDE controller. If `maybe_project_name` is `Some`, a project with provided name will
/// be opened. Otherwise controller will be used for project manager operations by Welcome
/// Screen.
pub async fn new(
project_manager: Rc<dyn project_manager::API>,
project_to_open: Option<ProjectToOpen>,
) -> FallibleResult<Self> {
let project = match project_to_open {
Some(project_to_open) =>
Some(Self::init_project_model(project_manager.clone_ref(), project_to_open).await?),
None => None,
};
Ok(Self::new_with_project_model(project_manager, project))
}

/// Create IDE controller with prepared project model. If `project` is `None`,
/// `API::current_project` returns `None` as well.
pub fn new_with_project_model(
project_manager: Rc<dyn project_manager::API>,
project: Option<model::Project>,
) -> Self {
let current_project = Rc::new(CloneCell::new(project));
let status_notifications = default();
let parser = Parser::new();
let notifications = default();
let component_browser_private_entries_visibility_flag = default();
Self {
current_project,
project_manager,
status_notifications: default(),
parser: default(),
notifications: default(),
component_browser_private_entries_visibility_flag: default(),
})
status_notifications,
parser,
notifications,
component_browser_private_entries_visibility_flag,
}
}

/// Open project with provided name.
async fn init_project_model(
project_manager: Rc<dyn project_manager::API>,
project_to_open: ProjectToOpen,
) -> FallibleResult<model::Project> {
// TODO[ao]: Reuse of initializer used in previous code design. It should be soon replaced
// anyway, because we will soon resign from the "open or create" approach when opening
// IDE. See https://github.com/enso-org/ide/issues/1492 for details.
let initializer = initializer::WithProjectManager::new(project_manager, project_to_open);
let model = initializer.initialize_project_model().await?;
Ok(model)
}
}

Expand Down Expand Up @@ -94,16 +133,14 @@ impl API for Handle {

impl ManagingProjectAPI for Handle {
#[profile(Objective)]
fn create_new_project(
&self,
name: Option<String>,
template: Option<project::Template>,
) -> BoxFuture<FallibleResult> {
fn create_new_project(&self, template: Option<project::Template>) -> BoxFuture<FallibleResult> {
async move {
use model::project::Synchronized as Project;

let list = self.project_manager.list_projects(&None).await?;
let existing_names: HashSet<_> =
list.projects.into_iter().map(|p| p.name.into()).collect();
let name = name.unwrap_or_else(|| make_project_name(&template));
let name = make_project_name(&template);
let name = choose_unique_project_name(&existing_names, &name);
let name = ProjectName::new_unchecked(name);
let version = &enso_config::ARGS.groups.engine.options.preferred_version.value;
Expand All @@ -114,7 +151,12 @@ impl ManagingProjectAPI for Handle {
.project_manager
.create_project(&name, &template.map(|t| t.into()), &version, &action)
.await?;
self.open_project(create_result.project_id).await
let new_project_id = create_result.project_id;
let project_mgr = self.project_manager.clone_ref();
let new_project = Project::new_opened(project_mgr, new_project_id);
self.current_project.set(Some(new_project.await?));
self.notifications.notify(Notification::NewProjectCreated);
Ok(())
}
.boxed_local()
}
Expand Down
35 changes: 34 additions & 1 deletion app/gui/src/controller/searcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ use crate::controller::searcher::component::group;
use crate::model::module::NodeEditStatus;
use crate::model::module::NodeMetadata;
use crate::model::suggestion_database;

use crate::presenter::searcher;

use breadcrumbs::Breadcrumbs;
use double_representation::graph::GraphInfo;
use double_representation::graph::LocationHint;
Expand Down Expand Up @@ -712,6 +712,33 @@ impl Searcher {
Mode::NewNode { .. } => self.add_example(&example).map(Some),
_ => Err(CannotExecuteWhenEditingNode.into()),
},
Action::ProjectManagement(action) => {
match self.ide.manage_projects() {
Ok(_) => {
let ide = self.ide.clone_ref();
executor::global::spawn(async move {
// We checked that manage_projects returns Some just a moment ago, so
// unwrapping is safe.
let manage_projects = ide.manage_projects().unwrap();
let result = match action {
action::ProjectManagement::CreateNewProject =>
manage_projects.create_new_project(None),
action::ProjectManagement::OpenProject { id, .. } =>
manage_projects.open_project(*id),
};
if let Err(err) = result.await {
error!("Error when creating new project: {err}");
}
});
Ok(None)
}
Err(err) => Err(NotSupported {
action_label: Action::ProjectManagement(action).to_string(),
reason: err,
}
.into()),
}
}
}
}

Expand Down Expand Up @@ -949,6 +976,12 @@ impl Searcher {
let mut actions = action::ListWithSearchResultBuilder::new();
let (libraries_icon, default_icon) =
action::hardcoded::ICONS.with(|i| (i.libraries.clone_ref(), i.default.clone_ref()));
if should_add_additional_entries && self.ide.manage_projects().is_ok() {
let mut root_cat = actions.add_root_category("Projects", default_icon.clone_ref());
let category = root_cat.add_category("Projects", default_icon.clone_ref());
let create_project = action::ProjectManagement::CreateNewProject;
category.add_action(Action::ProjectManagement(create_project));
}
let mut libraries_root_cat =
actions.add_root_category("Libraries", libraries_icon.clone_ref());
if should_add_additional_entries {
Expand Down
14 changes: 14 additions & 0 deletions app/gui/src/controller/searcher/action.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,14 @@ impl Suggestion {
/// Action of adding example code.
pub type Example = Rc<model::suggestion_database::Example>;

/// A variants of project management actions. See also [`Action`].
#[allow(missing_docs)]
#[derive(Clone, CloneRef, Debug, Eq, PartialEq)]
pub enum ProjectManagement {
CreateNewProject,
OpenProject { id: Immutable<Uuid>, name: ImString },
}

/// A single action on the Searcher list. See also `controller::searcher::Searcher` docs.
#[derive(Clone, CloneRef, Debug, PartialEq)]
pub enum Action {
Expand All @@ -77,6 +85,8 @@ pub enum Action {
/// Add to the current module a new function with example code, and a new node in
/// current scene calling that function.
Example(Example),
/// The project management operation: creating or opening, projects.
ProjectManagement(ProjectManagement),
// In the future, other action types will be added (like module/method management, etc.).
}

Expand All @@ -92,6 +102,10 @@ impl Display for Action {
Self::Suggestion(Suggestion::Hardcoded(suggestion)) =>
Display::fmt(&suggestion.name, f),
Self::Example(example) => write!(f, "Example: {}", example.name),
Self::ProjectManagement(ProjectManagement::CreateNewProject) =>
write!(f, "New Project"),
Self::ProjectManagement(ProjectManagement::OpenProject { name, .. }) =>
Display::fmt(name, f),
}
}
}
Expand Down
7 changes: 1 addition & 6 deletions app/gui/src/ide.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

use crate::prelude::*;

use crate::config::ProjectToOpen;
use crate::presenter::Presenter;

use analytics::AnonymousData;
Expand Down Expand Up @@ -94,11 +93,6 @@ impl Ide {
}
}
}

/// Open a project by name or ID. If no project with the given name exists, it will be created.
pub fn open_or_create_project(&self, project: ProjectToOpen) {
self.presenter.open_or_create_project(project)
}
}

/// A reduced version of [`Ide`] structure, representing an application which failed to initialize.
Expand All @@ -110,6 +104,7 @@ pub struct FailedIde {
pub view: ide_view::root::View,
}


/// The Path of the module initially opened after opening project in IDE.
pub fn initial_module_path(project: &model::Project) -> model::module::Path {
project.main_module_path()
Expand Down
Loading

0 comments on commit 853dd2f

Please sign in to comment.