@scm-manager/ui-components 3.7.4 → 3.7.5-20250212-173204
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +14 -13
- package/src/Duration.stories.tsx +18 -0
- package/src/__snapshots__/storyshots.test.ts.snap +53 -4
- package/src/buttons/Button.tsx +1 -1
- package/src/forms/InputField.tsx +4 -0
- package/src/layout/SecondaryNavigationColumn.tsx +10 -1
- package/src/navigation/ExternalNavLink.tsx +2 -4
- package/src/navigation/NavLink.tsx +5 -11
- package/src/navigation/SecondaryNavigation.stories.tsx +7 -4
- package/src/navigation/SecondaryNavigation.tsx +3 -12
- package/src/navigation/SecondaryNavigationContext.tsx +37 -0
- package/src/repos/LoadingDiff.tsx +4 -3
- package/src/repos/changesets/ChangesetButtonGroup.test.tsx +72 -0
- package/src/repos/changesets/ChangesetButtonGroup.tsx +8 -5
- package/src/repos/changesets/ChangesetList.tsx +6 -3
- package/src/repos/changesets/ChangesetRow.tsx +5 -3
- package/src/repos/changesets/changesets.test.ts +62 -2
- package/src/repos/changesets/changesets.ts +12 -2
- package/src/repos/diff/DiffFileTree.tsx +3 -3
- package/src/repos/diff/styledElements.tsx +16 -5
- package/src/useSecondaryNavigation.tsx +11 -17
- package/src/navigation/SecondaryNavigationContext.ts +0 -19
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scm-manager/ui-components",
|
|
3
|
-
"version": "3.7.
|
|
3
|
+
"version": "3.7.5-20250212-173204",
|
|
4
4
|
"description": "UI Components for SCM-Manager and its plugins",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"files": [
|
|
@@ -32,14 +32,14 @@
|
|
|
32
32
|
"react-query": "^3.39.2"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
|
-
"@scm-manager/ui-tests": "3.7.
|
|
36
|
-
"@scm-manager/ui-types": "3.7.
|
|
35
|
+
"@scm-manager/ui-tests": "3.7.5-20250212-173204",
|
|
36
|
+
"@scm-manager/ui-types": "3.7.5-20250212-173204",
|
|
37
37
|
"@types/fetch-mock": "^7.3.1",
|
|
38
38
|
"@types/react-select": "^2.0.19",
|
|
39
39
|
"@types/unist": "^2.0.3",
|
|
40
40
|
"gitdiff-parser": "^0.2.2",
|
|
41
41
|
"i18next-fetch-backend": "4",
|
|
42
|
-
"webpack": "^5.
|
|
42
|
+
"webpack": "^5.76.0",
|
|
43
43
|
"@storybook/addon-actions": "^6.5.10",
|
|
44
44
|
"@storybook/addon-essentials": "^6.5.10",
|
|
45
45
|
"@storybook/addon-interactions": "^6.5.10",
|
|
@@ -47,6 +47,7 @@
|
|
|
47
47
|
"@storybook/builder-webpack5": "^6.5.10",
|
|
48
48
|
"@storybook/manager-webpack5": "^6.5.10",
|
|
49
49
|
"@storybook/react": "^6.5.10",
|
|
50
|
+
"@testing-library/react": "^12.1.5",
|
|
50
51
|
"storybook-addon-i18next": "^1.3.0",
|
|
51
52
|
"storybook-addon-themes": "^6.1.0",
|
|
52
53
|
"@types/classnames": "^2.3.1",
|
|
@@ -67,17 +68,17 @@
|
|
|
67
68
|
"@scm-manager/jest-preset": "^2.14.1",
|
|
68
69
|
"@scm-manager/prettier-config": "^2.12.0",
|
|
69
70
|
"@scm-manager/tsconfig": "^2.13.0",
|
|
70
|
-
"@scm-manager/ui-syntaxhighlighting": "3.7.
|
|
71
|
-
"@scm-manager/ui-shortcuts": "3.7.
|
|
72
|
-
"@scm-manager/ui-text": "3.7.
|
|
71
|
+
"@scm-manager/ui-syntaxhighlighting": "3.7.5-20250212-173204",
|
|
72
|
+
"@scm-manager/ui-shortcuts": "3.7.5-20250212-173204",
|
|
73
|
+
"@scm-manager/ui-text": "3.7.5-20250212-173204"
|
|
73
74
|
},
|
|
74
75
|
"dependencies": {
|
|
75
|
-
"@scm-manager/ui-core": "3.7.
|
|
76
|
-
"@scm-manager/ui-overlays": "3.7.
|
|
77
|
-
"@scm-manager/ui-layout": "3.7.
|
|
78
|
-
"@scm-manager/ui-buttons": "3.7.
|
|
79
|
-
"@scm-manager/ui-api": "3.7.
|
|
80
|
-
"@scm-manager/ui-extensions": "3.7.
|
|
76
|
+
"@scm-manager/ui-core": "3.7.5-20250212-173204",
|
|
77
|
+
"@scm-manager/ui-overlays": "3.7.5-20250212-173204",
|
|
78
|
+
"@scm-manager/ui-layout": "3.7.5-20250212-173204",
|
|
79
|
+
"@scm-manager/ui-buttons": "3.7.5-20250212-173204",
|
|
80
|
+
"@scm-manager/ui-api": "3.7.5-20250212-173204",
|
|
81
|
+
"@scm-manager/ui-extensions": "3.7.5-20250212-173204",
|
|
81
82
|
"deepmerge": "^4.2.2",
|
|
82
83
|
"hast-util-sanitize": "^3.0.2",
|
|
83
84
|
"react-diff-view": "^2.4.10",
|
package/src/Duration.stories.tsx
CHANGED
|
@@ -20,21 +20,39 @@ import React from "react";
|
|
|
20
20
|
|
|
21
21
|
storiesOf("Duration", module).add("Duration", () => (
|
|
22
22
|
<div className="m-5 p-5">
|
|
23
|
+
<p>
|
|
24
|
+
<Duration duration={1} />
|
|
25
|
+
</p>
|
|
23
26
|
<p>
|
|
24
27
|
<Duration duration={500} />
|
|
25
28
|
</p>
|
|
29
|
+
<p>
|
|
30
|
+
<Duration duration={1000 + 1} />
|
|
31
|
+
</p>
|
|
26
32
|
<p>
|
|
27
33
|
<Duration duration={2000} />
|
|
28
34
|
</p>
|
|
35
|
+
<p>
|
|
36
|
+
<Duration duration={1000 * 60 + 1} />
|
|
37
|
+
</p>
|
|
29
38
|
<p>
|
|
30
39
|
<Duration duration={42 * 1000 * 60} />
|
|
31
40
|
</p>
|
|
41
|
+
<p>
|
|
42
|
+
<Duration duration={1000 * 60 * 60 + 1} />
|
|
43
|
+
</p>
|
|
32
44
|
<p>
|
|
33
45
|
<Duration duration={21 * 1000 * 60 * 60} />
|
|
34
46
|
</p>
|
|
47
|
+
<p>
|
|
48
|
+
<Duration duration={1000 * 60 * 60 * 24 + 1} />
|
|
49
|
+
</p>
|
|
35
50
|
<p>
|
|
36
51
|
<Duration duration={5 * 1000 * 60 * 60 * 24} />
|
|
37
52
|
</p>
|
|
53
|
+
<p>
|
|
54
|
+
<Duration duration={1000 * 60 * 60 * 24 * 7 + 1} />
|
|
55
|
+
</p>
|
|
38
56
|
<p>
|
|
39
57
|
<Duration duration={3 * 1000 * 60 * 60 * 24 * 7} />
|
|
40
58
|
</p>
|
|
@@ -1521,6 +1521,13 @@ exports[`Storyshots Duration Duration 1`] = `
|
|
|
1521
1521
|
<div
|
|
1522
1522
|
className="m-5 p-5"
|
|
1523
1523
|
>
|
|
1524
|
+
<p>
|
|
1525
|
+
<time
|
|
1526
|
+
dateTime="1ms"
|
|
1527
|
+
>
|
|
1528
|
+
duration.ms
|
|
1529
|
+
</time>
|
|
1530
|
+
</p>
|
|
1524
1531
|
<p>
|
|
1525
1532
|
<time
|
|
1526
1533
|
dateTime="500ms"
|
|
@@ -1528,6 +1535,13 @@ exports[`Storyshots Duration Duration 1`] = `
|
|
|
1528
1535
|
duration.ms
|
|
1529
1536
|
</time>
|
|
1530
1537
|
</p>
|
|
1538
|
+
<p>
|
|
1539
|
+
<time
|
|
1540
|
+
dateTime="1s"
|
|
1541
|
+
>
|
|
1542
|
+
duration.s
|
|
1543
|
+
</time>
|
|
1544
|
+
</p>
|
|
1531
1545
|
<p>
|
|
1532
1546
|
<time
|
|
1533
1547
|
dateTime="2s"
|
|
@@ -1535,6 +1549,13 @@ exports[`Storyshots Duration Duration 1`] = `
|
|
|
1535
1549
|
duration.s
|
|
1536
1550
|
</time>
|
|
1537
1551
|
</p>
|
|
1552
|
+
<p>
|
|
1553
|
+
<time
|
|
1554
|
+
dateTime="1m"
|
|
1555
|
+
>
|
|
1556
|
+
duration.m
|
|
1557
|
+
</time>
|
|
1558
|
+
</p>
|
|
1538
1559
|
<p>
|
|
1539
1560
|
<time
|
|
1540
1561
|
dateTime="42m"
|
|
@@ -1542,6 +1563,13 @@ exports[`Storyshots Duration Duration 1`] = `
|
|
|
1542
1563
|
duration.m
|
|
1543
1564
|
</time>
|
|
1544
1565
|
</p>
|
|
1566
|
+
<p>
|
|
1567
|
+
<time
|
|
1568
|
+
dateTime="1h"
|
|
1569
|
+
>
|
|
1570
|
+
duration.h
|
|
1571
|
+
</time>
|
|
1572
|
+
</p>
|
|
1545
1573
|
<p>
|
|
1546
1574
|
<time
|
|
1547
1575
|
dateTime="21h"
|
|
@@ -1549,6 +1577,13 @@ exports[`Storyshots Duration Duration 1`] = `
|
|
|
1549
1577
|
duration.h
|
|
1550
1578
|
</time>
|
|
1551
1579
|
</p>
|
|
1580
|
+
<p>
|
|
1581
|
+
<time
|
|
1582
|
+
dateTime="1d"
|
|
1583
|
+
>
|
|
1584
|
+
duration.d
|
|
1585
|
+
</time>
|
|
1586
|
+
</p>
|
|
1552
1587
|
<p>
|
|
1553
1588
|
<time
|
|
1554
1589
|
dateTime="5d"
|
|
@@ -1556,6 +1591,13 @@ exports[`Storyshots Duration Duration 1`] = `
|
|
|
1556
1591
|
duration.d
|
|
1557
1592
|
</time>
|
|
1558
1593
|
</p>
|
|
1594
|
+
<p>
|
|
1595
|
+
<time
|
|
1596
|
+
dateTime="1w"
|
|
1597
|
+
>
|
|
1598
|
+
duration.w
|
|
1599
|
+
</time>
|
|
1600
|
+
</p>
|
|
1559
1601
|
<p>
|
|
1560
1602
|
<time
|
|
1561
1603
|
dateTime="3w"
|
|
@@ -78158,11 +78200,18 @@ exports[`Storyshots Secondary Navigation Active when match 1`] = `
|
|
|
78158
78200
|
<div>
|
|
78159
78201
|
<button
|
|
78160
78202
|
aria-label="secondaryNavigation.hideContent"
|
|
78161
|
-
className="button SecondaryNavigation__MenuButton-sc-8p1rgi-2 fkeiWf menu-label
|
|
78203
|
+
className="button SecondaryNavigation__MenuButton-sc-8p1rgi-2 fkeiWf menu-label"
|
|
78162
78204
|
collapsed={false}
|
|
78163
78205
|
onClick={[Function]}
|
|
78164
78206
|
type="button"
|
|
78165
78207
|
>
|
|
78208
|
+
<i
|
|
78209
|
+
className="SecondaryNavigation__Icon-sc-8p1rgi-1 gqxbcY is-medium"
|
|
78210
|
+
>
|
|
78211
|
+
<i
|
|
78212
|
+
className="fas fa-caret-down"
|
|
78213
|
+
/>
|
|
78214
|
+
</i>
|
|
78166
78215
|
Hitchhiker
|
|
78167
78216
|
</button>
|
|
78168
78217
|
<ul
|
|
@@ -78215,7 +78264,7 @@ exports[`Storyshots Secondary Navigation Default 1`] = `
|
|
|
78215
78264
|
<div>
|
|
78216
78265
|
<button
|
|
78217
78266
|
aria-label="secondaryNavigation.hideContent"
|
|
78218
|
-
className="button SecondaryNavigation__MenuButton-sc-8p1rgi-2 fkeiWf menu-label
|
|
78267
|
+
className="button SecondaryNavigation__MenuButton-sc-8p1rgi-2 fkeiWf menu-label"
|
|
78219
78268
|
collapsed={false}
|
|
78220
78269
|
onClick={[Function]}
|
|
78221
78270
|
type="button"
|
|
@@ -78278,7 +78327,7 @@ exports[`Storyshots Secondary Navigation Extension Point 1`] = `
|
|
|
78278
78327
|
<div>
|
|
78279
78328
|
<button
|
|
78280
78329
|
aria-label="secondaryNavigation.hideContent"
|
|
78281
|
-
className="button SecondaryNavigation__MenuButton-sc-8p1rgi-2 fkeiWf menu-label
|
|
78330
|
+
className="button SecondaryNavigation__MenuButton-sc-8p1rgi-2 fkeiWf menu-label"
|
|
78282
78331
|
collapsed={false}
|
|
78283
78332
|
onClick={[Function]}
|
|
78284
78333
|
type="button"
|
|
@@ -78369,7 +78418,7 @@ exports[`Storyshots Secondary Navigation Sub Navigation 1`] = `
|
|
|
78369
78418
|
<div>
|
|
78370
78419
|
<button
|
|
78371
78420
|
aria-label="secondaryNavigation.hideContent"
|
|
78372
|
-
className="button SecondaryNavigation__MenuButton-sc-8p1rgi-2 fkeiWf menu-label
|
|
78421
|
+
className="button SecondaryNavigation__MenuButton-sc-8p1rgi-2 fkeiWf menu-label"
|
|
78373
78422
|
collapsed={false}
|
|
78374
78423
|
onClick={[Function]}
|
|
78375
78424
|
type="button"
|
package/src/buttons/Button.tsx
CHANGED
|
@@ -41,7 +41,7 @@ type Props = ButtonProps & {
|
|
|
41
41
|
};
|
|
42
42
|
|
|
43
43
|
/**
|
|
44
|
-
* @deprecated Use {@link ui-
|
|
44
|
+
* @deprecated Use {@link ui-core/src/base/buttons/Button} instead
|
|
45
45
|
*/
|
|
46
46
|
const Button = React.forwardRef<HTMLButtonElement | HTMLAnchorElement, Props>(
|
|
47
47
|
(
|
package/src/forms/InputField.tsx
CHANGED
|
@@ -21,6 +21,7 @@ import { createAttributesForTesting } from "../devBuild";
|
|
|
21
21
|
import useAutofocus from "./useAutofocus";
|
|
22
22
|
import { createFormFieldWrapper, FieldProps, FieldType, isLegacy, isUsingRef } from "./FormFieldTypes";
|
|
23
23
|
import { createA11yId } from "../createA11yId";
|
|
24
|
+
import { FieldMessage } from "@scm-manager/ui-core";
|
|
24
25
|
|
|
25
26
|
type BaseProps = {
|
|
26
27
|
label?: string;
|
|
@@ -40,6 +41,7 @@ type BaseProps = {
|
|
|
40
41
|
defaultValue?: string | number;
|
|
41
42
|
readOnly?: boolean;
|
|
42
43
|
required?: boolean;
|
|
44
|
+
warning?: string;
|
|
43
45
|
};
|
|
44
46
|
|
|
45
47
|
export const InnerInputField: FC<FieldProps<BaseProps, HTMLInputElement, string>> = ({
|
|
@@ -60,6 +62,7 @@ export const InnerInputField: FC<FieldProps<BaseProps, HTMLInputElement, string>
|
|
|
60
62
|
defaultValue,
|
|
61
63
|
readOnly,
|
|
62
64
|
required,
|
|
65
|
+
warning,
|
|
63
66
|
...props
|
|
64
67
|
}) => {
|
|
65
68
|
const field = useAutofocus<HTMLInputElement>(autofocus, props.innerRef);
|
|
@@ -123,6 +126,7 @@ export const InnerInputField: FC<FieldProps<BaseProps, HTMLInputElement, string>
|
|
|
123
126
|
{...createAttributesForTesting(testId)}
|
|
124
127
|
/>
|
|
125
128
|
</div>
|
|
129
|
+
{warning ? <FieldMessage variant="warning">{warning}</FieldMessage> : null}
|
|
126
130
|
{helper}
|
|
127
131
|
</fieldset>
|
|
128
132
|
);
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
import React, { FC } from "react";
|
|
18
18
|
import styled from "styled-components";
|
|
19
19
|
import { useSecondaryNavigation } from "../useSecondaryNavigation";
|
|
20
|
+
import { SecondaryNavigationProvider } from "../navigation/SecondaryNavigationContext";
|
|
20
21
|
|
|
21
22
|
const SecondaryColumn = styled.div<{ collapsed: boolean }>`
|
|
22
23
|
flex: 0 0 auto;
|
|
@@ -28,7 +29,7 @@ const SecondaryColumn = styled.div<{ collapsed: boolean }>`
|
|
|
28
29
|
}
|
|
29
30
|
`;
|
|
30
31
|
|
|
31
|
-
const
|
|
32
|
+
const SecondaryNavigationColumnIntern: FC = ({ children }) => {
|
|
32
33
|
const { collapsed } = useSecondaryNavigation();
|
|
33
34
|
|
|
34
35
|
return (
|
|
@@ -38,4 +39,12 @@ const SecondaryNavigationColumn: FC = ({ children }) => {
|
|
|
38
39
|
);
|
|
39
40
|
};
|
|
40
41
|
|
|
42
|
+
const SecondaryNavigationColumn: FC = ({ children }) => {
|
|
43
|
+
return (
|
|
44
|
+
<SecondaryNavigationProvider>
|
|
45
|
+
<SecondaryNavigationColumnIntern>{children}</SecondaryNavigationColumnIntern>
|
|
46
|
+
</SecondaryNavigationProvider>
|
|
47
|
+
);
|
|
48
|
+
};
|
|
49
|
+
|
|
41
50
|
export default SecondaryNavigationColumn;
|
|
@@ -14,11 +14,10 @@
|
|
|
14
14
|
* along with this program. If not, see https://www.gnu.org/licenses/.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import React, { FC
|
|
17
|
+
import React, { FC } from "react";
|
|
18
18
|
import classNames from "classnames";
|
|
19
19
|
import { useSecondaryNavigation } from "../useSecondaryNavigation";
|
|
20
20
|
import ExternalLink from "./ExternalLink";
|
|
21
|
-
import { SecondaryNavigationContext } from "./SecondaryNavigationContext";
|
|
22
21
|
|
|
23
22
|
type Props = {
|
|
24
23
|
to: string;
|
|
@@ -28,7 +27,6 @@ type Props = {
|
|
|
28
27
|
|
|
29
28
|
const ExternalNavLink: FC<Props> = ({ to, icon, label }) => {
|
|
30
29
|
const { collapsed } = useSecondaryNavigation();
|
|
31
|
-
const isSecondaryNavigation = useContext(SecondaryNavigationContext);
|
|
32
30
|
|
|
33
31
|
let showIcon;
|
|
34
32
|
if (icon) {
|
|
@@ -43,7 +41,7 @@ const ExternalNavLink: FC<Props> = ({ to, icon, label }) => {
|
|
|
43
41
|
<li title={collapsed ? label : undefined}>
|
|
44
42
|
<ExternalLink to={to} className={collapsed ? "has-text-centered" : ""} aria-label={collapsed ? label : undefined}>
|
|
45
43
|
{showIcon}
|
|
46
|
-
{
|
|
44
|
+
{collapsed ? null : label}
|
|
47
45
|
</ExternalLink>
|
|
48
46
|
</li>
|
|
49
47
|
);
|
|
@@ -17,12 +17,11 @@
|
|
|
17
17
|
import React, { FC, useContext, useEffect } from "react";
|
|
18
18
|
import classNames from "classnames";
|
|
19
19
|
import { Link } from "react-router-dom";
|
|
20
|
+
import { createAttributesForTesting } from "@scm-manager/ui-core";
|
|
20
21
|
import { useSecondaryNavigation } from "../useSecondaryNavigation";
|
|
21
22
|
import { RoutingProps } from "./RoutingProps";
|
|
22
|
-
import useActiveMatch from "./useActiveMatch";
|
|
23
|
-
import { createAttributesForTesting } from "@scm-manager/ui-core";
|
|
24
|
-
import { SecondaryNavigationContext } from "./SecondaryNavigationContext";
|
|
25
23
|
import { SubNavigationContext } from "./SubNavigationContext";
|
|
24
|
+
import useActiveMatch from "./useActiveMatch";
|
|
26
25
|
|
|
27
26
|
type Props = RoutingProps & {
|
|
28
27
|
label: string;
|
|
@@ -51,14 +50,13 @@ const NavLinkContent: FC<NavLinkContentProp> = ({ label, icon, collapsed }) => (
|
|
|
51
50
|
const NavLink: FC<Props> = ({ to, activeWhenMatch, activeOnlyWhenExact, title, testId, children, ...contentProps }) => {
|
|
52
51
|
const active = useActiveMatch({ to, activeWhenMatch, activeOnlyWhenExact });
|
|
53
52
|
const { collapsed, setCollapsible } = useSecondaryNavigation();
|
|
54
|
-
const isSecondaryNavigation = useContext(SecondaryNavigationContext);
|
|
55
53
|
const isSubNavigation = useContext(SubNavigationContext);
|
|
56
54
|
|
|
57
55
|
useEffect(() => {
|
|
58
|
-
if (
|
|
56
|
+
if (active) {
|
|
59
57
|
setCollapsible(!isSubNavigation);
|
|
60
58
|
}
|
|
61
|
-
}, [active,
|
|
59
|
+
}, [active, isSubNavigation, setCollapsible]);
|
|
62
60
|
|
|
63
61
|
return (
|
|
64
62
|
<li title={collapsed ? title : undefined}>
|
|
@@ -69,11 +67,7 @@ const NavLink: FC<Props> = ({ to, activeWhenMatch, activeOnlyWhenExact, title, t
|
|
|
69
67
|
aria-label={collapsed ? title : undefined}
|
|
70
68
|
{...(active ? { "aria-current": "page" } : {})}
|
|
71
69
|
>
|
|
72
|
-
{children
|
|
73
|
-
children
|
|
74
|
-
) : (
|
|
75
|
-
<NavLinkContent {...contentProps} collapsed={(isSecondaryNavigation && collapsed) ?? false} />
|
|
76
|
-
)}
|
|
70
|
+
{children || <NavLinkContent {...contentProps} collapsed={collapsed} />}
|
|
77
71
|
</Link>
|
|
78
72
|
</li>
|
|
79
73
|
);
|
|
@@ -16,12 +16,13 @@
|
|
|
16
16
|
|
|
17
17
|
import { storiesOf } from "@storybook/react";
|
|
18
18
|
import React, { ReactElement } from "react";
|
|
19
|
+
import { MemoryRouter } from "react-router-dom";
|
|
20
|
+
import styled from "styled-components";
|
|
21
|
+
import { Binder, ExtensionPoint, BinderContext } from "@scm-manager/ui-extensions";
|
|
22
|
+
import { SecondaryNavigationProvider } from "./SecondaryNavigationContext";
|
|
19
23
|
import SecondaryNavigation from "./SecondaryNavigation";
|
|
20
24
|
import SecondaryNavigationItem from "./SecondaryNavigationItem";
|
|
21
|
-
import styled from "styled-components";
|
|
22
25
|
import SubNavigation from "./SubNavigation";
|
|
23
|
-
import { Binder, ExtensionPoint, BinderContext } from "@scm-manager/ui-extensions";
|
|
24
|
-
import { MemoryRouter } from "react-router-dom";
|
|
25
26
|
|
|
26
27
|
const Columns = styled.div`
|
|
27
28
|
margin: 2rem;
|
|
@@ -46,7 +47,9 @@ const withRoute = (route: string) => {
|
|
|
46
47
|
storiesOf("Secondary Navigation", module)
|
|
47
48
|
.addDecorator((story) => (
|
|
48
49
|
<Columns className="columns">
|
|
49
|
-
<div className="column is-3">
|
|
50
|
+
<div className="column is-3">
|
|
51
|
+
<SecondaryNavigationProvider>{story()}</SecondaryNavigationProvider>
|
|
52
|
+
</div>
|
|
50
53
|
</Columns>
|
|
51
54
|
))
|
|
52
55
|
.add("Default", () =>
|
|
@@ -16,10 +16,8 @@
|
|
|
16
16
|
|
|
17
17
|
import React, { FC } from "react";
|
|
18
18
|
import styled from "styled-components";
|
|
19
|
-
import classNames from "classnames";
|
|
20
19
|
import { useTranslation } from "react-i18next";
|
|
21
20
|
import { useSecondaryNavigation } from "../useSecondaryNavigation";
|
|
22
|
-
import { SecondaryNavigationContext } from "./SecondaryNavigationContext";
|
|
23
21
|
import { Button } from "@scm-manager/ui-buttons";
|
|
24
22
|
|
|
25
23
|
type Props = {
|
|
@@ -73,14 +71,9 @@ const SecondaryNavigation: FC<Props> = ({ label, children, collapsible = true })
|
|
|
73
71
|
const menuAriaLabel = collapsed ? t("secondaryNavigation.showContent") : t("secondaryNavigation.hideContent");
|
|
74
72
|
|
|
75
73
|
return (
|
|
76
|
-
<SectionContainer className="menu" collapsed={collapsed
|
|
74
|
+
<SectionContainer className="menu" collapsed={collapsed}>
|
|
77
75
|
<div>
|
|
78
|
-
<MenuButton
|
|
79
|
-
className={classNames("menu-label", { "is-clickable": true })}
|
|
80
|
-
collapsed={collapsed}
|
|
81
|
-
onClick={toggleCollapse}
|
|
82
|
-
aria-label={menuAriaLabel}
|
|
83
|
-
>
|
|
76
|
+
<MenuButton className="menu-label" collapsed={collapsed} onClick={toggleCollapse} aria-label={menuAriaLabel}>
|
|
84
77
|
{isCollapsible ? (
|
|
85
78
|
<Icon className="is-medium" collapsed={collapsed}>
|
|
86
79
|
{arrowIcon}
|
|
@@ -88,9 +81,7 @@ const SecondaryNavigation: FC<Props> = ({ label, children, collapsible = true })
|
|
|
88
81
|
) : null}
|
|
89
82
|
{collapsed ? "" : label}
|
|
90
83
|
</MenuButton>
|
|
91
|
-
<ul className="menu-list">
|
|
92
|
-
<SecondaryNavigationContext.Provider value={true}>{children}</SecondaryNavigationContext.Provider>
|
|
93
|
-
</ul>
|
|
84
|
+
<ul className="menu-list">{children}</ul>
|
|
94
85
|
</div>
|
|
95
86
|
</SectionContainer>
|
|
96
87
|
);
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2020 - present Cloudogu GmbH
|
|
3
|
+
*
|
|
4
|
+
* This program is free software: you can redistribute it and/or modify it under
|
|
5
|
+
* the terms of the GNU Affero General Public License as published by the Free
|
|
6
|
+
* Software Foundation, version 3.
|
|
7
|
+
*
|
|
8
|
+
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
9
|
+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
10
|
+
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
|
11
|
+
* details.
|
|
12
|
+
*
|
|
13
|
+
* You should have received a copy of the GNU Affero General Public License
|
|
14
|
+
* along with this program. If not, see https://www.gnu.org/licenses/.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import React, { ReactNode, useMemo, useState } from "react";
|
|
18
|
+
|
|
19
|
+
type SecondaryNavigationContextState = {
|
|
20
|
+
collapsible: boolean;
|
|
21
|
+
setCollapsible: (collapsed: boolean) => void;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const dummy: SecondaryNavigationContextState = {
|
|
25
|
+
collapsible: false,
|
|
26
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
27
|
+
setCollapsible: () => {},
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export const SecondaryNavigationContext = React.createContext<SecondaryNavigationContextState>(dummy);
|
|
31
|
+
|
|
32
|
+
export const SecondaryNavigationProvider = ({ children }: { children: ReactNode }) => {
|
|
33
|
+
const [collapsible, setCollapsible] = useState(true);
|
|
34
|
+
const contextValue = useMemo(() => ({ collapsible, setCollapsible }), [collapsible, setCollapsible]);
|
|
35
|
+
|
|
36
|
+
return <SecondaryNavigationContext.Provider value={contextValue}>{children}</SecondaryNavigationContext.Provider>;
|
|
37
|
+
};
|
|
@@ -26,7 +26,7 @@ import { DiffObjectProps } from "./DiffTypes";
|
|
|
26
26
|
import DiffStatistics from "./DiffStatistics";
|
|
27
27
|
import { DiffDropDown } from "../index";
|
|
28
28
|
import DiffFileTree from "./diff/DiffFileTree";
|
|
29
|
-
import { DiffContent, Divider, FileTreeContent } from "./diff/styledElements";
|
|
29
|
+
import { DiffContent, Divider, FileTreeContent, StickyFileDiffContainer } from "./diff/styledElements";
|
|
30
30
|
import { useHistory, useLocation } from "react-router-dom";
|
|
31
31
|
import { getFileNameFromHash } from "./diffs";
|
|
32
32
|
import LayoutRadioButtons from "./LayoutRadioButtons";
|
|
@@ -111,7 +111,7 @@ const LoadingDiff: FC<Props> = ({ url, limit, refetchOnWindowFocus, ...props })
|
|
|
111
111
|
</div>
|
|
112
112
|
<LayoutRadioButtons layout={layout} setLayout={setLayout} />
|
|
113
113
|
<div className="is-flex mb-4 mt-1 columns is-multiline">
|
|
114
|
-
<
|
|
114
|
+
<StickyFileDiffContainer
|
|
115
115
|
className={
|
|
116
116
|
(layout === "Both" ? "column pl-3 is-one-quarter" : "column pl-3 is-full") +
|
|
117
117
|
(layout !== "Diff" ? "" : " is-hidden")
|
|
@@ -125,10 +125,11 @@ const LoadingDiff: FC<Props> = ({ url, limit, refetchOnWindowFocus, ...props })
|
|
|
125
125
|
tree={data.tree}
|
|
126
126
|
currentFile={decodeURIComponent(getFileNameFromHash(location.hash) ?? "")}
|
|
127
127
|
setCurrentFile={setFilePath}
|
|
128
|
+
gap={12}
|
|
128
129
|
/>
|
|
129
130
|
)}
|
|
130
131
|
</FileTreeContent>
|
|
131
|
-
</
|
|
132
|
+
</StickyFileDiffContainer>
|
|
132
133
|
<DiffContent id={diffContentId} className={layout !== "Tree" ? "column" : "is-hidden"}>
|
|
133
134
|
<Diff
|
|
134
135
|
defaultCollapse={collapsed}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2020 - present Cloudogu GmbH
|
|
3
|
+
*
|
|
4
|
+
* This program is free software: you can redistribute it and/or modify it under
|
|
5
|
+
* the terms of the GNU Affero General Public License as published by the Free
|
|
6
|
+
* Software Foundation, version 3.
|
|
7
|
+
*
|
|
8
|
+
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
9
|
+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
10
|
+
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
|
11
|
+
* details.
|
|
12
|
+
*
|
|
13
|
+
* You should have received a copy of the GNU Affero General Public License
|
|
14
|
+
* along with this program. If not, see https://www.gnu.org/licenses/.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import * as changesets from "./changesets";
|
|
18
|
+
import { render } from "@testing-library/react";
|
|
19
|
+
import { Branch, Changeset, Repository } from "@scm-manager/ui-types";
|
|
20
|
+
import ChangesetButtonGroup from "./ChangesetButtonGroup";
|
|
21
|
+
import React from "react";
|
|
22
|
+
import { BrowserRouter } from "react-router-dom";
|
|
23
|
+
import { stubI18Next } from "@scm-manager/ui-tests";
|
|
24
|
+
|
|
25
|
+
const createChangesetLink = jest.spyOn(changesets, "createChangesetLink");
|
|
26
|
+
const createChangesetLinkByBranch = jest.spyOn(changesets, "createChangesetLinkByBranch");
|
|
27
|
+
|
|
28
|
+
afterEach(() => {
|
|
29
|
+
jest.resetAllMocks();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe("ChangesetButtonGroup", () => {
|
|
33
|
+
test("shouldCallCreateChangesetLinkWithoutBranch", async () => {
|
|
34
|
+
stubI18Next();
|
|
35
|
+
const { repository, changeset } = createTestData();
|
|
36
|
+
render(
|
|
37
|
+
<BrowserRouter>
|
|
38
|
+
<ChangesetButtonGroup repository={repository} changeset={changeset}></ChangesetButtonGroup>
|
|
39
|
+
</BrowserRouter>
|
|
40
|
+
);
|
|
41
|
+
expect(createChangesetLink).toHaveBeenCalled();
|
|
42
|
+
expect(createChangesetLinkByBranch).toHaveBeenCalledTimes(0);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("shouldCallCreateChangesetLinkByBranchWithBranch", async () => {
|
|
46
|
+
stubI18Next();
|
|
47
|
+
const { repository, changeset, branch } = createTestData();
|
|
48
|
+
render(
|
|
49
|
+
<BrowserRouter>
|
|
50
|
+
<ChangesetButtonGroup repository={repository} changeset={changeset} branch={branch}></ChangesetButtonGroup>
|
|
51
|
+
</BrowserRouter>
|
|
52
|
+
);
|
|
53
|
+
expect(createChangesetLinkByBranch).toHaveBeenCalled();
|
|
54
|
+
expect(createChangesetLink).toHaveBeenCalledTimes(0);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// TODO centralized test data
|
|
59
|
+
function createTestData() {
|
|
60
|
+
const repository: Repository = { _links: {}, name: "", namespace: "", type: "" };
|
|
61
|
+
const changeset: Changeset = {
|
|
62
|
+
_links: {},
|
|
63
|
+
author: {
|
|
64
|
+
name: "",
|
|
65
|
+
},
|
|
66
|
+
date: new Date(),
|
|
67
|
+
description: "",
|
|
68
|
+
id: "",
|
|
69
|
+
};
|
|
70
|
+
const branch: Branch = { _links: {}, name: "", revision: "" };
|
|
71
|
+
return { repository, changeset, branch };
|
|
72
|
+
}
|
|
@@ -15,21 +15,24 @@
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import React from "react";
|
|
18
|
-
import { Changeset, File, Repository } from "@scm-manager/ui-types";
|
|
19
|
-
import {
|
|
20
|
-
import { createChangesetLink, createSourcesLink } from "./changesets";
|
|
18
|
+
import { Branch, Changeset, File, Repository } from "@scm-manager/ui-types";
|
|
19
|
+
import { Button, ButtonAddons } from "../../buttons";
|
|
20
|
+
import { createChangesetLink, createChangesetLinkByBranch, createSourcesLink } from "./changesets";
|
|
21
21
|
import { useTranslation } from "react-i18next";
|
|
22
22
|
|
|
23
23
|
type Props = {
|
|
24
24
|
repository: Repository;
|
|
25
25
|
changeset: Changeset;
|
|
26
26
|
file?: File;
|
|
27
|
+
branch?: Branch;
|
|
27
28
|
};
|
|
28
29
|
|
|
29
30
|
const ChangesetButtonGroup = React.forwardRef<HTMLButtonElement | HTMLAnchorElement, Props>(
|
|
30
|
-
({ repository, changeset, file }, ref) => {
|
|
31
|
+
({ repository, changeset, file, branch }, ref) => {
|
|
31
32
|
const [t] = useTranslation("repos");
|
|
32
|
-
const changesetLink =
|
|
33
|
+
const changesetLink = branch
|
|
34
|
+
? createChangesetLinkByBranch(repository, changeset, branch)
|
|
35
|
+
: createChangesetLink(repository, changeset);
|
|
33
36
|
const sourcesLink = createSourcesLink(repository, changeset, file);
|
|
34
37
|
return (
|
|
35
38
|
<ButtonAddons className="m-0">
|
|
@@ -16,20 +16,23 @@
|
|
|
16
16
|
|
|
17
17
|
import ChangesetRow from "./ChangesetRow";
|
|
18
18
|
import React, { FC } from "react";
|
|
19
|
-
import { Changeset, File, Repository } from "@scm-manager/ui-types";
|
|
19
|
+
import { Branch, Changeset, File, Repository } from "@scm-manager/ui-types";
|
|
20
20
|
import { KeyboardIterator } from "@scm-manager/ui-shortcuts";
|
|
21
21
|
|
|
22
22
|
type Props = {
|
|
23
23
|
repository: Repository;
|
|
24
24
|
changesets: Changeset[];
|
|
25
25
|
file?: File;
|
|
26
|
+
branch?: Branch;
|
|
26
27
|
};
|
|
27
28
|
|
|
28
|
-
const ChangesetList: FC<Props> = ({ repository, changesets, file }) => {
|
|
29
|
+
const ChangesetList: FC<Props> = ({ repository, changesets, file, branch }) => {
|
|
29
30
|
return (
|
|
30
31
|
<KeyboardIterator>
|
|
31
32
|
{changesets.map((changeset) => {
|
|
32
|
-
return
|
|
33
|
+
return (
|
|
34
|
+
<ChangesetRow key={changeset.id} repository={repository} changeset={changeset} file={file} branch={branch} />
|
|
35
|
+
);
|
|
33
36
|
})}
|
|
34
37
|
</KeyboardIterator>
|
|
35
38
|
);
|
|
@@ -18,7 +18,7 @@ import React, { FC } from "react";
|
|
|
18
18
|
import classNames from "classnames";
|
|
19
19
|
import styled from "styled-components";
|
|
20
20
|
import { ExtensionPoint, extensionPoints } from "@scm-manager/ui-extensions";
|
|
21
|
-
import { Changeset, File, Repository } from "@scm-manager/ui-types";
|
|
21
|
+
import { Branch, Changeset, File, Repository } from "@scm-manager/ui-types";
|
|
22
22
|
import ChangesetButtonGroup from "./ChangesetButtonGroup";
|
|
23
23
|
import SingleChangeset from "./SingleChangeset";
|
|
24
24
|
import { useKeyboardIteratorTarget } from "@scm-manager/ui-shortcuts";
|
|
@@ -27,11 +27,13 @@ type Props = {
|
|
|
27
27
|
repository: Repository;
|
|
28
28
|
changeset: Changeset;
|
|
29
29
|
file?: File;
|
|
30
|
+
branch?: Branch;
|
|
30
31
|
};
|
|
31
32
|
|
|
32
33
|
const Wrapper = styled.div`
|
|
33
34
|
// & references parent rule
|
|
34
35
|
// have a look at https://cssinjs.org/jss-plugin-nested?v=v10.0.0-alpha.9
|
|
36
|
+
|
|
35
37
|
& + & {
|
|
36
38
|
margin-top: 1rem;
|
|
37
39
|
padding-top: 1rem;
|
|
@@ -39,7 +41,7 @@ const Wrapper = styled.div`
|
|
|
39
41
|
}
|
|
40
42
|
`;
|
|
41
43
|
|
|
42
|
-
const ChangesetRow: FC<Props> = ({ repository, changeset, file }) => {
|
|
44
|
+
const ChangesetRow: FC<Props> = ({ repository, changeset, file, branch }) => {
|
|
43
45
|
const ref = useKeyboardIteratorTarget();
|
|
44
46
|
return (
|
|
45
47
|
<Wrapper>
|
|
@@ -48,7 +50,7 @@ const ChangesetRow: FC<Props> = ({ repository, changeset, file }) => {
|
|
|
48
50
|
<SingleChangeset repository={repository} changeset={changeset} />
|
|
49
51
|
</div>
|
|
50
52
|
<div className={classNames("column", "is-flex", "is-justify-content-flex-end", "is-align-items-center")}>
|
|
51
|
-
<ChangesetButtonGroup ref={ref} repository={repository} changeset={changeset} file={file} />
|
|
53
|
+
<ChangesetButtonGroup ref={ref} repository={repository} changeset={changeset} file={file} branch={branch} />
|
|
52
54
|
<ExtensionPoint<extensionPoints.ChangesetRight>
|
|
53
55
|
name="changeset.right"
|
|
54
56
|
props={{
|
|
@@ -14,9 +14,38 @@
|
|
|
14
14
|
* along with this program. If not, see https://www.gnu.org/licenses/.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import { parseDescription } from "./changesets";
|
|
17
|
+
import { createChangesetLink, createChangesetLinkByBranch, parseDescription } from "./changesets";
|
|
18
|
+
import { Branch, Changeset, Repository } from "@scm-manager/ui-types";
|
|
18
19
|
|
|
19
|
-
describe("
|
|
20
|
+
describe("createChangesetLink", () => {
|
|
21
|
+
it("should return a changeset link", () => {
|
|
22
|
+
const { repository, changeset } = createTestData();
|
|
23
|
+
const link = createChangesetLink(repository, changeset);
|
|
24
|
+
expect(link).toBe("/repo/sandbox/anarchy/code/changeset/4f153aa670d4b27c");
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
describe("createChangesetLinkByBranch", () => {
|
|
29
|
+
it("should return a changeset link with a branch query with given branch", () => {
|
|
30
|
+
const { repository, changeset, branch } = createTestData();
|
|
31
|
+
const link = createChangesetLinkByBranch(repository, changeset, branch);
|
|
32
|
+
expect(link).toBe("/repo/sandbox/anarchy/code/changeset/4f153aa670d4b27c?branch=resonanceCascade");
|
|
33
|
+
});
|
|
34
|
+
it("should return no branch query parameter with empty string", () => {
|
|
35
|
+
const { repository, changeset, branch } = createTestData();
|
|
36
|
+
branch.name = "";
|
|
37
|
+
const link = createChangesetLinkByBranch(repository, changeset, branch);
|
|
38
|
+
expect(link).toBe("/repo/sandbox/anarchy/code/changeset/4f153aa670d4b27c");
|
|
39
|
+
});
|
|
40
|
+
it("should escape a branch with a slash inside", () => {
|
|
41
|
+
const { repository, changeset, branch } = createTestData();
|
|
42
|
+
branch.name = "feature/rescueWorld";
|
|
43
|
+
const link = createChangesetLinkByBranch(repository, changeset, branch);
|
|
44
|
+
expect(link).toBe("/repo/sandbox/anarchy/code/changeset/4f153aa670d4b27c?branch=feature%2FrescueWorld");
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe("parseDescription", () => {
|
|
20
49
|
it("should return a description with title and message", () => {
|
|
21
50
|
const desc = parseDescription("Hello\nTrillian");
|
|
22
51
|
expect(desc.title).toBe("Hello");
|
|
@@ -34,3 +63,34 @@ describe("parseDescription tests", () => {
|
|
|
34
63
|
expect(desc.message).toBe("");
|
|
35
64
|
});
|
|
36
65
|
});
|
|
66
|
+
|
|
67
|
+
function createTestData() {
|
|
68
|
+
const repository: Repository = {
|
|
69
|
+
name: "anarchy",
|
|
70
|
+
namespace: "sandbox",
|
|
71
|
+
type: "git",
|
|
72
|
+
_links: {},
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const changeset: Changeset = {
|
|
76
|
+
author: {
|
|
77
|
+
name: "Gordon Freeman",
|
|
78
|
+
},
|
|
79
|
+
date: new Date(),
|
|
80
|
+
description: "Some repository.",
|
|
81
|
+
id: "4f153aa670d4b27c",
|
|
82
|
+
_links: {},
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const branch: Branch = {
|
|
86
|
+
name: "resonanceCascade",
|
|
87
|
+
revision: "4f153aa670d4b27c",
|
|
88
|
+
_links: {},
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
repository,
|
|
93
|
+
changeset,
|
|
94
|
+
branch,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* along with this program. If not, see https://www.gnu.org/licenses/.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import { Changeset, File, Repository } from "@scm-manager/ui-types";
|
|
17
|
+
import { Branch, Changeset, File, Repository } from "@scm-manager/ui-types";
|
|
18
18
|
|
|
19
19
|
export type Description = {
|
|
20
20
|
title: string;
|
|
@@ -25,6 +25,16 @@ export function createChangesetLink(repository: Repository, changeset: Changeset
|
|
|
25
25
|
return `/repo/${repository.namespace}/${repository.name}/code/changeset/${changeset.id}`;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
export function createChangesetLinkByBranch(repository: Repository, changeset: Changeset, branch: Branch) {
|
|
29
|
+
if (!branch.name) {
|
|
30
|
+
return `/repo/${repository.namespace}/${repository.name}/code/changeset/${changeset.id}`;
|
|
31
|
+
} else {
|
|
32
|
+
return `/repo/${repository.namespace}/${repository.name}/code/changeset/${changeset.id}?branch=${encodeURIComponent(
|
|
33
|
+
branch.name
|
|
34
|
+
)}`;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
28
38
|
export function createSourcesLink(repository: Repository, changeset: Changeset, file?: File) {
|
|
29
39
|
let url = `/repo/${repository.namespace}/${repository.name}/code/sources/${changeset.id}`;
|
|
30
40
|
|
|
@@ -50,6 +60,6 @@ export function parseDescription(description?: string): Description {
|
|
|
50
60
|
|
|
51
61
|
return {
|
|
52
62
|
title,
|
|
53
|
-
message
|
|
63
|
+
message,
|
|
54
64
|
};
|
|
55
65
|
}
|
|
@@ -21,16 +21,16 @@ import { Icon } from "@scm-manager/ui-core";
|
|
|
21
21
|
import { useTranslation } from "react-i18next";
|
|
22
22
|
import styled from "styled-components";
|
|
23
23
|
|
|
24
|
-
type Props = { tree: FileTree; currentFile: string; setCurrentFile: (path: string) => void };
|
|
24
|
+
type Props = { tree: FileTree; currentFile: string; setCurrentFile: (path: string) => void; gap?: number };
|
|
25
25
|
|
|
26
26
|
const StyledIcon = styled(Icon)`
|
|
27
27
|
min-width: 1.5rem;
|
|
28
28
|
`;
|
|
29
29
|
|
|
30
|
-
const DiffFileTree: FC<Props> = ({ tree, currentFile, setCurrentFile }) => {
|
|
30
|
+
const DiffFileTree: FC<Props> = ({ tree, currentFile, setCurrentFile, gap = 15 }) => {
|
|
31
31
|
return (
|
|
32
32
|
<FileDiffContainer className={"mt-4 py-3 pr-2"}>
|
|
33
|
-
<FileDiffContent>
|
|
33
|
+
<FileDiffContent gap={gap}>
|
|
34
34
|
{Object.keys(tree.children).map((key) => (
|
|
35
35
|
<TreeNode
|
|
36
36
|
key={key}
|
|
@@ -96,17 +96,28 @@ export const DiffContent = styled.div`
|
|
|
96
96
|
width: 100%;
|
|
97
97
|
`;
|
|
98
98
|
|
|
99
|
-
export const
|
|
99
|
+
export const StickyFileDiffContainer = styled.div`
|
|
100
|
+
top: 3rem;
|
|
100
101
|
position: sticky;
|
|
102
|
+
height: 100%;
|
|
103
|
+
`;
|
|
104
|
+
|
|
105
|
+
export const FileDiffContainer = styled.div`
|
|
101
106
|
top: 5rem;
|
|
102
107
|
`;
|
|
103
108
|
|
|
104
|
-
export const FileDiffContent = styled.ul
|
|
109
|
+
export const FileDiffContent = styled.ul<{ gap?: number }>`
|
|
105
110
|
overflow: auto;
|
|
106
|
-
|
|
107
|
-
|
|
111
|
+
${(props) => {
|
|
112
|
+
if (props.gap) {
|
|
113
|
+
return `
|
|
114
|
+
@supports (-moz-appearance: none) {
|
|
115
|
+
max-height: calc(100vh - ${props.gap}rem);
|
|
108
116
|
}
|
|
109
|
-
max-height: calc(100svh -
|
|
117
|
+
max-height: calc(100svh - ${props.gap}rem);
|
|
118
|
+
`;
|
|
119
|
+
}
|
|
120
|
+
}};
|
|
110
121
|
`;
|
|
111
122
|
|
|
112
123
|
export const Divider = styled.div`
|
|
@@ -15,17 +15,14 @@
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import { useLocalStorage } from "@scm-manager/ui-api";
|
|
18
|
-
import { useCallback,
|
|
18
|
+
import { useCallback, useContext } from "react";
|
|
19
|
+
import { SecondaryNavigationContext } from "./navigation/SecondaryNavigationContext";
|
|
19
20
|
|
|
20
21
|
export const useSecondaryNavigation = (isNavigationCollapsible = true) => {
|
|
21
|
-
const
|
|
22
|
-
const [
|
|
22
|
+
const { collapsible, setCollapsible } = useContext(SecondaryNavigationContext);
|
|
23
|
+
const [isCollapsed, setCollapsed] = useLocalStorage("secondaryNavigation.collapsed", false);
|
|
23
24
|
|
|
24
|
-
const
|
|
25
|
-
() => isRouteCollapsible && isNavigationCollapsible,
|
|
26
|
-
[isNavigationCollapsible, isRouteCollapsible]
|
|
27
|
-
);
|
|
28
|
-
const collapsed = useMemo(() => collapsible && isCollapsed, [collapsible, isCollapsed]);
|
|
25
|
+
const collapsed = collapsible && isCollapsed;
|
|
29
26
|
|
|
30
27
|
const toggleCollapse = useCallback(() => {
|
|
31
28
|
if (collapsible) {
|
|
@@ -33,13 +30,10 @@ export const useSecondaryNavigation = (isNavigationCollapsible = true) => {
|
|
|
33
30
|
}
|
|
34
31
|
}, [collapsible, setCollapsed]);
|
|
35
32
|
|
|
36
|
-
return
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}),
|
|
43
|
-
[collapsed, collapsible, setRouteCollapsible, toggleCollapse]
|
|
44
|
-
);
|
|
33
|
+
return {
|
|
34
|
+
collapsed,
|
|
35
|
+
collapsible,
|
|
36
|
+
setCollapsible,
|
|
37
|
+
toggleCollapse,
|
|
38
|
+
};
|
|
45
39
|
};
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright (c) 2020 - present Cloudogu GmbH
|
|
3
|
-
*
|
|
4
|
-
* This program is free software: you can redistribute it and/or modify it under
|
|
5
|
-
* the terms of the GNU Affero General Public License as published by the Free
|
|
6
|
-
* Software Foundation, version 3.
|
|
7
|
-
*
|
|
8
|
-
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
9
|
-
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
10
|
-
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
|
11
|
-
* details.
|
|
12
|
-
*
|
|
13
|
-
* You should have received a copy of the GNU Affero General Public License
|
|
14
|
-
* along with this program. If not, see https://www.gnu.org/licenses/.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
import React from "react";
|
|
18
|
-
|
|
19
|
-
export const SecondaryNavigationContext = React.createContext(false);
|