mirror of
https://github.com/scm-manager/scm-manager.git
synced 2026-06-17 18:42:04 +02:00
Improve collapse api on diff to enable external state management (#1736)
Create new diff collapse api which allows to handle the collapse state externally.
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -47,6 +47,10 @@ const Container = styled.div`
|
||||
padding: 2rem 6rem;
|
||||
`;
|
||||
|
||||
type ExternalDiffState = {
|
||||
[key: string]: boolean;
|
||||
};
|
||||
|
||||
const RoutingDecorator = (story: () => ReactNode) => <MemoryRouter initialEntries={["/"]}>{story()}</MemoryRouter>;
|
||||
|
||||
const fileControlFactory: (changeset: Changeset) => FileControlFactory = (changeset) => (file) => {
|
||||
@@ -183,4 +187,17 @@ storiesOf("Diff", module)
|
||||
};
|
||||
|
||||
return <ChangingContentDiff />;
|
||||
})
|
||||
.add("External state management", () => {
|
||||
const [externalState, setExternalState] = useState<ExternalDiffState>({});
|
||||
|
||||
const isCollapsed = (file: FileDiff) => {
|
||||
return externalState[file.newPath] || false;
|
||||
};
|
||||
|
||||
const onCollapseStateChange = (file: FileDiff) => {
|
||||
setExternalState((current) => ({ ...current, [file.newPath]: !current[file.newPath] }));
|
||||
};
|
||||
|
||||
return <Diff diff={diffFiles} isCollapsed={isCollapsed} onCollapseStateChange={onCollapseStateChange} />;
|
||||
});
|
||||
|
||||
@@ -97,7 +97,7 @@ const MarginlessModalContent = styled.div`
|
||||
class DiffFile extends React.Component<Props, State> {
|
||||
static defaultProps: Partial<Props> = {
|
||||
defaultCollapse: false,
|
||||
markConflicts: true
|
||||
markConflicts: true,
|
||||
};
|
||||
|
||||
constructor(props: Props) {
|
||||
@@ -106,14 +106,14 @@ class DiffFile extends React.Component<Props, State> {
|
||||
collapsed: this.defaultCollapse(),
|
||||
sideBySide: props.sideBySide,
|
||||
diffExpander: new DiffExpander(props.file),
|
||||
file: props.file
|
||||
file: props.file,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: Readonly<Props>) {
|
||||
if (this.props.defaultCollapse !== prevProps.defaultCollapse) {
|
||||
if (!this.props.isCollapsed && this.props.defaultCollapse !== prevProps.defaultCollapse) {
|
||||
this.setState({
|
||||
collapsed: this.defaultCollapse()
|
||||
collapsed: this.defaultCollapse(),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -130,27 +130,37 @@ class DiffFile extends React.Component<Props, State> {
|
||||
};
|
||||
|
||||
toggleCollapse = () => {
|
||||
const { onCollapseStateChange } = this.props;
|
||||
const { file } = this.state;
|
||||
if (this.hasContent(file)) {
|
||||
this.setState(state => ({
|
||||
collapsed: !state.collapsed
|
||||
}));
|
||||
if (onCollapseStateChange) {
|
||||
onCollapseStateChange(file);
|
||||
} else {
|
||||
this.setState((state) => ({
|
||||
collapsed: !state.collapsed,
|
||||
}));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
toggleSideBySide = (callback: () => void) => {
|
||||
this.setState(
|
||||
state => ({
|
||||
sideBySide: !state.sideBySide
|
||||
(state) => ({
|
||||
sideBySide: !state.sideBySide,
|
||||
}),
|
||||
() => callback()
|
||||
);
|
||||
};
|
||||
|
||||
setCollapse = (collapsed: boolean) => {
|
||||
this.setState({
|
||||
collapsed
|
||||
});
|
||||
const { onCollapseStateChange } = this.props;
|
||||
if (onCollapseStateChange) {
|
||||
onCollapseStateChange(this.state.file, collapsed);
|
||||
} else {
|
||||
this.setState({
|
||||
collapsed,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
createHunkHeader = (expandableHunk: ExpandableHunk) => {
|
||||
@@ -242,19 +252,13 @@ class DiffFile extends React.Component<Props, State> {
|
||||
|
||||
expandHead = (expandableHunk: ExpandableHunk, count: number) => {
|
||||
return () => {
|
||||
return expandableHunk
|
||||
.expandHead(count)
|
||||
.then(this.diffExpanded)
|
||||
.catch(this.diffExpansionFailed);
|
||||
return expandableHunk.expandHead(count).then(this.diffExpanded).catch(this.diffExpansionFailed);
|
||||
};
|
||||
};
|
||||
|
||||
expandBottom = (expandableHunk: ExpandableHunk, count: number) => {
|
||||
return () => {
|
||||
return expandableHunk
|
||||
.expandBottom(count)
|
||||
.then(this.diffExpanded)
|
||||
.catch(this.diffExpansionFailed);
|
||||
return expandableHunk.expandBottom(count).then(this.diffExpanded).catch(this.diffExpansionFailed);
|
||||
};
|
||||
};
|
||||
|
||||
@@ -272,7 +276,7 @@ class DiffFile extends React.Component<Props, State> {
|
||||
if (annotationFactory) {
|
||||
return annotationFactory({
|
||||
hunk,
|
||||
file
|
||||
file,
|
||||
});
|
||||
} else {
|
||||
return EMPTY_ANNOTATION_FACTORY;
|
||||
@@ -286,7 +290,7 @@ class DiffFile extends React.Component<Props, State> {
|
||||
changeId: getChangeKey(change),
|
||||
change,
|
||||
hunk,
|
||||
file
|
||||
file,
|
||||
};
|
||||
if (onClick) {
|
||||
onClick(context);
|
||||
@@ -299,7 +303,7 @@ class DiffFile extends React.Component<Props, State> {
|
||||
return {
|
||||
onClick: (event: ChangeEvent) => {
|
||||
this.handleClickEvent(event.change, hunk);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -409,12 +413,21 @@ class DiffFile extends React.Component<Props, State> {
|
||||
);
|
||||
};
|
||||
|
||||
isCollapsed = () => {
|
||||
const { file, isCollapsed } = this.props;
|
||||
if (isCollapsed) {
|
||||
return isCollapsed(file);
|
||||
}
|
||||
return this.state.collapsed;
|
||||
};
|
||||
|
||||
hasContent = (file: FileDiff) => file && !file.isBinary && file.hunks && file.hunks.length > 0;
|
||||
|
||||
render() {
|
||||
const { fileControlFactory, fileAnnotationFactory, t } = this.props;
|
||||
const { file, collapsed, sideBySide, diffExpander, expansionError } = this.state;
|
||||
const { file, sideBySide, diffExpander, expansionError } = this.state;
|
||||
const viewType = sideBySide ? "split" : "unified";
|
||||
const collapsed = this.isCollapsed();
|
||||
|
||||
const fileAnnotations = fileAnnotationFactory ? fileAnnotationFactory(file) : null;
|
||||
const innerContent = (
|
||||
@@ -437,9 +450,10 @@ class DiffFile extends React.Component<Props, State> {
|
||||
}
|
||||
const collapseIcon = this.hasContent(file) ? <Icon name={icon} color="inherit" /> : null;
|
||||
const fileControls = fileControlFactory ? fileControlFactory(file, this.setCollapse) : null;
|
||||
const modalTitle = file.type === "delete" ? file.oldPath : file.newPath;
|
||||
const openInFullscreen = file?.hunks?.length ? (
|
||||
<OpenInFullscreenButton
|
||||
modalTitle={file.type === "delete" ? file.oldPath : file.newPath}
|
||||
modalTitle={modalTitle}
|
||||
modalBody={<MarginlessModalContent>{innerContent}</MarginlessModalContent>}
|
||||
/>
|
||||
) : null;
|
||||
|
||||
@@ -40,9 +40,7 @@ export type AnnotationFactoryContext = BaseContext;
|
||||
export type FileAnnotationFactory = (file: File) => ReactNode[];
|
||||
|
||||
// key = change id, value = react component
|
||||
export type AnnotationFactory = (
|
||||
context: AnnotationFactoryContext
|
||||
) => {
|
||||
export type AnnotationFactory = (context: AnnotationFactoryContext) => {
|
||||
[key: string]: any;
|
||||
};
|
||||
|
||||
@@ -63,5 +61,7 @@ export type DiffObjectProps = {
|
||||
annotationFactory?: AnnotationFactory;
|
||||
markConflicts?: boolean;
|
||||
defaultCollapse?: DefaultCollapsed;
|
||||
isCollapsed?: (file: File) => boolean;
|
||||
onCollapseStateChange?: (file: File, newState?: boolean) => void;
|
||||
hunkClass?: (hunk: Hunk) => string;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user