@atlaspack/inspector-frontend 0.1.1

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.
Files changed (164) hide show
  1. package/.atlaspackrc +4 -0
  2. package/.eslintrc.json +19 -0
  3. package/CHANGELOG.md +12 -0
  4. package/dist/atlassian-dark-brand-refresh.91b786da.js +2 -0
  5. package/dist/atlassian-dark-brand-refresh.91b786da.js.map +1 -0
  6. package/dist/atlassian-dark-future.59ebadca.js +2 -0
  7. package/dist/atlassian-dark-future.59ebadca.js.map +1 -0
  8. package/dist/atlassian-dark-increased-contrast.ff6775f2.js +2 -0
  9. package/dist/atlassian-dark-increased-contrast.ff6775f2.js.map +1 -0
  10. package/dist/atlassian-dark.ad679134.js +2 -0
  11. package/dist/atlassian-dark.ad679134.js.map +1 -0
  12. package/dist/atlassian-legacy-dark.8aa27f7f.js +2 -0
  13. package/dist/atlassian-legacy-dark.8aa27f7f.js.map +1 -0
  14. package/dist/atlassian-legacy-light.2eb372ce.js +2 -0
  15. package/dist/atlassian-legacy-light.2eb372ce.js.map +1 -0
  16. package/dist/atlassian-light-brand-refresh.fadcab0a.js +2 -0
  17. package/dist/atlassian-light-brand-refresh.fadcab0a.js.map +1 -0
  18. package/dist/atlassian-light-future.612afe8a.js +2 -0
  19. package/dist/atlassian-light-future.612afe8a.js.map +1 -0
  20. package/dist/atlassian-light-increased-contrast.7161cd79.js +2 -0
  21. package/dist/atlassian-light-increased-contrast.7161cd79.js.map +1 -0
  22. package/dist/atlassian-light.bc343d4c.js +2 -0
  23. package/dist/atlassian-light.bc343d4c.js.map +1 -0
  24. package/dist/atlassian-shape.b92d69c0.js +2 -0
  25. package/dist/atlassian-shape.b92d69c0.js.map +1 -0
  26. package/dist/atlassian-spacing.60ddd8e7.js +2 -0
  27. package/dist/atlassian-spacing.60ddd8e7.js.map +1 -0
  28. package/dist/atlassian-typography-adg3.f88947f6.js +2 -0
  29. package/dist/atlassian-typography-adg3.f88947f6.js.map +1 -0
  30. package/dist/atlassian-typography-modernized.42016c51.js +2 -0
  31. package/dist/atlassian-typography-modernized.42016c51.js.map +1 -0
  32. package/dist/atlassian-typography-refreshed.ec0d111b.js +2 -0
  33. package/dist/atlassian-typography-refreshed.ec0d111b.js.map +1 -0
  34. package/dist/atlassian-typography.66d7e8f4.js +2 -0
  35. package/dist/atlassian-typography.66d7e8f4.js.map +1 -0
  36. package/dist/badge-light.7e55986a.png +0 -0
  37. package/dist/custom-theme.4680282a.js +2 -0
  38. package/dist/custom-theme.4680282a.js.map +1 -0
  39. package/dist/drag-handle.136830d3.js +2 -0
  40. package/dist/drag-handle.136830d3.js.map +1 -0
  41. package/dist/drag-handle.63bdb345.css +2 -0
  42. package/dist/drag-handle.63bdb345.css.map +1 -0
  43. package/dist/index.13289f53.js +28 -0
  44. package/dist/index.13289f53.js.map +1 -0
  45. package/dist/index.a41fafce.css +2 -0
  46. package/dist/index.a41fafce.css.map +1 -0
  47. package/dist/index.html +1 -0
  48. package/dist/index.runtime.3c39d71d.js +2 -0
  49. package/dist/index.runtime.3c39d71d.js.map +1 -0
  50. package/dist/refractor.2c1fd9a1.js +2 -0
  51. package/dist/refractor.2c1fd9a1.js.map +1 -0
  52. package/index.html +11 -0
  53. package/jest.config.js +16 -0
  54. package/package.json +64 -0
  55. package/src/APIError.test.ts +72 -0
  56. package/src/APIError.tsx +29 -0
  57. package/src/AppRoutes.tsx +56 -0
  58. package/src/hack-feature-flags.ts +6 -0
  59. package/src/main.tsx +50 -0
  60. package/src/test/stubCssModule.js +1 -0
  61. package/src/ui/App.module.css +122 -0
  62. package/src/ui/App.module.css.d.ts +8 -0
  63. package/src/ui/AppLayout/AppLayout.tsx +26 -0
  64. package/src/ui/AppLayout/SidebarNavigation/LinkItem.tsx +26 -0
  65. package/src/ui/AppLayout/SidebarNavigation/SidebarNavigation.tsx +45 -0
  66. package/src/ui/AppLayout/TopNavigation/Logo.module.css +12 -0
  67. package/src/ui/AppLayout/TopNavigation/Logo.module.css.d.ts +4 -0
  68. package/src/ui/AppLayout/TopNavigation/Logo.tsx +11 -0
  69. package/src/ui/AppLayout/TopNavigation/TopNavigation.module.css +14 -0
  70. package/src/ui/AppLayout/TopNavigation/TopNavigation.module.css.d.ts +3 -0
  71. package/src/ui/AppLayout/TopNavigation/TopNavigation.tsx +45 -0
  72. package/src/ui/AppLayout/TopNavigation/badge-light.png +0 -0
  73. package/src/ui/AppLayout/TopNavigation/logo-light.png +0 -0
  74. package/src/ui/DefaultLoadingIndicator/DefaultLoadingIndicator.module.css +9 -0
  75. package/src/ui/DefaultLoadingIndicator/DefaultLoadingIndicator.module.css.d.ts +3 -0
  76. package/src/ui/DefaultLoadingIndicator/DefaultLoadingIndicator.test.tsx +15 -0
  77. package/src/ui/DefaultLoadingIndicator/DefaultLoadingIndicator.tsx +14 -0
  78. package/src/ui/app/StatsPage.tsx +77 -0
  79. package/src/ui/app/cache/CacheKeysIndexPage.tsx +13 -0
  80. package/src/ui/app/cache/CacheKeysPage.module.css +11 -0
  81. package/src/ui/app/cache/CacheKeysPage.module.css.d.ts +4 -0
  82. package/src/ui/app/cache/CacheKeysPage.tsx +23 -0
  83. package/src/ui/app/cache/[key]/CacheValuePage.tsx +40 -0
  84. package/src/ui/app/cache/ui/CacheKeyList.module.css +40 -0
  85. package/src/ui/app/cache/ui/CacheKeyList.module.css.d.ts +7 -0
  86. package/src/ui/app/cache/ui/CacheKeyList.tsx +187 -0
  87. package/src/ui/app/cache-invalidation/CacheInvalidationPage.tsx +22 -0
  88. package/src/ui/app/cache-invalidation/[fileId]/CacheInvalidationFilePage.tsx +22 -0
  89. package/src/ui/app/cache-invalidation/ui/CacheFileList.module.css +40 -0
  90. package/src/ui/app/cache-invalidation/ui/CacheFileList.module.css.d.ts +7 -0
  91. package/src/ui/app/cache-invalidation/ui/CacheFileList.tsx +185 -0
  92. package/src/ui/app/treemap/BottomPanelResizeState.test.ts +25 -0
  93. package/src/ui/app/treemap/BottomPanelResizeState.tsx +48 -0
  94. package/src/ui/app/treemap/FoamTreemapPage.module.css +24 -0
  95. package/src/ui/app/treemap/FoamTreemapPage.module.css.d.ts +6 -0
  96. package/src/ui/app/treemap/FoamTreemapPage.tsx +47 -0
  97. package/src/ui/app/treemap/controllers/RelatedBundlesController.tsx +41 -0
  98. package/src/ui/app/treemap/controllers/UrlFocusController.tsx +33 -0
  99. package/src/ui/app/treemap/ui/BottomPanel/BottomPanel.module.css +24 -0
  100. package/src/ui/app/treemap/ui/BottomPanel/BottomPanel.module.css.d.ts +5 -0
  101. package/src/ui/app/treemap/ui/BottomPanel/BottomPanel.tsx +24 -0
  102. package/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AdvancedSettings.module.css +13 -0
  103. package/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AdvancedSettings.module.css.d.ts +5 -0
  104. package/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AdvancedSettings.tsx +53 -0
  105. package/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AssetTable/AssetTable.tsx +135 -0
  106. package/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AssetTable/CollapsibleTable/CollapsibleTable.module.css +7 -0
  107. package/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AssetTable/CollapsibleTable/CollapsibleTable.module.css.d.ts +3 -0
  108. package/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AssetTable/CollapsibleTable/CollapsibleTable.tsx +123 -0
  109. package/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AssetTable/CollapsibleTable/CollapsibleTableModel.tsx +18 -0
  110. package/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AssetTable/CollapsibleTable/CollapsibleTableRow.module.css +20 -0
  111. package/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AssetTable/CollapsibleTable/CollapsibleTableRow.module.css.d.ts +6 -0
  112. package/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AssetTable/CollapsibleTable/CollapsibleTableRow.tsx +79 -0
  113. package/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AssetTable/getFileURL.test.ts +19 -0
  114. package/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AssetTable/getFileURL.ts +24 -0
  115. package/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/FocusedGroupInfo.module.css +20 -0
  116. package/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/FocusedGroupInfo.module.css.d.ts +5 -0
  117. package/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/FocusedGroupInfo.tsx +42 -0
  118. package/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/FocusedGroupInfoInner.module.css +29 -0
  119. package/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/FocusedGroupInfoInner.module.css.d.ts +6 -0
  120. package/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/FocusedGroupInfoInner.tsx +107 -0
  121. package/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/GraphContainer.module.css +7 -0
  122. package/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/GraphContainer.module.css.d.ts +3 -0
  123. package/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/GraphContainer.tsx +20 -0
  124. package/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/SourceCodeURL.tsx +5 -0
  125. package/src/ui/app/treemap/ui/BundleGraphRenderer.module.css +13 -0
  126. package/src/ui/app/treemap/ui/BundleGraphRenderer.module.css.d.ts +4 -0
  127. package/src/ui/app/treemap/ui/BundleGraphRenderer.tsx +95 -0
  128. package/src/ui/app/treemap/ui/FocusBreadcrumbs/FocusBreadcrumbs.module.css +6 -0
  129. package/src/ui/app/treemap/ui/FocusBreadcrumbs/FocusBreadcrumbs.module.css.d.ts +3 -0
  130. package/src/ui/app/treemap/ui/FocusBreadcrumbs/FocusBreadcrumbs.tsx +49 -0
  131. package/src/ui/app/treemap/ui/SigmaGraph.module.css +5 -0
  132. package/src/ui/app/treemap/ui/SigmaGraph.module.css.d.ts +3 -0
  133. package/src/ui/app/treemap/ui/SigmaGraph.tsx +80 -0
  134. package/src/ui/app/treemap/ui/Treemap.tsx +14 -0
  135. package/src/ui/app/treemap/ui/TreemapRenderer/ImpactScore.module.css +32 -0
  136. package/src/ui/app/treemap/ui/TreemapRenderer/ImpactScore.module.css.d.ts +5 -0
  137. package/src/ui/app/treemap/ui/TreemapRenderer/ImpactScore.tsx +24 -0
  138. package/src/ui/app/treemap/ui/TreemapRenderer/TreemapRenderer.module.css +14 -0
  139. package/src/ui/app/treemap/ui/TreemapRenderer/TreemapRenderer.module.css.d.ts +4 -0
  140. package/src/ui/app/treemap/ui/TreemapRenderer/TreemapRenderer.tsx +271 -0
  141. package/src/ui/app/treemap/ui/TreemapRenderer/TreemapTooltip.module.css +15 -0
  142. package/src/ui/app/treemap/ui/TreemapRenderer/TreemapTooltip.module.css.d.ts +4 -0
  143. package/src/ui/app/treemap/ui/TreemapRenderer/TreemapTooltip.tsx +111 -0
  144. package/src/ui/app/treemap/ui/TreemapRenderer/controllers/useStableCallback.test.ts +27 -0
  145. package/src/ui/app/treemap/ui/TreemapRenderer/controllers/useStableCallback.ts +21 -0
  146. package/src/ui/app/treemap/ui/TreemapRenderer/useMouseMoveController.ts +20 -0
  147. package/src/ui/globals.css +26 -0
  148. package/src/ui/globals.css.d.ts +1 -0
  149. package/src/ui/globals.d.ts +9 -0
  150. package/src/ui/model/ViewModel.test.ts +31 -0
  151. package/src/ui/model/ViewModel.ts +62 -0
  152. package/src/ui/not-found/NotFoundPage.module.css +7 -0
  153. package/src/ui/not-found/NotFoundPage.module.css.d.ts +3 -0
  154. package/src/ui/not-found/NotFoundPage.tsx +9 -0
  155. package/src/ui/types/Graph.tsx +12 -0
  156. package/src/ui/util/ErrorBoundary.module.css +3 -0
  157. package/src/ui/util/ErrorBoundary.module.css.d.ts +3 -0
  158. package/src/ui/util/ErrorBoundary.test.tsx +65 -0
  159. package/src/ui/util/ErrorBoundary.tsx +75 -0
  160. package/src/ui/util/colorPalette.tsx +122 -0
  161. package/src/ui/util/formatBytes.test.ts +13 -0
  162. package/src/ui/util/formatBytes.tsx +9 -0
  163. package/src/ui/util/getRandomDarkerColor.tsx +31 -0
  164. package/tsconfig.json +12 -0
