fix: page style issues (#5317)

* fix: 7 emojis per line

* fix: remove shadow of the icons using presets cover

* fix: expand appbar buttons size

* fix: save new title name when it changed

* feat: add cover image preview

* fix: dismiss unsplash and presets panel auto

* feat: add selected color for cover image and layout section

* fix: selected icon size too small

* fix: dismiss page style panel before pushing to emoji and font selector

* chore: update back button icon

* chore: bump version 0.5.7
This commit is contained in:
Lucas.Xu 2024-05-13 13:26:19 +08:00 committed by GitHub
parent cdcb393efd
commit 4cfd83cbc4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 294 additions and 139 deletions

View File

@ -1,4 +1,22 @@
# Release Notes
## Version 0.5.7 - 05/10/2024
### Bug Fixes
- Resolved page opening issue on Android.
- Fixed text input inconsistency on Kanban board cards.
## Version 0.5.6 - 05/07/2024
### New Features
- Team collaboration is live! Add members to your workspace to edit and collaborate on pages together.
- Collaborate in real time on the same page with other members. Edits made by others will appear instantly.
- Create multiple workspaces for different kinds of content.
- Customize your entire page on mobile through the Page Style menu with options for layout, font, font size, emoji, and cover image.
- Open a row record as a full page.
### Bug Fixes
- Resolved issue with setting background color for the Simple Table block.
- Adjusted toolbar for various screen sizes.
- Added a request for photo permission before uploading images on mobile.
- Exported creation and last modification timestamps to CSV.
## Version 0.5.5 - 04/24/2024
### New Features
- Improved the display of code blocks with line numbers

View File

@ -26,7 +26,7 @@ CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE = true
CARGO_MAKE_CRATE_FS_NAME = "dart_ffi"
CARGO_MAKE_CRATE_NAME = "dart-ffi"
LIB_NAME = "dart_ffi"
APPFLOWY_VERSION = "0.5.6"
APPFLOWY_VERSION = "0.5.7"
FLUTTER_DESKTOP_FEATURES = "dart"
PRODUCT_NAME = "AppFlowy"
MACOSX_DEPLOYMENT_TARGET = "11.0"

View File

@ -1,8 +1,10 @@
import 'package:appflowy/mobile/application/page_style/document_page_style_bloc.dart';
import 'package:appflowy/user/application/user_service.dart';
import 'package:appflowy/workspace/application/view/prelude.dart';
import 'package:appflowy/workspace/application/view/view_ext.dart';
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
import 'package:appflowy_result/appflowy_result.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
@ -21,6 +23,9 @@ class MobileViewPageBloc
initial: () async {
_registerListeners();
final userProfilePB =
await UserBackendService.getCurrentUserProfile()
.fold((s) => s, (f) => null);
final result = await ViewBackendService.getView(viewId);
final isImmersiveMode =
_isImmersiveMode(result.fold((s) => s, (f) => null));
@ -29,6 +34,7 @@ class MobileViewPageBloc
isLoading: false,
result: result,
isImmersiveMode: isImmersiveMode,
userProfilePB: userProfilePB,
),
);
},
@ -71,7 +77,7 @@ class MobileViewPageBloc
final cover = view.cover;
if (cover == null || cover.type == PageStyleCoverImageType.none) {
return false;
} else if (view.layout == ViewLayoutPB.Document) {
} else if (view.layout == ViewLayoutPB.Document && !cover.isPresets) {
// only support immersive mode for document layout
return true;
}
@ -93,6 +99,7 @@ class MobileViewPageState with _$MobileViewPageState {
@Default(true) bool isLoading,
@Default(null) FlowyResult<ViewPB, FlowyError>? result,
@Default(false) bool isImmersiveMode,
@Default(null) UserProfilePB? userProfilePB,
}) = _MobileViewPageState;
factory MobileViewPageState.initial() => const MobileViewPageState();

View File

