mirror of
https://github.com/haiwen/seahub.git
synced 2025-05-12 09:55:53 +00:00
Dtable homepage (#4120)
* dtable homepage * style * add search dtable * warning * logo url * dtable-logo
This commit is contained in:
parent
c34969ce4b
commit
77c7012907
frontend/src
css
pages/dtable
media/img
seahub/templates
@ -7,13 +7,6 @@
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.dtable-header .dtable-logo {
|
||||
padding-right: 6px;
|
||||
font-size: 36px;
|
||||
line-height: 36px;
|
||||
color: #FEAC74;
|
||||
}
|
||||
|
||||
.dtable-header .dtable-text {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@ -23,6 +16,10 @@
|
||||
|
||||
.dtable-side-nav {
|
||||
padding: 12px;
|
||||
flex: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.dtable-nav-title {
|
||||
@ -69,14 +66,17 @@
|
||||
}
|
||||
|
||||
.dtable-header .common-toolbar>div {
|
||||
padding-right: 1rem;
|
||||
cursor: pointer;
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
.dtable-center .cur-view-title {
|
||||
.dtable-center .cur-view-header {
|
||||
line-height: 50px;
|
||||
font-size: 1.5rem;
|
||||
padding-left: 12px;
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
.dtable-center .cur-view-header .cur-view-title {
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
@ -154,6 +154,7 @@
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
min-height: 0;
|
||||
}
|
||||
.workspace .table-item-container .table-item {
|
||||
width: 48%;
|
||||
@ -178,6 +179,7 @@
|
||||
width: 15%;
|
||||
height: 100%;
|
||||
text-align: right;
|
||||
position: relative;
|
||||
}
|
||||
.workspace .table-item .table-dropdown-menu .table-dropdown-menu-icon {
|
||||
width: 24px;
|
||||
|
@ -27,7 +27,7 @@ class DTableWorkspaceCommon extends React.Component {
|
||||
isShowSharedDialog: false,
|
||||
isShowAPITokenDialog: false,
|
||||
currentTable: null,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@ -138,7 +138,7 @@ class DTableWorkspaceCommon extends React.Component {
|
||||
render() {
|
||||
let { isDataLoading } = this.state;
|
||||
if (isDataLoading) {
|
||||
return <Loading />
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
let { workspace } = this.props;
|
||||
|
@ -51,11 +51,11 @@ class DTableWorkspaceShared extends React.Component {
|
||||
|
||||
render() {
|
||||
if (this.state.isDataLoading) {
|
||||
return <Loading />
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
if (!this.state.tableList.length) {
|
||||
return "";
|
||||
return '';
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -23,7 +23,7 @@ class MainPanelDTables extends React.Component {
|
||||
isWorkspaceListLoading: true,
|
||||
isSharedTableListLoading: true,
|
||||
isShowCreateDialog: false
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@ -89,7 +89,7 @@ class MainPanelDTables extends React.Component {
|
||||
render() {
|
||||
let { isWorkspaceListLoading, isSharedTableListLoading } = this.state;
|
||||
if (isWorkspaceListLoading || isSharedTableListLoading) {
|
||||
return <Loading />
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
let { workspaceList, sharedTableList } = this.state;
|
||||
@ -104,8 +104,10 @@ class MainPanelDTables extends React.Component {
|
||||
return (
|
||||
<Fragment>
|
||||
<div className="main-panel-center dtable-center">
|
||||
<div className="main-panel-container d-flex flex-1 flex-column">
|
||||
<div className="cur-view-title">DTable</div>
|
||||
<div className="cur-view-container d-flex flex-1 flex-column">
|
||||
<div className="cur-view-header">
|
||||
<div className="cur-view-title">DTable</div>
|
||||
</div>
|
||||
<div className="cur-view-content">
|
||||
{this.state.errorMsg &&
|
||||
<p className="error text-center">{this.state.errorMsg}</p>
|
||||
@ -115,7 +117,7 @@ class MainPanelDTables extends React.Component {
|
||||
<DTableWorkspaceCommon workspace={personalWorkspace} />
|
||||
<DTableWorkspaceShared tableList={sharedTableList} />
|
||||
{groupWorkspaceList.length > 0 && groupWorkspaceList.map((workspace, index) => {
|
||||
return (<DTableWorkspaceCommon key={index} workspace={workspace} />)
|
||||
return (<DTableWorkspaceCommon key={index} workspace={workspace} />);
|
||||
})}
|
||||
<button className="btn btn-secondary dtable-add-btn mb-4" onClick={this.onCreateTableToggle}>{gettext('New DTable')}</button>
|
||||
</Fragment>
|
||||
|
@ -1,10 +1,14 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Router } from '@reach/router'
|
||||
import { Router } from '@reach/router';
|
||||
import Notification from '../../components/common/notification';
|
||||
import Account from '../../components/common/account';
|
||||
import Search from './search/search';
|
||||
import MainPanelDTables from './main-panel-dtables';
|
||||
import MainPanelApps from './main-panel-apps';
|
||||
import MainPanelTempletes from './main-panel-templetes';
|
||||
|
||||
import '../../css/search.css';
|
||||
|
||||
const siteRoot = window.app.config.siteRoot;
|
||||
|
||||
@ -15,14 +19,25 @@ const propTypes = {
|
||||
|
||||
class MainPanel extends React.Component {
|
||||
|
||||
onSearchedClick = (item) => {
|
||||
let url = siteRoot + 'workspace/' + item.workspace_id + '/dtable/' + item.name + '/';
|
||||
window.open(url)
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
let searchPlaceholder = this.props.searchPlaceholder || 'Search Dtables';
|
||||
|
||||
return (
|
||||
<div className="main-panel">
|
||||
<div className="main-panel-north dtable-header">
|
||||
<div className="common-toolbar">
|
||||
<div className="search" title={'Search'}>Search</div>
|
||||
<div className="notification" title={'Notification'}>Notification</div>
|
||||
<div className="avatar" title={'Avatar'}></div>
|
||||
<Search
|
||||
placeholder={searchPlaceholder}
|
||||
onSearchedClick={this.onSearchedClick}
|
||||
/>
|
||||
<Notification />
|
||||
<Account />
|
||||
</div>
|
||||
</div>
|
||||
<Router className="reach-router">
|
||||
|
33
frontend/src/pages/dtable/search/search-result-item.js
Normal file
33
frontend/src/pages/dtable/search/search-result-item.js
Normal file
@ -0,0 +1,33 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const propTypes = {
|
||||
item: PropTypes.object.isRequired,
|
||||
onItemClickHandler: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
class SearchResultItem extends React.Component {
|
||||
|
||||
onClickHandler = () => {
|
||||
var item = this.props.item;
|
||||
this.props.onItemClickHandler(item);
|
||||
}
|
||||
|
||||
render() {
|
||||
let item = this.props.item;
|
||||
return (
|
||||
<li className="search-result-item" onClick={this.onClickHandler}>
|
||||
<span className="sf3-font sf3-font-table"></span>
|
||||
<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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SearchResultItem.propTypes = propTypes;
|
||||
|
||||
export default SearchResultItem;
|
272
frontend/src/pages/dtable/search/search.js
Normal file
272
frontend/src/pages/dtable/search/search.js
Normal file
@ -0,0 +1,272 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import MediaQuery from 'react-responsive';
|
||||
import { seafileAPI } from '../../../utils/seafile-api';
|
||||
import { gettext, siteRoot, username } from '../../../utils/constants';
|
||||
import SearchResultItem from './search-result-item';
|
||||
import More from '../../../components/more';
|
||||
import Workspace from '../model/workspace';
|
||||
|
||||
const propTypes = {
|
||||
isPublic: PropTypes.bool,
|
||||
repoID: PropTypes.string,
|
||||
placeholder: PropTypes.string,
|
||||
onSearchedClick: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
class Search extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
width: 'default',
|
||||
value: '',
|
||||
resultItems: [],
|
||||
isMaskShow: false,
|
||||
isResultShow: false,
|
||||
isResultGetted: false,
|
||||
isCloseShow: false,
|
||||
isSearchInputShow: false, // for mobile
|
||||
};
|
||||
this.inputValue = '';
|
||||
}
|
||||
|
||||
onFocusHandler = () => {
|
||||
this.setState({
|
||||
width: '30rem',
|
||||
isMaskShow: true,
|
||||
isCloseShow: true
|
||||
});
|
||||
}
|
||||
|
||||
onCloseHandler = () => {
|
||||
this.resetToDefault();
|
||||
}
|
||||
|
||||
onItemClickHandler = (item) => {
|
||||
this.resetToDefault();
|
||||
this.props.onSearchedClick(item);
|
||||
}
|
||||
|
||||
onChangeHandler = (event) => {
|
||||
let _this = this;
|
||||
this.setState({value: event.target.value});
|
||||
let newValue = event.target.value;
|
||||
if (this.inputValue === newValue.trim()) {
|
||||
return false;
|
||||
}
|
||||
this.inputValue = newValue.trim();
|
||||
|
||||
if (this.inputValue === '' || _this.getValueLength(this.inputValue) < 3) {
|
||||
this.setState({
|
||||
isResultShow: false,
|
||||
isResultGetted: false
|
||||
});
|
||||
return false;
|
||||
}
|
||||
let queryData = {
|
||||
q: newValue,
|
||||
};
|
||||
|
||||
if (this.timer) {
|
||||
clearTimeout(this.timer);
|
||||
}
|
||||
|
||||
this.timer = setTimeout(_this.getSearchResult(queryData), 500);
|
||||
}
|
||||
|
||||
getSearchResult(queryData) {
|
||||
|
||||
this.setState({
|
||||
isResultShow: true,
|
||||
isResultGetted: false
|
||||
});
|
||||
|
||||
this.sendRequest(queryData);
|
||||
}
|
||||
|
||||
sendRequest(queryData) { //search dtable
|
||||
seafileAPI.listWorkspaces().then((res) => {
|
||||
let workspaceList = res.data.workspace_list.filter(item => {
|
||||
return new Workspace(item);
|
||||
});
|
||||
let dtableNameList = [];
|
||||
workspaceList.forEach(dtable => {
|
||||
let resultData = dtable.table_list.filter(item => {
|
||||
return item.name.indexOf(queryData.q) > -1;
|
||||
});
|
||||
if (resultData.length > 0) {
|
||||
dtableNameList.push(...resultData);
|
||||
}
|
||||
});
|
||||
this.setState({
|
||||
resultItems: dtableNameList,
|
||||
isResultGetted: true,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getValueLength(str) {
|
||||
var i = 0, code, len = 0;
|
||||
for (; i < str.length; i++) {
|
||||
code = str.charCodeAt(i);
|
||||
if (code == 10) { //solve enter problem
|
||||
len += 2;
|
||||
} else if (code < 0x007f) {
|
||||
len += 1;
|
||||
} else if (code >= 0x0080 && code <= 0x07ff) {
|
||||
len += 2;
|
||||
} else if (code >= 0x0800 && code <= 0xffff) {
|
||||
len += 3;
|
||||
}
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
resetToDefault() {
|
||||
this.inputValue = null;
|
||||
this.setState({
|
||||
width: '',
|
||||
value: '',
|
||||
isMaskShow: false,
|
||||
isCloseShow: false,
|
||||
isResultShow: false,
|
||||
isResultGetted: false,
|
||||
resultItems: [],
|
||||
isSearchInputShow: false,
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
renderSearchResult() {
|
||||
if (!this.state.isResultShow) {
|
||||
return;
|
||||
}
|
||||
if (!this.state.isResultGetted || this.getValueLength(this.inputValue) < 3) {
|
||||
return (
|
||||
<span className="loading-icon loading-tip"></span>
|
||||
);
|
||||
}
|
||||
if (!this.state.resultItems.length) {
|
||||
return (
|
||||
<div className="search-result-none">{gettext('No results matching.')}</div>
|
||||
);
|
||||
}
|
||||
let isShowMore = this.state.resultItems.length >= 5 ? true : false;
|
||||
return (
|
||||
<ul className="search-result-list">
|
||||
{this.state.resultItems.map(item => {
|
||||
return (
|
||||
<SearchResultItem
|
||||
key={item.id}
|
||||
item={item}
|
||||
onItemClickHandler={this.onItemClickHandler}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{isShowMore && <More onShowMore={this.onShowMore}/>}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
onSearchToggle = () => {
|
||||
this.setState({
|
||||
isSearchInputShow: !this.state.isSearchInputShow,
|
||||
isMaskShow: !this.state.isMaskShow,
|
||||
});
|
||||
}
|
||||
|
||||
onSearchPage = () => {
|
||||
window.location.href = siteRoot + 'search/';
|
||||
}
|
||||
|
||||
render() {
|
||||
let width = this.state.width !== 'default' ? this.state.width : '';
|
||||
let style = {'width': width};
|
||||
return (
|
||||
<Fragment>
|
||||
<MediaQuery query="(min-width: 768px)">
|
||||
<div className="search">
|
||||
<div className={`search-mask ${this.state.isMaskShow ? '' : 'hide'}`} onClick={this.onCloseHandler}></div>
|
||||
<div className="search-container">
|
||||
<div className="input-icon">
|
||||
<i className="search-icon-left input-icon-addon fas fa-search"></i>
|
||||
<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>
|
||||
}
|
||||
{this.state.isCloseShow && <i className='search-icon-right input-icon-addon fas fa-times' onClick={this.onCloseHandler}></i>}
|
||||
</div>
|
||||
<div className="search-result-container">
|
||||
{this.renderSearchResult()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</MediaQuery>
|
||||
<MediaQuery query="(max-width: 767.8px)">
|
||||
<div className="search-icon-container">
|
||||
<i className="search-icon fas fa-search" onClick={this.onSearchToggle}></i>
|
||||
</div>
|
||||
{this.state.isSearchInputShow &&
|
||||
<div className="search">
|
||||
<div className={`search-mask ${this.state.isMaskShow ? '' : 'hide'}`} onClick={this.onCloseHandler}></div>
|
||||
<div className="search-container">
|
||||
<div className="input-icon">
|
||||
<i className="search-icon-left input-icon-addon fas fa-search"></i>
|
||||
<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>
|
||||
}
|
||||
{this.state.isCloseShow && <i className='search-icon-right input-icon-addon fas fa-times' onClick={this.onCloseHandler}></i>}
|
||||
</div>
|
||||
<div className="search-result-container">
|
||||
{this.renderSearchResult()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</MediaQuery>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Search.propTypes = propTypes;
|
||||
|
||||
export default Search;
|
@ -21,12 +21,13 @@ class SidePanel extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
let imgUrl = siteRoot + 'media/img/dtable-logo.png'
|
||||
return (
|
||||
<div className="side-panel">
|
||||
<div className="side-panel-north dtable-header">
|
||||
<i className="sf3-font sf3-font-dtable-logo dtable-logo"></i>
|
||||
<span className="dtable-text">DTable</span>
|
||||
<div className="dtable-logo">
|
||||
<img src={imgUrl} height="32" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="side-panel-center">
|
||||
<div className="dtable-side-nav">
|
||||
|
BIN
media/img/dtable-logo.png
Normal file
BIN
media/img/dtable-logo.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 4.4 KiB |
@ -28,6 +28,7 @@
|
||||
window.app = {
|
||||
config: {
|
||||
siteRoot: '{{ SITE_ROOT }}',
|
||||
avatarURL: '{{ avatar_url }}'
|
||||
},
|
||||
pageOptions: {
|
||||
server: '{{ service_url }}',
|
||||
|
Loading…
Reference in New Issue
Block a user