insomnia/packages/insomnia-app/app/ui/components/modals/sync-branches-modal.js
Gregory Schier 0a616fba6b
Version Control (beta) (#1439)
* VCS proof of concept underway!

* Stuff

* Some things

* Replace deprecated Electron makeSingleInstance

* Rename `window` variables so not to be confused with window object

* Don't unnecessarily update request when URL does not change

* Regenerate package-lock

* Fix tests + ESLint

* Publish

 - insomnia-app@1.0.49
 - insomnia-cookies@0.0.12
 - insomnia-httpsnippet@1.16.18
 - insomnia-importers@2.0.13
 - insomnia-libcurl@0.0.23
 - insomnia-prettify@0.1.7
 - insomnia-url@0.1.6
 - insomnia-xpath@1.0.9
 - insomnia-plugin-base64@1.0.6
 - insomnia-plugin-cookie-jar@1.0.8
 - insomnia-plugin-core-themes@1.0.5
 - insomnia-plugin-default-headers@1.1.9
 - insomnia-plugin-file@1.0.7
 - insomnia-plugin-hash@1.0.7
 - insomnia-plugin-jsonpath@1.0.12
 - insomnia-plugin-now@1.0.11
 - insomnia-plugin-os@1.0.13
 - insomnia-plugin-prompt@1.1.9
 - insomnia-plugin-request@1.0.18
 - insomnia-plugin-response@1.0.16
 - insomnia-plugin-uuid@1.0.10

* Broken but w/e

* Some tweaks

* Big refactor. Create local snapshots and push done

* POC merging and a lot of improvements

* Lots of work done on initial UI/UX

* Fix old tests

* Atomic writes and size-based batches

* Update StageEntry definition once again to be better

* Factor out GraphQL query logic

* Merge algorithm, history modal, other minor things

* Fix test

* Merge, checkout, revert w/ user changes now work

* Force UI to refresh when switching branches changes active request

* Rough draft pull() and some cleanup

* E2EE stuff and some refactoring

* Add ability to share project with team and fixed tests

* VCS now created in root component and better remote project handling

* Remove unused definition

* Publish

 - insomnia-account@0.0.2
 - insomnia-app@1.1.1
 - insomnia-cookies@0.0.14
 - insomnia-httpsnippet@1.16.20
 - insomnia-importers@2.0.15
 - insomnia-libcurl@0.0.25
 - insomnia-prettify@0.1.9
 - insomnia-sync@0.0.2
 - insomnia-url@0.1.8
 - insomnia-xpath@1.0.11
 - insomnia-plugin-base64@1.0.8
 - insomnia-plugin-cookie-jar@1.0.10
 - insomnia-plugin-core-themes@1.0.7
 - insomnia-plugin-file@1.0.9
 - insomnia-plugin-hash@1.0.9
 - insomnia-plugin-jsonpath@1.0.14
 - insomnia-plugin-now@1.0.13
 - insomnia-plugin-os@1.0.15
 - insomnia-plugin-prompt@1.1.11
 - insomnia-plugin-request@1.0.20
 - insomnia-plugin-response@1.0.18
 - insomnia-plugin-uuid@1.0.12

* Move some deps around

* Fix Flow errors

* Update package.json

* Fix eslint errors

* Fix tests

* Update deps

* bootstrap insomnia-sync

* TRy fixing appveyor

* Try something else

* Bump lerna

* try powershell

*  Try again

* Fix imports

* Fixed errors

* sync types refactor

* Show remote projects in workspace dropdown

* Improved pulling of non-local workspaces

* Loading indicators and some tweaks

* Clean up sync staging modal

* Some sync improvements:

- No longer store stage
- Upgrade Electron
- Sync UI/UX improvements

* Fix snyc tests

* Upgraded deps and hot loader tweaks (it's broken for some reason)

* Fix tests

* Branches dialog, network refactoring, some tweaks

* Fixed merging when other branch is empty

* A bunch of small fixes from real testing

* Fixed pull merge logic

* Fix tests

* Some bug fixes

* A few small tweaks

* Conflict resolution and other improvements

* Fix tests

* Add revert changes

* Deal with duplicate projects per workspace

* Some tweaks and accessibility improvements

* Tooltip accessibility

* Fix API endpoint

* Fix tests

* Remove jest dep from insomnia-importers
2019-04-17 17:50:03 -07:00

286 lines
9.0 KiB
JavaScript

// @flow
import * as React from 'react';
import classnames from 'classnames';
import autobind from 'autobind-decorator';
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';
import { batchModifyDocs } from '../../../common/database';
import type { StatusCandidate } from '../../../sync/types';
import PromptButton from '../base/prompt-button';
import SyncPullButton from '../sync-pull-button';
type Props = {
workspace: Workspace,
syncItems: Array<StatusCandidate>,
vcs: VCS,
};
type State = {
error: string,
newBranchName: string,
currentBranch: string,
branches: Array<string>,
remoteBranches: Array<string>,
};
@autobind
class SyncBranchesModal extends React.PureComponent<Props, State> {
modal: ?Modal;
constructor(props: Props) {
super(props);
this.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);
await 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 {
await 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: 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);
await 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: SyntheticEvent<HTMLTextAreaElement>) {
this.setState({ newBranchName: e.currentTarget.value });
}
_handleClearError() {
this.setState({ error: '' });
}
async refreshState(newState?: Object) {
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: Function }) {
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;