Skip to content

Commit 4b551c9

Browse files
[ui-storagebrowser] updates browser url while navigating (#3919)
* [ui-storagebrowser] updates browser url while navigating * adds condition to change url only when they are different
1 parent ad00b2e commit 4b551c9

File tree

3 files changed

+108
-24
lines changed

3 files changed

+108
-24
lines changed

desktop/core/src/desktop/js/apps/storageBrowser/StorageBrowserTab/StorageBrowserTab.tsx

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
// See the License for the specific language governing permissions and
1515
// limitations under the License.
1616

17-
import React, { useState } from 'react';
17+
import React, { useEffect, useState } from 'react';
1818
import { Spin } from 'antd';
1919

2020
import { i18nReact } from '../../../utils/i18nReact';
@@ -28,6 +28,7 @@ import useLoadData from '../../../utils/hooks/useLoadData';
2828

2929
import './StorageBrowserTab.scss';
3030
import StorageFilePage from '../StorageFilePage/StorageFilePage';
31+
import changeURL from '../../../utils/url/changeURL';
3132

3233
interface StorageBrowserTabProps {
3334
homeDir: string;
@@ -39,8 +40,9 @@ const defaultProps = {
3940
};
4041

4142
const StorageBrowserTab = ({ homeDir, testId }: StorageBrowserTabProps): JSX.Element => {
42-
const [filePath, setFilePath] = useState<string>(homeDir);
43-
const fileName = filePath?.split('/')?.pop() ?? '';
43+
const [urlPathname, urlFilePath] = decodeURIComponent(window.location.pathname).split('view=');
44+
const [filePath, setFilePath] = useState<string>(urlFilePath || homeDir);
45+
const fileName = filePath.split('/').pop() ?? '';
4446

4547
const { t } = i18nReact.useTranslation();
4648

@@ -51,6 +53,17 @@ const StorageBrowserTab = ({ homeDir, testId }: StorageBrowserTabProps): JSX.Ele
5153
skip: !filePath
5254
});
5355

56+
useEffect(() => {
57+
const encodedPath = `${urlPathname}view=${encodeURIComponent(filePath)}`;
58+
if (filePath && urlFilePath && filePath !== urlFilePath) {
59+
changeURL(encodedPath);
60+
}
61+
// if url path is correct but not encoded properly
62+
else if (encodedPath !== window.location.pathname) {
63+
changeURL(encodedPath, {}, true);
64+
}
65+
}, [filePath, urlPathname, urlFilePath, window.location.pathname]);
66+
5467
return (
5568
<Spin spinning={loading}>
5669
<div className="hue-storage-browser-tab-content" data-testid={testId}>

desktop/core/src/desktop/js/utils/url/changeURL.test.ts

Lines changed: 68 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,75 @@
1515
// limitations under the License.
1616

1717
import changeURL from './changeURL';
18+
import { hueWindow } from 'types/types';
1819

19-
describe('changeURL.ts', () => {
20-
it('should change completely the URL', () => {
21-
changeURL('/banana');
20+
describe('changeURL', () => {
21+
const baseUrl = 'https://www.gethue.com/hue';
22+
const mockPushState = jest.fn();
23+
const mockReplaceState = jest.fn();
2224

23-
expect(window.location.pathname).toEqual('/banana');
24-
changeURL('/jasmine');
25+
beforeAll(() => {
26+
global.window.history.pushState = mockPushState;
27+
global.window.history.replaceState = mockReplaceState;
28+
(global.window as hueWindow).HUE_BASE_URL = baseUrl;
29+
});
30+
31+
beforeEach(() => {
32+
jest.clearAllMocks();
33+
});
34+
35+
it('should append query params to the URL with pushState', () => {
36+
const newURL = '/new/path';
37+
const params = { key1: 'value1', key2: 2 };
38+
39+
changeURL(newURL, params);
40+
41+
expect(mockReplaceState).not.toHaveBeenCalled();
42+
expect(mockPushState).toHaveBeenCalledWith(null, '', `${baseUrl}/new/path?key1=value1&key2=2`);
43+
});
44+
45+
it('should append hash to the URL with pushState', () => {
46+
const newURL = '/new/path#section';
47+
const params = { key1: 'value1' };
48+
49+
changeURL(newURL, params);
50+
51+
expect(mockReplaceState).not.toHaveBeenCalled();
52+
expect(mockPushState).toHaveBeenCalledWith(null, '', `${baseUrl}/new/path?key1=value1#section`);
53+
});
54+
55+
it('should handle replaceState when isReplace is true', () => {
56+
const newURL = '/new/path';
57+
const params = { key1: 'value1' };
58+
59+
changeURL(newURL, params, true);
60+
61+
expect(mockPushState).not.toHaveBeenCalled();
62+
expect(mockReplaceState).toHaveBeenCalledWith(null, '', `${baseUrl}/new/path?key1=value1`);
63+
});
64+
65+
it('should not add the base URL if the newURL already starts with the HUE_BASE_URL', () => {
66+
const newURL = `${baseUrl}/new/path`;
67+
68+
changeURL(newURL, { key1: 'value1' });
69+
70+
expect(mockReplaceState).not.toHaveBeenCalled();
71+
expect(mockPushState).toHaveBeenCalledWith(null, '', `${baseUrl}/new/path?key1=value1`);
72+
});
73+
74+
it('should handle the absence of params correctly', () => {
75+
const newURL = '/new/path#section';
76+
77+
changeURL(newURL);
78+
79+
expect(mockReplaceState).not.toHaveBeenCalled();
80+
expect(mockPushState).toHaveBeenCalledWith(null, '', `${baseUrl}/new/path#section`);
81+
});
82+
83+
it('should not change the URL if the new URL is the same as the current URL with hash', () => {
84+
changeURL(baseUrl, {});
85+
86+
expect(mockReplaceState).not.toHaveBeenCalled();
87+
expect(mockPushState).not.toHaveBeenCalled();
2588
});
2689
});

desktop/core/src/desktop/js/utils/url/changeURL.ts

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,36 +16,44 @@
1616

1717
import { hueWindow } from 'types/types';
1818

19-
const changeURL = (newURL: string, params?: Record<string, string | number | boolean>): void => {
19+
const changeURL = (
20+
newURL: string,
21+
params?: Record<string, string | number | boolean>,
22+
isReplace?: boolean
23+
): void => {
2024
let extraSearch = '';
2125
if (params) {
22-
const newSearchKeys = Object.keys(params);
23-
if (newSearchKeys.length) {
24-
while (newSearchKeys.length) {
25-
const newKey = newSearchKeys.pop() || '';
26-
extraSearch += newKey + '=' + params[newKey];
27-
if (newSearchKeys.length) {
28-
extraSearch += '&';
29-
}
30-
}
31-
}
26+
const paramsToString = Object.fromEntries(
27+
Object.entries(params).map(([key, value]) => [key, String(value)])
28+
);
29+
extraSearch += new URLSearchParams(paramsToString).toString();
3230
}
3331

3432
const hashSplit = newURL.split('#');
3533
const hueBaseUrl = (<hueWindow>window).HUE_BASE_URL;
3634
const base =
3735
hueBaseUrl && hashSplit[0].length && hashSplit[0].indexOf(hueBaseUrl) !== 0 ? hueBaseUrl : '';
38-
let url = base + hashSplit[0];
36+
let newUrl = base + hashSplit[0];
3937
if (extraSearch) {
40-
url += (url.indexOf('?') === -1 ? '?' : '&') + extraSearch;
38+
newUrl += (newUrl.indexOf('?') === -1 ? '?' : '&') + extraSearch;
4139
}
4240
if (hashSplit.length > 1) {
4341
//the foldername may contain # , so create substring ignoring first #
44-
url += '#' + newURL.substring(newURL.indexOf('#') + 1);
42+
newUrl += '#' + newURL.substring(newURL.indexOf('#') + 1);
4543
} else if (window.location.hash) {
46-
url += window.location.hash;
44+
newUrl += window.location.hash;
45+
}
46+
47+
if (window.location.href === newUrl) {
48+
// If the URLs are the same, do nothing
49+
return;
50+
}
51+
52+
if (isReplace) {
53+
window.history.replaceState(null, '', newUrl);
54+
} else {
55+
window.history.pushState(null, '', newUrl);
4756
}
48-
window.history.pushState(null, '', url);
4957
};
5058

5159
export default changeURL;

0 commit comments

Comments
 (0)