Skip to content

Commit 4fd4ea5

Browse files
wip
1 parent b987984 commit 4fd4ea5

File tree

4 files changed

+121
-54
lines changed

4 files changed

+121
-54
lines changed

src/frontend/apps/impress/src/features/docs/doc-management/types.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,15 @@ import { User } from '@/features/auth';
22

33
export interface Access {
44
id: string;
5+
max_ancestors_role: Role;
56
role: Role;
67
team: string;
78
user: User;
8-
document_id: string;
9+
document: {
10+
id: string;
11+
path: string;
12+
depth: number;
13+
};
914
abilities: {
1015
destroy: boolean;
1116
partial_update: boolean;
@@ -44,6 +49,7 @@ export interface Doc {
4449
created_at: string;
4550
creator: string;
4651
depth: number;
52+
path: string;
4753
is_favorite: boolean;
4854
link_reach: LinkReach;
4955
link_role: LinkRole;

src/frontend/apps/impress/src/features/docs/doc-share/components/DocInheritedShareContent.tsx

Lines changed: 71 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import { Button, Modal, ModalSize, useModal } from '@openfun/cunningham-react';
2+
import { Fragment, useMemo } from 'react';
23
import { useTranslation } from 'react-i18next';
34
import { createGlobalStyle } from 'styled-components';
45

56
import { Box, Text } from '@/components';
67
import { useCunninghamTheme } from '@/cunningham';
78

8-
import { Access, useDoc } from '../../doc-management';
9+
import { Access, useDoc, useDocStore } from '../../doc-management';
910
import SimpleFileIcon from '../../docs-grid/assets/simple-document.svg';
1011

1112
import { DocShareMemberItem } from './DocShareMemberItem';
@@ -19,15 +20,48 @@ const ShareModalStyle = createGlobalStyle`
1920
`;
2021

2122
type Props = {
22-
accesses: Map<string, Access[]>;
23+
rawAccesses: Access[];
2324
};
2425

25-
export const DocInheritedShareContent = ({ accesses }: Props) => {
26+
export const DocInheritedShareContent = ({ rawAccesses }: Props) => {
2627
const { t } = useTranslation();
2728
const { spacingsTokens } = useCunninghamTheme();
29+
const { currentDoc } = useDocStore();
30+
31+
const inheritedData = useMemo(() => {
32+
if (!currentDoc || rawAccesses.length === 0) {
33+
return null;
34+
}
35+
36+
let parentId = null;
37+
let parentPathLength = 0;
38+
const members: Access[] = [];
39+
40+
// Find the parent document with the longest path that is different from currentDoc
41+
for (const access of rawAccesses) {
42+
const docPath = access.document.path;
43+
44+
// Skip if it's the current document
45+
if (access.document.id === currentDoc.id) {
46+
continue;
47+
}
48+
49+
if (!members.some((member) => member.user.id === access.user.id)) {
50+
members.push(access);
51+
}
52+
53+
// Check if this document has a longer path than our current candidate
54+
if (docPath && (!parentId || docPath.length > parentPathLength)) {
55+
parentId = access.document.id;
56+
parentPathLength = docPath.length;
57+
}
58+
}
59+
60+
return { parentId, members };
61+
}, [currentDoc, rawAccesses]);
2862

2963
// Check if accesses map is empty
30-
const hasAccesses = accesses.size > 0;
64+
const hasAccesses = rawAccesses.length > 0;
3165

3266
if (!hasAccesses) {
3367
return null;
@@ -47,13 +81,13 @@ export const DocInheritedShareContent = ({ accesses }: Props) => {
4781
{t('Inherited share')}
4882
</Text>
4983

50-
{Array.from(accesses.keys()).map((documentId) => (
84+
{inheritedData && (
5185
<DocInheritedShareContentItem
52-
key={documentId}
53-
accesses={accesses.get(documentId) ?? []}
54-
document_id={documentId}
86+
key={inheritedData?.parentId}
87+
accesses={inheritedData?.members ?? []}
88+
document_id={inheritedData?.parentId ?? ''}
5589
/>
56-
))}
90+
)}
5791
</Box>
5892
</Box>
5993
);
@@ -69,10 +103,11 @@ export const DocInheritedShareContentItem = ({
69103
}: DocInheritedShareContentItemProps) => {
70104
const { t } = useTranslation();
71105
const { spacingsTokens } = useCunninghamTheme();
72-
const { data: doc } = useDoc({ id: document_id });
106+
const { data: doc, isLoading } = useDoc({ id: document_id });
107+
73108
const accessModal = useModal();
74109

75-
if (!doc) {
110+
if (!doc && !isLoading) {
76111
return null;
77112
}
78113

@@ -83,24 +118,36 @@ export const DocInheritedShareContentItem = ({
83118
$width="100%"
84119
$direction="row"
85120
$align="center"
121+
$margin={{ bottom: spacingsTokens.sm }}
86122
$justify="space-between"
87123
>
88124
<Box $direction="row" $align="center" $gap={spacingsTokens.sm}>
89125
<SimpleFileIcon />
90126
<Box>
91-
<Text $variation="1000" $weight="bold" $size="sm">
92-
{doc.title ?? t('Untitled document')}
93-
</Text>
94-
<Text $variation="600" $weight="400" $size="xs">
95-
{t('Members of this page have access')}
96-
</Text>
127+
{isLoading ? (
128+
<Box $direction="column" $gap="2px">
129+
<Box className="skeleton" $width="150px" $height="20px" />
130+
<Box className="skeleton" $width="200px" $height="17px" />
131+
</Box>
132+
) : (
133+
<>
134+
<Text $variation="1000" $weight="bold" $size="sm">
135+
{doc?.title ?? t('Untitled document')}
136+
</Text>
137+
<Text $variation="600" $weight="400" $size="xs">
138+
{t('Members of this page have access')}
139+
</Text>
140+
</>
141+
)}
97142
</Box>
98143
</Box>
99-
<Button color="primary-text" size="small" onClick={accessModal.open}>
100-
{t('See access')}
101-
</Button>
144+
{!isLoading && (
145+
<Button color="primary-text" size="small" onClick={accessModal.open}>
146+
{t('See access')}
147+
</Button>
148+
)}
102149
</Box>
103-
{accessModal.isOpen && (
150+
{doc && accessModal.isOpen && (
104151
<Modal
105152
isOpen
106153
closeOnClickOutside
@@ -117,12 +164,9 @@ export const DocInheritedShareContentItem = ({
117164
<ShareModalStyle />
118165
<Box $padding={{ top: spacingsTokens.sm }}>
119166
{accesses.map((access) => (
120-
<DocShareMemberItem
121-
key={access.id}
122-
doc={doc}
123-
access={access}
124-
isInherited
125-
/>
167+
<Fragment key={access.id}>
168+
<DocShareMemberItem doc={doc} access={access} isInherited />
169+
</Fragment>
126170
))}
127171
</Box>
128172
</Modal>

src/frontend/apps/impress/src/features/docs/doc-share/components/DocShareModal.tsx

Lines changed: 21 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -79,28 +79,9 @@ export const DocShareModal = ({ doc, onClose }: Props) => {
7979
},
8080
);
8181

82-
const accessesFromTopLevel = useMemo(() => {
83-
const accesses = membersQuery?.filter(
84-
(access) => access.document_id !== doc.id,
85-
);
86-
// Group accesses by document_id
87-
const accessesByDocumentId = new Map<string, Access[]>();
88-
89-
if (accesses) {
90-
for (const access of accesses) {
91-
if (!accessesByDocumentId.has(access.document_id)) {
92-
accessesByDocumentId.set(access.document_id, []);
93-
}
94-
accessesByDocumentId.get(access.document_id)?.push(access);
95-
}
96-
}
97-
98-
return accessesByDocumentId;
99-
}, [membersQuery, doc.id]);
100-
10182
const membersData: QuickSearchData<Access> = useMemo(() => {
10283
const members: Access[] =
103-
membersQuery?.filter((access) => access.document_id === doc.id) ?? [];
84+
membersQuery?.filter((access) => access.document.id === doc.id) ?? [];
10485

10586
const count = doc.nb_accesses_direct > 1 ? doc.nb_accesses_direct : 1;
10687

@@ -113,7 +94,7 @@ export const DocShareModal = ({ doc, onClose }: Props) => {
11394
}),
11495
elements: members,
11596
};
116-
}, [membersQuery, doc.id, t]);
97+
}, [membersQuery, doc.id, doc.nb_accesses_direct, t]);
11798

11899
const onFilter = useDebouncedCallback((str: string) => {
119100
setUserQuery(str);
@@ -141,7 +122,14 @@ export const DocShareModal = ({ doc, onClose }: Props) => {
141122
setListHeight(height);
142123
};
143124

144-
const showInheritedShareContent = accessesFromTopLevel && showMemberSection;
125+
const inheritedAccesses = useMemo(() => {
126+
return (
127+
membersQuery?.filter((access) => access.document.id !== doc.id) ?? []
128+
);
129+
}, [membersQuery, doc.id]);
130+
131+
const showInheritedShareContent =
132+
inheritedAccesses.length > 0 && showMemberSection;
145133

146134
return (
147135
<>
@@ -218,13 +206,20 @@ export const DocShareModal = ({ doc, onClose }: Props) => {
218206
loading={searchUsersQuery.isLoading}
219207
placeholder={t('Type a name or email')}
220208
>
221-
{accessesFromTopLevel && showInheritedShareContent && (
222-
<DocInheritedShareContent accesses={accessesFromTopLevel} />
223-
)}
209+
{inheritedAccesses.length > 0 &&
210+
showInheritedShareContent && (
211+
<DocInheritedShareContent
212+
rawAccesses={
213+
membersQuery?.filter(
214+
(access) => access.document.id !== doc.id,
215+
) ?? []
216+
}
217+
/>
218+
)}
224219
{showMemberSection ? (
225220
<QuickSearchMemberSection
226221
doc={doc}
227-
hasInheritedShareContent={accessesFromTopLevel.size > 0}
222+
hasInheritedShareContent={inheritedAccesses.length > 0}
228223
membersData={membersData}
229224
/>
230225
) : (

src/frontend/apps/impress/src/pages/globals.css

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,3 +76,25 @@ main ::-webkit-scrollbar-thumb:hover,
7676
nextjs-portal {
7777
display: none;
7878
}
79+
80+
81+
.skeleton {
82+
background: linear-gradient(
83+
100deg,
84+
var(--c--theme--colors--greyscale-050) 30%,
85+
var(--c--theme--colors--greyscale-100) 50%,
86+
var(--c--theme--colors--greyscale-050) 70%
87+
);
88+
background-size: 200% 100%;
89+
animation: shimmer 2.5s infinite;
90+
border-radius: 4px;
91+
}
92+
93+
@keyframes shimmer {
94+
0% {
95+
background-position: -200% 0;
96+
}
97+
100% {
98+
background-position: 200% 0;
99+
}
100+
}

0 commit comments

Comments
 (0)