Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: cache the url to keep the page live #2969

Merged
merged 7 commits into from
May 11, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 4 additions & 102 deletions Composer/packages/client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { ErrorBoundary } from './components/ErrorBoundary';
import { RequireAuth } from './components/RequireAuth';
import onboardingState from './utils/onboardingStorage';
import { isElectron } from './utils/electronUtil';
import { useLinks } from './utils/hooks';

initializeIcons(undefined, { disableWarnings: true });

Expand All @@ -29,111 +30,13 @@ const AppUpdater = React.lazy(() => import('./components/AppUpdater').then(modul
// eslint-disable-next-line react/display-name
const Content = forwardRef<HTMLDivElement>((props, ref) => <div css={content} {...props} ref={ref} />);

const topLinks = (projectId: string, openedDialogId: string) => {
const botLoaded = !!projectId;
let links = [
{
to: '/home',
iconName: 'Home',
labelName: formatMessage('Home'),
exact: true,
disabled: false,
},
{
to: `/bot/${projectId}/dialogs/${openedDialogId}`,
iconName: 'SplitObject',
labelName: formatMessage('Design Flow'),
exact: false,
disabled: !botLoaded,
},
// {
// to: '/test-conversation',
// iconName: 'WaitListConfirm',
// labelName: formatMessage('Test Conversation'),
// exact: false,
// disabled: true, // will delete
// },
{
to: `/bot/${projectId}/language-generation`,
iconName: 'Robot',
labelName: formatMessage('Bot Responses'),
exact: false,
disabled: !botLoaded,
},
{
to: `/bot/${projectId}/language-understanding`,
iconName: 'People',
labelName: formatMessage('User Input'),
exact: false,
disabled: !botLoaded,
},
// {
// to: '/evaluate-performance',
// iconName: 'Chart',
// labelName: formatMessage('Evaluate performance'),
// exact: false,
// disabled: true,
// },
{
to: `/bot/${projectId}/notifications`,
iconName: 'Warning',
labelName: formatMessage('Notifications'),
exact: true,
disabled: !botLoaded,
},
{
to: `/bot/${projectId}/publish`,
iconName: 'CloudUpload',
labelName: formatMessage('Publish'),
exact: true,
disabled: !botLoaded,
},
{
to: `/bot/${projectId}/skills`,
iconName: 'PlugDisconnected',
labelName: formatMessage('Skills'),
exact: true,
disabled: !botLoaded,
},
{
to: `/bot/${projectId}/settings/`,
iconName: 'Settings',
labelName: formatMessage('Settings'),
exact: false,
disabled: !botLoaded,
},
];

if (process.env.COMPOSER_AUTH_PROVIDER === 'abs-h') {
links = links.filter(link => link.to !== '/home');
}

return links;
};

const bottomLinks = [
// {
// to: '/help',
// iconName: 'unknown',
// labelName: formatMessage('Info'),
// exact: true,
// disabled: true,
// },
{
to: '/about',
iconName: 'info',
labelName: formatMessage('About'),
exact: true,
disabled: false,
},
];

export const App: React.FC = () => {
const { actions, state } = useContext(StoreContext);
const [sideBarExpand, setSideBarExpand] = useState(false);

const { onboardingSetComplete } = actions;
const { botName, projectId, dialogs, locale, designPageLocation, announcement } = state;
const { botName, locale, announcement } = state;
const { topLinks, bottomLinks } = useLinks();

useEffect(() => {
onboardingSetComplete(onboardingState.getComplete());
Expand All @@ -143,7 +46,6 @@ export const App: React.FC = () => {

const renderAppUpdater = isElectron();

const openedDialogId = designPageLocation.dialogId || dialogs.find(({ isRoot }) => isRoot === true)?.id || 'Main';
return (
<Fragment>
<div
Expand Down Expand Up @@ -176,7 +78,7 @@ export const App: React.FC = () => {
/>
<div css={dividerTop} />{' '}
<FocusZone allowFocusRoot={true}>
{topLinks(projectId, openedDialogId).map((link, index) => {
{topLinks.map((link, index) => {
return (
<NavItem
key={'NavLeftBar' + index}
Expand Down
7 changes: 5 additions & 2 deletions Composer/packages/client/src/components/NavItem/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { Link } from '@reach/router';
import { Icon } from 'office-ui-fabric-react/lib/Icon';

import { StoreContext } from '../../store';
import { useLocation } from '../../utils/hooks';
import { useLocation, useRouterCache } from '../../utils/hooks';

import { link, icon } from './styles';

Expand Down Expand Up @@ -36,6 +36,9 @@ export const NavItem: React.FC<INavItemProps> = props => {
const {
location: { pathname },
} = useLocation();

const linkTo = useRouterCache(to);

const active = pathname.startsWith(to);

const addRef = useCallback(ref => onboardingAddCoachMarkRef({ [`nav${labelName.replace(' ', '')}`]: ref }), []);
Expand All @@ -61,7 +64,7 @@ export const NavItem: React.FC<INavItemProps> = props => {
return (
<Link
data-testid={'LeftNav-CommandBarButton' + labelName}
to={to}
to={linkTo}
aria-disabled={disabled}
aria-label={labelName + (active ? '; selected' : '')}
ref={addRef}
Expand Down
11 changes: 6 additions & 5 deletions Composer/packages/client/src/pages/design/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ const getTabFromFragment = () => {
const DesignPage: React.FC<RouteComponentProps<{ dialogId: string; projectId: string }>> = props => {
const { state, actions } = useContext(StoreContext);
const visualPanelRef: React.RefObject<HTMLDivElement> = useRef<HTMLDivElement>(null);
const { dialogs, designPageLocation, breadcrumb, visualEditorSelection, projectId, schemas, focusPath } = state;
const { dialogs, breadcrumb, visualEditorSelection, projectId, schemas, focusPath } = state;
const {
removeDialog,
setDesignPageLocation,
Expand All @@ -96,8 +96,9 @@ const DesignPage: React.FC<RouteComponentProps<{ dialogId: string; projectId: st
clearUndoHistory,
onboardingAddCoachMarkRef,
} = actions;
const { location } = props;
const { dialogId, selected } = designPageLocation;
const { location, dialogId } = props;
const params = new URLSearchParams(location?.search);
const selected = params.get('selected') || '';
const [triggerModalVisible, setTriggerModalVisibility] = useState(false);
const [dialogJsonVisible, setDialogJsonVisibility] = useState(false);
const [currentDialog, setCurrentDialog] = useState<DialogInfo>(dialogs[0]);
Expand All @@ -120,10 +121,10 @@ const DesignPage: React.FC<RouteComponentProps<{ dialogId: string; projectId: st

useEffect(() => {
const index = currentDialog.triggers.findIndex(({ type }) => type === SDKKinds.OnBeginDialog);
if (index >= 0) {
if (index >= 0 && !location?.search) {
selectTo(createSelectedPath(index));
}
}, [currentDialog?.id]);
}, [currentDialog?.id, location]);

useEffect(() => {
if (location && props.dialogId && props.projectId) {
Expand Down
37 changes: 36 additions & 1 deletion Composer/packages/client/src/utils/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import { useState, useEffect } from 'react';
import { useState, useEffect, useContext, useRef } from 'react';
import { globalHistory } from '@reach/router';
import replace from 'lodash/replace';
import find from 'lodash/find';

import { bottomLinks, topLinks } from './pageLinks';
import { StoreContext } from './../store';
import routerCache from './routerCache';

export const useLocation = () => {
const { location, navigate } = globalHistory;
Expand All @@ -12,3 +18,32 @@ export const useLocation = () => {

return state;
};

export const useLinks = () => {
const { state } = useContext(StoreContext);
const { projectId, dialogs, designPageLocation } = state;
const openedDialogId = designPageLocation.dialogId || dialogs.find(({ isRoot }) => isRoot === true)?.id || 'Main';

return { topLinks: topLinks(projectId, openedDialogId), bottomLinks };
};

export const useRouterCache = (to: string) => {
const [state, setState] = useState(routerCache.getAll());
const { topLinks, bottomLinks } = useLinks();
const linksRef = useRef(topLinks.concat(bottomLinks));
linksRef.current = topLinks.concat(bottomLinks);
useEffect(() => {
globalHistory.listen(({ location }) => {
const links = linksRef.current;
const { href, origin } = location;
const uri = replace(href, origin, '');
const target = find(links, link => uri.startsWith(link.to));
if (target) {
routerCache.set(target.to, uri);
setState(routerCache.getAll());
}
});
}, []);

return state[to] || to;
};
81 changes: 81 additions & 0 deletions Composer/packages/client/src/utils/pageLinks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import formatMessage from 'format-message';

export const topLinks = (projectId: string, openedDialogId: string) => {
const botLoaded = !!projectId;
let links = [
{
to: '/home',
iconName: 'Home',
labelName: formatMessage('Home'),
exact: true,
disabled: false,
},
{
to: `/bot/${projectId}/dialogs/${openedDialogId}`,
iconName: 'SplitObject',
labelName: formatMessage('Design Flow'),
exact: false,
disabled: !botLoaded,
},
{
to: `/bot/${projectId}/language-generation`,
iconName: 'Robot',
labelName: formatMessage('Bot Responses'),
exact: false,
disabled: !botLoaded,
},
{
to: `/bot/${projectId}/language-understanding`,
iconName: 'People',
labelName: formatMessage('User Input'),
exact: false,
disabled: !botLoaded,
},
{
to: `/bot/${projectId}/notifications`,
iconName: 'Warning',
labelName: formatMessage('Notifications'),
exact: true,
disabled: !botLoaded,
},
{
to: `/bot/${projectId}/publish`,
iconName: 'CloudUpload',
labelName: formatMessage('Publish'),
exact: true,
disabled: !botLoaded,
},
{
to: `/bot/${projectId}/skills`,
iconName: 'PlugDisconnected',
labelName: formatMessage('Skills'),
exact: true,
disabled: !botLoaded,
},
{
to: `/bot/${projectId}/settings/`,
iconName: 'Settings',
labelName: formatMessage('Settings'),
exact: false,
disabled: !botLoaded,
},
];

if (process.env.COMPOSER_AUTH_PROVIDER === 'abs-h') {
links = links.filter(link => link.to !== '/home');
}

return links;
};

export const bottomLinks = [
{
to: '/about',
iconName: 'info',
labelName: formatMessage('About'),
exact: true,
disabled: false,
},
];
30 changes: 30 additions & 0 deletions Composer/packages/client/src/utils/routerCache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import { ClientStorage } from './storage';

const KEY = 'RouterCache';

class RouterCache {
private storage: ClientStorage;
private _all;

constructor() {
this.storage = new ClientStorage(window.sessionStorage);
this._all = this.storage.get(KEY, {});
}

get(to: string) {
return this._all[to] || {};
}

getAll() {
return this._all;
}

set(linkTo: string, uri: string) {
this._all[linkTo] = uri;
this.storage.set(KEY, this._all);
}
}

export default new RouterCache();