@equinor/fusion-framework-react-components-bookmark 2.0.4 → 2.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/version.js +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/version.d.ts +1 -1
- package/package.json +10 -7
- package/CHANGELOG.md +0 -1108
- package/src/__tests__/AppNameField.test.tsx +0 -39
- package/src/components/Bookmark.tsx +0 -98
- package/src/components/BookmarkProvider.tsx +0 -129
- package/src/components/create-bookmark/CreateBookmarkModal.tsx +0 -130
- package/src/components/create-bookmark/index.ts +0 -1
- package/src/components/edit-bookmark/AppNameField.tsx +0 -27
- package/src/components/edit-bookmark/EditBookmarkModal.tsx +0 -203
- package/src/components/edit-bookmark/index.ts +0 -1
- package/src/components/filter/BookmarkFilter.tsx +0 -44
- package/src/components/import-bookmark/ImportBookmarkModal.tsx +0 -100
- package/src/components/import-bookmark/index.ts +0 -1
- package/src/components/loading/Loading.tsx +0 -20
- package/src/components/messages/Message.tsx +0 -89
- package/src/components/row/MoreMenu.tsx +0 -41
- package/src/components/row/Row.tsx +0 -105
- package/src/components/section/Section.tsx +0 -63
- package/src/components/sectionList/SectionList.tsx +0 -186
- package/src/components/shared/SharedIcon.tsx +0 -51
- package/src/hooks/index.ts +0 -1
- package/src/hooks/useBookmarkGrouping.ts +0 -65
- package/src/index.ts +0 -12
- package/src/utils/append-bookmark-id-to-url.ts +0 -5
- package/src/utils/filter-empty-groups.ts +0 -5
- package/src/utils/sort-by-name.ts +0 -4
- package/src/utils/to-human-readable.ts +0 -6
- package/src/version.ts +0 -2
- package/tsconfig.json +0 -18
- package/vitest.config.ts +0 -10
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
import { Button, Dialog } from '@equinor/eds-core-react';
|
|
2
|
-
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
3
|
-
|
|
4
|
-
import styled from 'styled-components';
|
|
5
|
-
import { useBookmarkComponentContext } from '../BookmarkProvider';
|
|
6
|
-
import { filter, from, of } from 'rxjs';
|
|
7
|
-
import { map, switchMap } from 'rxjs/operators';
|
|
8
|
-
import { useObservableState } from '@equinor/fusion-observable/react';
|
|
9
|
-
|
|
10
|
-
const Styled = {
|
|
11
|
-
Actions: styled.div`
|
|
12
|
-
display: flex;
|
|
13
|
-
gap: 1rem;
|
|
14
|
-
`,
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Modal that prompts the user to import a bookmark shared via a `bookmarkId` URL parameter.
|
|
19
|
-
*
|
|
20
|
-
* @returns The import bookmark modal
|
|
21
|
-
*/
|
|
22
|
-
export const ImportBookmarkModal = () => {
|
|
23
|
-
const [isOpen, setIsOpen] = useState(false);
|
|
24
|
-
|
|
25
|
-
const { provider, currentUser } = useBookmarkComponentContext();
|
|
26
|
-
|
|
27
|
-
// Observe changes to current bookmark and check if it is already in bookmarks or favorites
|
|
28
|
-
const newBookmark$ = useMemo(() => {
|
|
29
|
-
// Without a provider there is no bookmark data source to observe
|
|
30
|
-
if (!provider) {
|
|
31
|
-
return of(null);
|
|
32
|
-
}
|
|
33
|
-
// Watch the current bookmark and resolve whether it should prompt an import
|
|
34
|
-
return provider.currentBookmark$.pipe(
|
|
35
|
-
// filter out null values, not interested in those
|
|
36
|
-
filter((bookmark) => !!bookmark),
|
|
37
|
-
switchMap((bookmark) => {
|
|
38
|
-
const checkBookmark = bookmark && bookmark.createdBy.id !== currentUser?.id;
|
|
39
|
-
// Only prompt to import bookmarks created by someone else
|
|
40
|
-
if (!checkBookmark) {
|
|
41
|
-
return of(null);
|
|
42
|
-
}
|
|
43
|
-
// Resolve to null once the bookmark is already a favorite, otherwise pass it through
|
|
44
|
-
return from(provider.isBookmarkInFavorites(bookmark.id)).pipe(
|
|
45
|
-
map((isFavorite) => (isFavorite ? null : bookmark)),
|
|
46
|
-
);
|
|
47
|
-
}),
|
|
48
|
-
);
|
|
49
|
-
}, [provider, currentUser?.id]);
|
|
50
|
-
|
|
51
|
-
const { value: newBookmark } = useObservableState(newBookmark$);
|
|
52
|
-
|
|
53
|
-
useEffect(() => {
|
|
54
|
-
// Open the import dialog whenever a new importable bookmark is detected
|
|
55
|
-
if (newBookmark) {
|
|
56
|
-
setIsOpen(true);
|
|
57
|
-
}
|
|
58
|
-
}, [newBookmark]);
|
|
59
|
-
|
|
60
|
-
const onAddBookmarkFavorite = useCallback(
|
|
61
|
-
(e: React.MouseEvent<HTMLButtonElement>) => {
|
|
62
|
-
// Without a provider we cannot persist the favorite, so bail out
|
|
63
|
-
if (!provider) {
|
|
64
|
-
console.error('Provider not available');
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
from(provider.addBookmarkToFavorites(e.currentTarget.value)).subscribe({
|
|
68
|
-
complete: () => {
|
|
69
|
-
setIsOpen(false);
|
|
70
|
-
},
|
|
71
|
-
});
|
|
72
|
-
},
|
|
73
|
-
[provider],
|
|
74
|
-
);
|
|
75
|
-
|
|
76
|
-
// Nothing to import when there is no candidate bookmark
|
|
77
|
-
if (!newBookmark) {
|
|
78
|
-
return null;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
return (
|
|
82
|
-
<Dialog style={{ width: '600px' }} open={isOpen}>
|
|
83
|
-
<Dialog.Header>Import bookmark</Dialog.Header>
|
|
84
|
-
<Dialog.Content>
|
|
85
|
-
<p>This bookmark was created by {newBookmark.createdBy.name}</p>
|
|
86
|
-
<p>Would you like to import it?</p>
|
|
87
|
-
</Dialog.Content>
|
|
88
|
-
<Dialog.Actions>
|
|
89
|
-
<Styled.Actions>
|
|
90
|
-
<Button onClick={() => setIsOpen(false)} variant={'ghost'} style={{ width: '70px' }}>
|
|
91
|
-
No
|
|
92
|
-
</Button>
|
|
93
|
-
<Button value={newBookmark.id} onClick={onAddBookmarkFavorite} variant="ghost">
|
|
94
|
-
Import
|
|
95
|
-
</Button>
|
|
96
|
-
</Styled.Actions>
|
|
97
|
-
</Dialog.Actions>
|
|
98
|
-
</Dialog>
|
|
99
|
-
);
|
|
100
|
-
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from './ImportBookmarkModal';
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { CircularProgress } from '@equinor/eds-core-react';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* A centered loading spinner.
|
|
5
|
-
*
|
|
6
|
-
* @returns The loading indicator
|
|
7
|
-
*/
|
|
8
|
-
export const Loading = () => (
|
|
9
|
-
<div
|
|
10
|
-
style={{
|
|
11
|
-
display: 'flex',
|
|
12
|
-
alignItems: 'center',
|
|
13
|
-
justifyContent: 'center',
|
|
14
|
-
height: '100%',
|
|
15
|
-
width: '100%',
|
|
16
|
-
}}
|
|
17
|
-
>
|
|
18
|
-
<CircularProgress />
|
|
19
|
-
</div>
|
|
20
|
-
);
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
import { Icon, Typography } from '@equinor/eds-core-react';
|
|
2
|
-
import { tokens } from '@equinor/eds-tokens';
|
|
3
|
-
import type { PropsWithChildren } from 'react';
|
|
4
|
-
import styled from 'styled-components';
|
|
5
|
-
|
|
6
|
-
import { error_outlined, file_description } from '@equinor/eds-icons';
|
|
7
|
-
|
|
8
|
-
export type PortalMessageType = 'Error' | 'Info' | 'Warning' | 'NoContent';
|
|
9
|
-
|
|
10
|
-
const getMessageType = (type?: PortalMessageType) => {
|
|
11
|
-
// Map each message type to its icon and color; unknown types render nothing
|
|
12
|
-
switch (type) {
|
|
13
|
-
case 'Error':
|
|
14
|
-
return { color: tokens.colors.interactive.danger__resting.hex, icon: error_outlined };
|
|
15
|
-
case 'Info':
|
|
16
|
-
return { color: tokens.colors.interactive.primary__resting.hex, icon: error_outlined };
|
|
17
|
-
case 'Warning':
|
|
18
|
-
return { color: tokens.colors.interactive.warning__resting.hex, icon: error_outlined };
|
|
19
|
-
case 'NoContent':
|
|
20
|
-
return {
|
|
21
|
-
color: tokens.colors.interactive.primary__resting.hex,
|
|
22
|
-
icon: file_description,
|
|
23
|
-
};
|
|
24
|
-
default:
|
|
25
|
-
return undefined;
|
|
26
|
-
}
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
const Styles = {
|
|
30
|
-
Wrapper: styled.div`
|
|
31
|
-
width: 100%;
|
|
32
|
-
height: 100%;
|
|
33
|
-
display: flex;
|
|
34
|
-
flex-direction: column;
|
|
35
|
-
align-items: center;
|
|
36
|
-
gap: 1rem;
|
|
37
|
-
justify-content: center;
|
|
38
|
-
`,
|
|
39
|
-
Content: styled.div`
|
|
40
|
-
display: flex;
|
|
41
|
-
flex-direction: column;
|
|
42
|
-
align-items: center;
|
|
43
|
-
gap: 0.5rem;
|
|
44
|
-
justify-content: center;
|
|
45
|
-
`,
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
type MessageProps = {
|
|
49
|
-
readonly title: string;
|
|
50
|
-
readonly body?: React.FC | string;
|
|
51
|
-
readonly type?: PortalMessageType;
|
|
52
|
-
readonly color?: string;
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Displays a titled message with an icon matching its type.
|
|
57
|
-
*
|
|
58
|
-
* @param props - The component's props
|
|
59
|
-
* @returns The message
|
|
60
|
-
*/
|
|
61
|
-
export const Message = ({
|
|
62
|
-
title,
|
|
63
|
-
type = 'Info',
|
|
64
|
-
color,
|
|
65
|
-
children,
|
|
66
|
-
}: PropsWithChildren<MessageProps>) => {
|
|
67
|
-
const currentType = getMessageType(type);
|
|
68
|
-
return (
|
|
69
|
-
<Styles.Wrapper>
|
|
70
|
-
<Icon
|
|
71
|
-
data-testid="icon"
|
|
72
|
-
size={40}
|
|
73
|
-
color={currentType?.color || color || tokens.colors.text.static_icons__tertiary.hex}
|
|
74
|
-
data={currentType?.icon || error_outlined}
|
|
75
|
-
/>
|
|
76
|
-
<Styles.Content>
|
|
77
|
-
<Typography
|
|
78
|
-
color={tokens.colors.text.static_icons__default.hex}
|
|
79
|
-
variant={'h3'}
|
|
80
|
-
aria-label={`Title for ${type} message`}
|
|
81
|
-
>
|
|
82
|
-
{title}
|
|
83
|
-
</Typography>
|
|
84
|
-
|
|
85
|
-
<Typography>{children && children}</Typography>
|
|
86
|
-
</Styles.Content>
|
|
87
|
-
</Styles.Wrapper>
|
|
88
|
-
);
|
|
89
|
-
};
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { Menu } from '@equinor/eds-core-react';
|
|
2
|
-
import type { MutableRefObject } from 'react';
|
|
3
|
-
import type { MenuOption } from './Row';
|
|
4
|
-
|
|
5
|
-
type MenuProps = {
|
|
6
|
-
readonly pRef: MutableRefObject<HTMLElement | null>;
|
|
7
|
-
readonly onClose: VoidFunction;
|
|
8
|
-
readonly open: boolean;
|
|
9
|
-
readonly options: MenuOption[];
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* A dropdown menu of row actions anchored to a reference element.
|
|
14
|
-
*
|
|
15
|
-
* @param props - The component's props
|
|
16
|
-
* @returns The menu
|
|
17
|
-
*/
|
|
18
|
-
export const MoreMenu = ({ pRef, onClose, open, options }: MenuProps) => {
|
|
19
|
-
// Render one menu item per configured row action
|
|
20
|
-
const items = options.map(({ onClick, name, disabled, Icon }) => (
|
|
21
|
-
<Menu.Item
|
|
22
|
-
disabled={disabled}
|
|
23
|
-
onClick={(e) => {
|
|
24
|
-
e.stopPropagation();
|
|
25
|
-
e.preventDefault();
|
|
26
|
-
onClick();
|
|
27
|
-
onClose();
|
|
28
|
-
}}
|
|
29
|
-
key={name}
|
|
30
|
-
>
|
|
31
|
-
{Icon && Icon}
|
|
32
|
-
{name}
|
|
33
|
-
</Menu.Item>
|
|
34
|
-
));
|
|
35
|
-
|
|
36
|
-
return (
|
|
37
|
-
<Menu open={open} anchorEl={pRef.current}>
|
|
38
|
-
{items}
|
|
39
|
-
</Menu>
|
|
40
|
-
);
|
|
41
|
-
};
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
import { Icon, Typography } from '@equinor/eds-core-react';
|
|
2
|
-
import { tokens } from '@equinor/eds-tokens';
|
|
3
|
-
import { useOutsideClick } from '@equinor/eds-utils';
|
|
4
|
-
import { type MutableRefObject, type ReactNode, useCallback, useRef } from 'react';
|
|
5
|
-
import { MoreMenu } from './MoreMenu';
|
|
6
|
-
|
|
7
|
-
import styled from 'styled-components';
|
|
8
|
-
import { useBookmarkComponentContext } from '../BookmarkProvider';
|
|
9
|
-
import { from } from 'rxjs';
|
|
10
|
-
|
|
11
|
-
export type MenuOption = {
|
|
12
|
-
name: string;
|
|
13
|
-
disabled: boolean;
|
|
14
|
-
onClick: VoidFunction;
|
|
15
|
-
Icon?: ReactNode;
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
type RowProps = {
|
|
19
|
-
readonly name: string;
|
|
20
|
-
readonly id: string;
|
|
21
|
-
readonly menuOpen: boolean;
|
|
22
|
-
readonly onMenuOpen: (id: string) => void;
|
|
23
|
-
readonly menuOptions: MenuOption[];
|
|
24
|
-
readonly children?: ReactNode;
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
const Styled = {
|
|
28
|
-
Icons: styled.div`
|
|
29
|
-
display: flex;
|
|
30
|
-
flex-direction: row;
|
|
31
|
-
gap: 0.2em;
|
|
32
|
-
align-items: center;
|
|
33
|
-
`,
|
|
34
|
-
ListItem: styled.div`
|
|
35
|
-
cursor: pointer;
|
|
36
|
-
&:hover {
|
|
37
|
-
background-color: ${tokens.colors.ui.background__light.hex};
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
display: flex;
|
|
41
|
-
align-items: center;
|
|
42
|
-
height: 32px;
|
|
43
|
-
min-height: 32px;
|
|
44
|
-
max-height: 32px;
|
|
45
|
-
justify-content: space-between;
|
|
46
|
-
border-bottom: 1px solid ${tokens.colors.ui.background__medium.hex};
|
|
47
|
-
`,
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* A single bookmark row, with actions and click-to-set-current behavior.
|
|
52
|
-
*
|
|
53
|
-
* @param props - The component's props
|
|
54
|
-
* @returns The row
|
|
55
|
-
*/
|
|
56
|
-
export const Row = ({ name, menuOptions, children, id, menuOpen, onMenuOpen }: RowProps) => {
|
|
57
|
-
const pRef = useRef<HTMLElement | null>(null);
|
|
58
|
-
const { provider } = useBookmarkComponentContext();
|
|
59
|
-
|
|
60
|
-
const onListItemClick = useCallback(
|
|
61
|
-
(e: React.MouseEvent) => {
|
|
62
|
-
e.preventDefault();
|
|
63
|
-
e.stopPropagation();
|
|
64
|
-
// Only attempt to set the current bookmark when a provider is available
|
|
65
|
-
if (provider) {
|
|
66
|
-
from(provider.setCurrentBookmark(id)).subscribe();
|
|
67
|
-
}
|
|
68
|
-
},
|
|
69
|
-
[provider, id],
|
|
70
|
-
);
|
|
71
|
-
|
|
72
|
-
useOutsideClick(pRef.current, () => onMenuOpen(''));
|
|
73
|
-
|
|
74
|
-
// TODO(#5093): fix accessibility (keyboard support) for this click handler
|
|
75
|
-
return (
|
|
76
|
-
/* eslint-disable-next-line styled-components-a11y/click-events-have-key-events, styled-components-a11y/no-static-element-interactions*/
|
|
77
|
-
<Styled.ListItem onClick={onListItemClick}>
|
|
78
|
-
<Typography>{name}</Typography>
|
|
79
|
-
<Styled.Icons>
|
|
80
|
-
{children}
|
|
81
|
-
{!!menuOptions?.length && (
|
|
82
|
-
<Icon
|
|
83
|
-
name="more_vertical"
|
|
84
|
-
onClick={(e) => {
|
|
85
|
-
e.preventDefault();
|
|
86
|
-
e.stopPropagation();
|
|
87
|
-
onMenuOpen(id);
|
|
88
|
-
}}
|
|
89
|
-
color={tokens.colors.interactive.primary__resting.hex}
|
|
90
|
-
ref={pRef as unknown as MutableRefObject<SVGSVGElement>}
|
|
91
|
-
/>
|
|
92
|
-
)}
|
|
93
|
-
|
|
94
|
-
<MoreMenu
|
|
95
|
-
open={menuOpen}
|
|
96
|
-
options={menuOptions}
|
|
97
|
-
onClose={() => {
|
|
98
|
-
onMenuOpen('');
|
|
99
|
-
}}
|
|
100
|
-
pRef={pRef}
|
|
101
|
-
/>
|
|
102
|
-
</Styled.Icons>
|
|
103
|
-
</Styled.ListItem>
|
|
104
|
-
);
|
|
105
|
-
};
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import { useState } from 'react';
|
|
2
|
-
import type { ReactNode } from 'react';
|
|
3
|
-
import { Button, Icon, Typography } from '@equinor/eds-core-react';
|
|
4
|
-
import { chevron_down, chevron_right } from '@equinor/eds-icons';
|
|
5
|
-
|
|
6
|
-
import { tokens } from '@equinor/eds-tokens';
|
|
7
|
-
import styled from 'styled-components';
|
|
8
|
-
|
|
9
|
-
const Styled = {
|
|
10
|
-
Button: styled(Button)`
|
|
11
|
-
width: 100%;
|
|
12
|
-
padding: 0;
|
|
13
|
-
padding-top: 0.5rem;
|
|
14
|
-
margin-bottom: 0.5rem;
|
|
15
|
-
display: flex;
|
|
16
|
-
justify-content: space-between;
|
|
17
|
-
align-items: center;
|
|
18
|
-
background: none;
|
|
19
|
-
border: none;
|
|
20
|
-
color: ${tokens.colors.infographic.primary__moss_green_100.hex};
|
|
21
|
-
cursor: pointer;
|
|
22
|
-
:hover {
|
|
23
|
-
background: none;
|
|
24
|
-
}
|
|
25
|
-
`,
|
|
26
|
-
List: styled.ol`
|
|
27
|
-
margin-left: 0.7rem;
|
|
28
|
-
padding-left: 1rem;
|
|
29
|
-
display: flex;
|
|
30
|
-
flex-direction: column;
|
|
31
|
-
gap: 1rem;
|
|
32
|
-
border-left: 1px solid ${tokens.colors.infographic.primary__moss_green_100.hex};
|
|
33
|
-
`,
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
Icon.add({
|
|
37
|
-
chevron_down,
|
|
38
|
-
chevron_right,
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
type SectionProps = {
|
|
42
|
-
readonly name: string;
|
|
43
|
-
readonly children: ReactNode;
|
|
44
|
-
};
|
|
45
|
-
/**
|
|
46
|
-
* A collapsible section with a named header and its contents.
|
|
47
|
-
*
|
|
48
|
-
* @param props - The component's props
|
|
49
|
-
* @returns The section
|
|
50
|
-
*/
|
|
51
|
-
export const Section = ({ children, name }: SectionProps) => {
|
|
52
|
-
const [isExpanded, setIsExpanded] = useState(true);
|
|
53
|
-
|
|
54
|
-
return (
|
|
55
|
-
<div>
|
|
56
|
-
<Styled.Button variant="ghost" onClick={() => setIsExpanded((s) => !s)}>
|
|
57
|
-
<Icon name={isExpanded ? chevron_down.name : chevron_right.name} />
|
|
58
|
-
<Typography variant="h6">{name}</Typography>
|
|
59
|
-
</Styled.Button>
|
|
60
|
-
{isExpanded && <Styled.List>{children}</Styled.List>}
|
|
61
|
-
</div>
|
|
62
|
-
);
|
|
63
|
-
};
|
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
import { Icon } from '@equinor/eds-core-react';
|
|
2
|
-
import type { useBookmarkGrouping } from '../../hooks';
|
|
3
|
-
import { type MenuOption, Row } from '../row/Row';
|
|
4
|
-
import { Section } from '../section/Section';
|
|
5
|
-
import { SharedIcon } from '../shared/SharedIcon';
|
|
6
|
-
// TODO(#5094): export from `@equinor/fusion-framework-react-module-bookmark`
|
|
7
|
-
import type { BookmarkWithoutData } from '@equinor/fusion-framework-module-bookmark';
|
|
8
|
-
import { useCurrentUser } from '@equinor/fusion-framework-react/hooks';
|
|
9
|
-
import { useCallback, useState } from 'react';
|
|
10
|
-
import { delete_to_trash, share, edit, close, update } from '@equinor/eds-icons';
|
|
11
|
-
import { filterEmptyGroups } from '../../utils/filter-empty-groups';
|
|
12
|
-
import { sortByName } from '../../utils/sort-by-name';
|
|
13
|
-
import { toHumanReadable } from '../../utils/to-human-readable';
|
|
14
|
-
import { useBookmarkComponentContext } from '../BookmarkProvider';
|
|
15
|
-
import { from } from 'rxjs';
|
|
16
|
-
|
|
17
|
-
Icon.add({
|
|
18
|
-
delete_to_trash,
|
|
19
|
-
edit,
|
|
20
|
-
share,
|
|
21
|
-
close,
|
|
22
|
-
update,
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
type SectionListProps = {
|
|
26
|
-
readonly bookmarkGroups: ReturnType<typeof useBookmarkGrouping>['bookmarkGroups'];
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Renders bookmark groups as sections of rows with edit/share/delete actions.
|
|
31
|
-
*
|
|
32
|
-
* @param props - The component's props
|
|
33
|
-
* @returns The section list
|
|
34
|
-
*/
|
|
35
|
-
export const SectionList = ({ bookmarkGroups }: SectionListProps) => {
|
|
36
|
-
// const { deleteBookmarkById, updateBookmark, removeBookmarkFavorite } = useBookmark();
|
|
37
|
-
|
|
38
|
-
const { provider, currentApp, showEditBookmark, addBookmarkToClipboard } =
|
|
39
|
-
useBookmarkComponentContext();
|
|
40
|
-
|
|
41
|
-
// TODO(#5095): should be removed when bookmark has property for isOwner
|
|
42
|
-
const user = useCurrentUser();
|
|
43
|
-
|
|
44
|
-
const [isMenuByIdOpen, setIsMenuByIdOpen] = useState('');
|
|
45
|
-
|
|
46
|
-
const createMenuOptions = useCallback(
|
|
47
|
-
(bookmark: BookmarkWithoutData) => {
|
|
48
|
-
// Without a provider there are no actions that can be performed on the bookmark
|
|
49
|
-
if (!provider) return [];
|
|
50
|
-
const appKey = currentApp?.appKey;
|
|
51
|
-
const bookmarkId = bookmark.id;
|
|
52
|
-
const isOwner = bookmark.createdBy.id === user?.localAccountId;
|
|
53
|
-
|
|
54
|
-
const copyUrl = {
|
|
55
|
-
name: 'Copy URL',
|
|
56
|
-
disabled: false,
|
|
57
|
-
onClick: async () => {
|
|
58
|
-
addBookmarkToClipboard(bookmark.id);
|
|
59
|
-
},
|
|
60
|
-
Icon: <Icon name="share" />,
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
// Only the bookmark owner is allowed to edit, update, remove, or share it
|
|
64
|
-
if (isOwner) {
|
|
65
|
-
const ownerOptions = [
|
|
66
|
-
{
|
|
67
|
-
name: 'Edit',
|
|
68
|
-
disabled: appKey !== bookmark.appKey,
|
|
69
|
-
onClick: () => {
|
|
70
|
-
showEditBookmark(bookmarkId);
|
|
71
|
-
},
|
|
72
|
-
Icon: <Icon name="edit" />,
|
|
73
|
-
},
|
|
74
|
-
{
|
|
75
|
-
name: 'Update with current view',
|
|
76
|
-
disabled: appKey !== bookmark.appKey,
|
|
77
|
-
onClick: () => {
|
|
78
|
-
// TODO(#5089): add success and failure message
|
|
79
|
-
from(
|
|
80
|
-
provider.updateBookmark(bookmark.id, undefined, {
|
|
81
|
-
excludePayloadGeneration: false,
|
|
82
|
-
}),
|
|
83
|
-
).subscribe({
|
|
84
|
-
error(err) {
|
|
85
|
-
console.error('Failed to update bookmark with current view', err);
|
|
86
|
-
},
|
|
87
|
-
complete() {
|
|
88
|
-
console.debug('Bookmark updated with current view');
|
|
89
|
-
},
|
|
90
|
-
});
|
|
91
|
-
},
|
|
92
|
-
Icon: <Icon name="update" />,
|
|
93
|
-
},
|
|
94
|
-
{
|
|
95
|
-
name: 'Remove',
|
|
96
|
-
disabled: false,
|
|
97
|
-
onClick: () => {
|
|
98
|
-
// TODO(#5089): add success and failure message
|
|
99
|
-
from(provider.deleteBookmark(bookmark.id)).subscribe({
|
|
100
|
-
error(err) {
|
|
101
|
-
console.error('Failed to remove bookmark', err);
|
|
102
|
-
},
|
|
103
|
-
complete() {
|
|
104
|
-
console.debug('Bookmark removed');
|
|
105
|
-
},
|
|
106
|
-
});
|
|
107
|
-
},
|
|
108
|
-
Icon: <Icon name="delete_to_trash" />,
|
|
109
|
-
},
|
|
110
|
-
{
|
|
111
|
-
name: bookmark.isShared ? 'Unshare' : 'Share',
|
|
112
|
-
disabled: false,
|
|
113
|
-
onClick: async () => {
|
|
114
|
-
from(
|
|
115
|
-
provider.updateBookmark(bookmark.id, {
|
|
116
|
-
isShared: !bookmark.isShared,
|
|
117
|
-
}),
|
|
118
|
-
).subscribe({
|
|
119
|
-
next(value) {
|
|
120
|
-
// Copy the bookmark URL to the clipboard once it becomes shared
|
|
121
|
-
if (value.isShared) {
|
|
122
|
-
addBookmarkToClipboard(value.id);
|
|
123
|
-
}
|
|
124
|
-
},
|
|
125
|
-
error(err) {
|
|
126
|
-
console.error('Failed to update bookmark', err);
|
|
127
|
-
},
|
|
128
|
-
complete() {
|
|
129
|
-
console.debug('Bookmark updated');
|
|
130
|
-
},
|
|
131
|
-
});
|
|
132
|
-
},
|
|
133
|
-
Icon: <Icon name="share" />,
|
|
134
|
-
},
|
|
135
|
-
] as MenuOption[];
|
|
136
|
-
|
|
137
|
-
return bookmark.isShared ? [...ownerOptions, copyUrl] : ownerOptions;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
return [
|
|
141
|
-
copyUrl,
|
|
142
|
-
{
|
|
143
|
-
name: 'Unfavourite',
|
|
144
|
-
disabled: false,
|
|
145
|
-
onClick: () => {
|
|
146
|
-
from(provider.removeBookmarkAsFavorite(bookmark.id)).subscribe({
|
|
147
|
-
error(err) {
|
|
148
|
-
console.error('Failed to remove bookmark from favorites', err);
|
|
149
|
-
},
|
|
150
|
-
complete() {
|
|
151
|
-
console.debug('Bookmark removed from favorites');
|
|
152
|
-
},
|
|
153
|
-
});
|
|
154
|
-
},
|
|
155
|
-
Icon: <Icon name="close" />,
|
|
156
|
-
},
|
|
157
|
-
];
|
|
158
|
-
},
|
|
159
|
-
[provider, showEditBookmark, addBookmarkToClipboard, user, currentApp?.appKey],
|
|
160
|
-
);
|
|
161
|
-
|
|
162
|
-
return (
|
|
163
|
-
<>
|
|
164
|
-
{bookmarkGroups.filter(filterEmptyGroups).map(({ groupingProperty, values }) => (
|
|
165
|
-
<Section key={groupingProperty} name={toHumanReadable(groupingProperty)}>
|
|
166
|
-
{values.sort(sortByName).map((b) => (
|
|
167
|
-
<Row
|
|
168
|
-
menuOptions={createMenuOptions(b)}
|
|
169
|
-
key={b.id}
|
|
170
|
-
name={b.name}
|
|
171
|
-
id={b.id}
|
|
172
|
-
menuOpen={isMenuByIdOpen === b.id}
|
|
173
|
-
onMenuOpen={(id: string) => {
|
|
174
|
-
setIsMenuByIdOpen(id);
|
|
175
|
-
}}
|
|
176
|
-
>
|
|
177
|
-
{b.isShared && (
|
|
178
|
-
<SharedIcon createdById={b.createdBy.id} createdBy={b.createdBy.name} />
|
|
179
|
-
)}
|
|
180
|
-
</Row>
|
|
181
|
-
))}
|
|
182
|
-
</Section>
|
|
183
|
-
))}
|
|
184
|
-
</>
|
|
185
|
-
);
|
|
186
|
-
};
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import { Icon } from '@equinor/eds-core-react';
|
|
2
|
-
import { tokens } from '@equinor/eds-tokens';
|
|
3
|
-
import { useFramework } from '@equinor/fusion-framework-react';
|
|
4
|
-
|
|
5
|
-
import styled from 'styled-components';
|
|
6
|
-
|
|
7
|
-
const Styled = {
|
|
8
|
-
Row: styled.div`
|
|
9
|
-
display: flex;
|
|
10
|
-
align-items: center;
|
|
11
|
-
gap: 0.2em;
|
|
12
|
-
`,
|
|
13
|
-
CreatedBy: styled.p`
|
|
14
|
-
font-size: 12px;
|
|
15
|
-
font-weight: 500;
|
|
16
|
-
line-height: 16px;
|
|
17
|
-
letter-spacing: 0em;
|
|
18
|
-
text-align: right;
|
|
19
|
-
`,
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
type SharedIconProps = {
|
|
23
|
-
readonly createdBy: string;
|
|
24
|
-
readonly createdById: string;
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Displays a "shared by" indicator when a bookmark was created by another user.
|
|
29
|
-
*
|
|
30
|
-
* @param props - The component's props
|
|
31
|
-
* @returns The shared icon, or nothing when the current user created the bookmark
|
|
32
|
-
*/
|
|
33
|
-
export const SharedIcon = ({ createdBy, createdById }: SharedIconProps) => {
|
|
34
|
-
const myId = useFramework().modules.auth.account?.localAccountId;
|
|
35
|
-
|
|
36
|
-
const isMe = createdById === myId;
|
|
37
|
-
|
|
38
|
-
return (
|
|
39
|
-
<Styled.Row>
|
|
40
|
-
{!isMe && <Styled.CreatedBy>shared by {createdBy}</Styled.CreatedBy>}
|
|
41
|
-
<Icon
|
|
42
|
-
name="share"
|
|
43
|
-
color={
|
|
44
|
-
isMe
|
|
45
|
-
? tokens.colors.interactive.primary__resting.hex
|
|
46
|
-
: tokens.colors.interactive.disabled__border.hex
|
|
47
|
-
}
|
|
48
|
-
/>
|
|
49
|
-
</Styled.Row>
|
|
50
|
-
);
|
|
51
|
-
};
|
package/src/hooks/index.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from './useBookmarkGrouping';
|