From 5c322f19305a49f080610a3502a5c9f5090b7800 Mon Sep 17 00:00:00 2001 From: Thomas Plumpton Date: Mon, 30 Jun 2025 19:02:51 +0100 Subject: [PATCH 01/26] feat(log): first pass at filtering commits in the log --- README.md | 5 + packages/demo/.storybook/preview.tsx | 5 +- packages/demo/src/GitLog.stories.tsx | 2 +- .../src/components/GitLogDemo/GitLogDemo.tsx | 8 + .../SearchField/SearchField.module.scss | 0 .../components/SearchField/SearchField.tsx | 20 + .../demo/src/components/SearchField/index.ts | 2 + .../demo/src/components/SearchField/types.ts | 5 + .../StoryHeader/StoryHeader.module.scss | 8 +- .../components/StoryHeader/StoryHeader.tsx | 31 +- packages/demo/src/context/DemoContext.ts | 9 + .../demo/src/context/DemoContextProvider.tsx | 18 + packages/demo/src/context/index.ts | 3 + packages/demo/src/context/types.ts | 4 + packages/demo/src/context/useDemoContext.ts | 4 + packages/demo/tsconfig.json | 1 + packages/demo/vite.config.ts | 1 + packages/library/src/GitLog.tsx | 2 +- .../src/components/GitLogCore/GitLogCore.tsx | 11 +- .../library/src/context/GitContext/types.ts | 7 +- .../src/data/computeFilteredNodeColumns.ts | 167 ++++++ packages/library/src/data/types.ts | 32 ++ .../src/modules/Graph/core/GraphCore.tsx | 9 +- .../Graph/strategies/Grid/HTMLGridGraph.tsx | 17 +- .../Grid/components/GraphColumn/types.ts | 8 +- .../Grid/hooks/useColumnData/useColumnData.ts | 480 +++++++++--------- packages/library/src/modules/Graph/types.ts | 2 +- packages/library/src/modules/Table/Table.tsx | 12 +- packages/library/src/modules/Tags/Tags.tsx | 11 +- packages/library/src/types.ts | 19 +- 30 files changed, 623 insertions(+), 280 deletions(-) create mode 100644 packages/demo/src/components/SearchField/SearchField.module.scss create mode 100644 packages/demo/src/components/SearchField/SearchField.tsx create mode 100644 packages/demo/src/components/SearchField/index.ts create mode 100644 packages/demo/src/components/SearchField/types.ts create mode 100644 packages/demo/src/context/DemoContext.ts create mode 100644 packages/demo/src/context/DemoContextProvider.tsx create mode 100644 packages/demo/src/context/index.ts create mode 100644 packages/demo/src/context/types.ts create mode 100644 packages/demo/src/context/useDemoContext.ts create mode 100644 packages/library/src/data/computeFilteredNodeColumns.ts diff --git a/README.md b/README.md index 49e0b680..ce76b37e 100644 --- a/README.md +++ b/README.md @@ -516,6 +516,11 @@ Graphs - Tags should be independent. Add a new optional field to the log entry / commit objects. - Branch / Tags column is fixed. Dynamically floor it to match the max tag size currently being rendered? - Support filtering so that the graph skips nodes + - Can we use one compute function now? Have a boolean to optionally do the find closest ancestor + - Can we use one set of graphData too and remove the filteredData + - Add styling to the broken edges to indicate its not the true parent + - Make index commit find the nearest HEAD ancestor, or go off the graph otherwise + - Does this work with GitLogPaged? Canvas2D - Custom prop for BG colour because of how canvas alpha channel works diff --git a/packages/demo/.storybook/preview.tsx b/packages/demo/.storybook/preview.tsx index 243d5200..df7ae5fa 100644 --- a/packages/demo/.storybook/preview.tsx +++ b/packages/demo/.storybook/preview.tsx @@ -2,6 +2,7 @@ import type { Preview } from '@storybook/react' import './preview.scss' import '@theme-toggles/react/css/Within.css' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import { DemoContextProvider } from '@context' const queryClient = new QueryClient() @@ -17,7 +18,9 @@ const preview: Preview = { decorators: [ Story => ( - + + + ) ] diff --git a/packages/demo/src/GitLog.stories.tsx b/packages/demo/src/GitLog.stories.tsx index 203c643c..0c60ef3e 100644 --- a/packages/demo/src/GitLog.stories.tsx +++ b/packages/demo/src/GitLog.stories.tsx @@ -8,7 +8,7 @@ import { GitLogDemo, GitLogStoryProps } from '@components/GitLogDemo' const meta: Meta = { title: 'GitLog', - component: GitLog, + component: GitLog, parameters: { layout: 'fullscreen' }, diff --git a/packages/demo/src/components/GitLogDemo/GitLogDemo.tsx b/packages/demo/src/components/GitLogDemo/GitLogDemo.tsx index 580fc1a0..1ae18860 100644 --- a/packages/demo/src/components/GitLogDemo/GitLogDemo.tsx +++ b/packages/demo/src/components/GitLogDemo/GitLogDemo.tsx @@ -4,6 +4,7 @@ import { StoryHeader } from '@components/StoryHeader' import { Loading } from '@components/Loading' import { GitLog } from '@tomplum/react-git-log' import { GitLogStoryProps } from './types' +import { useDemoContext } from '@context' export const GitLogDemo = (args: GitLogStoryProps) => { const { @@ -20,6 +21,8 @@ export const GitLogDemo = (args: GitLogStoryProps) => { handleChangeRepository } = useStoryState() + const { search } = useDemoContext() + return (
{ entries={entries} theme={theme} currentBranch={branch} + filter={search ? entries => { + return entries.filter(commit => { + return commit.message.includes(search) + }) + } : undefined} paging={{ page: args.page ?? 0, size: args.pageSize ?? entries.length diff --git a/packages/demo/src/components/SearchField/SearchField.module.scss b/packages/demo/src/components/SearchField/SearchField.module.scss new file mode 100644 index 00000000..e69de29b diff --git a/packages/demo/src/components/SearchField/SearchField.tsx b/packages/demo/src/components/SearchField/SearchField.tsx new file mode 100644 index 00000000..caa1350c --- /dev/null +++ b/packages/demo/src/components/SearchField/SearchField.tsx @@ -0,0 +1,20 @@ +import styles from './SearchField.module.scss' +import { SearchFieldProps } from './types' +import { ChangeEvent } from 'react' +import { useDemoContext } from '@context' + +export const SearchField = ({ theme }: SearchFieldProps) => { + const { search, setSearch } = useDemoContext() + + const handleChange= (e: ChangeEvent) => { + setSearch(e.target.value) + } + + return ( + + ) +} \ No newline at end of file diff --git a/packages/demo/src/components/SearchField/index.ts b/packages/demo/src/components/SearchField/index.ts new file mode 100644 index 00000000..e0017c08 --- /dev/null +++ b/packages/demo/src/components/SearchField/index.ts @@ -0,0 +1,2 @@ +export * from './types' +export * from './SearchField' \ No newline at end of file diff --git a/packages/demo/src/components/SearchField/types.ts b/packages/demo/src/components/SearchField/types.ts new file mode 100644 index 00000000..40c62e07 --- /dev/null +++ b/packages/demo/src/components/SearchField/types.ts @@ -0,0 +1,5 @@ +import { ThemeMode } from '@tomplum/react-git-log' + +export interface SearchFieldProps { + theme: ThemeMode +} \ No newline at end of file diff --git a/packages/demo/src/components/StoryHeader/StoryHeader.module.scss b/packages/demo/src/components/StoryHeader/StoryHeader.module.scss index 9f3d8c44..faad3da5 100644 --- a/packages/demo/src/components/StoryHeader/StoryHeader.module.scss +++ b/packages/demo/src/components/StoryHeader/StoryHeader.module.scss @@ -14,8 +14,14 @@ .controls { display: flex; - align-items: center; + flex-direction: column; gap: 10px; + + .controlsTop { + display: flex; + align-items: center; + gap: 10px; + } } } } \ No newline at end of file diff --git a/packages/demo/src/components/StoryHeader/StoryHeader.tsx b/packages/demo/src/components/StoryHeader/StoryHeader.tsx index 3a954bb9..780e078d 100644 --- a/packages/demo/src/components/StoryHeader/StoryHeader.tsx +++ b/packages/demo/src/components/StoryHeader/StoryHeader.tsx @@ -5,6 +5,7 @@ import { ThemeToggle } from '@components/ThemeToggle' import { PackageInfo } from '@components/PackageInfo' import { StoryHeaderProps } from '@components/StoryHeader/types' import { PropsWithChildren } from 'react' +import { SearchField } from '@components/SearchField' export const StoryHeader = ({ children, theme, repository, colours, onChangeColours, onChangeRepository, onChangeTheme }: PropsWithChildren) => { return ( @@ -16,21 +17,27 @@ export const StoryHeader = ({ children, theme, repository, colours, onChangeColo >
- +
+ - + + + +
-
diff --git a/packages/demo/src/context/DemoContext.ts b/packages/demo/src/context/DemoContext.ts new file mode 100644 index 00000000..7ff44599 --- /dev/null +++ b/packages/demo/src/context/DemoContext.ts @@ -0,0 +1,9 @@ +import { createContext } from 'react' +import { DemoContextBag } from './types' + +export const DemoContext = createContext({ + search: '', + setSearch: (value: string) => { + console.warn(`Tried to invoke setSearch(${value}) before the DemoContext was initialised.`) + } +}) \ No newline at end of file diff --git a/packages/demo/src/context/DemoContextProvider.tsx b/packages/demo/src/context/DemoContextProvider.tsx new file mode 100644 index 00000000..583344cf --- /dev/null +++ b/packages/demo/src/context/DemoContextProvider.tsx @@ -0,0 +1,18 @@ +import { PropsWithChildren, useMemo, useState } from 'react' +import { DemoContextBag } from './types' +import { DemoContext } from './DemoContext' + +export const DemoContextProvider = ({ children }: PropsWithChildren) => { + const [search, setSearch] = useState() + + const value = useMemo(() => ({ + search, + setSearch + }), [search]) + + return ( + + {children} + + ) +} \ No newline at end of file diff --git a/packages/demo/src/context/index.ts b/packages/demo/src/context/index.ts new file mode 100644 index 00000000..8cc46cd6 --- /dev/null +++ b/packages/demo/src/context/index.ts @@ -0,0 +1,3 @@ +export * from './types' +export * from './useDemoContext' +export * from './DemoContextProvider' \ No newline at end of file diff --git a/packages/demo/src/context/types.ts b/packages/demo/src/context/types.ts new file mode 100644 index 00000000..d9dc8942 --- /dev/null +++ b/packages/demo/src/context/types.ts @@ -0,0 +1,4 @@ +export interface DemoContextBag { + search?: string + setSearch: (value: string) => void +} \ No newline at end of file diff --git a/packages/demo/src/context/useDemoContext.ts b/packages/demo/src/context/useDemoContext.ts new file mode 100644 index 00000000..e23adf58 --- /dev/null +++ b/packages/demo/src/context/useDemoContext.ts @@ -0,0 +1,4 @@ +import { useContext } from 'react' +import { DemoContext } from './DemoContext' + +export const useDemoContext = () => useContext(DemoContext) \ No newline at end of file diff --git a/packages/demo/tsconfig.json b/packages/demo/tsconfig.json index 2465e883..b9dddf19 100644 --- a/packages/demo/tsconfig.json +++ b/packages/demo/tsconfig.json @@ -12,6 +12,7 @@ "@hooks/*": ["./src/hooks/*"], "@components/*": ["./src/components/*"], "@assets/*": ["./src/assets/*"], + "@context": ["./src/context/index.ts"], "@utils/*": ["./src/utils/*"], "@tomplum/react-git-log": ["../library/src"] }, diff --git a/packages/demo/vite.config.ts b/packages/demo/vite.config.ts index bef66ae0..82373c0b 100644 --- a/packages/demo/vite.config.ts +++ b/packages/demo/vite.config.ts @@ -16,6 +16,7 @@ export default defineConfig(({ mode }) => ({ '@assets': resolve(__dirname, '/src/assets'), '@hooks': resolve(__dirname, '/src/hooks'), '@utils': resolve(__dirname, '/src/utils'), + '@context': resolve(__dirname, '/src/context/index.ts'), // The Library Root '@tomplum/react-git-log': resolve(__dirname, '../library/src'), diff --git a/packages/library/src/GitLog.tsx b/packages/library/src/GitLog.tsx index 19294e5d..fa194d10 100644 --- a/packages/library/src/GitLog.tsx +++ b/packages/library/src/GitLog.tsx @@ -5,7 +5,7 @@ import { GraphCanvas2D, GraphHTMLGrid } from './modules/Graph' import { Table } from './modules/Table' import { GitLogCore } from './components/GitLogCore' -export const GitLog = ({ children, ...props }: PropsWithChildren>) => { +export const GitLog = ({ children, ...props }: PropsWithChildren>) => { return ( {...props} componentName="GitLog"> {children} diff --git a/packages/library/src/components/GitLogCore/GitLogCore.tsx b/packages/library/src/components/GitLogCore/GitLogCore.tsx index 930cd48c..e1f7f3de 100644 --- a/packages/library/src/components/GitLogCore/GitLogCore.tsx +++ b/packages/library/src/components/GitLogCore/GitLogCore.tsx @@ -12,6 +12,7 @@ import { ThemeContextProvider } from 'context/ThemeContext' import dayjs from 'dayjs' import utc from 'dayjs/plugin/utc' import { useCoreComponents } from 'components/GitLogCore/useCoreComponents' +import { computeFilteredNodePositions } from '../../data/computeFilteredNodeColumns' dayjs.extend(utc) @@ -22,6 +23,7 @@ export const GitLogCore = ({ rowSpacing = 0, theme = 'light', colours = 'rainbow-light', + filter, classes, defaultGraphWidth, onSelectCommit, @@ -47,6 +49,9 @@ export const GitLogCore = ({ const sortedCommits = temporalTopologicalSort([...hashToCommit.values()], children, hashToCommit) const { graphWidth, positions, edges } = computeNodePositions(sortedCommits, currentBranch, children, parents) + const filteredCommits = filter?.(sortedCommits) ?? [] + const filteredData = computeFilteredNodePositions(sortedCommits, currentBranch, children, parents, filteredCommits) + return { children, parents, @@ -54,9 +59,11 @@ export const GitLogCore = ({ graphWidth, positions, edges, + filteredData, + filteredCommits, commits: sortedCommits } - }, [currentBranch, entries, headCommitHash]) + }, [currentBranch, entries, filter, headCommitHash]) const [nodeSize, setNodeSize] = useState(DEFAULT_NODE_SIZE) const [graphOrientation, setGraphOrientation] = useState('normal') @@ -144,6 +151,7 @@ export const GitLogCore = ({ showTable: Boolean(table), showBranchesTags: Boolean(tags), classes, + filter, selectedCommit, setSelectedCommit: handleSelectCommit, previewedCommit, @@ -175,6 +183,7 @@ export const GitLogCore = ({ handleSelectCommit, handlePreviewCommit, urls, + filter, showHeaders, headCommit, currentBranch, diff --git a/packages/library/src/context/GitContext/types.ts b/packages/library/src/context/GitContext/types.ts index 3400d59a..1ae4f954 100644 --- a/packages/library/src/context/GitContext/types.ts +++ b/packages/library/src/context/GitContext/types.ts @@ -1,6 +1,6 @@ import { Commit } from 'types/Commit' import { GraphData } from 'data' -import { GitLogIndexStatus, GitLogStylingProps, GitLogUrlBuilder } from '../../types' +import { CommitFilter, GitLogIndexStatus, GitLogStylingProps, GitLogUrlBuilder } from '../../types' import { GraphOrientation } from 'modules/Graph' export interface GitContextBag { @@ -216,6 +216,11 @@ export interface GitContextBag { * pagination config. */ isIndexVisible: boolean + + /** + * Filters which entries show in the log. + */ + filter?: CommitFilter } export interface GraphPaging { diff --git a/packages/library/src/data/computeFilteredNodeColumns.ts b/packages/library/src/data/computeFilteredNodeColumns.ts new file mode 100644 index 00000000..e74100d3 --- /dev/null +++ b/packages/library/src/data/computeFilteredNodeColumns.ts @@ -0,0 +1,167 @@ +import { Commit } from 'types/Commit' +import { ActiveBranches } from './ActiveBranches' +import { CommitNodeLocation } from './types' +import { ActiveNodes } from './ActiveNodes' + +export const computeFilteredNodePositions = ( + commits: Commit[], + currentBranch: string, + children: Map, + parents: Map, + filteredCommits?: Commit[] +) => { + const activeBranches = new ActiveBranches() + const positions: Map = new Map() + const hashToIndex = new Map(commits.map((entry, i) => [entry.hash, i])) + const visibleHashes = new Set(filteredCommits?.map(c => c.hash) ?? commits.map(c => c.hash)) + + const headCommit = commits.find(commit => commit.branch.includes(currentBranch)) + let rowIndex = 1 + const activeNodes = new ActiveNodes() + + if (headCommit) { + activeNodes.enqueue([hashToIndex.get(headCommit.hash)!, 'index']) + } + + for (const commit of commits) { + let columnIndex = -1 + const commitHash = commit.hash + + const childHashes = children.get(commitHash) ?? [] + const branchChildren = childHashes.filter(childHash => parents.get(childHash)?.[0] === commitHash) + const mergeChildren = childHashes.filter(childHash => parents.get(childHash)?.[0] !== commitHash) + + let highestChild: string | undefined + let iMin = Infinity + for (const childSha of mergeChildren) { + const iChild = positions.get(childSha)?.[0] ?? Infinity + if (iChild < iMin) { + iMin = rowIndex + highestChild = childSha + } + } + const invalidIndices = highestChild ? activeNodes.get(highestChild) : new Set() + + let commitToReplaceHash: string | null = null + let commitToReplaceColumn = Infinity + + if (commitHash === headCommit?.hash) { + commitToReplaceHash = 'index' + commitToReplaceColumn = 0 + } else { + for (const childHash of branchChildren) { + const childColumn = positions.get(childHash)?.[1] ?? Infinity + if (!invalidIndices.has(childColumn) && childColumn < commitToReplaceColumn) { + commitToReplaceHash = childHash + commitToReplaceColumn = childColumn + } + } + } + + if (commitToReplaceHash) { + columnIndex = commitToReplaceColumn + activeBranches.setHash(columnIndex, commitHash) + } else if (childHashes.length > 0) { + const childHash = childHashes[0] + const childColumn = positions.get(childHash)?.[1] ?? 0 + columnIndex = activeBranches.insertCommit(commitHash, childColumn, invalidIndices) + } else { + columnIndex = activeBranches.insertCommit(commitHash, 0, new Set()) + } + + activeNodes.removeOutdatedNodes(rowIndex) + const columnsToAdd = [columnIndex, ...branchChildren.map(c => positions.get(c)?.[1] ?? 0)] + activeNodes.update(columnsToAdd) + activeNodes.initialiseNewColumn(commitHash) + + const parentIndices = commit.parents.map(p => hashToIndex.get(p)!).filter(i => i !== undefined) + const highestParentIndex: [number, string] = [Math.max(...parentIndices), commitHash] + activeNodes.enqueue(highestParentIndex) + + branchChildren.forEach(c => { + if (c !== commitToReplaceHash) { + const pos = positions.get(c) + if (pos) activeBranches.removeHash(pos[1]) + } + }) + + if (commit.parents.length === 0) { + activeBranches.removeHash(columnIndex) + } + + positions.set(commitHash, [rowIndex, columnIndex]) + rowIndex++ + } + + const filteredHashes = filteredCommits?.map(c => c.hash) ?? commits.map(c => c.hash) + const filteredRows = [...filteredHashes] + .map(hash => [hash, positions.get(hash)] as const) + .filter(([, pos]) => !!pos) as [string, CommitNodeLocation][] + + const rowMap = new Map( + filteredRows.map(([hash, [, col]], i) => [hash, [i + 1, col] as CommitNodeLocation]) + ) + + const findClosestVisibleAncestor = (() => { + const cache = new Map() + + const dfs = (hash: string): string | undefined => { + if (cache.has(hash)) return cache.get(hash) + const parentHashes = parents.get(hash) + if (!parentHashes || parentHashes.length === 0) { + cache.set(hash, undefined) + return undefined + } + + for (const parentHash of parentHashes) { + if (visibleHashes.has(parentHash)) { + cache.set(hash, parentHash) + return parentHash + } + } + + for (const parentHash of parentHashes) { + const ancestor = dfs(parentHash) + if (ancestor) { + cache.set(hash, ancestor) + return ancestor + } + } + + cache.set(hash, undefined) + return undefined + } + + return dfs + })() + + const filteredEdges = filteredCommits ?? commits + const edges = filteredEdges.flatMap(source => { + const output: { from: CommitNodeLocation; to: CommitNodeLocation; rerouted?: boolean }[] = [] + + for (const parentHash of source.parents) { + const fromPos = rowMap.get(source.hash) + const toHash = visibleHashes.has(parentHash) + ? parentHash + : findClosestVisibleAncestor(parentHash) + + const toPos = toHash ? rowMap.get(toHash) : undefined + + if (fromPos && toPos) { + output.push({ + from: fromPos, + to: toPos, + rerouted: toHash !== parentHash, + }) + } + } + + return output + }) + + return { + positions: rowMap, + graphWidth: activeBranches.length, + edges + } +} \ No newline at end of file diff --git a/packages/library/src/data/types.ts b/packages/library/src/data/types.ts index 5918b0f4..db41d409 100644 --- a/packages/library/src/data/types.ts +++ b/packages/library/src/data/types.ts @@ -53,6 +53,38 @@ export interface GraphData { * sorted temporally by committer date. */ commits: Commit[] + + filteredCommits: Commit[] + + filteredData: { + /** + * The width of the graph. A number + * that is the maximum concurrent active + * branches at any one time from all + * git log entries passed the log. + */ + graphWidth: number + + /** + * A map of the SHA1 hash of a commit + * and a {@link CommitNodeLocation} tuple that contains + * data about the row and column in which + * the node for that commit will be + * rendered in the graph. + */ + positions: Map + + /** + * An interval tree containing all the edges + * for relationships between commit nodes in + * the graph. + */ + edges: { + from: CommitNodeLocation + to: CommitNodeLocation + rerouted?: boolean + }[] + } } /** diff --git a/packages/library/src/modules/Graph/core/GraphCore.tsx b/packages/library/src/modules/Graph/core/GraphCore.tsx index a2767916..109ae1be 100644 --- a/packages/library/src/modules/Graph/core/GraphCore.tsx +++ b/packages/library/src/modules/Graph/core/GraphCore.tsx @@ -21,6 +21,7 @@ export const GraphCore = ({ }: PropsWithChildren>) => { const { paging, + filter, setNodeSize, setGraphOrientation, graphData: { graphWidth, commits } @@ -36,12 +37,14 @@ export const GraphCore = ({ const { width, ref, startResizing } = useResize() const visibleCommits = useMemo(() => { + const filteredCommits = filter?.(commits) ?? commits + if (paging) { - return commits.slice(paging.startIndex, paging.endIndex) + return filteredCommits.slice(paging.startIndex, paging.endIndex) } - return commits - }, [commits, paging]) + return filteredCommits + }, [commits, filter, paging]) const { columnData, virtualColumns } = useColumnData({ visibleCommits: visibleCommits.length diff --git a/packages/library/src/modules/Graph/strategies/Grid/HTMLGridGraph.tsx b/packages/library/src/modules/Graph/strategies/Grid/HTMLGridGraph.tsx index 3b6c67e3..4b76d845 100644 --- a/packages/library/src/modules/Graph/strategies/Grid/HTMLGridGraph.tsx +++ b/packages/library/src/modules/Graph/strategies/Grid/HTMLGridGraph.tsx @@ -6,12 +6,12 @@ import { GraphRow } from 'modules/Graph/strategies/Grid/components/GraphRow' import { useGraphContext } from 'modules/Graph/context' import { useGitContext } from 'context/GitContext' import { getEmptyColumnState } from 'modules/Graph/strategies/Grid/utility/getEmptyColumnState' -import { useMemo } from 'react' +import { CSSProperties, useMemo } from 'react' import { placeholderCommits } from 'modules/Graph/strategies/Grid/hooks/usePlaceholderData/data' export const HTMLGridGraph = () => { - const { isIndexVisible, rowSpacing, paging } = useGitContext() const { graphWidth, visibleCommits, columnData } = useGraphContext() + const { isIndexVisible, rowSpacing, paging } = useGitContext() const commitQuantity = useMemo(() => { // If there is no data being shown, then we'll @@ -32,14 +32,13 @@ export const HTMLGridGraph = () => { return visibleCommits.length }, [isIndexVisible, visibleCommits.length]) + const wrapperStyles: CSSProperties = { + gridTemplateColumns: `repeat(${graphWidth}, 1fr)`, + gridTemplateRows: `repeat(${commitQuantity}, ${ROW_HEIGHT + rowSpacing}px)` + } + return ( -
+
{visibleCommits.length === 0 && ( )} diff --git a/packages/library/src/modules/Graph/strategies/Grid/components/GraphColumn/types.ts b/packages/library/src/modules/Graph/strategies/Grid/components/GraphColumn/types.ts index a080a54c..683cde7b 100644 --- a/packages/library/src/modules/Graph/strategies/Grid/components/GraphColumn/types.ts +++ b/packages/library/src/modules/Graph/strategies/Grid/components/GraphColumn/types.ts @@ -98,7 +98,7 @@ export interface GraphColumnState { /** * Indicates that the column in the row above * the one that this column resides in is empty. - * Which it to say it contains now graphical elements + * Which is to say, it contains now graphical elements * such as nodes or lines. * * This indicates that the commit node in this column @@ -109,7 +109,7 @@ export interface GraphColumnState { /** * Indicates that the column in the row below * the one that this column resides in is empty. - * Which it to say it contains now graphical elements + * Which is to say, it contains now graphical elements * such as nodes or lines. * * This indicates that the commit node in this column @@ -144,4 +144,8 @@ export interface GraphColumnState { * filtering. */ isPlaceholderSkeleton?: boolean + + isTopBreakPoint?: boolean + + isBottomBreakPoint?: boolean } \ No newline at end of file diff --git a/packages/library/src/modules/Graph/strategies/Grid/hooks/useColumnData/useColumnData.ts b/packages/library/src/modules/Graph/strategies/Grid/hooks/useColumnData/useColumnData.ts index b4f9036d..562333d9 100644 --- a/packages/library/src/modules/Graph/strategies/Grid/hooks/useColumnData/useColumnData.ts +++ b/packages/library/src/modules/Graph/strategies/Grid/hooks/useColumnData/useColumnData.ts @@ -4,15 +4,17 @@ import { useGitContext } from 'context/GitContext' import { GraphColumnData, GraphColumnDataProps } from './types' import { isColumnEmpty } from 'modules/Graph/strategies/Grid/utility/isColumnEmpty' import { getEmptyColumnState as createEmptyColumn } from 'modules/Graph/strategies/Grid/utility/getEmptyColumnState' +import { CommitNodeLocation } from 'data' export const useColumnData = ({ visibleCommits }: GraphColumnDataProps): GraphColumnData => { const { paging, + filter, headCommit, headCommitHash, isIndexVisible, isServerSidePaginated, - graphData: { graphWidth, positions, edges, commits } + graphData: { graphWidth, positions, edges, commits, filteredData } } = useGitContext() const getEmptyColumnState = useCallback(() => { @@ -23,117 +25,120 @@ export const useColumnData = ({ visibleCommits }: GraphColumnDataProps): GraphCo // Maps the one-based row index to an array of column state data const rowToColumnState = new Map() - // An iterable array of tuples containing commit node row and column indices - const commitNodePositions = Array.from(positions.values()) - - // Iterate over all the edges update the graph column state - // for each of the respective branch/merge line segments. - edges.search(0, commits.length).forEach(([[rowStart, colStart], [rowEnd, colEnd]]) => { - // Are we connecting to nodes in the same column? - // I.e. drawing a straight merge line between them. - if (colStart === colEnd) { - for (let targetRow = rowStart; targetRow <= rowEnd; targetRow++) { - const columnState = rowToColumnState.get(targetRow) ?? getEmptyColumnState() - - columnState[colStart] = { - ...columnState[colStart], - isVerticalLine: true + // If, while server-side paginated, we find commits that need to draw + // lines to nodes that lie outside of this page of data, and those lines + // need to be drawn into columns that are beyond the current graph width, + // then we track the number of new "virtual" columns here that will be injected + // in the graph. + let virtualColumns = 0 + + const drawEdges = (edgeData: [CommitNodeLocation, CommitNodeLocation][]) => { + edgeData.forEach(([[rowStart, colStart], [rowEnd, colEnd]]) => { + // Are we connecting to nodes in the same column? + // I.e. drawing a straight merge line between them. + if (colStart === colEnd) { + for (let targetRow = rowStart; targetRow <= rowEnd; targetRow++) { + const columnState = rowToColumnState.get(targetRow) ?? getEmptyColumnState() + + columnState[colStart] = { + ...columnState[colStart], + isVerticalLine: true + } + + rowToColumnState.set(targetRow, columnState) } + } else { + // Are we connecting nodes in different columns? + // I.e. drawing a line that ultimately curves into another column + // to represent a new branch being created or a branch being merged. + for (let targetRow = rowStart; targetRow <= rowEnd; targetRow++) { + const columnState = rowToColumnState.get(targetRow) ?? getEmptyColumnState() + + // We're drawing a merge line from the bottom of + // a commit node, down, then to the left. + const edgeDownToLeft = rowEnd > rowStart && colEnd < colStart + + // If we're on the first row (the one with the start node) + if (targetRow === rowStart) { + if (edgeDownToLeft) { + // For the first row, just add a vertical merge line + // out the bottom of the commit node. + columnState[colStart] = { + ...columnState[colStart], + isVerticalLine: true + } + } else { + // Horizontal straight lines in all but the target column + // since that one will be a curved line. + for (let columnIndex = colStart; columnIndex < colEnd; columnIndex++) { + columnState[columnIndex] = { + ...columnState[columnIndex], + isHorizontalLine: true, + mergeSourceColumns: [ + ...(columnState[columnIndex]?.mergeSourceColumns ?? []), + colEnd + ] + } + } - rowToColumnState.set(targetRow, columnState) - } - } else { - // Are we connecting nodes in different columns? - // I.e. drawing a line that ultimately curves into another column - // to represent a new branch being created or a branch being merged. - for (let targetRow = rowStart; targetRow <= rowEnd; targetRow++) { - const columnState = rowToColumnState.get(targetRow) ?? getEmptyColumnState() - - // We're drawing a merge line from the bottom of - // a commit node, down, then to the left. - const edgeDownToLeft = rowEnd > rowStart && colEnd < colStart - - // If we're on the first row (the one with the start node) - if (targetRow === rowStart) { - if (edgeDownToLeft) { - // For the first row, just add a vertical merge line - // out the bottom of the commit node. - columnState[colStart] = { - ...columnState[colStart], - isVerticalLine: true + // Add in the curved line in the target column where the end node is + columnState[colEnd] = { + ...columnState[colEnd], + isLeftDownCurve: true + } } - } else { - // Horizontal straight lines in all but the target column - // since that one will be a curved line. - for (let columnIndex = colStart; columnIndex < colEnd; columnIndex++) { - columnState[columnIndex] = { - ...columnState[columnIndex], - isHorizontalLine: true, - mergeSourceColumns: [ - ...(columnState[columnIndex]?.mergeSourceColumns ?? []), - colEnd - ] + } else if (edgeDownToLeft) { + // Vertical straight lines down up until + // before we reach the target row since we'll + // have a curved line their around the corner. + if (targetRow !== rowStart && targetRow != rowEnd) { + columnState[colStart] = { + ...columnState[colStart], + isVerticalLine: true } } - // Add in the curved line in the target column where the end node is - columnState[colEnd] = { - ...columnState[colEnd], - isLeftDownCurve: true - } - } - } else if (edgeDownToLeft) { - // Vertical straight lines down up until - // before we reach the target row since we'll - // have a curved line their around the corner. - if (targetRow !== rowStart && targetRow != rowEnd) { - columnState[colStart] = { - ...columnState[colStart], - isVerticalLine: true - } - } + if (targetRow === rowEnd) { + // Add the curved line into the column that we're starting + // from (the commit nodes), and draw to the left towards our + // target node. + columnState[colStart] = { + ...columnState[colStart], + isLeftUpCurve: true + } - if (targetRow === rowEnd) { - // Add the curved line into the column that we're starting - // from (the commit nodes), and draw to the left towards our - // target node. - columnState[colStart] = { - ...columnState[colStart], - isLeftUpCurve: true - } + // For the remaining columns in this final row, draw + // horizontal lines towards the target commit node. + for (let columnIndex = colStart - 1; columnIndex >= colEnd; columnIndex--) { + columnState[columnIndex] = { + ...columnState[columnIndex], + isHorizontalLine: true, + mergeSourceColumns: [ + ...(columnState[columnIndex]?.mergeSourceColumns ?? []), + colStart + ] + } + } - // For the remaining columns in this final row, draw - // horizontal lines towards the target commit node. - for (let columnIndex = colStart - 1; columnIndex >= colEnd; columnIndex--) { - columnState[columnIndex] = { - ...columnState[columnIndex], - isHorizontalLine: true, - mergeSourceColumns: [ - ...(columnState[columnIndex]?.mergeSourceColumns ?? []), - colStart - ] + columnState[colEnd] = { + ...columnState[colEnd] } } - + } else { + // Else we're drawing a vertical line columnState[colEnd] = { - ...columnState[colEnd] + ...columnState[colEnd], + isVerticalLine: true } } - } else { - // Else we're drawing a vertical line - columnState[colEnd] = { - ...columnState[colEnd], - isVerticalLine: true - } - } - rowToColumnState.set(targetRow, columnState) + rowToColumnState.set(targetRow, columnState) + } } - } - }) + }) + } - // Add the commit nodes into their respective rows and columns - commitNodePositions.forEach((position) => { + const drawNode = (position: CommitNodeLocation) => { const [row, column] = position const columnState = rowToColumnState.get(row) ?? getEmptyColumnState() @@ -153,182 +158,195 @@ export const useColumnData = ({ visibleCommits }: GraphColumnDataProps): GraphCo } rowToColumnState.set(row, columnState) - }) - - // Add the vertical branch lines in from the current branches - // HEAD commit up to the index pseudo commit node. - if (headCommit && isIndexVisible) { - const headCommitRowIndex = positions.get(headCommit.hash)![0] - for (let rowIndex = 0; rowIndex <= headCommitRowIndex; rowIndex++) { - const columnState = rowToColumnState.get(rowIndex) ?? getEmptyColumnState() - - columnState[0] = { - ...columnState[0], - isVerticalLine: true, - isVerticalIndexLine: true - } - } } - // If, while server-side paginated, we find commits that need to draw - // lines to nodes that lie outside of this page of data, and those lines - // need to be drawn into columns that are beyond the current graph width, - // then we track the number of new "virtual" columns here that will be injected - // in the graph. - let virtualColumns = 0 + if (filter) { + filteredData.positions.forEach((position) => { + drawNode(position) + }) - if (isServerSidePaginated) { - // Any commits who have parent hashes that are not present in the graph - // must have vertical lines drawn from them down to the bottom row to indicate - // that the parent commit node lies beyond the rows currently shown. - const commitsWithUntrackedParents = commits.filter(({ parents }) => { - return parents.some(parentHash => { - return !positions.has(parentHash) - }) + drawEdges(filteredData.edges.map(({ from, to }) => [from, to] )) + } else { + // An iterable array of tuples containing commit node row and column indices + const commitNodePositions = Array.from(positions.values()) + + // Iterate over all the edges update the graph column state + // for each of the respective branch/merge line segments. + drawEdges(edges.search(0, commits.length).map(([from, to]) => [from, to] )) + + // Add the commit nodes into their respective rows and columns + commitNodePositions.forEach((position) => { + drawNode(position) }) - const drawVerticalLineToBottom = (fromCommitHash: string) => { - const [rowIndex, columnIndex] = positions.get(fromCommitHash)! - for (let targetRowIndex = rowIndex; targetRowIndex <= visibleCommits; targetRowIndex++) { - const columnState = rowToColumnState.get(targetRowIndex) ?? getEmptyColumnState() + // Add the vertical branch lines in from the current branches + // HEAD commit up to the index pseudo commit node. + if (headCommit && isIndexVisible) { + const headCommitRowIndex = positions.get(headCommit.hash)![0] + for (let rowIndex = 0; rowIndex <= headCommitRowIndex; rowIndex++) { + const columnState = rowToColumnState.get(rowIndex) ?? getEmptyColumnState() - columnState[columnIndex] = { - ...columnState[columnIndex], + columnState[0] = { + ...columnState[0], isVerticalLine: true, - isColumnBelowEmpty: false + isVerticalIndexLine: true } - - rowToColumnState.set(targetRowIndex, columnState) } } - // Non-merge commits we can just draw straight down to the edge of the graph - commitsWithUntrackedParents.filter(commit => commit.parents.length === 1).forEach(orphan => { - drawVerticalLineToBottom(orphan.hash) - }) + if (isServerSidePaginated) { + // Any commits who have parent hashes that are not present in the graph + // must have vertical lines drawn from them down to the bottom row to indicate + // that the parent commit node lies beyond the rows currently shown. + const commitsWithUntrackedParents = commits.filter(({ parents }) => { + return parents.some(parentHash => { + return !positions.has(parentHash) + }) + }) + + const drawVerticalLineToBottom = (fromCommitHash: string) => { + const [rowIndex, columnIndex] = positions.get(fromCommitHash)! + for (let targetRowIndex = rowIndex; targetRowIndex <= visibleCommits; targetRowIndex++) { + const columnState = rowToColumnState.get(targetRowIndex) ?? getEmptyColumnState() - // Merge commits may have lines coming out horizontally and then down to the bottom. - // Or we may find they can draw straight down if there is free space below to the bottom. - commitsWithUntrackedParents - .filter(commit => commit.parents.length > 1) - .sort((a, b) => positions.get(a.hash)![0] < positions.get(b.hash)![0] ? -1 : 1) - .forEach(orphan => { - const [rowIndex, columnIndex] = positions.get(orphan.hash)! - const columnStates = rowToColumnState.get(rowIndex) ?? getEmptyColumnState() - - // Can we just draw straight down in the current column? - let columnsBelowContainNode = false - let targetRowIndex = rowIndex + 1 - while(targetRowIndex <= visibleCommits) { - if (rowToColumnState.get(targetRowIndex)![columnIndex].isNode) { - columnsBelowContainNode = true + columnState[columnIndex] = { + ...columnState[columnIndex], + isVerticalLine: true, + isColumnBelowEmpty: false } - targetRowIndex++ + + rowToColumnState.set(targetRowIndex, columnState) } + } - if (!columnsBelowContainNode && rowIndex != visibleCommits) { - drawVerticalLineToBottom(orphan.hash) - } else { - // If not, we'll have to find a column to the side - let targetColumnIndex = columnIndex + // Non-merge commits we can just draw straight down to the edge of the graph + commitsWithUntrackedParents.filter(commit => commit.parents.length === 1).forEach(orphan => { + drawVerticalLineToBottom(orphan.hash) + }) - // Find the nearest column to the right that is empty - while(!isColumnEmpty(columnStates[targetColumnIndex])) { - targetColumnIndex++ + // Merge commits may have lines coming out horizontally and then down to the bottom. + // Or we may find they can draw straight down if there is free space below to the bottom. + commitsWithUntrackedParents + .filter(commit => commit.parents.length > 1) + .sort((a, b) => positions.get(a.hash)![0] < positions.get(b.hash)![0] ? -1 : 1) + .forEach(orphan => { + const [rowIndex, columnIndex] = positions.get(orphan.hash)! + const columnStates = rowToColumnState.get(rowIndex) ?? getEmptyColumnState() + + // Can we just draw straight down in the current column? + let columnsBelowContainNode = false + let targetRowIndex = rowIndex + 1 + while(targetRowIndex <= visibleCommits) { + if (rowToColumnState.get(targetRowIndex)![columnIndex].isNode) { + columnsBelowContainNode = true + } + targetRowIndex++ } - // For all columns in this row up until the target, draw a horizontal line - for (let colIndex = columnIndex; colIndex < targetColumnIndex; colIndex++) { - columnStates[colIndex] = { - ...columnStates[colIndex], - isHorizontalLine: true, + if (!columnsBelowContainNode && rowIndex != visibleCommits) { + drawVerticalLineToBottom(orphan.hash) + } else { + // If not, we'll have to find a column to the side + let targetColumnIndex = columnIndex + + // Find the nearest column to the right that is empty + while(!isColumnEmpty(columnStates[targetColumnIndex])) { + targetColumnIndex++ + } + + // For all columns in this row up until the target, draw a horizontal line + for (let colIndex = columnIndex; colIndex < targetColumnIndex; colIndex++) { + columnStates[colIndex] = { + ...columnStates[colIndex], + isHorizontalLine: true, + mergeSourceColumns: [targetColumnIndex] + } + } + + // Add the curve at the target index + columnStates[targetColumnIndex] = { + ...columnStates[targetColumnIndex], + isLeftDownCurve: true, mergeSourceColumns: [targetColumnIndex] } - } - // Add the curve at the target index - columnStates[targetColumnIndex] = { - ...columnStates[targetColumnIndex], - isLeftDownCurve: true, - mergeSourceColumns: [targetColumnIndex] - } + // Finally, add vertical lines from below the curve to the bottom of the graph + if (rowIndex < visibleCommits) { + for (let targetRowIndex = rowIndex + 1; targetRowIndex <= visibleCommits; targetRowIndex++) { + const targetRowColumnStates = rowToColumnState.get(targetRowIndex) ?? getEmptyColumnState() - // Finally, add vertical lines from below the curve to the bottom of the graph - if (rowIndex < visibleCommits) { - for (let targetRowIndex = rowIndex + 1; targetRowIndex <= visibleCommits; targetRowIndex++) { - const targetRowColumnStates = rowToColumnState.get(targetRowIndex) ?? getEmptyColumnState() + targetRowColumnStates[targetColumnIndex] = { + ...targetRowColumnStates[targetColumnIndex], + isVerticalLine: true, + mergeSourceColumns: [targetColumnIndex] + } - targetRowColumnStates[targetColumnIndex] = { - ...targetRowColumnStates[targetColumnIndex], - isVerticalLine: true, - mergeSourceColumns: [targetColumnIndex] + rowToColumnState.set(targetRowIndex, targetRowColumnStates) } + } - rowToColumnState.set(targetRowIndex, targetRowColumnStates) + // If we've had to draw outside the graph, then add enough virtual + // columns to support the new horizontal -> curve -> vertical merge lines. + const maxColumnIndex = graphWidth - 1 + if (targetColumnIndex > maxColumnIndex) { + // Add a virtual column for each horizontal line drawn, + // plus the column with the curve and vertical lines + virtualColumns = targetColumnIndex - maxColumnIndex } } - // If we've had to draw outside the graph, then add enough virtual - // columns to support the new horizontal -> curve -> vertical merge lines. - const maxColumnIndex = graphWidth - 1 - if (targetColumnIndex > maxColumnIndex) { - // Add a virtual column for each horizontal line drawn, - // plus the column with the curve and vertical lines - virtualColumns = targetColumnIndex - maxColumnIndex + rowToColumnState.set(rowIndex, columnStates) + }) + + // Any commits who have child hashes that are not present in the graph and + // are not the HEAD commit, must have vertical lines drawn from them up to + // the top row to indicate that the child commit node is before the rows currently shown. + commits.filter(commit => { + return commit.children.length === 0 && commit.hash !== headCommitHash + }).forEach(commitWithNoChildren => { + const [rowIndex, columnIndex] = positions.get(commitWithNoChildren.hash)! + for (let targetRowIndex = rowIndex; targetRowIndex >= 1; targetRowIndex--) { + const columnState = rowToColumnState.get(targetRowIndex) ?? getEmptyColumnState() + + columnState[columnIndex] = { + ...columnState[columnIndex], + isVerticalLine: true, + isColumnAboveEmpty: false } - } - rowToColumnState.set(rowIndex, columnStates) - }) - - // Any commits who have child hashes that are not present in the graph and - // are not the HEAD commit, must have vertical lines drawn from them up to - // the top row to indicate that the child commit node is before the rows currently shown. - commits.filter(commit => { - return commit.children.length === 0 && commit.hash !== headCommitHash - }).forEach(commitWithNoChildren => { - const [rowIndex, columnIndex] = positions.get(commitWithNoChildren.hash)! - for (let targetRowIndex = rowIndex; targetRowIndex >= 1; targetRowIndex--) { - const columnState = rowToColumnState.get(targetRowIndex) ?? getEmptyColumnState() - - columnState[columnIndex] = { - ...columnState[columnIndex], - isVerticalLine: true, - isColumnAboveEmpty: false + rowToColumnState.set(targetRowIndex, columnState) } + }) + } - rowToColumnState.set(targetRowIndex, columnState) + // The first row is told that its first so it can render with a gradient + const firstVisibleRowIndex = paging ? paging.startIndex + 1 : 1 + const firstRow = rowToColumnState.get(firstVisibleRowIndex) ?? getEmptyColumnState() + for(let firstRowColumn = 0; firstRowColumn < firstRow.length; firstRowColumn++) { + firstRow[firstRowColumn] = { + ...firstRow[firstRowColumn], + isFirstRow: true, } - }) - } - - // The first row is told that its first so it can render with a gradient - const firstVisibleRowIndex = paging ? paging.startIndex + 1 : 1 - const firstRow = rowToColumnState.get(firstVisibleRowIndex) ?? getEmptyColumnState() - for(let firstRowColumn = 0; firstRowColumn < firstRow.length; firstRowColumn++) { - firstRow[firstRowColumn] = { - ...firstRow[firstRowColumn], - isFirstRow: true, } - } - rowToColumnState.set(firstVisibleRowIndex, firstRow) - - // The last row is told that its last so it can render with a gradient - const lastVisibleRowIndex = paging ? paging.endIndex : visibleCommits - const lastRow = rowToColumnState.get(lastVisibleRowIndex) ?? getEmptyColumnState() - for(let lastRowColumn = 0; lastRowColumn < lastRow.length; lastRowColumn++) { - lastRow[lastRowColumn] = { - ...lastRow[lastRowColumn], - isLastRow: true, + rowToColumnState.set(firstVisibleRowIndex, firstRow) + + // The last row is told that its last so it can render with a gradient + const lastVisibleRowIndex = paging ? paging.endIndex : visibleCommits + const lastRow = rowToColumnState.get(lastVisibleRowIndex) ?? getEmptyColumnState() + for(let lastRowColumn = 0; lastRowColumn < lastRow.length; lastRowColumn++) { + lastRow[lastRowColumn] = { + ...lastRow[lastRowColumn], + isLastRow: true, + } } + rowToColumnState.set(lastVisibleRowIndex, lastRow) } - rowToColumnState.set(lastVisibleRowIndex, lastRow) return { rowToColumnState, virtualColumns } - }, [positions, edges, commits, headCommit, isIndexVisible, isServerSidePaginated, paging, getEmptyColumnState, visibleCommits, graphWidth, headCommitHash]) + }, [filter, getEmptyColumnState, filteredData.positions, filteredData.edges, positions, edges, commits, headCommit, isIndexVisible, isServerSidePaginated, paging, visibleCommits, graphWidth, headCommitHash]) return { getEmptyColumnState, diff --git a/packages/library/src/modules/Graph/types.ts b/packages/library/src/modules/Graph/types.ts index 6b898be3..e200ec89 100644 --- a/packages/library/src/modules/Graph/types.ts +++ b/packages/library/src/modules/Graph/types.ts @@ -5,7 +5,7 @@ export type GraphOrientation = 'normal' | 'flipped' export type Canvas2DGraphProps = GraphPropsCommon -export interface HTMLGridGraphProps extends GraphPropsCommon { +export interface HTMLGridGraphProps extends GraphPropsCommon { /** * Whether to show the commit hash * to the side of the node in the graph. diff --git a/packages/library/src/modules/Table/Table.tsx b/packages/library/src/modules/Table/Table.tsx index 71268036..12bac52e 100644 --- a/packages/library/src/modules/Table/Table.tsx +++ b/packages/library/src/modules/Table/Table.tsx @@ -23,17 +23,23 @@ export const Table = ({ }: TableProps) => { const { textColour, } = useTheme() const { placeholderData } = usePlaceholderData() - const { showHeaders, graphData, paging, indexCommit, isIndexVisible } = useGitContext() + const { showHeaders, graphData, paging, indexCommit, isIndexVisible, filter } = useGitContext() const tableData = useMemo(() => { - const data = graphData.commits.slice(paging?.startIndex, paging?.endIndex) + let data = graphData.commits + + if (filter) { + data = graphData.filteredCommits + } + + data = data.slice(paging?.startIndex, paging?.endIndex) if (isIndexVisible && indexCommit) { data.unshift(indexCommit) } return data - }, [graphData.commits, indexCommit, paging?.endIndex, isIndexVisible, paging?.startIndex]) + }, [graphData.commits, graphData.filteredCommits, paging?.startIndex, paging?.endIndex, filter, isIndexVisible, indexCommit]) const tableContextValue = useMemo(() => ({ timestampFormat diff --git a/packages/library/src/modules/Tags/Tags.tsx b/packages/library/src/modules/Tags/Tags.tsx index eb23e732..ec911f0c 100644 --- a/packages/library/src/modules/Tags/Tags.tsx +++ b/packages/library/src/modules/Tags/Tags.tsx @@ -34,18 +34,23 @@ export const Tags = () => { graphWidth, rowSpacing, isIndexVisible, - graphOrientation + graphOrientation, + filter } = useGitContext() const preparedCommits = useMemo(() => { - const data = graphData.commits.slice(paging?.startIndex, paging?.endIndex) + let data = graphData.commits.slice(paging?.startIndex, paging?.endIndex) + + if (filter) { + data = filter(data) + } if (isIndexVisible && indexCommit) { data.unshift(indexCommit) } return prepareCommits(data) - }, [graphData.commits, indexCommit, paging?.endIndex, isIndexVisible, paging?.startIndex]) + }, [graphData.commits, paging?.startIndex, paging?.endIndex, filter, isIndexVisible, indexCommit]) const tagLineWidth = useCallback((commit: Commit) => { const isNormalOrientation = graphOrientation === 'normal' diff --git a/packages/library/src/types.ts b/packages/library/src/types.ts index c9d6c6e2..b5407598 100644 --- a/packages/library/src/types.ts +++ b/packages/library/src/types.ts @@ -11,17 +11,14 @@ interface GitLogCommonProps { entries: GitLogEntry[] /** - * A list of SHA1 commit hashes that belong - * to commits that would normally be present - * on in the log but have been filtered out - * due to something like a search. + * Filters out entries from the log. * * The log, and any relevant subcomponents, * will filter these commits out, so they no * longer render, but will change their styling * to make it clear that commits are missing. */ - filteredCommits?: string[] + filter?: CommitFilter /** * The variant of the default colour @@ -65,7 +62,7 @@ interface GitLogCommonProps { * to link out to the remote repository on * the external Git provider. */ - urls?: GitLogUrlBuilder + urls?: GitLogUrlBuilder /** * The default width of the graph in pixels. @@ -142,7 +139,7 @@ interface GitLogCommonProps { classes?: GitLogStylingProps } -export interface GitLogProps extends GitLogCommonProps { +export interface GitLogProps extends GitLogCommonProps { /** * The name of the branch that is * currently checked out. @@ -262,13 +259,13 @@ export interface GitLogUrls { branch?: string } -export interface GitLogUrlBuilderArgs { +export interface GitLogUrlBuilderArgs { /** * Details of the given commit in context * of a URL. E.g. the one you clicked on * to link out to the external provider. */ - commit: Commit + commit: Commit } /** @@ -278,4 +275,6 @@ export interface GitLogUrlBuilderArgs { * * @param args Contextual commit information to help build the URLs. */ -export type GitLogUrlBuilder = (args: GitLogUrlBuilderArgs) => GitLogUrls \ No newline at end of file +export type GitLogUrlBuilder = (args: GitLogUrlBuilderArgs) => GitLogUrls + +export type CommitFilter = (entries: Commit[]) => Commit[] \ No newline at end of file From 79e556598aebb602f0b03c529c65ff85a134a295 Mon Sep 17 00:00:00 2001 From: Thomas Plumpton Date: Mon, 30 Jun 2025 19:17:24 +0100 Subject: [PATCH 02/26] chore(demo): hoisted theme state into demo context --- packages/demo/src/GitLog.stories.tsx | 17 +++++++---------- .../ColourSelector/ColourSelector.tsx | 5 ++++- .../demo/src/components/ColourSelector/types.ts | 3 --- .../src/components/GitLogDemo/GitLogDemo.tsx | 8 ++------ .../demo/src/components/Loading/Loading.tsx | 5 +++-- packages/demo/src/components/Loading/index.ts | 1 - packages/demo/src/components/Loading/types.ts | 5 ----- .../RepositorySelector/RepositorySelector.tsx | 5 ++++- .../src/components/RepositorySelector/types.ts | 3 --- .../SearchField/SearchField.module.scss | 3 +++ .../src/components/SearchField/SearchField.tsx | 14 +++++++++----- .../demo/src/components/SearchField/index.ts | 1 - .../demo/src/components/SearchField/types.ts | 5 ----- .../src/components/StoryHeader/StoryHeader.tsx | 16 ++++++---------- .../demo/src/components/StoryHeader/types.ts | 3 --- .../src/components/ThemeToggle/ThemeToggle.tsx | 9 +++++---- .../demo/src/components/ThemeToggle/index.ts | 1 - .../demo/src/components/ThemeToggle/types.ts | 6 ------ packages/demo/src/context/DemoContext.ts | 5 +++++ .../demo/src/context/DemoContextProvider.tsx | 8 ++++++-- packages/demo/src/context/types.ts | 4 ++++ .../src/hooks/useStoryState/useStoryState.ts | 12 ++++-------- .../library/src/context/GitContext/types.ts | 2 +- .../components/VerticalLine/VerticalLine.tsx | 2 +- packages/library/src/types.ts | 4 ++-- 25 files changed, 66 insertions(+), 81 deletions(-) delete mode 100644 packages/demo/src/components/Loading/types.ts delete mode 100644 packages/demo/src/components/SearchField/types.ts delete mode 100644 packages/demo/src/components/ThemeToggle/types.ts diff --git a/packages/demo/src/GitLog.stories.tsx b/packages/demo/src/GitLog.stories.tsx index 0c60ef3e..cc23787c 100644 --- a/packages/demo/src/GitLog.stories.tsx +++ b/packages/demo/src/GitLog.stories.tsx @@ -5,6 +5,7 @@ import { Loading } from '@components/Loading' import { useStoryState } from '@hooks/useStoryState' import { StoryHeader } from '@components/StoryHeader' import { GitLogDemo, GitLogStoryProps } from '@components/GitLogDemo' +import { useDemoContext } from '@context' const meta: Meta = { title: 'GitLog', @@ -284,7 +285,6 @@ export const Demo: Story = { export const CustomTableRow = () => { const { - theme, loading, colours, entries, @@ -292,25 +292,24 @@ export const CustomTableRow = () => { buildUrls, repository, backgroundColour, - handleChangeTheme, handleChangeColors, handleChangeRepository } = useStoryState() + const { theme } = useDemoContext() + return (
{loading && (
- +
)} @@ -388,7 +387,6 @@ const nodeImages = ['millie', 'neo', 'millie_neo', 'neo_banana', 'neo_2', 'bella export const CustomCommitNode = () => { const { - theme, loading, colours, entries, @@ -396,25 +394,24 @@ export const CustomCommitNode = () => { buildUrls, repository, backgroundColour, - handleChangeTheme, handleChangeColors, handleChangeRepository } = useStoryState() + const { theme } = useDemoContext() + return (
{loading && (
- +
)} diff --git a/packages/demo/src/components/ColourSelector/ColourSelector.tsx b/packages/demo/src/components/ColourSelector/ColourSelector.tsx index 6715f094..28524a8d 100644 --- a/packages/demo/src/components/ColourSelector/ColourSelector.tsx +++ b/packages/demo/src/components/ColourSelector/ColourSelector.tsx @@ -4,6 +4,7 @@ import { cyberpunkNeon, natureEssence, neonAurora, rainbow, retroPop, solarFlare import { CustomSelect } from '@components/CustomSelect' import { ColourItem } from '@components/ColourItem' import styles from './ColourSelector.module.scss' +import { useDemoContext } from '@context' const getTheme = (name: string) => { switch (name) { @@ -31,7 +32,9 @@ const getTheme = (name: string) => { } } -export const ColourSelector = ({ selected, onChange, theme }: ThemeSelectorProps) => { +export const ColourSelector = ({ selected, onChange }: ThemeSelectorProps) => { + const { theme } = useDemoContext() + const handleChange = useCallback((id: string) => { onChange({ id, diff --git a/packages/demo/src/components/ColourSelector/types.ts b/packages/demo/src/components/ColourSelector/types.ts index 178a2899..2595df1b 100644 --- a/packages/demo/src/components/ColourSelector/types.ts +++ b/packages/demo/src/components/ColourSelector/types.ts @@ -1,5 +1,3 @@ -import { ThemeMode } from '@tomplum/react-git-log' - export interface ColourSelection { id: string colors: string[] @@ -7,6 +5,5 @@ export interface ColourSelection { export interface ThemeSelectorProps { selected: string - theme: ThemeMode onChange: (theme: ColourSelection) => void } \ No newline at end of file diff --git a/packages/demo/src/components/GitLogDemo/GitLogDemo.tsx b/packages/demo/src/components/GitLogDemo/GitLogDemo.tsx index 1ae18860..05ad9a1b 100644 --- a/packages/demo/src/components/GitLogDemo/GitLogDemo.tsx +++ b/packages/demo/src/components/GitLogDemo/GitLogDemo.tsx @@ -8,7 +8,6 @@ import { useDemoContext } from '@context' export const GitLogDemo = (args: GitLogStoryProps) => { const { - theme, loading, colours, entries, @@ -16,27 +15,24 @@ export const GitLogDemo = (args: GitLogStoryProps) => { buildUrls, repository, backgroundColour, - handleChangeTheme, handleChangeColors, handleChangeRepository } = useStoryState() - const { search } = useDemoContext() + const { search, theme } = useDemoContext() return (
{loading && (
- +
)} diff --git a/packages/demo/src/components/Loading/Loading.tsx b/packages/demo/src/components/Loading/Loading.tsx index ba142e7e..11075e9c 100644 --- a/packages/demo/src/components/Loading/Loading.tsx +++ b/packages/demo/src/components/Loading/Loading.tsx @@ -1,7 +1,8 @@ import styles from './Loading.module.scss' -import { LoadingProps } from './types' +import { useDemoContext } from '@context' -export const Loading = ({ theme }: LoadingProps) => { +export const Loading = () => { + const { theme } = useDemoContext() const lineColour = theme === 'dark' ? ['#f8f8f8', '#0000'] : ['#524656', '#0000'] return ( diff --git a/packages/demo/src/components/Loading/index.ts b/packages/demo/src/components/Loading/index.ts index 86ae65bf..e6c307f4 100644 --- a/packages/demo/src/components/Loading/index.ts +++ b/packages/demo/src/components/Loading/index.ts @@ -1,2 +1 @@ -export * from './types' export * from './Loading' \ No newline at end of file diff --git a/packages/demo/src/components/Loading/types.ts b/packages/demo/src/components/Loading/types.ts deleted file mode 100644 index 61b4ab66..00000000 --- a/packages/demo/src/components/Loading/types.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { ThemeMode } from '@tomplum/react-git-log' - -export interface LoadingProps { - theme: ThemeMode -} \ No newline at end of file diff --git a/packages/demo/src/components/RepositorySelector/RepositorySelector.tsx b/packages/demo/src/components/RepositorySelector/RepositorySelector.tsx index 07a5470b..4b6eb56a 100644 --- a/packages/demo/src/components/RepositorySelector/RepositorySelector.tsx +++ b/packages/demo/src/components/RepositorySelector/RepositorySelector.tsx @@ -5,8 +5,11 @@ import JapaneseLantern from '@assets/lantern.svg?react' import SleepIcon from '@assets/sleep.svg?react' import StarIcon from '@assets/star.svg?react' import styles from './RepositorySelector.module.scss' +import { useDemoContext } from '@context' + +export const RepositorySelector = ({ selected, onSelect }: RepositorySelectorProps) => { + const { theme } = useDemoContext() -export const RepositorySelector = ({ selected, onSelect, theme }: RepositorySelectorProps) => { return ( void } \ No newline at end of file diff --git a/packages/demo/src/components/SearchField/SearchField.module.scss b/packages/demo/src/components/SearchField/SearchField.module.scss index e69de29b..abd53076 100644 --- a/packages/demo/src/components/SearchField/SearchField.module.scss +++ b/packages/demo/src/components/SearchField/SearchField.module.scss @@ -0,0 +1,3 @@ +.SearchField { + +} \ No newline at end of file diff --git a/packages/demo/src/components/SearchField/SearchField.tsx b/packages/demo/src/components/SearchField/SearchField.tsx index caa1350c..b2daea0b 100644 --- a/packages/demo/src/components/SearchField/SearchField.tsx +++ b/packages/demo/src/components/SearchField/SearchField.tsx @@ -1,20 +1,24 @@ import styles from './SearchField.module.scss' -import { SearchFieldProps } from './types' -import { ChangeEvent } from 'react' +import { ChangeEvent, CSSProperties, useMemo } from 'react' import { useDemoContext } from '@context' -export const SearchField = ({ theme }: SearchFieldProps) => { - const { search, setSearch } = useDemoContext() +export const SearchField = () => { + const { search, setSearch, theme } = useDemoContext() const handleChange= (e: ChangeEvent) => { setSearch(e.target.value) } + const style = useMemo(() => ({ + background: theme === 'dark' ? '' : '' + }), [theme]) + return ( ) } \ No newline at end of file diff --git a/packages/demo/src/components/SearchField/index.ts b/packages/demo/src/components/SearchField/index.ts index e0017c08..a0ee850f 100644 --- a/packages/demo/src/components/SearchField/index.ts +++ b/packages/demo/src/components/SearchField/index.ts @@ -1,2 +1 @@ -export * from './types' export * from './SearchField' \ No newline at end of file diff --git a/packages/demo/src/components/SearchField/types.ts b/packages/demo/src/components/SearchField/types.ts deleted file mode 100644 index 40c62e07..00000000 --- a/packages/demo/src/components/SearchField/types.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { ThemeMode } from '@tomplum/react-git-log' - -export interface SearchFieldProps { - theme: ThemeMode -} \ No newline at end of file diff --git a/packages/demo/src/components/StoryHeader/StoryHeader.tsx b/packages/demo/src/components/StoryHeader/StoryHeader.tsx index 780e078d..e279bd79 100644 --- a/packages/demo/src/components/StoryHeader/StoryHeader.tsx +++ b/packages/demo/src/components/StoryHeader/StoryHeader.tsx @@ -6,8 +6,11 @@ import { PackageInfo } from '@components/PackageInfo' import { StoryHeaderProps } from '@components/StoryHeader/types' import { PropsWithChildren } from 'react' import { SearchField } from '@components/SearchField' +import { useDemoContext } from '@context' -export const StoryHeader = ({ children, theme, repository, colours, onChangeColours, onChangeRepository, onChangeTheme }: PropsWithChildren) => { +export const StoryHeader = ({ children, repository, colours, onChangeColours, onChangeRepository }: PropsWithChildren) => { + const { theme } = useDemoContext() + return (
- +
- +
diff --git a/packages/demo/src/components/StoryHeader/types.ts b/packages/demo/src/components/StoryHeader/types.ts index 5c3b6f9b..74daf4a6 100644 --- a/packages/demo/src/components/StoryHeader/types.ts +++ b/packages/demo/src/components/StoryHeader/types.ts @@ -1,11 +1,8 @@ import { ColourSelection } from '@components/ColourSelector' -import { ThemeMode } from '@tomplum/react-git-log' export interface StoryHeaderProps { - theme: ThemeMode colours: ColourSelection repository: string onChangeRepository: (repository: string) => void onChangeColours: (event: ColourSelection) => void - onChangeTheme: (theme: ThemeMode) => void } \ No newline at end of file diff --git a/packages/demo/src/components/ThemeToggle/ThemeToggle.tsx b/packages/demo/src/components/ThemeToggle/ThemeToggle.tsx index ff9a59b8..41e82902 100644 --- a/packages/demo/src/components/ThemeToggle/ThemeToggle.tsx +++ b/packages/demo/src/components/ThemeToggle/ThemeToggle.tsx @@ -1,15 +1,16 @@ -import { ThemeToggleProps } from './types' import { useCallback } from 'react' import { Within } from '@theme-toggles/react' +import { useDemoContext } from '@context' /** * From https://toggles.dev/within */ -export const ThemeToggle = ({ theme, onChange }: ThemeToggleProps) => { +export const ThemeToggle = () => { + const { theme, setTheme } = useDemoContext() const handleChange = useCallback(() => { - onChange(theme === 'dark' ? 'light' : 'dark') - }, [onChange, theme]) + setTheme(theme === 'dark' ? 'light' : 'dark') + }, [setTheme, theme]) return ( // @ts-expect-error Bad typing in library diff --git a/packages/demo/src/components/ThemeToggle/index.ts b/packages/demo/src/components/ThemeToggle/index.ts index 8a914bfe..5a360f3a 100644 --- a/packages/demo/src/components/ThemeToggle/index.ts +++ b/packages/demo/src/components/ThemeToggle/index.ts @@ -1,2 +1 @@ -export * from './types' export * from './ThemeToggle' \ No newline at end of file diff --git a/packages/demo/src/components/ThemeToggle/types.ts b/packages/demo/src/components/ThemeToggle/types.ts deleted file mode 100644 index 38813731..00000000 --- a/packages/demo/src/components/ThemeToggle/types.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { ThemeMode } from '@tomplum/react-git-log' - -export interface ThemeToggleProps { - theme: ThemeMode - onChange: (theme: ThemeMode) => void -} \ No newline at end of file diff --git a/packages/demo/src/context/DemoContext.ts b/packages/demo/src/context/DemoContext.ts index 7ff44599..4eefb243 100644 --- a/packages/demo/src/context/DemoContext.ts +++ b/packages/demo/src/context/DemoContext.ts @@ -1,9 +1,14 @@ import { createContext } from 'react' import { DemoContextBag } from './types' +import { ThemeMode } from '@tomplum/react-git-log' export const DemoContext = createContext({ search: '', setSearch: (value: string) => { console.warn(`Tried to invoke setSearch(${value}) before the DemoContext was initialised.`) + }, + theme: 'dark', + setTheme: (theme: ThemeMode) => { + console.warn(`Tried to invoke setTheme(${theme}) before the DemoContext was initialised.`) } }) \ No newline at end of file diff --git a/packages/demo/src/context/DemoContextProvider.tsx b/packages/demo/src/context/DemoContextProvider.tsx index 583344cf..d8eb672a 100644 --- a/packages/demo/src/context/DemoContextProvider.tsx +++ b/packages/demo/src/context/DemoContextProvider.tsx @@ -1,14 +1,18 @@ import { PropsWithChildren, useMemo, useState } from 'react' import { DemoContextBag } from './types' import { DemoContext } from './DemoContext' +import { ThemeMode } from '@tomplum/react-git-log' export const DemoContextProvider = ({ children }: PropsWithChildren) => { const [search, setSearch] = useState() + const [theme, setTheme] = useState('dark') const value = useMemo(() => ({ search, - setSearch - }), [search]) + setSearch, + theme, + setTheme + }), [search, theme]) return ( diff --git a/packages/demo/src/context/types.ts b/packages/demo/src/context/types.ts index d9dc8942..cef179db 100644 --- a/packages/demo/src/context/types.ts +++ b/packages/demo/src/context/types.ts @@ -1,4 +1,8 @@ +import { ThemeMode } from '@tomplum/react-git-log' + export interface DemoContextBag { search?: string setSearch: (value: string) => void + theme: ThemeMode + setTheme: (theme: ThemeMode) => void } \ No newline at end of file diff --git a/packages/demo/src/hooks/useStoryState/useStoryState.ts b/packages/demo/src/hooks/useStoryState/useStoryState.ts index d7832418..31229f1f 100644 --- a/packages/demo/src/hooks/useStoryState/useStoryState.ts +++ b/packages/demo/src/hooks/useStoryState/useStoryState.ts @@ -1,9 +1,10 @@ import { useCallback, useEffect, useMemo, useState } from 'react' -import type { ThemeMode, GitLogUrlBuilder, GitLogUrlBuilderArgs } from '@tomplum/react-git-log' +import type { GitLogUrlBuilder, GitLogUrlBuilderArgs } from '@tomplum/react-git-log' import { ColourSelection } from '@components/ColourSelector' import { rainbow } from 'themes' import { StoryStateProps } from './types' import { useGitLogEntries } from '@hooks/useGitLogEntries' +import { useDemoContext } from '@context' const getRepositoryConfig = (name: string) => { switch (name) { @@ -46,6 +47,8 @@ const getRepositoryConfig = (name: string) => { export const useStoryState = ({ isServerSidePaginated, onChangeRepository }: StoryStateProps = {}) => { const [repository, setRepository] = useState('TomPlum/sleep') + const { theme } = useDemoContext() + const { branchName, fileNameEntireHistory, @@ -59,7 +62,6 @@ export const useStoryState = ({ isServerSidePaginated, onChangeRepository }: Sto fileName: isServerSidePaginated ? fileNameCheckedOutBranch : fileNameEntireHistory }) - const [theme, setTheme] = useState('dark') const [colours, setColours] = useState({ id: 'rainbow', colors: rainbow }) const handleChangeColors = useCallback((selected: ColourSelection) => { @@ -68,10 +70,6 @@ export const useStoryState = ({ isServerSidePaginated, onChangeRepository }: Sto const backgroundColour = theme === 'dark' ? '#1a1a1a' : 'white' - const handleChangeTheme = useCallback((newTheme: ThemeMode) => { - setTheme(newTheme) - }, []) - const buildUrls = useMemo(() => { return ({ commit }: GitLogUrlBuilderArgs) => { const formattedBranch = commit.branch @@ -98,12 +96,10 @@ export const useStoryState = ({ isServerSidePaginated, onChangeRepository }: Sto branch: branchName, headCommitHash: isServerSidePaginated ? headCommitCheckoutOutBranch : headCommitHash, entries: data, - theme, colours, repository, backgroundColour, handleChangeColors, - handleChangeTheme, handleChangeRepository: setRepository, buildUrls } diff --git a/packages/library/src/context/GitContext/types.ts b/packages/library/src/context/GitContext/types.ts index 1ae4f954..601be2a5 100644 --- a/packages/library/src/context/GitContext/types.ts +++ b/packages/library/src/context/GitContext/types.ts @@ -117,7 +117,7 @@ export interface GitContextBag { * A function that builds links to the remote * repository on the external Git provider. */ - remoteProviderUrlBuilder?: GitLogUrlBuilder + remoteProviderUrlBuilder?: GitLogUrlBuilder /** * The spacing between the rows of the log. diff --git a/packages/library/src/modules/Graph/strategies/Grid/components/VerticalLine/VerticalLine.tsx b/packages/library/src/modules/Graph/strategies/Grid/components/VerticalLine/VerticalLine.tsx index 3ee36b90..e46ecaab 100644 --- a/packages/library/src/modules/Graph/strategies/Grid/components/VerticalLine/VerticalLine.tsx +++ b/packages/library/src/modules/Graph/strategies/Grid/components/VerticalLine/VerticalLine.tsx @@ -100,7 +100,7 @@ export const VerticalLine = ({ state, columnIndex, columnColour, commit, indexCo } } - // If none of the above conditions are met then + // If none of the above conditions are met, then // we must be in a column with no commit node, and // so we draw a line the full height of the column. return { diff --git a/packages/library/src/types.ts b/packages/library/src/types.ts index b5407598..8282ab7d 100644 --- a/packages/library/src/types.ts +++ b/packages/library/src/types.ts @@ -259,7 +259,7 @@ export interface GitLogUrls { branch?: string } -export interface GitLogUrlBuilderArgs { +export interface GitLogUrlBuilderArgs { /** * Details of the given commit in context * of a URL. E.g. the one you clicked on @@ -275,6 +275,6 @@ export interface GitLogUrlBuilderArgs { * * @param args Contextual commit information to help build the URLs. */ -export type GitLogUrlBuilder = (args: GitLogUrlBuilderArgs) => GitLogUrls +export type GitLogUrlBuilder = (args: GitLogUrlBuilderArgs) => GitLogUrls export type CommitFilter = (entries: Commit[]) => Commit[] \ No newline at end of file From 42f4a737a8728bea283367f9a7278ead68bfc061 Mon Sep 17 00:00:00 2001 From: Thomas Plumpton Date: Mon, 30 Jun 2025 19:31:49 +0100 Subject: [PATCH 03/26] chore(demo): made search field styling consistent with other demo ui elements --- .../SearchField/SearchField.module.scss | 7 ++++ .../components/SearchField/SearchField.tsx | 9 ++++- .../ThemeToggle/ThemeToggle.module.scss | 8 ++++ .../components/ThemeToggle/ThemeToggle.tsx | 38 +++++++++++-------- 4 files changed, 45 insertions(+), 17 deletions(-) create mode 100644 packages/demo/src/components/ThemeToggle/ThemeToggle.module.scss diff --git a/packages/demo/src/components/SearchField/SearchField.module.scss b/packages/demo/src/components/SearchField/SearchField.module.scss index abd53076..9bb88f4a 100644 --- a/packages/demo/src/components/SearchField/SearchField.module.scss +++ b/packages/demo/src/components/SearchField/SearchField.module.scss @@ -1,3 +1,10 @@ .SearchField { + border: none; + border-radius: 5px; + padding: 8px; + font-family: 'JetBrains Mono', system-ui, Avenir, Helvetica, Arial, sans-serif; + &:focus { + outline: none; + } } \ No newline at end of file diff --git a/packages/demo/src/components/SearchField/SearchField.tsx b/packages/demo/src/components/SearchField/SearchField.tsx index b2daea0b..3e01dae6 100644 --- a/packages/demo/src/components/SearchField/SearchField.tsx +++ b/packages/demo/src/components/SearchField/SearchField.tsx @@ -9,9 +9,13 @@ export const SearchField = () => { setSearch(e.target.value) } + const textColour = theme === 'dark' ? 'rgb(239,239,239)' : 'rgb(0,0,0)' + const backgroundColour = theme === 'dark' ? 'rgb(68,68,68)' : 'rgb(218,218,218)' + const style = useMemo(() => ({ - background: theme === 'dark' ? '' : '' - }), [theme]) + background: backgroundColour, + color: textColour + }), [backgroundColour, textColour]) return ( { value={search ?? ''} onChange={handleChange} className={styles.SearchField} + placeholder='Search to filter commits...' /> ) } \ No newline at end of file diff --git a/packages/demo/src/components/ThemeToggle/ThemeToggle.module.scss b/packages/demo/src/components/ThemeToggle/ThemeToggle.module.scss new file mode 100644 index 00000000..accf0111 --- /dev/null +++ b/packages/demo/src/components/ThemeToggle/ThemeToggle.module.scss @@ -0,0 +1,8 @@ +.Wrapper { + height: 44px; + width: 44px; + display: flex; + align-content: center; + justify-content: center; + border-radius: 5px; +} \ No newline at end of file diff --git a/packages/demo/src/components/ThemeToggle/ThemeToggle.tsx b/packages/demo/src/components/ThemeToggle/ThemeToggle.tsx index 41e82902..af908864 100644 --- a/packages/demo/src/components/ThemeToggle/ThemeToggle.tsx +++ b/packages/demo/src/components/ThemeToggle/ThemeToggle.tsx @@ -1,6 +1,7 @@ -import { useCallback } from 'react' +import { CSSProperties, useCallback, useMemo } from 'react' import { Within } from '@theme-toggles/react' import { useDemoContext } from '@context' +import styles from './ThemeToggle.module.scss' /** * From https://toggles.dev/within @@ -12,20 +13,27 @@ export const ThemeToggle = () => { setTheme(theme === 'dark' ? 'light' : 'dark') }, [setTheme, theme]) + const backgroundColour = theme === 'dark' ? 'rgb(68,68,68)' : 'rgb(218,218,218)' + + const style = useMemo(() => ({ + background: backgroundColour, + }), [backgroundColour]) + return ( - // @ts-expect-error Bad typing in library - +
+ {/* @ts-expect-error Bad typing in library */} + +
) } \ No newline at end of file From f905e5c48b0e23737fe3acc683ef85eda12dbd83 Mon Sep 17 00:00:00 2001 From: Thomas Plumpton Date: Tue, 1 Jul 2025 19:30:54 +0100 Subject: [PATCH 04/26] feat(graph): added in filtered graph break points for the HTML graph --- README.md | 5 + packages/library/src/GitLog.spec.tsx | 22 + .../src/__snapshots__/GitLog.spec.tsx.snap | 4720 +++++++++++++++++ packages/library/src/data/types.ts | 2 +- .../src/modules/Graph/context/GraphContext.ts | 3 +- .../src/modules/Graph/context/types.ts | 7 + .../src/modules/Graph/core/GraphCore.tsx | 8 +- .../strategies/Grid/HTMLGridGraph.spec.tsx | 1 + .../components/GraphColumn/GraphColumn.tsx | 2 + .../IndexPseudoRow/IndexPseudoRow.tsx | 7 +- .../LeftDownCurve/LeftDownCurve.module.scss | 15 + .../LeftDownCurve/LeftDownCurve.tsx | 10 +- .../Grid/components/LeftDownCurve/types.ts | 1 + .../LeftUpCurve/LeftUpCurve.module.scss | 15 + .../components/LeftUpCurve/LeftUpCurve.tsx | 10 +- .../Grid/components/LeftUpCurve/types.ts | 1 + .../VerticalLine/VerticalLine.module.scss | 47 + .../components/VerticalLine/VerticalLine.tsx | 81 +- .../Grid/hooks/useColumnData/useColumnData.ts | 85 +- 19 files changed, 5008 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index ce76b37e..4603e9fd 100644 --- a/README.md +++ b/README.md @@ -521,6 +521,11 @@ Graphs - Add styling to the broken edges to indicate its not the true parent - Make index commit find the nearest HEAD ancestor, or go off the graph otherwise - Does this work with GitLogPaged? + - Rework CommitNodeLocation from a tuple into an object + - Hover effect on lines to show missing commits? + - Break point themes? Slash, dot, zig-zag etc + - Make storybook radios into dropdowns + - Prop to override graph fallback? No search results yields the placeholder nodes Canvas2D - Custom prop for BG colour because of how canvas alpha channel works diff --git a/packages/library/src/GitLog.spec.tsx b/packages/library/src/GitLog.spec.tsx index 44d2832f..f1d6e5c0 100644 --- a/packages/library/src/GitLog.spec.tsx +++ b/packages/library/src/GitLog.spec.tsx @@ -137,6 +137,28 @@ describe('GitLog', () => { expect(asFragment()).toMatchSnapshot() }) + it('should render correctly and match the snapshot of the GitLog component that has been filtered', { timeout: 1000 * 10 } ,() => { + const { asFragment } = render( + { + return commits.filter(({ message }) => { + return message.includes('deps') + }) + }} + > + + + + + ) + + expect(asFragment()).toMatchSnapshot() + }) + it('should render correctly and match the snapshot of the GitLog component when there is no data', { timeout: 1000 * 10 } ,() => { const { asFragment } = render( HTML Grid Graph > should render correctly and match the snapsh `; +exports[`GitLog > HTML Grid Graph > should render correctly and match the snapshot of the GitLog component that has been filtered 1`] = ` + +
+
+

+ Branch / Tag +

+
+ + + + + +
+ + +
+
+
+ +
+
+
+
+
+
+
+
+
+
+ + + + +
+ + + +
+
+
+

+ Graph +

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
+
+ Commit message +
+
+ Author +
+
+ Timestamp +
+
+
+
+ // WIP +
+
+ - +
+
+ - +
+
+
+
+ fix(deps): update all non-major dependencies +
+
+ renovate[bot] +
+
+ an hour ago +
+
+
+
+ fix(deps): update dependency @ant-design/icons to v6 +
+
+ renovate[bot] +
+
+ 3 days ago +
+
+
+
+ fix(deps): update react monorepo to v19 +
+
+ renovate[bot] +
+
+ 2025-03-05 17:29:54 +
+
+
+
+ fix(deps): update dependency react-router-dom to v7 +
+
+ renovate[bot] +
+
+ 2025-03-05 17:29:47 +
+
+
+
+ fix(deps): update all non-major dependencies +
+
+ renovate[bot] +
+
+ 2025-03-05 10:55:09 +
+
+
+
+ fix(deps): update react monorepo to v19 +
+
+ renovate[bot] +
+
+ 2025-02-25 17:14:01 +
+
+
+
+ chore(deps): update dependency globals to v16 +
+
+ renovate[bot] +
+
+ 2025-02-22 07:05:41 +
+
+
+
+ chore(deps): update dependency @stylistic/eslint-plugin to v4 +
+
+ renovate[bot] +
+
+ 2025-02-22 02:47:35 +
+
+
+
+ fix(deps): update all non-major dependencies +
+
+ renovate[bot] +
+
+ 2025-02-22 02:47:25 +
+
+
+
+ chore(deps): update dependency vite to v6 +
+
+ renovate[bot] +
+
+ 2025-02-16 15:21:55 +
+
+
+
+ fix(deps): update all non-major dependencies +
+
+ renovate[bot] +
+
+ 2025-02-16 15:21:05 +
+
+
+
+ fix(deps): update all non-major dependencies +
+
+ renovate[bot] +
+
+ 2025-02-14 01:53:30 +
+
+
+
+ fix(deps): npm install to fix lockfile issues +
+
+ Thomas Plumpton +
+
+ 2025-02-01 15:22:07 +
+
+
+
+ chore(deps): update dependency @stylistic/eslint-plugin to v3 +
+
+ renovate[bot] +
+
+ 2025-02-01 15:16:12 +
+
+
+
+ fix(deps): update all non-major dependencies +
+
+ renovate[bot] +
+
+ 2025-01-31 21:51:43 +
+
+
+
+ chore(deps): update vitest monorepo to v3 +
+
+ renovate[bot] +
+
+ 2025-01-19 09:39:08 +
+
+
+
+ chore(deps): update dependency jsdom to v26 +
+
+ renovate[bot] +
+
+ 2025-01-11 01:17:46 +
+
+
+
+ fix(deps): removed redundant package-lock.json entries +
+
+ Thomas Plumpton +
+
+ 2025-01-09 20:16:59 +
+
+
+
+ fix(deps): update all non-major dependencies +
+
+ renovate[bot] +
+
+ 2025-01-08 22:25:06 +
+
+
+
+ fix(deps): update dependency react-error-boundary to v5 +
+
+ renovate[bot] +
+
+ 2024-12-21 21:55:38 +
+
+
+
+ chore(deps): removed react-force-graph and replaced with 3d standalone package and bumped three back to latest +
+
+ Thomas Plumpton +
+
+ 2024-11-28 17:57:17 +
+
+
+
+ chore(deps): upgraded i18next to major version 24 +
+
+ Thomas Plumpton +
+
+ 2024-11-23 19:39:33 +
+
+
+
+ chore(deps): update all non-major dependencies +
+
+ renovate[bot] +
+
+ 2024-11-23 04:50:20 +
+
+
+
+ chore(deps): update all non-major dependencies +
+
+ renovate[bot] +
+
+ 2024-11-19 14:00:17 +
+
+
+
+ chore(deps): update all non-major dependencies +
+
+ renovate[bot] +
+
+ 2024-11-14 13:09:47 +
+
+
+
+ chore(deps): update all non-major dependencies +
+
+ renovate[bot] +
+
+ 2024-11-09 04:39:56 +
+
+
+
+ chore(deps): update all non-major dependencies +
+
+ renovate[bot] +
+
+ 2024-10-30 15:18:13 +
+
+
+
+ chore(deps): update all non-major dependencies +
+
+ renovate[bot] +
+
+ 2024-10-19 04:30:16 +
+
+
+
+ chore(deps): update all non-major dependencies +
+
+ renovate[bot] +
+
+ 2024-10-14 13:44:02 +
+
+
+
+
+ +`; + exports[`GitLog > HTML Grid Graph > should render correctly and match the snapshot of the GitLog component when the index is disabled 1`] = `
{ edges: { from: CommitNodeLocation to: CommitNodeLocation - rerouted?: boolean + rerouted: boolean }[] } } diff --git a/packages/library/src/modules/Graph/context/GraphContext.ts b/packages/library/src/modules/Graph/context/GraphContext.ts index 63050a23..395dcfad 100644 --- a/packages/library/src/modules/Graph/context/GraphContext.ts +++ b/packages/library/src/modules/Graph/context/GraphContext.ts @@ -9,5 +9,6 @@ export const GraphContext = createContext({ graphWidth: 0, orientation: 'normal', visibleCommits: [], - columnData: new Map() + columnData: new Map(), + isHeadCommitVisible: false }) \ No newline at end of file diff --git a/packages/library/src/modules/Graph/context/types.ts b/packages/library/src/modules/Graph/context/types.ts index 2df076f7..53895289 100644 --- a/packages/library/src/modules/Graph/context/types.ts +++ b/packages/library/src/modules/Graph/context/types.ts @@ -71,6 +71,13 @@ export interface GraphContextBag { */ visibleCommits: Commit[] + /** + * True if the HEAD commit is visible + * based on the current pagination + * and any filtering. + */ + isHeadCommitVisible: boolean + /** * A map of row indices to their * respective column states. diff --git a/packages/library/src/modules/Graph/core/GraphCore.tsx b/packages/library/src/modules/Graph/core/GraphCore.tsx index 109ae1be..0643cd96 100644 --- a/packages/library/src/modules/Graph/core/GraphCore.tsx +++ b/packages/library/src/modules/Graph/core/GraphCore.tsx @@ -23,6 +23,7 @@ export const GraphCore = ({ paging, filter, setNodeSize, + headCommit, setGraphOrientation, graphData: { graphWidth, commits } } = useGitContext() @@ -46,6 +47,10 @@ export const GraphCore = ({ return filteredCommits }, [commits, filter, paging]) + const isHeadCommitVisible = useMemo(() => { + return visibleCommits.find(commit => commit.hash === headCommit?.hash) !== undefined + }, [headCommit, visibleCommits]) + const { columnData, virtualColumns } = useColumnData({ visibleCommits: visibleCommits.length }) @@ -60,8 +65,9 @@ export const GraphCore = ({ orientation, visibleCommits, columnData, + isHeadCommitVisible, highlightedBackgroundHeight - }), [node, showCommitNodeTooltips, showCommitNodeHashes, nodeTheme, nodeSize, graphWidth, virtualColumns, orientation, visibleCommits, columnData, highlightedBackgroundHeight]) + }), [node, showCommitNodeTooltips, isHeadCommitVisible, showCommitNodeHashes, nodeTheme, nodeSize, graphWidth, virtualColumns, orientation, visibleCommits, columnData, highlightedBackgroundHeight]) return ( diff --git a/packages/library/src/modules/Graph/strategies/Grid/HTMLGridGraph.spec.tsx b/packages/library/src/modules/Graph/strategies/Grid/HTMLGridGraph.spec.tsx index fcaf53dc..00ea9886 100644 --- a/packages/library/src/modules/Graph/strategies/Grid/HTMLGridGraph.spec.tsx +++ b/packages/library/src/modules/Graph/strategies/Grid/HTMLGridGraph.spec.tsx @@ -22,6 +22,7 @@ describe('HTML Grid Graph', () => { vi.spyOn(graphContext, 'useGraphContext').mockReturnValue(graphContextBag({ nodeSize: 21, + isHeadCommitVisible: true, node: ({ commit, nodeSize, isIndexPseudoNode, rowIndex, columnIndex, colour }) => { const id = `custom-commit-node-${rowIndex}` diff --git a/packages/library/src/modules/Graph/strategies/Grid/components/GraphColumn/GraphColumn.tsx b/packages/library/src/modules/Graph/strategies/Grid/components/GraphColumn/GraphColumn.tsx index 836b49af..de8e6eeb 100644 --- a/packages/library/src/modules/Graph/strategies/Grid/components/GraphColumn/GraphColumn.tsx +++ b/packages/library/src/modules/Graph/strategies/Grid/components/GraphColumn/GraphColumn.tsx @@ -208,6 +208,7 @@ export const GraphColumn = ({ )} @@ -215,6 +216,7 @@ export const GraphColumn = ({ {state.isLeftUpCurve && ( )} diff --git a/packages/library/src/modules/Graph/strategies/Grid/components/IndexPseudoRow/IndexPseudoRow.tsx b/packages/library/src/modules/Graph/strategies/Grid/components/IndexPseudoRow/IndexPseudoRow.tsx index e02261ad..6c470af3 100644 --- a/packages/library/src/modules/Graph/strategies/Grid/components/IndexPseudoRow/IndexPseudoRow.tsx +++ b/packages/library/src/modules/Graph/strategies/Grid/components/IndexPseudoRow/IndexPseudoRow.tsx @@ -6,18 +6,19 @@ import { useGraphContext } from 'modules/Graph/context' export const IndexPseudoRow = () => { const { indexCommit } = useGitContext() - const { graphWidth } = useGraphContext() + const { graphWidth, isHeadCommitVisible } = useGraphContext() const indexColumns = useMemo(() => { const columns = new Array(graphWidth).fill({}) columns[0] = { isNode: true, - isVerticalLine: true + isVerticalLine: true, + isBottomBreakPoint: !isHeadCommitVisible } return columns - }, [graphWidth]) + }, [graphWidth, isHeadCommitVisible]) if (indexCommit) { return ( diff --git a/packages/library/src/modules/Graph/strategies/Grid/components/LeftDownCurve/LeftDownCurve.module.scss b/packages/library/src/modules/Graph/strategies/Grid/components/LeftDownCurve/LeftDownCurve.module.scss index c67674e0..9e82a90b 100644 --- a/packages/library/src/modules/Graph/strategies/Grid/components/LeftDownCurve/LeftDownCurve.module.scss +++ b/packages/library/src/modules/Graph/strategies/Grid/components/LeftDownCurve/LeftDownCurve.module.scss @@ -4,4 +4,19 @@ .curve, .line { position: absolute; +} + +.breakPoint { + position: absolute; + + $size: 14px; + content: ''; + left: 50%; + top: 0; + width: $size; + height: 3px; + border-radius: 2px; + background: var(--breakpoint-colour); + transform: translate(-50%, -50%) rotate(-45deg); + transform-origin: center; } \ No newline at end of file diff --git a/packages/library/src/modules/Graph/strategies/Grid/components/LeftDownCurve/LeftDownCurve.tsx b/packages/library/src/modules/Graph/strategies/Grid/components/LeftDownCurve/LeftDownCurve.tsx index 44806ca9..a2b34537 100644 --- a/packages/library/src/modules/Graph/strategies/Grid/components/LeftDownCurve/LeftDownCurve.tsx +++ b/packages/library/src/modules/Graph/strategies/Grid/components/LeftDownCurve/LeftDownCurve.tsx @@ -3,14 +3,22 @@ import { CURVE_SIZE, ROW_HEIGHT } from 'constants/constants' import { CurvedEdge } from 'modules/Graph/strategies/Grid/components/CurvedEdge' import { useGitContext } from 'context/GitContext' import { LeftDownCurveProps } from './types' +import { CSSProperties } from 'react' -export const LeftDownCurve = ({ color, isPlaceholder }: LeftDownCurveProps) => { +export const LeftDownCurve = ({ color, isPlaceholder, showBottomBreakPoint }: LeftDownCurveProps) => { const { rowSpacing } = useGitContext() const borderStyle = isPlaceholder ? 'dotted' : 'solid' return (
+ {showBottomBreakPoint && ( +
+ )} +
{ +export const LeftUpCurve = ({ color, isPlaceholder, showTopBreakPoint }: LeftUpCurveProps) => { const { rowSpacing } = useGitContext() const borderStyle = isPlaceholder ? 'dotted' : 'solid' return (
+ {showTopBreakPoint && ( +
+ )} +
{ const { headCommit, isServerSidePaginated, headCommitHash, isIndexVisible } = useGitContext() + const lineColour = isIndex ? indexCommitNodeBorder : columnColour + const isRowCommitIndexNode = commit.hash === 'index' + const border = useMemo(() => { // Border is dotted for the index pseudo-node // and the skeleton placeholder elements. - const borderStyle = (isIndex) || state.isPlaceholderSkeleton ? 'dotted' : 'solid' - - const borderColour = isIndex ? indexCommitNodeBorder : columnColour + const borderStyle = isIndex || state.isPlaceholderSkeleton ? 'dotted' : 'solid' // If we're on the first or last row of a page of data, // use the border-image trick to render a linear-gradient @@ -21,24 +22,36 @@ export const VerticalLine = ({ state, columnIndex, columnColour, commit, indexCo const direction = state.isLastRow ? 'bottom' : 'top' return { borderRight: '2px solid transparent', - borderImage: `linear-gradient(to ${direction}, ${borderColour}, transparent) 1` + borderImage: `linear-gradient(to ${direction}, ${lineColour}, transparent) 1` } } // Otherwise it's just a normal border return { - borderRight: `2px ${borderStyle} ${borderColour}` + borderRight: `2px ${borderStyle} ${lineColour}` } - }, [columnColour, indexCommitNodeBorder, isIndex, state.isFirstRow, state.isLastRow, state.isPlaceholderSkeleton, state.isVerticalIndexLine]) + }, [isIndex, lineColour, state.isFirstRow, state.isLastRow, state.isPlaceholderSkeleton, state.isVerticalIndexLine]) const { style, variant } = useMemo<{ style: CSSProperties, variant: string }>(() => { - const isRowCommitIndexNode = commit.hash === 'index' const rowsCommitIsHead = commit.hash === headCommit?.hash && state.isNode - // If the current column is the index pseudo-node + // If the current column is the index pseudo-node, // then draw a dotted line underneath it that will // eventually meet with the HEAD commit of the current branch. if (isRowCommitIndexNode) { + if (state.isBottomBreakPoint) { + return { + variant: 'bottom-half-dotted-with-break-point', + style: { + top: '50%', + height: '70%', + zIndex: columnIndex + 1, + '--breakpoint-colour': indexCommitNodeBorder, + borderRight: `2px dotted ${indexCommitNodeBorder}` + } + } + } + return { variant: 'bottom-half-dotted', style: { @@ -69,6 +82,19 @@ export const VerticalLine = ({ state, columnIndex, columnColour, commit, indexCo // in the graph, so just draw a solid line above it. const isFirstCommit = state.isNode && commit.parents.length === 0 if (isFirstCommit || state.isColumnBelowEmpty) { + if (state.isTopBreakPoint) { + return { + variant: 'top-half-with-break-point', + style: { + top: 0, + height: '50%', + zIndex: columnIndex + 1, + ...border, + '--breakpoint-colour': lineColour + } + } + } + return { variant: 'top-half', style: { @@ -82,7 +108,7 @@ export const VerticalLine = ({ state, columnIndex, columnColour, commit, indexCo // If this column contains a commit node, and // it is the tip of its branch, then just draw - // a line underneath the node. Same goes for a + // a line underneath the node. The same goes for a // column that an empty one in the row above. const isBranchTip = isServerSidePaginated ? commit.hash === headCommitHash @@ -100,6 +126,32 @@ export const VerticalLine = ({ state, columnIndex, columnColour, commit, indexCo } } + if (state.isBottomBreakPoint) { + return { + variant: 'bottom-break-point', + style: { + top: 0, + height: '50%', + zIndex: columnIndex + 1, + ...border, + '--breakpoint-colour': lineColour + } + } + } + + if (state.isTopBreakPoint) { + return { + variant: 'top-break-point', + style: { + top: 0, + height: '100%', + zIndex: columnIndex + 1, + ...border, + '--breakpoint-colour': lineColour + } + } + } + // If none of the above conditions are met, then // we must be in a column with no commit node, and // so we draw a line the full height of the column. @@ -112,15 +164,20 @@ export const VerticalLine = ({ state, columnIndex, columnColour, commit, indexCo ...border } } - }, [commit.hash, commit.parents.length, commit.isBranchTip, headCommit?.hash, state.isNode, state.isColumnBelowEmpty, state.isColumnAboveEmpty, isIndexVisible, isServerSidePaginated, headCommitHash, columnIndex, border, indexCommitNodeBorder]) + }, [commit.hash, commit.parents.length, commit.isBranchTip, headCommit?.hash, state.isNode, state.isColumnBelowEmpty, state.isColumnAboveEmpty, state.isBottomBreakPoint, state.isTopBreakPoint, isRowCommitIndexNode, isIndexVisible, isServerSidePaginated, headCommitHash, columnIndex, border, indexCommitNodeBorder, lineColour]) - return (
) } \ No newline at end of file diff --git a/packages/library/src/modules/Graph/strategies/Grid/hooks/useColumnData/useColumnData.ts b/packages/library/src/modules/Graph/strategies/Grid/hooks/useColumnData/useColumnData.ts index 562333d9..14334972 100644 --- a/packages/library/src/modules/Graph/strategies/Grid/hooks/useColumnData/useColumnData.ts +++ b/packages/library/src/modules/Graph/strategies/Grid/hooks/useColumnData/useColumnData.ts @@ -32,8 +32,25 @@ export const useColumnData = ({ visibleCommits }: GraphColumnDataProps): GraphCo // in the graph. let virtualColumns = 0 - const drawEdges = (edgeData: [CommitNodeLocation, CommitNodeLocation][]) => { - edgeData.forEach(([[rowStart, colStart], [rowEnd, colEnd]]) => { + const isColumnAboveEmpty = (row: number, column: number) => { + return rowToColumnState.has(row - 1) + ? isColumnEmpty(rowToColumnState.get(row - 1)![column]) + : false + } + + const columnContainsCommitNode = (row: number, column: number) => { + return rowToColumnState.has(row) + ? rowToColumnState.get(row)![column].isNode + : false + } + + const drawEdges = (edgeData: { from: CommitNodeLocation, to: CommitNodeLocation, rerouted: boolean }[]) => { + const columnBreakPointChecks: { location: CommitNodeLocation, check: () => boolean, breakPoint: 'top' | 'bottom' }[] = [] + + edgeData.forEach(({ from, to, rerouted }) => { + const [rowStart, colStart] = from + const [rowEnd, colEnd] = to + // Are we connecting to nodes in the same column? // I.e. drawing a straight merge line between them. if (colStart === colEnd) { @@ -42,7 +59,8 @@ export const useColumnData = ({ visibleCommits }: GraphColumnDataProps): GraphCo columnState[colStart] = { ...columnState[colStart], - isVerticalLine: true + isVerticalLine: true, + isBottomBreakPoint: targetRow === rowEnd - 1 && rerouted } rowToColumnState.set(targetRow, columnState) @@ -84,7 +102,8 @@ export const useColumnData = ({ visibleCommits }: GraphColumnDataProps): GraphCo // Add in the curved line in the target column where the end node is columnState[colEnd] = { ...columnState[colEnd], - isLeftDownCurve: true + isLeftDownCurve: true, + isBottomBreakPoint: rerouted && targetRow === rowEnd && !columnContainsCommitNode(targetRow + 1, colStart) } } } else if (edgeDownToLeft) { @@ -94,7 +113,8 @@ export const useColumnData = ({ visibleCommits }: GraphColumnDataProps): GraphCo if (targetRow !== rowStart && targetRow != rowEnd) { columnState[colStart] = { ...columnState[colStart], - isVerticalLine: true + isVerticalLine: true, + isBottomBreakPoint: rerouted && targetRow === rowEnd - 1 } } @@ -107,6 +127,18 @@ export const useColumnData = ({ visibleCommits }: GraphColumnDataProps): GraphCo isLeftUpCurve: true } + // Since we draw the edges first, we can't check if + // the column above has a node or not. We can't tell if we + // need a top break-point yet, so we'll add it to the list + // to check afterwards. + if (rerouted) { + columnBreakPointChecks.push({ + location: [targetRow, colStart], + breakPoint: 'top', + check: () => !columnContainsCommitNode(targetRow - 1, colStart) + }) + } + // For the remaining columns in this final row, draw // horizontal lines towards the target commit node. for (let columnIndex = colStart - 1; columnIndex >= colEnd; columnIndex--) { @@ -128,7 +160,8 @@ export const useColumnData = ({ visibleCommits }: GraphColumnDataProps): GraphCo // Else we're drawing a vertical line columnState[colEnd] = { ...columnState[colEnd], - isVerticalLine: true + isVerticalLine: true, + isBottomBreakPoint: rerouted && targetRow === rowEnd - 1 } } @@ -136,43 +169,67 @@ export const useColumnData = ({ visibleCommits }: GraphColumnDataProps): GraphCo } } }) + + return { columnBreakPointChecks } } const drawNode = (position: CommitNodeLocation) => { const [row, column] = position const columnState = rowToColumnState.get(row) ?? getEmptyColumnState() - const isColumnAboveEmpty = rowToColumnState.has(row - 1) - ? isColumnEmpty(rowToColumnState.get(row - 1)![column]) - : false - const isColumnBelowEmpty = rowToColumnState.has(row + 1) ? isColumnEmpty(rowToColumnState.get(row + 1)![column]) : false + const isColumnAboveBreakPoint = rowToColumnState.has(row - 1) + ? rowToColumnState.get(row - 1)![column].isBottomBreakPoint + : false + columnState[column] = { ...columnState[column], isNode: true, - isColumnAboveEmpty, - isColumnBelowEmpty + isColumnAboveEmpty: isColumnAboveEmpty(row, column), + isColumnBelowEmpty, + isTopBreakPoint: isColumnAboveBreakPoint } rowToColumnState.set(row, columnState) } if (filter) { + const { columnBreakPointChecks } = drawEdges(filteredData.edges) + filteredData.positions.forEach((position) => { drawNode(position) }) - drawEdges(filteredData.edges.map(({ from, to }) => [from, to] )) + columnBreakPointChecks.forEach(({ check, breakPoint, location }) => { + if (breakPoint === 'top') { + const shouldApplyBreakPoint = check() + const rowIndex = location[0] + + if (shouldApplyBreakPoint && rowToColumnState.has(rowIndex)) { + const columnState = rowToColumnState.get(rowIndex)! + const columnIndex = location[1] + + columnState[columnIndex] = { + ...columnState[columnIndex], + isTopBreakPoint: true + } + } + } + }) } else { // An iterable array of tuples containing commit node row and column indices const commitNodePositions = Array.from(positions.values()) // Iterate over all the edges update the graph column state // for each of the respective branch/merge line segments. - drawEdges(edges.search(0, commits.length).map(([from, to]) => [from, to] )) + drawEdges(edges.search(0, commits.length).map(([from, to]) => ({ + from, + to, + rerouted: false + }))) // Add the commit nodes into their respective rows and columns commitNodePositions.forEach((position) => { From 0c44dc839af117484deccf09daf1bf8e121edc75 Mon Sep 17 00:00:00 2001 From: Thomas Plumpton Date: Tue, 1 Jul 2025 19:38:41 +0100 Subject: [PATCH 05/26] chore(demo): converted radios to selects and fixed typing in demos --- README.md | 3 +- packages/demo/src/GitLog.stories.tsx | 39 +++++++++++------- packages/demo/src/GitLogPaged.stories.tsx | 41 +++++++++++-------- .../src/components/GitLogPagedDemo/types.ts | 2 +- 4 files changed, 51 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index 4603e9fd..054b3c37 100644 --- a/README.md +++ b/README.md @@ -518,14 +518,13 @@ Graphs - Support filtering so that the graph skips nodes - Can we use one compute function now? Have a boolean to optionally do the find closest ancestor - Can we use one set of graphData too and remove the filteredData - - Add styling to the broken edges to indicate its not the true parent - - Make index commit find the nearest HEAD ancestor, or go off the graph otherwise - Does this work with GitLogPaged? - Rework CommitNodeLocation from a tuple into an object - Hover effect on lines to show missing commits? - Break point themes? Slash, dot, zig-zag etc - Make storybook radios into dropdowns - Prop to override graph fallback? No search results yields the placeholder nodes + - Split useColumnData. class maybe to manage rowToColumn map? Canvas2D - Custom prop for BG colour because of how canvas alpha channel works diff --git a/packages/demo/src/GitLog.stories.tsx b/packages/demo/src/GitLog.stories.tsx index cc23787c..891733b6 100644 --- a/packages/demo/src/GitLog.stories.tsx +++ b/packages/demo/src/GitLog.stories.tsx @@ -89,11 +89,14 @@ const meta: Meta = { table: { category: 'Visibility' }, - control: 'radio', - options: { - Default: 'default', - Plain: 'plain' - } + control: { + type: 'select', + labels: { + default: 'Default', + plain: 'Plain' + }, + }, + options: ['default', 'plain'] }, showGitIndex: { name: 'Show Git Index', @@ -107,11 +110,14 @@ const meta: Meta = { table: { category: 'Visibility' }, - control: 'radio', - options: { - 'HTML Grid': 'html-grid', - Canvas2D: 'canvas' - } + control: { + type: 'select', + labels: { + 'html-grid': 'HTML Grid', + canvas: 'Canvas2D' + } + }, + options: ['html-grid', 'canvas'], }, enableSelectedCommitStyling: { name: 'Enable Selection Styling', @@ -194,11 +200,14 @@ const meta: Meta = { table: { category: 'Dimensions' }, - control: 'radio', - options: { - Normal: 'normal', - Flipped: 'flipped' - } + control: { + type: 'select', + labels: { + normal: 'Normal', + flipped: 'Flipped' + } + }, + options: ['normal', 'flipped'], }, onSelectCommit: { name: 'onSelectCommit', diff --git a/packages/demo/src/GitLogPaged.stories.tsx b/packages/demo/src/GitLogPaged.stories.tsx index 2300336f..18df7608 100644 --- a/packages/demo/src/GitLogPaged.stories.tsx +++ b/packages/demo/src/GitLogPaged.stories.tsx @@ -5,7 +5,7 @@ import { GitLogPagedDemo, GitLogPagedStoryProps } from '@components/GitLogPagedD const meta: Meta = { title: 'GitLogPaged', - component: GitLogPaged, + component: GitLogPaged, parameters: { layout: 'fullscreen' }, @@ -66,11 +66,14 @@ const meta: Meta = { table: { category: 'Visibility' }, - control: 'radio', - options: { - Default: 'default', - Plain: 'plain' - } + control: { + type: 'select', + labels: { + default: 'Default', + plain: 'Plain' + }, + }, + options: ['default', 'plain'] }, showGitIndex: { name: 'Show Git Index', @@ -84,11 +87,14 @@ const meta: Meta = { table: { category: 'Visibility' }, - control: 'radio', - options: { - 'HTML Grid': 'html-grid', - Canvas2D: 'canvas' - } + control: { + type: 'select', + labels: { + 'html-grid': 'HTML Grid', + canvas: 'Canvas2D' + } + }, + options: ['html-grid', 'canvas'], }, enableSelectedCommitStyling: { name: 'Enable Selection Styling', @@ -149,11 +155,14 @@ const meta: Meta = { table: { category: 'Dimensions' }, - control: 'radio', - options: { - Normal: 'normal', - Flipped: 'flipped' - } + control: { + type: 'select', + labels: { + normal: 'Normal', + flipped: 'Flipped' + } + }, + options: ['normal', 'flipped'], }, onSelectCommit: { name: 'onSelectCommit', diff --git a/packages/demo/src/components/GitLogPagedDemo/types.ts b/packages/demo/src/components/GitLogPagedDemo/types.ts index bc2c07b2..5fc01bf4 100644 --- a/packages/demo/src/components/GitLogPagedDemo/types.ts +++ b/packages/demo/src/components/GitLogPagedDemo/types.ts @@ -1,6 +1,6 @@ import { Canvas2DGraphProps, GitLogPagedProps, HTMLGridGraphProps, TableProps } from '@tomplum/react-git-log' -export interface GitLogPagedStoryProps extends GitLogPagedProps, HTMLGridGraphProps, Canvas2DGraphProps, TableProps { +export interface GitLogPagedStoryProps extends GitLogPagedProps, HTMLGridGraphProps, Canvas2DGraphProps, TableProps { showTable: boolean showCommitNodeHashes: boolean renderStrategy: 'html-grid' | 'canvas' From db1cb990730cd35469435c6bc5b1640fdbe34ecb Mon Sep 17 00:00:00 2001 From: Thomas Plumpton Date: Tue, 1 Jul 2025 20:19:20 +0100 Subject: [PATCH 06/26] feat(graph): added breakpoint theme prop and extracted BreakPoint component --- README.md | 1 - .../src/__snapshots__/GitLog.spec.tsx.snap | 396 +++++++++++++----- packages/library/src/hooks/useTheme/types.ts | 7 + .../src/modules/Graph/context/GraphContext.ts | 1 + .../src/modules/Graph/context/types.ts | 8 +- .../src/modules/Graph/core/GraphCore.tsx | 4 +- .../BreakPoint/BreakPoint.module.scss | 18 + .../Grid/components/BreakPoint/BreakPoint.tsx | 56 +++ .../Grid/components/BreakPoint/index.ts | 2 + .../Grid/components/BreakPoint/types.ts | 8 + .../LeftDownCurve/LeftDownCurve.module.scss | 15 - .../LeftDownCurve/LeftDownCurve.tsx | 9 +- .../LeftUpCurve/LeftUpCurve.module.scss | 15 - .../components/LeftUpCurve/LeftUpCurve.tsx | 9 +- .../VerticalLine/VerticalLine.module.scss | 47 --- .../components/VerticalLine/VerticalLine.tsx | 48 ++- packages/library/src/modules/Graph/types.ts | 9 +- 17 files changed, 451 insertions(+), 202 deletions(-) create mode 100644 packages/library/src/modules/Graph/strategies/Grid/components/BreakPoint/BreakPoint.module.scss create mode 100644 packages/library/src/modules/Graph/strategies/Grid/components/BreakPoint/BreakPoint.tsx create mode 100644 packages/library/src/modules/Graph/strategies/Grid/components/BreakPoint/index.ts create mode 100644 packages/library/src/modules/Graph/strategies/Grid/components/BreakPoint/types.ts diff --git a/README.md b/README.md index 054b3c37..116e96d9 100644 --- a/README.md +++ b/README.md @@ -522,7 +522,6 @@ Graphs - Rework CommitNodeLocation from a tuple into an object - Hover effect on lines to show missing commits? - Break point themes? Slash, dot, zig-zag etc - - Make storybook radios into dropdowns - Prop to override graph fallback? No search results yields the placeholder nodes - Split useColumnData. class maybe to manage rowToColumn map? diff --git a/packages/library/src/__snapshots__/GitLog.spec.tsx.snap b/packages/library/src/__snapshots__/GitLog.spec.tsx.snap index 9636520a..4b2e0f02 100644 --- a/packages/library/src/__snapshots__/GitLog.spec.tsx.snap +++ b/packages/library/src/__snapshots__/GitLog.spec.tsx.snap @@ -144998,11 +144998,17 @@ exports[`GitLog > HTML Grid Graph > should render correctly and match the snapsh style="width: 20px; height: 20px; border: 2px dotted rgb(255, 128, 128); background-color: rgb(255, 242, 242);" />
+ style="top: 50%; height: 70%; z-index: 1; border-right: 2px dotted rgb(255, 128, 128);" + > +
+
@@ -145286,8 +145286,8 @@ exports[`GitLog > HTML Grid Graph > should render correctly and match the snapsh style="top: 0px; height: 50%; z-index: 2; border-right: 2px solid rgb(255, 219, 0);" >
@@ -145306,8 +145306,8 @@ exports[`GitLog > HTML Grid Graph > should render correctly and match the snapsh style="top: 0px; height: 50%; z-index: 3; border-right: 2px solid rgb(73, 255, 0);" >
@@ -145340,8 +145340,8 @@ exports[`GitLog > HTML Grid Graph > should render correctly and match the snapsh style="top: 0px; height: 50%; z-index: 6; border-right: 2px solid rgb(73, 0, 255);" >
@@ -145397,8 +145397,8 @@ exports[`GitLog > HTML Grid Graph > should render correctly and match the snapsh style="top: 0px; height: 50%; z-index: 2; border-right: 2px solid rgb(255, 219, 0);" >
@@ -145428,8 +145428,8 @@ exports[`GitLog > HTML Grid Graph > should render correctly and match the snapsh id="left-up-curve" >
HTML Grid Graph > should render correctly and match the snapsh id="left-up-curve" >
HTML Grid Graph > should render correctly and match the snapsh style="top: 0px; height: 50%; z-index: 7; border-right: 2px solid rgb(255, 0, 219);" >
@@ -146011,8 +146011,8 @@ exports[`GitLog > HTML Grid Graph > should render correctly and match the snapsh style="top: 0px; height: 50%; z-index: 2; border-right: 2px solid rgb(255, 219, 0);" >
@@ -146031,8 +146031,8 @@ exports[`GitLog > HTML Grid Graph > should render correctly and match the snapsh style="top: 0px; height: 50%; z-index: 3; border-right: 2px solid rgb(73, 255, 0);" >
@@ -146065,8 +146065,8 @@ exports[`GitLog > HTML Grid Graph > should render correctly and match the snapsh style="top: 0px; height: 50%; z-index: 6; border-right: 2px solid rgb(73, 0, 255);" >
@@ -146093,8 +146093,8 @@ exports[`GitLog > HTML Grid Graph > should render correctly and match the snapsh style="top: 0px; height: 100%; z-index: 7; border-right: 2px solid rgb(255, 0, 219);" >
@@ -146128,8 +146128,8 @@ exports[`GitLog > HTML Grid Graph > should render correctly and match the snapsh style="top: 0px; height: 50%; z-index: 2; border-right: 2px solid rgb(255, 219, 0);" >
@@ -146194,8 +146194,8 @@ exports[`GitLog > HTML Grid Graph > should render correctly and match the snapsh id="left-up-curve" >
HTML Grid Graph > should render correctly and match the snapsh id="left-up-curve" >
HTML Grid Graph > should render correctly and match the snapsh style="top: 0px; height: 50%; z-index: 7; border-right: 2px solid rgb(255, 0, 219);" >
@@ -146422,8 +146422,8 @@ exports[`GitLog > HTML Grid Graph > should render correctly and match the snapsh id="left-up-curve" >
HTML Grid Graph > should render correctly and match the snapsh style="top: 0px; height: 50%; z-index: 3; border-right: 2px solid rgb(73, 255, 0);" >
@@ -146577,13 +146577,13 @@ exports[`GitLog > HTML Grid Graph > should render correctly and match the snapsh style="top: 0px; height: 50%; z-index: 3; border-right: 2px solid rgb(73, 255, 0);" >
@@ -146659,8 +146659,8 @@ exports[`GitLog > HTML Grid Graph > should render correctly and match the snapsh style="top: 0px; height: 50%; z-index: 3; border-right: 2px solid rgb(73, 255, 0);" >
@@ -146827,8 +146827,8 @@ exports[`GitLog > HTML Grid Graph > should render correctly and match the snapsh style="top: 0px; height: 50%; z-index: 2; border-right: 2px solid rgb(255, 219, 0);" >
@@ -146919,8 +146919,8 @@ exports[`GitLog > HTML Grid Graph > should render correctly and match the snapsh style="top: 0px; height: 100%; z-index: 2; border-right: 2px solid rgb(255, 219, 0);" >
@@ -147031,8 +147031,8 @@ exports[`GitLog > HTML Grid Graph > should render correctly and match the snapsh style="top: 0px; height: 50%; z-index: 4; border-right: 2px solid rgb(0, 255, 146);" >
@@ -147058,8 +147058,8 @@ exports[`GitLog > HTML Grid Graph > should render correctly and match the snapsh style="top: 0px; height: 50%; z-index: 6; border-right: 2px solid rgb(73, 0, 255);" >
@@ -147092,8 +147092,8 @@ exports[`GitLog > HTML Grid Graph > should render correctly and match the snapsh style="top: 0px; height: 50%; z-index: 2; border-right: 2px solid rgb(255, 219, 0);" >
@@ -147134,8 +147134,8 @@ exports[`GitLog > HTML Grid Graph > should render correctly and match the snapsh style="top: 0px; height: 100%; z-index: 4; border-right: 2px solid rgb(0, 255, 146);" >
@@ -147173,8 +147173,8 @@ exports[`GitLog > HTML Grid Graph > should render correctly and match the snapsh id="left-up-curve" >
HTML Grid Graph > should render correctly and match the snapsh style="top: 0px; height: 50%; z-index: 2; border-right: 2px solid rgb(255, 219, 0);" >
@@ -147390,8 +147390,8 @@ exports[`GitLog > HTML Grid Graph > should render correctly and match the snapsh style="top: 0px; height: 50%; z-index: 4; border-right: 2px solid rgb(0, 255, 146);" >
@@ -147446,8 +147446,8 @@ exports[`GitLog > HTML Grid Graph > should render correctly and match the snapsh style="top: 50%; height: 50%; z-index: 2; border-right: 2px solid rgb(255, 219, 0);" >
@@ -147526,8 +147526,8 @@ exports[`GitLog > HTML Grid Graph > should render correctly and match the snapsh id="left-up-curve" >
HTML Grid Graph > should render correctly and match the snapsh style="top: 0px; height: 50%; z-index: 2; border-right: 2px solid rgb(255, 219, 0);" >
@@ -147736,8 +147736,8 @@ exports[`GitLog > HTML Grid Graph > should render correctly and match the snapsh style="top: 0px; height: 50%; z-index: 3; border-right: 2px solid rgb(73, 255, 0);" >
@@ -147806,13 +147806,13 @@ exports[`GitLog > HTML Grid Graph > should render correctly and match the snapsh style="top: 0px; height: 50%; z-index: 3; border-right: 2px solid rgb(73, 255, 0);" >
@@ -147881,13 +147881,13 @@ exports[`GitLog > HTML Grid Graph > should render correctly and match the snapsh style="top: 0px; height: 50%; z-index: 3; border-right: 2px solid rgb(73, 255, 0);" >
@@ -147956,8 +147956,8 @@ exports[`GitLog > HTML Grid Graph > should render correctly and match the snapsh style="top: 0px; height: 100%; z-index: 3; border-right: 2px solid rgb(73, 255, 0);" >
diff --git a/packages/library/src/hooks/useTheme/types.ts b/packages/library/src/hooks/useTheme/types.ts index ed261bd1..032fea5f 100644 --- a/packages/library/src/hooks/useTheme/types.ts +++ b/packages/library/src/hooks/useTheme/types.ts @@ -124,7 +124,7 @@ export type NodeTheme = 'default' | 'plain' * the node edges in the graph if a filter is active * and is causing breaks in the graph) are rendered. */ -export type BreakPointTheme = 'slash' | 'dot' | 'zig-zag' +export type BreakPointTheme = 'slash' | 'dot' | 'zig-zag' | 'line' | 'double-line' export type ThemeColours = 'rainbow-dark' | diff --git a/packages/library/src/modules/Graph/strategies/Grid/components/BreakPoint/BreakPoint.module.scss b/packages/library/src/modules/Graph/strategies/Grid/components/BreakPoint/BreakPoint.module.scss index 3925baeb..7c5460ce 100644 --- a/packages/library/src/modules/Graph/strategies/Grid/components/BreakPoint/BreakPoint.module.scss +++ b/packages/library/src/modules/Graph/strategies/Grid/components/BreakPoint/BreakPoint.module.scss @@ -93,4 +93,61 @@ left: 50%; } } +} + +.Line { + position: absolute; + width: 12px; + height: 2px; + border-radius: 10px; + background: var(--breakpoint-colour); + left: calc(50% - 4px); + + &--top { + top: 0; + } + + &--bottom { + top: 100%; + } +} + +.DoubleLine { + position: absolute; + + &::before { + position: absolute; + content: ''; + width: 14px; + height: 2px; + border-radius: 10px; + background: var(--breakpoint-colour); + left: calc(50% - 4px); + } + + &::after { + position: absolute; + content: ''; + width: 8px; + height: 1px; + border-radius: 10px; + background: var(--breakpoint-colour); + left: calc(50% - 1px); + } + + &--top { + top: 0; + + &::after { + top: -4px; + } + } + + &--bottom { + top: 100%; + + &::after { + top: 4px; + } + } } \ No newline at end of file diff --git a/packages/library/src/modules/Graph/strategies/Grid/components/BreakPoint/BreakPoint.spec.tsx b/packages/library/src/modules/Graph/strategies/Grid/components/BreakPoint/BreakPoint.spec.tsx index 31df6b58..e9bd3fd3 100644 --- a/packages/library/src/modules/Graph/strategies/Grid/components/BreakPoint/BreakPoint.spec.tsx +++ b/packages/library/src/modules/Graph/strategies/Grid/components/BreakPoint/BreakPoint.spec.tsx @@ -30,6 +30,22 @@ describe('Filtered Graph Break Point', () => { { theme: 'zig-zag', position: 'bottom' + }, + { + theme: 'line', + position: 'top' + }, + { + theme: 'line', + position: 'bottom' + }, + { + theme: 'double-line', + position: 'top' + }, + { + theme: 'double-line', + position: 'bottom' } ] diff --git a/packages/library/src/modules/Graph/strategies/Grid/components/BreakPoint/BreakPoint.tsx b/packages/library/src/modules/Graph/strategies/Grid/components/BreakPoint/BreakPoint.tsx index 53299253..798c0edf 100644 --- a/packages/library/src/modules/Graph/strategies/Grid/components/BreakPoint/BreakPoint.tsx +++ b/packages/library/src/modules/Graph/strategies/Grid/components/BreakPoint/BreakPoint.tsx @@ -51,5 +51,31 @@ export const BreakPoint = ({ position, className, color, style }: BreakPointProp /> ) } + case 'line': { + return ( +
+ ) + } + case 'double-line': { + return ( +
+ ) + } } } \ No newline at end of file diff --git a/packages/library/src/modules/Graph/strategies/Grid/components/BreakPoint/__snapshots__/BreakPoint.spec.tsx.snap b/packages/library/src/modules/Graph/strategies/Grid/components/BreakPoint/__snapshots__/BreakPoint.spec.tsx.snap index 4c8f05bd..e43b9c66 100644 --- a/packages/library/src/modules/Graph/strategies/Grid/components/BreakPoint/__snapshots__/BreakPoint.spec.tsx.snap +++ b/packages/library/src/modules/Graph/strategies/Grid/components/BreakPoint/__snapshots__/BreakPoint.spec.tsx.snap @@ -10,6 +10,26 @@ exports[`Filtered Graph Break Point > should render a [bottom] break point corre `; +exports[`Filtered Graph Break Point > should render a [bottom] break point correctly when the theme is [double-line] 1`] = ` + +
+ +`; + +exports[`Filtered Graph Break Point > should render a [bottom] break point correctly when the theme is [line] 1`] = ` + +
+ +`; + exports[`Filtered Graph Break Point > should render a [bottom] break point correctly when the theme is [slash] 1`] = `
should render a [top] break point correctl `; +exports[`Filtered Graph Break Point > should render a [top] break point correctly when the theme is [double-line] 1`] = ` + +
+ +`; + +exports[`Filtered Graph Break Point > should render a [top] break point correctly when the theme is [line] 1`] = ` + +
+ +`; + exports[`Filtered Graph Break Point > should render a [top] break point correctly when the theme is [slash] 1`] = `
)} diff --git a/packages/library/src/modules/Graph/strategies/Grid/components/LeftUpCurve/LeftUpCurve.tsx b/packages/library/src/modules/Graph/strategies/Grid/components/LeftUpCurve/LeftUpCurve.tsx index ec467737..10521853 100644 --- a/packages/library/src/modules/Graph/strategies/Grid/components/LeftUpCurve/LeftUpCurve.tsx +++ b/packages/library/src/modules/Graph/strategies/Grid/components/LeftUpCurve/LeftUpCurve.tsx @@ -19,7 +19,9 @@ export const LeftUpCurve = ({ color, isPlaceholder, showTopBreakPoint }: LeftUpC style={{ slash: { left: '50%' }, dot: { left: '50%' }, + line: { left: 'calc(50% - 6px)' }, 'zig-zag': { left: 'calc(50% - 2px)' }, + 'double-line': { left: 'calc(50% - 3px)' }, }} /> )} From 7bf32d3769dcf6f848696775447bf65b159b02c7 Mon Sep 17 00:00:00 2001 From: Thomas Plumpton Date: Wed, 2 Jul 2025 22:01:12 +0100 Subject: [PATCH 12/26] chore(log): combined redundant graph computation functions - Restores original graphData object which is used for both filtered and non-filtered graphs - Removes the dependency on node-interval-tree - Fixes minor styling issues with vertical edge gradients - Fixed minor storybook demo controls and theming --- README.md | 1 + package-lock.json | 15 - packages/demo/src/GitLog.stories.tsx | 5 + packages/demo/src/GitLogPaged.stories.tsx | 12 + .../GitLogPagedDemo/GitLogPagedDemo.tsx | 14 +- packages/library/package.json | 1 - .../src/__snapshots__/GitLog.spec.tsx.snap | 8 +- packages/library/src/_test/stubs.ts | 5 +- .../src/components/GitLogCore/GitLogCore.tsx | 40 +-- .../src/context/GitContext/GitContext.ts | 9 +- packages/library/src/data/ActiveBranches.ts | 2 +- ...lteredNodeColumns.ts => buildGraphData.ts} | 22 +- packages/library/src/data/buildNodeGraph.ts | 38 --- .../library/src/data/computeNodeColumns.ts | 122 ------- packages/library/src/data/index.ts | 2 +- packages/library/src/data/types.ts | 78 ++--- .../Graph/strategies/Canvas/CanvasRenderer.ts | 11 +- .../components/VerticalLine/VerticalLine.tsx | 8 +- .../Grid/hooks/useColumnData/useColumnData.ts | 311 ++++++++---------- packages/library/src/modules/Table/Table.tsx | 12 +- packages/library/src/types.ts | 7 +- 21 files changed, 262 insertions(+), 461 deletions(-) rename packages/library/src/data/{computeFilteredNodeColumns.ts => buildGraphData.ts} (86%) delete mode 100644 packages/library/src/data/buildNodeGraph.ts delete mode 100644 packages/library/src/data/computeNodeColumns.ts diff --git a/README.md b/README.md index 1c30524a..4b4cea1b 100644 --- a/README.md +++ b/README.md @@ -534,5 +534,6 @@ Canvas2D # Known Bugs - The `GraphCanvas2D` component has the preview/select background cut off by canvases left-edge. - The `GraphCanvas2D` component does not set the correct selected node BG colour, it's slightly off from the table row. +- The `GraphCanvas2D` component assumes dark theme is enabled and uses a dark background colour to emulate transparency in its gradients. - The `GraphHTMLGrid` component renders the node-edge gradient to last node, but should be solid. - The `GraphHTMLGrid` component is missing node edges from some commit nodes that are present in the canvas variant. \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 41b25d31..e5abafa2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11560,7 +11560,6 @@ "classnames": "^2.5.1", "dayjs": "^1.11.13", "fastpriorityqueue": "^0.7.5", - "node-interval-tree": "^2.1.2", "react": ">=19.0.0", "react-dom": ">=19.0.0", "react-tiny-popover": "^8.1.6" @@ -11612,20 +11611,6 @@ "packages/library/node_modules/fastpriorityqueue": { "version": "0.7.5", "license": "Apache-2.0" - }, - "packages/library/node_modules/node-interval-tree": { - "version": "2.1.2", - "license": "MIT", - "dependencies": { - "shallowequal": "^1.1.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "packages/library/node_modules/shallowequal": { - "version": "1.1.0", - "license": "MIT" } } } diff --git a/packages/demo/src/GitLog.stories.tsx b/packages/demo/src/GitLog.stories.tsx index 54561a78..f0bd75ea 100644 --- a/packages/demo/src/GitLog.stories.tsx +++ b/packages/demo/src/GitLog.stories.tsx @@ -299,6 +299,11 @@ const meta: Meta = { table: { disable: true } + }, + filter: { + table: { + disable: true + } } } } satisfies Meta diff --git a/packages/demo/src/GitLogPaged.stories.tsx b/packages/demo/src/GitLogPaged.stories.tsx index 5621edb8..c4c4867c 100644 --- a/packages/demo/src/GitLogPaged.stories.tsx +++ b/packages/demo/src/GitLogPaged.stories.tsx @@ -14,6 +14,7 @@ const meta: Meta = { showTable: true, branchName: 'release', showCommitNodeHashes: false, + showCommitNodeTooltips: false, showHeaders: true, enableResize: false, enablePreviewedCommitStyling: true, @@ -49,6 +50,12 @@ const meta: Meta = { category: 'Visibility' } }, + showCommitNodeTooltips: { + name: 'Show Commit Tooltips', + table: { + category: 'Visibility' + } + }, showTable: { name: 'Show Table', table: { @@ -225,6 +232,11 @@ const meta: Meta = { table: { disable: true } + }, + filter: { + table: { + disable: true + } } } } satisfies Meta diff --git a/packages/demo/src/components/GitLogPagedDemo/GitLogPagedDemo.tsx b/packages/demo/src/components/GitLogPagedDemo/GitLogPagedDemo.tsx index 96f8141d..179c91fc 100644 --- a/packages/demo/src/components/GitLogPagedDemo/GitLogPagedDemo.tsx +++ b/packages/demo/src/components/GitLogPagedDemo/GitLogPagedDemo.tsx @@ -6,13 +6,13 @@ import { Pagination } from '@components/Pagination' import { Loading } from '@components/Loading' import { GitLog, GitLogPaged } from '@tomplum/react-git-log' import { GitLogPagedStoryProps } from './types' +import { useDemoContext } from '@context' export const GitLogPagedDemo = (args: GitLogPagedStoryProps) => { const [pageNumber, setPageNumber] = useState(1) const [pageSize, setPageSize] = useState(20) const { - theme, loading, colours, entries, @@ -21,20 +21,19 @@ export const GitLogPagedDemo = (args: GitLogPagedStoryProps) => { repository, headCommitHash, backgroundColour, - handleChangeTheme, handleChangeColors, handleChangeRepository } = useStoryState({ isServerSidePaginated: true }) + const { theme } = useDemoContext() + return (
@@ -52,7 +51,7 @@ export const GitLogPagedDemo = (args: GitLogPagedStoryProps) => { {loading && (
- +
)} @@ -70,11 +69,6 @@ export const GitLogPagedDemo = (args: GitLogPagedStoryProps) => { }, containerClass: styles.gitLogContainer }} - indexStatus={{ - added: args.indexStatusFilesAdded, - modified: args.indexStatusFilesModified, - deleted: args.indexStatusFilesDeleted - }} urls={buildUrls} > {args.renderStrategy === 'html-grid' && ( diff --git a/packages/library/package.json b/packages/library/package.json index 986858ef..38089df7 100644 --- a/packages/library/package.json +++ b/packages/library/package.json @@ -43,7 +43,6 @@ "classnames": "^2.5.1", "dayjs": "^1.11.13", "fastpriorityqueue": "^0.7.5", - "node-interval-tree": "^2.1.2", "react": ">=19.0.0", "react-dom": ">=19.0.0", "react-tiny-popover": "^8.1.6" diff --git a/packages/library/src/__snapshots__/GitLog.spec.tsx.snap b/packages/library/src/__snapshots__/GitLog.spec.tsx.snap index f54c9f40..d49aeb9c 100644 --- a/packages/library/src/__snapshots__/GitLog.spec.tsx.snap +++ b/packages/library/src/__snapshots__/GitLog.spec.tsx.snap @@ -22830,7 +22830,7 @@ exports[`GitLog > HTML Grid Graph > should render correctly and match the snapsh class="line vertical" data-testid="vertical-line-bottom-half" id="vertical-line-bottom-half" - style="top: 50%; height: 50%; z-index: 2; border-right: 2px solid transparent; border-image: linear-gradient(to top, rgb(255, 219, 0), transparent) 1;" + style="top: 50%; height: 50%; z-index: 2; border-right: 2px solid rgb(255, 219, 0);" />