mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-15 05:37:32 +00:00
Compare commits
2 Commits
v0.26.3-be
...
eyhn/adjus
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
68b57cdee7 | ||
|
|
afa108d517 |
@@ -31,6 +31,23 @@ import {
|
|||||||
export interface MasonryProps extends React.HTMLAttributes<HTMLDivElement> {
|
export interface MasonryProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||||
items: MasonryItem[] | MasonryGroup[];
|
items: MasonryItem[] | MasonryGroup[];
|
||||||
|
|
||||||
|
itemComponent: React.ComponentType<{
|
||||||
|
groupId: string;
|
||||||
|
itemId: string;
|
||||||
|
}>;
|
||||||
|
groupComponent?: React.ComponentType<{
|
||||||
|
groupId: string;
|
||||||
|
itemCount: number;
|
||||||
|
collapsed: boolean;
|
||||||
|
onCollapse: (collapsed: boolean) => void;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
groupHeight?: number | ((group: MasonryGroup) => number);
|
||||||
|
itemHeight: number | ((item: MasonryItem) => number);
|
||||||
|
|
||||||
|
groupClassName?: string;
|
||||||
|
itemClassName?: string;
|
||||||
|
|
||||||
gapX?: number;
|
gapX?: number;
|
||||||
gapY?: number;
|
gapY?: number;
|
||||||
paddingX?: MasonryPX;
|
paddingX?: MasonryPX;
|
||||||
@@ -39,8 +56,7 @@ export interface MasonryProps extends React.HTMLAttributes<HTMLDivElement> {
|
|||||||
groupsGap?: number;
|
groupsGap?: number;
|
||||||
groupHeaderGapWithItems?: number;
|
groupHeaderGapWithItems?: number;
|
||||||
stickyGroupHeader?: boolean;
|
stickyGroupHeader?: boolean;
|
||||||
collapsedGroups?: string[];
|
|
||||||
onGroupCollapse?: (groupId: string, collapsed: boolean) => void;
|
|
||||||
/**
|
/**
|
||||||
* Specify the width of the item.
|
* Specify the width of the item.
|
||||||
* - `number`: The width of the item in pixels.
|
* - `number`: The width of the item in pixels.
|
||||||
@@ -61,6 +77,9 @@ export interface MasonryProps extends React.HTMLAttributes<HTMLDivElement> {
|
|||||||
columns?: number;
|
columns?: number;
|
||||||
resizeDebounce?: number;
|
resizeDebounce?: number;
|
||||||
preloadHeight?: number;
|
preloadHeight?: number;
|
||||||
|
|
||||||
|
itemSelected?: string[];
|
||||||
|
onItemSelectedChanged?: (selected: string[]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Masonry = ({
|
export const Masonry = ({
|
||||||
@@ -77,11 +96,15 @@ export const Masonry = ({
|
|||||||
groupsGap = 0,
|
groupsGap = 0,
|
||||||
groupHeaderGapWithItems = 0,
|
groupHeaderGapWithItems = 0,
|
||||||
stickyGroupHeader = true,
|
stickyGroupHeader = true,
|
||||||
collapsedGroups,
|
|
||||||
columns,
|
columns,
|
||||||
preloadHeight = 50,
|
preloadHeight = 50,
|
||||||
resizeDebounce = 20,
|
resizeDebounce = 20,
|
||||||
onGroupCollapse,
|
groupComponent: GroupComponent,
|
||||||
|
itemComponent: ItemComponent,
|
||||||
|
groupClassName,
|
||||||
|
itemClassName,
|
||||||
|
itemHeight,
|
||||||
|
groupHeight,
|
||||||
...props
|
...props
|
||||||
}: MasonryProps) => {
|
}: MasonryProps) => {
|
||||||
const rootRef = useRef<HTMLDivElement>(null);
|
const rootRef = useRef<HTMLDivElement>(null);
|
||||||
@@ -99,9 +122,17 @@ export const Masonry = ({
|
|||||||
undefined
|
undefined
|
||||||
);
|
);
|
||||||
const [totalWidth, setTotalWidth] = useState(0);
|
const [totalWidth, setTotalWidth] = useState(0);
|
||||||
|
|
||||||
|
const [collapsedGroups, setCollapsedGroups] = useState<string[]>([]);
|
||||||
|
const onGroupCollapse = useCallback((groupId: string, collapsed: boolean) => {
|
||||||
|
setCollapsedGroups(prev =>
|
||||||
|
collapsed ? [...prev, groupId] : prev.filter(id => id !== groupId)
|
||||||
|
);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const stickyGroupCollapsed = !!(
|
const stickyGroupCollapsed = !!(
|
||||||
collapsedGroups &&
|
collapsedGroups &&
|
||||||
stickyGroupId &&
|
stickyGroupId !== undefined &&
|
||||||
collapsedGroups.includes(stickyGroupId)
|
collapsedGroups.includes(stickyGroupId)
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -114,7 +145,7 @@ export const Masonry = ({
|
|||||||
}, [items]);
|
}, [items]);
|
||||||
|
|
||||||
const stickyGroup = useMemo(() => {
|
const stickyGroup = useMemo(() => {
|
||||||
if (!stickyGroupId) return undefined;
|
if (stickyGroupId === undefined) return undefined;
|
||||||
return groups.find(group => group.id === stickyGroupId);
|
return groups.find(group => group.id === stickyGroupId);
|
||||||
}, [groups, stickyGroupId]);
|
}, [groups, stickyGroupId]);
|
||||||
|
|
||||||
@@ -164,6 +195,8 @@ export const Masonry = ({
|
|||||||
groupsGap,
|
groupsGap,
|
||||||
groupHeaderGapWithItems,
|
groupHeaderGapWithItems,
|
||||||
collapsedGroups: collapsedGroups ?? [],
|
collapsedGroups: collapsedGroups ?? [],
|
||||||
|
groupHeight: groupHeight ?? 0,
|
||||||
|
itemHeight,
|
||||||
});
|
});
|
||||||
setLayoutMap(layout);
|
setLayoutMap(layout);
|
||||||
setHeight(height);
|
setHeight(height);
|
||||||
@@ -180,8 +213,10 @@ export const Masonry = ({
|
|||||||
gapX,
|
gapX,
|
||||||
gapY,
|
gapY,
|
||||||
groupHeaderGapWithItems,
|
groupHeaderGapWithItems,
|
||||||
|
groupHeight,
|
||||||
groups,
|
groups,
|
||||||
groupsGap,
|
groupsGap,
|
||||||
|
itemHeight,
|
||||||
itemWidth,
|
itemWidth,
|
||||||
itemWidthMin,
|
itemWidthMin,
|
||||||
paddingX,
|
paddingX,
|
||||||
@@ -233,13 +268,7 @@ export const Masonry = ({
|
|||||||
>
|
>
|
||||||
{groups.map(group => {
|
{groups.map(group => {
|
||||||
// sleep is not calculated, do not render
|
// sleep is not calculated, do not render
|
||||||
const {
|
const { id: groupId, items, ...groupProps } = group;
|
||||||
id: groupId,
|
|
||||||
items,
|
|
||||||
className,
|
|
||||||
Component,
|
|
||||||
...groupProps
|
|
||||||
} = group;
|
|
||||||
const collapsed =
|
const collapsed =
|
||||||
collapsedGroups && collapsedGroups.includes(groupId);
|
collapsedGroups && collapsedGroups.includes(groupId);
|
||||||
|
|
||||||
@@ -248,14 +277,16 @@ export const Masonry = ({
|
|||||||
{/* group header */}
|
{/* group header */}
|
||||||
{virtualScroll && !activeMap.get(group.id) ? null : (
|
{virtualScroll && !activeMap.get(group.id) ? null : (
|
||||||
<MasonryGroupHeader
|
<MasonryGroupHeader
|
||||||
className={clsx(styles.groupHeader, className)}
|
className={clsx(styles.groupHeader, groupClassName)}
|
||||||
key={`header-${groupId}`}
|
key={`header-${groupId}`}
|
||||||
id={groupId}
|
id={groupId}
|
||||||
locateMode={locateMode}
|
locateMode={locateMode}
|
||||||
xywh={layoutMap.get(groupId)}
|
xywh={layoutMap.get(groupId)}
|
||||||
{...groupProps}
|
{...groupProps}
|
||||||
onClick={() => onGroupCollapse?.(groupId, !collapsed)}
|
onCollapse={collapsed =>
|
||||||
Component={Component}
|
onGroupCollapse?.(groupId, collapsed)
|
||||||
|
}
|
||||||
|
Component={GroupComponent}
|
||||||
itemCount={items.length}
|
itemCount={items.length}
|
||||||
collapsed={!!collapsed}
|
collapsed={!!collapsed}
|
||||||
groupId={groupId}
|
groupId={groupId}
|
||||||
@@ -265,19 +296,20 @@ export const Masonry = ({
|
|||||||
{/* group items */}
|
{/* group items */}
|
||||||
{collapsed
|
{collapsed
|
||||||
? null
|
? null
|
||||||
: items.map(({ id: itemId, Component, ...item }) => {
|
: items.map(item => {
|
||||||
|
const itemId = item.id;
|
||||||
const mixId = groupId ? `${groupId}:${itemId}` : itemId;
|
const mixId = groupId ? `${groupId}:${itemId}` : itemId;
|
||||||
if (virtualScroll && !activeMap.get(mixId)) return null;
|
if (virtualScroll && !activeMap.get(mixId)) return null;
|
||||||
return (
|
return (
|
||||||
<MasonryGroupItem
|
<MasonryGroupItem
|
||||||
key={mixId}
|
key={mixId}
|
||||||
id={mixId}
|
id={mixId}
|
||||||
{...item}
|
|
||||||
locateMode={locateMode}
|
locateMode={locateMode}
|
||||||
xywh={layoutMap.get(mixId)}
|
xywh={layoutMap.get(mixId)}
|
||||||
groupId={groupId}
|
groupId={groupId}
|
||||||
itemId={itemId}
|
itemId={itemId}
|
||||||
Component={Component}
|
className={itemClassName}
|
||||||
|
Component={ItemComponent}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@@ -289,24 +321,24 @@ export const Masonry = ({
|
|||||||
<Scrollable.Scrollbar />
|
<Scrollable.Scrollbar />
|
||||||
{stickyGroup ? (
|
{stickyGroup ? (
|
||||||
<div
|
<div
|
||||||
className={clsx(styles.stickyGroupHeader, stickyGroup.className)}
|
className={clsx(styles.stickyGroupHeader, groupClassName)}
|
||||||
style={{
|
style={{
|
||||||
padding: `0 ${calcPX(paddingX, totalWidth)}px`,
|
padding: `0 ${calcPX(paddingX, totalWidth)}px`,
|
||||||
height: stickyGroup.height,
|
height:
|
||||||
...stickyGroup.style,
|
typeof groupHeight === 'function'
|
||||||
|
? groupHeight(stickyGroup)
|
||||||
|
: groupHeight,
|
||||||
}}
|
}}
|
||||||
onClick={() =>
|
|
||||||
onGroupCollapse?.(stickyGroup.id, !stickyGroupCollapsed)
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
{stickyGroup.Component ? (
|
{GroupComponent && (
|
||||||
<stickyGroup.Component
|
<GroupComponent
|
||||||
groupId={stickyGroup.id}
|
groupId={stickyGroup.id}
|
||||||
itemCount={stickyGroup.items.length}
|
itemCount={stickyGroup.items.length}
|
||||||
collapsed={stickyGroupCollapsed}
|
collapsed={stickyGroupCollapsed}
|
||||||
|
onCollapse={collapsed => {
|
||||||
|
onGroupCollapse(stickyGroup.id, collapsed);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
) : (
|
|
||||||
stickyGroup.children
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
@@ -330,11 +362,12 @@ const MasonryGroupHeader = memo(function MasonryGroupHeader({
|
|||||||
groupId,
|
groupId,
|
||||||
itemCount,
|
itemCount,
|
||||||
collapsed,
|
collapsed,
|
||||||
height,
|
|
||||||
paddingX,
|
paddingX,
|
||||||
|
onCollapse,
|
||||||
...props
|
...props
|
||||||
}: Omit<MasonryItemProps, 'Component'> & {
|
}: Omit<MasonryItemProps, 'Component'> & {
|
||||||
Component?: MasonryGroup['Component'];
|
Component?: MasonryProps['groupComponent'];
|
||||||
|
onCollapse: (collapsed: boolean) => void;
|
||||||
groupId: string;
|
groupId: string;
|
||||||
itemCount: number;
|
itemCount: number;
|
||||||
collapsed: boolean;
|
collapsed: boolean;
|
||||||
@@ -347,16 +380,16 @@ const MasonryGroupHeader = memo(function MasonryGroupHeader({
|
|||||||
groupId={groupId}
|
groupId={groupId}
|
||||||
itemCount={itemCount}
|
itemCount={itemCount}
|
||||||
collapsed={collapsed}
|
collapsed={collapsed}
|
||||||
|
onCollapse={onCollapse}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return children;
|
return children;
|
||||||
}, [Component, children, collapsed, groupId, itemCount]);
|
}, [Component, children, collapsed, groupId, itemCount, onCollapse]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MasonryItem
|
<MasonryItem
|
||||||
id={id}
|
id={id}
|
||||||
height={height}
|
|
||||||
style={{
|
style={{
|
||||||
padding: `0 ${paddingX}px`,
|
padding: `0 ${paddingX}px`,
|
||||||
height: '100%',
|
height: '100%',
|
||||||
@@ -381,6 +414,7 @@ const MasonryGroupItem = memo(function MasonryGroupItem({
|
|||||||
}: MasonryItemProps & {
|
}: MasonryItemProps & {
|
||||||
groupId: string;
|
groupId: string;
|
||||||
itemId: string;
|
itemId: string;
|
||||||
|
Component?: MasonryProps['itemComponent'];
|
||||||
}) {
|
}) {
|
||||||
const content = useMemo(() => {
|
const content = useMemo(() => {
|
||||||
if (Component) {
|
if (Component) {
|
||||||
|
|||||||
@@ -1,18 +1,10 @@
|
|||||||
export interface MasonryItem extends React.HTMLAttributes<HTMLDivElement> {
|
export interface MasonryItem {
|
||||||
id: string;
|
id: string;
|
||||||
height: number;
|
|
||||||
Component?: React.ComponentType<{ groupId: string; itemId: string }>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MasonryGroup extends React.HTMLAttributes<HTMLDivElement> {
|
export interface MasonryGroup {
|
||||||
id: string;
|
id: string;
|
||||||
height: number;
|
|
||||||
items: MasonryItem[];
|
items: MasonryItem[];
|
||||||
Component?: React.ComponentType<{
|
|
||||||
groupId: string;
|
|
||||||
collapsed?: boolean;
|
|
||||||
itemCount: number;
|
|
||||||
}>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MasonryItemXYWH {
|
export interface MasonryItemXYWH {
|
||||||
|
|||||||
@@ -61,6 +61,8 @@ export const calcLayout = (
|
|||||||
groupsGap: number;
|
groupsGap: number;
|
||||||
groupHeaderGapWithItems: number;
|
groupHeaderGapWithItems: number;
|
||||||
collapsedGroups: string[];
|
collapsedGroups: string[];
|
||||||
|
groupHeight: number | ((group: MasonryGroup) => number);
|
||||||
|
itemHeight: number | ((item: MasonryItem) => number);
|
||||||
}
|
}
|
||||||
) => {
|
) => {
|
||||||
const {
|
const {
|
||||||
@@ -74,6 +76,8 @@ export const calcLayout = (
|
|||||||
groupsGap,
|
groupsGap,
|
||||||
groupHeaderGapWithItems,
|
groupHeaderGapWithItems,
|
||||||
collapsedGroups,
|
collapsedGroups,
|
||||||
|
groupHeight,
|
||||||
|
itemHeight,
|
||||||
} = options;
|
} = options;
|
||||||
const paddingX = calcPX(_paddingX, totalWidth);
|
const paddingX = calcPX(_paddingX, totalWidth);
|
||||||
|
|
||||||
@@ -92,7 +96,7 @@ export const calcLayout = (
|
|||||||
x: 0,
|
x: 0,
|
||||||
y: finalHeight,
|
y: finalHeight,
|
||||||
w: totalWidth,
|
w: totalWidth,
|
||||||
h: group.height,
|
h: typeof groupHeight === 'function' ? groupHeight(group) : groupHeight,
|
||||||
};
|
};
|
||||||
layout.set(group.id, groupHeaderLayout);
|
layout.set(group.id, groupHeaderLayout);
|
||||||
|
|
||||||
@@ -110,19 +114,21 @@ export const calcLayout = (
|
|||||||
const hasGap = heightStack[minHeightIndex] ? gapY : 0;
|
const hasGap = heightStack[minHeightIndex] ? gapY : 0;
|
||||||
const x = minHeightIndex * (width + gapX) + paddingX;
|
const x = minHeightIndex * (width + gapX) + paddingX;
|
||||||
const y = finalHeight + minHeight + hasGap;
|
const y = finalHeight + minHeight + hasGap;
|
||||||
|
const height =
|
||||||
|
typeof itemHeight === 'function' ? itemHeight(item) : itemHeight;
|
||||||
|
|
||||||
heightStack[minHeightIndex] += item.height + hasGap;
|
heightStack[minHeightIndex] += height + hasGap;
|
||||||
layout.set(itemId, {
|
layout.set(itemId, {
|
||||||
type: 'item',
|
type: 'item',
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
w: width,
|
w: width,
|
||||||
h: item.height,
|
h: height,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const groupHeight = Math.max(...heightStack) + paddingY;
|
const height = Math.max(...heightStack) + paddingY;
|
||||||
finalHeight += groupHeight;
|
finalHeight += height;
|
||||||
});
|
});
|
||||||
|
|
||||||
return { layout, height: finalHeight };
|
return { layout, height: finalHeight };
|
||||||
@@ -167,9 +173,9 @@ export const calcSticky = (options: {
|
|||||||
return xywh.y < scrollY && (!next || next[1].y > scrollY);
|
return xywh.y < scrollY && (!next || next[1].y > scrollY);
|
||||||
});
|
});
|
||||||
|
|
||||||
return stickyGroupEntry
|
return stickyGroupEntry !== undefined
|
||||||
? stickyGroupEntry[0]
|
? stickyGroupEntry[0]
|
||||||
: groupEntries.length > 0
|
: groupEntries.length > 0
|
||||||
? groupEntries[0][0]
|
? groupEntries[0][0]
|
||||||
: '';
|
: undefined;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import type { ExplorerPreference } from './types';
|
|||||||
export type DocExplorerContextType = {
|
export type DocExplorerContextType = {
|
||||||
view$: LiveData<DocListItemView>;
|
view$: LiveData<DocListItemView>;
|
||||||
groups$: LiveData<Array<{ key: string; items: string[] }>>;
|
groups$: LiveData<Array<{ key: string; items: string[] }>>;
|
||||||
collapsedGroups$: LiveData<string[]>;
|
|
||||||
selectMode$?: LiveData<boolean>;
|
selectMode$?: LiveData<boolean>;
|
||||||
selectedDocIds$: LiveData<string[]>;
|
selectedDocIds$: LiveData<string[]>;
|
||||||
prevCheckAnchorId$?: LiveData<string | null>;
|
prevCheckAnchorId$?: LiveData<string | null>;
|
||||||
@@ -25,7 +24,6 @@ export const createDocExplorerContext = () =>
|
|||||||
({
|
({
|
||||||
view$: new LiveData<DocListItemView>('list'),
|
view$: new LiveData<DocListItemView>('list'),
|
||||||
groups$: new LiveData<Array<{ key: string; items: string[] }>>([]),
|
groups$: new LiveData<Array<{ key: string; items: string[] }>>([]),
|
||||||
collapsedGroups$: new LiveData<string[]>([]),
|
|
||||||
selectMode$: new LiveData<boolean>(false),
|
selectMode$: new LiveData<boolean>(false),
|
||||||
selectedDocIds$: new LiveData<string[]>([]),
|
selectedDocIds$: new LiveData<string[]>([]),
|
||||||
prevCheckAnchorId$: new LiveData<string | null>(null),
|
prevCheckAnchorId$: new LiveData<string | null>(null),
|
||||||
|
|||||||
@@ -16,16 +16,19 @@ import * as styles from './group-header.css';
|
|||||||
export const DocGroupHeader = ({
|
export const DocGroupHeader = ({
|
||||||
className,
|
className,
|
||||||
groupId,
|
groupId,
|
||||||
|
collapsed,
|
||||||
|
onCollapse,
|
||||||
...props
|
...props
|
||||||
}: HTMLAttributes<HTMLDivElement> & {
|
}: HTMLAttributes<HTMLDivElement> & {
|
||||||
groupId: string;
|
groupId: string;
|
||||||
|
collapsed: boolean;
|
||||||
|
onCollapse: (collapsed: boolean) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
const contextValue = useContext(DocExplorerContext);
|
const contextValue = useContext(DocExplorerContext);
|
||||||
|
|
||||||
const groups = useLiveData(contextValue.groups$);
|
const groups = useLiveData(contextValue.groups$);
|
||||||
const selectedDocIds = useLiveData(contextValue.selectedDocIds$);
|
const selectedDocIds = useLiveData(contextValue.selectedDocIds$);
|
||||||
const collapsedGroups = useLiveData(contextValue.collapsedGroups$);
|
|
||||||
const selectMode = useLiveData(contextValue.selectMode$);
|
const selectMode = useLiveData(contextValue.selectMode$);
|
||||||
|
|
||||||
const group = groups.find(g => g.key === groupId);
|
const group = groups.find(g => g.key === groupId);
|
||||||
@@ -34,13 +37,8 @@ export const DocGroupHeader = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const handleToggleCollapse = useCallback(() => {
|
const handleToggleCollapse = useCallback(() => {
|
||||||
const prev = contextValue.collapsedGroups$.value;
|
onCollapse(!collapsed);
|
||||||
contextValue.collapsedGroups$.next(
|
}, [collapsed, onCollapse]);
|
||||||
prev.includes(groupId)
|
|
||||||
? prev.filter(id => id !== groupId)
|
|
||||||
: [...prev, groupId]
|
|
||||||
);
|
|
||||||
}, [groupId, contextValue]);
|
|
||||||
|
|
||||||
const handleSelectAll = useCallback(() => {
|
const handleSelectAll = useCallback(() => {
|
||||||
const prev = contextValue.selectedDocIds$.value;
|
const prev = contextValue.selectedDocIds$.value;
|
||||||
@@ -64,10 +62,7 @@ export const DocGroupHeader = ({
|
|||||||
).length;
|
).length;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div className={styles.groupHeader} data-collapsed={collapsed}>
|
||||||
className={styles.groupHeader}
|
|
||||||
data-collapsed={collapsedGroups.includes(groupId)}
|
|
||||||
>
|
|
||||||
<div className={clsx(styles.content, className)} {...props} />
|
<div className={clsx(styles.content, className)} {...props} />
|
||||||
{selectMode ? (
|
{selectMode ? (
|
||||||
<div className={styles.selectInfo}>
|
<div className={styles.selectInfo}>
|
||||||
@@ -105,6 +100,8 @@ export const PlainTextDocGroupHeader = ({
|
|||||||
...props
|
...props
|
||||||
}: HTMLAttributes<HTMLDivElement> & {
|
}: HTMLAttributes<HTMLDivElement> & {
|
||||||
groupId: string;
|
groupId: string;
|
||||||
|
collapsed: boolean;
|
||||||
|
onCollapse: (collapsed: boolean) => void;
|
||||||
docCount: number;
|
docCount: number;
|
||||||
icon?: ReactNode;
|
icon?: ReactNode;
|
||||||
}) => {
|
}) => {
|
||||||
|
|||||||
@@ -30,4 +30,5 @@ export interface GroupHeaderProps {
|
|||||||
groupId: string;
|
groupId: string;
|
||||||
docCount: number;
|
docCount: number;
|
||||||
collapsed: boolean;
|
collapsed: boolean;
|
||||||
|
onCollapse: (collapsed: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -100,10 +100,17 @@ export const CheckboxDocListProperty = ({
|
|||||||
export const CheckboxGroupHeader = ({
|
export const CheckboxGroupHeader = ({
|
||||||
groupId,
|
groupId,
|
||||||
docCount,
|
docCount,
|
||||||
|
collapsed,
|
||||||
|
onCollapse,
|
||||||
}: GroupHeaderProps) => {
|
}: GroupHeaderProps) => {
|
||||||
const text = groupId === 'true' ? 'Checked' : 'Unchecked';
|
const text = groupId === 'true' ? 'Checked' : 'Unchecked';
|
||||||
return (
|
return (
|
||||||
<PlainTextDocGroupHeader docCount={docCount} groupId={groupId}>
|
<PlainTextDocGroupHeader
|
||||||
|
docCount={docCount}
|
||||||
|
groupId={groupId}
|
||||||
|
collapsed={collapsed}
|
||||||
|
onCollapse={onCollapse}
|
||||||
|
>
|
||||||
{text}
|
{text}
|
||||||
</PlainTextDocGroupHeader>
|
</PlainTextDocGroupHeader>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -166,11 +166,18 @@ export const UpdatedByDocListInlineProperty = ({
|
|||||||
export const ModifiedByGroupHeader = ({
|
export const ModifiedByGroupHeader = ({
|
||||||
groupId,
|
groupId,
|
||||||
docCount,
|
docCount,
|
||||||
|
collapsed,
|
||||||
|
onCollapse,
|
||||||
}: GroupHeaderProps) => {
|
}: GroupHeaderProps) => {
|
||||||
const userId = groupId;
|
const userId = groupId;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PlainTextDocGroupHeader groupId={groupId} docCount={docCount}>
|
<PlainTextDocGroupHeader
|
||||||
|
groupId={groupId}
|
||||||
|
docCount={docCount}
|
||||||
|
collapsed={collapsed}
|
||||||
|
onCollapse={onCollapse}
|
||||||
|
>
|
||||||
<div className={styles.userLabelContainer}>
|
<div className={styles.userLabelContainer}>
|
||||||
<PublicUserLabel id={userId} size={20} showName={false} />
|
<PublicUserLabel id={userId} size={20} showName={false} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -243,11 +243,21 @@ export const UpdatedDateDocListProperty = ({ doc }: DocListPropertyProps) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DateGroupHeader = ({ groupId, docCount }: GroupHeaderProps) => {
|
export const DateGroupHeader = ({
|
||||||
|
groupId,
|
||||||
|
docCount,
|
||||||
|
collapsed,
|
||||||
|
onCollapse,
|
||||||
|
}: GroupHeaderProps) => {
|
||||||
const date = groupId || 'No Date';
|
const date = groupId || 'No Date';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PlainTextDocGroupHeader groupId={groupId} docCount={docCount}>
|
<PlainTextDocGroupHeader
|
||||||
|
groupId={groupId}
|
||||||
|
docCount={docCount}
|
||||||
|
collapsed={collapsed}
|
||||||
|
onCollapse={onCollapse}
|
||||||
|
>
|
||||||
{date}
|
{date}
|
||||||
</PlainTextDocGroupHeader>
|
</PlainTextDocGroupHeader>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -137,6 +137,8 @@ export const DocPrimaryModeDocListProperty = ({
|
|||||||
export const DocPrimaryModeGroupHeader = ({
|
export const DocPrimaryModeGroupHeader = ({
|
||||||
groupId,
|
groupId,
|
||||||
docCount,
|
docCount,
|
||||||
|
collapsed,
|
||||||
|
onCollapse,
|
||||||
}: GroupHeaderProps) => {
|
}: GroupHeaderProps) => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
const text =
|
const text =
|
||||||
@@ -147,7 +149,12 @@ export const DocPrimaryModeGroupHeader = ({
|
|||||||
: 'Default';
|
: 'Default';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PlainTextDocGroupHeader groupId={groupId} docCount={docCount}>
|
<PlainTextDocGroupHeader
|
||||||
|
groupId={groupId}
|
||||||
|
docCount={docCount}
|
||||||
|
collapsed={collapsed}
|
||||||
|
onCollapse={onCollapse}
|
||||||
|
>
|
||||||
{text}
|
{text}
|
||||||
</PlainTextDocGroupHeader>
|
</PlainTextDocGroupHeader>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -236,10 +236,20 @@ export const JournalDocListProperty = ({ doc }: DocListPropertyProps) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const JournalGroupHeader = ({ groupId, docCount }: GroupHeaderProps) => {
|
export const JournalGroupHeader = ({
|
||||||
|
groupId,
|
||||||
|
docCount,
|
||||||
|
collapsed,
|
||||||
|
onCollapse,
|
||||||
|
}: GroupHeaderProps) => {
|
||||||
const text = groupId === 'true' ? 'Journal' : 'Not Journal';
|
const text = groupId === 'true' ? 'Journal' : 'Not Journal';
|
||||||
return (
|
return (
|
||||||
<PlainTextDocGroupHeader groupId={groupId} docCount={docCount}>
|
<PlainTextDocGroupHeader
|
||||||
|
groupId={groupId}
|
||||||
|
docCount={docCount}
|
||||||
|
collapsed={collapsed}
|
||||||
|
onCollapse={onCollapse}
|
||||||
|
>
|
||||||
{text}
|
{text}
|
||||||
</PlainTextDocGroupHeader>
|
</PlainTextDocGroupHeader>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -202,7 +202,12 @@ export const TagsDocListProperty = ({ doc }: DocListPropertyProps) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const TagsGroupHeader = ({ groupId, docCount }: GroupHeaderProps) => {
|
export const TagsGroupHeader = ({
|
||||||
|
groupId,
|
||||||
|
docCount,
|
||||||
|
collapsed,
|
||||||
|
onCollapse,
|
||||||
|
}: GroupHeaderProps) => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
const tagService = useService(TagService);
|
const tagService = useService(TagService);
|
||||||
const tag = useLiveData(tagService.tagList.tagByTagId$(groupId));
|
const tag = useLiveData(tagService.tagList.tagByTagId$(groupId));
|
||||||
@@ -212,6 +217,8 @@ export const TagsGroupHeader = ({ groupId, docCount }: GroupHeaderProps) => {
|
|||||||
<PlainTextDocGroupHeader
|
<PlainTextDocGroupHeader
|
||||||
groupId={groupId}
|
groupId={groupId}
|
||||||
docCount={docCount}
|
docCount={docCount}
|
||||||
|
collapsed={collapsed}
|
||||||
|
onCollapse={onCollapse}
|
||||||
icon={
|
icon={
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
@@ -231,6 +238,8 @@ export const TagsGroupHeader = ({ groupId, docCount }: GroupHeaderProps) => {
|
|||||||
<PlainTextDocGroupHeader
|
<PlainTextDocGroupHeader
|
||||||
groupId={groupId}
|
groupId={groupId}
|
||||||
docCount={docCount}
|
docCount={docCount}
|
||||||
|
collapsed={collapsed}
|
||||||
|
onCollapse={onCollapse}
|
||||||
icon={<TagIcon tag={tag} />}
|
icon={<TagIcon tag={tag} />}
|
||||||
>
|
>
|
||||||
<TagName tag={tag} />
|
<TagName tag={tag} />
|
||||||
|
|||||||
@@ -260,10 +260,20 @@ export const TextDocListProperty = ({ value }: { value: string }) => {
|
|||||||
return <StackProperty icon={<TextTypeIcon />}>{value}</StackProperty>;
|
return <StackProperty icon={<TextTypeIcon />}>{value}</StackProperty>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const TextGroupHeader = ({ groupId, docCount }: GroupHeaderProps) => {
|
export const TextGroupHeader = ({
|
||||||
|
groupId,
|
||||||
|
docCount,
|
||||||
|
collapsed,
|
||||||
|
onCollapse,
|
||||||
|
}: GroupHeaderProps) => {
|
||||||
const text = groupId || 'No Text';
|
const text = groupId || 'No Text';
|
||||||
return (
|
return (
|
||||||
<PlainTextDocGroupHeader groupId={groupId} docCount={docCount}>
|
<PlainTextDocGroupHeader
|
||||||
|
groupId={groupId}
|
||||||
|
docCount={docCount}
|
||||||
|
collapsed={collapsed}
|
||||||
|
onCollapse={onCollapse}
|
||||||
|
>
|
||||||
{text}
|
{text}
|
||||||
</PlainTextDocGroupHeader>
|
</PlainTextDocGroupHeader>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -50,10 +50,12 @@ import { PinnedCollections } from './pinned-collections';
|
|||||||
const GroupHeader = memo(function GroupHeader({
|
const GroupHeader = memo(function GroupHeader({
|
||||||
groupId,
|
groupId,
|
||||||
collapsed,
|
collapsed,
|
||||||
|
onCollapse,
|
||||||
itemCount,
|
itemCount,
|
||||||
}: {
|
}: {
|
||||||
groupId: string;
|
groupId: string;
|
||||||
collapsed?: boolean;
|
collapsed?: boolean;
|
||||||
|
onCollapse: (collapsed: boolean) => void;
|
||||||
itemCount: number;
|
itemCount: number;
|
||||||
}) {
|
}) {
|
||||||
const contextValue = useContext(DocExplorerContext);
|
const contextValue = useContext(DocExplorerContext);
|
||||||
@@ -76,12 +78,21 @@ const GroupHeader = memo(function GroupHeader({
|
|||||||
groupId={groupId}
|
groupId={groupId}
|
||||||
docCount={itemCount}
|
docCount={itemCount}
|
||||||
collapsed={!!collapsed}
|
collapsed={!!collapsed}
|
||||||
|
onCollapse={onCollapse}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return '// TODO: ' + groupType;
|
return '// TODO: ' + groupType;
|
||||||
}
|
}
|
||||||
}, [allProperties, collapsed, groupId, groupKey, groupType, itemCount]);
|
}, [
|
||||||
|
allProperties,
|
||||||
|
collapsed,
|
||||||
|
groupId,
|
||||||
|
groupKey,
|
||||||
|
groupType,
|
||||||
|
itemCount,
|
||||||
|
onCollapse,
|
||||||
|
]);
|
||||||
|
|
||||||
if (!groupType) {
|
if (!groupType) {
|
||||||
return null;
|
return null;
|
||||||
@@ -114,6 +125,18 @@ export const AllPage = () => {
|
|||||||
const collectionService = useService(CollectionService);
|
const collectionService = useService(CollectionService);
|
||||||
const pinnedCollectionService = useService(PinnedCollectionService);
|
const pinnedCollectionService = useService(PinnedCollectionService);
|
||||||
|
|
||||||
|
const isCollectionDataReady = useLiveData(
|
||||||
|
collectionService.collectionDataReady$
|
||||||
|
);
|
||||||
|
|
||||||
|
const isPinnedCollectionDataReady = useLiveData(
|
||||||
|
pinnedCollectionService.pinnedCollectionDataReady$
|
||||||
|
);
|
||||||
|
|
||||||
|
const pinnedCollections = useLiveData(
|
||||||
|
pinnedCollectionService.pinnedCollections$
|
||||||
|
);
|
||||||
|
|
||||||
const [selectedCollectionId, setSelectedCollectionId] = useState<
|
const [selectedCollectionId, setSelectedCollectionId] = useState<
|
||||||
string | null
|
string | null
|
||||||
>(null);
|
>(null);
|
||||||
@@ -124,17 +147,28 @@ export const AllPage = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// if selected collection is not found, set selected collection id to null
|
// if selected collection is not in pinned collections, set selected collection id to null
|
||||||
if (!selectedCollection && selectedCollectionId) {
|
if (
|
||||||
|
isPinnedCollectionDataReady &&
|
||||||
|
selectedCollectionId &&
|
||||||
|
!pinnedCollections.some(c => c.collectionId === selectedCollectionId)
|
||||||
|
) {
|
||||||
setSelectedCollectionId(null);
|
setSelectedCollectionId(null);
|
||||||
}
|
}
|
||||||
}, [selectedCollection, selectedCollectionId]);
|
}, [isPinnedCollectionDataReady, pinnedCollections, selectedCollectionId]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// if selected collection is not found, set selected collection id to null
|
||||||
|
if (!selectedCollection && selectedCollectionId && isCollectionDataReady) {
|
||||||
|
setSelectedCollectionId(null);
|
||||||
|
}
|
||||||
|
}, [isCollectionDataReady, selectedCollection, selectedCollectionId]);
|
||||||
|
|
||||||
const selectedCollectionInfo = useLiveData(
|
const selectedCollectionInfo = useLiveData(
|
||||||
selectedCollection ? selectedCollection.info$ : null
|
selectedCollection ? selectedCollection.info$ : null
|
||||||
);
|
);
|
||||||
|
|
||||||
const [tempFilters, setTempFilters] = useState<FilterParams[]>([]);
|
const [tempFilters, setTempFilters] = useState<FilterParams[] | null>(null);
|
||||||
|
|
||||||
const [explorerContextValue] = useState(createDocExplorerContext);
|
const [explorerContextValue] = useState(createDocExplorerContext);
|
||||||
|
|
||||||
@@ -143,7 +177,6 @@ export const AllPage = () => {
|
|||||||
const orderBy = useLiveData(explorerContextValue.orderBy$);
|
const orderBy = useLiveData(explorerContextValue.orderBy$);
|
||||||
const groups = useLiveData(explorerContextValue.groups$);
|
const groups = useLiveData(explorerContextValue.groups$);
|
||||||
const selectedDocIds = useLiveData(explorerContextValue.selectedDocIds$);
|
const selectedDocIds = useLiveData(explorerContextValue.selectedDocIds$);
|
||||||
const collapsedGroups = useLiveData(explorerContextValue.collapsedGroups$);
|
|
||||||
const selectMode = useLiveData(explorerContextValue.selectMode$);
|
const selectMode = useLiveData(explorerContextValue.selectMode$);
|
||||||
|
|
||||||
const { openPromptModal } = usePromptModal();
|
const { openPromptModal } = usePromptModal();
|
||||||
@@ -152,36 +185,23 @@ export const AllPage = () => {
|
|||||||
const items = groups.map((group: any) => {
|
const items = groups.map((group: any) => {
|
||||||
return {
|
return {
|
||||||
id: group.key,
|
id: group.key,
|
||||||
Component: groups.length > 1 ? GroupHeader : undefined,
|
|
||||||
height: groups.length > 1 ? 24 : 0,
|
|
||||||
className: styles.groupHeader,
|
|
||||||
items: group.items.map((docId: string) => {
|
items: group.items.map((docId: string) => {
|
||||||
return {
|
return {
|
||||||
id: docId,
|
id: docId,
|
||||||
Component: DocListItemComponent,
|
|
||||||
height:
|
|
||||||
view === 'list'
|
|
||||||
? 42
|
|
||||||
: view === 'grid'
|
|
||||||
? 280
|
|
||||||
: calcCardHeightById(docId),
|
|
||||||
'data-view': view,
|
|
||||||
className: styles.docItem,
|
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
} satisfies MasonryGroup;
|
} satisfies MasonryGroup;
|
||||||
});
|
});
|
||||||
return items;
|
return items;
|
||||||
}, [groups, view]);
|
}, [groups]);
|
||||||
|
|
||||||
const collectionRulesService = useService(CollectionRulesService);
|
const collectionRulesService = useService(CollectionRulesService);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const subscription = collectionRulesService
|
const subscription = collectionRulesService
|
||||||
.watch(
|
.watch(
|
||||||
// collection filters and temp filters can't exist at the same time
|
|
||||||
selectedCollectionInfo
|
selectedCollectionInfo
|
||||||
? {
|
? {
|
||||||
filters: selectedCollectionInfo.rules.filters,
|
filters: tempFilters ?? selectedCollectionInfo.rules.filters,
|
||||||
groupBy,
|
groupBy,
|
||||||
orderBy,
|
orderBy,
|
||||||
extraAllowList: selectedCollectionInfo.allowList,
|
extraAllowList: selectedCollectionInfo.allowList,
|
||||||
@@ -303,42 +323,74 @@ export const AllPage = () => {
|
|||||||
});
|
});
|
||||||
}, [docsService.list, openConfirmModal, selectedDocIds, t]);
|
}, [docsService.list, openConfirmModal, selectedDocIds, t]);
|
||||||
|
|
||||||
|
const handleSelectCollection = useCallback((collectionId: string) => {
|
||||||
|
setSelectedCollectionId(collectionId);
|
||||||
|
setTempFilters(null);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleEditCollection = useCallback(
|
||||||
|
(collectionId: string) => {
|
||||||
|
const collection = collectionService.collection$(collectionId).value;
|
||||||
|
if (!collection) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setSelectedCollectionId(collectionId);
|
||||||
|
setTempFilters(collection.info$.value.rules.filters);
|
||||||
|
},
|
||||||
|
[collectionService]
|
||||||
|
);
|
||||||
|
|
||||||
const handleSaveFilters = useCallback(() => {
|
const handleSaveFilters = useCallback(() => {
|
||||||
openPromptModal({
|
if (selectedCollectionId) {
|
||||||
title: t['com.affine.editCollection.saveCollection'](),
|
collectionService.updateCollection(selectedCollectionId, {
|
||||||
label: t['com.affine.editCollectionName.name'](),
|
rules: {
|
||||||
inputOptions: {
|
filters: tempFilters ?? [],
|
||||||
placeholder: t['com.affine.editCollectionName.name.placeholder'](),
|
},
|
||||||
},
|
});
|
||||||
children: t['com.affine.editCollectionName.createTips'](),
|
setTempFilters(null);
|
||||||
confirmText: t['com.affine.editCollection.save'](),
|
} else {
|
||||||
cancelText: t['com.affine.editCollection.button.cancel'](),
|
openPromptModal({
|
||||||
confirmButtonOptions: {
|
title: t['com.affine.editCollection.saveCollection'](),
|
||||||
variant: 'primary',
|
label: t['com.affine.editCollectionName.name'](),
|
||||||
},
|
inputOptions: {
|
||||||
onConfirm(name) {
|
placeholder: t['com.affine.editCollectionName.name.placeholder'](),
|
||||||
const id = collectionService.createCollection({
|
},
|
||||||
name,
|
children: t['com.affine.editCollectionName.createTips'](),
|
||||||
rules: {
|
confirmText: t['com.affine.editCollection.save'](),
|
||||||
filters: tempFilters,
|
cancelText: t['com.affine.editCollection.button.cancel'](),
|
||||||
},
|
confirmButtonOptions: {
|
||||||
});
|
variant: 'primary',
|
||||||
pinnedCollectionService.addPinnedCollection({
|
},
|
||||||
collectionId: id,
|
onConfirm(name) {
|
||||||
index: pinnedCollectionService.indexAt('after'),
|
const id = collectionService.createCollection({
|
||||||
});
|
name,
|
||||||
setTempFilters([]);
|
rules: {
|
||||||
setSelectedCollectionId(id);
|
filters: tempFilters ?? [],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
pinnedCollectionService.addPinnedCollection({
|
||||||
|
collectionId: id,
|
||||||
|
index: pinnedCollectionService.indexAt('after'),
|
||||||
|
});
|
||||||
|
setTempFilters(null);
|
||||||
|
setSelectedCollectionId(id);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
}, [
|
}, [
|
||||||
collectionService,
|
collectionService,
|
||||||
openPromptModal,
|
openPromptModal,
|
||||||
pinnedCollectionService,
|
pinnedCollectionService,
|
||||||
|
selectedCollectionId,
|
||||||
t,
|
t,
|
||||||
tempFilters,
|
tempFilters,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const handleNewTempFilter = useCallback((params: FilterParams) => {
|
||||||
|
setSelectedCollectionId(null);
|
||||||
|
setTempFilters([params]);
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DocExplorerContext.Provider value={explorerContextValue}>
|
<DocExplorerContext.Provider value={explorerContextValue}>
|
||||||
<ViewTitle title={t['All pages']()} />
|
<ViewTitle title={t['All pages']()} />
|
||||||
@@ -352,29 +404,24 @@ export const AllPage = () => {
|
|||||||
<div className={styles.pinnedCollection}>
|
<div className={styles.pinnedCollection}>
|
||||||
<PinnedCollections
|
<PinnedCollections
|
||||||
activeCollectionId={selectedCollectionId}
|
activeCollectionId={selectedCollectionId}
|
||||||
onClickAll={() => setSelectedCollectionId(null)}
|
onActiveAll={() => setSelectedCollectionId(null)}
|
||||||
onClickCollection={collectionId => {
|
onActiveCollection={handleSelectCollection}
|
||||||
setSelectedCollectionId(collectionId);
|
onAddFilter={handleNewTempFilter}
|
||||||
setTempFilters([]);
|
onEditCollection={handleEditCollection}
|
||||||
}}
|
hiddenAdd={tempFilters !== null}
|
||||||
onAddFilter={params => {
|
|
||||||
setSelectedCollectionId(null);
|
|
||||||
setTempFilters([...(tempFilters ?? []), params]);
|
|
||||||
}}
|
|
||||||
hiddenAdd={tempFilters.length > 0}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{tempFilters.length > 0 && (
|
{tempFilters !== null && (
|
||||||
<div className={styles.filterArea}>
|
<div className={styles.filterArea}>
|
||||||
<Filters
|
<Filters
|
||||||
className={styles.filters}
|
className={styles.filters}
|
||||||
filters={tempFilters ?? []}
|
filters={tempFilters}
|
||||||
onChange={handleFilterChange}
|
onChange={handleFilterChange}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
variant="plain"
|
variant="plain"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setTempFilters([]);
|
setTempFilters(null);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t['Cancel']()}
|
{t['Cancel']()}
|
||||||
@@ -394,11 +441,24 @@ export const AllPage = () => {
|
|||||||
preloadHeight={100}
|
preloadHeight={100}
|
||||||
itemWidth={'stretch'}
|
itemWidth={'stretch'}
|
||||||
virtualScroll
|
virtualScroll
|
||||||
collapsedGroups={collapsedGroups}
|
groupComponent={GroupHeader}
|
||||||
|
itemComponent={DocListItemComponent}
|
||||||
|
groupClassName={styles.groupHeader}
|
||||||
|
itemClassName={styles.docItem}
|
||||||
paddingX={useCallback(
|
paddingX={useCallback(
|
||||||
(w: number) => (w > 500 ? 24 : w > 393 ? 20 : 16),
|
(w: number) => (w > 500 ? 24 : w > 393 ? 20 : 16),
|
||||||
[]
|
[]
|
||||||
)}
|
)}
|
||||||
|
groupHeight={groupBy ? 24 : 0}
|
||||||
|
itemHeight={useMemo(
|
||||||
|
() =>
|
||||||
|
view === 'list'
|
||||||
|
? 42
|
||||||
|
: view === 'grid'
|
||||||
|
? 280
|
||||||
|
: item => calcCardHeightById(item.id),
|
||||||
|
[view]
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -27,6 +27,38 @@ export const item = style({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const itemContent = style({
|
||||||
|
display: 'inline-block',
|
||||||
|
overflow: 'hidden',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
textAlign: 'center',
|
||||||
|
maxWidth: '128px',
|
||||||
|
minWidth: '32px',
|
||||||
|
|
||||||
|
selectors: {
|
||||||
|
[`${item}:hover > &`]: {
|
||||||
|
mask:
|
||||||
|
'linear-gradient(#fff) left / calc(100% - 32px) no-repeat,' +
|
||||||
|
'linear-gradient(90deg,#fff 0%,transparent 50%,transparent 100%) right / 32px no-repeat',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const editIconButton = style({
|
||||||
|
opacity: 0,
|
||||||
|
marginLeft: -16,
|
||||||
|
backgroundColor: cssVarV2('layer/background/hoverOverlay'),
|
||||||
|
|
||||||
|
selectors: {
|
||||||
|
[`${item}:hover > &`]: {
|
||||||
|
opacity: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const closeButton = style({});
|
||||||
|
|
||||||
export const container = style({
|
export const container = style({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
|
|||||||
@@ -7,7 +7,13 @@ import {
|
|||||||
} from '@affine/core/modules/collection';
|
} from '@affine/core/modules/collection';
|
||||||
import type { FilterParams } from '@affine/core/modules/collection-rules';
|
import type { FilterParams } from '@affine/core/modules/collection-rules';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import { CollectionsIcon, FilterIcon, PlusIcon } from '@blocksuite/icons/rc';
|
import {
|
||||||
|
CloseIcon,
|
||||||
|
CollectionsIcon,
|
||||||
|
EditIcon,
|
||||||
|
FilterIcon,
|
||||||
|
PlusIcon,
|
||||||
|
} from '@blocksuite/icons/rc';
|
||||||
import { useLiveData, useService } from '@toeverything/infra';
|
import { useLiveData, useService } from '@toeverything/infra';
|
||||||
import { useMemo, useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
|
|
||||||
@@ -17,10 +23,14 @@ export const PinnedCollectionItem = ({
|
|||||||
record,
|
record,
|
||||||
isActive,
|
isActive,
|
||||||
onClick,
|
onClick,
|
||||||
|
onClickRemove,
|
||||||
|
onClickEdit,
|
||||||
}: {
|
}: {
|
||||||
record: PinnedCollectionRecord;
|
record: PinnedCollectionRecord;
|
||||||
|
onClickRemove: () => void;
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
onClick: () => void;
|
onClick: () => void;
|
||||||
|
onClickEdit: () => void;
|
||||||
}) => {
|
}) => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
const collectionService = useService(CollectionService);
|
const collectionService = useService(CollectionService);
|
||||||
@@ -38,22 +48,46 @@ export const PinnedCollectionItem = ({
|
|||||||
data-active={isActive ? 'true' : undefined}
|
data-active={isActive ? 'true' : undefined}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
>
|
>
|
||||||
{name ?? t['Untitled']()}
|
<span className={styles.itemContent}>{name ?? t['Untitled']()}</span>
|
||||||
|
<IconButton
|
||||||
|
size="16"
|
||||||
|
className={styles.editIconButton}
|
||||||
|
onClick={e => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onClickEdit();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<EditIcon />
|
||||||
|
</IconButton>
|
||||||
|
{isActive && (
|
||||||
|
<IconButton
|
||||||
|
className={styles.closeButton}
|
||||||
|
size="16"
|
||||||
|
onClick={e => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onClickRemove();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CloseIcon />
|
||||||
|
</IconButton>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const PinnedCollections = ({
|
export const PinnedCollections = ({
|
||||||
activeCollectionId,
|
activeCollectionId,
|
||||||
onClickAll,
|
onActiveAll,
|
||||||
onClickCollection,
|
onActiveCollection,
|
||||||
onAddFilter,
|
onAddFilter,
|
||||||
|
onEditCollection,
|
||||||
hiddenAdd,
|
hiddenAdd,
|
||||||
}: {
|
}: {
|
||||||
activeCollectionId: string | null;
|
activeCollectionId: string | null;
|
||||||
onClickAll: () => void;
|
onActiveAll: () => void;
|
||||||
onClickCollection: (collectionId: string) => void;
|
onActiveCollection: (collectionId: string) => void;
|
||||||
onAddFilter: (params: FilterParams) => void;
|
onAddFilter: (params: FilterParams) => void;
|
||||||
|
onEditCollection: (collectionId: string) => void;
|
||||||
hiddenAdd?: boolean;
|
hiddenAdd?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
@@ -74,22 +108,32 @@ export const PinnedCollections = ({
|
|||||||
<div
|
<div
|
||||||
className={styles.item}
|
className={styles.item}
|
||||||
data-active={activeCollectionId === null ? 'true' : undefined}
|
data-active={activeCollectionId === null ? 'true' : undefined}
|
||||||
onClick={onClickAll}
|
onClick={onActiveAll}
|
||||||
role="button"
|
role="button"
|
||||||
>
|
>
|
||||||
{t['com.affine.all-docs.pinned-collection.all']()}
|
{t['com.affine.all-docs.pinned-collection.all']()}
|
||||||
</div>
|
</div>
|
||||||
{pinnedCollections.map(record => (
|
{pinnedCollections.map((record, index) => (
|
||||||
<PinnedCollectionItem
|
<PinnedCollectionItem
|
||||||
key={record.collectionId}
|
key={record.collectionId}
|
||||||
record={record}
|
record={record}
|
||||||
isActive={activeCollectionId === record.collectionId}
|
isActive={activeCollectionId === record.collectionId}
|
||||||
onClick={() => onClickCollection(record.collectionId)}
|
onClick={() => onActiveCollection(record.collectionId)}
|
||||||
|
onClickEdit={() => onEditCollection(record.collectionId)}
|
||||||
|
onClickRemove={() => {
|
||||||
|
const nextCollectionId = pinnedCollections[index - 1]?.collectionId;
|
||||||
|
if (nextCollectionId) {
|
||||||
|
onActiveCollection(nextCollectionId);
|
||||||
|
} else {
|
||||||
|
onActiveAll();
|
||||||
|
}
|
||||||
|
pinnedCollectionService.removePinnedCollection(record.collectionId);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{!hiddenAdd && (
|
{!hiddenAdd && (
|
||||||
<AddPinnedCollection
|
<AddPinnedCollection
|
||||||
onAddPinnedCollection={handleAddPinnedCollection}
|
onPinCollection={handleAddPinnedCollection}
|
||||||
onAddFilter={onAddFilter}
|
onAddFilter={onAddFilter}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -98,17 +142,17 @@ export const PinnedCollections = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const AddPinnedCollection = ({
|
export const AddPinnedCollection = ({
|
||||||
onAddPinnedCollection,
|
onPinCollection,
|
||||||
onAddFilter,
|
onAddFilter,
|
||||||
}: {
|
}: {
|
||||||
onAddPinnedCollection: (collectionId: string) => void;
|
onPinCollection: (collectionId: string) => void;
|
||||||
onAddFilter: (params: FilterParams) => void;
|
onAddFilter: (params: FilterParams) => void;
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<Menu
|
<Menu
|
||||||
items={
|
items={
|
||||||
<AddPinnedCollectionMenuContent
|
<AddPinnedCollectionMenuContent
|
||||||
onAddPinnedCollection={onAddPinnedCollection}
|
onPinCollection={onPinCollection}
|
||||||
onAddFilter={onAddFilter}
|
onAddFilter={onAddFilter}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
@@ -121,10 +165,10 @@ export const AddPinnedCollection = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const AddPinnedCollectionMenuContent = ({
|
export const AddPinnedCollectionMenuContent = ({
|
||||||
onAddPinnedCollection,
|
onPinCollection,
|
||||||
onAddFilter,
|
onAddFilter,
|
||||||
}: {
|
}: {
|
||||||
onAddPinnedCollection: (collectionId: string) => void;
|
onPinCollection: (collectionId: string) => void;
|
||||||
onAddFilter: (params: FilterParams) => void;
|
onAddFilter: (params: FilterParams) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const [addingFilter, setAddingFilter] = useState<boolean>(false);
|
const [addingFilter, setAddingFilter] = useState<boolean>(false);
|
||||||
@@ -167,7 +211,7 @@ export const AddPinnedCollectionMenuContent = ({
|
|||||||
prefixIcon={<CollectionsIcon />}
|
prefixIcon={<CollectionsIcon />}
|
||||||
suffixIcon={<PlusIcon />}
|
suffixIcon={<PlusIcon />}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
onAddPinnedCollection(meta.id);
|
onPinCollection(meta.id);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{meta.name ?? t['Untitled']()}
|
{meta.name ?? t['Untitled']()}
|
||||||
|
|||||||
@@ -136,7 +136,8 @@ export class DocCreatedByUpdatedBySyncService extends Service {
|
|||||||
workspaceRootDocSynced &&
|
workspaceRootDocSynced &&
|
||||||
isOwnerOrAdmin &&
|
isOwnerOrAdmin &&
|
||||||
missingCreatedBy &&
|
missingCreatedBy &&
|
||||||
!markedSynced
|
!markedSynced &&
|
||||||
|
this.workspaceService.workspace.flavour !== 'local'
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
false
|
false
|
||||||
|
|||||||
@@ -19,6 +19,11 @@ export class CollectionService extends Service {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
readonly collectionDataReady$ = LiveData.from(
|
||||||
|
this.store.watchCollectionDataReady(),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
// collection metas used in collection list, only include `id` and `name`, without `rules` and `allowList`
|
// collection metas used in collection list, only include `id` and `name`, without `rules` and `allowList`
|
||||||
readonly collectionMetas$ = LiveData.from(
|
readonly collectionMetas$ = LiveData.from(
|
||||||
this.store.watchCollectionMetas(),
|
this.store.watchCollectionMetas(),
|
||||||
|
|||||||
@@ -14,6 +14,11 @@ export class PinnedCollectionService extends Service {
|
|||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pinnedCollectionDataReady$ = LiveData.from(
|
||||||
|
this.pinnedCollectionStore.watchPinnedCollectionDataReady(),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
pinnedCollections$ = LiveData.from<PinnedCollectionRecord[]>(
|
pinnedCollections$ = LiveData.from<PinnedCollectionRecord[]>(
|
||||||
this.pinnedCollectionStore.watchPinnedCollections(),
|
this.pinnedCollectionStore.watchPinnedCollections(),
|
||||||
[]
|
[]
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
} from '@toeverything/infra';
|
} from '@toeverything/infra';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { nanoid } from 'nanoid';
|
import { nanoid } from 'nanoid';
|
||||||
import { map, type Observable, switchMap } from 'rxjs';
|
import { distinctUntilChanged, map, type Observable, switchMap } from 'rxjs';
|
||||||
import { Array as YArray } from 'yjs';
|
import { Array as YArray } from 'yjs';
|
||||||
|
|
||||||
import type { FilterParams } from '../../collection-rules';
|
import type { FilterParams } from '../../collection-rules';
|
||||||
@@ -35,6 +35,17 @@ export class CollectionStore extends Store {
|
|||||||
return this.rootYDoc.getMap('setting');
|
return this.rootYDoc.getMap('setting');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
watchCollectionDataReady() {
|
||||||
|
return this.workspaceService.workspace.engine.doc
|
||||||
|
.docState$(this.workspaceService.workspace.id)
|
||||||
|
.pipe(
|
||||||
|
map(docState => {
|
||||||
|
return docState.ready;
|
||||||
|
}),
|
||||||
|
distinctUntilChanged()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
watchCollectionMetas() {
|
watchCollectionMetas() {
|
||||||
return yjsGetPath(this.workspaceSettingYMap, 'collections').pipe(
|
return yjsGetPath(this.workspaceSettingYMap, 'collections').pipe(
|
||||||
switchMap(yjsObserveDeep),
|
switchMap(yjsObserveDeep),
|
||||||
|
|||||||
@@ -13,6 +13,10 @@ export class PinnedCollectionStore extends Store {
|
|||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
watchPinnedCollectionDataReady() {
|
||||||
|
return this.workspaceDBService.db.pinnedCollections.isReady$;
|
||||||
|
}
|
||||||
|
|
||||||
watchPinnedCollections(): Observable<PinnedCollectionRecord[]> {
|
watchPinnedCollections(): Observable<PinnedCollectionRecord[]> {
|
||||||
return this.workspaceDBService.db.pinnedCollections.find$();
|
return this.workspaceDBService.db.pinnedCollections.find$();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import type {
|
|||||||
TableSchemaBuilder,
|
TableSchemaBuilder,
|
||||||
} from '@toeverything/infra';
|
} from '@toeverything/infra';
|
||||||
import { Entity, LiveData } from '@toeverything/infra';
|
import { Entity, LiveData } from '@toeverything/infra';
|
||||||
import { map } from 'rxjs';
|
import { distinctUntilChanged, map } from 'rxjs';
|
||||||
|
|
||||||
import type { WorkspaceService } from '../../workspace';
|
import type { WorkspaceService } from '../../workspace';
|
||||||
|
|
||||||
@@ -19,10 +19,23 @@ export class WorkspaceDBTable<
|
|||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isReady$ = LiveData.from(
|
||||||
|
this.workspaceService.workspace.engine.doc
|
||||||
|
.docState$(this.props.storageDocId)
|
||||||
|
.pipe(
|
||||||
|
map(docState => docState.ready),
|
||||||
|
distinctUntilChanged()
|
||||||
|
),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
isSyncing$ = LiveData.from(
|
isSyncing$ = LiveData.from(
|
||||||
this.workspaceService.workspace.engine.doc
|
this.workspaceService.workspace.engine.doc
|
||||||
.docState$(this.props.storageDocId)
|
.docState$(this.props.storageDocId)
|
||||||
.pipe(map(docState => docState.syncing)),
|
.pipe(
|
||||||
|
map(docState => docState.syncing),
|
||||||
|
distinctUntilChanged()
|
||||||
|
),
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user