Skip to content

Commit a978dd7

Browse files
authored
Merge branch 'main' into scroll-driven-animations
2 parents 74d07b0 + 7fc7fbc commit a978dd7

File tree

44 files changed

+741
-146
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+741
-146
lines changed

.github/workflows/crowdin.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ jobs:
1919
uses: actions/checkout@v4
2020

2121
- name: crowdin action
22-
uses: crowdin/github-action@v2.7.0
22+
uses: crowdin/github-action@v2.7.1
2323
with:
2424
upload_translations: true
2525
download_translations: true

.vscode/tasks.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
{
55
"label": "Run Jenkins",
66
"type": "shell",
7-
"command": "mvn hpi:run -Dskip.npm -P quick-build",
7+
"command": "mvn hpi:run -Djava.awt.headless=true -Dskip.npm -P quick-build",
88
"options": {
99
"env": {
1010
// Do not wait for debugger to connect (suspend=n), unlike mvnDebug

package-lock.json

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { useId } from "react";
2+
3+
export default function Checkbox({
4+
label,
5+
value,
6+
setValue,
7+
}: {
8+
label: string;
9+
value: boolean;
10+
setValue: (e: boolean) => void;
11+
}) {
12+
const id = useId();
13+
return (
14+
<div className="jenkins-checkbox">
15+
<input
16+
type="checkbox"
17+
id={id}
18+
name={id}
19+
checked={value}
20+
onChange={(e) => setValue(e.target.checked)}
21+
/>
22+
<label htmlFor={id}>{label}</label>
23+
</div>
24+
);
25+
}

src/main/frontend/common/components/dropdown-portal.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@ import { createPortal } from "react-dom";
33

44
interface DropdownPortalProps {
55
children: ReactNode;
6+
container: HTMLElement | null;
67
}
78

8-
export default function DropdownPortal({ children }: DropdownPortalProps) {
9-
const container = document.getElementById("console-pipeline-overflow-root");
10-
9+
export default function DropdownPortal({
10+
children,
11+
container,
12+
}: DropdownPortalProps) {
1113
if (!container) {
1214
console.error("DropdownPortal: Target container not found!");
1315
return null;

src/main/frontend/common/components/dropdown.tsx

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,17 @@ import Tooltip from "./tooltip.tsx";
88
*/
99
export default function Dropdown({
1010
items,
11+
tooltip = "More actions",
1112
disabled,
1213
className,
14+
icon,
1315
}: DropdownProps) {
1416
const [visible, setVisible] = useState(false);
1517
const show = () => setVisible(true);
1618
const hide = () => setVisible(false);
1719

1820
return (
19-
<Tooltip content={"More actions"}>
21+
<Tooltip content={tooltip}>
2022
<Tippy
2123
visible={visible}
2224
onClickOutside={hide}
@@ -66,11 +68,14 @@ export default function Dropdown({
6668
disabled={disabled}
6769
onClick={visible ? hide : show}
6870
>
69-
<div className="jenkins-overflow-button__ellipsis">
70-
<span />
71-
<span />
72-
<span />
73-
</div>
71+
<span className={"jenkins-visually-hidden"}>{tooltip}</span>
72+
{icon || (
73+
<div className="jenkins-overflow-button__ellipsis">
74+
<span />
75+
<span />
76+
<span />
77+
</div>
78+
)}
7479
</button>
7580
</Tippy>
7681
</Tooltip>
@@ -90,8 +95,10 @@ export const DefaultDropdownProps: TippyProps = {
9095

9196
interface DropdownProps {
9297
items: (DropdownItem | ReactElement | "separator")[];
98+
tooltip?: string;
9399
disabled?: boolean;
94100
className?: string;
101+
icon?: ReactNode;
95102
}
96103

97104
interface DropdownItem {

src/main/frontend/pipeline-console-view/pipeline-console/main/symbols.tsx renamed to src/main/frontend/common/components/symbols.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export const CONSOLE = (
4444
);
4545

4646
export const SETTINGS = (
47-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
47+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" aria-hidden>
4848
<path
4949
fill="none"
5050
stroke="currentColor"

src/main/frontend/common/i18n/i18n-provider.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ export const I18NProvider: FunctionComponent<I18NProviderProps> = ({
4646
);
4747
};
4848

49-
export function useMessages() {
50-
return useContext(I18NContext);
49+
export function useMessages(): Messages {
50+
const messages = useContext(I18NContext);
51+
if (!messages) {
52+
throw new Error("useI18N must be used within an I18NProvider");
53+
}
54+
return messages;
5155
}

src/main/frontend/common/i18n/messages.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,9 @@ export enum LocalizedMessageKey {
8181
start = "node.start",
8282
end = "node.end",
8383
changesSummary = "changes.summary",
84+
settings = "settings",
85+
showNames = "settings.showStageName",
86+
showDuration = "settings.showStageDuration",
8487
consoleNewTab = "console.newTab",
8588
}
8689

@@ -97,6 +100,9 @@ const DEFAULT_MESSAGES: ResourceBundle = {
97100
[LocalizedMessageKey.start]: "Start",
98101
[LocalizedMessageKey.end]: "End",
99102
[LocalizedMessageKey.changesSummary]: "{0} {0,choice,1#change|1<changes}",
103+
[LocalizedMessageKey.settings]: "Settings",
104+
[LocalizedMessageKey.showNames]: "Show stage names",
105+
[LocalizedMessageKey.showDuration]: "Show stage duration",
100106
[LocalizedMessageKey.consoleNewTab]: "View step as plain text",
101107
};
102108

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import { createContext, ReactNode, useContext, useState } from "react";
2+
3+
interface PipelineGraphViewPreferences {
4+
showNames: boolean;
5+
setShowNames: (val: boolean) => void;
6+
showDurations: boolean;
7+
setShowDurations: (val: boolean) => void;
8+
}
9+
10+
const defaultPreferences = {
11+
showNames: false,
12+
showDurations: false,
13+
};
14+
15+
const UserPreferencesContext = createContext<
16+
PipelineGraphViewPreferences | undefined
17+
>(undefined);
18+
19+
const makeKey = (setting: string) => `pgv-graph-view.${setting}`;
20+
21+
const parsePreferenceValue = <T,>(
22+
value: string | undefined,
23+
fallback: T,
24+
): T => {
25+
if (value === undefined) {
26+
return fallback;
27+
}
28+
if (typeof fallback === "boolean") {
29+
return (value === "true") as typeof fallback;
30+
}
31+
return value as unknown as T;
32+
};
33+
34+
const loadFromLocalStorage = <T,>(key: string, fallback: T): T => {
35+
try {
36+
const value = window.localStorage.getItem(key) ?? undefined;
37+
return parsePreferenceValue(value, fallback);
38+
} catch (e) {
39+
console.error(`Error loading localStorage key "${key}"`, e);
40+
}
41+
return fallback;
42+
};
43+
44+
const loadFromDOM = <T,>(key: string, fallback: T): T => {
45+
const preferencesModule = document.querySelector(
46+
"[data-module='user-preferences']",
47+
);
48+
const value =
49+
preferencesModule && "dataset" in preferencesModule
50+
? (preferencesModule as HTMLElement).dataset[key]
51+
: undefined;
52+
return parsePreferenceValue(value, fallback);
53+
};
54+
55+
export const UserPreferencesProvider = ({
56+
children,
57+
}: {
58+
children: ReactNode;
59+
}) => {
60+
const stageNamesKey = makeKey("stageNames");
61+
const stageDurationsKey = makeKey("stageDurations");
62+
63+
const [showNames, setShowNames] = useState<boolean>(
64+
loadFromLocalStorage(
65+
stageNamesKey,
66+
loadFromDOM("preferenceShowStageNames", defaultPreferences.showNames),
67+
),
68+
);
69+
const [showDurations, setShowDurations] = useState<boolean>(
70+
loadFromLocalStorage(
71+
stageDurationsKey,
72+
loadFromDOM(
73+
"preferenceShowStageDurations",
74+
defaultPreferences.showDurations,
75+
),
76+
),
77+
);
78+
79+
const persistShowNames = (val: boolean) => {
80+
window.localStorage.setItem(stageNamesKey, String(val));
81+
setShowNames(val);
82+
};
83+
84+
const persistShowDurations = (val: boolean) => {
85+
window.localStorage.setItem(stageDurationsKey, String(val));
86+
setShowDurations(val);
87+
};
88+
89+
return (
90+
<UserPreferencesContext.Provider
91+
value={{
92+
showNames,
93+
setShowNames: persistShowNames,
94+
showDurations,
95+
setShowDurations: persistShowDurations,
96+
}}
97+
>
98+
{children}
99+
</UserPreferencesContext.Provider>
100+
);
101+
};
102+
103+
export const useUserPreferences = (): PipelineGraphViewPreferences => {
104+
const context = useContext(UserPreferencesContext);
105+
if (!context) {
106+
throw new Error(
107+
"useMonitorPreferences must be used within a UserPreferencesProvider",
108+
);
109+
}
110+
return context;
111+
};

0 commit comments

Comments
 (0)