@ -37,6 +37,7 @@ class FlowyAppBar extends AppBar {
Widget? title,
String? titleText,
FlowyAppBarLeadingType leadingType = FlowyAppBarLeadingType.back,
double? leadingWidth,
Widget? leading,
super.centerTitle,
VoidCallback? onTapLeading,
@ -52,7 +53,7 @@ class FlowyAppBar extends AppBar {
titleSpacing: 0,
elevation: 0,
leading: leading ?? leadingType.getWidget(onTapLeading),
leadingWidth: leadingType.width,
leadingWidth: leadingWidth ?? leadingType.width,
toolbarHeight: 44.0,
bottom: showDivider
? const PreferredSize(

View File

@ -43,8 +43,9 @@ class MobileViewPageImmersiveAppBar extends StatelessWidget
AppBarTheme.of(context).backgroundColor?.withOpacity(opacity),
showDivider: false,
title: Opacity(opacity: opacity >= 0.99 ? 1.0 : 0, child: title),
leadingWidth: 44,
leading: Padding(
padding: const EdgeInsets.symmetric(horizontal: 2.0, vertical: 4.0),
padding: const EdgeInsets.only(top: 4.0, bottom: 4.0, left: 12.0),
child: _buildAppBarBackButton(context),
),
actions: actions,
@ -59,7 +60,7 @@ class MobileViewPageImmersiveAppBar extends StatelessWidget
child: _ImmersiveAppBarButton(
icon: FlowySvgs.m_app_bar_back_s,
dimension: 30.0,
iconPadding: 6.0,
iconPadding: 3.0,
isImmersiveMode:
context.read<MobileViewPageBloc>().state.isImmersiveMode,
appBarOpacity: appBarOpacity,
@ -104,7 +105,7 @@ class MobileViewPageMoreButton extends StatelessWidget {
child: _ImmersiveAppBarButton(
icon: FlowySvgs.m_app_bar_more_s,
dimension: 30.0,
iconPadding: 5.0,
iconPadding: 3.0,
isImmersiveMode: isImmersiveMode,
appBarOpacity: appBarOpacity,
),
@ -144,8 +145,11 @@ class MobileViewPageLayoutButton extends StatelessWidget {
showHeader: true,
title: LocaleKeys.pageStyle_title.tr(),
backgroundColor: Theme.of(context).colorScheme.background,
builder: (_) => BlocProvider.value(
value: context.read<DocumentPageStyleBloc>(),
builder: (_) => MultiBlocProvider(
providers: [
BlocProvider.value(value: context.read<DocumentPageStyleBloc>()),
BlocProvider.value(value: context.read<MobileViewPageBloc>()),
],
child: PageStyleBottomSheet(
view: context.read<ViewBloc>().state.view,
),
@ -155,7 +159,7 @@ class MobileViewPageLayoutButton extends StatelessWidget {
child: _ImmersiveAppBarButton(
icon: FlowySvgs.m_layout_s,
dimension: 30.0,
iconPadding: 5.0,
iconPadding: 3.0,
isImmersiveMode: isImmersiveMode,
appBarOpacity: appBarOpacity,
),

View File

@ -149,14 +149,11 @@ class _DocumentImmersiveCoverState extends State<DocumentImmersiveCover> {
fontSize: 28.0,
fontWeight: FontWeight.w700,
fontFamily: fontFamily,
color: state.cover.type == PageStyleCoverImageType.none
? null
: Colors.white,
color:
state.cover.isNone || state.cover.isPresets ? null : Colors.white,
),
onSubmitted: (value) {
scrollController.position.jumpTo(0);
context.read<ViewBloc>().add(ViewEvent.rename(value));
},
onChanged: _rename,
onSubmitted: _rename,
);
}
@ -250,4 +247,9 @@ class _DocumentImmersiveCoverState extends State<DocumentImmersiveCover> {
focusNode.unfocus(disposition: UnfocusDisposition.previouslyFocusedChild);
}
}
void _rename(String name) {
scrollController.position.jumpTo(0);
context.read<ViewBloc>().add(ViewEvent.rename(name));
}
}

View File

@ -131,12 +131,16 @@ class _UnsplashImages extends StatelessWidget {
};
final mainAxisSpacing = switch (type) {
UnsplashImageType.halfScreen => 16.0,
UnsplashImageType.fullScreen => 8.0,
UnsplashImageType.fullScreen => 16.0,
};
final crossAxisSpacing = switch (type) {
UnsplashImageType.halfScreen => 10.0,
UnsplashImageType.fullScreen => 16.0,
};
return GridView.count(
crossAxisCount: crossAxisCount,
mainAxisSpacing: mainAxisSpacing,
crossAxisSpacing: 10.0,
crossAxisSpacing: crossAxisSpacing,
childAspectRatio: 4 / 3,
children: photos
.map(
@ -197,7 +201,9 @@ class _UnsplashImage extends StatelessWidget {
}
Widget _buildFullScreenImage(BuildContext context) {
return Stack(
return ClipRRect(
borderRadius: BorderRadius.circular(8.0),
child: Stack(
children: [
LayoutBuilder(
builder: (context, constraints) {
@ -210,15 +216,16 @@ class _UnsplashImage extends StatelessWidget {
},
),
Positioned(
bottom: 6,
left: 6,
bottom: 9,
left: 10,
child: FlowyText.medium(
photo.name,
fontSize: 10.0,
fontSize: 13.0,
color: Colors.white,
),
),
],
),
);
}
}

View File

@ -1,16 +1,21 @@
import 'dart:async';
import 'dart:io';
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/application/base/mobile_view_page_bloc.dart';
import 'package:appflowy/mobile/application/page_style/document_page_style_bloc.dart';
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/image_util.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/unsplash_image_widget.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/page_style/_page_cover_bottom_sheet.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/page_style/_page_style_util.dart';
import 'package:appflowy/shared/appflowy_network_image.dart';
import 'package:appflowy/shared/feedback_gesture_detector.dart';
import 'package:appflowy/shared/flowy_gradient_colors.dart';
import 'package:appflowy/shared/permission/permission_checker.dart';
import 'package:appflowy/user/application/user_service.dart';
import 'package:appflowy/util/string_extension.dart';
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
import 'package:appflowy_result/appflowy_result.dart';
@ -19,6 +24,7 @@ import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/style_widget/snap_bar.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:image_picker/image_picker.dart';
class PageStyleCoverImage extends StatelessWidget {
@ -33,13 +39,11 @@ class PageStyleCoverImage extends StatelessWidget {
final backgroundColor = context.pageStyleBackgroundColor;
return BlocBuilder<DocumentPageStyleBloc, DocumentPageStyleState>(
builder: (context, state) {
return Row(
return Column(
children: [
_buildOptionGroup(
context,
backgroundColor,
state,
),
_buildOptionGroup(context, backgroundColor, state),
const VSpace(16.0),
_buildPreview(context, state),
],
);
},
@ -51,8 +55,7 @@ class PageStyleCoverImage extends StatelessWidget {
Color backgroundColor,
DocumentPageStyleState state,
) {
return Expanded(
child: Container(
return Container(
decoration: BoxDecoration(
color: backgroundColor,
borderRadius: const BorderRadius.horizontal(
@ -86,11 +89,90 @@ class PageStyleCoverImage extends StatelessWidget {
),
],
),
);
}
Widget _buildPreview(
BuildContext context,
DocumentPageStyleState state,
) {
final cover = state.coverImage;
if (cover.isNone) {
return const SizedBox.shrink();
}
final value = cover.value;
final type = cover.type;
Widget preview = const SizedBox.shrink();
if (type == PageStyleCoverImageType.customImage ||
type == PageStyleCoverImageType.unsplashImage) {
final userProfilePB =
context.read<MobileViewPageBloc>().state.userProfilePB;
preview = FlowyNetworkImage(
url: value,
userProfilePB: userProfilePB,
);
}
if (type == PageStyleCoverImageType.builtInImage) {
preview = Image.asset(
PageStyleCoverImageType.builtInImagePath(value),
fit: BoxFit.cover,
);
}
if (type == PageStyleCoverImageType.pureColor) {
final color = value.coverColor(context);
if (color != null) {
preview = ColoredBox(
color: color,
);
}
}
if (type == PageStyleCoverImageType.gradientColor) {
preview = Container(
decoration: BoxDecoration(
gradient: FlowyGradientColor.fromId(value).linear,
),
);
}
if (type == PageStyleCoverImageType.localImage) {
preview = Image.file(
File(value),
fit: BoxFit.cover,
);
}
return Row(
children: [
FlowyText(LocaleKeys.pageStyle_image.tr()),
const Spacer(),
Container(
width: 40,
height: 28,
clipBehavior: Clip.hardEdge,
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(6.0)),
border: Border.all(color: const Color(0x1F222533)),
),
child: ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(5.0)),
child: preview,
),
),
],
);
}
void _showPresets(BuildContext context) {
final pageStyleBloc = context.read<DocumentPageStyleBloc>();
context.pop();
showMobileBottomSheet(
context,
showDragHandle: true,
@ -99,18 +181,18 @@ class PageStyleCoverImage extends StatelessWidget {
showHeader: true,
showRemoveButton: true,
onRemove: () {
context.read<DocumentPageStyleBloc>().add(
pageStyleBloc.add(
DocumentPageStyleEvent.updateCoverImage(
PageStyleCover.none(),
),
);
},
title: LocaleKeys.pageStyle_pageCover.tr(),
title: LocaleKeys.pageStyle_presets.tr(),
barrierColor: Colors.transparent,
backgroundColor: Theme.of(context).colorScheme.background,
builder: (_) {
return BlocProvider.value(
value: context.read<DocumentPageStyleBloc>(),
value: pageStyleBloc,
child: const PageCoverBottomSheet(),
);
},
@ -174,6 +256,9 @@ class PageStyleCoverImage extends StatelessWidget {
}
void _showUnsplash(BuildContext context) {
final pageStyleBloc = context.read<DocumentPageStyleBloc>();
context.pop();
showMobileBottomSheet(
context,
showDragHandle: true,
@ -181,11 +266,11 @@ class PageStyleCoverImage extends StatelessWidget {
showDoneButton: true,
showHeader: true,
showRemoveButton: true,
title: LocaleKeys.pageStyle_coverImage.tr(),
title: LocaleKeys.pageStyle_unsplash.tr(),
barrierColor: Colors.transparent,
backgroundColor: Theme.of(context).colorScheme.background,
onRemove: () {
context.read<DocumentPageStyleBloc>().add(
pageStyleBloc.add(
DocumentPageStyleEvent.updateCoverImage(
PageStyleCover.none(),
),
@ -204,7 +289,7 @@ class PageStyleCoverImage extends StatelessWidget {
child: UnsplashImageWidget(
type: UnsplashImageType.fullScreen,
onSelectUnsplashImage: (url) {
context.read<DocumentPageStyleBloc>().add(
pageStyleBloc.add(
DocumentPageStyleEvent.updateCoverImage(
PageStyleCover(
type: PageStyleCoverImageType.unsplashImage,
@ -308,6 +393,7 @@ class _CoverOptionButton extends StatelessWidget {
duration: Durations.medium1,
decoration: selected
? ShapeDecoration(
color: const Color(0x141AC3F2),
shape: RoundedRectangleBorder(
side: const BorderSide(
width: 1.50,

View File

@ -11,8 +11,9 @@ import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_emoji_mart/flutter_emoji_mart.dart';
import 'package:go_router/go_router.dart';
class PageStyleIcon extends StatelessWidget {
class PageStyleIcon extends StatefulWidget {
const PageStyleIcon({
super.key,
required this.view,
@ -20,10 +21,15 @@ class PageStyleIcon extends StatelessWidget {
final ViewPB view;
@override
State<PageStyleIcon> createState() => _PageStyleIconState();
}
class _PageStyleIconState extends State<PageStyleIcon> {
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => PageStyleIconBloc(view: view)
create: (_) => PageStyleIconBloc(view: widget.view)
..add(const PageStyleIconEvent.initial()),
child: BlocBuilder<PageStyleIconBloc, PageStyleIconState>(
builder: (context, state) {
@ -60,6 +66,10 @@ class PageStyleIcon extends StatelessWidget {
}
void _showIconSelector(BuildContext context, String selectedIcon) {
context.pop();
final pageStyleIconBloc = PageStyleIconBloc(view: widget.view)
..add(const PageStyleIconEvent.initial());
showMobileBottomSheet(
context,
showDragHandle: true,
@ -75,13 +85,13 @@ class PageStyleIcon extends StatelessWidget {
initialChildSize: 0.61,
showRemoveButton: true,
onRemove: () {
context.read<PageStyleIconBloc>().add(
pageStyleIconBloc.add(
const PageStyleIconEvent.updateIcon('', true),
);
},
scrollableWidgetBuilder: (_, controller) {
return BlocProvider.value(
value: context.read<PageStyleIconBloc>(),
value: pageStyleIconBloc,
child: Expanded(
child: Scrollbar(
controller: controller,
@ -112,6 +122,8 @@ class _IconSelectorState extends State<_IconSelector> {
EmojiData? emojiData;
List<String> availableEmojis = [];
PageStyleIconBloc? pageStyleIconBloc;
@override
void initState() {
super.initState();
@ -131,6 +143,14 @@ class _IconSelectorState extends State<_IconSelector> {
},
);
}
pageStyleIconBloc = context.read<PageStyleIconBloc>();
}
@override
void dispose() {
pageStyleIconBloc?.close();
super.dispose();
}
@override
@ -146,7 +166,7 @@ class _IconSelectorState extends State<_IconSelector> {
_buildSearchBar(context),
Expanded(
child: GridView.count(
crossAxisCount: _getEmojiPerLine(context),
crossAxisCount: 7,
controller: widget.scrollController,
children: [
for (final emoji in availableEmojis)
@ -165,27 +185,33 @@ class _IconSelectorState extends State<_IconSelector> {
String emoji,
String? selectedEmoji,
) {
Widget child = Center(
Widget child = SizedBox.square(
dimension: 24.0,
child: Center(
child: FlowyText.emoji(
emoji,
fontSize: 24,
),
),
);
if (emoji == selectedEmoji) {
child = Container(
margin: const EdgeInsets.all(8.0),
child = Center(
child: Container(
width: 40,
height: 40,
decoration: ShapeDecoration(
shape: RoundedRectangleBorder(
side: const BorderSide(
width: 1.50,
width: 1.40,
strokeAlign: BorderSide.strokeAlignOutside,
color: Color(0xFF00BCF0),
),
borderRadius: BorderRadius.circular(9),
borderRadius: BorderRadius.circular(10),
),
),
child: child,
),
);
}
@ -208,11 +234,6 @@ class _IconSelectorState extends State<_IconSelector> {
return availableEmojis;
}
int _getEmojiPerLine(BuildContext context) {
final width = MediaQuery.of(context).size.width;
return width ~/ 48.0; // the size of the emoji
}
Widget _buildSearchBar(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(

View File

@ -11,6 +11,7 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
const kPageStyleLayoutHeight = 52.0;
@ -117,6 +118,7 @@ class _OptionGroup<T> extends StatelessWidget {
duration: Durations.medium1,
decoration: selected
? ShapeDecoration(
color: const Color(0x141AC3F2),
shape: RoundedRectangleBorder(
side: const BorderSide(
width: 1.50,
@ -180,7 +182,10 @@ class _FontButton extends StatelessWidget {
const HSpace(16.0),
FlowyText(LocaleKeys.titleBar_font.tr()),
const Spacer(),
FlowyText(fontFamilyDisplayName),
FlowyText(
fontFamilyDisplayName,
color: context.pageStyleTextColor,
),
const HSpace(6.0),
const FlowySvg(FlowySvgs.m_page_style_arrow_right_s),
const HSpace(12.0),
@ -193,6 +198,9 @@ class _FontButton extends StatelessWidget {
}
void _showFontSelector(BuildContext context) {
final pageStyleBloc = context.read<DocumentPageStyleBloc>();
context.pop();
showMobileBottomSheet(
context,
showDragHandle: true,
@ -208,7 +216,7 @@ class _FontButton extends StatelessWidget {
initialChildSize: 0.61,
scrollableWidgetBuilder: (_, controller) {
return BlocProvider.value(
value: context.read<DocumentPageStyleBloc>(),
value: pageStyleBloc,
child: BlocBuilder<DocumentPageStyleBloc, DocumentPageStyleState>(
builder: (context, state) {
return Expanded(
@ -219,7 +227,7 @@ class _FontButton extends StatelessWidget {
selectedFontFamilyName:
state.fontFamily ?? defaultFontFamily,
onFontFamilySelected: (fontFamilyName) {
context.read<DocumentPageStyleBloc>().add(
pageStyleBloc.add(
DocumentPageStyleEvent.updateFontFamily(
fontFamilyName,
),

View File

@ -25,7 +25,7 @@ class PageStyleBottomSheet extends StatelessWidget {
children: [
// cover image
FlowyText(
LocaleKeys.pageStyle_backgroundImage.tr(),
LocaleKeys.pageStyle_coverImage.tr(),
color: context.pageStyleTextColor,
fontSize: 14.0,
),

View File

@ -15,7 +15,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 0.5.6
version: 0.5.7
environment:
flutter: ">=3.19.0"

View File

@ -1,5 +1,5 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="icon_left_outlined">
<path id="Union" d="M13.5774 1.9107C13.9028 2.23614 13.9028 2.76378 13.5774 3.08921L6.66667 9.99996L13.5774 16.9107C13.9028 17.2361 13.9028 17.7638 13.5774 18.0892C13.252 18.4147 12.7243 18.4147 12.3989 18.0892L5.48816 11.1785C4.83728 10.5276 4.83728 9.47232 5.48816 8.82145L12.3989 1.9107C12.7243 1.58527 13.252 1.58527 13.5774 1.9107Z" fill="#2B2F36"/>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g opacity="0.8">
<path d="M15.0796 19.9001L8.57604 13.3966C7.80799 12.6285 7.80799 11.3717 8.57604 10.6036L15.0796 4.1001" stroke="black" stroke-width="1.49621" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 491 B

After

Width:  |  Height:  |  Size: 342 B

View File

@ -1611,7 +1611,8 @@
"photoPermissionDescription": "Allow access to the photo library for uploading images.",
"openSettings": "Open Settings",
"photoPermissionTitle": "AppFlowy Would Like to Access Your Photo Library",
"doNotAllow": "Don't Allow"
"doNotAllow": "Don't Allow",
"image": "Image"
},
"commandPalette": {
"placeholder": "Type to search for views...",