@@ -0,0 +1,185 @@
1
+ import {useNavigate, useSearchParams} from 'react-router';
2
+ import {useVirtualizer} from '@tanstack/react-virtual';
3
+ import {useEffect, useMemo, useRef} from 'react';
4
+ import {useInfiniteQuery} from '@tanstack/react-query';
5
+ import qs from 'qs';
6
+ import axios from 'axios';
7
+ import Button, {LinkButton} from '@atlaskit/button/new';
8
+ import {Box} from '@atlaskit/primitives';
9
+
10
+ import * as styles from './CacheFileList.module.css';
11
+ import * as appStyles from '../../../App.module.css';
12
+
13
+ export function CacheFileList() {
14
+ const [searchParams, setSearchParams] = useSearchParams();
15
+ const sortBy = searchParams.get('sortBy') || 'invalidationCount';
16
+ const setSortBy = (value: string) => {
17
+ searchParams.set('sortBy', value);
18
+ setSearchParams(searchParams);
19
+ };
20
+
21
+ const {
22
+ data: cacheFiles,
23
+ isLoading,
24
+ error,
25
+ fetchNextPage,
26
+ isFetchingNextPage,
27
+ hasNextPage,
28
+ } = useInfiniteQuery<{
29
+ files: {
30
+ id: string;
31
+ }[];
32
+ count: number;
33
+ hasNextPage: boolean;
34
+ nextPageCursor: string | null;
35
+ }>({
36
+ queryFn: async ({pageParam}) => {
37
+ const backendUrl = process.env.ATLASPACK_INSPECTOR_BACKEND_URL;
38
+ const {data} = await axios.get(
39
+ `${backendUrl}/api/cache-invalidation-files/?` +
40
+ qs.stringify({cursor: pageParam, sortBy}),
41
+ );
42
+ return data;
43
+ },
44
+ queryKey: ['/api/cache-invalidation-files/?' + qs.stringify({sortBy})],
45
+ initialPageParam: null,
46
+ getNextPageParam: (lastPage) => lastPage.nextPageCursor,
47
+ });
48
+
49
+ const allFiles = useMemo(
50
+ () => cacheFiles?.pages.flatMap((page) => page.files) ?? [],
51
+ [cacheFiles?.pages],
52
+ );
53
+ const lastPage = cacheFiles?.pages[cacheFiles.pages.length - 1];
54
+ const parentRef = useRef<HTMLDivElement>(null);
55
+ const rowVirtualizer = useVirtualizer({
56
+ count: lastPage?.hasNextPage ? allFiles.length + 1 : allFiles.length,
57
+ getScrollElement: () => parentRef.current,
58
+ estimateSize: () => 35,
59
+ });
60
+
61
+ const virtualItems = rowVirtualizer.getVirtualItems();
62
+ useEffect(() => {
63
+ const lastVirtualItem = virtualItems[virtualItems.length - 1];
64
+ const loaderRowIsShown =
65
+ allFiles[lastVirtualItem?.index] == null && lastPage?.hasNextPage;
66
+
67
+ if (hasNextPage && loaderRowIsShown && !isFetchingNextPage) {
68
+ fetchNextPage();
69
+ }
70
+ }, [
71
+ allFiles,
72
+ allFiles.length,
73
+ fetchNextPage,
74
+ hasNextPage,
75
+ isFetchingNextPage,
76
+ lastPage,
77
+ lastPage?.hasNextPage,
78
+ virtualItems,
79
+ ]);
80
+
81
+ const navigate = useNavigate();
82
+
83
+ const renderItems = () => {
84
+ if (isLoading) {
85
+ return (
86
+ <div className={styles.cacheFilePlaceholderContainer}>Loading...</div>
87
+ );
88
+ }
89
+
90
+ if (error) {
91
+ return (
92
+ <div className={styles.cacheFilePlaceholderContainer}>
93
+ Error: {error.message}
94
+ </div>
95
+ );
96
+ }
97
+
98
+ if (!allFiles.length) {
99
+ return (
100
+ <div className={styles.cacheFilePlaceholderContainer}>
101
+ No cache files
102
+ </div>
103
+ );
104
+ }
105
+
106
+ return (
107
+ <div className={styles.cacheFileListItemsContainer} ref={parentRef}>
108
+ <div
109
+ className={styles.cacheFileListItemsContainerInner}
110
+ style={{
111
+ height: `${rowVirtualizer.getTotalSize()}px`,
112
+ }}
113
+ >
114
+ {rowVirtualizer.getVirtualItems().map((virtualItem) => {
115
+ const file = allFiles[virtualItem.index];
116
+ const isLoaderRow = file == null && lastPage?.hasNextPage;
117
+ const rowStyle = {
118
+ position: 'absolute',
119
+ top: 0,
120
+ left: 0,
121
+ width: '100%',
122
+ height: `${virtualItem.size}px`,
123
+ transform: `translateY(${virtualItem.start}px)`,
124
+ } as const;
125
+
126
+ if (isLoaderRow) {
127
+ return (
128
+ <div
129
+ key="loader"
130
+ className={appStyles.sidebarItem}
131
+ style={rowStyle}
132
+ >
133
+ Loading...
134
+ </div>
135
+ );
136
+ }
137
+ const href = `/app/cache-invalidation/${encodeURIComponent(
138
+ file.id,
139
+ )}?${searchParams.toString()}`;
140
+
141
+ return (
142
+ <div
143
+ key={virtualItem.index}
144
+ className={appStyles.sidebarItem}
145
+ style={rowStyle}
146
+ >
147
+ <LinkButton
148
+ appearance="subtle"
149
+ href={href}
150
+ onClick={(e) => {
151
+ e.preventDefault();
152
+ navigate(href);
153
+ }}
154
+ >
155
+ {file.id}
156
+ </LinkButton>
157
+ </div>
158
+ );
159
+ })}
160
+ </div>
161
+ </div>
162
+ );
163
+ };
164
+
165
+ return (
166
+ <div className={styles.cacheFileList}>
167
+ <Box>
168
+ <Button
169
+ appearance="subtle"
170
+ onClick={() => {
171
+ if (sortBy === 'order') {
172
+ setSortBy('invalidationCount');
173
+ } else {
174
+ setSortBy('order');
175
+ }
176
+ }}
177
+ >
178
+ Sorting by {sortBy}
179
+ </Button>
180
+ </Box>
181
+
182
+ <div className={styles.cacheFileListInner}>{renderItems()}</div>
183
+ </div>
184
+ );
185
+ }
@@ -0,0 +1,25 @@
1
+ import {ViewModel} from '../../model/ViewModel';
2
+ import {BottomPanelResizeState} from './BottomPanelResizeState';
3
+
4
+ describe('BottomPanelResizeState', () => {
5
+ it('should resize the bottom panel', () => {
6
+ const viewModel = new ViewModel();
7
+ viewModel.bottomPanelHeight = 100;
8
+ const resizeState = new BottomPanelResizeState(viewModel);
9
+
10
+ resizeState.startResize({clientY: 100} as React.MouseEvent);
11
+ expect(viewModel.bottomPanelHeight).toBe(100);
12
+
13
+ resizeState.onMouseMove({clientY: 50} as MouseEvent);
14
+ expect(viewModel.bottomPanelHeight).toBe(150);
15
+
16
+ resizeState.onMouseMove({clientY: 30} as MouseEvent);
17
+ expect(viewModel.bottomPanelHeight).toBe(170);
18
+
19
+ resizeState.onMouseMove({clientY: 110} as MouseEvent);
20
+ expect(viewModel.bottomPanelHeight).toBe(90);
21
+
22
+ resizeState.stopResize();
23
+ expect(viewModel.bottomPanelHeight).toBe(90);
24
+ });
25
+ });
@@ -0,0 +1,48 @@
1
+ import {makeAutoObservable} from 'mobx';
2
+ import {ViewModel} from '../../model/ViewModel';
3
+
4
+ /**
5
+ * View model for bottom panel UI.
6
+ */
7
+ export class BottomPanelResizeState {
8
+ isResizing = false;
9
+ /**
10
+ * Last known Y mouse coordinate
11
+ */
12
+ lastMouseY = 0;
13
+ isHovering = false;
14
+ viewModel: ViewModel;
15
+
16
+ constructor(viewModel: ViewModel) {
17
+ this.viewModel = viewModel;
18
+ makeAutoObservable(this);
19
+ }
20
+
21
+ startResize = (e: React.MouseEvent) => {
22
+ this.isResizing = true;
23
+ this.lastMouseY = e.clientY;
24
+ document.addEventListener('mousemove', this.onMouseMove);
25
+ };
26
+
27
+ mouseEnter = () => {
28
+ this.isHovering = true;
29
+ };
30
+
31
+ mouseLeave = () => {
32
+ this.isHovering = false;
33
+ };
34
+
35
+ stopResize = () => {
36
+ this.isResizing = false;
37
+ document.removeEventListener('mousemove', this.onMouseMove);
38
+ };
39
+
40
+ onMouseMove = (e: MouseEvent) => {
41
+ if (this.isResizing) {
42
+ const deltaY = this.lastMouseY - e.clientY;
43
+ this.viewModel.bottomPanelHeight =
44
+ this.viewModel.bottomPanelHeight + deltaY;
45
+ this.lastMouseY = e.clientY;
46
+ }
47
+ };
48
+ }
@@ -0,0 +1,24 @@
1
+ .foamTreemapPage {
2
+ height: 100%;
3
+ width: 100%;
4
+ display: flex;
5
+ flex-direction: column;
6
+ }
7
+
8
+ .foamTreemapPageRenderer {
9
+ flex: 1;
10
+ }
11
+
12
+ .foamTreemapPageBottomPanel {
13
+ border-top: 2px solid var(--ds-border);
14
+ position: relative;
15
+ }
16
+
17
+ .foamTreemapPageBottomPanelResizeHandle {
18
+ position: absolute;
19
+ top: 0;
20
+ left: 0;
21
+ width: 100%;
22
+ height: 10px;
23
+ cursor: row-resize;
24
+ }
@@ -0,0 +1,6 @@
1
+ export const __esModule: true;
2
+ export const foamTreemapPage: string;
3
+ export const foamTreemapPageBottomPanel: string;
4
+ export const foamTreemapPageBottomPanelResizeHandle: string;
5
+ export const foamTreemapPageRenderer: string;
6
+
@@ -0,0 +1,47 @@
1
+ import {observer} from 'mobx-react-lite';
2
+ import {RelatedBundlesController} from './controllers/RelatedBundlesController';
3
+ import {BottomPanel} from './ui/BottomPanel/BottomPanel';
4
+ import {FocusBreadcrumbs} from './ui/FocusBreadcrumbs/FocusBreadcrumbs';
5
+ import {TreemapRenderer} from './ui/TreemapRenderer/TreemapRenderer';
6
+ import {UrlFocusController} from './controllers/UrlFocusController';
7
+
8
+ import * as styles from './FoamTreemapPage.module.css';
9
+ import {viewModel} from '../../model/ViewModel';
10
+ import {BottomPanelResizeState} from './BottomPanelResizeState';
11
+
12
+ const bottomPanelResizeState = new BottomPanelResizeState(viewModel);
13
+
14
+ export const FoamTreemapPage = observer(() => {
15
+ return (
16
+ <div className={styles.foamTreemapPage}>
17
+ <FocusBreadcrumbs />
18
+
19
+ <div className={styles.foamTreemapPageRenderer}>
20
+ <TreemapRenderer />
21
+ </div>
22
+
23
+ <div
24
+ className={styles.foamTreemapPageBottomPanel}
25
+ style={{
26
+ height: `${viewModel.bottomPanelHeight}px`,
27
+ borderColor: bottomPanelResizeState.isHovering
28
+ ? 'var(--ds-border-accent-blue)'
29
+ : 'var(--ds-border)',
30
+ }}
31
+ >
32
+ <div
33
+ className={styles.foamTreemapPageBottomPanelResizeHandle}
34
+ onMouseDown={bottomPanelResizeState.startResize}
35
+ onMouseUp={bottomPanelResizeState.stopResize}
36
+ onMouseEnter={bottomPanelResizeState.mouseEnter}
37
+ onMouseLeave={bottomPanelResizeState.mouseLeave}
38
+ />
39
+
40
+ <BottomPanel />
41
+ </div>
42
+
43
+ <RelatedBundlesController />
44
+ <UrlFocusController />
45
+ </div>
46
+ );
47
+ });
@@ -0,0 +1,41 @@
1
+ import {useSearchParams} from 'react-router';
2
+ import {useQuery} from '@tanstack/react-query';
3
+ import {observer} from 'mobx-react-lite';
4
+ import {useEffect} from 'react';
5
+ import {runInAction} from 'mobx';
6
+ import {RelatedBundles, viewModel} from '../../../model/ViewModel';
7
+ import qs from 'qs';
8
+
9
+ export const RelatedBundlesController = observer(() => {
10
+ const [searchParams] = useSearchParams();
11
+ const focusedBundleId = searchParams.get('focusedBundleId');
12
+ const {data} = useQuery<RelatedBundles>({
13
+ queryKey: [
14
+ '/api/bundle-graph/related-bundles?' +
15
+ qs.stringify({
16
+ bundle: focusedBundleId,
17
+ }),
18
+ ],
19
+ enabled: focusedBundleId != null,
20
+ });
21
+
22
+ useEffect(() => {
23
+ if (data && data.childBundles.length > 0) {
24
+ runInAction(() => {
25
+ viewModel.relatedBundles = data;
26
+ });
27
+ }
28
+ }, [data]);
29
+
30
+ const searchParamsBundle = searchParams.get('bundle');
31
+ useEffect(() => {
32
+ if (searchParamsBundle != null) {
33
+ runInAction(() => {
34
+ viewModel.relatedBundles = null;
35
+ viewModel.hasDetails = true;
36
+ });
37
+ }
38
+ }, [searchParamsBundle]);
39
+
40
+ return null;
41
+ });
@@ -0,0 +1,33 @@
1
+ import {observer} from 'mobx-react-lite';
2
+ import {useEffect} from 'react';
3
+ import {useSearchParams} from 'react-router';
4
+ import {viewModel} from '../../../model/ViewModel';
5
+ import {runInAction} from 'mobx';
6
+
7
+ export const UrlFocusController = observer(() => {
8
+ const [searchParams] = useSearchParams();
9
+ const focusedBundleId = searchParams.get('focusedBundleId');
10
+ const focusedGroupId = searchParams.get('focusedGroupId');
11
+
12
+ useEffect(() => {
13
+ if (focusedBundleId !== viewModel.focusedBundle?.id) {
14
+ runInAction(() => {
15
+ viewModel.focusedBundle = focusedBundleId
16
+ ? (viewModel.groupsById.get(focusedBundleId) ?? null)
17
+ : null;
18
+ });
19
+ }
20
+ }, [focusedBundleId]);
21
+
22
+ useEffect(() => {
23
+ if (focusedGroupId !== viewModel.focusedGroup?.id) {
24
+ runInAction(() => {
25
+ viewModel.focusedGroup = focusedGroupId
26
+ ? (viewModel.groupsById.get(focusedGroupId) ?? null)
27
+ : null;
28
+ });
29
+ }
30
+ }, [focusedGroupId]);
31
+
32
+ return null;
33
+ });
@@ -0,0 +1,24 @@
1
+ .bottomPanel {
2
+ border-left: 1px solid var(--border-color);
3
+ height: 100%;
4
+ display: flex;
5
+ }
6
+
7
+ .bottomPanelInner {
8
+ display: flex;
9
+ flex-direction: row;
10
+ gap: 8px;
11
+ padding: 8px;
12
+ width: 100%;
13
+ height: 100%;
14
+ }
15
+
16
+ .loadingIndicator {
17
+ display: flex;
18
+ align-items: center;
19
+ justify-content: center;
20
+ flex-direction: column;
21
+ gap: 16px;
22
+ height: 100%;
23
+ width: 100%;
24
+ }
@@ -0,0 +1,5 @@
1
+ export const __esModule: true;
2
+ export const bottomPanel: string;
3
+ export const bottomPanelInner: string;
4
+ export const loadingIndicator: string;
5
+
@@ -0,0 +1,24 @@
1
+ import {Suspense} from 'react';
2
+ import Spinner from '@atlaskit/spinner';
3
+ import {FocusedGroupInfo} from './FocusedGroupInfo/FocusedGroupInfo';
4
+
5
+ import * as styles from './BottomPanel.module.css';
6
+
7
+ export function BottomPanel() {
8
+ return (
9
+ <div className={styles.bottomPanel} onClick={(e) => e.stopPropagation()}>
10
+ <div className={styles.bottomPanelInner}>
11
+ <Suspense
12
+ fallback={
13
+ <div className={styles.loadingIndicator}>
14
+ <Spinner size="large" />
15
+ <h2>Loading bundle graph data...</h2>
16
+ </div>
17
+ }
18
+ >
19
+ <FocusedGroupInfo />
20
+ </Suspense>
21
+ </div>
22
+ </div>
23
+ );
24
+ }
@@ -0,0 +1,13 @@
1
+ .advancedSettings {
2
+ padding: 8px 0;
3
+ }
4
+
5
+ .advancedSettingsInner {
6
+ display: flex;
7
+ flex-direction: column;
8
+ gap: 8px;
9
+ }
10
+
11
+ .advancedSettingsLabel {
12
+ font-weight: bold;
13
+ }
@@ -0,0 +1,5 @@
1
+ export const __esModule: true;
2
+ export const advancedSettings: string;
3
+ export const advancedSettingsInner: string;
4
+ export const advancedSettingsLabel: string;
5
+
@@ -0,0 +1,53 @@
1
+ import {useSearchParams} from 'react-router';
2
+
3
+ import * as styles from './AdvancedSettings.module.css';
4
+
5
+ export function AdvancedSettings() {
6
+ const [searchParams, setSearchParams] = useSearchParams();
7
+ const bundle = searchParams.get('bundle');
8
+ const isDetailView = bundle != null;
9
+ const maxLevels = searchParams.get('maxLevels') ?? 0;
10
+ const stacking = searchParams.get('stacking') ?? 'hierarchical';
11
+
12
+ return (
13
+ <div className={styles.advancedSettings}>
14
+ <div className={styles.advancedSettingsInner}>
15
+ <label className={styles.advancedSettingsLabel}>
16
+ Max levels: {maxLevels}
17
+ </label>
18
+
19
+ <input
20
+ disabled={!isDetailView}
21
+ type="range"
22
+ min={0}
23
+ max={10}
24
+ value={maxLevels}
25
+ onChange={(e) =>
26
+ setSearchParams((prev) => {
27
+ prev.set('maxLevels', e.target.value);
28
+ return prev;
29
+ })
30
+ }
31
+ />
32
+ </div>
33
+
34
+ <div className={styles.advancedSettingsInner}>
35
+ <label className={styles.advancedSettingsLabel}>Stacking</label>
36
+
37
+ <select
38
+ disabled={!isDetailView}
39
+ value={stacking}
40
+ onChange={(e) =>
41
+ setSearchParams((prev) => {
42
+ prev.set('stacking', e.target.value);
43
+ return prev;
44
+ })
45
+ }
46
+ >
47
+ <option value="hierarchical">Hierarchical</option>
48
+ <option value="flattened">Flattened</option>
49
+ </select>
50
+ </div>
51
+ </div>
52
+ );
53
+ }
@@ -0,0 +1,135 @@
1
+ import {observer} from 'mobx-react-lite';
2
+ import {useMemo} from 'react';
3
+ import {makeAutoObservable} from 'mobx';
4
+ import {
5
+ CollapsibleTableModel,
6
+ CollapsibleTableNode,
7
+ } from './CollapsibleTable/CollapsibleTableModel';
8
+ import {CollapsibleTable} from './CollapsibleTable/CollapsibleTable';
9
+ import {SourceCodeURL} from '../SourceCodeURL';
10
+ import {getFileURL} from './getFileURL';
11
+
12
+ export const AssetTable = observer(
13
+ ({
14
+ data,
15
+ isBottomUp,
16
+ }: {
17
+ data: {
18
+ relevantPaths: string[][];
19
+ sourceCodeURL: SourceCodeURL | null;
20
+ projectRoot: string;
21
+ repositoryRoot: string;
22
+ };
23
+ isBottomUp: boolean;
24
+ }) => {
25
+ const model: CollapsibleTableModel = useMemo(() => {
26
+ // this is horrible ; but let's just hope there aren't that many children
27
+ // anyway things won't perform properly in that case
28
+ const expanded = (node: any) => {
29
+ if (node.isExpanded) {
30
+ return [
31
+ node,
32
+ ...node.children.flatMap((child: any) => expanded(child)),
33
+ ];
34
+ }
35
+ return [node];
36
+ };
37
+ const model: CollapsibleTableModel = makeAutoObservable({
38
+ nodes: [],
39
+ focusedNodeId: null,
40
+ get flatNodeList() {
41
+ return this.nodes.flatMap((node) => {
42
+ return expanded(node);
43
+ });
44
+ },
45
+ });
46
+
47
+ if (isBottomUp) {
48
+ const seenRoots = new Set();
49
+ for (let path of data.relevantPaths) {
50
+ const node = path.slice();
51
+ node.reverse();
52
+
53
+ if (seenRoots.has(node[0])) {
54
+ continue;
55
+ }
56
+ seenRoots.add(node[0]);
57
+
58
+ const root: CollapsibleTableNode = makeAutoObservable({
59
+ id: node[0],
60
+ path: node[0],
61
+ sourceCodeUrl: getFileURL(node[0], data.sourceCodeURL),
62
+ isExpanded: false,
63
+ children: [],
64
+ parent: null,
65
+ level: 0,
66
+ });
67
+ let current: CollapsibleTableNode = root;
68
+
69
+ for (let i = 1; i < node.length; i++) {
70
+ const newNode = makeAutoObservable({
71
+ id: current.id + '--->>>>' + node[i],
72
+ path: node[i],
73
+ sourceCodeUrl: getFileURL(node[i], data.sourceCodeURL),
74
+ isExpanded: false,
75
+ children: [],
76
+ parent: current.id,
77
+ level: i,
78
+ });
79
+ current.children.push(newNode);
80
+ current = newNode;
81
+ }
82
+
83
+ model.nodes.push(root);
84
+ }
85
+ } else {
86
+ const roots = new Map();
87
+ for (let path of data.relevantPaths) {
88
+ const node = path.slice();
89
+
90
+ if (!roots.has(node[0])) {
91
+ const root = makeAutoObservable({
92
+ id: node[0],
93
+ path: node[0],
94
+ sourceCodeUrl: getFileURL(node[0], data.sourceCodeURL),
95
+ isExpanded: false,
96
+ children: [] as any[],
97
+ parent: null,
98
+ level: 0,
99
+ });
100
+ roots.set(node[0], root);
101
+ model.nodes.push(root);
102
+ }
103
+
104
+ const root = roots.get(node[0]);
105
+ let current = root;
106
+
107
+ for (let i = 1; i < node.length; i++) {
108
+ const existingNode = current.children.find(
109
+ (child: any) => child.path === node[i],
110
+ );
111
+ if (existingNode) {
112
+ current = existingNode as any;
113
+ continue;
114
+ }
115
+
116
+ const newNode = makeAutoObservable({
117
+ id: current.id + '--->>>>' + node[i],
118
+ path: node[i],
119
+ isExpanded: false,
120
+ children: [],
121
+ parent: current.id,
122
+ level: i,
123
+ });
124
+ current.children.push(newNode);
125
+ current = newNode;
126
+ }
127
+ }
128
+ }
129
+
130
+ return model;
131
+ }, [data, isBottomUp]);
132
+
133
+ return <CollapsibleTable model={model} />;
134
+ },
135
+ );
@@ -0,0 +1,7 @@
1
+ .collapsibleTable {
2
+ width: 100%;
3
+ }
4
+
5
+ .collapsibleTable table {
6
+ width: 100%;
7
+ }
@@ -0,0 +1,3 @@
1
+ export const __esModule: true;
2
+ export const collapsibleTable: string;
3
+