mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-08-08 20:57:08 +08:00
feat(core): move collapsed groups state to masonry
This commit is contained in:
@@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
+8
-1
@@ -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;
|
||||||
@@ -166,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();
|
||||||
@@ -175,27 +185,15 @@ 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(() => {
|
||||||
@@ -443,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>
|
||||||
|
|||||||
@@ -136,7 +136,8 @@ export class DocCreatedByUpdatedBySyncService extends Service {
|
|||||||
workspaceRootDocSynced &&
|
workspaceRootDocSynced &&
|
||||||
isOwnerOrAdmin &&
|
isOwnerOrAdmin &&
|
||||||
missingCreatedBy &&
|
missingCreatedBy &&
|
||||||
!markedSynced
|
!markedSynced &&
|
||||||
|
this.workspaceService.workspace.flavour !== 'local'
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
false
|
false
|
||||||
|
|||||||
Reference in New Issue
Block a user