Complete local-first content migration slice
This commit is contained in:
+82
-6
@@ -68,6 +68,7 @@ use crate::drive::panel::DrivePanelAction;
|
||||
use crate::editor::{EditorView, Event as EditorEvent, SingleLineEditorOptions};
|
||||
use crate::env_vars::CloudEnvVarCollection;
|
||||
use crate::features::FeatureFlag;
|
||||
use crate::local_object_repository::LocalObjectRepository;
|
||||
use crate::menu::{Event, Menu, MenuItem, MenuItemFields};
|
||||
use crate::network::NetworkStatus;
|
||||
use crate::notebooks::CloudNotebookModel;
|
||||
@@ -3522,9 +3523,31 @@ impl DriveIndex {
|
||||
cloud_object_type_and_id: CloudObjectTypeAndId,
|
||||
ctx: &mut ViewContext<Self>,
|
||||
) {
|
||||
UpdateManager::handle(ctx).update(ctx, move |update_manager, ctx| {
|
||||
update_manager.trash_object(cloud_object_type_and_id, ctx);
|
||||
});
|
||||
match cloud_object_type_and_id {
|
||||
CloudObjectTypeAndId::Notebook(id) => {
|
||||
LocalObjectRepository::handle(ctx).update(ctx, |repository, ctx| {
|
||||
repository.set_notebook_trashed(id, true, ctx);
|
||||
});
|
||||
}
|
||||
CloudObjectTypeAndId::Workflow(id) => {
|
||||
LocalObjectRepository::handle(ctx).update(ctx, |repository, ctx| {
|
||||
repository.set_workflow_trashed(id, true, ctx);
|
||||
});
|
||||
}
|
||||
CloudObjectTypeAndId::GenericStringObject {
|
||||
object_type: GenericStringObjectFormat::Json(JsonObjectType::EnvVarCollection),
|
||||
id,
|
||||
} => {
|
||||
LocalObjectRepository::handle(ctx).update(ctx, |repository, ctx| {
|
||||
repository.set_env_var_collection_trashed(id, true, ctx);
|
||||
});
|
||||
}
|
||||
CloudObjectTypeAndId::Folder(_) | CloudObjectTypeAndId::GenericStringObject { .. } => {
|
||||
UpdateManager::handle(ctx).update(ctx, move |update_manager, ctx| {
|
||||
update_manager.trash_object(cloud_object_type_and_id, ctx);
|
||||
});
|
||||
}
|
||||
}
|
||||
self.reset_menus(ctx);
|
||||
ctx.notify();
|
||||
}
|
||||
@@ -3534,6 +3557,37 @@ impl DriveIndex {
|
||||
cloud_object_type_and_id: &CloudObjectTypeAndId,
|
||||
ctx: &mut ViewContext<Self>,
|
||||
) {
|
||||
match cloud_object_type_and_id {
|
||||
CloudObjectTypeAndId::Notebook(id) => {
|
||||
LocalObjectRepository::handle(ctx).update(ctx, |repository, ctx| {
|
||||
repository.set_notebook_trashed(*id, false, ctx);
|
||||
});
|
||||
self.reset_menus(ctx);
|
||||
ctx.notify();
|
||||
return;
|
||||
}
|
||||
CloudObjectTypeAndId::Workflow(id) => {
|
||||
LocalObjectRepository::handle(ctx).update(ctx, |repository, ctx| {
|
||||
repository.set_workflow_trashed(*id, false, ctx);
|
||||
});
|
||||
self.reset_menus(ctx);
|
||||
ctx.notify();
|
||||
return;
|
||||
}
|
||||
CloudObjectTypeAndId::GenericStringObject {
|
||||
object_type: GenericStringObjectFormat::Json(JsonObjectType::EnvVarCollection),
|
||||
id,
|
||||
} => {
|
||||
LocalObjectRepository::handle(ctx).update(ctx, |repository, ctx| {
|
||||
repository.set_env_var_collection_trashed(*id, false, ctx);
|
||||
});
|
||||
self.reset_menus(ctx);
|
||||
ctx.notify();
|
||||
return;
|
||||
}
|
||||
CloudObjectTypeAndId::Folder(_) | CloudObjectTypeAndId::GenericStringObject { .. } => {}
|
||||
}
|
||||
|
||||
// Check if object being untrashed is in team space, if it is, then check
|
||||
// corresponding object limits for that team.
|
||||
if let Some(space) =
|
||||
@@ -3663,9 +3717,31 @@ impl DriveIndex {
|
||||
cloud_object_type_and_id: &CloudObjectTypeAndId,
|
||||
ctx: &mut ViewContext<Self>,
|
||||
) {
|
||||
UpdateManager::handle(ctx).update(ctx, move |update_manager, ctx| {
|
||||
update_manager.delete_object_by_user(*cloud_object_type_and_id, ctx);
|
||||
});
|
||||
match cloud_object_type_and_id {
|
||||
CloudObjectTypeAndId::Notebook(id) => {
|
||||
LocalObjectRepository::handle(ctx).update(ctx, |repository, ctx| {
|
||||
repository.delete_notebook(*id, ctx);
|
||||
});
|
||||
}
|
||||
CloudObjectTypeAndId::Workflow(id) => {
|
||||
LocalObjectRepository::handle(ctx).update(ctx, |repository, ctx| {
|
||||
repository.delete_workflow(*id, ctx);
|
||||
});
|
||||
}
|
||||
CloudObjectTypeAndId::GenericStringObject {
|
||||
object_type: GenericStringObjectFormat::Json(JsonObjectType::EnvVarCollection),
|
||||
id,
|
||||
} => {
|
||||
LocalObjectRepository::handle(ctx).update(ctx, |repository, ctx| {
|
||||
repository.delete_env_var_collection(*id, ctx);
|
||||
});
|
||||
}
|
||||
CloudObjectTypeAndId::Folder(_) | CloudObjectTypeAndId::GenericStringObject { .. } => {
|
||||
UpdateManager::handle(ctx).update(ctx, move |update_manager, ctx| {
|
||||
update_manager.delete_object_by_user(*cloud_object_type_and_id, ctx);
|
||||
});
|
||||
}
|
||||
}
|
||||
self.reset_menus(ctx);
|
||||
ctx.notify();
|
||||
}
|
||||
|
||||
@@ -37,6 +37,13 @@ fn initialize_app(app: &mut App) {
|
||||
initialize_settings_for_tests(app);
|
||||
|
||||
app.add_singleton_model(CloudModel::mock);
|
||||
app.add_singleton_model(|ctx| {
|
||||
crate::local_object_repository::LocalObjectRepository::new(
|
||||
None,
|
||||
Some(Owner::mock_current_user()),
|
||||
ctx,
|
||||
)
|
||||
});
|
||||
app.add_singleton_model(UserWorkspaces::default_mock);
|
||||
app.add_singleton_model(|_| NetworkStatus::new());
|
||||
app.add_singleton_model(|_| Appearance::mock());
|
||||
@@ -131,39 +138,42 @@ fn test_retry_menu_item_visibility() {
|
||||
// by default, it doesn't show up
|
||||
index.update(&mut app, |index, ctx| {
|
||||
let menu_items = index.menu_items(&Space::Personal, &warp_drive_item_id, ctx);
|
||||
assert_eq!(menu_items.len(), 5);
|
||||
assert_eq!(menu_items.len(), 6);
|
||||
assert_eq!(label_for_menu_item(&menu_items[0]), "Edit");
|
||||
assert_eq!(label_for_menu_item(&menu_items[1]), "Copy workflow text");
|
||||
assert_eq!(label_for_menu_item(&menu_items[2]), "Share");
|
||||
assert_eq!(label_for_menu_item(&menu_items[3]), "Duplicate");
|
||||
assert_eq!(label_for_menu_item(&menu_items[4]), "Export");
|
||||
assert_eq!(label_for_menu_item(&menu_items[5]), "Trash");
|
||||
});
|
||||
|
||||
// when the object is in error, it should show up
|
||||
set_object_in_error(&mut app, &cloud_object_type_and_id);
|
||||
index.update(&mut app, |index, ctx| {
|
||||
let menu_items = index.menu_items(&Space::Personal, &warp_drive_item_id, ctx);
|
||||
assert_eq!(menu_items.len(), 6);
|
||||
assert_eq!(menu_items.len(), 7);
|
||||
assert_eq!(label_for_menu_item(&menu_items[0]), "Retry");
|
||||
assert_eq!(label_for_menu_item(&menu_items[1]), "Edit");
|
||||
assert_eq!(label_for_menu_item(&menu_items[2]), "Copy workflow text");
|
||||
assert_eq!(label_for_menu_item(&menu_items[3]), "Share");
|
||||
assert_eq!(label_for_menu_item(&menu_items[4]), "Duplicate");
|
||||
assert_eq!(label_for_menu_item(&menu_items[5]), "Export");
|
||||
assert_eq!(label_for_menu_item(&menu_items[6]), "Trash");
|
||||
});
|
||||
|
||||
// but if we're offline, it shouldn't show up
|
||||
// local trash remains available offline
|
||||
NetworkStatus::handle(&app).update(&mut app, |network_status, ctx| {
|
||||
network_status.reachability_changed(false, ctx);
|
||||
});
|
||||
index.update(&mut app, |index, ctx| {
|
||||
let menu_items = index.menu_items(&Space::Personal, &warp_drive_item_id, ctx);
|
||||
assert_eq!(menu_items.len(), 5);
|
||||
assert_eq!(menu_items.len(), 6);
|
||||
assert_eq!(label_for_menu_item(&menu_items[0]), "Edit");
|
||||
assert_eq!(label_for_menu_item(&menu_items[1]), "Copy workflow text");
|
||||
assert_eq!(label_for_menu_item(&menu_items[2]), "Share");
|
||||
assert_eq!(label_for_menu_item(&menu_items[3]), "Duplicate");
|
||||
assert_eq!(label_for_menu_item(&menu_items[4]), "Export");
|
||||
assert_eq!(label_for_menu_item(&menu_items[5]), "Trash");
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
+38
-1
@@ -26,6 +26,7 @@ use crate::cloud_object::{
|
||||
};
|
||||
use crate::env_vars::manager::EnvVarCollectionSource;
|
||||
use crate::env_vars::CloudEnvVarCollection;
|
||||
use crate::local_object_repository::{local_owner, LocalObjectRepository};
|
||||
use crate::notebooks::manager::NotebookSource;
|
||||
use crate::notebooks::CloudNotebook;
|
||||
use crate::server::cloud_objects::update_manager::{InitiatedBy, UpdateManager};
|
||||
@@ -130,7 +131,12 @@ impl DrivePanel {
|
||||
Some(folder_id) => CloudModel::as_ref(app)
|
||||
.get_folder(folder_id)
|
||||
.map(|folder| folder.permissions.owner),
|
||||
None => UserWorkspaces::as_ref(app).space_to_owner(space, app),
|
||||
None => match space {
|
||||
Space::Personal => Some(local_owner()),
|
||||
Space::Shared | Space::Team { .. } => {
|
||||
UserWorkspaces::as_ref(app).space_to_owner(space, app)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -357,6 +363,37 @@ impl DrivePanel {
|
||||
cloud_object_type_and_id: &CloudObjectTypeAndId,
|
||||
ctx: &mut ViewContext<Self>,
|
||||
) {
|
||||
match cloud_object_type_and_id {
|
||||
CloudObjectTypeAndId::Notebook(id) => {
|
||||
LocalObjectRepository::handle(ctx).update(ctx, |repository, ctx| {
|
||||
repository.duplicate_notebook(*id, ctx);
|
||||
});
|
||||
self.reset_all_menus(ctx);
|
||||
ctx.notify();
|
||||
return;
|
||||
}
|
||||
CloudObjectTypeAndId::Workflow(id) => {
|
||||
LocalObjectRepository::handle(ctx).update(ctx, |repository, ctx| {
|
||||
repository.duplicate_workflow(*id, ctx);
|
||||
});
|
||||
self.reset_all_menus(ctx);
|
||||
ctx.notify();
|
||||
return;
|
||||
}
|
||||
CloudObjectTypeAndId::GenericStringObject {
|
||||
object_type: GenericStringObjectFormat::Json(JsonObjectType::EnvVarCollection),
|
||||
id,
|
||||
} => {
|
||||
LocalObjectRepository::handle(ctx).update(ctx, |repository, ctx| {
|
||||
repository.duplicate_env_var_collection(*id, ctx);
|
||||
});
|
||||
self.reset_all_menus(ctx);
|
||||
ctx.notify();
|
||||
return;
|
||||
}
|
||||
CloudObjectTypeAndId::Folder(_) | CloudObjectTypeAndId::GenericStringObject { .. } => {}
|
||||
}
|
||||
|
||||
// Check if object being duplicated is in team space, if it is, then check
|
||||
// corresponding object limits for that team.
|
||||
if let Some(space) =
|
||||
|
||||
@@ -34,7 +34,7 @@ use crate::appearance::Appearance;
|
||||
use crate::auth::UserUid;
|
||||
use crate::cloud_object::breadcrumbs::{ContainingObject, ContainingObjectKind};
|
||||
use crate::cloud_object::model::persistence::{CloudModel, CloudModelEvent};
|
||||
use crate::cloud_object::{CloudObject, CloudObjectEventEntrypoint, ObjectType, Owner, Revision};
|
||||
use crate::cloud_object::{CloudObject, ObjectType, Owner, Revision};
|
||||
use crate::drive::cloud_object_styling::warp_drive_icon_color;
|
||||
use crate::drive::items::WarpDriveItemId;
|
||||
use crate::drive::{CloudObjectTypeAndId, DriveObjectType};
|
||||
@@ -43,9 +43,8 @@ use crate::editor::{
|
||||
PlainTextEditorViewAction as EditorAction, PropagateAndNoOpNavigationKeys, TextOptions,
|
||||
TextStyleOperation,
|
||||
};
|
||||
use crate::local_object_repository::LocalObjectRepository;
|
||||
use crate::menu::{Event, Menu, MenuItem, MenuItemFields};
|
||||
use crate::network::NetworkStatus;
|
||||
use crate::server::cloud_objects::update_manager::UpdateManager;
|
||||
use crate::server::ids::{ClientId, ServerId, SyncId};
|
||||
use crate::server::server_api::ai::AIClient;
|
||||
use crate::themes::theme::AnsiColorIdentifier;
|
||||
@@ -661,37 +660,25 @@ impl WorkflowModal {
|
||||
// Close workflow editor
|
||||
self.close(true, ctx);
|
||||
|
||||
UpdateManager::handle(ctx).update(ctx, move |update_manager, ctx| {
|
||||
update_manager.trash_object(
|
||||
CloudObjectTypeAndId::from_id_and_type(workflow_id, ObjectType::Workflow),
|
||||
ctx,
|
||||
);
|
||||
LocalObjectRepository::handle(ctx).update(ctx, |repository, ctx| {
|
||||
repository.set_workflow_trashed(workflow_id, true, ctx);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn menu_items(&self, app: &AppContext) -> Vec<MenuItem<WorkflowModalAction>> {
|
||||
let mut menu_items = Vec::new();
|
||||
|
||||
// Add "Copy workflow text" to menu
|
||||
menu_items.push(
|
||||
vec![
|
||||
// Add "Copy workflow text" to menu
|
||||
MenuItemFields::new("Copy workflow text")
|
||||
.with_on_select_action(WorkflowModalAction::CopyObjectToClipboard)
|
||||
.with_icon(Icon::CopyMenuItem)
|
||||
.into_item(),
|
||||
);
|
||||
|
||||
// Add "Trash" to menu
|
||||
if self.is_online(app) {
|
||||
menu_items.push(
|
||||
MenuItemFields::new("Trash")
|
||||
.with_on_select_action(WorkflowModalAction::TrashObject)
|
||||
.with_icon(Icon::Trash)
|
||||
.into_item(),
|
||||
);
|
||||
}
|
||||
|
||||
menu_items
|
||||
// Add "Trash" to menu
|
||||
MenuItemFields::new("Trash")
|
||||
.with_on_select_action(WorkflowModalAction::TrashObject)
|
||||
.with_icon(Icon::Trash)
|
||||
.into_item(),
|
||||
]
|
||||
}
|
||||
|
||||
pub fn should_show_unsaved_changes_dialog(&self, app: &AppContext) -> bool {
|
||||
@@ -731,20 +718,17 @@ impl WorkflowModal {
|
||||
|
||||
match (self.workflow_id, self.owner) {
|
||||
(Some(workflow_id), None) => {
|
||||
UpdateManager::handle(ctx).update(ctx, |update_manager, ctx| {
|
||||
update_manager.update_workflow(workflow, workflow_id, self.revision_ts.clone(), ctx);
|
||||
LocalObjectRepository::handle(ctx).update(ctx, |repository, ctx| {
|
||||
repository.update_workflow(workflow_id, workflow, ctx);
|
||||
});
|
||||
ctx.emit(WorkflowModalEvent::UpdatedWorkflow(workflow_id));
|
||||
}
|
||||
(None, Some(owner)) => {
|
||||
UpdateManager::handle(ctx).update(ctx, |update_manager, ctx| {
|
||||
update_manager.create_workflow(
|
||||
workflow,
|
||||
owner,
|
||||
(None, Some(_)) => {
|
||||
LocalObjectRepository::handle(ctx).update(ctx, |repository, ctx| {
|
||||
repository.create_workflow_with_id(
|
||||
SyncId::ClientId(ClientId::new()),
|
||||
self.initial_folder_id,
|
||||
ClientId::default(),
|
||||
CloudObjectEventEntrypoint::Unknown,
|
||||
true,
|
||||
workflow,
|
||||
ctx,
|
||||
);
|
||||
});
|
||||
@@ -1348,10 +1332,6 @@ impl WorkflowModal {
|
||||
)
|
||||
}
|
||||
|
||||
fn is_online(&self, app: &AppContext) -> bool {
|
||||
NetworkStatus::as_ref(app).is_online()
|
||||
}
|
||||
|
||||
fn render_header_menu_and_close(&self, appearance: &Appearance) -> Box<dyn Element> {
|
||||
let mut row = Flex::row();
|
||||
|
||||
|
||||
@@ -2,11 +2,12 @@ use std::sync::Arc;
|
||||
|
||||
use galaxy_core::ui::appearance::Appearance;
|
||||
use warpui::platform::WindowStyle;
|
||||
use warpui::{App, SingletonEntity, ViewHandle};
|
||||
use warpui::{AddSingletonModel, App, SingletonEntity, ViewHandle};
|
||||
|
||||
use super::WorkflowModal;
|
||||
use crate::auth::AuthStateProvider;
|
||||
use crate::cloud_object::model::persistence::CloudModel;
|
||||
use crate::cloud_object::{CloudObject, Owner};
|
||||
use crate::editor::PlainTextEditorViewAction as EditorAction;
|
||||
use crate::server::server_api::team::MockTeamClient;
|
||||
use crate::server::server_api::workspace::MockWorkspaceClient;
|
||||
@@ -21,6 +22,13 @@ fn initialize_app(app: &mut App) {
|
||||
|
||||
app.add_singleton_model(|_| Appearance::mock());
|
||||
app.add_singleton_model(CloudModel::mock);
|
||||
app.add_singleton_model(|ctx| {
|
||||
crate::local_object_repository::LocalObjectRepository::new(
|
||||
None,
|
||||
Some(Owner::mock_current_user()),
|
||||
ctx,
|
||||
)
|
||||
});
|
||||
app.add_singleton_model(|_| ServerApiProvider::new_for_test());
|
||||
app.add_singleton_model(|_| KeybindingChangedNotifier::mock());
|
||||
app.add_singleton_model(|_| AuthStateProvider::new_for_test());
|
||||
@@ -63,6 +71,34 @@ fn build_argument(
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_save_new_workflow_persists_locally() {
|
||||
App::test((), |mut app| async move {
|
||||
let modal_view = create_modal(&mut app);
|
||||
|
||||
modal_view.update(&mut app, |view, ctx| {
|
||||
view.open_with_new(Owner::mock_current_user(), None, ctx);
|
||||
view.title_editor.update(ctx, |editor, ctx| {
|
||||
editor.set_buffer_text("Local workflow", ctx);
|
||||
});
|
||||
view.content_editor.update(ctx, |editor, ctx| {
|
||||
editor.set_buffer_text("cargo test", ctx);
|
||||
});
|
||||
view.save_workflow_and_close(ctx);
|
||||
});
|
||||
|
||||
app.read(|ctx| {
|
||||
let workflows = CloudModel::as_ref(ctx)
|
||||
.get_all_active_workflows()
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(workflows.len(), 1);
|
||||
assert_eq!(workflows[0].model().data.name(), "Local workflow");
|
||||
assert_eq!(workflows[0].model().data.command(), Some("cargo test"));
|
||||
assert!(!workflows[0].metadata().has_pending_content_changes());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pasting_command_no_argument_overlap_fewer_arguments() {
|
||||
App::test((), |mut app| async move {
|
||||
|
||||
Reference in New Issue
Block a user