2021SC@SDUSC
这一次的博客聚焦toolbar,也就是工具条。在以后的开发中可能会涉及到增加toolbar的功能,因此对toolbar组件的源码分析,以及这些UI界面对应的代码在项目的哪些文件中,这次需要搞清楚。
首先看最上边的bar:
如图:
let searchPlaceholder = this.props.searchPlaceholder || gettext('Search Files'); //预置搜索内容
return (
<div className="common-toolbar">
{isPro && (
<Search //搜索框
repoID={this.props.repoID} //仓库ID,用户在对应资料库下的搜索
placeholder={searchPlaceholder} //搜索内容
onSearchedClick={this.props.onSearchedClick} //事件函数
/>
)}
{this.props.isLibView && !isPro &&
<SearchByName
repoID={this.props.repoID}
repoName={this.props.repoName}
/>
}
<Notification /> //通知组件
<Account /> //用户组件
{showLogoutIcon && (<Logout />)} //功能图标
</div>
咱们上上此已经对Notification
组件和Account
组件进行过分析了,详情请见源码分析之seahub-frontend-components-Account。
这里简单看一下搜索组件:
在common-toolbar中,search作为子组件,接受了common-toolbar传过来的三个参数,分别是repo_ID,搜索内容,以及事件函数。在子组件search中,这三个参数的作用:
repo_ID确定要搜索的库:
onShowMore = () => {
let repoID = this.props.repoID;
let newValue = this.state.value;
let queryData = {
q: newValue,
search_repo: repoID ? repoID : 'all',
search_ftypes: 'all',
};
let params = '';
for (let key in queryData) {
params += key + '=' + queryData[key] + '&';
}
window.location = siteRoot + 'search/?' + params.slice(0, params.length - 1);
}
Placeholder作为搜索的关键词,在search组件的input框中发挥作用。
<input
type="text"
className="form-control search-input"
name="query"
placeholder={this.props.placeholder}
style={style}
value={this.state.value}
onFocus={this.onFocusHandler}
onChange={this.onChangeHandler}
autoComplete="off"
/>
{(this.state.isCloseShow && username) &&
<i className='search-icon-right input-icon-addon fas fa-external-link-alt search-icon-arrow'
onClick={this.onSearchPage}></i>
}
通过点击最后的icon,调用API的SearchPage
函数,实现搜索。查找结果被封装成了Search-result
组件。包括每一个文件的文件名,资料库名,文件链接,以及是否危险的标识。
<li className="search-result-item" onClick={this.onClickHandler}>
<img className={className} src={fileIconUrl} alt="" />
<div className="item-content">
<div className="item-name ellipsis">{item.name}</div>
<div className="item-link ellipsis">{item.repo_name}/{item.link_content}</div>
<div className="item-text ellipsis" dangerouslySetInnerHTML={{__html: item.content}}></div>
</div>
</li>
搜索的方式以及涉及到的算法都在服务端,这里先对前端的一些组件的实现以及位置做一下分析。后边分析后端的时候会留意搜索算法的。
下面看operation-toolbar
,这一部分对应的是进入每个库时,最上面一行的操作栏,如图:
这一部分对应了两种显示方式,每一种对应的事件函数不同。区分普通操作栏还是下拉操作栏的条件是:是否是客户端Utils.isDesktop()
group-toolbar
先看UI界面图:
看下面代码,除了基本的按钮,和icon图标以及对应的点击事件,我们还看到有一个span,用来决定是否显示侧边面板,疑惑的是这里并没有发现类似功能。然后就是最后看到了我们上面分析过的CommonToolbar,也就是搜索框。这一部分封装成群组工具栏,然后渲染到页面的指定位置。
return (
<div className="main-panel-north border-left-show">
<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>
{canAddGroup && (
<div className="operation">
<MediaQuery query="(min-width: 768px)">
<Button color="btn btn-secondary operation-item" onClick={this.props.toggleAddGroupModal}>
<i className="fas fa-plus-square text-secondary mr-1"></i>{gettext('New Group')}
</Button>
</MediaQuery>
<MediaQuery query="(max-width: 767.8px)">
<span className="sf2-icon-plus mobile-toolbar-icon" title={gettext('New Group')} onClick={this.props.toggleAddGroupModal}></span>
</MediaQuery>
</div>
)}
</div>
<CommonToolbar searchPlaceholder={this.props.searchPlaceholder} onSearchedClick={onSearchedClick}/>
</div>
);
invitation-toolbar
这是一个负责邀请用户进入group的工具组件。同样系统先判断是否是desktop,然后决定页面样式:
同样的,最下面仍有搜索框,并一起封装到指定工具栏处。
{Utils.isDesktop() ? (
<div className="operation">
<Button color="btn btn-secondary operation-item" onClick={toggleInvitePeopleDialog}>
<i className="fas fa-plus-square text-secondary mr-1"></i>{gettext('Invite Guest')}
</Button>
</div>
) : (
<span className="sf2-icon-plus mobile-toolbar-icon" title={gettext('Invite Guest')} onClick={toggleInvitePeopleDialog}></span>
)}
</div>
<CommonToolbar searchPlaceholder={this.props.searchPlaceholder} onSearchedClick={onSearchedClick}/>
</div>
);
下面是markdown-view-toolbar:这一部分没有分析的必要,因为海思的人太厉害了,将这一出写的让人五体投地,这就是标准的markdown编辑器了,如图:
这种实现的网页版的编辑器功能十分强大,基本上涵盖了文本编辑的大部分功能,还还以切换成普通版的类似于CSDN博客的编辑界面。
详细源码见github-markdown-view-toolbar
组件multiple-dir-operation-toolbar,主要是用户在文件未打开时的操作工具栏,可以实现文件的移动、复制、下载、删除等功能。
先锁定这部分内容对应的界面如图:
这部分的工具栏显示,必须要选中文件才会出现,代码实现如下:
(userPerm === 'rw' || userPerm === 'admin' || isCustomPermission)
也就是在用户有选中事件之后才会有该工具栏。
同时点击每个操作按钮会有相应的Dialog,这个在上次分析中有设计,由于dialog种类较多,以及大多数实现都极为相似,目的是为了避免不必要的url跳转。
代码截图如下:
repo-view-bar组件
对应于在资料库时,不管是在私人资料库还是在共享资料库,对应于最上面的操作工具栏。
UI界面如图:
这部分代码也比较简单
就是点击NewLibrary按钮后,会出现新建资料库的Dialog,点击More会以下拉菜单栏的方式出现删除资料库的按钮,点击删除按钮同样是触发一个Dialog。然后具体在Dialog中调用对应的接口实现库的增删。
view-mode-toolbar
对应于文件的三种查看模式:列表,方格,列。
这里是切换模式调用的函数,至于具体的switchViewMode是在view-mode-toolbar的父组件LibContentView.js中实现的。
switchViewMode = (e) => {
e.preventDefault();
let id = e.target.id;
if (id === this.props.currentMode) {
return;
}
this.props.switchViewMode(id);
}
//LibContentView中的实现。
switchViewMode = (mode) => {
if (mode === this.state.currentMode) {
return;
}
if (mode === 'detail') {
this.toggleDirentDetail();
return;
}
cookie.save('seafile_view_mode', mode);
let path = this.state.path;
if (this.state.currentMode === 'column' && this.state.isViewFile) {
path = Utils.getDirName(path);
this.setState({
path: path,
isViewFile: false,
});
let repoInfo = this.state.currentRepoInfo;
let url = siteRoot + 'library/' + repoInfo.repo_id + '/' + encodeURIComponent(repoInfo.repo_name) + Utils.encodePath(path);
window.history.pushState({url: url, path: path}, path, url);
}
if (mode === 'column') {
this.loadSidePanel(this.state.path);
}
this.isNeedUpdateHistoryState = false;
this.setState({currentMode: mode});
this.showDir(path);
}
列表布局:
方格布局:
列布局: