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

Typescriptize MainHeader component #4535

Merged
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions jsapp/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import 'js/surveyCompanionStore'; // importing it so it exists
import {} from 'js/bemComponents'; // importing it so it exists
import bem from 'js/bem';
import mixins from 'js/mixins';
import MainHeader from 'js/components/header/mainHeader';
import MainHeader from 'js/components/header/mainHeader.component';
import Drawer from 'js/components/drawer';
import FormViewSideTabs from 'js/components/formViewSideTabs';
import ProjectTopTabs from 'js/project/projectTopTabs.component';
Expand Down Expand Up @@ -87,7 +87,7 @@ class App extends React.Component {

{!this.isFormBuilder() && (
<React.Fragment>
<MainHeader assetid={assetid} />
<MainHeader assetUid={assetid} />
<Drawer />
</React.Fragment>
)}
Expand Down
33 changes: 17 additions & 16 deletions jsapp/js/assetUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import {
getSupplementalTranslationPath,
} from 'js/components/processing/processingUtils';
import type {LanguageCode} from 'js/components/languages/languagesStore';
import type {IconName} from 'jsapp/fonts/k-icons';

/**
* Removes whitespace from tags. Returns list of cleaned up tags.
Expand Down Expand Up @@ -266,43 +267,43 @@ export function isAssetPublic(permissions?: Permission[]) {
}

/**
* For getting the icon class name for given asset type. Returned string always
* contains two class names: base `k-icon` and respective CSS class name.
* For getting the icon name for given asset type. Recommended to be used with
* the `<Icon>` component.
*/
export function getAssetIcon(asset: AssetResponse) {
export function getAssetIcon(asset: AssetResponse): IconName {
switch (asset.asset_type) {
case ASSET_TYPES.template.id:
if ('summary' in asset && asset.summary?.lock_any) {
return 'k-icon k-icon-template-locked';
return 'template-locked';
} else {
return 'k-icon k-icon-template';
return 'template';
}
case ASSET_TYPES.question.id:
return 'k-icon k-icon-question';
return 'question';
case ASSET_TYPES.block.id:
return 'k-icon k-icon-block';
return 'block';
case ASSET_TYPES.survey.id:
if ('summary' in asset && asset.summary?.lock_any) {
return 'k-icon k-icon-project-locked';
return 'project-locked';
} else if (asset.deployment_status === 'archived') {
return 'k-icon k-icon-project-archived';
return 'project-archived';
} else if (asset.deployment_status === 'deployed') {
return 'k-icon k-icon-project-deployed';
return 'project-deployed';
} else {
return 'k-icon k-icon-project-draft';
return 'project-draft';
}
case ASSET_TYPES.collection.id:
if ('access_types' in asset && asset?.access_types?.includes(ACCESS_TYPES.subscribed)) {
return 'k-icon k-icon-folder-subscribed';
return 'folder-subscribed';
} else if (isAssetPublic(asset.permissions)) {
return 'k-icon k-icon-folder-public';
return 'folder-public';
} else if (asset?.access_types?.includes(ACCESS_TYPES.shared)) {
return 'k-icon k-icon-folder-shared';
return 'folder-shared';
} else {
return 'k-icon k-icon-folder';
return 'folder';
}
default:
return 'k-icon k-icon-project';
return 'project';
}
}

Expand Down
6 changes: 0 additions & 6 deletions jsapp/js/bemComponents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,12 +154,6 @@ bem.FormView__map = makeBem(bem.FormView, 'map');
bem.FormView__mapButton = makeBem(bem.FormView, 'map-button');
bem.FormView__mapList = makeBem(bem.FormView, 'map-list');

bem.MainHeader = makeBem(null, 'main-header', 'header');
bem.MainHeader__icon = makeBem(bem.MainHeader, 'icon', 'i');
bem.MainHeader__title = makeBem(bem.MainHeader, 'title');
bem.MainHeader__counter = makeBem(bem.MainHeader, 'counter');


bem.ReportView = makeBem(null, 'report-view');
bem.ReportView__wrap = makeBem(bem.ReportView, 'wrap');
bem.ReportView__item = makeBem(bem.ReportView, 'item');
Expand Down
8 changes: 2 additions & 6 deletions jsapp/js/components/assetsTable/assetsTableRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {ASSET_TYPES} from 'js/constants';
import assetUtils from 'js/assetUtils';
import type {AssetsTableContextName} from './assetsTableConstants';
import {ASSETS_TABLE_CONTEXTS} from './assetsTableConstants';
import Icon from 'js/components/common/icon';

interface AssetsTableRowProps {
asset: AssetResponse;
Expand All @@ -16,11 +17,6 @@ interface AssetsTableRowProps {

class AssetsTableRow extends React.Component<AssetsTableRowProps> {
render() {
let iconClassName = '';
if (this.props.asset) {
iconClassName = assetUtils.getAssetIcon(this.props.asset);
}

let rowCount = null;
if (
this.props.asset.asset_type !== ASSET_TYPES.collection.id &&
Expand All @@ -43,7 +39,7 @@ class AssetsTableRow extends React.Component<AssetsTableRowProps> {
</bem.AssetsTableRow__buttons>

<bem.AssetsTableRow__column m='icon-status'>
<i className={`k-icon ${iconClassName}`}/>
<Icon name={assetUtils.getAssetIcon(this.props.asset)} size='l' />
</bem.AssetsTableRow__column>

<bem.AssetsTableRow__column m='name'>
Expand Down
188 changes: 188 additions & 0 deletions jsapp/js/components/header/mainHeader.component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
import React from 'react';
import {observer} from 'mobx-react';
import {stores} from 'js/stores';
import sessionStore from 'js/stores/session';
import assetStore from 'js/assetStore';
import bem, {makeBem} from 'js/bem';
import {
getLoginUrl,
isAnyFormRoute,
isAnyProjectsViewRoute,
isMyLibraryRoute,
isPublicCollectionsRoute,
} from 'js/router/routerUtils';
import {getAssetIcon} from 'js/assetUtils';
import HeaderTitleEditor from 'js/components/header/headerTitleEditor';
import SearchBox from 'js/components/header/searchBox';
import myLibraryStore from 'js/components/library/myLibraryStore';
import {userCan} from 'js/components/permissions/utils';
import AccountMenu from './accountMenu';
import type {AssetResponse} from 'js/dataInterface';
import {withRouter, router} from 'js/router/legacy';
import type {WithRouterProps} from 'js/router/legacy';
import Icon from 'js/components/common/icon';
import type {IconName} from 'jsapp/fonts/k-icons';

bem.MainHeader = makeBem(null, 'main-header', 'header');
bem.MainHeader__icon = makeBem(bem.MainHeader, 'icon');
bem.MainHeader__title = makeBem(bem.MainHeader, 'title');
bem.MainHeader__counter = makeBem(bem.MainHeader, 'counter');

interface MainHeaderProps extends WithRouterProps {
assetUid: string | null;
}

const MainHeader = class MainHeader extends React.Component<MainHeaderProps> {
private unlisteners: Function[] = [];

componentDidMount() {
// HACK: re-rendering this every time we navigate is not perfect. We need to
// come up with a better solution.
router!.subscribe(() => this.forceUpdate());

// Without much refactor, we ensure that the header re-renders itself,
// whenever any linked store changes.
this.unlisteners.push(
assetStore.listen(() => this.forceUpdate(), this),
myLibraryStore.listen(() => this.forceUpdate(), this)
);
}

componentWillUnmount() {
this.unlisteners.forEach((clb) => {
clb();
});
}

isSearchBoxDisabled() {
if (isMyLibraryRoute()) {
// disable search when user has zero assets
return myLibraryStore.getCurrentUserTotalAssets() === null;
} else {
return false;
}
}

renderLoginButton() {
return (
<bem.LoginBox>
<a href={getLoginUrl()} className='kobo-button kobo-button--blue'>
{t('Log In')}
</a>
</bem.LoginBox>
);
}

renderGitRevInfo() {
if (
'git_rev' in sessionStore.currentAccount &&
sessionStore.currentAccount.git_rev.branch &&
sessionStore.currentAccount.git_rev.short
) {
return (
<bem.GitRev>
<bem.GitRev__item>
branch: {sessionStore.currentAccount.git_rev.branch}
</bem.GitRev__item>
<bem.GitRev__item>
commit: {sessionStore.currentAccount.git_rev.short}
</bem.GitRev__item>
</bem.GitRev>
);
}

return false;
}

toggleFixedDrawer() {
stores.pageState.toggleFixedDrawer();
}

render() {
const isLoggedIn = sessionStore.isLoggedIn;

let asset: AssetResponse | undefined;
if (this.props.assetUid) {
asset = assetStore.getAsset(this.props.assetUid);
}

let userCanEditAsset = false;
if (asset) {
userCanEditAsset = userCan('change_asset', asset);
}

let iconName: IconName | undefined;
if (asset) {
iconName = getAssetIcon(asset);
}

let librarySearchBoxPlaceholder = t('Search My Library');
if (isPublicCollectionsRoute()) {
librarySearchBoxPlaceholder = t('Search Public Collections');
}

return (
<bem.MainHeader className='mdl-layout__header'>
<div className='mdl-layout__header-row'>
{sessionStore.isLoggedIn && (
<bem.Button m='icon' onClick={this.toggleFixedDrawer}>
<i className='k-icon k-icon-menu' />
</bem.Button>
)}

<span className='mdl-layout__title'>
<a href='/'>
<bem.Header__logo />
</a>
</span>

{/* Things for Library */}
{isLoggedIn && (isMyLibraryRoute() || isPublicCollectionsRoute()) && (
<div className='mdl-layout__header-searchers'>
<SearchBox
placeholder={librarySearchBoxPlaceholder}
disabled={this.isSearchBoxDisabled()}
/>
</div>
)}

{/* Things for My Projects and any Custom View */}
{isLoggedIn && isAnyProjectsViewRoute() && (
<div className='mdl-layout__header-searchers'>
<SearchBox
placeholder={t('Search…')}
disabled={this.isSearchBoxDisabled()}
/>
</div>
)}

{/* Things for Project */}
{asset && isAnyFormRoute() && (
<React.Fragment>
{iconName && (
<bem.MainHeader__icon>
<Icon name={iconName} />
</bem.MainHeader__icon>
)}

<HeaderTitleEditor asset={asset} isEditable={userCanEditAsset} />

{asset.has_deployment && (
<bem.MainHeader__counter>
{asset.deployment__submission_count} {t('submissions')}
</bem.MainHeader__counter>
)}
</React.Fragment>
)}

<AccountMenu />

{!isLoggedIn && this.renderLoginButton()}
</div>
{this.renderGitRevInfo()}
</bem.MainHeader>
);
}
};

export default observer(withRouter(MainHeader));