1
0
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:
llj 2024-06-25 21:48:57 +08:00 committed by GitHub
parent a5edeafb74
commit 26bdd0decd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 116 additions and 120 deletions

View File

@ -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>
); );

View File

@ -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 />)}

View File

@ -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>
); );
} }

View File

@ -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}

View File

@ -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>
); );
} }
} }

View File

@ -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;
} }

View File

@ -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>
); );