mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: support moving view across sections (#5015)
This commit is contained in:
parent
893d23d6a3
commit
723423d423
@ -207,6 +207,13 @@ class ViewBloc extends Bloc<ViewEvent, ViewState> {
|
||||
),
|
||||
);
|
||||
},
|
||||
updateViewVisibility: (value) async {
|
||||
final view = value.view;
|
||||
await ViewBackendService.updateViewsVisibility(
|
||||
[view],
|
||||
value.isPublic,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
@ -370,6 +377,8 @@ class ViewEvent with _$ViewEvent {
|
||||
) = ViewDidUpdate;
|
||||
const factory ViewEvent.viewUpdateChildView(ViewPB result) =
|
||||
ViewUpdateChildView;
|
||||
const factory ViewEvent.updateViewVisibility(ViewPB view, bool isPublic) =
|
||||
UpdateViewVisibility;
|
||||
}
|
||||
|
||||
@freezed
|
||||
|
@ -280,4 +280,15 @@ class ViewBackendService {
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
static Future<FlowyResult<void, FlowyError>> updateViewsVisibility(
|
||||
List<ViewPB> views,
|
||||
bool isPublic,
|
||||
) async {
|
||||
final payload = UpdateViewVisibilityStatusPayloadPB(
|
||||
viewIds: views.map((e) => e.id).toList(),
|
||||
isPublic: isPublic,
|
||||
);
|
||||
return FolderEventUpdateViewVisibilityStatus(payload).send();
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/workspace/application/menu/sidebar_sections_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/folder/_folder_header.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/rename_view_dialog.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/view/view_item.dart';
|
||||
@ -95,6 +96,25 @@ class SectionFolder extends StatelessWidget {
|
||||
isHoverEnabled: isHoverEnabled,
|
||||
),
|
||||
),
|
||||
if (views.isEmpty)
|
||||
ViewItem(
|
||||
categoryType: categoryType,
|
||||
view: ViewPB(
|
||||
parentViewId: context
|
||||
.read<UserWorkspaceBloc>()
|
||||
.state
|
||||
.currentWorkspace
|
||||
?.workspaceId ??
|
||||
'',
|
||||
),
|
||||
level: 0,
|
||||
leftPadding: 16,
|
||||
isFeedback: false,
|
||||
onSelected: (_) {},
|
||||
onTertiarySelected: (_) {},
|
||||
isHoverEnabled: isHoverEnabled,
|
||||
isPlaceholder: true,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
|
@ -26,6 +26,7 @@ class DraggableViewItem extends StatefulWidget {
|
||||
this.topHighlightColor,
|
||||
this.bottomHighlightColor,
|
||||
this.onDragging,
|
||||
this.onMove,
|
||||
});
|
||||
|
||||
final Widget child;
|
||||
@ -36,6 +37,7 @@ class DraggableViewItem extends StatefulWidget {
|
||||
final Color? topHighlightColor;
|
||||
final Color? bottomHighlightColor;
|
||||
final void Function(bool isDragging)? onDragging;
|
||||
final void Function(ViewPB from, ViewPB to)? onMove;
|
||||
|
||||
@override
|
||||
State<DraggableViewItem> createState() => _DraggableViewItemState();
|
||||
@ -189,6 +191,11 @@ class _DraggableViewItemState extends State<DraggableViewItem> {
|
||||
return;
|
||||
}
|
||||
|
||||
if (widget.onMove != null) {
|
||||
widget.onMove?.call(from, to);
|
||||
return;
|
||||
}
|
||||
|
||||
final fromSection = getViewSection(from);
|
||||
final toSection = getViewSection(to);
|
||||
|
||||
|
@ -43,6 +43,7 @@ class ViewItem extends StatelessWidget {
|
||||
required this.isFeedback,
|
||||
this.height = 28.0,
|
||||
this.isHoverEnabled = true,
|
||||
this.isPlaceholder = false,
|
||||
});
|
||||
|
||||
final ViewPB view;
|
||||
@ -78,6 +79,10 @@ class ViewItem extends StatelessWidget {
|
||||
|
||||
final bool isHoverEnabled;
|
||||
|
||||
// all the view movement depends on the [ViewItem] widget, so we have to add a
|
||||
// placeholder widget to receive the drop event when moving view across sections.
|
||||
final bool isPlaceholder;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
@ -105,6 +110,7 @@ class ViewItem extends StatelessWidget {
|
||||
isFeedback: isFeedback,
|
||||
height: height,
|
||||
isHoverEnabled: isHoverEnabled,
|
||||
isPlaceholder: isPlaceholder,
|
||||
);
|
||||
},
|
||||
),
|
||||
@ -132,6 +138,7 @@ class InnerViewItem extends StatelessWidget {
|
||||
required this.isFeedback,
|
||||
required this.height,
|
||||
this.isHoverEnabled = true,
|
||||
this.isPlaceholder = false,
|
||||
});
|
||||
|
||||
final ViewPB view;
|
||||
@ -154,6 +161,7 @@ class InnerViewItem extends StatelessWidget {
|
||||
final double height;
|
||||
|
||||
final bool isHoverEnabled;
|
||||
final bool isPlaceholder;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -170,6 +178,7 @@ class InnerViewItem extends StatelessWidget {
|
||||
leftPadding: leftPadding,
|
||||
isFeedback: isFeedback,
|
||||
height: height,
|
||||
isPlaceholder: isPlaceholder,
|
||||
);
|
||||
|
||||
// if the view is expanded and has child views, render its child views
|
||||
@ -188,6 +197,7 @@ class InnerViewItem extends StatelessWidget {
|
||||
isDraggable: isDraggable,
|
||||
leftPadding: leftPadding,
|
||||
isFeedback: isFeedback,
|
||||
isPlaceholder: isPlaceholder,
|
||||
);
|
||||
}).toList();
|
||||
|
||||
@ -222,14 +232,17 @@ class InnerViewItem extends StatelessWidget {
|
||||
}
|
||||
|
||||
// wrap the child with DraggableItem if isDraggable is true
|
||||
if (isDraggable && !isReferencedDatabaseView(view, parentView)) {
|
||||
if ((isDraggable || isPlaceholder) &&
|
||||
!isReferencedDatabaseView(view, parentView)) {
|
||||
child = DraggableViewItem(
|
||||
isFirstChild: isFirstChild,
|
||||
view: view,
|
||||
child: child,
|
||||
onDragging: (isDragging) {
|
||||
_isDragging = isDragging;
|
||||
},
|
||||
onMove: isPlaceholder
|
||||
? (from, to) => _moveViewCrossSection(context, from, to)
|
||||
: null,
|
||||
feedback: (context) {
|
||||
return ViewItem(
|
||||
view: view,
|
||||
@ -243,6 +256,7 @@ class InnerViewItem extends StatelessWidget {
|
||||
isFeedback: true,
|
||||
);
|
||||
},
|
||||
child: child,
|
||||
);
|
||||
} else {
|
||||
// keep the same height of the DraggableItem
|
||||
@ -254,6 +268,37 @@ class InnerViewItem extends StatelessWidget {
|
||||
|
||||
return child;
|
||||
}
|
||||
|
||||
void _moveViewCrossSection(
|
||||
BuildContext context,
|
||||
ViewPB from,
|
||||
ViewPB to,
|
||||
) {
|
||||
if (isReferencedDatabaseView(view, parentView)) {
|
||||
return;
|
||||
}
|
||||
final fromSection = categoryType == FolderCategoryType.public
|
||||
? ViewSectionPB.Private
|
||||
: ViewSectionPB.Public;
|
||||
final toSection = categoryType == FolderCategoryType.public
|
||||
? ViewSectionPB.Public
|
||||
: ViewSectionPB.Private;
|
||||
context.read<ViewBloc>().add(
|
||||
ViewEvent.move(
|
||||
from,
|
||||
to.parentViewId,
|
||||
null,
|
||||
fromSection,
|
||||
toSection,
|
||||
),
|
||||
);
|
||||
context.read<ViewBloc>().add(
|
||||
ViewEvent.updateViewVisibility(
|
||||
from,
|
||||
categoryType == FolderCategoryType.public,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SingleInnerViewItem extends StatefulWidget {
|
||||
@ -272,6 +317,7 @@ class SingleInnerViewItem extends StatefulWidget {
|
||||
required this.isFeedback,
|
||||
required this.height,
|
||||
this.isHoverEnabled = true,
|
||||
this.isPlaceholder = false,
|
||||
});
|
||||
|
||||
final ViewPB view;
|
||||
@ -291,6 +337,7 @@ class SingleInnerViewItem extends StatefulWidget {
|
||||
final double height;
|
||||
|
||||
final bool isHoverEnabled;
|
||||
final bool isPlaceholder;
|
||||
|
||||
@override
|
||||
State<SingleInnerViewItem> createState() => _SingleInnerViewItemState();
|
||||
@ -305,6 +352,13 @@ class _SingleInnerViewItemState extends State<SingleInnerViewItem> {
|
||||
final isSelected =
|
||||
getIt<MenuSharedState>().latestOpenView?.id == widget.view.id;
|
||||
|
||||
if (widget.isPlaceholder) {
|
||||
return const SizedBox(
|
||||
height: 4,
|
||||
width: double.infinity,
|
||||
);
|
||||
}
|
||||
|
||||
if (widget.isFeedback || !widget.isHoverEnabled) {
|
||||
return _buildViewItem(
|
||||
false,
|
||||
|
@ -475,6 +475,15 @@ pub struct UpdateRecentViewPayloadPB {
|
||||
pub add_in_recent: bool,
|
||||
}
|
||||
|
||||
#[derive(Default, ProtoBuf)]
|
||||
pub struct UpdateViewVisibilityStatusPayloadPB {
|
||||
#[pb(index = 1)]
|
||||
pub view_ids: Vec<String>,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub is_public: bool,
|
||||
}
|
||||
|
||||
// impl<'de> Deserialize<'de> for ViewDataType {
|
||||
// fn deserialize<D>(deserializer: D) -> Result<Self, <D as Deserializer<'de>>::Error>
|
||||
// where
|
||||
|
@ -363,3 +363,14 @@ pub(crate) async fn reload_workspace_handler(
|
||||
folder.reload_workspace().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip(data, folder), err)]
|
||||
pub(crate) async fn update_view_visibility_status_handler(
|
||||
data: AFPluginData<UpdateViewVisibilityStatusPayloadPB>,
|
||||
folder: AFPluginState<Weak<FolderManager>>,
|
||||
) -> Result<(), FlowyError> {
|
||||
let folder = upgrade_folder(folder)?;
|
||||
let params = data.into_inner();
|
||||
folder.set_views_visibility(params.view_ids, params.is_public);
|
||||
Ok(())
|
||||
}
|
||||
|
@ -40,6 +40,7 @@ pub fn init(folder: Weak<FolderManager>) -> AFPlugin {
|
||||
.event(FolderEvent::ReloadWorkspace, reload_workspace_handler)
|
||||
.event(FolderEvent::ReadPrivateViews, read_private_views_handler)
|
||||
.event(FolderEvent::ReadCurrentWorkspaceViews, get_current_workspace_views_handler)
|
||||
.event(FolderEvent::UpdateViewVisibilityStatus, update_view_visibility_status_handler)
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Display, Hash, ProtoBuf_Enum, Flowy_Event)]
|
||||
@ -166,4 +167,7 @@ pub enum FolderEvent {
|
||||
/// Only the first level of child views are included.
|
||||
#[event(output = "RepeatedViewPB")]
|
||||
ReadCurrentWorkspaceViews = 40,
|
||||
|
||||
#[event(input = "UpdateViewVisibilityStatusPayloadPB")]
|
||||
UpdateViewVisibilityStatus = 41,
|
||||
}
|
||||
|
@ -1120,6 +1120,19 @@ impl FolderManager {
|
||||
&self.cloud_service
|
||||
}
|
||||
|
||||
pub fn set_views_visibility(&self, view_ids: Vec<String>, is_public: bool) {
|
||||
self.with_folder(
|
||||
|| (),
|
||||
|folder| {
|
||||
if is_public {
|
||||
folder.delete_private_view_ids(view_ids);
|
||||
} else {
|
||||
folder.add_private_view_ids(view_ids);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn get_sections(&self, section_type: Section) -> Vec<SectionItem> {
|
||||
self.with_folder(Vec::new, |folder| {
|
||||
let trash_ids = folder
|
||||
|
Loading…
Reference in New Issue
Block a user