mirror of
https://github.com/enso-org/enso.git
synced 2024-11-26 17:06:48 +03:00
This commit is contained in:
parent
f691713077
commit
853dd2f455
@ -5,8 +5,6 @@
|
|||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
use crate::config::ProjectToOpen;
|
|
||||||
|
|
||||||
use double_representation::name::project;
|
use double_representation::name::project;
|
||||||
use mockall::automock;
|
use mockall::automock;
|
||||||
use parser::Parser;
|
use parser::Parser;
|
||||||
@ -104,7 +102,9 @@ impl StatusNotificationPublisher {
|
|||||||
/// used internally in code.
|
/// used internally in code.
|
||||||
#[derive(Copy, Clone, Debug)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
pub enum Notification {
|
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,
|
ProjectOpened,
|
||||||
/// User closed the project.
|
/// User closed the project.
|
||||||
ProjectClosed,
|
ProjectClosed,
|
||||||
@ -118,12 +118,10 @@ pub enum Notification {
|
|||||||
|
|
||||||
// === Errors ===
|
// === Errors ===
|
||||||
|
|
||||||
/// Error raised when a project with given name or ID was not found.
|
#[allow(missing_docs)]
|
||||||
#[derive(Clone, Debug, Fail)]
|
#[derive(Clone, Debug, Fail)]
|
||||||
#[fail(display = "Project '{}' was not found.", project)]
|
#[fail(display = "Project with name \"{}\" not found.", 0)]
|
||||||
pub struct ProjectNotFound {
|
struct ProjectNotFound(String);
|
||||||
project: ProjectToOpen,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// === Managing API ===
|
// === Managing API ===
|
||||||
@ -133,16 +131,11 @@ pub struct ProjectNotFound {
|
|||||||
/// It is a separate trait, because those methods are not supported in some environments (see also
|
/// It is a separate trait, because those methods are not supported in some environments (see also
|
||||||
/// [`API::manage_projects`]).
|
/// [`API::manage_projects`]).
|
||||||
pub trait ManagingProjectAPI {
|
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
|
/// `template` is an optional project template name. Available template names are defined in
|
||||||
/// `lib/scala/pkg/src/main/scala/org/enso/pkg/Template.scala`.
|
/// `lib/scala/pkg/src/main/scala/org/enso/pkg/Template.scala`.
|
||||||
fn create_new_project(
|
fn create_new_project(&self, template: Option<project::Template>) -> BoxFuture<FallibleResult>;
|
||||||
&self,
|
|
||||||
name: Option<String>,
|
|
||||||
template: Option<project::Template>,
|
|
||||||
) -> BoxFuture<FallibleResult>;
|
|
||||||
|
|
||||||
/// Return a list of existing projects.
|
/// Return a list of existing projects.
|
||||||
fn list_projects(&self) -> BoxFuture<FallibleResult<Vec<ProjectMetadata>>>;
|
fn list_projects(&self) -> BoxFuture<FallibleResult<Vec<ProjectMetadata>>>;
|
||||||
@ -157,44 +150,18 @@ pub trait ManagingProjectAPI {
|
|||||||
/// and then for the project opening.
|
/// and then for the project opening.
|
||||||
fn open_project_by_name(&self, name: String) -> BoxFuture<FallibleResult> {
|
fn open_project_by_name(&self, name: String) -> BoxFuture<FallibleResult> {
|
||||||
async move {
|
async move {
|
||||||
let project_id = self.find_project(&ProjectToOpen::Name(name.into())).await?;
|
let projects = self.list_projects().await?;
|
||||||
self.open_project(project_id).await
|
let mut projects = projects.into_iter();
|
||||||
}
|
let project = projects.find(|project| project.name.as_ref() == name);
|
||||||
.boxed_local()
|
let uuid = project.map(|project| project.id);
|
||||||
}
|
if let Some(uuid) = uuid {
|
||||||
|
self.open_project(uuid).await
|
||||||
/// 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 {
|
} else {
|
||||||
Err(error)
|
Err(ProjectNotFound(name).into())
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.boxed_local()
|
.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()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -226,11 +193,6 @@ pub trait API: Debug {
|
|||||||
#[allow(clippy::needless_lifetimes)]
|
#[allow(clippy::needless_lifetimes)]
|
||||||
fn manage_projects<'a>(&'a self) -> FallibleResult<&'a dyn ManagingProjectAPI>;
|
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.
|
/// Return whether private entries should be visible in the component browser.
|
||||||
fn are_component_browser_private_entries_visible(&self) -> bool;
|
fn are_component_browser_private_entries_visible(&self) -> bool;
|
||||||
|
|
||||||
|
@ -4,10 +4,12 @@
|
|||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
use crate::config::ProjectToOpen;
|
||||||
use crate::controller::ide::ManagingProjectAPI;
|
use crate::controller::ide::ManagingProjectAPI;
|
||||||
use crate::controller::ide::Notification;
|
use crate::controller::ide::Notification;
|
||||||
use crate::controller::ide::StatusNotificationPublisher;
|
use crate::controller::ide::StatusNotificationPublisher;
|
||||||
use crate::controller::ide::API;
|
use crate::controller::ide::API;
|
||||||
|
use crate::ide::initializer;
|
||||||
|
|
||||||
use double_representation::name::project;
|
use double_representation::name::project;
|
||||||
use engine_protocol::project_manager;
|
use engine_protocol::project_manager;
|
||||||
@ -47,16 +49,53 @@ pub struct Handle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Handle {
|
impl Handle {
|
||||||
/// Create IDE controller.
|
/// Create IDE controller. If `maybe_project_name` is `Some`, a project with provided name will
|
||||||
pub fn new(project_manager: Rc<dyn project_manager::API>) -> FallibleResult<Self> {
|
/// be opened. Otherwise controller will be used for project manager operations by Welcome
|
||||||
Ok(Self {
|
/// Screen.
|
||||||
current_project: default(),
|
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,
|
project_manager,
|
||||||
status_notifications: default(),
|
status_notifications,
|
||||||
parser: default(),
|
parser,
|
||||||
notifications: default(),
|
notifications,
|
||||||
component_browser_private_entries_visibility_flag: default(),
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,16 +133,14 @@ impl API for Handle {
|
|||||||
|
|
||||||
impl ManagingProjectAPI for Handle {
|
impl ManagingProjectAPI for Handle {
|
||||||
#[profile(Objective)]
|
#[profile(Objective)]
|
||||||
fn create_new_project(
|
fn create_new_project(&self, template: Option<project::Template>) -> BoxFuture<FallibleResult> {
|
||||||
&self,
|
|
||||||
name: Option<String>,
|
|
||||||
template: Option<project::Template>,
|
|
||||||
) -> BoxFuture<FallibleResult> {
|
|
||||||
async move {
|
async move {
|
||||||
|
use model::project::Synchronized as Project;
|
||||||
|
|
||||||
let list = self.project_manager.list_projects(&None).await?;
|
let list = self.project_manager.list_projects(&None).await?;
|
||||||
let existing_names: HashSet<_> =
|
let existing_names: HashSet<_> =
|
||||||
list.projects.into_iter().map(|p| p.name.into()).collect();
|
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 = choose_unique_project_name(&existing_names, &name);
|
||||||
let name = ProjectName::new_unchecked(name);
|
let name = ProjectName::new_unchecked(name);
|
||||||
let version = &enso_config::ARGS.groups.engine.options.preferred_version.value;
|
let version = &enso_config::ARGS.groups.engine.options.preferred_version.value;
|
||||||
@ -114,7 +151,12 @@ impl ManagingProjectAPI for Handle {
|
|||||||
.project_manager
|
.project_manager
|
||||||
.create_project(&name, &template.map(|t| t.into()), &version, &action)
|
.create_project(&name, &template.map(|t| t.into()), &version, &action)
|
||||||
.await?;
|
.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()
|
.boxed_local()
|
||||||
}
|
}
|
||||||
|
@ -10,8 +10,8 @@ use crate::controller::searcher::component::group;
|
|||||||
use crate::model::module::NodeEditStatus;
|
use crate::model::module::NodeEditStatus;
|
||||||
use crate::model::module::NodeMetadata;
|
use crate::model::module::NodeMetadata;
|
||||||
use crate::model::suggestion_database;
|
use crate::model::suggestion_database;
|
||||||
|
|
||||||
use crate::presenter::searcher;
|
use crate::presenter::searcher;
|
||||||
|
|
||||||
use breadcrumbs::Breadcrumbs;
|
use breadcrumbs::Breadcrumbs;
|
||||||
use double_representation::graph::GraphInfo;
|
use double_representation::graph::GraphInfo;
|
||||||
use double_representation::graph::LocationHint;
|
use double_representation::graph::LocationHint;
|
||||||
@ -712,6 +712,33 @@ impl Searcher {
|
|||||||
Mode::NewNode { .. } => self.add_example(&example).map(Some),
|
Mode::NewNode { .. } => self.add_example(&example).map(Some),
|
||||||
_ => Err(CannotExecuteWhenEditingNode.into()),
|
_ => 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()),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -949,6 +976,12 @@ impl Searcher {
|
|||||||
let mut actions = action::ListWithSearchResultBuilder::new();
|
let mut actions = action::ListWithSearchResultBuilder::new();
|
||||||
let (libraries_icon, default_icon) =
|
let (libraries_icon, default_icon) =
|
||||||
action::hardcoded::ICONS.with(|i| (i.libraries.clone_ref(), i.default.clone_ref()));
|
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 =
|
let mut libraries_root_cat =
|
||||||
actions.add_root_category("Libraries", libraries_icon.clone_ref());
|
actions.add_root_category("Libraries", libraries_icon.clone_ref());
|
||||||
if should_add_additional_entries {
|
if should_add_additional_entries {
|
||||||
|
@ -67,6 +67,14 @@ impl Suggestion {
|
|||||||
/// Action of adding example code.
|
/// Action of adding example code.
|
||||||
pub type Example = Rc<model::suggestion_database::Example>;
|
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.
|
/// A single action on the Searcher list. See also `controller::searcher::Searcher` docs.
|
||||||
#[derive(Clone, CloneRef, Debug, PartialEq)]
|
#[derive(Clone, CloneRef, Debug, PartialEq)]
|
||||||
pub enum Action {
|
pub enum Action {
|
||||||
@ -77,6 +85,8 @@ pub enum Action {
|
|||||||
/// Add to the current module a new function with example code, and a new node in
|
/// Add to the current module a new function with example code, and a new node in
|
||||||
/// current scene calling that function.
|
/// current scene calling that function.
|
||||||
Example(Example),
|
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.).
|
// In the future, other action types will be added (like module/method management, etc.).
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,6 +102,10 @@ impl Display for Action {
|
|||||||
Self::Suggestion(Suggestion::Hardcoded(suggestion)) =>
|
Self::Suggestion(Suggestion::Hardcoded(suggestion)) =>
|
||||||
Display::fmt(&suggestion.name, f),
|
Display::fmt(&suggestion.name, f),
|
||||||
Self::Example(example) => write!(f, "Example: {}", example.name),
|
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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
use crate::config::ProjectToOpen;
|
|
||||||
use crate::presenter::Presenter;
|
use crate::presenter::Presenter;
|
||||||
|
|
||||||
use analytics::AnonymousData;
|
use analytics::AnonymousData;
|
||||||
@ -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.
|
/// A reduced version of [`Ide`] structure, representing an application which failed to initialize.
|
||||||
@ -110,6 +104,7 @@ pub struct FailedIde {
|
|||||||
pub view: ide_view::root::View,
|
pub view: ide_view::root::View,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// The Path of the module initially opened after opening project in IDE.
|
/// The Path of the module initially opened after opening project in IDE.
|
||||||
pub fn initial_module_path(project: &model::Project) -> model::module::Path {
|
pub fn initial_module_path(project: &model::Project) -> model::module::Path {
|
||||||
project.main_module_path()
|
project.main_module_path()
|
||||||
|
@ -3,14 +3,17 @@
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
use crate::config;
|
use crate::config;
|
||||||
|
use crate::config::ProjectToOpen;
|
||||||
use crate::ide::Ide;
|
use crate::ide::Ide;
|
||||||
use crate::retry::retry_operation;
|
use crate::retry::retry_operation;
|
||||||
use crate::transport::web::WebSocket;
|
use crate::transport::web::WebSocket;
|
||||||
use crate::FailedIde;
|
use crate::FailedIde;
|
||||||
|
|
||||||
use engine_protocol::project_manager;
|
use engine_protocol::project_manager;
|
||||||
|
use engine_protocol::project_manager::ProjectName;
|
||||||
use ensogl::application::Application;
|
use ensogl::application::Application;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -32,6 +35,19 @@ const INITIALIZATION_RETRY_TIMES: &[Duration] =
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// ==============
|
||||||
|
// === Errors ===
|
||||||
|
// ==============
|
||||||
|
|
||||||
|
/// Error raised when project with given name was not found.
|
||||||
|
#[derive(Clone, Debug, Fail)]
|
||||||
|
#[fail(display = "Project '{}' was not found.", name)]
|
||||||
|
pub struct ProjectNotFound {
|
||||||
|
name: ProjectToOpen,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// ===================
|
// ===================
|
||||||
// === Initializer ===
|
// === Initializer ===
|
||||||
// ===================
|
// ===================
|
||||||
@ -80,13 +96,7 @@ impl Initializer {
|
|||||||
|
|
||||||
match self.initialize_ide_controller_with_retries().await {
|
match self.initialize_ide_controller_with_retries().await {
|
||||||
Ok(controller) => {
|
Ok(controller) => {
|
||||||
let can_manage_projects = controller.can_manage_projects();
|
|
||||||
let ide = Ide::new(ensogl_app, view, controller);
|
let ide = Ide::new(ensogl_app, view, controller);
|
||||||
if can_manage_projects {
|
|
||||||
if let Some(project) = &self.config.project_to_open {
|
|
||||||
ide.open_or_create_project(project.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
info!("IDE was successfully initialized.");
|
info!("IDE was successfully initialized.");
|
||||||
Ok(ide)
|
Ok(ide)
|
||||||
}
|
}
|
||||||
@ -116,8 +126,9 @@ impl Initializer {
|
|||||||
match &self.config.backend {
|
match &self.config.backend {
|
||||||
ProjectManager { endpoint } => {
|
ProjectManager { endpoint } => {
|
||||||
let project_manager = self.setup_project_manager(endpoint).await?;
|
let project_manager = self.setup_project_manager(endpoint).await?;
|
||||||
let controller = controller::ide::Desktop::new(project_manager)?;
|
let project_to_open = self.config.project_to_open.clone();
|
||||||
Ok(Rc::new(controller))
|
let controller = controller::ide::Desktop::new(project_manager, project_to_open);
|
||||||
|
Ok(Rc::new(controller.await?))
|
||||||
}
|
}
|
||||||
LanguageServer { json_endpoint, binary_endpoint, namespace, project_name } => {
|
LanguageServer { json_endpoint, binary_endpoint, namespace, project_name } => {
|
||||||
let json_endpoint = json_endpoint.clone();
|
let json_endpoint = json_endpoint.clone();
|
||||||
@ -157,6 +168,81 @@ impl Initializer {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// ==========================
|
||||||
|
// === WithProjectManager ===
|
||||||
|
// ==========================
|
||||||
|
|
||||||
|
/// Ide Initializer with project manager.
|
||||||
|
///
|
||||||
|
/// This structure do the specific initialization part when we are connected to Project Manager,
|
||||||
|
/// like list projects, find the one we want to open, open it, or create new one if it does not
|
||||||
|
/// exist.
|
||||||
|
#[allow(missing_docs)]
|
||||||
|
#[derive(Clone, Derivative)]
|
||||||
|
#[derivative(Debug)]
|
||||||
|
pub struct WithProjectManager {
|
||||||
|
#[derivative(Debug = "ignore")]
|
||||||
|
pub project_manager: Rc<dyn project_manager::API>,
|
||||||
|
pub project_to_open: ProjectToOpen,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WithProjectManager {
|
||||||
|
/// Constructor.
|
||||||
|
pub fn new(
|
||||||
|
project_manager: Rc<dyn project_manager::API>,
|
||||||
|
project_to_open: ProjectToOpen,
|
||||||
|
) -> Self {
|
||||||
|
Self { project_manager, project_to_open }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create and initialize a new Project Model, for a project with name passed in constructor.
|
||||||
|
///
|
||||||
|
/// If the project with given name does not exist yet, it will be created.
|
||||||
|
pub async fn initialize_project_model(self) -> FallibleResult<model::Project> {
|
||||||
|
let project_id = self.get_project_or_create_new().await?;
|
||||||
|
let project_manager = self.project_manager;
|
||||||
|
model::project::Synchronized::new_opened(project_manager, project_id).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new project and returns its id, so the newly connected project can be opened.
|
||||||
|
pub async fn create_project(&self, project_name: &ProjectName) -> FallibleResult<Uuid> {
|
||||||
|
use project_manager::MissingComponentAction::Install;
|
||||||
|
info!("Creating a new project named '{}'.", project_name);
|
||||||
|
let version = &enso_config::ARGS.groups.engine.options.preferred_version.value;
|
||||||
|
let version = (!version.is_empty()).as_some_from(|| version.clone());
|
||||||
|
let response = self.project_manager.create_project(project_name, &None, &version, &Install);
|
||||||
|
Ok(response.await?.project_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn lookup_project(&self) -> FallibleResult<Uuid> {
|
||||||
|
let response = self.project_manager.list_projects(&None).await?;
|
||||||
|
let mut projects = response.projects.iter();
|
||||||
|
projects
|
||||||
|
.find(|project_metadata| self.project_to_open.matches(project_metadata))
|
||||||
|
.map(|md| md.id)
|
||||||
|
.ok_or_else(|| ProjectNotFound { name: self.project_to_open.clone() }.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Look for the project with the name specified when constructing this initializer,
|
||||||
|
/// or, if it does not exist, create it. The id of found/created project is returned.
|
||||||
|
pub async fn get_project_or_create_new(&self) -> FallibleResult<Uuid> {
|
||||||
|
let project = self.lookup_project().await;
|
||||||
|
if let Ok(project_id) = project {
|
||||||
|
Ok(project_id)
|
||||||
|
} else if let ProjectToOpen::Name(name) = &self.project_to_open {
|
||||||
|
info!("Attempting to create {}", name);
|
||||||
|
self.create_project(name).await
|
||||||
|
} else {
|
||||||
|
// This can happen only if we are told to open project by id but it cannot be found.
|
||||||
|
// We cannot fallback to creating a new project in this case, as we cannot create a
|
||||||
|
// project with a given id. Thus, we simply propagate the lookup result.
|
||||||
|
project
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// =============
|
// =============
|
||||||
// === Utils ===
|
// === Utils ===
|
||||||
// =============
|
// =============
|
||||||
@ -201,10 +287,6 @@ pub fn register_views(app: &Application) {
|
|||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
use crate::config::ProjectToOpen;
|
|
||||||
use crate::controller::ide::ManagingProjectAPI;
|
|
||||||
use crate::engine_protocol::project_manager::ProjectName;
|
|
||||||
|
|
||||||
use json_rpc::expect_call;
|
use json_rpc::expect_call;
|
||||||
use wasm_bindgen_test::wasm_bindgen_test;
|
use wasm_bindgen_test::wasm_bindgen_test;
|
||||||
|
|
||||||
@ -228,10 +310,9 @@ mod test {
|
|||||||
expect_call!(mock_client.list_projects(count) => Ok(project_lists));
|
expect_call!(mock_client.list_projects(count) => Ok(project_lists));
|
||||||
|
|
||||||
let project_manager = Rc::new(mock_client);
|
let project_manager = Rc::new(mock_client);
|
||||||
let ide_controller = controller::ide::Desktop::new(project_manager).unwrap();
|
|
||||||
let project_to_open = ProjectToOpen::Name(project_name);
|
let project_to_open = ProjectToOpen::Name(project_name);
|
||||||
let project_id =
|
let initializer = WithProjectManager { project_manager, project_to_open };
|
||||||
ide_controller.find_project(&project_to_open).await.expect("Couldn't get project.");
|
let project = initializer.get_project_or_create_new().await;
|
||||||
assert_eq!(project_id, expected_id);
|
assert_eq!(expected_id, project.expect("Couldn't get project."))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -83,10 +83,7 @@ impl Fixture {
|
|||||||
let project_management =
|
let project_management =
|
||||||
controller.manage_projects().expect("Cannot access Managing Project API");
|
controller.manage_projects().expect("Cannot access Managing Project API");
|
||||||
|
|
||||||
project_management
|
project_management.create_new_project(None).await.expect("Failed to create new project");
|
||||||
.create_new_project(None, None)
|
|
||||||
.await
|
|
||||||
.expect("Failed to create new project");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// After returning, the IDE is in a state with the project opened and ready to work
|
/// After returning, the IDE is in a state with the project opened and ready to work
|
||||||
|
@ -4,16 +4,13 @@
|
|||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
use crate::config::ProjectToOpen;
|
|
||||||
use crate::controller::ide::StatusNotification;
|
use crate::controller::ide::StatusNotification;
|
||||||
use crate::executor::global::spawn_stream_handler;
|
use crate::executor::global::spawn_stream_handler;
|
||||||
use crate::presenter;
|
use crate::presenter;
|
||||||
|
|
||||||
use enso_frp as frp;
|
use enso_frp as frp;
|
||||||
use ensogl::system::js;
|
|
||||||
use ide_view as view;
|
use ide_view as view;
|
||||||
use ide_view::graph_editor::SharedHashMap;
|
use ide_view::graph_editor::SharedHashMap;
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
|
|
||||||
// ==============
|
// ==============
|
||||||
@ -32,19 +29,6 @@ pub use searcher::component_browser::ComponentBrowserSearcher;
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
// =================
|
|
||||||
// === Constants ===
|
|
||||||
// =================
|
|
||||||
|
|
||||||
/// We don't know how long opening the project will take, but we still want to show a fake
|
|
||||||
/// progress indicator for the user. This constant represents how long the spinner will run for in
|
|
||||||
/// milliseconds.
|
|
||||||
const OPEN_PROJECT_SPINNER_TIME_MS: u64 = 5_000;
|
|
||||||
/// The interval in milliseconds at which we should increase the spinner
|
|
||||||
const OPEN_PROJECT_SPINNER_UPDATE_PERIOD_MS: u64 = 10;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// =============
|
// =============
|
||||||
// === Model ===
|
// === Model ===
|
||||||
// =============
|
// =============
|
||||||
@ -110,15 +94,15 @@ impl Model {
|
|||||||
#[profile(Task)]
|
#[profile(Task)]
|
||||||
pub fn open_project(&self, project_name: String) {
|
pub fn open_project(&self, project_name: String) {
|
||||||
let controller = self.controller.clone_ref();
|
let controller = self.controller.clone_ref();
|
||||||
crate::executor::global::spawn(with_progress_indicator(|| async move {
|
crate::executor::global::spawn(async move {
|
||||||
if let Ok(managing_api) = controller.manage_projects() {
|
if let Ok(managing_api) = controller.manage_projects() {
|
||||||
if let Err(err) = managing_api.open_project_by_name(project_name).await {
|
if let Err(err) = managing_api.open_project_by_name(project_name).await {
|
||||||
error!("Cannot open project by name: {err}.");
|
error!("Cannot open project by name: {err}.");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
warn!("Project Manager API not available, cannot open project.");
|
warn!("Project opening failed: no ProjectManagingAPI available.");
|
||||||
}
|
}
|
||||||
}));
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new project. `template` is an optional name of the project template passed to the
|
/// Create a new project. `template` is an optional name of the project template passed to the
|
||||||
@ -129,10 +113,9 @@ impl Model {
|
|||||||
if let Ok(template) =
|
if let Ok(template) =
|
||||||
template.map(double_representation::name::project::Template::from_text).transpose()
|
template.map(double_representation::name::project::Template::from_text).transpose()
|
||||||
{
|
{
|
||||||
crate::executor::global::spawn(with_progress_indicator(|| async move {
|
crate::executor::global::spawn(async move {
|
||||||
if let Ok(managing_api) = controller.manage_projects() {
|
if let Ok(managing_api) = controller.manage_projects() {
|
||||||
if let Err(err) = managing_api.create_new_project(None, template.clone()).await
|
if let Err(err) = managing_api.create_new_project(template.clone()).await {
|
||||||
{
|
|
||||||
if let Some(template) = template {
|
if let Some(template) = template {
|
||||||
error!("Could not create new project from template {template}: {err}.");
|
error!("Could not create new project from template {template}: {err}.");
|
||||||
} else {
|
} else {
|
||||||
@ -140,66 +123,13 @@ impl Model {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
warn!("Project Manager API not available, cannot create project.");
|
warn!("Project creation failed: no ProjectManagingAPI available.");
|
||||||
}
|
}
|
||||||
}))
|
})
|
||||||
} else if let Some(template) = template {
|
} else if let Some(template) = template {
|
||||||
error!("Invalid project template name: {template}");
|
error!("Invalid project template name: {template}");
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Open a project by name or ID. If no project with the given name exists, it will be created.
|
|
||||||
#[profile(Task)]
|
|
||||||
fn open_or_create_project(&self, project: ProjectToOpen) {
|
|
||||||
let controller = self.controller.clone_ref();
|
|
||||||
crate::executor::global::spawn(with_progress_indicator(|| async move {
|
|
||||||
if let Ok(managing_api) = controller.manage_projects() {
|
|
||||||
if let Err(error) = managing_api.open_or_create_project(project).await {
|
|
||||||
error!("Cannot open or create project. {error}");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
warn!("Project Manager API not available, cannot open or create project.");
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Show a full-screen spinner for the exact duration of the specified function.
|
|
||||||
async fn with_progress_indicator<F, T>(f: F)
|
|
||||||
where
|
|
||||||
F: FnOnce() -> T,
|
|
||||||
T: Future<Output = ()>, {
|
|
||||||
// TODO[ss]: Use a safer variant of getting the JS app. This one gets a variable from JS, casts
|
|
||||||
// it to a type, etc. Somewhere in EnsoGL we might already have some logic for getting the JS
|
|
||||||
// app and throwing an error if it's not defined.
|
|
||||||
let Ok(app) = js::app() else { return error!("Failed to get JavaScript EnsoGL app.") };
|
|
||||||
app.show_progress_indicator(0.0);
|
|
||||||
|
|
||||||
let (finished_tx, finished_rx) = futures::channel::oneshot::channel();
|
|
||||||
let spinner_progress = futures::stream::unfold(0, |time| async move {
|
|
||||||
enso_web::sleep(Duration::from_millis(OPEN_PROJECT_SPINNER_UPDATE_PERIOD_MS)).await;
|
|
||||||
let new_time = time + OPEN_PROJECT_SPINNER_UPDATE_PERIOD_MS;
|
|
||||||
if new_time < OPEN_PROJECT_SPINNER_TIME_MS {
|
|
||||||
let progress = new_time as f32 / OPEN_PROJECT_SPINNER_TIME_MS as f32;
|
|
||||||
Some((progress, new_time))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.take_until(finished_rx);
|
|
||||||
executor::global::spawn(spinner_progress.for_each(|progress| async move {
|
|
||||||
let Ok(app) = js::app() else { return error!("Failed to get JavaScript EnsoGL app.") };
|
|
||||||
app.show_progress_indicator(progress);
|
|
||||||
}));
|
|
||||||
|
|
||||||
f().await;
|
|
||||||
|
|
||||||
// This fails when the spinner progressed until the end before the function got completed
|
|
||||||
// and therefore the receiver got dropped, so we'll ignore the result.
|
|
||||||
let _ = finished_tx.send(());
|
|
||||||
|
|
||||||
let Ok(app) = js::app() else { return error!("Failed to get JavaScript EnsoGL app.") };
|
|
||||||
app.hide_progress_indicator();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -235,13 +165,6 @@ impl Presenter {
|
|||||||
let root_frp = &model.view.frp;
|
let root_frp = &model.view.frp;
|
||||||
root_frp.switch_view_to_project <+ welcome_view_frp.create_project.constant(());
|
root_frp.switch_view_to_project <+ welcome_view_frp.create_project.constant(());
|
||||||
root_frp.switch_view_to_project <+ welcome_view_frp.open_project.constant(());
|
root_frp.switch_view_to_project <+ welcome_view_frp.open_project.constant(());
|
||||||
|
|
||||||
eval root_frp.selected_project ([model] (project) {
|
|
||||||
if let Some(project) = project {
|
|
||||||
model.close_project();
|
|
||||||
model.open_project(project.name.to_string());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Self { model, network }.init()
|
Self { model, network }.init()
|
||||||
@ -251,8 +174,8 @@ impl Presenter {
|
|||||||
fn init(self) -> Self {
|
fn init(self) -> Self {
|
||||||
self.setup_status_bar_notification_handler();
|
self.setup_status_bar_notification_handler();
|
||||||
self.setup_controller_notification_handler();
|
self.setup_controller_notification_handler();
|
||||||
executor::global::spawn(self.clone_ref().set_projects_list_on_welcome_screen());
|
|
||||||
self.model.clone_ref().setup_and_display_new_project();
|
self.model.clone_ref().setup_and_display_new_project();
|
||||||
|
executor::global::spawn(self.clone_ref().set_projects_list_on_welcome_screen());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -291,7 +214,8 @@ impl Presenter {
|
|||||||
let weak = Rc::downgrade(&self.model);
|
let weak = Rc::downgrade(&self.model);
|
||||||
spawn_stream_handler(weak, stream, move |notification, model| {
|
spawn_stream_handler(weak, stream, move |notification, model| {
|
||||||
match notification {
|
match notification {
|
||||||
controller::ide::Notification::ProjectOpened =>
|
controller::ide::Notification::NewProjectCreated
|
||||||
|
| controller::ide::Notification::ProjectOpened =>
|
||||||
model.setup_and_display_new_project(),
|
model.setup_and_display_new_project(),
|
||||||
controller::ide::Notification::ProjectClosed => {
|
controller::ide::Notification::ProjectClosed => {
|
||||||
model.close_project();
|
model.close_project();
|
||||||
@ -315,11 +239,6 @@ impl Presenter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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.model.open_or_create_project(project)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -11,8 +11,8 @@ use crate::presenter::searcher::SearcherPresenter;
|
|||||||
use crate::presenter::ComponentBrowserSearcher;
|
use crate::presenter::ComponentBrowserSearcher;
|
||||||
|
|
||||||
use engine_protocol::language_server::ExecutionEnvironment;
|
use engine_protocol::language_server::ExecutionEnvironment;
|
||||||
use engine_protocol::project_manager::ProjectMetadata;
|
|
||||||
use enso_frp as frp;
|
use enso_frp as frp;
|
||||||
|
use ensogl::system::js;
|
||||||
use ide_view as view;
|
use ide_view as view;
|
||||||
use ide_view::project::SearcherParams;
|
use ide_view::project::SearcherParams;
|
||||||
use ide_view::project::SearcherType;
|
use ide_view::project::SearcherType;
|
||||||
@ -22,6 +22,16 @@ use model::project::VcsStatus;
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// =================
|
||||||
|
// === Constants ===
|
||||||
|
// =================
|
||||||
|
|
||||||
|
/// We don't know how long the project opening will take, but we still want to show a fake progress
|
||||||
|
/// indicator for the user. This constant represents a progress percentage that will be displayed.
|
||||||
|
const OPEN_PROJECT_SPINNER_PROGRESS: f32 = 0.8;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// =============
|
// =============
|
||||||
// === Model ===
|
// === Model ===
|
||||||
// =============
|
// =============
|
||||||
@ -39,7 +49,7 @@ struct Model {
|
|||||||
graph: presenter::Graph,
|
graph: presenter::Graph,
|
||||||
code: presenter::Code,
|
code: presenter::Code,
|
||||||
searcher: RefCell<Option<Box<dyn SearcherPresenter>>>,
|
searcher: RefCell<Option<Box<dyn SearcherPresenter>>>,
|
||||||
available_projects: Rc<RefCell<Vec<ProjectMetadata>>>,
|
available_projects: Rc<RefCell<Vec<(ImString, Uuid)>>>,
|
||||||
shortcut_transaction: RefCell<Option<Rc<model::undo_redo::Transaction>>>,
|
shortcut_transaction: RefCell<Option<Rc<model::undo_redo::Transaction>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -275,6 +285,8 @@ impl Model {
|
|||||||
executor::global::spawn(async move {
|
executor::global::spawn(async move {
|
||||||
if let Ok(api) = controller.manage_projects() {
|
if let Ok(api) = controller.manage_projects() {
|
||||||
if let Ok(projects) = api.list_projects().await {
|
if let Ok(projects) = api.list_projects().await {
|
||||||
|
let projects = projects.into_iter();
|
||||||
|
let projects = projects.map(|p| (p.name.clone().into(), p.id)).collect_vec();
|
||||||
*projects_list.borrow_mut() = projects;
|
*projects_list.borrow_mut() = projects;
|
||||||
project_list_ready.emit(());
|
project_list_ready.emit(());
|
||||||
}
|
}
|
||||||
@ -282,6 +294,36 @@ impl Model {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// User clicked a project in the Open Project dialog. Open it.
|
||||||
|
fn open_project(&self, id_in_list: &usize) {
|
||||||
|
let controller = self.ide_controller.clone_ref();
|
||||||
|
let projects_list = self.available_projects.clone_ref();
|
||||||
|
let view = self.view.clone_ref();
|
||||||
|
let status_bar = self.status_bar.clone_ref();
|
||||||
|
let id = *id_in_list;
|
||||||
|
executor::global::spawn(async move {
|
||||||
|
let app = js::app_or_panic();
|
||||||
|
app.show_progress_indicator(OPEN_PROJECT_SPINNER_PROGRESS);
|
||||||
|
view.hide_graph_editor();
|
||||||
|
if let Ok(api) = controller.manage_projects() {
|
||||||
|
api.close_project();
|
||||||
|
let uuid = projects_list.borrow().get(id).map(|(_name, uuid)| *uuid);
|
||||||
|
if let Some(uuid) = uuid {
|
||||||
|
if let Err(error) = api.open_project(uuid).await {
|
||||||
|
error!("Error opening project: {error}.");
|
||||||
|
status_bar.add_event(format!("Error opening project: {error}."));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
error!("Project with id {id} not found.");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
error!("Project Manager API not available, cannot open project.");
|
||||||
|
}
|
||||||
|
app.hide_progress_indicator();
|
||||||
|
view.show_graph_editor();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn execution_environment_changed(
|
fn execution_environment_changed(
|
||||||
&self,
|
&self,
|
||||||
execution_environment: ide_view::execution_environment_selector::ExecutionEnvironment,
|
execution_environment: ide_view::execution_environment_selector::ExecutionEnvironment,
|
||||||
@ -355,15 +397,28 @@ impl Project {
|
|||||||
let view = &model.view.frp;
|
let view = &model.view.frp;
|
||||||
let breadcrumbs = &model.view.graph().model.breadcrumbs;
|
let breadcrumbs = &model.view.graph().model.breadcrumbs;
|
||||||
let graph_view = &model.view.graph().frp;
|
let graph_view = &model.view.graph().frp;
|
||||||
let project_list = &model.view.project_list().frp;
|
let project_list = &model.view.project_list();
|
||||||
|
|
||||||
frp::extend! { network
|
frp::extend! { network
|
||||||
project_list_ready <- source_();
|
project_list_ready <- source_();
|
||||||
project_list.project_list <+ project_list_ready.map(
|
|
||||||
f_!(model.available_projects.borrow().clone())
|
project_list.grid.reset_entries <+ project_list_ready.map(f_!([model]{
|
||||||
);
|
let cols = 1;
|
||||||
|
let rows = model.available_projects.borrow().len();
|
||||||
|
(rows, cols)
|
||||||
|
}));
|
||||||
|
entry_model <- project_list.grid.model_for_entry_needed.map(f!([model]((row, col)) {
|
||||||
|
let projects = model.available_projects.borrow();
|
||||||
|
let project = projects.get(*row);
|
||||||
|
project.map(|(name, _)| (*row, *col, name.clone_ref()))
|
||||||
|
})).filter_map(|t| t.clone());
|
||||||
|
project_list.grid.model_for_entry <+ entry_model;
|
||||||
|
|
||||||
open_project_list <- view.project_list_shown.on_true();
|
open_project_list <- view.project_list_shown.on_true();
|
||||||
eval_ open_project_list (model.project_list_opened(project_list_ready.clone_ref()));
|
eval_ open_project_list(model.project_list_opened(project_list_ready.clone_ref()));
|
||||||
|
selected_project <- project_list.grid.entry_selected.filter_map(|e| *e);
|
||||||
|
eval selected_project(((row, _col)) model.open_project(row));
|
||||||
|
project_list.grid.select_entry <+ selected_project.constant(None);
|
||||||
|
|
||||||
eval view.searcher ([model](params) {
|
eval view.searcher ([model](params) {
|
||||||
if let Some(params) = params {
|
if let Some(params) = params {
|
||||||
|
@ -18,10 +18,16 @@ use ide_view::graph_editor::NodeId;
|
|||||||
use ide_view::project::SearcherParams;
|
use ide_view::project::SearcherParams;
|
||||||
use ide_view::project::SearcherType;
|
use ide_view::project::SearcherType;
|
||||||
|
|
||||||
|
|
||||||
|
// ==============
|
||||||
|
// === Export ===
|
||||||
|
// ==============
|
||||||
|
|
||||||
pub mod ai;
|
pub mod ai;
|
||||||
pub mod component_browser;
|
pub mod component_browser;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// Trait for the searcher.
|
/// Trait for the searcher.
|
||||||
pub trait SearcherPresenter: Debug {
|
pub trait SearcherPresenter: Debug {
|
||||||
/// Initiate the operating mode for the searcher based on the given [`SearcherParams`]. If the
|
/// Initiate the operating mode for the searcher based on the given [`SearcherParams`]. If the
|
||||||
|
@ -13,8 +13,8 @@ use crate::presenter;
|
|||||||
use crate::presenter::graph::AstNodeId;
|
use crate::presenter::graph::AstNodeId;
|
||||||
use crate::presenter::graph::ViewNodeId;
|
use crate::presenter::graph::ViewNodeId;
|
||||||
use crate::presenter::searcher::component_browser::provider::ControllerComponentsProviderExt;
|
use crate::presenter::searcher::component_browser::provider::ControllerComponentsProviderExt;
|
||||||
|
|
||||||
use crate::presenter::searcher::SearcherPresenter;
|
use crate::presenter::searcher::SearcherPresenter;
|
||||||
|
|
||||||
use enso_frp as frp;
|
use enso_frp as frp;
|
||||||
use enso_suggestion_database::documentation_ir::EntryDocumentation;
|
use enso_suggestion_database::documentation_ir::EntryDocumentation;
|
||||||
use enso_suggestion_database::documentation_ir::Placeholder;
|
use enso_suggestion_database::documentation_ir::Placeholder;
|
||||||
@ -35,6 +35,7 @@ use ide_view::project::SearcherParams;
|
|||||||
pub mod provider;
|
pub mod provider;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// ==============
|
// ==============
|
||||||
// === Errors ===
|
// === Errors ===
|
||||||
// ==============
|
// ==============
|
||||||
|
@ -133,6 +133,7 @@ impl ide_view::searcher::DocumentationProvider for Action {
|
|||||||
Some(doc.unwrap_or_else(|| Self::doc_placeholder_for(&suggestion)))
|
Some(doc.unwrap_or_else(|| Self::doc_placeholder_for(&suggestion)))
|
||||||
}
|
}
|
||||||
Action::Example(example) => Some(example.documentation_html.clone()),
|
Action::Example(example) => Some(example.documentation_html.clone()),
|
||||||
|
Action::ProjectManagement(_) => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
use super::prelude::*;
|
use super::prelude::*;
|
||||||
|
|
||||||
use crate::controller::ide;
|
use crate::config::ProjectToOpen;
|
||||||
use crate::controller::ide::ManagingProjectAPI;
|
use crate::ide;
|
||||||
use crate::transport::test_utils::TestWithMockedTransport;
|
use crate::transport::test_utils::TestWithMockedTransport;
|
||||||
|
|
||||||
use engine_protocol::project_manager;
|
use engine_protocol::project_manager;
|
||||||
|
use engine_protocol::project_manager::ProjectName;
|
||||||
use json_rpc::test_util::transport::mock::MockTransport;
|
use json_rpc::test_util::transport::mock::MockTransport;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use wasm_bindgen_test::wasm_bindgen_test;
|
use wasm_bindgen_test::wasm_bindgen_test;
|
||||||
@ -27,8 +28,11 @@ fn failure_to_open_project_is_reported() {
|
|||||||
fixture.run_test(async move {
|
fixture.run_test(async move {
|
||||||
let project_manager = Rc::new(project_manager::Client::new(transport));
|
let project_manager = Rc::new(project_manager::Client::new(transport));
|
||||||
executor::global::spawn(project_manager.runner());
|
executor::global::spawn(project_manager.runner());
|
||||||
let ide_controller = ide::Desktop::new(project_manager).unwrap();
|
let name = ProjectName::new_unchecked(crate::constants::DEFAULT_PROJECT_NAME.to_owned());
|
||||||
let result = ide_controller.create_new_project(None, None).await;
|
let project_to_open = ProjectToOpen::Name(name);
|
||||||
|
let initializer =
|
||||||
|
ide::initializer::WithProjectManager::new(project_manager, project_to_open);
|
||||||
|
let result = initializer.initialize_project_model().await;
|
||||||
result.expect_err("Error should have been reported.");
|
result.expect_err("Error should have been reported.");
|
||||||
});
|
});
|
||||||
fixture.when_stalled_send_response(json!({
|
fixture.when_stalled_send_response(json!({
|
||||||
|
@ -105,6 +105,10 @@ ensogl::define_endpoints! {
|
|||||||
hide_project_list(),
|
hide_project_list(),
|
||||||
/// Close the searcher without taking any actions
|
/// Close the searcher without taking any actions
|
||||||
close_searcher(),
|
close_searcher(),
|
||||||
|
/// Show the graph editor.
|
||||||
|
show_graph_editor(),
|
||||||
|
/// Hide the graph editor.
|
||||||
|
hide_graph_editor(),
|
||||||
/// Simulates a style toggle press event.
|
/// Simulates a style toggle press event.
|
||||||
toggle_style(),
|
toggle_style(),
|
||||||
/// Toggles the visibility of private components in the component browser.
|
/// Toggles the visibility of private components in the component browser.
|
||||||
@ -296,6 +300,14 @@ impl Model {
|
|||||||
fn hide_project_list(&self) {
|
fn hide_project_list(&self) {
|
||||||
self.display_object.remove_child(&*self.project_list);
|
self.display_object.remove_child(&*self.project_list);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn show_graph_editor(&self) {
|
||||||
|
self.display_object.add_child(&*self.graph_editor);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hide_graph_editor(&self) {
|
||||||
|
self.display_object.remove_child(&*self.graph_editor);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -428,6 +440,9 @@ impl View {
|
|||||||
let documentation = &searcher.model().documentation;
|
let documentation = &searcher.model().documentation;
|
||||||
|
|
||||||
frp::extend! { network
|
frp::extend! { network
|
||||||
|
eval_ frp.show_graph_editor(model.show_graph_editor());
|
||||||
|
eval_ frp.hide_graph_editor(model.hide_graph_editor());
|
||||||
|
|
||||||
// We block graph navigator if it interferes with other panels (searcher, documentation,
|
// We block graph navigator if it interferes with other panels (searcher, documentation,
|
||||||
// etc.)
|
// etc.)
|
||||||
searcher_active <- searcher.is_hovered || documentation.frp.is_selected;
|
searcher_active <- searcher.is_hovered || documentation.frp.is_selected;
|
||||||
@ -657,7 +672,7 @@ impl View {
|
|||||||
let project_list = &model.project_list;
|
let project_list = &model.project_list;
|
||||||
frp::extend! { network
|
frp::extend! { network
|
||||||
eval_ frp.show_project_list (model.show_project_list());
|
eval_ frp.show_project_list (model.show_project_list());
|
||||||
project_chosen <- project_list.frp.selected_project.constant(());
|
project_chosen <- project_list.grid.entry_selected.constant(());
|
||||||
mouse_down <- scene.mouse.frp_deprecated.down.constant(());
|
mouse_down <- scene.mouse.frp_deprecated.down.constant(());
|
||||||
clicked_on_bg <- mouse_down.filter(f_!(scene.mouse.target.get().is_background()));
|
clicked_on_bg <- mouse_down.filter(f_!(scene.mouse.target.get().is_background()));
|
||||||
should_be_closed <- any(frp.hide_project_list,project_chosen,clicked_on_bg);
|
should_be_closed <- any(frp.hide_project_list,project_chosen,clicked_on_bg);
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use ensogl::display::shape::*;
|
use ensogl::display::shape::*;
|
||||||
|
|
||||||
use engine_protocol::project_manager::ProjectMetadata;
|
|
||||||
use enso_frp as frp;
|
use enso_frp as frp;
|
||||||
use ensogl::application::frp::API;
|
use ensogl::application::frp::API;
|
||||||
use ensogl::application::Application;
|
use ensogl::application::Application;
|
||||||
@ -196,23 +195,6 @@ mod background {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
// ===========
|
|
||||||
// === FRP ===
|
|
||||||
// ===========
|
|
||||||
|
|
||||||
ensogl::define_endpoints! {
|
|
||||||
Input {
|
|
||||||
/// This is a list of projects to choose from.
|
|
||||||
project_list (Vec<ProjectMetadata>),
|
|
||||||
}
|
|
||||||
Output {
|
|
||||||
/// This is the selected project.
|
|
||||||
selected_project (Option<ProjectMetadata>),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// ===================
|
// ===================
|
||||||
// === ProjectList ===
|
// === ProjectList ===
|
||||||
// ===================
|
// ===================
|
||||||
@ -222,19 +204,18 @@ ensogl::define_endpoints! {
|
|||||||
/// This is a list of projects in a nice frame with a title.
|
/// This is a list of projects in a nice frame with a title.
|
||||||
#[derive(Clone, CloneRef, Debug)]
|
#[derive(Clone, CloneRef, Debug)]
|
||||||
pub struct ProjectList {
|
pub struct ProjectList {
|
||||||
|
network: frp::Network,
|
||||||
display_object: display::object::Instance,
|
display_object: display::object::Instance,
|
||||||
background: background::View,
|
background: background::View,
|
||||||
caption: text::Text,
|
caption: text::Text,
|
||||||
grid: grid_view::scrollable::SelectableGridView<Entry>,
|
|
||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
pub frp: Frp,
|
pub grid: grid_view::scrollable::SelectableGridView<Entry>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ProjectList {
|
impl ProjectList {
|
||||||
/// Create Project List Component.
|
/// Create Project List Component.
|
||||||
pub fn new(app: &Application) -> Self {
|
pub fn new(app: &Application) -> Self {
|
||||||
let frp = Frp::new();
|
let network = frp::Network::new("ProjectList");
|
||||||
let network = &frp.network;
|
|
||||||
let display_object = display::object::Instance::new();
|
let display_object = display::object::Instance::new();
|
||||||
let background = background::View::new();
|
let background = background::View::new();
|
||||||
let caption = app.new_view::<text::Text>();
|
let caption = app.new_view::<text::Text>();
|
||||||
@ -247,7 +228,7 @@ impl ProjectList {
|
|||||||
caption.add_to_scene_layer(&app.display.default_scene.layers.panel_text);
|
caption.add_to_scene_layer(&app.display.default_scene.layers.panel_text);
|
||||||
|
|
||||||
let style_frp = StyleWatchFrp::new(&app.display.default_scene.style_sheet);
|
let style_frp = StyleWatchFrp::new(&app.display.default_scene.style_sheet);
|
||||||
let style = Style::from_theme(network, &style_frp);
|
let style = Style::from_theme(&network, &style_frp);
|
||||||
|
|
||||||
frp::extend! { network
|
frp::extend! { network
|
||||||
init <- source::<()>();
|
init <- source::<()>();
|
||||||
@ -285,30 +266,11 @@ impl ProjectList {
|
|||||||
grid_x <- grid_width.map(|width| -width / 2.0);
|
grid_x <- grid_width.map(|width| -width / 2.0);
|
||||||
grid_y <- all_with3(&content_size, &bar_height, &paddings, |s,h,p| s.y / 2.0 - *h - *p);
|
grid_y <- all_with3(&content_size, &bar_height, &paddings, |s,h,p| s.y / 2.0 - *h - *p);
|
||||||
_eval <- all_with(&grid_x, &grid_y, f!((x, y) grid.set_xy(Vector2(*x, *y))));
|
_eval <- all_with(&grid_x, &grid_y, f!((x, y) grid.set_xy(Vector2(*x, *y))));
|
||||||
|
|
||||||
grid.reset_entries <+ frp.input.project_list.map(|projects| (projects.len(), 1));
|
|
||||||
grid_model_for_entry <= grid.model_for_entry_needed.map2(
|
|
||||||
&frp.input.project_list,
|
|
||||||
|(row, col), projects| {
|
|
||||||
let project = projects.get(*row)?;
|
|
||||||
Some((*row, *col, project.name.clone().into()))
|
|
||||||
}
|
|
||||||
);
|
|
||||||
grid.model_for_entry <+ grid_model_for_entry;
|
|
||||||
|
|
||||||
frp.source.selected_project <+ grid.entry_selected.map2(
|
|
||||||
&frp.input.project_list,
|
|
||||||
|selected_entry, projects| {
|
|
||||||
let (row, _) = (*selected_entry)?;
|
|
||||||
projects.get(row).cloned()
|
|
||||||
}
|
|
||||||
);
|
|
||||||
grid.select_entry <+ frp.output.selected_project.filter_map(|s| s.as_ref().map(|_| None));
|
|
||||||
}
|
}
|
||||||
style.init.emit(());
|
style.init.emit(());
|
||||||
init.emit(());
|
init.emit(());
|
||||||
|
|
||||||
Self { display_object, background, caption, grid, frp }
|
Self { network, display_object, background, caption, grid }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,7 +6,6 @@
|
|||||||
|
|
||||||
use ensogl::prelude::*;
|
use ensogl::prelude::*;
|
||||||
|
|
||||||
use engine_protocol::project_manager::ProjectMetadata;
|
|
||||||
use enso_frp as frp;
|
use enso_frp as frp;
|
||||||
use ensogl::application;
|
use ensogl::application;
|
||||||
use ensogl::application::Application;
|
use ensogl::application::Application;
|
||||||
@ -40,12 +39,11 @@ pub struct Model {
|
|||||||
status_bar: crate::status_bar::View,
|
status_bar: crate::status_bar::View,
|
||||||
welcome_view: crate::welcome_screen::View,
|
welcome_view: crate::welcome_screen::View,
|
||||||
project_view: Rc<CloneCell<Option<crate::project::View>>>,
|
project_view: Rc<CloneCell<Option<crate::project::View>>>,
|
||||||
frp_outputs: FrpOutputsSource,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Model {
|
impl Model {
|
||||||
/// Constructor.
|
/// Constuctor.
|
||||||
pub fn new(app: &Application, frp: &Frp) -> Self {
|
pub fn new(app: &Application) -> Self {
|
||||||
let app = app.clone_ref();
|
let app = app.clone_ref();
|
||||||
let display_object = display::object::Instance::new();
|
let display_object = display::object::Instance::new();
|
||||||
let state = Rc::new(CloneCell::new(State::WelcomeScreen));
|
let state = Rc::new(CloneCell::new(State::WelcomeScreen));
|
||||||
@ -54,9 +52,8 @@ impl Model {
|
|||||||
let welcome_view = app.new_view::<crate::welcome_screen::View>();
|
let welcome_view = app.new_view::<crate::welcome_screen::View>();
|
||||||
let project_view = Rc::new(CloneCell::new(None));
|
let project_view = Rc::new(CloneCell::new(None));
|
||||||
display_object.add_child(&welcome_view);
|
display_object.add_child(&welcome_view);
|
||||||
let frp_outputs = frp.output.source.clone_ref();
|
|
||||||
|
|
||||||
Self { app, display_object, state, status_bar, welcome_view, project_view, frp_outputs }
|
Self { app, display_object, state, status_bar, welcome_view, project_view }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Switch displayed view from Project View to Welcome Screen. Project View will not be
|
/// Switch displayed view from Project View to Welcome Screen. Project View will not be
|
||||||
@ -87,7 +84,6 @@ impl Model {
|
|||||||
if self.project_view.get().is_none() {
|
if self.project_view.get().is_none() {
|
||||||
let view = self.app.new_view::<crate::project::View>();
|
let view = self.app.new_view::<crate::project::View>();
|
||||||
let network = &view.network;
|
let network = &view.network;
|
||||||
let project_list_frp = &view.project_list().frp;
|
|
||||||
let status_bar = &self.status_bar;
|
let status_bar = &self.status_bar;
|
||||||
let display_object = &self.display_object;
|
let display_object = &self.display_object;
|
||||||
frp::extend! { network
|
frp::extend! { network
|
||||||
@ -95,8 +91,6 @@ impl Model {
|
|||||||
fs_vis_hidden <- view.fullscreen_visualization_shown.on_false();
|
fs_vis_hidden <- view.fullscreen_visualization_shown.on_false();
|
||||||
eval fs_vis_shown ((_) status_bar.unset_parent());
|
eval fs_vis_shown ((_) status_bar.unset_parent());
|
||||||
eval fs_vis_hidden ([display_object, status_bar](_) display_object.add_child(&status_bar));
|
eval fs_vis_hidden ([display_object, status_bar](_) display_object.add_child(&status_bar));
|
||||||
|
|
||||||
self.frp_outputs.selected_project <+ project_list_frp.selected_project;
|
|
||||||
}
|
}
|
||||||
self.project_view.set(Some(view));
|
self.project_view.set(Some(view));
|
||||||
}
|
}
|
||||||
@ -117,8 +111,6 @@ ensogl::define_endpoints! {
|
|||||||
switch_view_to_welcome_screen(),
|
switch_view_to_welcome_screen(),
|
||||||
}
|
}
|
||||||
Output {
|
Output {
|
||||||
/// The selected project in the project list
|
|
||||||
selected_project (Option<ProjectMetadata>),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,8 +138,8 @@ impl Deref for View {
|
|||||||
impl View {
|
impl View {
|
||||||
/// Constuctor.
|
/// Constuctor.
|
||||||
pub fn new(app: &Application) -> Self {
|
pub fn new(app: &Application) -> Self {
|
||||||
|
let model = Model::new(app);
|
||||||
let frp = Frp::new();
|
let frp = Frp::new();
|
||||||
let model = Model::new(app, &frp);
|
|
||||||
let network = &frp.network;
|
let network = &frp.network;
|
||||||
let style = StyleWatchFrp::new(&app.display.default_scene.style_sheet);
|
let style = StyleWatchFrp::new(&app.display.default_scene.style_sheet);
|
||||||
let offset_y = style.get_number(ensogl_hardcoded_theme::application::status_bar::offset_y);
|
let offset_y = style.get_number(ensogl_hardcoded_theme::application::status_bar::offset_y);
|
||||||
|
@ -33,7 +33,7 @@ impl TestOnNewProjectControllersOnly {
|
|||||||
let initializer = enso_gui::Initializer::new(config);
|
let initializer = enso_gui::Initializer::new(config);
|
||||||
let error_msg = "Couldn't open project.";
|
let error_msg = "Couldn't open project.";
|
||||||
let ide = initializer.initialize_ide_controller().await.expect(error_msg);
|
let ide = initializer.initialize_ide_controller().await.expect(error_msg);
|
||||||
ide.manage_projects().unwrap().create_new_project(None, None).await.unwrap();
|
ide.manage_projects().unwrap().create_new_project(None).await.unwrap();
|
||||||
let project = ide.current_project().unwrap();
|
let project = ide.current_project().unwrap();
|
||||||
Self { _ide: ide, project, _executor: executor }
|
Self { _ide: ide, project, _executor: executor }
|
||||||
}
|
}
|
||||||
|
@ -428,9 +428,10 @@ export class App {
|
|||||||
|
|
||||||
/** Show a spinner. The displayed progress is constant. */
|
/** Show a spinner. The displayed progress is constant. */
|
||||||
showProgressIndicator(progress: number) {
|
showProgressIndicator(progress: number) {
|
||||||
if (this.progressIndicator == null) {
|
if (this.progressIndicator) {
|
||||||
this.progressIndicator = new wasm.ProgressIndicator(this.config)
|
this.hideProgressIndicator()
|
||||||
}
|
}
|
||||||
|
this.progressIndicator = new wasm.ProgressIndicator(this.config)
|
||||||
this.progressIndicator.set(progress)
|
this.progressIndicator.set(progress)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user