From e5aec724c94e871716e2a7dd3b56c0764d131025 Mon Sep 17 00:00:00 2001 From: Will Huang Date: Wed, 9 Nov 2022 12:16:54 -0800 Subject: [PATCH 01/24] feat: persist bookmarks --- .../src/helpers/bookmarkLocalStorage.ts | 26 +++++++++++++++++++ .../src/store/slices/entrySlice.ts | 14 ++++++++++ 2 files changed, 40 insertions(+) create mode 100644 apps/lockfile-explorer-web/src/helpers/bookmarkLocalStorage.ts diff --git a/apps/lockfile-explorer-web/src/helpers/bookmarkLocalStorage.ts b/apps/lockfile-explorer-web/src/helpers/bookmarkLocalStorage.ts new file mode 100644 index 00000000000..cb29cf96190 --- /dev/null +++ b/apps/lockfile-explorer-web/src/helpers/bookmarkLocalStorage.ts @@ -0,0 +1,26 @@ +import { LockfileEntry } from '../parsing/LockfileEntry'; + +const BOOKMARK_KEY = 'LOCKFILE_EXPLORER_BOOKMARKS'; + +export const getBookmarksFromStorage = (): Set => { + let currBookmarks = JSON.parse(localStorage.getItem(BOOKMARK_KEY) || '{}'); + const bookmarkSet = new Set(); + for (const key of Object.keys(currBookmarks)) { + bookmarkSet.add(key); + } + return bookmarkSet; +}; + +export const saveBookmarkToLocalStorage = (entry: LockfileEntry): void => { + const key = entry.rawEntryId; + let currBookmarks = JSON.parse(localStorage.getItem(BOOKMARK_KEY) || '{}'); + currBookmarks[key] = true; + localStorage.setItem(BOOKMARK_KEY, JSON.stringify(currBookmarks)); +}; + +export const removeBookmarkFromLocalStorage = (entry: LockfileEntry): void => { + const key = entry.rawEntryId; + let currBookmarks = JSON.parse(localStorage.getItem(BOOKMARK_KEY) || '{}'); + delete currBookmarks[key]; + localStorage.setItem(BOOKMARK_KEY, JSON.stringify(currBookmarks)); +}; diff --git a/apps/lockfile-explorer-web/src/store/slices/entrySlice.ts b/apps/lockfile-explorer-web/src/store/slices/entrySlice.ts index 8b015546030..cb0421987ac 100644 --- a/apps/lockfile-explorer-web/src/store/slices/entrySlice.ts +++ b/apps/lockfile-explorer-web/src/store/slices/entrySlice.ts @@ -4,6 +4,11 @@ import { createSlice, PayloadAction, Reducer } from '@reduxjs/toolkit'; import { LockfileEntry, LockfileEntryFilter } from '../../parsing/LockfileEntry'; import { RootState } from '../index'; +import { + getBookmarksFromStorage, + removeBookmarkFromLocalStorage, + saveBookmarkToLocalStorage +} from '../../helpers/bookmarkLocalStorage'; // eslint-disable-next-line @typescript-eslint/consistent-type-definitions type EntryState = { @@ -34,6 +39,13 @@ const entrySlice = createSlice({ reducers: { loadEntries: (state, payload: PayloadAction) => { state.allEntries = payload.payload; + // Hydrate the bookmarks state + const bookmarkSet = getBookmarksFromStorage(); + for (const entry of payload.payload) { + if (bookmarkSet.has(entry.rawEntryId)) { + state.bookmarkedEntries.push(entry); + } + } }, setFilter: (state, payload: PayloadAction<{ filter: LockfileEntryFilter; state: boolean }>) => { state.filters[payload.payload.filter] = payload.payload.state; @@ -52,12 +64,14 @@ const entrySlice = createSlice({ addBookmark: (state, payload: PayloadAction) => { if (!state.bookmarkedEntries.includes(payload.payload)) { state.bookmarkedEntries.push(payload.payload); + saveBookmarkToLocalStorage(payload.payload); } }, removeBookmark: (state, payload: PayloadAction) => { state.bookmarkedEntries = state.bookmarkedEntries.filter( (entry: LockfileEntry) => entry.rawEntryId !== payload.payload.rawEntryId ); + removeBookmarkFromLocalStorage(payload.payload); } } }); From 49c1460eae180f895806c9dfa738af7bacaa7a85 Mon Sep 17 00:00:00 2001 From: Will Huang Date: Wed, 9 Nov 2022 13:16:25 -0800 Subject: [PATCH 02/24] feat: improve user experience when dealing with peer dependencies --- .../LockfileEntryDetailsView/index.tsx | 33 ++++++++++++++++--- .../LockfileEntryDetailsView/styles.scss | 8 +++++ .../src/containers/LockfileViewer/index.tsx | 17 +++++++--- ...ookmarkLocalStorage.ts => localStorage.ts} | 9 +++++ .../src/parsing/LockfileDependency.ts | 1 + .../src/parsing/readLockfile.ts | 3 ++ .../src/store/slices/entrySlice.ts | 3 +- 7 files changed, 63 insertions(+), 11 deletions(-) rename apps/lockfile-explorer-web/src/helpers/{bookmarkLocalStorage.ts => localStorage.ts} (78%) diff --git a/apps/lockfile-explorer-web/src/containers/LockfileEntryDetailsView/index.tsx b/apps/lockfile-explorer-web/src/containers/LockfileEntryDetailsView/index.tsx index 90038515f95..301e550b762 100644 --- a/apps/lockfile-explorer-web/src/containers/LockfileEntryDetailsView/index.tsx +++ b/apps/lockfile-explorer-web/src/containers/LockfileEntryDetailsView/index.tsx @@ -43,7 +43,15 @@ export const LockfileEntryDetailsView = (): JSX.Element | ReactNull => { logDiagnosticInfo('No resolved entry for dependency:', dependencyToTrace); } } else if (selectedEntry) { + console.log('dependency to trace: ', dependencyToTrace); setInspectDependency(dependencyToTrace); + + // Check if we need to calculate influencers. + // If the current dependencyToTrace is a peer dependency then we do + if (dependencyToTrace.dependencyType !== IDependencyType.PEER_DEPENDENCY) { + return; + } + // calculate influencers const stack = [selectedEntry]; const determinants = new Set(); @@ -74,7 +82,9 @@ export const LockfileEntryDetailsView = (): JSX.Element | ReactNull => { // field. console.error( 'Error analyzing influencers: A referrer appears to be missing its "transitivePeerDependencies" field in the YAML file: ', - referrer + dependencyToTrace, + referrer, + currEntry ); } for (const referrer of currEntry.referrers) { @@ -108,6 +118,7 @@ export const LockfileEntryDetailsView = (): JSX.Element | ReactNull => { const selectResolvedReferencer = useCallback( (referrer) => () => { + console.log('going to entry: ', referrer); dispatch(pushToStack(referrer)); }, [selectedEntry] @@ -135,17 +146,29 @@ export const LockfileEntryDetailsView = (): JSX.Element | ReactNull => { return (
-
Influencers:
+
Determinants:
{influencers .filter((inf) => inf.type === InfluencerTypes.Determinant) .map(({ entry }) => ( -
{entry.displayText}
+ + {entry.displayText} + ))}
Transitive Referencers:
{influencers .filter((inf) => inf.type === InfluencerTypes.TransitiveReferrer) .map(({ entry }) => ( -
{entry.displayText}
+ + {entry.displayText} + ))}
); @@ -185,7 +208,7 @@ export const LockfileEntryDetailsView = (): JSX.Element | ReactNull => { {selectedEntry.dependencies?.map((dependency: LockfileDependency) => (
{ const [filter, setFilter] = useState(''); const entries = useAppSelector(selectFilteredEntries); const activeFilters = useAppSelector((state) => state.entry.filters); - const updateFilter = useCallback((e) => setFilter(e.target.value), []); + const updateFilter = useCallback((e) => { + setFilter(e.target.value); + saveFilterToLocalStorage(e.target.value); + }, []); + const pop = useCallback(() => { + dispatch(popStack()); + }, []); + + useEffect(() => { + setFilter(getFilterFromLocalStorage()); + }, []); // const selectedEntry = useAppSelector(selectCurrentEntry); const entryStack = useAppSelector((state) => state.entry.selectedEntryStack); @@ -83,10 +94,6 @@ export const LockfileViewer = (): JSX.Element | ReactNull => { if (!entries) return ReactNull; - const pop = useCallback(() => { - dispatch(popStack()); - }, []); - const getEntriesToShow = (): ILockfileEntryGroup[] => { let filteredEntries: LockfileEntry[] = []; if (filter) { diff --git a/apps/lockfile-explorer-web/src/helpers/bookmarkLocalStorage.ts b/apps/lockfile-explorer-web/src/helpers/localStorage.ts similarity index 78% rename from apps/lockfile-explorer-web/src/helpers/bookmarkLocalStorage.ts rename to apps/lockfile-explorer-web/src/helpers/localStorage.ts index cb29cf96190..747560df086 100644 --- a/apps/lockfile-explorer-web/src/helpers/bookmarkLocalStorage.ts +++ b/apps/lockfile-explorer-web/src/helpers/localStorage.ts @@ -24,3 +24,12 @@ export const removeBookmarkFromLocalStorage = (entry: LockfileEntry): void => { delete currBookmarks[key]; localStorage.setItem(BOOKMARK_KEY, JSON.stringify(currBookmarks)); }; + +const FILTER_KEY = 'LOCKFILE_EXPLORER_FILTER'; +export const saveFilterToLocalStorage = (filter: string): void => { + localStorage.setItem(FILTER_KEY, filter); +}; + +export const getFilterFromLocalStorage = (): string => { + return localStorage.getItem(FILTER_KEY) || ''; +}; diff --git a/apps/lockfile-explorer-web/src/parsing/LockfileDependency.ts b/apps/lockfile-explorer-web/src/parsing/LockfileDependency.ts index 72e0c7c189a..efc5aaf518e 100644 --- a/apps/lockfile-explorer-web/src/parsing/LockfileDependency.ts +++ b/apps/lockfile-explorer-web/src/parsing/LockfileDependency.ts @@ -87,6 +87,7 @@ export class LockfileDependency { ? node.peerDependenciesMeta[this.name].optional : false }; + this.entryId = 'peer dep:' + this.name; } else { console.error('Peer dependencies info missing!', node); } diff --git a/apps/lockfile-explorer-web/src/parsing/readLockfile.ts b/apps/lockfile-explorer-web/src/parsing/readLockfile.ts index 00f508ceff7..07e2668b9ad 100644 --- a/apps/lockfile-explorer-web/src/parsing/readLockfile.ts +++ b/apps/lockfile-explorer-web/src/parsing/readLockfile.ts @@ -2,6 +2,7 @@ // See LICENSE in the project root for license information. import { LockfileEntry, LockfileEntryFilter } from './LockfileEntry'; +import { IDependencyType } from './LockfileDependency'; const serviceUrl: string = window.appContext.serviceUrl; @@ -95,6 +96,8 @@ export const generateLockfileGraph = (lockfile: ILockfilePackageType): LockfileE // Construct the graph for (const entry of allEntries) { for (const dependency of entry.dependencies) { + // Peer dependencies do not have a matching entry + if (dependency.dependencyType === IDependencyType.PEER_DEPENDENCY) continue; const matchedEntry = allEntriesById[dependency.entryId]; if (matchedEntry) { // Create a two-way link between the dependency and the entry diff --git a/apps/lockfile-explorer-web/src/store/slices/entrySlice.ts b/apps/lockfile-explorer-web/src/store/slices/entrySlice.ts index cb0421987ac..1afea2da173 100644 --- a/apps/lockfile-explorer-web/src/store/slices/entrySlice.ts +++ b/apps/lockfile-explorer-web/src/store/slices/entrySlice.ts @@ -6,9 +6,10 @@ import { LockfileEntry, LockfileEntryFilter } from '../../parsing/LockfileEntry' import { RootState } from '../index'; import { getBookmarksFromStorage, + getFilterFromLocalStorage, removeBookmarkFromLocalStorage, saveBookmarkToLocalStorage -} from '../../helpers/bookmarkLocalStorage'; +} from '../../helpers/localStorage'; // eslint-disable-next-line @typescript-eslint/consistent-type-definitions type EntryState = { From aa9cc905490d8f9b6b2312498587048657cd6e55 Mon Sep 17 00:00:00 2001 From: Will Huang Date: Wed, 9 Nov 2022 15:30:57 -0800 Subject: [PATCH 03/24] feat: better css for layout --- apps/lockfile-explorer-web/src/App.scss | 12 ++++---- apps/lockfile-explorer-web/src/App.tsx | 28 +++++++++---------- .../containers/BookmarksSidebar/styles.scss | 3 +- .../LockfileEntryDetailsView/index.tsx | 10 +++---- .../LockfileEntryDetailsView/styles.scss | 8 ++++++ .../src/containers/LockfileViewer/index.tsx | 6 ++-- .../src/containers/LockfileViewer/styles.scss | 12 ++++++-- .../src/containers/LogoPanel/index.tsx | 15 +++++----- .../src/containers/LogoPanel/styles.scss | 10 ++++--- .../containers/PackageJsonViewer/index.tsx | 2 +- .../containers/PackageJsonViewer/styles.scss | 5 ++++ .../SelectedEntryPreview/styles.scss | 4 +-- .../src/helpers/logDiagnosticInfo.ts | 5 ++-- apps/lockfile-explorer-web/src/start.css | 1 + 14 files changed, 74 insertions(+), 47 deletions(-) diff --git a/apps/lockfile-explorer-web/src/App.scss b/apps/lockfile-explorer-web/src/App.scss index 2ff7cfc3c6b..3ece0475b14 100644 --- a/apps/lockfile-explorer-web/src/App.scss +++ b/apps/lockfile-explorer-web/src/App.scss @@ -4,7 +4,6 @@ box-shadow: $ms-depth-shadow-8; background-color: $ms-color-gray10; padding: 24px; - width: calc(100% - 48px); } .ContainerCard { @@ -15,7 +14,9 @@ display: flex; flex-direction: row; flex-wrap: nowrap; - padding: 24px; + & > * { + width: 100%; + } } .AppGrid { @@ -23,7 +24,8 @@ } .BodyContainer { - & > * + * { - margin-top: 24px; - } + height: 100vh; + overflow: hidden; + display: flex; + flex-direction: column; } diff --git a/apps/lockfile-explorer-web/src/App.tsx b/apps/lockfile-explorer-web/src/App.tsx index 79ba0c2e09b..0e9d68151ea 100644 --- a/apps/lockfile-explorer-web/src/App.tsx +++ b/apps/lockfile-explorer-web/src/App.tsx @@ -31,24 +31,22 @@ export const App = (): JSX.Element => { return (
-
-
-
-
- -
-
- - - -
-
- -
+
+
+
+ + +
+
+ + + +
+
+
-
); }; diff --git a/apps/lockfile-explorer-web/src/containers/BookmarksSidebar/styles.scss b/apps/lockfile-explorer-web/src/containers/BookmarksSidebar/styles.scss index 667a95c238e..b1a157e35f2 100644 --- a/apps/lockfile-explorer-web/src/containers/BookmarksSidebar/styles.scss +++ b/apps/lockfile-explorer-web/src/containers/BookmarksSidebar/styles.scss @@ -1,5 +1,6 @@ .BookmarksWrapper { - height: calc(100vh - 96px); + flex: 1; + margin: 12px 0; } .BookmarkEntry { diff --git a/apps/lockfile-explorer-web/src/containers/LockfileEntryDetailsView/index.tsx b/apps/lockfile-explorer-web/src/containers/LockfileEntryDetailsView/index.tsx index 301e550b762..a568ed0245a 100644 --- a/apps/lockfile-explorer-web/src/containers/LockfileEntryDetailsView/index.tsx +++ b/apps/lockfile-explorer-web/src/containers/LockfileEntryDetailsView/index.tsx @@ -36,7 +36,7 @@ export const LockfileEntryDetailsView = (): JSX.Element | ReactNull => { const selectResolvedEntry = useCallback( (dependencyToTrace) => () => { - if (inspectDependency && inspectDependency.name === dependencyToTrace.name) { + if (inspectDependency && inspectDependency.entryId === dependencyToTrace.entryId) { if (dependencyToTrace.resolvedEntry) { dispatch(pushToStack(dependencyToTrace.resolvedEntry)); } else { @@ -131,21 +131,21 @@ export const LockfileEntryDetailsView = (): JSX.Element | ReactNull => { ); if (!peerDeps.length) { return ( -
+
No peer dependencies.
); } if (!inspectDependency || inspectDependency.dependencyType !== IDependencyType.PEER_DEPENDENCY) { return ( -
+
Select a peer dependency to view its influencers
); } return ( -
+
Determinants:
{influencers .filter((inf) => inf.type === InfluencerTypes.Determinant) @@ -176,7 +176,7 @@ export const LockfileEntryDetailsView = (): JSX.Element | ReactNull => { if (!selectedEntry) { return ( -
+
Select an entry to view its details
); diff --git a/apps/lockfile-explorer-web/src/containers/LockfileEntryDetailsView/styles.scss b/apps/lockfile-explorer-web/src/containers/LockfileEntryDetailsView/styles.scss index 173e243052d..5a43b6adccf 100644 --- a/apps/lockfile-explorer-web/src/containers/LockfileEntryDetailsView/styles.scss +++ b/apps/lockfile-explorer-web/src/containers/LockfileEntryDetailsView/styles.scss @@ -3,6 +3,7 @@ .LockfileEntryListView { display: flex; justify-content: space-between; + height: 30%; & > * { width: calc(50% - 48px - 12px); } @@ -36,7 +37,14 @@ background-color: #dff6dd; } +.InfluencerList { + margin-top: 12px; + height: 10%; + overflow: scroll; +} + .InfluencerEntry { + display: block; cursor: pointer; &:hover { color: #107c10; diff --git a/apps/lockfile-explorer-web/src/containers/LockfileViewer/index.tsx b/apps/lockfile-explorer-web/src/containers/LockfileViewer/index.tsx index 5f650e08dc1..15f26e4179f 100644 --- a/apps/lockfile-explorer-web/src/containers/LockfileViewer/index.tsx +++ b/apps/lockfile-explorer-web/src/containers/LockfileViewer/index.tsx @@ -147,7 +147,7 @@ export const LockfileViewer = (): JSX.Element | ReactNull => { ); return ( - <> +
{ } ]} /> -
+
{entryStack.length > 1 ? : null}
filter:
@@ -193,6 +193,6 @@ export const LockfileViewer = (): JSX.Element | ReactNull => {
- +
); }; diff --git a/apps/lockfile-explorer-web/src/containers/LockfileViewer/styles.scss b/apps/lockfile-explorer-web/src/containers/LockfileViewer/styles.scss index db0cce15f75..6ab81a4a9a7 100644 --- a/apps/lockfile-explorer-web/src/containers/LockfileViewer/styles.scss +++ b/apps/lockfile-explorer-web/src/containers/LockfileViewer/styles.scss @@ -1,5 +1,13 @@ .ViewWrapper { - height: calc(95vh - 96px); + // Logo is 70px tall + height: calc(100% - 70px); + padding-top: 12px; +} + +.ViewContents { + display: flex; + flex-direction: column; + max-height: calc(100% - 70px - 35px); } .LockfileEntryListViewWrapper { @@ -44,8 +52,8 @@ } .lockfileEntriesWrapper { - height: calc(95vh - 220px); overflow: scroll; + flex: 1; & > * + * { margin-top: 12px; } diff --git a/apps/lockfile-explorer-web/src/containers/LogoPanel/index.tsx b/apps/lockfile-explorer-web/src/containers/LogoPanel/index.tsx index 7edd9d04af0..c5cb083280a 100644 --- a/apps/lockfile-explorer-web/src/containers/LogoPanel/index.tsx +++ b/apps/lockfile-explorer-web/src/containers/LogoPanel/index.tsx @@ -15,14 +15,15 @@ export const LogoPanel = (): JSX.Element => {
-
- +
+
+ +
+
+ +
+
{appPackageVersion} rushstack.io
-
- -
-
{appPackageVersion}
-
rushstack.io
); diff --git a/apps/lockfile-explorer-web/src/containers/LogoPanel/styles.scss b/apps/lockfile-explorer-web/src/containers/LogoPanel/styles.scss index 141ff441909..5e409eb526c 100644 --- a/apps/lockfile-explorer-web/src/containers/LogoPanel/styles.scss +++ b/apps/lockfile-explorer-web/src/containers/LogoPanel/styles.scss @@ -1,10 +1,13 @@ .LogoPanel { - width: 70px; - padding-left: 5px; + height: 70px; } .LogoPanel a { text-decoration: none; + display: flex; + & > * + * { + margin-left: 12px; + } } .Icon { @@ -19,7 +22,6 @@ .Title2 { padding-top: 5px; - padding-bottom: 10px; height: 20px; } @@ -32,5 +34,5 @@ color: #ab9e8e; font-family: Tahoma, sans-serif; font-size: 14px; - padding-bottom: 3px; + padding-top: 4px; } diff --git a/apps/lockfile-explorer-web/src/containers/PackageJsonViewer/index.tsx b/apps/lockfile-explorer-web/src/containers/PackageJsonViewer/index.tsx index fc0ddf98a24..937c3dfea74 100644 --- a/apps/lockfile-explorer-web/src/containers/PackageJsonViewer/index.tsx +++ b/apps/lockfile-explorer-web/src/containers/PackageJsonViewer/index.tsx @@ -128,7 +128,7 @@ export const PackageJsonViewer = (): JSX.Element => { }; return ( -
+
{ - console.log('Diagnostic: ', ...args); + if (window.appContext.debugMode) { + console.log('Diagnostic: ', ...args); + } }; diff --git a/apps/lockfile-explorer-web/src/start.css b/apps/lockfile-explorer-web/src/start.css index c38be1b7bc3..b2b26125d12 100644 --- a/apps/lockfile-explorer-web/src/start.css +++ b/apps/lockfile-explorer-web/src/start.css @@ -8,6 +8,7 @@ body { height: 100%; background-color: #f3f2f1; font-family: Tahoma, sans-serif; + box-sizing: border-box; } h5, From f2890d49bba736c7da65e91c3f5171bfcbeca815 Mon Sep 17 00:00:00 2001 From: Will Huang Date: Wed, 9 Nov 2022 16:30:57 -0800 Subject: [PATCH 04/24] feat: add forward jumping --- apps/lockfile-explorer-web/src/App.scss | 1 - .../src/containers/LockfileViewer/index.tsx | 16 +++++++++++++--- .../src/containers/LockfileViewer/styles.scss | 6 +++++- .../src/store/slices/entrySlice.ts | 16 +++++++++++++--- 4 files changed, 31 insertions(+), 8 deletions(-) diff --git a/apps/lockfile-explorer-web/src/App.scss b/apps/lockfile-explorer-web/src/App.scss index 3ece0475b14..2f740d64a98 100644 --- a/apps/lockfile-explorer-web/src/App.scss +++ b/apps/lockfile-explorer-web/src/App.scss @@ -25,7 +25,6 @@ .BodyContainer { height: 100vh; - overflow: hidden; display: flex; flex-direction: column; } diff --git a/apps/lockfile-explorer-web/src/containers/LockfileViewer/index.tsx b/apps/lockfile-explorer-web/src/containers/LockfileViewer/index.tsx index 15f26e4179f..2cd1dc79d96 100644 --- a/apps/lockfile-explorer-web/src/containers/LockfileViewer/index.tsx +++ b/apps/lockfile-explorer-web/src/containers/LockfileViewer/index.tsx @@ -9,6 +9,7 @@ import { ReactNull } from '../../types/ReactNull'; import { useAppDispatch, useAppSelector } from '../../store/hooks'; import { clearStackAndPush, + forwardStack, popStack, selectCurrentEntry, selectFilteredEntries, @@ -82,13 +83,17 @@ export const LockfileViewer = (): JSX.Element | ReactNull => { const pop = useCallback(() => { dispatch(popStack()); }, []); + const forward = useCallback(() => { + dispatch(forwardStack()); + }, []); useEffect(() => { setFilter(getFilterFromLocalStorage()); }, []); - // const selectedEntry = useAppSelector(selectCurrentEntry); const entryStack = useAppSelector((state) => state.entry.selectedEntryStack); + const entryForwardStack = useAppSelector((state) => state.entry.selectedEntryForwardStack); + console.log('entry forward stack: ', entryForwardStack); const dispatch = useAppDispatch(); @@ -164,9 +169,14 @@ export const LockfileViewer = (): JSX.Element | ReactNull => { />
- {entryStack.length > 1 ? : null} -
filter:
+
Filter:
+ +
{getEntriesToShow().map((lockfileEntryGroup) => ( diff --git a/apps/lockfile-explorer-web/src/containers/LockfileViewer/styles.scss b/apps/lockfile-explorer-web/src/containers/LockfileViewer/styles.scss index 6ab81a4a9a7..da22012d845 100644 --- a/apps/lockfile-explorer-web/src/containers/LockfileViewer/styles.scss +++ b/apps/lockfile-explorer-web/src/containers/LockfileViewer/styles.scss @@ -1,6 +1,6 @@ .ViewWrapper { // Logo is 70px tall - height: calc(100% - 70px); + height: calc(100% - 82px); padding-top: 12px; } @@ -25,6 +25,10 @@ align-items: center; margin-bottom: 12px; + input { + width: 100%; + } + & > * + * { margin-left: 12px; } diff --git a/apps/lockfile-explorer-web/src/store/slices/entrySlice.ts b/apps/lockfile-explorer-web/src/store/slices/entrySlice.ts index 1afea2da173..151ea4aeedc 100644 --- a/apps/lockfile-explorer-web/src/store/slices/entrySlice.ts +++ b/apps/lockfile-explorer-web/src/store/slices/entrySlice.ts @@ -1,12 +1,11 @@ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. // See LICENSE in the project root for license information. -import { createSlice, PayloadAction, Reducer } from '@reduxjs/toolkit'; +import { createSlice, current, PayloadAction, Reducer } from '@reduxjs/toolkit'; import { LockfileEntry, LockfileEntryFilter } from '../../parsing/LockfileEntry'; import { RootState } from '../index'; import { getBookmarksFromStorage, - getFilterFromLocalStorage, removeBookmarkFromLocalStorage, saveBookmarkToLocalStorage } from '../../helpers/localStorage'; @@ -18,6 +17,7 @@ type EntryState = { [key in LockfileEntryFilter]: boolean; }; selectedEntryStack: LockfileEntry[]; + selectedEntryForwardStack: LockfileEntry[]; bookmarkedEntries: LockfileEntry[]; }; @@ -30,6 +30,7 @@ const initialState: EntryState = { [LockfileEntryFilter.Doppelganger]: false }, selectedEntryStack: [], + selectedEntryForwardStack: [], bookmarkedEntries: [] }; @@ -53,13 +54,21 @@ const entrySlice = createSlice({ }, clearStackAndPush: (state, payload: PayloadAction) => { state.selectedEntryStack = [payload.payload]; + state.selectedEntryForwardStack = []; }, pushToStack: (state, payload: PayloadAction) => { state.selectedEntryStack.push(payload.payload); }, popStack: (state) => { if (state.selectedEntryStack.length > 1) { - state.selectedEntryStack.pop(); + const poppedEntry = state.selectedEntryStack.pop() as LockfileEntry; + state.selectedEntryForwardStack.push(poppedEntry); + } + }, + forwardStack: (state) => { + if (state.selectedEntryForwardStack.length > 0) { + const poppedEntry = state.selectedEntryForwardStack.pop() as LockfileEntry; + state.selectedEntryStack.push(poppedEntry); } }, addBookmark: (state, payload: PayloadAction) => { @@ -105,6 +114,7 @@ export const { clearStackAndPush, pushToStack, popStack, + forwardStack, addBookmark, removeBookmark } = entrySlice.actions; From a8de0c019623533070460390440a3aa49b51196e Mon Sep 17 00:00:00 2001 From: William Huang Date: Thu, 10 Nov 2022 10:43:50 -0800 Subject: [PATCH 05/24] feat: add dependency view panel --- apps/lockfile-explorer-web/src/App.scss | 1 + .../LockfileEntryDetailsView/index.tsx | 19 +++++++++++++++++++ .../LockfileEntryDetailsView/styles.scss | 10 ++++++++++ apps/lockfile-explorer/src/start.ts | 3 ++- 4 files changed, 32 insertions(+), 1 deletion(-) diff --git a/apps/lockfile-explorer-web/src/App.scss b/apps/lockfile-explorer-web/src/App.scss index 2f740d64a98..cc5f9906fae 100644 --- a/apps/lockfile-explorer-web/src/App.scss +++ b/apps/lockfile-explorer-web/src/App.scss @@ -27,4 +27,5 @@ height: 100vh; display: flex; flex-direction: column; + overflow-y: scroll; } diff --git a/apps/lockfile-explorer-web/src/containers/LockfileEntryDetailsView/index.tsx b/apps/lockfile-explorer-web/src/containers/LockfileEntryDetailsView/index.tsx index a568ed0245a..b563f03a700 100644 --- a/apps/lockfile-explorer-web/src/containers/LockfileEntryDetailsView/index.tsx +++ b/apps/lockfile-explorer-web/src/containers/LockfileEntryDetailsView/index.tsx @@ -124,6 +124,24 @@ export const LockfileEntryDetailsView = (): JSX.Element | ReactNull => { [selectedEntry] ); + const renderDependencyMetadata = (): JSX.Element | ReactNull => { + if (!inspectDependency) { + return ReactNull; + } + return ( +
+
+
Selected Dependency:
+ + {inspectDependency.name}: {inspectDependency.version} + +
+
package.json spec:
+
.pnpmfile.cjs:
+
+ ); + }; + const renderPeerDependencies = (): JSX.Element | ReactNull => { if (!selectedEntry) return ReactNull; const peerDeps = selectedEntry.dependencies.filter( @@ -230,6 +248,7 @@ export const LockfileEntryDetailsView = (): JSX.Element | ReactNull => {
+ {renderDependencyMetadata()} {renderPeerDependencies()} ); diff --git a/apps/lockfile-explorer-web/src/containers/LockfileEntryDetailsView/styles.scss b/apps/lockfile-explorer-web/src/containers/LockfileEntryDetailsView/styles.scss index 5a43b6adccf..3a3c9e53c34 100644 --- a/apps/lockfile-explorer-web/src/containers/LockfileEntryDetailsView/styles.scss +++ b/apps/lockfile-explorer-web/src/containers/LockfileEntryDetailsView/styles.scss @@ -4,6 +4,7 @@ display: flex; justify-content: space-between; height: 30%; + padding-top: 12px; & > * { width: calc(50% - 48px - 12px); } @@ -51,3 +52,12 @@ text-decoration: underline; } } + +.DependencyDetailInfo { + display: flex; + align-items: center; + span { + font-size: 12px; + margin-left: 4px; + } +} diff --git a/apps/lockfile-explorer/src/start.ts b/apps/lockfile-explorer/src/start.ts index e12760cd446..c28ec0c58de 100644 --- a/apps/lockfile-explorer/src/start.ts +++ b/apps/lockfile-explorer/src/start.ts @@ -56,7 +56,8 @@ app.post( const fileLocation = path.resolve(appState.projectRoot, projectPath, 'package.json'); if (!fs.existsSync(fileLocation)) { return res.status(400).send({ - message: `Could not load package.json file in location: ${projectPath}` + message: `Could not load package.json file for this package. Have you installed all the dependencies for this workspace?`, + error: `No package.json in location: ${projectPath}` }); } const packageJson = fs.readFileSync(fileLocation); From 1c75df0f6535b47deb377e99e749b5e2b1851c7f Mon Sep 17 00:00:00 2001 From: William Huang Date: Thu, 10 Nov 2022 11:26:14 -0800 Subject: [PATCH 06/24] feat: add workspace slice --- apps/lockfile-explorer-web/src/store/index.ts | 4 ++- .../src/store/slices/workspaceSlice.ts | 27 +++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 apps/lockfile-explorer-web/src/store/slices/workspaceSlice.ts diff --git a/apps/lockfile-explorer-web/src/store/index.ts b/apps/lockfile-explorer-web/src/store/index.ts index 9cef198be78..769dcea9e3b 100644 --- a/apps/lockfile-explorer-web/src/store/index.ts +++ b/apps/lockfile-explorer-web/src/store/index.ts @@ -4,11 +4,13 @@ import { configureStore } from '@reduxjs/toolkit'; import { entryReducer } from './slices/entrySlice'; +import { workspaceReducer } from './slices/workspaceSlice'; /* eslint @rushstack/typedef-var: off */ export const store = configureStore({ reducer: { - entry: entryReducer + entry: entryReducer, + workspace: workspaceReducer }, middleware: (getDefaultMiddleware) => getDefaultMiddleware({ diff --git a/apps/lockfile-explorer-web/src/store/slices/workspaceSlice.ts b/apps/lockfile-explorer-web/src/store/slices/workspaceSlice.ts new file mode 100644 index 00000000000..f6dad9419d7 --- /dev/null +++ b/apps/lockfile-explorer-web/src/store/slices/workspaceSlice.ts @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import { createSlice, PayloadAction, Reducer } from '@reduxjs/toolkit'; +import { ISpecChange } from '../../parsing/compareSpec'; + +type WorkspaceState = { + specChanges: Map; +}; + +const initialState: WorkspaceState = { + specChanges: new Map() +}; + +const workspaceSlice = createSlice({ + name: 'workspace', + initialState, + reducers: { + loadSpecChanges: (state, payload: PayloadAction>) => { + state.specChanges = payload.payload; + } + } +}); + +export const { loadSpecChanges } = workspaceSlice.actions; + +export const workspaceReducer: Reducer = workspaceSlice.reducer; From e70a266d6319b9239dde0137502219676cb9a215 Mon Sep 17 00:00:00 2001 From: William Huang Date: Thu, 10 Nov 2022 11:27:14 -0800 Subject: [PATCH 07/24] chore: ms copyright --- apps/lockfile-explorer-web/src/helpers/localStorage.ts | 3 +++ apps/lockfile-explorer-web/src/store/slices/entrySlice.ts | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/lockfile-explorer-web/src/helpers/localStorage.ts b/apps/lockfile-explorer-web/src/helpers/localStorage.ts index 747560df086..f46c98d5452 100644 --- a/apps/lockfile-explorer-web/src/helpers/localStorage.ts +++ b/apps/lockfile-explorer-web/src/helpers/localStorage.ts @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + import { LockfileEntry } from '../parsing/LockfileEntry'; const BOOKMARK_KEY = 'LOCKFILE_EXPLORER_BOOKMARKS'; diff --git a/apps/lockfile-explorer-web/src/store/slices/entrySlice.ts b/apps/lockfile-explorer-web/src/store/slices/entrySlice.ts index 151ea4aeedc..f50a2bbae83 100644 --- a/apps/lockfile-explorer-web/src/store/slices/entrySlice.ts +++ b/apps/lockfile-explorer-web/src/store/slices/entrySlice.ts @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. // See LICENSE in the project root for license information. -import { createSlice, current, PayloadAction, Reducer } from '@reduxjs/toolkit'; +import { createSlice, PayloadAction, Reducer } from '@reduxjs/toolkit'; import { LockfileEntry, LockfileEntryFilter } from '../../parsing/LockfileEntry'; import { RootState } from '../index'; import { From 5fd0ca7bc6d45eab99d3527ca4919bd5bcbcb7a8 Mon Sep 17 00:00:00 2001 From: Will Huang Date: Thu, 10 Nov 2022 13:12:19 -0800 Subject: [PATCH 08/24] feat: UI improvements --- .../containers/PackageJsonViewer/index.tsx | 35 +++++++++++++------ .../containers/PackageJsonViewer/styles.scss | 9 +++++ .../src/parsing/LockfileDependency.ts | 2 +- 3 files changed, 35 insertions(+), 11 deletions(-) diff --git a/apps/lockfile-explorer-web/src/containers/PackageJsonViewer/index.tsx b/apps/lockfile-explorer-web/src/containers/PackageJsonViewer/index.tsx index 937c3dfea74..9818aa59150 100644 --- a/apps/lockfile-explorer-web/src/containers/PackageJsonViewer/index.tsx +++ b/apps/lockfile-explorer-web/src/containers/PackageJsonViewer/index.tsx @@ -5,33 +5,36 @@ import React, { useCallback, useEffect, useState } from 'react'; import { readPnpmfile, readPackageSpec, readPackageJson } from '../../parsing/getPackageFiles'; import styles from './styles.scss'; import appStyles from '../../App.scss'; -import { useAppSelector } from '../../store/hooks'; +import { useAppDispatch, useAppSelector } from '../../store/hooks'; import { selectCurrentEntry } from '../../store/slices/entrySlice'; import { IPackageJson } from '../../types/IPackageJson'; import { compareSpec, ISpecChange } from '../../parsing/compareSpec'; import { FilterBar } from '../../components/FilterBar'; +import { loadSpecChanges } from '../../store/slices/workspaceSlice'; enum PackageView { PACKAGE_JSON, - CJS, + PACKAGE_SPEC, PARSED_PACKAGE_JSON } export const PackageJsonViewer = (): JSX.Element => { + const dispatch = useAppDispatch(); const [packageJSON, setPackageJSON] = useState(undefined); const [parsedPackageJSON, setParsedPackageJSON] = useState(undefined); - const [specChanges, setSpecChanges] = useState>(new Map()); - const [cjs, setCjs] = useState(''); + const [pnpmfile, setPnpmfile] = useState(''); const selectedEntry = useAppSelector(selectCurrentEntry); + const specChanges = useAppSelector((state) => state.workspace.specChanges); + const [selection, setSelection] = useState(PackageView.PARSED_PACKAGE_JSON); const cb = useCallback((s: PackageView) => () => setSelection(s), []); useEffect(() => { async function loadPackageDetails(packageName: string): Promise { - const cjsFile = await readPnpmfile(); - setCjs(cjsFile); + const pnpmfile = await readPnpmfile(); + setPnpmfile(pnpmfile); const packageJSONFile = await readPackageJson(packageName); setPackageJSON(packageJSONFile); const parsedJSON = await readPackageSpec(packageName); @@ -39,7 +42,7 @@ export const PackageJsonViewer = (): JSX.Element => { if (packageJSONFile && parsedJSON) { const diffDeps = compareSpec(packageJSONFile, parsedJSON); - setSpecChanges(diffDeps); + dispatch(loadSpecChanges(diffDeps)); } } if (selectedEntry) { @@ -106,12 +109,20 @@ export const PackageJsonViewer = (): JSX.Element => { case PackageView.PACKAGE_JSON: if (!packageJSON) return null; return
{JSON.stringify(packageJSON, null, 2)}
; - case PackageView.CJS: - return
{cjs}
; + case PackageView.PACKAGE_SPEC: + return
{pnpmfile}
; case PackageView.PARSED_PACKAGE_JSON: if (!parsedPackageJSON) return null; return (
+
+
Package Name:
+

{selectedEntry?.displayText}

+
+
+
Version:
+

{selectedEntry?.entryPackageVersion}

+
Dependencies
{parsedPackageJSON.dependencies && Object.entries(parsedPackageJSON.dependencies).map(renderDep)}
Dev Dependencies
@@ -141,7 +152,11 @@ export const PackageJsonViewer = (): JSX.Element => { active: selection === PackageView.PACKAGE_JSON, onClick: cb(PackageView.PACKAGE_JSON) }, - { text: '.pnpmfile.cjs', active: selection === PackageView.CJS, onClick: cb(PackageView.CJS) } + { + text: '.pnpmfile.cjs', + active: selection === PackageView.PACKAGE_SPEC, + onClick: cb(PackageView.PACKAGE_SPEC) + } ]} />
diff --git a/apps/lockfile-explorer-web/src/containers/PackageJsonViewer/styles.scss b/apps/lockfile-explorer-web/src/containers/PackageJsonViewer/styles.scss index 18f6a75ed90..b15cd1d4754 100644 --- a/apps/lockfile-explorer-web/src/containers/PackageJsonViewer/styles.scss +++ b/apps/lockfile-explorer-web/src/containers/PackageJsonViewer/styles.scss @@ -37,3 +37,12 @@ margin-top: 8px; } } + +.PackageSpecEntry { + & > * { + display: inline; + } + & > * + * { + margin-left: 12px; + } +} diff --git a/apps/lockfile-explorer-web/src/parsing/LockfileDependency.ts b/apps/lockfile-explorer-web/src/parsing/LockfileDependency.ts index efc5aaf518e..586d19184ba 100644 --- a/apps/lockfile-explorer-web/src/parsing/LockfileDependency.ts +++ b/apps/lockfile-explorer-web/src/parsing/LockfileDependency.ts @@ -87,7 +87,7 @@ export class LockfileDependency { ? node.peerDependenciesMeta[this.name].optional : false }; - this.entryId = 'peer dep:' + this.name; + this.entryId = 'Peer: ' + this.name; } else { console.error('Peer dependencies info missing!', node); } From 800fe221b00bbc5e8911b91ef4b8e23d953f4255 Mon Sep 17 00:00:00 2001 From: Will Huang Date: Thu, 10 Nov 2022 14:03:14 -0800 Subject: [PATCH 09/24] feat: finish UI --- .../LockfileEntryDetailsView/index.tsx | 25 ++++++++++++++++--- .../LockfileEntryDetailsView/styles.scss | 6 +++++ .../src/containers/LockfileViewer/index.tsx | 4 +-- .../containers/PackageJsonViewer/index.tsx | 7 +++--- .../SelectedEntryPreview/styles.scss | 3 ++- .../src/helpers/displaySpecChanges.tsx | 15 +++++++++++ 6 files changed, 50 insertions(+), 10 deletions(-) create mode 100644 apps/lockfile-explorer-web/src/helpers/displaySpecChanges.tsx diff --git a/apps/lockfile-explorer-web/src/containers/LockfileEntryDetailsView/index.tsx b/apps/lockfile-explorer-web/src/containers/LockfileEntryDetailsView/index.tsx index b563f03a700..6db83ed8f90 100644 --- a/apps/lockfile-explorer-web/src/containers/LockfileEntryDetailsView/index.tsx +++ b/apps/lockfile-explorer-web/src/containers/LockfileEntryDetailsView/index.tsx @@ -10,6 +10,7 @@ import { pushToStack, selectCurrentEntry } from '../../store/slices/entrySlice'; import { ReactNull } from '../../types/ReactNull'; import { LockfileEntry } from '../../parsing/LockfileEntry'; import { logDiagnosticInfo } from '../../helpers/logDiagnosticInfo'; +import { displaySpecChanges } from '../../helpers/displaySpecChanges'; enum InfluencerTypes { Determinant, @@ -23,6 +24,7 @@ interface IInfluencerType { export const LockfileEntryDetailsView = (): JSX.Element | ReactNull => { const selectedEntry = useAppSelector(selectCurrentEntry); + const specChanges = useAppSelector((state) => state.workspace.specChanges); const dispatch = useAppDispatch(); const [inspectDependency, setInspectDependency] = useState(null); @@ -128,16 +130,33 @@ export const LockfileEntryDetailsView = (): JSX.Element | ReactNull => { if (!inspectDependency) { return ReactNull; } + console.log('inspect dependency: ', inspectDependency, specChanges.get(inspectDependency.name)); return ( -
+
Selected Dependency:
{inspectDependency.name}: {inspectDependency.version}
-
package.json spec:
-
.pnpmfile.cjs:
+
+
package.json spec:
+ + {inspectDependency.dependencyType === IDependencyType.PEER_DEPENDENCY + ? `"${inspectDependency.peerDependencyMeta.version}" ${ + inspectDependency.peerDependencyMeta.optional ? 'Optional' : 'Required' + } Peer` + : inspectDependency.version} + +
+
+
.pnpmfile.cjs:
+ + {specChanges.has(inspectDependency.name) + ? displaySpecChanges(specChanges, inspectDependency.name) + : 'No Effect'} + +
); }; diff --git a/apps/lockfile-explorer-web/src/containers/LockfileEntryDetailsView/styles.scss b/apps/lockfile-explorer-web/src/containers/LockfileEntryDetailsView/styles.scss index 3a3c9e53c34..00a907d994f 100644 --- a/apps/lockfile-explorer-web/src/containers/LockfileEntryDetailsView/styles.scss +++ b/apps/lockfile-explorer-web/src/containers/LockfileEntryDetailsView/styles.scss @@ -44,6 +44,12 @@ overflow: scroll; } +.DependencyDetails { + margin-top: 12px; + height: 4%; + overflow: scroll; +} + .InfluencerEntry { display: block; cursor: pointer; diff --git a/apps/lockfile-explorer-web/src/containers/LockfileViewer/index.tsx b/apps/lockfile-explorer-web/src/containers/LockfileViewer/index.tsx index 2cd1dc79d96..6d800771983 100644 --- a/apps/lockfile-explorer-web/src/containers/LockfileViewer/index.tsx +++ b/apps/lockfile-explorer-web/src/containers/LockfileViewer/index.tsx @@ -73,6 +73,7 @@ const multipleVersions = (entries: LockfileEntry[]): boolean => { }; export const LockfileViewer = (): JSX.Element | ReactNull => { + const dispatch = useAppDispatch(); const [filter, setFilter] = useState(''); const entries = useAppSelector(selectFilteredEntries); const activeFilters = useAppSelector((state) => state.entry.filters); @@ -93,9 +94,6 @@ export const LockfileViewer = (): JSX.Element | ReactNull => { const entryStack = useAppSelector((state) => state.entry.selectedEntryStack); const entryForwardStack = useAppSelector((state) => state.entry.selectedEntryForwardStack); - console.log('entry forward stack: ', entryForwardStack); - - const dispatch = useAppDispatch(); if (!entries) return ReactNull; diff --git a/apps/lockfile-explorer-web/src/containers/PackageJsonViewer/index.tsx b/apps/lockfile-explorer-web/src/containers/PackageJsonViewer/index.tsx index 9818aa59150..8b2e6fc4c54 100644 --- a/apps/lockfile-explorer-web/src/containers/PackageJsonViewer/index.tsx +++ b/apps/lockfile-explorer-web/src/containers/PackageJsonViewer/index.tsx @@ -11,6 +11,7 @@ import { IPackageJson } from '../../types/IPackageJson'; import { compareSpec, ISpecChange } from '../../parsing/compareSpec'; import { FilterBar } from '../../components/FilterBar'; import { loadSpecChanges } from '../../store/slices/workspaceSlice'; +import { displaySpecChanges } from '../../helpers/displaySpecChanges'; enum PackageView { PACKAGE_JSON, @@ -67,7 +68,7 @@ export const PackageJsonViewer = (): JSX.Element => { {dep}: {version} {' '} - [Added by .pnpmfile.cjs] + {displaySpecChanges(specChanges, dep)}

); case 'diff': @@ -76,7 +77,7 @@ export const PackageJsonViewer = (): JSX.Element => { {dep}: {version} {' '} - [Changed from {specChanges.get(dep)?.from}] + {displaySpecChanges(specChanges, dep)}

); case 'remove': @@ -85,7 +86,7 @@ export const PackageJsonViewer = (): JSX.Element => { {dep}: {version} {' '} - [Deleted by .pnpmfile.cjs] + {displaySpecChanges(specChanges, dep)}

); default: diff --git a/apps/lockfile-explorer-web/src/containers/SelectedEntryPreview/styles.scss b/apps/lockfile-explorer-web/src/containers/SelectedEntryPreview/styles.scss index cc733dbaeb2..0dee14a364d 100644 --- a/apps/lockfile-explorer-web/src/containers/SelectedEntryPreview/styles.scss +++ b/apps/lockfile-explorer-web/src/containers/SelectedEntryPreview/styles.scss @@ -5,7 +5,8 @@ box-shadow: $ms-depth-shadow-8; background-color: $ms-color-gray10; padding: 24px; - height: 10%; + height: 6%; + overflow: scroll; p { font-size: 12px; } diff --git a/apps/lockfile-explorer-web/src/helpers/displaySpecChanges.tsx b/apps/lockfile-explorer-web/src/helpers/displaySpecChanges.tsx new file mode 100644 index 00000000000..ac60d65a730 --- /dev/null +++ b/apps/lockfile-explorer-web/src/helpers/displaySpecChanges.tsx @@ -0,0 +1,15 @@ +import React from 'react'; +import { ISpecChange } from '../parsing/compareSpec'; + +export const displaySpecChanges = (specChanges: Map, dep: string): string => { + switch (specChanges.get(dep)?.type) { + case 'add': + return '[Added by .pnpmfile.cjs]'; + case 'diff': + return '[Changed from {specChanges.get(dep)?.from}]'; + case 'remove': + return '[Deleted by .pnpmfile.cjs]'; + default: + return 'No Change'; + } +}; From cfb68286b0aa7ca8b177ddcc2a2510dbd04a493d Mon Sep 17 00:00:00 2001 From: Will Huang Date: Thu, 10 Nov 2022 16:04:38 -0800 Subject: [PATCH 10/24] chore: eslint errors --- .../src/containers/PackageJsonViewer/index.tsx | 2 +- .../{displaySpecChanges.tsx => displaySpecChanges.ts} | 1 - apps/lockfile-explorer-web/src/helpers/localStorage.ts | 10 +++++----- .../src/store/slices/workspaceSlice.ts | 2 ++ 4 files changed, 8 insertions(+), 7 deletions(-) rename apps/lockfile-explorer-web/src/helpers/{displaySpecChanges.tsx => displaySpecChanges.ts} (94%) diff --git a/apps/lockfile-explorer-web/src/containers/PackageJsonViewer/index.tsx b/apps/lockfile-explorer-web/src/containers/PackageJsonViewer/index.tsx index 8b2e6fc4c54..bffa4615f61 100644 --- a/apps/lockfile-explorer-web/src/containers/PackageJsonViewer/index.tsx +++ b/apps/lockfile-explorer-web/src/containers/PackageJsonViewer/index.tsx @@ -8,7 +8,7 @@ import appStyles from '../../App.scss'; import { useAppDispatch, useAppSelector } from '../../store/hooks'; import { selectCurrentEntry } from '../../store/slices/entrySlice'; import { IPackageJson } from '../../types/IPackageJson'; -import { compareSpec, ISpecChange } from '../../parsing/compareSpec'; +import { compareSpec } from '../../parsing/compareSpec'; import { FilterBar } from '../../components/FilterBar'; import { loadSpecChanges } from '../../store/slices/workspaceSlice'; import { displaySpecChanges } from '../../helpers/displaySpecChanges'; diff --git a/apps/lockfile-explorer-web/src/helpers/displaySpecChanges.tsx b/apps/lockfile-explorer-web/src/helpers/displaySpecChanges.ts similarity index 94% rename from apps/lockfile-explorer-web/src/helpers/displaySpecChanges.tsx rename to apps/lockfile-explorer-web/src/helpers/displaySpecChanges.ts index ac60d65a730..9005d1563fc 100644 --- a/apps/lockfile-explorer-web/src/helpers/displaySpecChanges.tsx +++ b/apps/lockfile-explorer-web/src/helpers/displaySpecChanges.ts @@ -1,4 +1,3 @@ -import React from 'react'; import { ISpecChange } from '../parsing/compareSpec'; export const displaySpecChanges = (specChanges: Map, dep: string): string => { diff --git a/apps/lockfile-explorer-web/src/helpers/localStorage.ts b/apps/lockfile-explorer-web/src/helpers/localStorage.ts index f46c98d5452..d98af67a48e 100644 --- a/apps/lockfile-explorer-web/src/helpers/localStorage.ts +++ b/apps/lockfile-explorer-web/src/helpers/localStorage.ts @@ -3,10 +3,10 @@ import { LockfileEntry } from '../parsing/LockfileEntry'; -const BOOKMARK_KEY = 'LOCKFILE_EXPLORER_BOOKMARKS'; +const BOOKMARK_KEY: string = 'LOCKFILE_EXPLORER_BOOKMARKS'; export const getBookmarksFromStorage = (): Set => { - let currBookmarks = JSON.parse(localStorage.getItem(BOOKMARK_KEY) || '{}'); + const currBookmarks = JSON.parse(localStorage.getItem(BOOKMARK_KEY) || '{}'); const bookmarkSet = new Set(); for (const key of Object.keys(currBookmarks)) { bookmarkSet.add(key); @@ -16,19 +16,19 @@ export const getBookmarksFromStorage = (): Set => { export const saveBookmarkToLocalStorage = (entry: LockfileEntry): void => { const key = entry.rawEntryId; - let currBookmarks = JSON.parse(localStorage.getItem(BOOKMARK_KEY) || '{}'); + const currBookmarks = JSON.parse(localStorage.getItem(BOOKMARK_KEY) || '{}'); currBookmarks[key] = true; localStorage.setItem(BOOKMARK_KEY, JSON.stringify(currBookmarks)); }; export const removeBookmarkFromLocalStorage = (entry: LockfileEntry): void => { const key = entry.rawEntryId; - let currBookmarks = JSON.parse(localStorage.getItem(BOOKMARK_KEY) || '{}'); + const currBookmarks = JSON.parse(localStorage.getItem(BOOKMARK_KEY) || '{}'); delete currBookmarks[key]; localStorage.setItem(BOOKMARK_KEY, JSON.stringify(currBookmarks)); }; -const FILTER_KEY = 'LOCKFILE_EXPLORER_FILTER'; +const FILTER_KEY: string = 'LOCKFILE_EXPLORER_FILTER'; export const saveFilterToLocalStorage = (filter: string): void => { localStorage.setItem(FILTER_KEY, filter); }; diff --git a/apps/lockfile-explorer-web/src/store/slices/workspaceSlice.ts b/apps/lockfile-explorer-web/src/store/slices/workspaceSlice.ts index f6dad9419d7..34cbe0b937e 100644 --- a/apps/lockfile-explorer-web/src/store/slices/workspaceSlice.ts +++ b/apps/lockfile-explorer-web/src/store/slices/workspaceSlice.ts @@ -4,6 +4,7 @@ import { createSlice, PayloadAction, Reducer } from '@reduxjs/toolkit'; import { ISpecChange } from '../../parsing/compareSpec'; +// eslint-disable-next-line @typescript-eslint/consistent-type-definitions type WorkspaceState = { specChanges: Map; }; @@ -12,6 +13,7 @@ const initialState: WorkspaceState = { specChanges: new Map() }; +/* eslint @rushstack/typedef-var: off */ const workspaceSlice = createSlice({ name: 'workspace', initialState, From 65b9b877d6d9f603acdf27ccb0899c238cac50f7 Mon Sep 17 00:00:00 2001 From: Will Huang Date: Fri, 11 Nov 2022 12:40:06 -0800 Subject: [PATCH 11/24] chore: add changefile --- .../will-lockfile-explorer-step3_2022-11-11-20-39.json | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 common/changes/@rushstack/lockfile-explorer/will-lockfile-explorer-step3_2022-11-11-20-39.json diff --git a/common/changes/@rushstack/lockfile-explorer/will-lockfile-explorer-step3_2022-11-11-20-39.json b/common/changes/@rushstack/lockfile-explorer/will-lockfile-explorer-step3_2022-11-11-20-39.json new file mode 100644 index 00000000000..9752f6e8f9f --- /dev/null +++ b/common/changes/@rushstack/lockfile-explorer/will-lockfile-explorer-step3_2022-11-11-20-39.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@rushstack/lockfile-explorer", + "comment": "Fixed issues related to influencer resolution in lockfile explorer; Cleaned up UI & improved UX.", + "type": "patch" + } + ], + "packageName": "@rushstack/lockfile-explorer" +} \ No newline at end of file From 5f9358687f1919fbe6f22626a783079dfcafd15c Mon Sep 17 00:00:00 2001 From: Will Huang Date: Mon, 14 Nov 2022 13:58:05 -0800 Subject: [PATCH 12/24] fix: websocket port --- apps/lockfile-explorer-web/webpack.config.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/lockfile-explorer-web/webpack.config.js b/apps/lockfile-explorer-web/webpack.config.js index 216c7ad40e3..5d8baf21a38 100644 --- a/apps/lockfile-explorer-web/webpack.config.js +++ b/apps/lockfile-explorer-web/webpack.config.js @@ -28,6 +28,11 @@ module.exports = function createConfig(env, argv) { port: 8096, static: { directory: path.join(__dirname, 'dist') + }, + client: { + webSocketURL: { + port: 8096 + } } } } From b22126051b0b04d168f25960cfc832860d3e96b8 Mon Sep 17 00:00:00 2001 From: Will Huang Date: Wed, 16 Nov 2022 17:46:37 -0800 Subject: [PATCH 13/24] feat: fix some PR comments --- apps/lockfile-explorer-web/src/App.tsx | 2 +- .../LockfileEntryDetailsView/index.tsx | 12 +- .../src/containers/LockfileViewer/index.tsx | 39 ++--- .../src/containers/LockfileViewer/styles.scss | 4 +- .../src/containers/LogoPanel/index.tsx | 19 ++- .../src/containers/LogoPanel/styles.scss | 13 +- .../containers/PackageJsonViewer/index.tsx | 143 +++++++++++------- .../containers/PackageJsonViewer/styles.scss | 13 ++ ...kfile-explorer-step3_2022-11-11-20-39.json | 4 +- 9 files changed, 155 insertions(+), 94 deletions(-) diff --git a/apps/lockfile-explorer-web/src/App.tsx b/apps/lockfile-explorer-web/src/App.tsx index 0e9d68151ea..bbe5c0a4e34 100644 --- a/apps/lockfile-explorer-web/src/App.tsx +++ b/apps/lockfile-explorer-web/src/App.tsx @@ -34,7 +34,6 @@ export const App = (): JSX.Element => {
-
@@ -43,6 +42,7 @@ export const App = (): JSX.Element => {
+
diff --git a/apps/lockfile-explorer-web/src/containers/LockfileEntryDetailsView/index.tsx b/apps/lockfile-explorer-web/src/containers/LockfileEntryDetailsView/index.tsx index 6db83ed8f90..511451d482b 100644 --- a/apps/lockfile-explorer-web/src/containers/LockfileEntryDetailsView/index.tsx +++ b/apps/lockfile-explorer-web/src/containers/LockfileEntryDetailsView/index.tsx @@ -12,14 +12,14 @@ import { LockfileEntry } from '../../parsing/LockfileEntry'; import { logDiagnosticInfo } from '../../helpers/logDiagnosticInfo'; import { displaySpecChanges } from '../../helpers/displaySpecChanges'; -enum InfluencerTypes { +enum DependencyType { Determinant, TransitiveReferrer } interface IInfluencerType { entry: LockfileEntry; - type: InfluencerTypes; + type: DependencyType; } export const LockfileEntryDetailsView = (): JSX.Element | ReactNull => { @@ -103,13 +103,13 @@ export const LockfileEntryDetailsView = (): JSX.Element | ReactNull => { for (const determinant of determinants.values()) { influencers.push({ entry: determinant, - type: InfluencerTypes.Determinant + type: DependencyType.Determinant }); } for (const referrer of transitiveReferrers.values()) { influencers.push({ entry: referrer, - type: InfluencerTypes.TransitiveReferrer + type: DependencyType.TransitiveReferrer }); } setInfluencers(influencers); @@ -185,7 +185,7 @@ export const LockfileEntryDetailsView = (): JSX.Element | ReactNull => {
Determinants:
{influencers - .filter((inf) => inf.type === InfluencerTypes.Determinant) + .filter((inf) => inf.type === DependencyType.Determinant) .map(({ entry }) => ( { ))}
Transitive Referencers:
{influencers - .filter((inf) => inf.type === InfluencerTypes.TransitiveReferrer) + .filter((inf) => inf.type === DependencyType.TransitiveReferrer) .map(({ entry }) => (
{ }; const changeFilter = useCallback( - (filter: LockfileEntryFilter) => - (ev: React.ChangeEvent): void => { - dispatch(selectFilter({ filter, state: ev.target.checked })); - }, + (filter: LockfileEntryFilter, enabled: boolean) => (): void => { + dispatch(selectFilter({ filter, state: enabled })); + }, [] ); @@ -169,10 +168,10 @@ export const LockfileViewer = (): JSX.Element | ReactNull => {
Filter:
- -
@@ -183,20 +182,24 @@ export const LockfileViewer = (): JSX.Element | ReactNull => {
Filters
-
- +
+
Must have side-by-side versions
-
- +
+
Must have doppelgangers
diff --git a/apps/lockfile-explorer-web/src/containers/LockfileViewer/styles.scss b/apps/lockfile-explorer-web/src/containers/LockfileViewer/styles.scss index da22012d845..78b50730674 100644 --- a/apps/lockfile-explorer-web/src/containers/LockfileViewer/styles.scss +++ b/apps/lockfile-explorer-web/src/containers/LockfileViewer/styles.scss @@ -1,6 +1,5 @@ .ViewWrapper { - // Logo is 70px tall - height: calc(100% - 82px); + height: 100%; padding-top: 12px; } @@ -73,6 +72,7 @@ .filterOption { display: flex; align-items: center; + cursor: pointer; & > * + * { margin-left: 4px; } diff --git a/apps/lockfile-explorer-web/src/containers/LogoPanel/index.tsx b/apps/lockfile-explorer-web/src/containers/LogoPanel/index.tsx index c5cb083280a..c3aebdd90d4 100644 --- a/apps/lockfile-explorer-web/src/containers/LogoPanel/index.tsx +++ b/apps/lockfile-explorer-web/src/containers/LogoPanel/index.tsx @@ -11,20 +11,23 @@ export const LogoPanel = (): JSX.Element => { return (
); }; diff --git a/apps/lockfile-explorer-web/src/containers/LogoPanel/styles.scss b/apps/lockfile-explorer-web/src/containers/LogoPanel/styles.scss index 5e409eb526c..03785342b4b 100644 --- a/apps/lockfile-explorer-web/src/containers/LogoPanel/styles.scss +++ b/apps/lockfile-explorer-web/src/containers/LogoPanel/styles.scss @@ -1,13 +1,12 @@ .LogoPanel { height: 70px; -} - -.LogoPanel a { - text-decoration: none; display: flex; & > * + * { margin-left: 12px; } + a { + text-decoration: none; + } } .Icon { @@ -23,6 +22,12 @@ .Title2 { padding-top: 5px; height: 20px; + display: flex; + align-content: flex-end; + & > div { + padding-top: 0; + margin-left: 4px; + } } .Image { diff --git a/apps/lockfile-explorer-web/src/containers/PackageJsonViewer/index.tsx b/apps/lockfile-explorer-web/src/containers/PackageJsonViewer/index.tsx index bffa4615f61..81209e14566 100644 --- a/apps/lockfile-explorer-web/src/containers/PackageJsonViewer/index.tsx +++ b/apps/lockfile-explorer-web/src/containers/PackageJsonViewer/index.tsx @@ -58,52 +58,69 @@ export const PackageJsonViewer = (): JSX.Element => { } }, [selectedEntry]); - const renderDep = (dependencyDetails: [string, string]): JSX.Element => { - const [dep, version] = dependencyDetails; - if (specChanges.has(dep)) { - switch (specChanges.get(dep)?.type) { - case 'add': - return ( -

- - {dep}: {version} - {' '} - {displaySpecChanges(specChanges, dep)} -

- ); - case 'diff': - return ( -

- - {dep}: {version} - {' '} - {displaySpecChanges(specChanges, dep)} -

- ); - case 'remove': - return ( -

- - {dep}: {version} - {' '} - {displaySpecChanges(specChanges, dep)} -

- ); - default: - return ( -

- {dep}: {version} -

- ); + const renderDep = + (name: boolean) => + (dependencyDetails: [string, string]): JSX.Element => { + const [dep, version] = dependencyDetails; + if (specChanges.has(dep)) { + switch (specChanges.get(dep)?.type) { + case 'add': + if (name) { + return ( +

+ {dep} +

+ ); + } else { + return ( +

+ {version} {displaySpecChanges(specChanges, dep)} +

+ ); + } + case 'diff': + if (name) { + return ( +

+ {dep} +

+ ); + } else { + return ( +

+ {version} {displaySpecChanges(specChanges, dep)} +

+ ); + } + case 'remove': + if (name) { + return ( +

+ {dep} +

+ ); + } else { + return ( +

+ {version} {displaySpecChanges(specChanges, dep)} +

+ ); + } + default: + if (name) { + return

{dep}:

; + } else { + return

{version}

; + } + } + } else { + if (name) { + return

{dep}:

; + } else { + return

{version}

; + } } - } else { - return ( -

- {dep}: {version} -

- ); - } - }; + }; const renderFile = (): JSX.Element | null => { switch (selection) { @@ -124,14 +141,34 @@ export const PackageJsonViewer = (): JSX.Element => {
Version:

{selectedEntry?.entryPackageVersion}

-
Dependencies
- {parsedPackageJSON.dependencies && Object.entries(parsedPackageJSON.dependencies).map(renderDep)} -
Dev Dependencies
- {parsedPackageJSON.devDependencies && - Object.entries(parsedPackageJSON.devDependencies).map(renderDep)} -
Peer Dependencies
- {parsedPackageJSON.peerDependencies && - Object.entries(parsedPackageJSON.peerDependencies).map(renderDep)} +
+
+
Dependencies
+ {parsedPackageJSON.dependencies && + Object.entries(parsedPackageJSON.dependencies).map(renderDep(true))} + +
Dev Dependencies
+ {parsedPackageJSON.devDependencies && + Object.entries(parsedPackageJSON.devDependencies).map(renderDep(true))} + +
Peer Dependencies
+ {parsedPackageJSON.peerDependencies && + Object.entries(parsedPackageJSON.peerDependencies).map(renderDep(true))} +
+
+
 
+ {parsedPackageJSON.dependencies && + Object.entries(parsedPackageJSON.dependencies).map(renderDep(false))} + +
 
+ {parsedPackageJSON.devDependencies && + Object.entries(parsedPackageJSON.devDependencies).map(renderDep(false))} + +
 
+ {parsedPackageJSON.peerDependencies && + Object.entries(parsedPackageJSON.peerDependencies).map(renderDep(false))} +
+
); default: diff --git a/apps/lockfile-explorer-web/src/containers/PackageJsonViewer/styles.scss b/apps/lockfile-explorer-web/src/containers/PackageJsonViewer/styles.scss index b15cd1d4754..3b8c866609e 100644 --- a/apps/lockfile-explorer-web/src/containers/PackageJsonViewer/styles.scss +++ b/apps/lockfile-explorer-web/src/containers/PackageJsonViewer/styles.scss @@ -46,3 +46,16 @@ margin-left: 12px; } } + +.DependencyRows { + display: flex; + align-items: flex-start; + & > * + * { + margin-left: 12px; + } + div { + & > * + * { + margin-top: 4px; + } + } +} diff --git a/common/changes/@rushstack/lockfile-explorer/will-lockfile-explorer-step3_2022-11-11-20-39.json b/common/changes/@rushstack/lockfile-explorer/will-lockfile-explorer-step3_2022-11-11-20-39.json index 9752f6e8f9f..d739b070780 100644 --- a/common/changes/@rushstack/lockfile-explorer/will-lockfile-explorer-step3_2022-11-11-20-39.json +++ b/common/changes/@rushstack/lockfile-explorer/will-lockfile-explorer-step3_2022-11-11-20-39.json @@ -3,8 +3,8 @@ { "packageName": "@rushstack/lockfile-explorer", "comment": "Fixed issues related to influencer resolution in lockfile explorer; Cleaned up UI & improved UX.", - "type": "patch" + "type": "minor" } ], "packageName": "@rushstack/lockfile-explorer" -} \ No newline at end of file +} From 90a88d2e5680b2c185f3a1d0bef5e0d6767cebe8 Mon Sep 17 00:00:00 2001 From: William Huang Date: Thu, 17 Nov 2022 10:11:14 -0800 Subject: [PATCH 14/24] feat: display modified package spec in tab header --- .../containers/LockfileEntryDetailsView/index.tsx | 1 - .../src/containers/LockfileViewer/index.tsx | 4 ++-- .../src/containers/PackageJsonViewer/index.tsx | 11 ++++++----- .../src/helpers/isEntryModified.ts | 15 +++++++++++++++ 4 files changed, 23 insertions(+), 8 deletions(-) create mode 100644 apps/lockfile-explorer-web/src/helpers/isEntryModified.ts diff --git a/apps/lockfile-explorer-web/src/containers/LockfileEntryDetailsView/index.tsx b/apps/lockfile-explorer-web/src/containers/LockfileEntryDetailsView/index.tsx index 511451d482b..6834470a5cc 100644 --- a/apps/lockfile-explorer-web/src/containers/LockfileEntryDetailsView/index.tsx +++ b/apps/lockfile-explorer-web/src/containers/LockfileEntryDetailsView/index.tsx @@ -130,7 +130,6 @@ export const LockfileEntryDetailsView = (): JSX.Element | ReactNull => { if (!inspectDependency) { return ReactNull; } - console.log('inspect dependency: ', inspectDependency, specChanges.get(inspectDependency.name)); return (
diff --git a/apps/lockfile-explorer-web/src/containers/LockfileViewer/index.tsx b/apps/lockfile-explorer-web/src/containers/LockfileViewer/index.tsx index 4bab7cd3778..d6e1daec8f6 100644 --- a/apps/lockfile-explorer-web/src/containers/LockfileViewer/index.tsx +++ b/apps/lockfile-explorer-web/src/containers/LockfileViewer/index.tsx @@ -189,7 +189,7 @@ export const LockfileViewer = (): JSX.Element | ReactNull => { !activeFilters[LockfileEntryFilter.SideBySide] )} > - +
Must have side-by-side versions
{ !activeFilters[LockfileEntryFilter.Doppelganger] )} > - +
Must have doppelgangers
diff --git a/apps/lockfile-explorer-web/src/containers/PackageJsonViewer/index.tsx b/apps/lockfile-explorer-web/src/containers/PackageJsonViewer/index.tsx index 81209e14566..06052f79053 100644 --- a/apps/lockfile-explorer-web/src/containers/PackageJsonViewer/index.tsx +++ b/apps/lockfile-explorer-web/src/containers/PackageJsonViewer/index.tsx @@ -12,6 +12,7 @@ import { compareSpec } from '../../parsing/compareSpec'; import { FilterBar } from '../../components/FilterBar'; import { loadSpecChanges } from '../../store/slices/workspaceSlice'; import { displaySpecChanges } from '../../helpers/displaySpecChanges'; +import { isEntryModified } from '../../helpers/isEntryModified'; enum PackageView { PACKAGE_JSON, @@ -67,13 +68,13 @@ export const PackageJsonViewer = (): JSX.Element => { case 'add': if (name) { return ( -

+

{dep}

); } else { return ( -

+

{version} {displaySpecChanges(specChanges, dep)}

); @@ -87,7 +88,7 @@ export const PackageJsonViewer = (): JSX.Element => { ); } else { return ( -

+

{version} {displaySpecChanges(specChanges, dep)}

); @@ -101,7 +102,7 @@ export const PackageJsonViewer = (): JSX.Element => { ); } else { return ( -

+

{version} {displaySpecChanges(specChanges, dep)}

); @@ -181,7 +182,7 @@ export const PackageJsonViewer = (): JSX.Element => { +): boolean => { + if (!entry) return false; + for (const dep of entry.dependencies) { + if (specChanges.has(dep.name)) { + return true; + } + } + return false; +}; From b8aa4346c259f4b796967e5d3dfb39e0765fe049 Mon Sep 17 00:00:00 2001 From: William Huang Date: Thu, 17 Nov 2022 10:39:12 -0800 Subject: [PATCH 15/24] feat: split filter into seperate values depending on tab --- .../src/containers/LockfileViewer/index.tsx | 41 +++++++++++++------ .../src/containers/LockfileViewer/styles.scss | 3 +- .../src/helpers/localStorage.ts | 21 +++++++--- 3 files changed, 46 insertions(+), 19 deletions(-) diff --git a/apps/lockfile-explorer-web/src/containers/LockfileViewer/index.tsx b/apps/lockfile-explorer-web/src/containers/LockfileViewer/index.tsx index d6e1daec8f6..5109dea6157 100644 --- a/apps/lockfile-explorer-web/src/containers/LockfileViewer/index.tsx +++ b/apps/lockfile-explorer-web/src/containers/LockfileViewer/index.tsx @@ -74,13 +74,21 @@ const multipleVersions = (entries: LockfileEntry[]): boolean => { export const LockfileViewer = (): JSX.Element | ReactNull => { const dispatch = useAppDispatch(); - const [filter, setFilter] = useState(''); + const [projectFilter, setProjectFilter] = useState(''); + const [packageFilter, setPackageFilter] = useState(''); const entries = useAppSelector(selectFilteredEntries); const activeFilters = useAppSelector((state) => state.entry.filters); - const updateFilter = useCallback((e) => { - setFilter(e.target.value); - saveFilterToLocalStorage(e.target.value); - }, []); + const updateFilter = useCallback( + (type: LockfileEntryFilter) => (e: React.ChangeEvent) => { + if (type === LockfileEntryFilter.Project) { + setProjectFilter(e.target.value); + } else { + setPackageFilter(e.target.value); + } + saveFilterToLocalStorage(e.target.value, type); + }, + [] + ); const pop = useCallback(() => { dispatch(popStack()); }, []); @@ -89,7 +97,8 @@ export const LockfileViewer = (): JSX.Element | ReactNull => { }, []); useEffect(() => { - setFilter(getFilterFromLocalStorage()); + setProjectFilter(getFilterFromLocalStorage(LockfileEntryFilter.Project)); + setPackageFilter(getFilterFromLocalStorage(LockfileEntryFilter.Package)); }, []); const entryStack = useAppSelector((state) => state.entry.selectedEntryStack); @@ -98,11 +107,11 @@ export const LockfileViewer = (): JSX.Element | ReactNull => { if (!entries) return ReactNull; const getEntriesToShow = (): ILockfileEntryGroup[] => { - let filteredEntries: LockfileEntry[] = []; - if (filter) { - filteredEntries = entries.filter((entry) => entry.entryPackageName.indexOf(filter) !== -1); - } else { - filteredEntries = entries; + let filteredEntries: LockfileEntry[] = entries; + if (projectFilter && activeFilters[LockfileEntryFilter.Project]) { + filteredEntries = entries.filter((entry) => entry.entryPackageName.indexOf(projectFilter) !== -1); + } else if (packageFilter && activeFilters[LockfileEntryFilter.Package]) { + filteredEntries = entries.filter((entry) => entry.entryPackageName.indexOf(packageFilter) !== -1); } const reducedEntries = filteredEntries.reduce((groups: { [key in string]: LockfileEntry[] }, item) => { const group = groups[item.entryPackageName] || []; @@ -167,7 +176,15 @@ export const LockfileViewer = (): JSX.Element | ReactNull => {
Filter:
- + diff --git a/apps/lockfile-explorer-web/src/containers/LockfileViewer/styles.scss b/apps/lockfile-explorer-web/src/containers/LockfileViewer/styles.scss index 78b50730674..806f937af6d 100644 --- a/apps/lockfile-explorer-web/src/containers/LockfileViewer/styles.scss +++ b/apps/lockfile-explorer-web/src/containers/LockfileViewer/styles.scss @@ -63,7 +63,8 @@ } .filterSection { - margin-top: 24px; + border-top: 1px solid #000; + padding-top: 24px; & > * + * { margin-top: 4px; } diff --git a/apps/lockfile-explorer-web/src/helpers/localStorage.ts b/apps/lockfile-explorer-web/src/helpers/localStorage.ts index d98af67a48e..8b929e8f9cb 100644 --- a/apps/lockfile-explorer-web/src/helpers/localStorage.ts +++ b/apps/lockfile-explorer-web/src/helpers/localStorage.ts @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. // See LICENSE in the project root for license information. -import { LockfileEntry } from '../parsing/LockfileEntry'; +import { LockfileEntry, LockfileEntryFilter } from '../parsing/LockfileEntry'; const BOOKMARK_KEY: string = 'LOCKFILE_EXPLORER_BOOKMARKS'; @@ -28,11 +28,20 @@ export const removeBookmarkFromLocalStorage = (entry: LockfileEntry): void => { localStorage.setItem(BOOKMARK_KEY, JSON.stringify(currBookmarks)); }; -const FILTER_KEY: string = 'LOCKFILE_EXPLORER_FILTER'; -export const saveFilterToLocalStorage = (filter: string): void => { - localStorage.setItem(FILTER_KEY, filter); +const PROJECT_FILTER_KEY: string = 'LOCKFILE_EXPLORER_PROJECT_FILTER'; +const PACKAGE_FILTER_KEY: string = 'LOCKFILE_EXPLORER_PACKAGE_FILTER'; +export const saveFilterToLocalStorage = (filter: string, type: LockfileEntryFilter): void => { + if (type === LockfileEntryFilter.Project) { + localStorage.setItem(PROJECT_FILTER_KEY, filter); + } else { + localStorage.setItem(PACKAGE_FILTER_KEY, filter); + } }; -export const getFilterFromLocalStorage = (): string => { - return localStorage.getItem(FILTER_KEY) || ''; +export const getFilterFromLocalStorage = (type: LockfileEntryFilter): string => { + if (type === LockfileEntryFilter.Project) { + return localStorage.getItem(PROJECT_FILTER_KEY) || ''; + } else { + return localStorage.getItem(PACKAGE_FILTER_KEY) || ''; + } }; From c993bf9d28757ca7f5afabac35c269d87bd02e6a Mon Sep 17 00:00:00 2001 From: William Huang Date: Thu, 17 Nov 2022 10:54:04 -0800 Subject: [PATCH 16/24] feat: move add bookmark button to top --- .../containers/SelectedEntryPreview/index.tsx | 30 +++++++++++-------- .../SelectedEntryPreview/styles.scss | 13 ++++++-- 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/apps/lockfile-explorer-web/src/containers/SelectedEntryPreview/index.tsx b/apps/lockfile-explorer-web/src/containers/SelectedEntryPreview/index.tsx index a1107937a25..8625de22a4d 100644 --- a/apps/lockfile-explorer-web/src/containers/SelectedEntryPreview/index.tsx +++ b/apps/lockfile-explorer-web/src/containers/SelectedEntryPreview/index.tsx @@ -30,19 +30,25 @@ export const SelectedEntryPreview = (): JSX.Element => { return (
-
-
Selected entry:
- {selectedEntry.displayText} +
+
+
Selected entry:
+ {selectedEntry.displayText} +
+ {isBookmarked ? ( + + ) : ( + + )} +
+
+

Package Entry: {selectedEntry.rawEntryId}

+

Package JSON path: {selectedEntry.packageJsonFolderPath}

- -

Package Entry: {selectedEntry.rawEntryId}

-

Package JSON path: {selectedEntry.packageJsonFolderPath}

- - {isBookmarked ? ( - - ) : ( - - )}
); }; diff --git a/apps/lockfile-explorer-web/src/containers/SelectedEntryPreview/styles.scss b/apps/lockfile-explorer-web/src/containers/SelectedEntryPreview/styles.scss index 0dee14a364d..fc72e5b6f17 100644 --- a/apps/lockfile-explorer-web/src/containers/SelectedEntryPreview/styles.scss +++ b/apps/lockfile-explorer-web/src/containers/SelectedEntryPreview/styles.scss @@ -4,8 +4,8 @@ margin-top: 12px; box-shadow: $ms-depth-shadow-8; background-color: $ms-color-gray10; - padding: 24px; - height: 6%; + padding: 12px 24px; + height: 8%; overflow: scroll; p { font-size: 12px; @@ -15,6 +15,11 @@ } } +.SelectedEntryBookmarkRow { + display: flex; + justify-content: space-between; +} + .SelectedEntryHeader { display: flex; align-items: center; @@ -22,3 +27,7 @@ margin-left: 8px; } } + +.BookmarkButton { + height: fit-content; +} From 18e4c37a1226de4576b76a52e4328b5f1131b78b Mon Sep 17 00:00:00 2001 From: William Huang Date: Thu, 17 Nov 2022 10:56:18 -0800 Subject: [PATCH 17/24] chore: eslint errors --- .../src/containers/PackageJsonViewer/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/lockfile-explorer-web/src/containers/PackageJsonViewer/index.tsx b/apps/lockfile-explorer-web/src/containers/PackageJsonViewer/index.tsx index 06052f79053..d4a5494735a 100644 --- a/apps/lockfile-explorer-web/src/containers/PackageJsonViewer/index.tsx +++ b/apps/lockfile-explorer-web/src/containers/PackageJsonViewer/index.tsx @@ -60,8 +60,8 @@ export const PackageJsonViewer = (): JSX.Element => { }, [selectedEntry]); const renderDep = - (name: boolean) => - (dependencyDetails: [string, string]): JSX.Element => { + (name: boolean): ((dependencyDetails: [string, string]) => JSX.Element) => + (dependencyDetails) => { const [dep, version] = dependencyDetails; if (specChanges.has(dep)) { switch (specChanges.get(dep)?.type) { From dd84f0f336b64e00edd1a0b25f9aad1164b4c263 Mon Sep 17 00:00:00 2001 From: William Huang Date: Thu, 17 Nov 2022 13:53:31 -0800 Subject: [PATCH 18/24] feat: better ux --- .../src/containers/LockfileViewer/index.tsx | 69 ++++++++----------- .../src/containers/LockfileViewer/styles.scss | 2 +- .../src/containers/LogoPanel/index.tsx | 2 +- .../src/containers/LogoPanel/styles.scss | 7 -- .../containers/SelectedEntryPreview/index.tsx | 46 ++++++++++--- .../SelectedEntryPreview/styles.scss | 10 ++- apps/lockfile-explorer-web/src/start.css | 1 + .../src/store/slices/entrySlice.ts | 26 +++++++ 8 files changed, 101 insertions(+), 62 deletions(-) diff --git a/apps/lockfile-explorer-web/src/containers/LockfileViewer/index.tsx b/apps/lockfile-explorer-web/src/containers/LockfileViewer/index.tsx index 5109dea6157..5d9121df276 100644 --- a/apps/lockfile-explorer-web/src/containers/LockfileViewer/index.tsx +++ b/apps/lockfile-explorer-web/src/containers/LockfileViewer/index.tsx @@ -8,9 +8,9 @@ import { LockfileEntry, LockfileEntryFilter } from '../../parsing/LockfileEntry' import { ReactNull } from '../../types/ReactNull'; import { useAppDispatch, useAppSelector } from '../../store/hooks'; import { - clearStackAndPush, forwardStack, popStack, + pushToStack, selectCurrentEntry, selectFilteredEntries, setFilter as selectFilter @@ -29,7 +29,7 @@ const LockfileEntryLi = ({ group }: { group: ILockfileEntryGroup }): JSX.Element const fieldRef = useRef() as React.MutableRefObject; const clear = useCallback( (entry: LockfileEntry) => () => { - dispatch(clearStackAndPush(entry)); + dispatch(pushToStack(entry)); }, [] ); @@ -89,21 +89,12 @@ export const LockfileViewer = (): JSX.Element | ReactNull => { }, [] ); - const pop = useCallback(() => { - dispatch(popStack()); - }, []); - const forward = useCallback(() => { - dispatch(forwardStack()); - }, []); useEffect(() => { setProjectFilter(getFilterFromLocalStorage(LockfileEntryFilter.Project)); setPackageFilter(getFilterFromLocalStorage(LockfileEntryFilter.Package)); }, []); - const entryStack = useAppSelector((state) => state.entry.selectedEntryStack); - const entryForwardStack = useAppSelector((state) => state.entry.selectedEntryForwardStack); - if (!entries) return ReactNull; const getEntriesToShow = (): ILockfileEntryGroup[] => { @@ -147,10 +138,10 @@ export const LockfileViewer = (): JSX.Element | ReactNull => { const togglePackageView = useCallback( (selected: LockfileEntryFilter) => () => { if (selected === LockfileEntryFilter.Project) { - dispatch(selectFilter({ filter: selected, state: !activeFilters[selected] })); + dispatch(selectFilter({ filter: LockfileEntryFilter.Project, state: true })); dispatch(selectFilter({ filter: LockfileEntryFilter.Package, state: false })); } else { - dispatch(selectFilter({ filter: selected, state: !activeFilters[selected] })); + dispatch(selectFilter({ filter: LockfileEntryFilter.Package, state: true })); dispatch(selectFilter({ filter: LockfileEntryFilter.Project, state: false })); } }, @@ -185,41 +176,37 @@ export const LockfileViewer = (): JSX.Element | ReactNull => { : LockfileEntryFilter.Package )} /> - -
{getEntriesToShow().map((lockfileEntryGroup) => ( ))}
-
-
Filters
-
- -
Must have side-by-side versions
+ {activeFilters[LockfileEntryFilter.Package] ? ( +
+
Filters
+
+ +
Must have side-by-side versions
+
+
+ +
Must have doppelgangers
+
-
- -
Must have doppelgangers
-
-
+ ) : null}
); diff --git a/apps/lockfile-explorer-web/src/containers/LockfileViewer/styles.scss b/apps/lockfile-explorer-web/src/containers/LockfileViewer/styles.scss index 806f937af6d..b04eff968f6 100644 --- a/apps/lockfile-explorer-web/src/containers/LockfileViewer/styles.scss +++ b/apps/lockfile-explorer-web/src/containers/LockfileViewer/styles.scss @@ -6,7 +6,7 @@ .ViewContents { display: flex; flex-direction: column; - max-height: calc(100% - 70px - 35px); + min-height: calc(100% - 70px - 35px); } .LockfileEntryListViewWrapper { diff --git a/apps/lockfile-explorer-web/src/containers/LogoPanel/index.tsx b/apps/lockfile-explorer-web/src/containers/LogoPanel/index.tsx index c3aebdd90d4..af266a0f86e 100644 --- a/apps/lockfile-explorer-web/src/containers/LogoPanel/index.tsx +++ b/apps/lockfile-explorer-web/src/containers/LogoPanel/index.tsx @@ -21,8 +21,8 @@ export const LogoPanel = (): JSX.Element => {
-
{appPackageVersion}
+
{appPackageVersion}
rushstack.io
diff --git a/apps/lockfile-explorer-web/src/containers/LogoPanel/styles.scss b/apps/lockfile-explorer-web/src/containers/LogoPanel/styles.scss index 03785342b4b..a668bfaf689 100644 --- a/apps/lockfile-explorer-web/src/containers/LogoPanel/styles.scss +++ b/apps/lockfile-explorer-web/src/containers/LogoPanel/styles.scss @@ -1,5 +1,4 @@ .LogoPanel { - height: 70px; display: flex; & > * + * { margin-left: 12px; @@ -22,12 +21,6 @@ .Title2 { padding-top: 5px; height: 20px; - display: flex; - align-content: flex-end; - & > div { - padding-top: 0; - margin-left: 4px; - } } .Image { diff --git a/apps/lockfile-explorer-web/src/containers/SelectedEntryPreview/index.tsx b/apps/lockfile-explorer-web/src/containers/SelectedEntryPreview/index.tsx index 8625de22a4d..ec926daccd7 100644 --- a/apps/lockfile-explorer-web/src/containers/SelectedEntryPreview/index.tsx +++ b/apps/lockfile-explorer-web/src/containers/SelectedEntryPreview/index.tsx @@ -4,13 +4,22 @@ import React, { useCallback } from 'react'; import styles from './styles.scss'; import { useAppDispatch, useAppSelector } from '../../store/hooks'; -import { addBookmark, removeBookmark, selectCurrentEntry } from '../../store/slices/entrySlice'; +import { + addBookmark, + forwardStack, + popStack, + removeBookmark, + selectCurrentEntry +} from '../../store/slices/entrySlice'; export const SelectedEntryPreview = (): JSX.Element => { const selectedEntry = useAppSelector(selectCurrentEntry); const isBookmarked = useAppSelector((state) => selectedEntry ? state.entry.bookmarkedEntries.includes(selectedEntry) : false ); + + const entryStack = useAppSelector((state) => state.entry.selectedEntryStack); + const entryForwardStack = useAppSelector((state) => state.entry.selectedEntryForwardStack); const useDispatch = useAppDispatch(); const bookmark = useCallback(() => { @@ -20,6 +29,31 @@ export const SelectedEntryPreview = (): JSX.Element => { if (selectedEntry) useDispatch(removeBookmark(selectedEntry)); }, [selectedEntry]); + const pop = useCallback(() => { + useDispatch(popStack()); + }, []); + const forward = useCallback(() => { + useDispatch(forwardStack()); + }, []); + + const renderButtonRow = () => { + return ( +
+ + + {isBookmarked ? ( + + ) : ( + + )} +
+ ); + }; + if (!selectedEntry) { return (
@@ -35,15 +69,7 @@ export const SelectedEntryPreview = (): JSX.Element => {
Selected entry:
{selectedEntry.displayText}
- {isBookmarked ? ( - - ) : ( - - )} + {renderButtonRow()}

Package Entry: {selectedEntry.rawEntryId}

diff --git a/apps/lockfile-explorer-web/src/containers/SelectedEntryPreview/styles.scss b/apps/lockfile-explorer-web/src/containers/SelectedEntryPreview/styles.scss index fc72e5b6f17..b816c5a6770 100644 --- a/apps/lockfile-explorer-web/src/containers/SelectedEntryPreview/styles.scss +++ b/apps/lockfile-explorer-web/src/containers/SelectedEntryPreview/styles.scss @@ -28,6 +28,12 @@ } } -.BookmarkButton { - height: fit-content; +.NavigationButtonRow { + display: flex; + & > * { + height: 21px; + } + & > * + * { + margin-left: 8px; + } } diff --git a/apps/lockfile-explorer-web/src/start.css b/apps/lockfile-explorer-web/src/start.css index b2b26125d12..c7d2f557200 100644 --- a/apps/lockfile-explorer-web/src/start.css +++ b/apps/lockfile-explorer-web/src/start.css @@ -14,4 +14,5 @@ body { h5, p { margin: 0; + font-size: 14px; } diff --git a/apps/lockfile-explorer-web/src/store/slices/entrySlice.ts b/apps/lockfile-explorer-web/src/store/slices/entrySlice.ts index f50a2bbae83..8cd468c59dc 100644 --- a/apps/lockfile-explorer-web/src/store/slices/entrySlice.ts +++ b/apps/lockfile-explorer-web/src/store/slices/entrySlice.ts @@ -58,17 +58,43 @@ const entrySlice = createSlice({ }, pushToStack: (state, payload: PayloadAction) => { state.selectedEntryStack.push(payload.payload); + state.selectedEntryForwardStack = []; + if (payload.payload.kind === LockfileEntryFilter.Package) { + state.filters[LockfileEntryFilter.Project] = false; + state.filters[LockfileEntryFilter.Package] = true; + } else { + state.filters[LockfileEntryFilter.Project] = true; + state.filters[LockfileEntryFilter.Package] = false; + } }, popStack: (state) => { if (state.selectedEntryStack.length > 1) { const poppedEntry = state.selectedEntryStack.pop() as LockfileEntry; state.selectedEntryForwardStack.push(poppedEntry); + + if (state.selectedEntryStack.length >= 1) { + const currEntry = state.selectedEntryStack[state.selectedEntryStack.length - 1]; + if (currEntry.kind === LockfileEntryFilter.Package) { + state.filters[LockfileEntryFilter.Project] = false; + state.filters[LockfileEntryFilter.Package] = true; + } else { + state.filters[LockfileEntryFilter.Project] = true; + state.filters[LockfileEntryFilter.Package] = false; + } + } } }, forwardStack: (state) => { if (state.selectedEntryForwardStack.length > 0) { const poppedEntry = state.selectedEntryForwardStack.pop() as LockfileEntry; state.selectedEntryStack.push(poppedEntry); + if (poppedEntry.kind === LockfileEntryFilter.Package) { + state.filters[LockfileEntryFilter.Project] = false; + state.filters[LockfileEntryFilter.Package] = true; + } else { + state.filters[LockfileEntryFilter.Project] = true; + state.filters[LockfileEntryFilter.Package] = false; + } } }, addBookmark: (state, payload: PayloadAction) => { From 42145f7ac9299c1bd0b278f118104cb9324bc010 Mon Sep 17 00:00:00 2001 From: William Huang Date: Thu, 17 Nov 2022 13:55:24 -0800 Subject: [PATCH 19/24] chore: eslint --- .../src/containers/LockfileViewer/index.tsx | 2 -- .../src/containers/SelectedEntryPreview/index.tsx | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/lockfile-explorer-web/src/containers/LockfileViewer/index.tsx b/apps/lockfile-explorer-web/src/containers/LockfileViewer/index.tsx index 5d9121df276..a8b00ffc3b2 100644 --- a/apps/lockfile-explorer-web/src/containers/LockfileViewer/index.tsx +++ b/apps/lockfile-explorer-web/src/containers/LockfileViewer/index.tsx @@ -8,8 +8,6 @@ import { LockfileEntry, LockfileEntryFilter } from '../../parsing/LockfileEntry' import { ReactNull } from '../../types/ReactNull'; import { useAppDispatch, useAppSelector } from '../../store/hooks'; import { - forwardStack, - popStack, pushToStack, selectCurrentEntry, selectFilteredEntries, diff --git a/apps/lockfile-explorer-web/src/containers/SelectedEntryPreview/index.tsx b/apps/lockfile-explorer-web/src/containers/SelectedEntryPreview/index.tsx index ec926daccd7..cc548fd8524 100644 --- a/apps/lockfile-explorer-web/src/containers/SelectedEntryPreview/index.tsx +++ b/apps/lockfile-explorer-web/src/containers/SelectedEntryPreview/index.tsx @@ -36,7 +36,7 @@ export const SelectedEntryPreview = (): JSX.Element => { useDispatch(forwardStack()); }, []); - const renderButtonRow = () => { + const renderButtonRow = (): JSX.Element => { return (
{isBookmarked ? ( - + ) : ( - + )}
); @@ -57,7 +61,10 @@ export const SelectedEntryPreview = (): JSX.Element => { if (!selectedEntry) { return (
-
No Entry Selected
+
+
No Entry Selected
+ {renderButtonRow()} +
); } From 6f4a8c7e71467eeadfe143183b228d7332526e90 Mon Sep 17 00:00:00 2001 From: William Huang Date: Thu, 17 Nov 2022 15:44:33 -0800 Subject: [PATCH 23/24] chore: stop scrolling when entry is selected --- .../src/containers/LockfileViewer/styles.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/lockfile-explorer-web/src/containers/LockfileViewer/styles.scss b/apps/lockfile-explorer-web/src/containers/LockfileViewer/styles.scss index 5108d165f13..b1ae4bc5bff 100644 --- a/apps/lockfile-explorer-web/src/containers/LockfileViewer/styles.scss +++ b/apps/lockfile-explorer-web/src/containers/LockfileViewer/styles.scss @@ -1,5 +1,5 @@ .ViewWrapper { - height: 100%; + height: calc(100% - 12px); padding-top: 12px; } From 053dfac23c269d843a5135680ed2ecc6c3d38a21 Mon Sep 17 00:00:00 2001 From: William Huang Date: Thu, 17 Nov 2022 15:46:42 -0800 Subject: [PATCH 24/24] chore: tweak padding on bookmarks --- .../src/containers/BookmarksSidebar/styles.scss | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/lockfile-explorer-web/src/containers/BookmarksSidebar/styles.scss b/apps/lockfile-explorer-web/src/containers/BookmarksSidebar/styles.scss index b1a157e35f2..329508f3696 100644 --- a/apps/lockfile-explorer-web/src/containers/BookmarksSidebar/styles.scss +++ b/apps/lockfile-explorer-web/src/containers/BookmarksSidebar/styles.scss @@ -4,7 +4,8 @@ } .BookmarkEntry { - padding: 4px 8px; + padding: 4px 0; + margin-top: 8px; cursor: pointer; &:hover { background: #dff6dd;