-
Notifications
You must be signed in to change notification settings - Fork 373
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* add-seaTable-integration * update-request * update-path * update-json * update-UI * update-json * update-text * update-text * remove-space * update-UI * add-hover * add variable control --------- Co-authored-by: 杨顺强 <[email protected]>
- Loading branch information
1 parent
a514228
commit 21bac02
Showing
12 changed files
with
856 additions
and
2 deletions.
There are no files selected for viewing
192 changes: 192 additions & 0 deletions
192
frontend/src/components/dialog/repo-seatable-integration-dialog.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,192 @@ | ||
import React, { Fragment } from 'react'; | ||
import PropTypes from 'prop-types'; | ||
import { Modal, ModalHeader, ModalBody } from 'reactstrap'; | ||
import SeatableAccountSettingList from '../seatable-integration-account-setting-widgets/seatable-account-setting-list.js'; | ||
import AddSeatableAccountSetting from '../../components/seatable-integration-account-setting-widgets/add-seatable-account-setting.js'; | ||
import toaster from '../toast'; | ||
import { seafileAPI } from '../../utils/seafile-api'; | ||
import { Utils } from '../../utils/utils'; | ||
import { gettext, internalFilePath, dirPath } from '../../utils/constants'; | ||
|
||
import '../../css/repo-seatable-integration-dialog.css'; | ||
|
||
const propTypes = { | ||
repo: PropTypes.object.isRequired, | ||
onSeaTableIntegrationToggle: PropTypes.func.isRequired, | ||
}; | ||
|
||
const STATUS = { | ||
SEATABLE_ACCOUNT_MANAGE: 'seatable_account_manage', | ||
ADD_SETABLE_ACCOUNT: 'add_seatable_account', | ||
UPDATE_SEATABLE_ACCOUNT: 'update_seatable_account' | ||
}; | ||
|
||
class RepoSeaTableIntegrationDialog extends React.Component { | ||
constructor(props) { | ||
super(props); | ||
this.state = { | ||
activeTab: 'SeaTable', | ||
seatableSettings: [], | ||
baseApiToken: '', | ||
isPasswordVisible: false, | ||
isShowDialog: false, | ||
currentDtableInfo: {}, | ||
status: STATUS.SEATABLE_ACCOUNT_MANAGE, | ||
}; | ||
this.repo = this.props.repo; | ||
} | ||
|
||
componentDidMount() { | ||
this.getSeatableSettings(); | ||
} | ||
|
||
getSeatableSettings = async (status) => { | ||
seafileAPI.req.defaults.headers.Authorization = null; | ||
const [downloadLinkRes] = await seafileAPI.getFileDownloadLink(this.repo.repo_id, internalFilePath).then(res => [res, null]).catch((err) => [null, err]); | ||
if (downloadLinkRes && downloadLinkRes.data) { | ||
const fileInfoRes = await seafileAPI.getFileContent(downloadLinkRes.data); | ||
if (fileInfoRes?.data && fileInfoRes.data) { | ||
this.setState({ | ||
seatableSettings: fileInfoRes.data | ||
}); | ||
status && this.setState({ status }); | ||
} | ||
} | ||
}; | ||
|
||
changeTab = (tab) => { | ||
if (this.state.activeTab !== tab) { | ||
this.setState({activeTab: tab}); | ||
} | ||
}; | ||
|
||
changeStatus = (status) => { | ||
this.setState({status}); | ||
}; | ||
|
||
getFile = (detail, fileList) => { | ||
const { base_name, seatable_url, seatable_api_token } = detail; | ||
let content = [{ | ||
'base_name': base_name, | ||
'seatable_server_url': seatable_url, | ||
'base_api_token': seatable_api_token | ||
}]; | ||
|
||
if (fileList && fileList.length !== 0) { | ||
const index = fileList?.findIndex((item) => item.base_api_token === seatable_api_token); | ||
if (index !== -1) { | ||
fileList[index] = content[0]; | ||
} else { | ||
fileList.push(content[0]); | ||
} | ||
content = fileList; | ||
} | ||
|
||
const fileName = internalFilePath.split('/')[2]; | ||
const fileContent = JSON.stringify(content); | ||
const newFile = new File([fileContent], fileName); | ||
return newFile; | ||
}; | ||
|
||
editSeatableSettingAccount = (baseApiToken) => { | ||
const { seatableSettings } = this.state; | ||
this.setState({ | ||
status: STATUS.UPDATE_SEATABLE_ACCOUNT, | ||
currentDtableInfo: seatableSettings.find((item) => item.base_api_token === baseApiToken) | ||
}); | ||
}; | ||
|
||
deleteStableAccountSetting = async (setting, status) => { | ||
const { base_api_token } = setting; | ||
seafileAPI.req.defaults.headers.Authorization = null; | ||
const [downloadLinkRes] = await seafileAPI.getFileDownloadLink(this.repo.repo_id, internalFilePath).then(res => [res, null]).catch((err) => [null, err]); | ||
if (downloadLinkRes && downloadLinkRes.data) { | ||
const fileInfoRes = await seafileAPI.getFileContent(downloadLinkRes.data); | ||
if (fileInfoRes?.data) { | ||
const fileList = fileInfoRes.data; | ||
const index = fileList?.findIndex((item) => item.base_api_token === base_api_token); | ||
if (index !== -1) { | ||
fileList.splice(index, 1); | ||
const fileContent = JSON.stringify(fileList); | ||
const fileName = internalFilePath.split('/')[2]; | ||
const newFile = new File([fileContent], fileName); | ||
const updateLink = await seafileAPI.getUpdateLink(this.repo.repo_id, internalFilePath.slice(0, 10)); | ||
await seafileAPI.updateFile(updateLink.data, internalFilePath, fileName, newFile).catch(err => {toaster.danger(gettext(err.message));}); | ||
this.getSeatableSettings(status); | ||
} | ||
} | ||
} | ||
}; | ||
|
||
onSubmit = async (detail, status) => { | ||
seafileAPI.req.defaults.headers.Authorization = null; | ||
const [downloadLinkRes, err] = await seafileAPI.getFileDownloadLink(this.repo.repo_id, internalFilePath).then(res => [res, null]).catch((err) => [null, err]); | ||
// Contains configuration files | ||
if (downloadLinkRes && downloadLinkRes.data) { | ||
const fileInfoRes = await seafileAPI.getFileContent(downloadLinkRes.data); | ||
if (fileInfoRes?.data) { | ||
const newFile = this.getFile(detail, fileInfoRes.data); | ||
const updateLink = await seafileAPI.getUpdateLink(this.repo.repo_id, internalFilePath.slice(0, 10)); | ||
const fileName = internalFilePath.split('/')[2]; | ||
await seafileAPI.updateFile(updateLink.data, internalFilePath, fileName, newFile).catch(err => {toaster.danger(gettext(err.message));}); | ||
this.getSeatableSettings(status); | ||
} | ||
} | ||
// No configuration file | ||
if (err) { | ||
const uploadLink = await seafileAPI.getFileServerUploadLink(this.repo.repo_id, dirPath); | ||
const newFile = this.getFile(detail); | ||
const formData = new FormData(); | ||
formData.append('file', newFile); | ||
formData.append('relative_path', internalFilePath.split('/')[1]); | ||
formData.append('parent_dir', dirPath); | ||
await seafileAPI.uploadImage(uploadLink.data + '?ret-json=1', formData).catch(err => {toaster.danger(gettext(err.message));}); | ||
this.getSeatableSettings(status); | ||
} | ||
}; | ||
|
||
render() { | ||
const { seatableSettings, status, currentDtableInfo } = this.state; | ||
const { onSeaTableIntegrationToggle } = this.props; | ||
let repo = this.repo; | ||
const itemName = '<span class="op-target">' + Utils.HTMLescape(repo.repo_name) + '</span>'; | ||
const title = gettext('{placeholder} SeaTable integration').replace('{placeholder}', itemName); | ||
|
||
return ( | ||
<Modal isOpen={true} toggle={onSeaTableIntegrationToggle} className="account-dialog"> | ||
<ModalHeader toggle={onSeaTableIntegrationToggle}> | ||
<p dangerouslySetInnerHTML={{__html: title}} className="m-0"></p> | ||
</ModalHeader> | ||
<ModalBody className="account-dialog-content"> | ||
<div className="account-dialog-main"> | ||
{status === STATUS.SEATABLE_ACCOUNT_MANAGE && ( | ||
<SeatableAccountSettingList | ||
seatableSettings={seatableSettings} | ||
changeStatus={() => this.changeStatus(STATUS.ADD_SETABLE_ACCOUNT)} | ||
editSeatableSettingAccount={this.editSeatableSettingAccount} | ||
deleteStableAccountSetting={this.deleteStableAccountSetting} | ||
/> | ||
)} | ||
{status === STATUS.ADD_SETABLE_ACCOUNT && ( | ||
<AddSeatableAccountSetting | ||
changeStatus={() => this.changeStatus(STATUS.SEATABLE_ACCOUNT_MANAGE)} | ||
onSubmit={this.onSubmit} | ||
/> | ||
)} | ||
{status === STATUS.UPDATE_SEATABLE_ACCOUNT && ( | ||
<AddSeatableAccountSetting | ||
currentDtableInfo={currentDtableInfo} | ||
changeStatus={() => this.changeStatus(STATUS.SEATABLE_ACCOUNT_MANAGE)} | ||
onSubmit={this.onSubmit} | ||
/> | ||
)} | ||
</div> | ||
</ModalBody> | ||
</Modal> | ||
); | ||
} | ||
} | ||
|
||
RepoSeaTableIntegrationDialog.propTypes = propTypes; | ||
|
||
export default RepoSeaTableIntegrationDialog; |
170 changes: 170 additions & 0 deletions
170
...c/components/seatable-integration-account-setting-widgets/add-seatable-account-setting.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
import React, { Component } from 'react'; | ||
import { Alert, Input, FormGroup, Label, InputGroup, InputGroupText } from 'reactstrap'; | ||
import PropTypes from 'prop-types'; | ||
import { seafileAPI } from '../../utils/seafile-api'; | ||
import { gettext } from '../../utils/constants'; | ||
|
||
class AddSeatableAccountSetting extends Component { | ||
|
||
static propTypes = { | ||
t: PropTypes.func, | ||
changeStatus: PropTypes.func, | ||
onSubmit: PropTypes.func, | ||
currentDtableInfo: PropTypes.object, | ||
addSeatableAccountSetting: PropTypes.func, | ||
}; | ||
|
||
constructor(props) { | ||
super(props); | ||
const { currentDtableInfo } = props; | ||
this.state = { | ||
errMessage: '', | ||
base_name: currentDtableInfo?.base_name || '', | ||
seatable_url: currentDtableInfo?.seatable_url || 'https://dev.seatable.cn/', | ||
seatable_api_token: currentDtableInfo?.base_api_token || '', | ||
successMessage: null, | ||
stage: 'toCheck', // toCheck: need to check -> toSubmit: need to submit | ||
passwordType: 'password' | ||
}; | ||
} | ||
|
||
onChangeBaseName = (event) => { | ||
let value = event.target.value; | ||
if (value === this.state.base_name) { | ||
return; | ||
} | ||
this.setState({ | ||
base_name: value, | ||
errMessage: '', | ||
}); | ||
}; | ||
|
||
onChangeSeatableUrl = (event) => { | ||
let value = event.target.value; | ||
if (value === this.state.seatable_url) { | ||
return; | ||
} | ||
this.setState({ | ||
seatable_url: value, | ||
successMessage: null, | ||
stage: 'toCheck', | ||
errMessage: '', | ||
}); | ||
}; | ||
|
||
onChangeSeatableApiToken = (event) => { | ||
let value = event.target.value; | ||
if (value === this.state.seatable_api_token) { | ||
return; | ||
} | ||
this.setState({ | ||
seatable_api_token: value, | ||
successMessage: null, | ||
stage: 'toCheck', | ||
errMessage: '', | ||
}); | ||
}; | ||
|
||
addSeatableAccountSetting = () => { | ||
const { t } = this.props; | ||
let { base_name, seatable_url, seatable_api_token } = this.state; | ||
base_name = base_name.trim(); | ||
seatable_url = seatable_url.trim(); | ||
seatable_api_token = seatable_api_token.trim(); | ||
let errMessage = ''; | ||
if (!base_name) { | ||
errMessage = gettext('Base name is required'); | ||
} | ||
else if (!seatable_url) { | ||
errMessage = gettext('URL is required'); | ||
} | ||
else if (!seatable_api_token) { | ||
errMessage = gettext('SeaTable API token is required'); | ||
} | ||
|
||
this.setState({errMessage}); | ||
if (errMessage) return; | ||
let detail = { | ||
base_name, | ||
seatable_url, | ||
seatable_api_token | ||
}; | ||
this.props.onSubmit(detail, 'seatable_account_manage'); | ||
}; | ||
|
||
testSeatableAPIToken = async () => { | ||
const { seatable_url, seatable_api_token } = this.state; | ||
seafileAPI.req.defaults.headers.Authorization = `Token ${seatable_api_token}`; | ||
const [res, err] = await seafileAPI.req.get(`${seatable_url}api/v2.1/dtable/app-access-token/`).then(res => [res, null]).catch((err) => [null, err]); | ||
if (res) { | ||
this.setState({ | ||
successMessage: res.data, | ||
stage: 'toSubmit', | ||
}); | ||
} | ||
if (err) { | ||
this.setState({ | ||
errMessage: gettext('URL or SeaTable API token is invalid'), | ||
}); | ||
} | ||
}; | ||
|
||
togglePasswordShow = () => { | ||
if (this.state.passwordType === 'password') { | ||
this.setState({passwordType: 'text'}); | ||
} else { | ||
this.setState({passwordType: 'password'}); | ||
} | ||
}; | ||
|
||
render() { | ||
const { errMessage, stage, successMessage, base_name, seatable_url, seatable_api_token, passwordType } = this.state; | ||
return ( | ||
<div className="add-account"> | ||
<div className="add-account-header d-flex align-items-center justify-content-between"> | ||
<span> | ||
<span className="back-btn d-inline-flex align-items-center justify-content-center" onClick={this.props.changeStatus}> | ||
<i className="link-icon icon-left sf3-font sf3-font-arrow" style={{transform: 'rotate(180deg)', color: '#999'}}></i> | ||
</span> | ||
<span className="add-account-header-text">{gettext('Add SeaTable Integration')}</span> | ||
</span> | ||
<button | ||
onClick={stage === 'toCheck'? this.testSeatableAPIToken : this.addSeatableAccountSetting} | ||
type="button" | ||
className="btn btn-secondary add-account-btn" | ||
>{stage === 'toCheck' ? gettext('Check') : gettext('Submit')}</button> | ||
</div> | ||
<div className="base-account"> | ||
<div className="account-name-desc"> | ||
<FormGroup> | ||
<Label>{gettext('Base name')}</Label> | ||
<Input value={base_name} onChange={this.onChangeBaseName}/> | ||
</FormGroup> | ||
<FormGroup> | ||
<Label>{gettext('SeaTable server URL')}</Label> | ||
<Input value={seatable_url} onChange={this.onChangeSeatableUrl}/> | ||
</FormGroup> | ||
<FormGroup className="base-account-password"> | ||
<Label>{gettext('SeaTable API token')}</Label> | ||
<InputGroup> | ||
<Input value={seatable_api_token} type={passwordType} onChange={this.onChangeSeatableApiToken}/> | ||
<InputGroupText> | ||
<i className={`fas ${passwordType === 'password' ? 'fa-eye-slash' : 'fa-eye'} cursor-pointer`} onClick={this.togglePasswordShow} /> | ||
</InputGroupText> | ||
</InputGroup> | ||
</FormGroup> | ||
</div> | ||
{errMessage && <Alert color="danger">{errMessage}</Alert>} | ||
{successMessage && ( | ||
<Alert color="success"> | ||
<span className="dtable-font dtable-icon-check-circle mr-2"></span> | ||
{gettext('Successfully connected to SeaTable')} | ||
</Alert> | ||
)} | ||
</div> | ||
</div> | ||
); | ||
} | ||
} | ||
|
||
export default AddSeatableAccountSetting; |
32 changes: 32 additions & 0 deletions
32
...nd/src/components/seatable-integration-account-setting-widgets/delete-seatables-dialog.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import React, { Component } from 'react'; | ||
import PropTypes from 'prop-types'; | ||
import { Button, Modal, ModalHeader, ModalBody, ModalFooter } from 'reactstrap'; | ||
import { gettext } from '../../utils/constants'; | ||
|
||
class DeleteSeatablesDialog extends Component { | ||
|
||
static propTypes = { | ||
t: PropTypes.func, | ||
accountName: PropTypes.string, | ||
onDeleteSeatables: PropTypes.func, | ||
closeDialog: PropTypes.func, | ||
}; | ||
|
||
render () { | ||
const { accountName, closeDialog } = this.props; | ||
return ( | ||
<Modal isOpen={true} toggle={closeDialog}> | ||
<ModalHeader toggle={closeDialog}>{gettext('Delete SeaTable base')}</ModalHeader> | ||
<ModalBody> | ||
<div className="pb-6">{gettext('Are you sure to delete SeaTable')}{' '}{accountName}?</div> | ||
</ModalBody> | ||
<ModalFooter> | ||
<Button color="secondary" onClick={closeDialog}>{gettext('Cancel')}</Button> | ||
<Button color="primary" onClick={this.props.onDeleteSeatables}>{gettext('Delete')}</Button> | ||
</ModalFooter> | ||
</Modal> | ||
); | ||
} | ||
} | ||
|
||
export default DeleteSeatablesDialog; |
Oops, something went wrong.