mirror of
https://github.com/haiwen/seahub.git
synced 2025-08-12 04:12:16 +00:00
[settings] redesigned the 'heading' bar; fixup for the side nav; impr… (#6250)
* [settings] redesigned the 'heading' bar; fixup for the side nav; improved UI for content headings * [settings] resigned it (the top bar, the side panel; the code frame)
This commit is contained in:
parent
a5edeafb74
commit
26bdd0decd
@ -4,22 +4,26 @@ import Logo from './logo';
|
|||||||
import MainSideNav from './main-side-nav';
|
import MainSideNav from './main-side-nav';
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
isSidePanelClosed: PropTypes.bool.isRequired,
|
isSidePanelClosed: PropTypes.bool,
|
||||||
currentTab: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
|
currentTab: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||||
onCloseSidePanel: PropTypes.func.isRequired,
|
onCloseSidePanel: PropTypes.func,
|
||||||
tabItemClick: PropTypes.func.isRequired,
|
tabItemClick: PropTypes.func,
|
||||||
|
children: PropTypes.object
|
||||||
};
|
};
|
||||||
|
|
||||||
class SidePanel extends React.Component {
|
class SidePanel extends React.Component {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const { children } = this.props;
|
||||||
return (
|
return (
|
||||||
<div className={`side-panel ${this.props.isSidePanelClosed ? '' : 'left-zero'}`}>
|
<div className={`side-panel ${this.props.isSidePanelClosed ? '' : 'left-zero'}`}>
|
||||||
<div className="side-panel-north">
|
<div className="side-panel-north">
|
||||||
<Logo onCloseSidePanel={this.props.onCloseSidePanel}/>
|
<Logo onCloseSidePanel={this.props.onCloseSidePanel}/>
|
||||||
</div>
|
</div>
|
||||||
<div className="side-panel-center">
|
<div className="side-panel-center">
|
||||||
<MainSideNav tabItemClick={this.props.tabItemClick} currentTab={this.props.currentTab} />
|
{children ? children :
|
||||||
|
<MainSideNav tabItemClick={this.props.tabItemClick} currentTab={this.props.currentTab} />
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -13,10 +13,11 @@ const propTypes = {
|
|||||||
path: PropTypes.string,
|
path: PropTypes.string,
|
||||||
repoName: PropTypes.string,
|
repoName: PropTypes.string,
|
||||||
isLibView: PropTypes.bool,
|
isLibView: PropTypes.bool,
|
||||||
onSearchedClick: PropTypes.func.isRequired,
|
onSearchedClick: PropTypes.func,
|
||||||
searchPlaceholder: PropTypes.string,
|
searchPlaceholder: PropTypes.string,
|
||||||
currentRepoInfo: PropTypes.object,
|
currentRepoInfo: PropTypes.object,
|
||||||
isViewFile: PropTypes.bool,
|
isViewFile: PropTypes.bool,
|
||||||
|
showSearch: PropTypes.bool
|
||||||
};
|
};
|
||||||
|
|
||||||
class CommonToolbar extends React.Component {
|
class CommonToolbar extends React.Component {
|
||||||
@ -62,9 +63,10 @@ class CommonToolbar extends React.Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const { showSearch = true } = this.props;
|
||||||
return (
|
return (
|
||||||
<div className="common-toolbar">
|
<div className="common-toolbar">
|
||||||
{this.renderSearch()}
|
{showSearch && this.renderSearch()}
|
||||||
<Notification />
|
<Notification />
|
||||||
<Account />
|
<Account />
|
||||||
{showLogoutIcon && (<Logout />)}
|
{showLogoutIcon && (<Logout />)}
|
||||||
|
@ -3,24 +3,29 @@ import PropTypes from 'prop-types';
|
|||||||
import CommonToolbar from './common-toolbar';
|
import CommonToolbar from './common-toolbar';
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
onShowSidePanel: PropTypes.func.isRequired,
|
onShowSidePanel: PropTypes.func,
|
||||||
onSearchedClick: PropTypes.func.isRequired,
|
onSearchedClick: PropTypes.func,
|
||||||
searchPlaceholder: PropTypes.string,
|
searchPlaceholder: PropTypes.string,
|
||||||
children: PropTypes.object
|
children: PropTypes.object,
|
||||||
|
showSearch: PropTypes.bool
|
||||||
};
|
};
|
||||||
|
|
||||||
class TopToolbar extends React.Component {
|
class TopToolbar extends React.Component {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { onShowSidePanel, onSearchedClick } = this.props;
|
const { onShowSidePanel, onSearchedClick, children, showSearch } = this.props;
|
||||||
return (
|
return (
|
||||||
<div className="main-panel-north border-left-show">
|
<div className={`main-panel-north ${children ? 'border-left-show' : ''}`}>
|
||||||
<div className="cur-view-toolbar">
|
<div className="cur-view-toolbar">
|
||||||
<span title="Side Nav Menu" onClick={onShowSidePanel} className="sf2-icon-menu side-nav-toggle hidden-md-up d-md-none">
|
<span title="Side Nav Menu" onClick={onShowSidePanel} className="sf2-icon-menu side-nav-toggle hidden-md-up d-md-none">
|
||||||
</span>
|
</span>
|
||||||
{this.props.children}
|
{this.props.children}
|
||||||
</div>
|
</div>
|
||||||
<CommonToolbar searchPlaceholder={this.props.searchPlaceholder} onSearchedClick={onSearchedClick} />
|
<CommonToolbar
|
||||||
|
showSearch={showSearch}
|
||||||
|
searchPlaceholder={this.props.searchPlaceholder}
|
||||||
|
onSearchedClick={onSearchedClick}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -225,7 +225,7 @@ class LinkedDevices extends Component {
|
|||||||
return (
|
return (
|
||||||
<div className="setting-item" id="linked-devices">
|
<div className="setting-item" id="linked-devices">
|
||||||
<h3 className="setting-item-heading">{gettext('Linked Devices')}</h3>
|
<h3 className="setting-item-heading">{gettext('Linked Devices')}</h3>
|
||||||
<div className="cur-view-content">
|
<div>
|
||||||
<Content
|
<Content
|
||||||
loading={loading}
|
loading={loading}
|
||||||
errorMsg={errorMsg}
|
errorMsg={errorMsg}
|
||||||
|
@ -4,12 +4,16 @@ import PropTypes from 'prop-types';
|
|||||||
class SideNav extends React.Component {
|
class SideNav extends React.Component {
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<ul className="nav flex-column user-setting-nav">
|
<div className="side-nav">
|
||||||
{this.props.data.map((item, index) => {
|
<div className="side-nav-con">
|
||||||
return item.show ?
|
<ul className="nav nav-pills flex-column">
|
||||||
<li key={index} className={`nav-item${this.props.curItemID == item.href.substr(1) ? ' active' : ''}`}><a className="nav-link" href={item.href}>{item.text}</a></li> : null;
|
{this.props.data.map((item, index) => {
|
||||||
})}
|
return item.show ?
|
||||||
</ul>
|
<li key={index} className={`nav-item${this.props.curItemID == item.href.substr(1) ? ' active' : ''}`}><a className={`nav-link${this.props.curItemID == item.href.substr(1) ? ' active' : ''}`} href={item.href}>{item.text}</a></li> : null;
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,52 +4,24 @@ body {
|
|||||||
#wrapper {
|
#wrapper {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
.top-header {
|
|
||||||
background: #f4f4f7;
|
.main-panel {
|
||||||
border-bottom: 1px solid #e8e8e8;
|
overflow: hidden;
|
||||||
padding: .5rem 1rem;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.side-panel {
|
|
||||||
flex: 0 0 22%;
|
|
||||||
padding: 1rem;
|
|
||||||
border-right: 1px solid #eee;
|
|
||||||
}
|
|
||||||
.main-panel {
|
|
||||||
flex: 1 0 78%;
|
|
||||||
}
|
|
||||||
.heading {
|
|
||||||
padding: 8px 16px;
|
|
||||||
background: #f9f9f9;
|
|
||||||
font-size: 1rem;
|
|
||||||
color: #212529;
|
|
||||||
font-weight: normal;
|
|
||||||
line-height: 1.5;
|
|
||||||
margin:0;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
.heading:after {
|
|
||||||
position: absolute;
|
|
||||||
left: 16px;
|
|
||||||
right: 16px;
|
|
||||||
bottom: 0;
|
|
||||||
content: '';
|
|
||||||
border-bottom: 1px solid #e8e8e8;
|
|
||||||
}
|
|
||||||
.content {
|
.content {
|
||||||
padding: 0rem 1rem 8rem;
|
padding: 0rem 1rem 8rem;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
.setting-item {
|
.setting-item {
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
margin: 1em 0 3em;
|
padding: 1em 0 3em;
|
||||||
}
|
}
|
||||||
.setting-item-heading {
|
.setting-item-heading {
|
||||||
font-size: 0.9375rem;
|
font-size: 0.9375rem;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
padding-bottom: 0.3rem;
|
padding-bottom: 0.3rem;
|
||||||
border-bottom: 1px solid #ddd;
|
border-bottom: 1px solid #eee;
|
||||||
margin-bottom: 0.7rem;
|
margin-bottom: 0.7rem;
|
||||||
}
|
}
|
||||||
.user-avatar {
|
.user-avatar {
|
||||||
@ -69,20 +41,6 @@ body {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.user-setting-nav .nav-item .nav-link {
|
|
||||||
flex: auto;
|
|
||||||
margin: 0;
|
|
||||||
padding-left: 1em;
|
|
||||||
border-left: 2px solid transparent;
|
|
||||||
color: #212529;
|
|
||||||
}
|
|
||||||
.user-setting-nav .nav-item.active .nav-link {
|
|
||||||
color: #ff9800;
|
|
||||||
border-color: #ff9800;
|
|
||||||
}
|
|
||||||
.user-setting-nav .nav-item .nav-link:hover {
|
|
||||||
color: #eb8205;
|
|
||||||
}
|
|
||||||
.eye-icon {
|
.eye-icon {
|
||||||
color: #666;
|
color: #666;
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDom from 'react-dom';
|
import ReactDom from 'react-dom';
|
||||||
import { navigate } from '@gatsbyjs/reach-router';
|
import MediaQuery from 'react-responsive';
|
||||||
|
import { Modal } from 'reactstrap';
|
||||||
import { Utils } from './utils/utils';
|
import { Utils } from './utils/utils';
|
||||||
import { isPro, isDBSqlite3, gettext, siteRoot, mediaUrl, logoPath, logoWidth, logoHeight, siteTitle } from './utils/constants';
|
import { isPro, isDBSqlite3, gettext, siteRoot } from './utils/constants';
|
||||||
import { seafileAPI } from './utils/seafile-api';
|
import { seafileAPI } from './utils/seafile-api';
|
||||||
import toaster from './components/toast';
|
import toaster from './components/toast';
|
||||||
import CommonToolbar from './components/toolbar/common-toolbar';
|
import SidePanel from './components/side-panel';
|
||||||
|
import MainPanel from './components/main-panel';
|
||||||
|
import TopToolbar from './components/toolbar/top-toolbar';
|
||||||
import SideNav from './components/user-settings/side-nav';
|
import SideNav from './components/user-settings/side-nav';
|
||||||
import UserAvatarForm from './components/user-settings/user-avatar-form';
|
import UserAvatarForm from './components/user-settings/user-avatar-form';
|
||||||
import UserBasicInfoForm from './components/user-settings/user-basic-info-form';
|
import UserBasicInfoForm from './components/user-settings/user-basic-info-form';
|
||||||
@ -21,6 +24,7 @@ import SocialLoginSAML from './components/user-settings/social-login-saml';
|
|||||||
import LinkedDevices from './components/user-settings/linked-devices';
|
import LinkedDevices from './components/user-settings/linked-devices';
|
||||||
import DeleteAccount from './components/user-settings/delete-account';
|
import DeleteAccount from './components/user-settings/delete-account';
|
||||||
|
|
||||||
|
import './css/layout.css';
|
||||||
import './css/toolbar.css';
|
import './css/toolbar.css';
|
||||||
import './css/search.css';
|
import './css/search.css';
|
||||||
|
|
||||||
@ -59,6 +63,7 @@ class Settings extends React.Component {
|
|||||||
];
|
];
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
|
isSidePanelClosed: false,
|
||||||
curItemID: this.sideNavItems[0].href.substr(1)
|
curItemID: this.sideNavItems[0].href.substr(1)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -86,17 +91,6 @@ class Settings extends React.Component {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
onSearchedClick = (selectedItem) => {
|
|
||||||
if (selectedItem.is_dir === true) {
|
|
||||||
let url = siteRoot + 'library/' + selectedItem.repo_id + '/' + selectedItem.repo_name + selectedItem.path;
|
|
||||||
navigate(url, {repalce: true});
|
|
||||||
} else {
|
|
||||||
let url = siteRoot + 'lib/' + selectedItem.repo_id + '/file' + Utils.encodePath(selectedItem.path);
|
|
||||||
let newWindow = window.open('about:blank');
|
|
||||||
newWindow.location.href = url;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
handleContentScroll = (e) => {
|
handleContentScroll = (e) => {
|
||||||
const scrollTop = e.target.scrollTop;
|
const scrollTop = e.target.scrollTop;
|
||||||
const scrolled = this.sideNavItems.filter((item, index) => {
|
const scrolled = this.sideNavItems.filter((item, index) => {
|
||||||
@ -109,50 +103,79 @@ class Settings extends React.Component {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onCloseSidePanel = () => {
|
||||||
|
this.setState({
|
||||||
|
isSidePanelClosed: !this.state.isSidePanelClosed
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
onShowSidePanel = () => {
|
||||||
|
this.setState({
|
||||||
|
isSidePanelClosed: !this.state.isSidePanelClosed
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
toggleSidePanel = () => {
|
||||||
|
this.setState({
|
||||||
|
isSidePanelClosed: !this.state.isSidePanelClosed
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const { isSidePanelClosed } = this.state;
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<div className="h-100 d-flex flex-column">
|
<div id="main" className="h-100">
|
||||||
<div className="top-header d-flex justify-content-between">
|
<SidePanel
|
||||||
<a href={siteRoot}>
|
isSidePanelClosed={isSidePanelClosed}
|
||||||
<img src={mediaUrl + logoPath} height={logoHeight} width={logoWidth} title={siteTitle} alt="logo" />
|
onCloseSidePanel={this.onCloseSidePanel}
|
||||||
</a>
|
>
|
||||||
<CommonToolbar onSearchedClick={this.onSearchedClick} />
|
<SideNav data={this.sideNavItems} curItemID={this.state.curItemID} />
|
||||||
</div>
|
</SidePanel>
|
||||||
<div className="flex-auto d-flex o-hidden">
|
<MainPanel>
|
||||||
<div className="side-panel o-auto">
|
<>
|
||||||
<SideNav data={this.sideNavItems} curItemID={this.state.curItemID} />
|
<TopToolbar
|
||||||
</div>
|
onShowSidePanel={this.onShowSidePanel}
|
||||||
<div className="main-panel d-flex flex-column">
|
showSearch={false}
|
||||||
<h2 className="heading">{gettext('Settings')}</h2>
|
>
|
||||||
<div className="content position-relative" onScroll={this.handleContentScroll}>
|
</TopToolbar>
|
||||||
<div id="user-basic-info" className="setting-item">
|
<div className="main-panel-center flex-row">
|
||||||
<h3 className="setting-item-heading">{gettext('Profile Setting')}</h3>
|
<div className="cur-view-container">
|
||||||
<UserAvatarForm />
|
<div className="cur-view-path">
|
||||||
{this.state.userInfo && <UserBasicInfoForm userInfo={this.state.userInfo} updateUserInfo={this.updateUserInfo} />}
|
<h3 className="sf-heading m-0">{gettext('Settings')}</h3>
|
||||||
|
</div>
|
||||||
|
<div className="cur-view-content content position-relative" onScroll={this.handleContentScroll}>
|
||||||
|
<div id="user-basic-info" className="setting-item">
|
||||||
|
<h3 className="setting-item-heading">{gettext('Profile Setting')}</h3>
|
||||||
|
<UserAvatarForm />
|
||||||
|
{this.state.userInfo && <UserBasicInfoForm userInfo={this.state.userInfo} updateUserInfo={this.updateUserInfo} />}
|
||||||
|
</div>
|
||||||
|
{canUpdatePassword &&
|
||||||
|
<div id="update-user-passwd" className="setting-item">
|
||||||
|
<h3 className="setting-item-heading">{gettext('Password')}</h3>
|
||||||
|
<a href={`${siteRoot}accounts/password/change/`} className="btn btn-outline-primary">{passwordOperationText}</a>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
{enableGetAuthToken && <WebAPIAuthToken />}
|
||||||
|
{enableWebdavSecret && <WebdavPassword />}
|
||||||
|
{enableAddressBook && this.state.userInfo &&
|
||||||
|
<ListInAddressBook userInfo={this.state.userInfo} updateUserInfo={this.updateUserInfo} />}
|
||||||
|
<LanguageSetting />
|
||||||
|
{(isPro || !isDBSqlite3) && <EmailNotice />}
|
||||||
|
{twoFactorAuthEnabled && <TwoFactorAuthentication />}
|
||||||
|
{enableWechatWork && <SocialLogin />}
|
||||||
|
{enableDingtalk && <SocialLoginDingtalk />}
|
||||||
|
{(enableADFS || (enableMultiADFS && isOrgContext)) && <SocialLoginSAML />}
|
||||||
|
<LinkedDevices />
|
||||||
|
{enableDeleteAccount && <DeleteAccount />}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{canUpdatePassword &&
|
|
||||||
<div id="update-user-passwd" className="setting-item">
|
|
||||||
<h3 className="setting-item-heading">{gettext('Password')}</h3>
|
|
||||||
<a href={`${siteRoot}accounts/password/change/`} className="btn btn-outline-primary">{passwordOperationText}</a>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
{enableGetAuthToken && <WebAPIAuthToken />}
|
|
||||||
{enableWebdavSecret && <WebdavPassword />}
|
|
||||||
{enableAddressBook && this.state.userInfo &&
|
|
||||||
<ListInAddressBook userInfo={this.state.userInfo} updateUserInfo={this.updateUserInfo} />}
|
|
||||||
<LanguageSetting />
|
|
||||||
{(isPro || !isDBSqlite3) && <EmailNotice />}
|
|
||||||
{twoFactorAuthEnabled && <TwoFactorAuthentication />}
|
|
||||||
{enableWechatWork && <SocialLogin />}
|
|
||||||
{enableDingtalk && <SocialLoginDingtalk />}
|
|
||||||
{(enableADFS || (enableMultiADFS && isOrgContext)) && <SocialLoginSAML />}
|
|
||||||
<LinkedDevices />
|
|
||||||
{enableDeleteAccount && <DeleteAccount />}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</>
|
||||||
</div>
|
</MainPanel>
|
||||||
|
<MediaQuery query="(max-width: 767.8px)">
|
||||||
|
<Modal zIndex="1030" isOpen={!isSidePanelClosed} toggle={this.toggleSidePanel} contentClassName="d-none"></Modal>
|
||||||
|
</MediaQuery>
|
||||||
</div>
|
</div>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
|
Loading…
Reference in New Issue
Block a user