From 21bac022ccff86fe9b399ddeb6f4064d2ebfef03 Mon Sep 17 00:00:00 2001
From: yinjianfei-user <85612278+yinjianfei-user@users.noreply.github.com>
Date: Tue, 12 Dec 2023 15:08:37 +0800
Subject: [PATCH] add-seaTable-integration (#5761)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* 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: 杨顺强 <978987373@qq.com>
---
.../repo-seatable-integration-dialog.js | 192 ++++++++++++
.../add-seatable-account-setting.js | 170 +++++++++++
.../delete-seatables-dialog.js | 32 ++
.../seatable-account-setting-item.js | 78 +++++
.../seatable-account-setting-list.js | 74 +++++
.../css/repo-seatable-integration-dialog.css | 274 ++++++++++++++++++
.../src/pages/my-libs/mylib-repo-list-item.js | 18 ++
frontend/src/pages/my-libs/mylib-repo-menu.js | 9 +-
frontend/src/utils/constants.js | 3 +
seahub/base/context_processors.py | 4 +-
seahub/settings.py | 3 +
seahub/templates/base_for_react.html | 1 +
12 files changed, 856 insertions(+), 2 deletions(-)
create mode 100644 frontend/src/components/dialog/repo-seatable-integration-dialog.js
create mode 100644 frontend/src/components/seatable-integration-account-setting-widgets/add-seatable-account-setting.js
create mode 100644 frontend/src/components/seatable-integration-account-setting-widgets/delete-seatables-dialog.js
create mode 100644 frontend/src/components/seatable-integration-account-setting-widgets/seatable-account-setting-item.js
create mode 100644 frontend/src/components/seatable-integration-account-setting-widgets/seatable-account-setting-list.js
create mode 100644 frontend/src/css/repo-seatable-integration-dialog.css
diff --git a/frontend/src/components/dialog/repo-seatable-integration-dialog.js b/frontend/src/components/dialog/repo-seatable-integration-dialog.js
new file mode 100644
index 00000000000..4b5b81c5554
--- /dev/null
+++ b/frontend/src/components/dialog/repo-seatable-integration-dialog.js
@@ -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 = '' + Utils.HTMLescape(repo.repo_name) + '';
+ const title = gettext('{placeholder} SeaTable integration').replace('{placeholder}', itemName);
+
+ return (
+
+
+
+
+
+
+ {status === STATUS.SEATABLE_ACCOUNT_MANAGE && (
+
this.changeStatus(STATUS.ADD_SETABLE_ACCOUNT)}
+ editSeatableSettingAccount={this.editSeatableSettingAccount}
+ deleteStableAccountSetting={this.deleteStableAccountSetting}
+ />
+ )}
+ {status === STATUS.ADD_SETABLE_ACCOUNT && (
+ this.changeStatus(STATUS.SEATABLE_ACCOUNT_MANAGE)}
+ onSubmit={this.onSubmit}
+ />
+ )}
+ {status === STATUS.UPDATE_SEATABLE_ACCOUNT && (
+ this.changeStatus(STATUS.SEATABLE_ACCOUNT_MANAGE)}
+ onSubmit={this.onSubmit}
+ />
+ )}
+
+
+
+ );
+ }
+}
+
+RepoSeaTableIntegrationDialog.propTypes = propTypes;
+
+export default RepoSeaTableIntegrationDialog;
diff --git a/frontend/src/components/seatable-integration-account-setting-widgets/add-seatable-account-setting.js b/frontend/src/components/seatable-integration-account-setting-widgets/add-seatable-account-setting.js
new file mode 100644
index 00000000000..188ec83d727
--- /dev/null
+++ b/frontend/src/components/seatable-integration-account-setting-widgets/add-seatable-account-setting.js
@@ -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 (
+
+
+
+
+
+
+ {gettext('Add SeaTable Integration')}
+
+
+
+
+
+ );
+ }
+}
+
+export default AddSeatableAccountSetting;
diff --git a/frontend/src/components/seatable-integration-account-setting-widgets/delete-seatables-dialog.js b/frontend/src/components/seatable-integration-account-setting-widgets/delete-seatables-dialog.js
new file mode 100644
index 00000000000..479872fc8a8
--- /dev/null
+++ b/frontend/src/components/seatable-integration-account-setting-widgets/delete-seatables-dialog.js
@@ -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 (
+
+ {gettext('Delete SeaTable base')}
+
+ {gettext('Are you sure to delete SeaTable')}{' '}{accountName}?
+
+
+
+
+
+
+ );
+ }
+}
+
+export default DeleteSeatablesDialog;
diff --git a/frontend/src/components/seatable-integration-account-setting-widgets/seatable-account-setting-item.js b/frontend/src/components/seatable-integration-account-setting-widgets/seatable-account-setting-item.js
new file mode 100644
index 00000000000..861e83cd3b7
--- /dev/null
+++ b/frontend/src/components/seatable-integration-account-setting-widgets/seatable-account-setting-item.js
@@ -0,0 +1,78 @@
+import React, { Component } from 'react';
+import PropTypes from 'prop-types';
+import DeleteSeatablesDialog from './delete-seatables-dialog';
+import { gettext } from '../../utils/constants';
+
+class SeatableAccountItem extends Component {
+
+ constructor (props) {
+ super(props);
+ this.state = {
+ isShowDialog: false,
+ };
+ }
+
+ static propTypes = {
+ t: PropTypes.func,
+ editSeatableSettingAccount: PropTypes.func,
+ deleteStableAccountSetting: PropTypes.func,
+ setting: PropTypes.object,
+ index: PropTypes.number,
+ };
+
+ openDialog = () => {
+ this.setState({isShowDialog: true});
+ };
+
+ closeDialog = () => {
+ this.setState({isShowDialog: false});
+ };
+
+ onDeleteSeatables = () => {
+ const { setting } = this.props;
+ this.props.deleteStableAccountSetting(setting, 'seatable_account_manage');
+ this.closeDialog();
+ };
+
+ render() {
+ const { isShowDialog } = this.state;
+ const { setting, t, index } = this.props;
+ const { base_api_token, base_name, seatable_server_url } = setting;
+ return (
+
+ {base_name} |
+
+ {seatable_server_url}
+ |
+
+
+
+
+
+
+
+ |
+ {isShowDialog &&
+
+ }
+
+ );
+ }
+}
+
+export default SeatableAccountItem;
diff --git a/frontend/src/components/seatable-integration-account-setting-widgets/seatable-account-setting-list.js b/frontend/src/components/seatable-integration-account-setting-widgets/seatable-account-setting-list.js
new file mode 100644
index 00000000000..acf384597ef
--- /dev/null
+++ b/frontend/src/components/seatable-integration-account-setting-widgets/seatable-account-setting-list.js
@@ -0,0 +1,74 @@
+import React, { Component } from 'react';
+import PropTypes from 'prop-types';
+import { Button } from 'reactstrap';
+import { gettext, mediaUrl } from '../../utils/constants';
+import SeatableAccountItem from './seatable-account-setting-item';
+
+class SeatableAccountSettingList extends Component {
+
+ static propTypes = {
+ accounts: PropTypes.array,
+ changeStatus: PropTypes.func,
+ editSeatableSettingAccount: PropTypes.func,
+ seatableSettings: PropTypes.array,
+ deleteStableAccountSetting: PropTypes.func,
+ };
+
+ renderContent = () => {
+ const { seatableSettings } = this.props;
+ if (!Array.isArray(seatableSettings) || seatableSettings.length === 0) {
+ return (
+
+
+
{gettext('No Seafile libraries')}
+
+ );
+ }
+ return (
+ <>
+
+
+
+ {gettext('Base name')} |
+ {gettext('SeaTable server URL')} |
+ |
+
+
+
+
+
+
+ {seatableSettings.map((setting, index) => {
+ return (
+
+ );
+ })}
+
+
+
+ >
+ );
+ };
+
+ render() {
+ return (
+
+
+ {gettext('SeaTable')}
+
+
+
+ {this.renderContent()}
+
+
+ );
+ }
+}
+
+export default SeatableAccountSettingList;
diff --git a/frontend/src/css/repo-seatable-integration-dialog.css b/frontend/src/css/repo-seatable-integration-dialog.css
new file mode 100644
index 00000000000..a4951f1c514
--- /dev/null
+++ b/frontend/src/css/repo-seatable-integration-dialog.css
@@ -0,0 +1,274 @@
+/* common */
+.account-dialog .nav .nav-item {
+ padding: 0;
+}
+
+.account-dialog .nav .nav-item .nav-link {
+ padding: 0.5rem 0;
+ color: #8a948f;
+ font-weight: normal;
+ transition: none;
+ width: 100%;
+}
+
+.account-dialog .nav .nav-item .nav-link.active {
+ color: #ff8000;
+ text-decoration: none;
+ border-bottom: 0.125rem solid #ff8000;
+}
+
+.account-dialog .nav-pills .nav-item .nav-link {
+ padding: .3125rem 1rem .3125rem 8px;
+ color: #333;
+}
+
+.account-dialog .nav-pills .nav-item .nav-link:hover {
+ background-color: #f5f5f5;
+}
+
+.account-dialog .nav-pills .nav-item .nav-link.active {
+ background-color: #ff8000;
+ color: #fff;
+ border: none;
+}
+
+.account-dialog table {
+ width: 100%;
+ table-layout: fixed;
+}
+
+.account-dialog .accounts-list {
+ max-height: 400px;
+ overflow: auto;
+}
+
+.account-dialog .accounts-list table thead tr {
+ border-bottom: 1px solid #efefef;
+ height: 2.1875rem;
+}
+
+.account-dialog .accounts-list table tbody tr {
+ border-bottom: 1px solid #efefef;
+ height: 2.5625rem;
+}
+
+.account-dialog table th {
+ padding: 0.3125rem 0.1875rem;
+ font-weight: 500;
+}
+
+.account-dialog table td {
+ padding: 3px 0.1875rem;
+ color: #333;
+ font-size: 14px;
+ word-break: break-all;
+}
+
+.account-dialog table td a {
+ text-decoration: none;
+ color: inherit;
+}
+
+.account-dialog .ellipsis {
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.account-dialog .select-module {
+ font-size: 16px;
+}
+
+.account-dialog .select-module.select-module-icon {
+ width: 1.5rem;
+ height: 1.5rem;
+}
+
+.account-dialog .select-module.select-module-name {
+ margin-left: 0.5rem;
+}
+
+.account-dialog .op-target {
+ color: #ea7500;
+ word-wrap: break-word;
+}
+
+/* custom */
+.account-dialog {
+ max-width: 600px;
+ height: calc(100% - 56px);
+}
+
+.account-dialog .account-dialog-content {
+ padding: 0;
+ min-height: 27rem;
+ display: flex;
+ flex-direction: row;
+ overflow: hidden;
+}
+
+.account-dialog-content .account-dialog-side {
+ display: flex;
+ flex: 0 0 25%;
+ padding: 12px 8px;
+ border-right: 1px solid #eee;
+}
+
+.account-dialog-content .account-dialog-main {
+ flex: 1;
+ padding: 0.5rem 1.5rem 2rem;
+}
+
+.account-dialog-content .account-dialog-main .tab-content {
+ flex: 1;
+}
+
+.account-dialog-content .account-dialog-main .tab-pane {
+ height: 100%;
+}
+
+.account-dialog .wechat-input-content {
+ height: 250px;
+}
+
+.accounts-manage .accounts-manage-header {
+ border-bottom: 1px solid #efefef;
+ padding: .375rem 0;
+}
+
+.accounts-manage .accounts-manage-header button {
+ font-weight: normal;
+}
+
+.accounts-manage .no-accounts {
+ height: 400px;
+}
+
+.accounts-manage .no-accounts img {
+ width: 120px;
+}
+
+.accounts-manage .no-accounts p {
+ margin-top: 1rem;
+ color: #afafaf;
+}
+
+.accounts-manage .accounts-list-body {
+ max-height: 400px;
+ overflow-y: auto;
+ cursor: pointer;
+}
+
+.accounts-manage th,
+.accounts-manage td {
+ padding: .5rem .1875rem;
+ border-color: #efefef;
+}
+
+.accounts-manage th {
+ color: #999;
+}
+
+.accounts-manage .accounts-list-body tr:hover {
+ background-color: #f9f9f9;
+}
+
+.accounts-manage .accounts-list-body .account-operation-btn {
+ display: none;
+ margin-right: .2rem;
+}
+
+.accounts-manage .accounts-list-body tr:hover .account-operation-btn {
+ display: inline-flex;
+ width: 24px;
+ height: 24px;
+ align-items: center;
+ justify-content: center;
+ cursor: pointer;
+}
+
+.accounts-manage .account-operation-btn .dtable-font {
+ font-size: 14px;
+ color: #999;
+}
+
+.accounts-manage .account-operation-btn:hover .dtable-font {
+ color: #5a5a5a;
+}
+
+.add-account,
+.edit-account {
+ height: 100%;
+}
+
+.add-account .add-account-header,
+.edit-account .edit-account-header {
+ padding: .375rem 0;
+ border-bottom: 1px solid #efefef;
+}
+
+.add-account .back-btn,
+.edit-account .back-btn {
+ width: 18px;
+ height: 18px;
+ cursor: pointer;
+}
+
+.add-account .back-btn .dtable-icon-return,
+.edit-account .back-btn .dtable-icon-return {
+ font-size: 14px;
+ color: #999;
+}
+
+.add-account .back-btn:hover .dtable-icon-return,
+.edit-account .back-btn:hover .dtable-icon-return {
+ color: #5a5a5a;
+}
+
+.add-account .add-account-header-text,
+.edit-account .edit-account-header-text {
+ margin-left: .25rem;
+}
+
+.add-account .add-account-btn,
+.edit-account .edit-account-btn {
+ margin: 0;
+ padding-top: 0;
+ padding-bottom: 0;
+ font-weight: normal;
+ border-color: #ff8000;
+ color: #ff8000;
+}
+
+.add-account .account-loading-tip {
+ margin-top: 1rem;
+}
+
+.add-account .add-account-btn:hover,
+.edit-account .edit-account-btn:hover {
+ background-color: transparent;
+}
+
+.base-account {
+ height: calc(100% - 38px);
+}
+
+.base-account input[type="checkbox"] {
+ margin-top: -2px;
+ vertical-align: middle;
+}
+
+.base-account .account-name-desc {
+ margin-top: .375rem;
+}
+
+.base-account .base-account-password .input-group .form-control {
+ height: 38px;
+ border-right: none;
+}
+
+.base-account .base-account-password .input-group .input-group-text {
+ height: 38px;
+ border-radius: 0 3px 3px 0;
+}
+
diff --git a/frontend/src/pages/my-libs/mylib-repo-list-item.js b/frontend/src/pages/my-libs/mylib-repo-list-item.js
index e001fdcaa1a..1198be5bc0c 100644
--- a/frontend/src/pages/my-libs/mylib-repo-list-item.js
+++ b/frontend/src/pages/my-libs/mylib-repo-list-item.js
@@ -19,6 +19,7 @@ import LibSubFolderPermissionDialog from '../../components/dialog/lib-sub-folder
import Rename from '../../components/rename';
import MylibRepoMenu from './mylib-repo-menu';
import RepoAPITokenDialog from '../../components/dialog/repo-api-token-dialog';
+import RepoSeaTableIntegrationDialog from '../../components/dialog/repo-seatable-integration-dialog';
import RepoShareAdminDialog from '../../components/dialog/repo-share-admin-dialog';
import LibOldFilesAutoDelDialog from '../../components/dialog/lib-old-files-auto-del-dialog';
import RepoMonitoredIcon from '../../components/repo-monitored-icon';
@@ -52,6 +53,7 @@ class MylibRepoListItem extends React.Component {
isLabelRepoStateDialogOpen: false,
isFolderPermissionDialogShow: false,
isAPITokenDialogShow: false,
+ isSeaTableIntegrationShow: false,
isRepoShareAdminDialogOpen: false,
isRepoDeleted: false,
isOldFilesAutoDelDialogOpen: false,
@@ -132,6 +134,9 @@ class MylibRepoListItem extends React.Component {
case 'Old Files Auto Delete':
this.toggleOldFilesAutoDelDialog();
break;
+ case 'SeaTable integration':
+ this.onSeaTableIntegrationToggle();
+ break;
default:
break;
}
@@ -240,6 +245,10 @@ class MylibRepoListItem extends React.Component {
this.setState({isAPITokenDialogShow: !this.state.isAPITokenDialogShow});
};
+ onSeaTableIntegrationToggle = () => {
+ this.setState({isSeaTableIntegrationShow: !this.state.isSeaTableIntegrationShow});
+ };
+
toggleRepoShareAdminDialog = () => {
this.setState({isRepoShareAdminDialogOpen: !this.state.isRepoShareAdminDialogOpen});
};
@@ -509,6 +518,15 @@ class MylibRepoListItem extends React.Component {
)}
+ {this.state.isSeaTableIntegrationShow && (
+
+
+
+ )}
+
{this.state.isRepoShareAdminDialogOpen && (