insomnia/packages/insomnia-app/app/ui/components/modals/sync-branches-modal.tsx
Opender Singh 2471147642
🧹 Convert VCS to named export and rename file (#3471)
* rename file from index.ts -> vcs.ts

* convert to named export

* fix jest.mock path for moved file

* update class mock

* update mocked function
2021-06-15 01:28:44 +12:00

310 lines
9.5 KiB
TypeScript

import React, { PureComponent } from 'react';
import classnames from 'classnames';
import { autoBindMethodsForReact } from 'class-autobind-decorator';
import { AUTOBIND_CFG } from '../../../common/constants';
import Modal from '../base/modal';
import ModalBody from '../base/modal-body';
import ModalHeader from '../base/modal-header';
import type { Workspace } from '../../../models/workspace';
import { VCS } from '../../../sync/vcs/vcs';
import { database as db } from '../../../common/database';
import type { StatusCandidate } from '../../../sync/types';
import PromptButton from '../base/prompt-button';
import SyncPullButton from '../sync-pull-button';
interface Props {
workspace: Workspace;
syncItems: StatusCandidate[];
vcs: VCS;
}
interface State {
error: string;
newBranchName: string;
currentBranch: string;
branches: string[];
remoteBranches: string[];
}
@autoBindMethodsForReact(AUTOBIND_CFG)
class SyncBranchesModal extends PureComponent<Props, State> {
modal: Modal | null = null;
state: State = {
error: '',
newBranchName: '',
branches: [],
remoteBranches: [],
currentBranch: '',
}
_setModalRef(m: Modal) {
this.modal = m;
}
async _handleCheckout(branch: string) {
const { vcs, syncItems } = this.props;
try {
const delta = await vcs.checkout(syncItems, branch);
// @ts-expect-error -- TSCONVERSION
await db.batchModifyDocs(delta);
await this.refreshState();
} catch (err) {
console.log('Failed to checkout', err.stack);
this.setState({
error: err.message,
});
}
}
async _handleMerge(branch: string) {
const { vcs, syncItems } = this.props;
const delta = await vcs.merge(syncItems, branch);
try {
// @ts-expect-error -- TSCONVERSION
await db.batchModifyDocs(delta);
await this.refreshState();
} catch (err) {
console.log('Failed to merge', err.stack);
this.setState({
error: err.message,
});
}
}
async _handleRemoteDelete(branch: string) {
const { vcs } = this.props;
try {
await vcs.removeRemoteBranch(branch);
await this.refreshState();
} catch (err) {
console.log('Failed to remote delete', err.stack);
this.setState({
error: err.message,
});
}
}
async _handleDelete(branch: string) {
const { vcs } = this.props;
try {
await vcs.removeBranch(branch);
await this.refreshState();
} catch (err) {
console.log('Failed to delete', err.stack);
this.setState({
error: err.message,
});
}
}
async _handleCreate(e: React.SyntheticEvent<HTMLFormElement>) {
e.preventDefault();
const { vcs, syncItems } = this.props;
try {
// Create new branch
const { newBranchName } = this.state;
await vcs.fork(newBranchName);
// Checkout new branch
const delta = await vcs.checkout(syncItems, newBranchName);
// @ts-expect-error -- TSCONVERSION
await db.batchModifyDocs(delta);
// Clear branch name and refresh things
await this.refreshState({
newBranchName: '',
});
} catch (err) {
console.log('Failed to create', err.stack);
this.setState({
error: err.message,
});
}
}
_updateNewBranchName(e: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) {
this.setState({ newBranchName: e.currentTarget.value });
}
_handleClearError() {
this.setState({ error: '' });
}
async refreshState(newState?: Record<string, any>) {
const { vcs } = this.props;
try {
const currentBranch = await vcs.getBranch();
const branches = (await vcs.getBranches()).sort();
this.setState({
branches,
currentBranch,
error: '',
...newState,
});
const remoteBranches = (await vcs.getRemoteBranches())
.filter(b => !branches.includes(b))
.sort();
this.setState({
remoteBranches,
});
} catch (err) {
console.log('Failed to refresh', err.stack);
this.setState({
error: err.message,
});
}
}
hide() {
this.modal && this.modal.hide();
}
async show(options: { onHide: (...args: any[]) => any }) {
this.modal &&
this.modal.show({
onHide: options.onHide,
});
await this.refreshState();
}
render() {
const { vcs } = this.props;
const { branches, remoteBranches, currentBranch, newBranchName, error } = this.state;
return (
<Modal ref={this._setModalRef}>
<ModalHeader>Branches</ModalHeader>
<ModalBody className="wide pad">
{error && (
<p className="notice error margin-bottom-sm no-margin-top">
<button className="pull-right icon" onClick={this._handleClearError}>
<i className="fa fa-times" />
</button>
{error}
</p>
)}
<form onSubmit={this._handleCreate}>
<div className="form-row">
<div className="form-control form-control--outlined">
<label>
New Branch Name
<input
type="text"
onChange={this._updateNewBranchName}
placeholder="testing-branch"
value={newBranchName}
/>
</label>
</div>
<div className="form-control form-control--no-label width-auto">
<button type="submit" className="btn btn--clicky" disabled={!newBranchName}>
Create
</button>
</div>
</div>
</form>
<div className="pad-top">
<table className="table--fancy table--outlined">
<thead>
<tr>
<th className="text-left">Branches</th>
<th className="text-right">&nbsp;</th>
</tr>
</thead>
<tbody>
{branches.map(name => (
<tr key={name} className="table--no-outline-row">
<td>
<span
className={classnames({
bold: name === currentBranch,
})}>
{name}
</span>
{name === currentBranch ? (
<span className="txt-sm space-left">(current)</span>
) : null}
{name === 'master' && <i className="fa fa-lock space-left faint" />}
</td>
<td className="text-right">
<PromptButton
className="btn btn--micro btn--outlined space-left"
doneMessage="Merged"
disabled={name === currentBranch}
onClick={() => this._handleMerge(name)}>
Merge
</PromptButton>
<PromptButton
className="btn btn--micro btn--outlined space-left"
doneMessage="Deleted"
disabled={name === currentBranch || name === 'master'}
onClick={() => this._handleDelete(name)}>
Delete
</PromptButton>
<button
className="btn btn--micro btn--outlined space-left"
disabled={name === currentBranch}
onClick={() => this._handleCheckout(name)}>
Checkout
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
{remoteBranches.length > 0 && (
<div className="pad-top">
<table className="table--fancy table--outlined">
<thead>
<tr>
<th className="text-left">Remote Branches</th>
<th className="text-right">&nbsp;</th>
</tr>
</thead>
<tbody>
{remoteBranches.map(name => (
<tr key={name} className="table--no-outline-row">
<td>
{name}
{name === 'master' && <i className="fa fa-lock space-left faint" />}
</td>
<td className="text-right">
{name !== 'master' && (
<PromptButton
className="btn btn--micro btn--outlined space-left"
doneMessage="Deleted"
disabled={name === currentBranch}
onClick={() => this._handleRemoteDelete(name)}>
Delete
</PromptButton>
)}
<SyncPullButton
className="btn btn--micro btn--outlined space-left"
branch={name}
onPull={this.refreshState}
disabled={name === currentBranch}
vcs={vcs}>
Fetch
</SyncPullButton>
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</ModalBody>
</Modal>
);
}
}
export default SyncBranchesModal;