fix: space issues (#5531)

* fix: display the space icon in breadcrumb and disable the space entry

* fix: the icon and the title are not aligned center in more menu

* fix: incorrect space icon corner radius

* fix: missed tooltip for add new page button

* fix: disable space migration for local user

* chore: use general as default space name

* fix: space name overflow

* fix: only show arrow button when hovering on page

* fix: sidebar tooltip font size

* fix: use space name as hint text

* feat: support adding a new space from space menu

* fix: filter the space view
This commit is contained in:
Lucas.Xu 2024-06-14 09:32:02 +08:00 committed by GitHub
parent e2ce274718
commit 785597f53e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 149 additions and 57 deletions

View File

@ -10,7 +10,19 @@ import 'package:collection/collection.dart';
class FavoriteService {
Future<FlowyResult<RepeatedFavoriteViewPB, FlowyError>> readFavorites() {
return FolderEventReadFavorites().send();
final result = FolderEventReadFavorites().send();
return result.then((result) {
return result.fold(
(favoriteViews) {
return FlowyResult.success(
RepeatedFavoriteViewPB(
items: favoriteViews.items.where((e) => !e.item.isSpace),
),
);
},
(error) => FlowyResult.failure(error),
);
});
}
Future<FlowyResult<void, FlowyError>> toggleFavorite(

View File

@ -2,6 +2,7 @@ import 'dart:async';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/workspace/application/recent/recent_listener.dart';
import 'package:appflowy/workspace/application/view/view_ext.dart';
import 'package:appflowy_backend/dispatch/dispatch.dart';
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
@ -65,8 +66,20 @@ class CachedRecentService {
).send();
}
Future<FlowyResult<RepeatedRecentViewPB, FlowyError>> _readRecentViews() =>
FolderEventReadRecentViews().send();
Future<FlowyResult<RepeatedRecentViewPB, FlowyError>>
_readRecentViews() async {
final result = await FolderEventReadRecentViews().send();
return result.fold(
(recentViews) {
return FlowyResult.success(
RepeatedRecentViewPB(
items: recentViews.items.where((e) => !e.item.isSpace),
),
);
},
(error) => FlowyResult.failure(error),
);
}
bool _isInitialized = false;

View File

@ -69,6 +69,7 @@ class SpaceBloc extends Bloc<SpaceEvent, SpaceState> {
publicViews: publicViews,
privateViews: privateViews,
);
final currentSpace = await _getLastOpenedSpace(spaces);
final isExpanded = await _getSpaceExpandStatus(currentSpace);
emit(
@ -79,6 +80,10 @@ class SpaceBloc extends Bloc<SpaceEvent, SpaceState> {
shouldShowUpgradeDialog: shouldShowUpgradeDialog,
),
);
if (shouldShowUpgradeDialog) {
add(const SpaceEvent.migrate());
}
},
create: (name, icon, iconColor, permission) async {
final space = await _createSpace(
@ -391,6 +396,10 @@ class SpaceBloc extends Bloc<SpaceEvent, SpaceState> {
final isOwner = members.items
.any((e) => e.role == AFRolePB.Owner && e.email == user.email);
if (members.items.isEmpty) {
return true;
}
// only one member in the workspace, migrate it immediately
// only the owner can migrate the public space
if (members.items.length == 1 || isOwner) {
@ -399,7 +408,7 @@ class SpaceBloc extends Bloc<SpaceEvent, SpaceState> {
final publicViews =
await _workspaceService.getPublicViews().getOrThrow();
final publicSpace = await _createSpace(
name: 'shared',
name: 'Shared',
icon: builtInSpaceIcons.first,
iconColor: builtInSpaceColors.first,
permission: SpacePermission.publicToAll,
@ -422,7 +431,7 @@ class SpaceBloc extends Bloc<SpaceEvent, SpaceState> {
final privateViews =
await _workspaceService.getPrivateViews().getOrThrow();
final privateSpace = await _createSpace(
name: 'private',
name: 'Private',
icon: builtInSpaceIcons.last,
iconColor: builtInSpaceColors.last,
permission: SpacePermission.private,

View File

@ -18,6 +18,7 @@ class ViewTitleBloc extends Bloc<ViewTitleEvent, ViewTitleState> {
state.copyWith(
name: view.name,
icon: view.icon.value,
view: view,
),
);
@ -27,16 +28,18 @@ class ViewTitleBloc extends Bloc<ViewTitleEvent, ViewTitleState> {
ViewTitleEvent.updateNameOrIcon(
view.name,
view.icon.value,
view,
),
);
},
);
},
updateNameOrIcon: (name, icon) async {
updateNameOrIcon: (name, icon, view) async {
emit(
state.copyWith(
name: name,
icon: icon,
view: view,
),
);
},
@ -58,8 +61,11 @@ class ViewTitleBloc extends Bloc<ViewTitleEvent, ViewTitleState> {
@freezed
class ViewTitleEvent with _$ViewTitleEvent {
const factory ViewTitleEvent.initial() = Initial;
const factory ViewTitleEvent.updateNameOrIcon(String name, String icon) =
UpdateNameOrIcon;
const factory ViewTitleEvent.updateNameOrIcon(
String name,
String icon,
ViewPB? view,
) = UpdateNameOrIcon;
}
@freezed
@ -67,6 +73,7 @@ class ViewTitleState with _$ViewTitleState {
const factory ViewTitleState({
required String name,
required String icon,
@Default(null) ViewPB? view,
}) = _ViewTitleState;
factory ViewTitleState.initial() => const ViewTitleState(name: '', icon: '');

View File

@ -70,13 +70,14 @@ class SidebarTopMenu extends StatelessWidget {
children: [
TextSpan(
text: '${LocaleKeys.sideBar_closeSidebar.tr()}\n',
style: Theme.of(context).textTheme.bodyMedium!.copyWith(color: color),
style:
Theme.of(context).tooltipTheme.textStyle!.copyWith(color: color),
),
TextSpan(
text: Platform.isMacOS ? '⌘+.' : 'Ctrl+\\',
style: Theme.of(context)
.textTheme
.bodyMedium!
.tooltipTheme
.textStyle!
.copyWith(color: Theme.of(context).hintColor),
),
],
@ -91,6 +92,7 @@ class SidebarTopMenu extends StatelessWidget {
child: FlowyTooltip(
richMessage: textSpan,
child: Listener(
behavior: HitTestBehavior.translucent,
onPointerDown: (_) => context
.read<HomeSettingBloc>()
.add(const HomeSettingEvent.collapseMenu()),

View File

@ -326,11 +326,12 @@ class _SidebarState extends State<_Sidebar> {
}
Widget _renderFolderOrSpace(EdgeInsets menuHorizontalInset) {
final spaceState = context.read<SpaceBloc>().state;
final workspaceState = context.read<UserWorkspaceBloc>().state;
// there's no space or the workspace is not collaborative,
// show the folder section (Workspace, Private, Personal)
// otherwise, show the space
return context.watch<SpaceBloc>().state.spaces.isEmpty ||
!context.read<UserWorkspaceBloc>().state.isCollabWorkspaceOn
return spaceState.spaces.isEmpty || !workspaceState.isCollabWorkspaceOn
? Expanded(
child: Padding(
padding: menuHorizontalInset - const EdgeInsets.only(right: 6),
@ -362,7 +363,10 @@ class _SidebarState extends State<_Sidebar> {
}
Widget _renderUpgradeSpaceButton(EdgeInsets menuHorizontalInset) {
return !context.watch<SpaceBloc>().state.shouldShowUpgradeDialog
final spaceState = context.watch<SpaceBloc>().state;
final workspaceState = context.read<UserWorkspaceBloc>().state;
return !spaceState.shouldShowUpgradeDialog ||
!workspaceState.isCollabWorkspaceOn
? const SizedBox.shrink()
: Container(
height: 40,

View File

@ -15,9 +15,9 @@ class CreateSpacePopup extends StatefulWidget {
}
class _CreateSpacePopupState extends State<CreateSpacePopup> {
String spaceName = '';
String spaceIcon = '';
String spaceIconColor = '';
String spaceName = LocaleKeys.space_defaultSpaceName.tr();
String spaceIcon = builtInSpaceIcons.first;
String spaceIconColor = builtInSpaceColors.first;
SpacePermission spacePermission = SpacePermission.publicToAll;
@override
@ -98,7 +98,7 @@ class _SpaceNameTextField extends StatelessWidget {
SizedBox(
height: 40,
child: FlowyTextField(
hintText: 'Untitled space',
text: LocaleKeys.space_defaultSpaceName.tr(),
onChanged: onChanged,
),
),

View File

@ -99,6 +99,7 @@ class _SpaceNameTextField extends StatelessWidget {
SizedBox.square(
dimension: 40,
child: SpaceIconPopup(
cornerRadius: 12,
icon: space?.spaceIcon,
iconColor: space?.spaceIconColor,
onIconChanged: onIconChanged,

View File

@ -10,6 +10,7 @@ import 'package:appflowy/workspace/presentation/home/home_sizes.dart';
import 'package:appflowy/workspace/presentation/home/menu/menu_shared_state.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/favorites/favorite_folder.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/shared/rename_view_dialog.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/create_space_popup.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/sidebar_space_header.dart';
import 'package:appflowy/workspace/presentation/home/menu/view/view_item.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
@ -79,33 +80,47 @@ class _SpaceState extends State<_Space> {
Widget build(BuildContext context) {
return BlocBuilder<SpaceBloc, SpaceState>(
builder: (context, state) {
// final isCollaborativeWorkspace =
// context.read<UserWorkspaceBloc>().state.isCollabWorkspaceOn;
if (state.spaces.isEmpty) {
return const SizedBox.shrink();
}
final currentSpace = state.currentSpace ?? state.spaces.first;
return MouseRegion(
onEnter: (_) => isHovered.value = true,
onExit: (_) => isHovered.value = false,
child: Column(
children: [
SidebarSpaceHeader(
isExpanded: state.isExpanded,
space: currentSpace,
onAdded: () => _showCreatePagePopup(context, currentSpace),
onPressed: () {},
onTapMore: () {},
),
_Pages(
return Column(
children: [
SidebarSpaceHeader(
isExpanded: state.isExpanded,
space: currentSpace,
onAdded: () => _showCreatePagePopup(context, currentSpace),
onCreateNewSpace: () => _showCreateSpaceDialog(context),
),
MouseRegion(
onEnter: (_) => isHovered.value = true,
onExit: (_) => isHovered.value = false,
child: _Pages(
key: ValueKey(currentSpace.id),
space: currentSpace,
isHovered: isHovered,
),
],
),
],
);
},
);
}
void _showCreateSpaceDialog(BuildContext context) {
final spaceBloc = context.read<SpaceBloc>();
showDialog(
context: context,
builder: (_) {
return Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12.0),
),
child: BlocProvider.value(
value: spaceBloc,
child: const CreateSpacePopup(),
),
);
},

View File

@ -20,16 +20,14 @@ class SidebarSpaceHeader extends StatefulWidget {
const SidebarSpaceHeader({
super.key,
required this.space,
required this.onPressed,
required this.onAdded,
required this.onTapMore,
required this.onCreateNewSpace,
required this.isExpanded,
});
final ViewPB space;
final VoidCallback onPressed;
final VoidCallback onAdded;
final VoidCallback onTapMore;
final VoidCallback onCreateNewSpace;
final bool isExpanded;
@override
@ -73,6 +71,7 @@ class _SidebarSpaceHeaderState extends State<SidebarSpaceHeader> {
// rightIcon: _buildRightIcon(),
iconPadding: 10.0,
text: _buildChild(),
rightIcon: const HSpace(60.0),
),
),
Positioned(
@ -95,10 +94,13 @@ class _SidebarSpaceHeaderState extends State<SidebarSpaceHeader> {
cornerRadius: 6.0,
),
const HSpace(10),
FlowyText.medium(
widget.space.name,
lineHeight: 1.15,
fontSize: 14.0,
Flexible(
child: FlowyText.medium(
widget.space.name,
lineHeight: 1.15,
fontSize: 14.0,
overflow: TextOverflow.ellipsis,
),
),
const HSpace(4.0),
FlowySvg(
@ -127,6 +129,7 @@ class _SidebarSpaceHeaderState extends State<SidebarSpaceHeader> {
const HSpace(8.0),
FlowyIconButton(
width: 24,
tooltipText: LocaleKeys.sideBar_addAPage.tr(),
iconPadding: const EdgeInsets.all(4.0),
icon: const FlowySvg(FlowySvgs.view_item_add_s),
onPressed: widget.onAdded,
@ -150,6 +153,7 @@ class _SidebarSpaceHeaderState extends State<SidebarSpaceHeader> {
_showManageSpaceDialog(context);
break;
case SpaceMoreActionType.addNewSpace:
widget.onCreateNewSpace();
break;
case SpaceMoreActionType.collapseAllPages:
break;
@ -166,6 +170,7 @@ class _SidebarSpaceHeaderState extends State<SidebarSpaceHeader> {
title: LocaleKeys.space_rename.tr(),
value: widget.space.name,
autoSelectAllText: true,
hintText: LocaleKeys.space_spaceName.tr(),
onConfirm: (name, _) {
context.read<SpaceBloc>().add(SpaceEvent.rename(widget.space, name));
},

View File

@ -29,12 +29,14 @@ class SpaceIconPopup extends StatefulWidget {
super.key,
this.icon,
this.iconColor,
this.cornerRadius = 16,
required this.onIconChanged,
});
final String? icon;
final String? iconColor;
final void Function(String icon, String color) onIconChanged;
final double cornerRadius;
@override
State<SpaceIconPopup> createState() => _SpaceIconPopupState();
@ -93,7 +95,7 @@ class _SpaceIconPopupState extends State<SpaceIconPopup> {
valueListenable: selectedIcon,
builder: (_, icon, __) {
final child = ClipRRect(
borderRadius: BorderRadius.circular(16.0),
borderRadius: BorderRadius.circular(widget.cornerRadius),
child: FlowySvg(
FlowySvgData('assets/flowy_icons/16x/$icon.svg'),
color: Color(int.parse(color)),

View File

@ -40,6 +40,7 @@ class SpaceMorePopup extends StatelessWidget {
return FlowyIconButton(
width: 24,
icon: const FlowySvg(FlowySvgs.workspace_three_dots_s),
tooltipText: LocaleKeys.space_manage.tr(),
onPressed: () {
onEditing(true);
popover.show();
@ -68,9 +69,8 @@ class SpaceMorePopup extends StatelessWidget {
SpaceMoreActionType.rename,
SpaceMoreActionType.changeIcon,
SpaceMoreActionType.manage,
// SpaceMoreActionType.divider,
// SpaceMoreActionType.addNewSpace,
// SpaceMoreActionType.collapseAllPages,
SpaceMoreActionType.divider,
SpaceMoreActionType.addNewSpace,
SpaceMoreActionType.divider,
SpaceMoreActionType.delete,
];
@ -163,7 +163,7 @@ class SpaceMoreActionTypeWrapper extends CustomActionCell {
rightIcon: inner.rightIcon,
iconPadding: 10.0,
text: SizedBox(
height: 18.0,
// height: 16.0,
child: FlowyText.regular(
inner.name,
color: inner == SpaceMoreActionType.delete

View File

@ -72,15 +72,15 @@ class FlowyNavigation extends StatelessWidget {
TextSpan(
text: '${LocaleKeys.sideBar_openSidebar.tr()}\n',
style: Theme.of(context)
.textTheme
.bodyMedium!
.tooltipTheme
.textStyle!
.copyWith(color: color),
),
TextSpan(
text: Platform.isMacOS ? '⌘+.' : 'Ctrl+\\',
style: Theme.of(context)
.textTheme
.bodyMedium!
.tooltipTheme
.textStyle!
.copyWith(color: Theme.of(context).hintColor),
),
],

View File

@ -3,6 +3,7 @@ import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart';
import 'package:appflowy/workspace/application/view/view_ext.dart';
import 'package:appflowy/workspace/application/view_title/view_title_bar_bloc.dart';
import 'package:appflowy/workspace/application/view_title/view_title_bloc.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/space_icon.dart';
import 'package:appflowy/workspace/presentation/widgets/rename_view_popover.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
@ -153,6 +154,8 @@ class _ViewTitleState extends State<_ViewTitle> {
const HSpace(4.0),
],
);
} else if (widget.view.isSpace) {
return _buildSpaceTitle(context, state);
} else if (isEditable) {
return _buildEditableViewTitle(context, state);
} else {
@ -163,6 +166,14 @@ class _ViewTitleState extends State<_ViewTitle> {
);
}
Widget _buildSpaceTitle(BuildContext context, ViewTitleState state) {
return Container(
alignment: Alignment.center,
margin: const EdgeInsets.symmetric(horizontal: 6.0),
child: _buildIconAndName(state, false),
);
}
Widget _buildUnEditableViewTitle(BuildContext context, ViewTitleState state) {
return Listener(
onPointerDown: (_) => context.read<TabsBloc>().openPlugin(widget.view),
@ -171,7 +182,6 @@ class _ViewTitleState extends State<_ViewTitle> {
child: FlowyButton(
useIntrinsicWidth: true,
margin: const EdgeInsets.symmetric(horizontal: 6.0),
onTap: () {},
text: _buildIconAndName(state, false),
),
),
@ -220,6 +230,15 @@ class _ViewTitleState extends State<_ViewTitle> {
),
const HSpace(4.0),
],
if (state.view?.isSpace == true &&
state.view?.spaceIconSvg != null) ...[
SpaceIcon(
dimension: 14,
space: state.view!,
cornerRadius: 4,
),
const HSpace(6.0),
],
Opacity(
opacity: isEditable ? 1.0 : 0.5,
child: FlowyText.regular(

View File

@ -254,7 +254,7 @@
"clickToHideWorkspace": "Click to hide workspace\nPages you created here are visible to every member",
"clickToHidePersonal": "Click to hide personal space",
"clickToHideFavorites": "Click to hide favorite space",
"addAPage": "Add a page",
"addAPage": "Add a new page",
"addAPageToPrivate": "Add a page to private space",
"addAPageToWorkspace": "Add a page to workspace",
"recent": "Recent",
@ -1912,6 +1912,7 @@
"unableToDeleteLastSpace": "Cannot delete the last space",
"unableToDeleteSpaceNotCreatedByYou": "Cannot delete a space created by others",
"enableSpacesForYourWorkspace": "Enable spaces for your workspace",
"title": "Spaces"
"title": "Spaces",
"defaultSpaceName": "General"
}
}

View File

@ -1235,11 +1235,12 @@ pub(crate) fn get_workspace_public_view_pbs(workspace_id: &str, folder: &Folder)
.into_iter()
.map(|view| {
// Get child views
let child_views = folder
let mut child_views: Vec<Arc<View>> = folder
.views
.get_views_belong_to(&view.id)
.into_iter()
.collect();
child_views.retain(|view| !trash_ids.contains(&view.id));
view_pb_with_child_views(view, child_views)
})
.collect()
@ -1284,11 +1285,12 @@ pub(crate) fn get_workspace_private_view_pbs(workspace_id: &str, folder: &Folder
.into_iter()
.map(|view| {
// Get child views
let child_views = folder
let mut child_views: Vec<Arc<View>> = folder
.views
.get_views_belong_to(&view.id)
.into_iter()
.collect();
child_views.retain(|view| !trash_ids.contains(&view.id));
view_pb_with_child_views(view, child_views)
})
.collect()