fix(core): add favorite folder in menu, adjust empty-page new page button (#7730)

close AF-1150, AF-1128, AF-1131
- Replace favorite migration related copy
- Adjust empty page's "New Page" button
  ![CleanShot 2024-08-05 at 15.16.06@2x.png](https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/LakojjjzZNf6ogjOVwKE/1cf7d75a-a33a-4eec-9dc1-87d50d9526f1.png)
- Add toggle favorite to folder menu
  ![CleanShot 2024-08-05 at 15.17.50@2x.png](https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/LakojjjzZNf6ogjOVwKE/af6116b5-47d1-49a6-9660-41c0d7cd8fd3.png)
- Adjust `Button`
  - add `withoutHover` state
  - remove cursor: not-allowed when disabled
This commit is contained in:
CatsJuice
2024-08-05 09:15:16 +00:00
parent 73a6723d15
commit 6d253c0600
9 changed files with 121 additions and 29 deletions

View File

@@ -79,6 +79,10 @@ export const button = style({
lineHeight: lineHeightVar, lineHeight: lineHeightVar,
selectors: { selectors: {
// hover layer
'&[data-no-hover]:before, &[data-disabled]:before': {
display: 'none',
},
'&:hover:before': { opacity: 1 }, '&:hover:before': { opacity: 1 },
'&[data-block]': { display: 'flex' }, '&[data-block]': { display: 'flex' },
@@ -162,7 +166,6 @@ export const button = style({
// disabled // disabled
'&[data-disabled]': { '&[data-disabled]': {
cursor: 'not-allowed',
opacity: 0.5, opacity: 0.5,
}, },

View File

@@ -43,6 +43,9 @@ export interface ButtonProps
*/ */
loading?: boolean; loading?: boolean;
/** No hover state */
withoutHover?: boolean;
/** /**
* By default, it is considered as an icon with preset size and color, * By default, it is considered as an icon with preset size and color,
* can be overridden by `prefixClassName` and `prefixStyle`. * can be overridden by `prefixClassName` and `prefixStyle`.
@@ -103,6 +106,7 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
block, block,
loading, loading,
className, className,
withoutHover,
prefix, prefix,
prefixClassName, prefixClassName,
@@ -142,6 +146,7 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
data-disabled={disabled || undefined} data-disabled={disabled || undefined}
data-size={size} data-size={size}
data-variant={variant} data-variant={variant}
data-no-hover={withoutHover || undefined}
onClick={handleClick} onClick={handleClick}
> >
<IconSlot <IconSlot

View File

@@ -107,7 +107,8 @@ const NameWorkspaceContent = ({
confirmText={t['com.affine.nameWorkspace.button.create']()} confirmText={t['com.affine.nameWorkspace.button.create']()}
confirmButtonOptions={{ confirmButtonOptions={{
variant: 'primary', variant: 'primary',
disabled: !workspaceName || loading, loading,
disabled: !workspaceName,
['data-testid' as string]: 'create-workspace-create-button', ['data-testid' as string]: 'create-workspace-create-button',
}} }}
closeButtonOptions={{ closeButtonOptions={{

View File

@@ -19,6 +19,7 @@ import {
type FolderNode, type FolderNode,
OrganizeService, OrganizeService,
} from '@affine/core/modules/organize'; } from '@affine/core/modules/organize';
import { CompatibleFavoriteItemsAdapter } from '@affine/core/modules/properties';
import { WorkbenchService } from '@affine/core/modules/workbench'; import { WorkbenchService } from '@affine/core/modules/workbench';
import type { AffineDNDData } from '@affine/core/types/dnd'; import type { AffineDNDData } from '@affine/core/types/dnd';
import { Unreachable } from '@affine/env/constant'; import { Unreachable } from '@affine/env/constant';
@@ -43,6 +44,7 @@ import { ExplorerDocNode } from '../doc';
import { ExplorerTagNode } from '../tag'; import { ExplorerTagNode } from '../tag';
import type { GenericExplorerNode } from '../types'; import type { GenericExplorerNode } from '../types';
import { FolderEmpty } from './empty'; import { FolderEmpty } from './empty';
import { FavoriteFolderOperation } from './operations';
export const ExplorerFolderNode = ({ export const ExplorerFolderNode = ({
nodeId, nodeId,
@@ -164,6 +166,7 @@ export const ExplorerFolderNodeFolder = ({
const { docsService, workbenchService } = useServices({ const { docsService, workbenchService } = useServices({
DocsService, DocsService,
WorkbenchService, WorkbenchService,
CompatibleFavoriteItemsAdapter,
}); });
const openDocsSelector = useSelectDoc(); const openDocsSelector = useSelectDoc();
const openTagsSelector = useSelectTag(); const openTagsSelector = useSelectTag();
@@ -710,6 +713,17 @@ export const ExplorerFolderNodeFolder = ({
</MenuSub> </MenuSub>
), ),
}, },
// favorite, only new favorite available
...(runtimeConfig.enableNewFavorite && node.id
? [
{
index: 200,
view: <FavoriteFolderOperation id={node.id} />,
},
]
: []),
{ {
index: 9999, index: 9999,
view: <MenuSeparator key="menu-separator" />, view: <MenuSeparator key="menu-separator" />,
@@ -731,7 +745,14 @@ export const ExplorerFolderNodeFolder = ({
), ),
}, },
]; ];
}, [handleAddToFolder, handleCreateSubfolder, handleDelete, handleNewDoc, t]); }, [
handleAddToFolder,
handleCreateSubfolder,
handleDelete,
handleNewDoc,
node.id,
t,
]);
const finalOperations = useMemo(() => { const finalOperations = useMemo(() => {
if (additionalOperations) { if (additionalOperations) {

View File

@@ -0,0 +1,39 @@
import { MenuIcon, MenuItem } from '@affine/component';
import { CompatibleFavoriteItemsAdapter } from '@affine/core/modules/properties';
import { useI18n } from '@affine/i18n';
import { FavoritedIcon, FavoriteIcon } from '@blocksuite/icons/rc';
import { useLiveData, useService } from '@toeverything/infra';
import { cssVar } from '@toeverything/theme';
import { useMemo } from 'react';
export const FavoriteFolderOperation = ({ id }: { id: string }) => {
const t = useI18n();
const compatibleFavoriteItemsAdapter = useService(
CompatibleFavoriteItemsAdapter
);
const favorite = useLiveData(
useMemo(() => {
return compatibleFavoriteItemsAdapter.isFavorite$(id, 'folder');
}, [compatibleFavoriteItemsAdapter, id])
);
return (
<MenuItem
preFix={
<MenuIcon>
{favorite ? (
<FavoritedIcon style={{ color: cssVar('primaryColor') }} />
) : (
<FavoriteIcon />
)}
</MenuIcon>
}
onClick={() => compatibleFavoriteItemsAdapter.toggle(id, 'folder')}
>
{favorite
? t['com.affine.rootAppSidebar.organize.folder-rm-favorite']()
: t['com.affine.rootAppSidebar.organize.folder-add-favorite']()}
</MenuItem>
);
};

View File

@@ -7,7 +7,7 @@ import { LiveData, Service } from '@toeverything/infra';
import { defaultsDeep } from 'lodash-es'; import { defaultsDeep } from 'lodash-es';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import type { FavoriteService } from '../../favorite'; import type { FavoriteService, FavoriteSupportType } from '../../favorite';
import { import {
PagePropertyType, PagePropertyType,
PageSystemPropertyId, PageSystemPropertyId,
@@ -305,6 +305,10 @@ export class FavoriteItemsAdapter extends Service {
} }
} }
type CompatibleFavoriteSupportType =
| WorkspaceFavoriteItem['type']
| FavoriteSupportType;
/** /**
* A service written for compatibility,with the same API as FavoriteItemsAdapter. * A service written for compatibility,with the same API as FavoriteItemsAdapter.
* When `runtimeConfig.enableNewFavorite` is false, it operates FavoriteItemsAdapter, * When `runtimeConfig.enableNewFavorite` is false, it operates FavoriteItemsAdapter,
@@ -318,24 +322,29 @@ export class CompatibleFavoriteItemsAdapter extends Service {
super(); super();
} }
toggle(id: string, type: WorkspaceFavoriteItem['type']) { // old adapter only supports doc and collection
if (runtimeConfig.enableNewFavorite) { private isNewType(type: CompatibleFavoriteSupportType) {
return type !== 'doc' && type !== 'collection';
}
toggle(id: string, type: CompatibleFavoriteSupportType) {
if (runtimeConfig.enableNewFavorite || this.isNewType(type)) {
this.favoriteService.favoriteList.toggle(type, id); this.favoriteService.favoriteList.toggle(type, id);
} else { } else {
this.favoriteItemsAdapter.toggle(id, type); this.favoriteItemsAdapter.toggle(id, type);
} }
} }
isFavorite$(id: string, type: WorkspaceFavoriteItem['type']) { isFavorite$(id: string, type: CompatibleFavoriteSupportType) {
if (runtimeConfig.enableNewFavorite) { if (runtimeConfig.enableNewFavorite || this.isNewType(type)) {
return this.favoriteService.favoriteList.isFavorite$(type, id); return this.favoriteService.favoriteList.isFavorite$(type, id);
} else { } else {
return this.favoriteItemsAdapter.isFavorite$(id, type); return this.favoriteItemsAdapter.isFavorite$(id, type);
} }
} }
isFavorite(id: string, type: WorkspaceFavoriteItem['type']) { isFavorite(id: string, type: CompatibleFavoriteSupportType) {
if (runtimeConfig.enableNewFavorite) { if (runtimeConfig.enableNewFavorite || this.isNewType(type)) {
return this.favoriteService.favoriteList.isFavorite$(type, id).value; return this.favoriteService.favoriteList.isFavorite$(type, id).value;
} else { } else {
return this.favoriteItemsAdapter.isFavorite(id, type); return this.favoriteItemsAdapter.isFavorite(id, type);

View File

@@ -1,4 +1,5 @@
import { cssVar } from '@toeverything/theme'; import { cssVar } from '@toeverything/theme';
import { cssVarV2 } from '@toeverything/theme/v2';
import { style } from '@vanilla-extract/css'; import { style } from '@vanilla-extract/css';
export const pageListEmptyStyle = style({ export const pageListEmptyStyle = style({
height: 'calc(100% - 52px)', height: 'calc(100% - 52px)',
@@ -23,3 +24,15 @@ export const emptyDescKbd = style([
cursor: 'text', cursor: 'text',
}, },
]); ]);
export const plusButton = style({
borderWidth: 1,
borderColor: cssVarV2('layer/border'),
boxShadow: 'none',
cursor: 'default',
});
export const descWrapper = style({
display: 'flex',
alignItems: 'center',
gap: 8,
});

View File

@@ -1,33 +1,30 @@
import { Empty } from '@affine/component'; import { Empty, IconButton } from '@affine/component';
import { Trans, useI18n } from '@affine/i18n'; import { Trans, useI18n } from '@affine/i18n';
import { PlusIcon } from '@blocksuite/icons/rc';
import type { DocCollection } from '@blocksuite/store'; import type { DocCollection } from '@blocksuite/store';
import type { ReactNode } from 'react'; import type { ReactNode } from 'react';
import { useCallback } from 'react';
import { usePageHelper } from '../../components/blocksuite/block-suite-page-list/utils';
import * as styles from './page-list-empty.css'; import * as styles from './page-list-empty.css';
export const EmptyPageList = ({ export const EmptyPageList = ({
type, type,
docCollection,
heading, heading,
}: { }: {
type: 'all' | 'trash' | 'shared' | 'public'; type: 'all' | 'trash' | 'shared' | 'public';
docCollection: DocCollection; docCollection: DocCollection;
heading?: ReactNode; heading?: ReactNode;
}) => { }) => {
const { createPage } = usePageHelper(docCollection);
const t = useI18n(); const t = useI18n();
const onCreatePage = useCallback(() => {
createPage?.();
}, [createPage]);
const getEmptyDescription = () => { const getEmptyDescription = () => {
if (type === 'all') { if (type === 'all') {
const createNewPageButton = ( const createNewPageButton = (
<button className={styles.emptyDescButton} onClick={onCreatePage}> <IconButton
{t['New Page']()} withoutHover
</button> className={styles.plusButton}
variant="solid"
icon={<PlusIcon />}
/>
); );
if (environment.isDesktop) { if (environment.isDesktop) {
const shortcut = environment.isMacOs ? '⌘ + N' : 'Ctrl + N'; const shortcut = environment.isMacOs ? '⌘ + N' : 'Ctrl + N';
@@ -61,7 +58,9 @@ export const EmptyPageList = ({
{heading && <div>{heading}</div>} {heading && <div>{heading}</div>}
<Empty <Empty
title={t['com.affine.emptyDesc']()} title={t['com.affine.emptyDesc']()}
description={getEmptyDescription()} description={
<span className={styles.descWrapper}>{getEmptyDescription()}</span>
}
/> />
</div> </div>
); );

View File

@@ -1118,12 +1118,12 @@
"com.affine.rootAppSidebar.collections": "Collections", "com.affine.rootAppSidebar.collections": "Collections",
"com.affine.rootAppSidebar.favorites": "Favourites", "com.affine.rootAppSidebar.favorites": "Favourites",
"com.affine.rootAppSidebar.migration-data": "Migration data", "com.affine.rootAppSidebar.migration-data": "Migration data",
"com.affine.rootAppSidebar.migration-data.help": "Migration your favorites data", "com.affine.rootAppSidebar.migration-data.help": "The old \"Favorites\" will be replaced",
"com.affine.rootAppSidebar.migration-data.help.description": "This is the old Favourites data. Previously, Favourites were shared during collaboration. We have redesigned the Favourites feature, which requires you to take certain actions. You can drag this content or directly hide and delete this part of the data.", "com.affine.rootAppSidebar.migration-data.help.description": "Your documents are safe, yet you may need to re-pin your most-used ones. Previously, 'Favorites' were shared across the workspace. We've improved on this - now each person has a personal 'Favorites' section for their top documents, collections, and folders. We advise migrating your data.\nThe old 'Favorites' will disappear once emptied. Drag your items with ease from the old shared 'Favorites' into your new personal section, or opt to delete all old favorites by simply clicking 'Delete all the old data' now.",
"com.affine.rootAppSidebar.migration-data.help.clean-all": "Clear all migration data", "com.affine.rootAppSidebar.migration-data.help.clean-all": "Delete all the old data",
"com.affine.rootAppSidebar.migration-data.help.confirm": "OK", "com.affine.rootAppSidebar.migration-data.help.confirm": "OK",
"com.affine.rootAppSidebar.migration-data.clean-all": "Clean all migration data", "com.affine.rootAppSidebar.migration-data.clean-all": "Delete All Old Data",
"com.affine.rootAppSidebar.migration-data.clean-all.description": "This will delete all Migration data, don't worry, <b>it won't delete any data entities</b>, it will just clean up the content on the sidebar. Once the content is cleared, the sidebar will no longer display this section until the next occurrence of conflicting data.", "com.affine.rootAppSidebar.migration-data.clean-all.description": "This action will empty and delete the entire discontinued Favorites section. Don't worry, <b>all your documents will not be affected - this only removes the old Favorites from the sidebar to place the new one</b>. In the meantime, please verify that you've moved all your frequently accessed documents to the spanking new personal Favorites section first.",
"com.affine.rootAppSidebar.migration-data.clean-all.confirm": "OK", "com.affine.rootAppSidebar.migration-data.clean-all.confirm": "OK",
"com.affine.rootAppSidebar.migration-data.clean-all.cancel": "Cancel", "com.affine.rootAppSidebar.migration-data.clean-all.cancel": "Cancel",
"com.affine.rootAppSidebar.organize": "Organize", "com.affine.rootAppSidebar.organize": "Organize",
@@ -1140,6 +1140,8 @@
"com.affine.rootAppSidebar.organize.delete": "Delete", "com.affine.rootAppSidebar.organize.delete": "Delete",
"com.affine.rootAppSidebar.organize.delete-from-folder": "Remove from folder", "com.affine.rootAppSidebar.organize.delete-from-folder": "Remove from folder",
"com.affine.rootAppSidebar.organize.root-folder-only": "Only folder can be placed on here", "com.affine.rootAppSidebar.organize.root-folder-only": "Only folder can be placed on here",
"com.affine.rootAppSidebar.organize.folder-add-favorite": "Add to favorites",
"com.affine.rootAppSidebar.organize.folder-rm-favorite": "Remove from favorites",
"com.affine.rootAppSidebar.tags": "Tags", "com.affine.rootAppSidebar.tags": "Tags",
"com.affine.rootAppSidebar.tags.empty": "No Tags", "com.affine.rootAppSidebar.tags.empty": "No Tags",
"com.affine.rootAppSidebar.tags.new-tag": "New Tag", "com.affine.rootAppSidebar.tags.new-tag": "New Tag",
@@ -1393,8 +1395,8 @@
"com.affine.syncing": "Syncing", "com.affine.syncing": "Syncing",
"core": "core", "core": "core",
"dark": "Dark", "dark": "Dark",
"emptyAllPages": "Click on the <1>$t(New Doc)</1> button to create your first doc.", "emptyAllPages": "Click on the <1></1> button to create your first doc.",
"emptyAllPagesClient": "Click on the <1>$t(New Doc)</1> button Or press <3>{{shortcut}}</3> to create your first doc.", "emptyAllPagesClient": "Click on the <1></1> button Or press <3>{{shortcut}}</3> to create your first doc.",
"emptyFavorite": "Click Add to Favourites and the doc will appear here.", "emptyFavorite": "Click Add to Favourites and the doc will appear here.",
"emptySharedPages": "Shared docs will appear here.", "emptySharedPages": "Shared docs will appear here.",
"emptyTrash": "Click Add to Trash and the doc will appear here.", "emptyTrash": "Click Add to Trash and the doc will appear here.",