Verify Before Syncing (#98)

* Added an UNSET sync mode

* Fixed tests

* Remove sync logs and adjusted dropdown

* Fixed duplication kve bug

* Added sync config modal

* AUtobind

* Autobind working

* Hot loading works again

* Remove express

* Fixed tests

* Fix one thing

* Fixed some hmr-related bugs
This commit is contained in:
Gregory Schier 2017-03-02 17:44:07 -08:00 committed by GitHub
parent c1e9e718e9
commit 314daf155e
82 changed files with 1711 additions and 1036 deletions

25
.babelrc Normal file
View File

@ -0,0 +1,25 @@
{
"presets": [
"react"
],
"plugins": [
"transform-object-rest-spread",
"transform-decorators-legacy"
],
"env": {
"development": {
"plugins": [
"react-hot-loader/babel"
]
},
"test": {
"presets": [
"es2015"
],
"plugins": [
"transform-regenerator",
"transform-runtime"
]
}
}
}

4
.gitignore vendored
View File

@ -5,6 +5,10 @@ build/*
.DS_Store
coverage
# Generated
*.tmp.js
*.map
# Logs
logs
*.log

View File

@ -1,4 +1,4 @@
export default {
module.exports = {
remote: {
app: {
getPath (name) {
@ -11,4 +11,4 @@ export default {
}
}
}
};

View File

@ -6,6 +6,14 @@
</head>
<body>
<div id="root"></div>
<script>
// HOT RELOADING IN DEV
(function () {
const script = document.createElement('script');
script.src = (process.env.HOT) ? 'http://localhost:3333/bundle.js' : './bundle.js';
document.write(script.outerHTML);
}());
</script>
<script src="static/raven.min.js"></script>
<script>
// UPDATE HANDLERS
@ -109,14 +117,6 @@
}
})()
</script>
<script>
// HOT RELOADING IN DEV
(function () {
const script = document.createElement('script');
script.src = (process.env.HOT) ? 'http://localhost:3333/build/bundle.js' : './bundle.js';
document.write(script.outerHTML);
}());
</script>
<script>
// SOME HELPERS

View File

@ -39,7 +39,7 @@ describe('Test push/pull behaviour', () => {
// Set up sync modes
await sync.createOrUpdateConfig(resourceRequest.resourceGroupId, syncStorage.SYNC_MODE_ON);
await sync.createOrUpdateConfig(resourceRequest2.resourceGroupId, syncStorage.SYNC_MODE_OFF);
await sync.createOrUpdateConfig(resourceRequest2.resourceGroupId, syncStorage.SYNC_MODE_UNSET);
await sync.push(); // Push only active configs
await sync.push(resourceRequest.resourceGroupId); // Force push rg_1
@ -268,8 +268,19 @@ describe('Integration tests for creating Resources and pushing', () => {
expect((await syncStorage.allConfigs()).length).toBe(2);
expect((await syncStorage.allResources()).length).toBe(7);
// Mark all configs as auto sync
const configs = await syncStorage.allConfigs();
for (const config of configs) {
await syncStorage.updateConfig(config, {
syncMode: syncStorage.SYNC_MODE_ON
});
}
// Do initial push
await sync.push();
// Reset mocks once again before tests
await _setupSessionMocks();
});
it('Resources created on DB change', async () => {
@ -294,15 +305,10 @@ describe('Integration tests for creating Resources and pushing', () => {
expect(_decryptResource(resource).url).toBe('https://google.com');
expect(resource.removed).toBe(false);
expect(session.syncPush.mock.calls.length).toBe(2);
expect(session.syncPush.mock.calls[0][0].length).toBe(7);
// NOTE: This would be 1 if we were mocking the "push" response properly
// and telling the client to set the created docs to dirty=false
expect(session.syncPush.mock.calls[1][0].length).toBe(8);
expect(session.syncPush.mock.calls.length).toBe(1);
expect(session.syncPush.mock.calls[0][0].length).toBe(8);
expect(session.syncPull.mock.calls.length).toBe(1);
expect(session.syncPull.mock.calls[0][0].blacklist.length).toBe(0);
expect(session.syncPull.mock.calls[0][0].resources.length).toEqual(7);
expect(session.syncPull.mock.calls).toEqual([]);
});
it('Resources revived on DB change', async () => {
@ -358,13 +364,10 @@ describe('Integration tests for creating Resources and pushing', () => {
expect(_decryptResource(updatedResource).name).toBe('New Name');
expect(resource.removed).toBe(false);
expect(session.syncPush.mock.calls.length).toBe(2);
expect(session.syncPush.mock.calls.length).toBe(1);
expect(session.syncPush.mock.calls[0][0].length).toBe(7);
expect(session.syncPush.mock.calls[1][0].length).toBe(7);
expect(session.syncPull.mock.calls.length).toBe(1);
expect(session.syncPull.mock.calls[0][0].blacklist.length).toBe(0);
expect(session.syncPull.mock.calls[0][0].resources.length).toEqual(7);
expect(session.syncPull.mock.calls).toEqual([]);
});
it('Resources removed on DB change', async () => {
@ -384,13 +387,10 @@ describe('Integration tests for creating Resources and pushing', () => {
expect(resource.removed).toBe(false);
expect(updatedResource.removed).toBe(true);
expect(session.syncPush.mock.calls.length).toBe(2);
expect(session.syncPush.mock.calls.length).toBe(1);
expect(session.syncPush.mock.calls[0][0].length).toBe(7);
expect(session.syncPush.mock.calls[1][0].length).toBe(7);
expect(session.syncPull.mock.calls.length).toBe(1);
expect(session.syncPull.mock.calls[0][0].blacklist.length).toBe(0);
expect(session.syncPull.mock.calls[0][0].resources.length).toEqual(7);
expect(session.syncPull.mock.calls).toEqual([]);
});
});

View File

@ -544,11 +544,8 @@ export async function fetchResourceGroup (resourceGroupId, invalidateCache = fal
}
// Also make sure a config exists when we first fetch it.
// TODO: This exists in multiple places, so move it to one place.
const config = await getOrCreateConfig(resourceGroupId);
const syncMode = config ? config.syncMode : store.SYNC_MODE_OFF;
await createOrUpdateConfig(resourceGroupId, syncMode);
// (This may not be needed but we'll do it just in case)
await ensureConfigExists(resourceGroupId);
}
// Bust cached promise because we're done with it.
@ -648,7 +645,7 @@ export async function createResourceGroup (parentId, name) {
}
// Create a config for it
await ensureConfigExists(resourceGroup.id, store.SYNC_MODE_ON);
await ensureConfigExists(resourceGroup.id, store.SYNC_MODE_UNSET);
logger.debug(`Created ResourceGroup ${resourceGroup.id}`);
return resourceGroup;
@ -684,7 +681,6 @@ export async function createResourceForDoc (doc) {
if (!workspaceResource) {
const workspaceResourceGroup = await createResourceGroup(workspace._id, workspace.name);
await ensureConfigExists(workspaceResourceGroup.id, store.SYNC_MODE_OFF);
workspaceResource = await createResource(workspace, workspaceResourceGroup.id);
}

View File

@ -9,6 +9,8 @@ const TYPE_CONFIG = 'Config';
export const SYNC_MODE_OFF = 'paused';
export const SYNC_MODE_ON = 'active';
export const SYNC_MODE_NEVER = 'never';
export const SYNC_MODE_UNSET = 'unset';
export function allActiveResources (resourceGroupId = null) {
if (resourceGroupId) {
@ -26,7 +28,7 @@ export function allResources () {
return findResources({});
}
export async function findResources (query) {
export async function findResources (query = {}) {
return _execDB(TYPE_RESOURCE, 'find', query);
}
@ -118,12 +120,9 @@ export function allConfigs () {
export function findInactiveConfigs (excludedResourceGroupId = null) {
if (excludedResourceGroupId) {
return findConfigs({
syncMode: SYNC_MODE_OFF,
$not: {excludedResourceGroupId}
})
return findConfigs({$not: {syncMode: SYNC_MODE_ON, excludedResourceGroupId}})
} else {
return findConfigs({syncMode: SYNC_MODE_OFF})
return findConfigs({$not: {syncMode: SYNC_MODE_ON}})
}
}
@ -159,7 +158,7 @@ export async function insertConfig (config) {
function _initConfig (data) {
return Object.assign({
_id: util.generateId('scf'),
syncMode: SYNC_MODE_ON,
syncMode: SYNC_MODE_UNSET,
resourceGroupId: null
}, data);
}

View File

@ -1,15 +1,27 @@
import React, {PureComponent, PropTypes} from 'react';
import autobind from 'autobind-decorator';
import {Cookie} from 'tough-cookie';
@autobind
class CookieInput extends PureComponent {
state = {isValid: true};
constructor (props) {
super(props);
_handleChange () {
this.state = {
isValid: true,
};
}
_setInputRef (n) {
this._input = n;
}
_handleChange (e) {
const isValid = this._isValid();
if (isValid) {
this.props.onChange(this._input.value);
const value = e.target.value;
this.props.onChange(value);
}
this.setState({isValid})
@ -31,10 +43,10 @@ class CookieInput extends PureComponent {
return (
<input
className={isValid ? '' : 'input--error'}
ref={n => this._input = n}
ref={this._setInputRef}
type="text"
defaultValue={defaultValue}
onChange={e => this._handleChange(e.target.value)}
onChange={this._handleChange}
/>
);
}

View File

@ -1,9 +1,16 @@
import React, {PropTypes, PureComponent} from 'react';
import autobind from 'autobind-decorator';
import * as querystring from '../../common/querystring';
import * as util from '../../common/misc';
@autobind
class RenderedQueryString extends PureComponent {
state = {string: ''};
constructor (props) {
super(props);
this.state = {
string: ''
};
}
_update (props, delay = false) {
clearTimeout(this._triggerTimeout);

View File

@ -1,5 +1,5 @@
import React, {PureComponent, PropTypes} from 'react';
import {parse as urlParse} from 'url';
import autobind from 'autobind-decorator';
import {Tab, Tabs, TabList, TabPanel} from 'react-tabs';
import Lazy from './base/Lazy';
import KeyValueEditor from './keyvalueeditor/Editor';
@ -14,29 +14,43 @@ import {debounce} from '../../common/misc';
import {trackEvent} from '../../analytics/index';
import * as querystring from '../../common/querystring';
@autobind
class RequestPane extends PureComponent {
_handleHidePasswords = () => this.props.updateSettingsShowPasswords(false);
_handleShowPasswords = () => this.props.updateSettingsShowPasswords(true);
constructor (props) {
super(props);
_handleUpdateSettingsUseBulkHeaderEditor = () => {
this._handleUpdateRequestUrl = debounce(this._handleUpdateRequestUrl);
}
_handleHidePasswords () {
this.props.updateSettingsShowPasswords(false);
}
_handleShowPasswords () {
this.props.updateSettingsShowPasswords(true);
}
_handleUpdateSettingsUseBulkHeaderEditor () {
const {useBulkHeaderEditor, updateSettingsUseBulkHeaderEditor} = this.props;
updateSettingsUseBulkHeaderEditor(!useBulkHeaderEditor);
trackEvent('Headers', 'Toggle Bulk', !useBulkHeaderEditor ? 'On' : 'Off');
};
}
_handleImportFile = () => {
_handleImportFile () {
this.props.handleImportFile();
trackEvent('Request Pane', 'CTA', 'Import');
};
}
_handleCreateRequest = () => {
_handleCreateRequest () {
this.props.handleCreateRequest(this.props.request);
trackEvent('Request Pane', 'CTA', 'New Request');
}
_handleUpdateRequestUrl (url) {
this.props.updateRequestUrl(url)
};
_handleUpdateRequestUrl = debounce(url => this.props.updateRequestUrl(url));
_handleImportQueryFromUrl = () => {
_handleImportQueryFromUrl () {
const {request} = this.props;
let query;
@ -58,20 +72,39 @@ class RequestPane extends PureComponent {
if (url !== request.url) {
this.props.forceUpdateRequest({url, parameters});
}
};
}
_trackQueryToggle = pair => trackEvent('Query', 'Toggle', pair.disabled ? 'Disable' : 'Enable');
_trackQueryCreate = () => trackEvent('Query', 'Create');
_trackQueryDelete = () => trackEvent('Query', 'Delete');
_trackTabBody = () => trackEvent('Request Pane', 'View', 'Body');
_trackTabHeaders = () => trackEvent('Request Pane', 'View', 'Headers');
_trackTabAuthentication = () => trackEvent('Request Pane', 'View', 'Authentication');
_trackTabQuery = () => trackEvent('Request Pane', 'View', 'Query');
_trackQueryToggle (pair) {
trackEvent('Query', 'Toggle', pair.disabled ? 'Disable' : 'Enable');
}
_trackQueryCreate () {
trackEvent('Query', 'Create');
}
_trackQueryDelete () {
trackEvent('Query', 'Delete');
}
_trackTabBody () {
trackEvent('Request Pane', 'View', 'Body');
}
_trackTabHeaders () {
trackEvent('Request Pane', 'View', 'Headers');
}
_trackTabAuthentication () {
trackEvent('Request Pane', 'View', 'Authentication');
}
_trackTabQuery () {
trackEvent('Request Pane', 'View', 'Query');
}
render () {
const {
request,
environmentId,
showPasswords,
editorFontSize,
editorKeyMap,
@ -317,7 +350,6 @@ RequestPane.propTypes = {
editorKeyMap: PropTypes.string.isRequired,
editorLineWrapping: PropTypes.bool.isRequired,
workspace: PropTypes.object.isRequired,
environmentId: PropTypes.string.isRequired,
forceRefreshCounter: PropTypes.number.isRequired,
// Optional

View File

@ -1,4 +1,5 @@
import React, {PureComponent, PropTypes} from 'react';
import autobind from 'autobind-decorator';
import {remote} from 'electron';
import {DEBOUNCE_MILLIS, isMac} from '../../common/constants';
import {Dropdown, DropdownButton, DropdownItem, DropdownDivider, DropdownHint} from './base/dropdown';
@ -9,39 +10,49 @@ import PromptModal from './modals/PromptModal';
import PromptButton from './base/PromptButton';
import OneLineEditor from './codemirror/OneLineEditor';
@autobind
class RequestUrlBar extends PureComponent {
state = {
currentInterval: null,
currentTimeout: null,
downloadPath: null
};
constructor (props) {
super(props);
this.state = {
currentInterval: null,
currentTimeout: null,
downloadPath: null
};
_urlChangeDebounceTimeout = null;
_lastPastedText = null;
this._urlChangeDebounceTimeout = null;
this._lastPastedText = null;
}
_setDropdownRef = n => this._dropdown = n;
_setInputRef = n => this._input = n;
_setDropdownRef (n) {
this._dropdown = n;
}
_handleMetaClickSend = e => {
_setInputRef (n) {
this._input = n;
}
_handleMetaClickSend (e) {
e.preventDefault();
this._dropdown.show();
};
}
_handleFormSubmit = e => {
_handleFormSubmit (e) {
e.preventDefault();
e.stopPropagation();
this._handleSend();
};
}
_handleMethodChange = method => {
_handleMethodChange (method) {
this.props.onMethodChange(method);
trackEvent('Request', 'Method Change', method);
};
}
_handleUrlChange = url => {
_handleUrlChange (url) {
clearTimeout(this._urlChangeDebounceTimeout);
this._urlChangeDebounceTimeout = setTimeout(async () => {
const pastedText = this._lastPastedText;
// If no pasted text in the queue, just fire the regular change handler
if (!pastedText) {
@ -49,7 +60,6 @@ class RequestUrlBar extends PureComponent {
}
// Reset pasted text cache
const pastedText = this._lastPastedText;
this._lastPastedText = null;
// Attempt to import the pasted text
@ -63,19 +73,19 @@ class RequestUrlBar extends PureComponent {
}
}, DEBOUNCE_MILLIS);
};
}
_handleUrlPaste = e => {
_handleUrlPaste (e) {
// NOTE: We're not actually doing the import here to avoid races with onChange
this._lastPastedText = e.clipboardData.getData('text/plain');
};
}
_handleGenerateCode = () => {
_handleGenerateCode () {
this.props.handleGenerateCode();
trackEvent('Request', 'Generate Code', 'Send Action');
};
}
_handleSetDownloadLocation = () => {
_handleSetDownloadLocation () {
const options = {
title: 'Select Download Location',
buttonLabel: 'Select',
@ -90,13 +100,13 @@ class RequestUrlBar extends PureComponent {
this.setState({downloadPath: paths[0]});
});
};
}
_handleClearDownloadLocation = () => {
_handleClearDownloadLocation () {
this.setState({downloadPath: null});
};
}
_handleKeyDown = e => {
_handleKeyDown (e) {
if (!this._input) {
return;
}
@ -108,9 +118,9 @@ class RequestUrlBar extends PureComponent {
this._input.focus();
this._input.select();
}
};
}
_handleSend = () => {
_handleSend () {
// Don't stop interval because duh, it needs to keep going!
// XXX this._handleStopInterval(); XXX
@ -122,9 +132,9 @@ class RequestUrlBar extends PureComponent {
} else {
this.props.handleSend();
}
};
}
_handleSendAfterDelay = async () => {
async _handleSendAfterDelay () {
const seconds = await showModal(PromptModal, {
inputType: 'decimal',
headerName: 'Send After Delay',
@ -140,7 +150,7 @@ class RequestUrlBar extends PureComponent {
trackEvent('Request', 'Send on Delay', 'Send Action', seconds);
};
_handleSendOnInterval = async () => {
async _handleSendOnInterval () {
const seconds = await showModal(PromptModal, {
inputType: 'decimal',
headerName: 'Send on Interval',
@ -154,17 +164,17 @@ class RequestUrlBar extends PureComponent {
this.setState({currentInterval: seconds});
trackEvent('Request', 'Send on Interval', 'Send Action', seconds);
};
}
_handleStopInterval = () => {
_handleStopInterval () {
clearTimeout(this._sendInterval);
if (this.state.currentInterval) {
this.setState({currentInterval: null});
trackEvent('Request', 'Stop Send Interval');
}
};
}
_handleStopTimeout = () => {
_handleStopTimeout () {
clearTimeout(this._sendTimeout);
if (this.state.currentTimeout) {
this.setState({currentTimeout: null});
@ -172,7 +182,7 @@ class RequestUrlBar extends PureComponent {
}
};
_handleClickSend = e => {
_handleClickSend (e) {
const metaPressed = isMac() ? e.metaKey : e.ctrlKey;
// If we're pressing a meta key, let the dropdown open
@ -184,7 +194,7 @@ class RequestUrlBar extends PureComponent {
// If we're not pressing a meta key, cancel dropdown and send the request
e.stopPropagation(); // Don't trigger the dropdown
this._handleFormSubmit(e);
};
}
componentDidMount () {
document.body.addEventListener('keydown', this._handleKeyDown);

View File

@ -1,4 +1,5 @@
import React, {PropTypes, PureComponent} from 'react';
import autobind from 'autobind-decorator';
import fs from 'fs';
import mime from 'mime-types';
import {remote} from 'electron';
@ -19,10 +20,18 @@ import {getSetCookieHeaders, nullFn} from '../../common/misc';
import {cancelCurrentRequest} from '../../common/network';
import {trackEvent} from '../../analytics';
@autobind
class ResponsePane extends PureComponent {
state = {response: null};
constructor (props) {
super(props);
this.state = {
response: null
};
}
_trackTab = name => trackEvent('Response Pane', 'View', name);
_trackTab (name) {
trackEvent('Response Pane', 'View', name);
}
async _getResponse (requestId, responseId) {
let response = await models.response.getById(responseId);
@ -34,7 +43,7 @@ class ResponsePane extends PureComponent {
this.setState({response});
}
_handleDownloadResponseBody = async () => {
async _handleDownloadResponseBody () {
if (!this.state.response) {
// Should never happen
console.warn('No response to download');
@ -69,7 +78,7 @@ class ResponsePane extends PureComponent {
}
});
});
};
}
componentWillReceiveProps (nextProps) {
const activeRequestId = nextProps.request ? nextProps.request._id : null;
@ -167,27 +176,27 @@ class ResponsePane extends PureComponent {
loadStartTime={loadStartTime}
/>
{!response ? null : (
<header className="pane__header row-spaced">
<div className="no-wrap scrollable scrollable--no-bars pad-left">
<StatusTag
statusCode={response.statusCode}
statusMessage={response.statusMessage || null}
<header className="pane__header row-spaced">
<div className="no-wrap scrollable scrollable--no-bars pad-left">
<StatusTag
statusCode={response.statusCode}
statusMessage={response.statusMessage || null}
/>
<TimeTag milliseconds={response.elapsedTime}/>
<SizeTag bytes={response.bytesRead}/>
</div>
<ResponseHistoryDropdown
requestId={request._id}
isLatestResponseActive={!activeResponseId}
activeResponseId={response._id}
handleSetActiveResponse={handleSetActiveResponse}
handleDeleteResponses={handleDeleteResponses}
onChange={nullFn}
className="tall pane__header__right"
right
/>
<TimeTag milliseconds={response.elapsedTime}/>
<SizeTag bytes={response.bytesRead}/>
</div>
<ResponseHistoryDropdown
requestId={request._id}
isLatestResponseActive={!activeResponseId}
activeResponseId={response._id}
handleSetActiveResponse={handleSetActiveResponse}
handleDeleteResponses={handleDeleteResponses}
onChange={nullFn}
className="tall pane__header__right"
right
/>
</header>
)}
</header>
)}
<Tabs className="pane__body" forceRenderTabPanel>
<TabList>
<Tab>
@ -203,19 +212,19 @@ class ResponsePane extends PureComponent {
<Tab>
<Button onClick={this._trackTab} value="Cookies">
Cookies {cookieHeaders.length ? (
<span className="txt-sm">
<span className="txt-sm">
({cookieHeaders.length})
</span>
) : null}
) : null}
</Button>
</Tab>
<Tab>
<Button onClick={this._trackTab} value="Headers">
Headers {response.headers.length ? (
<span className="txt-sm">
<span className="txt-sm">
({response.headers.length})
</span>
) : null}
) : null}
</Button>
</Tab>
</TabList>

View File

@ -1,4 +1,5 @@
import React, {PropTypes, PureComponent} from 'react';
import autobind from 'autobind-decorator';
import classnames from 'classnames';
import Link from './base/Link';
import * as fetch from '../../common/fetch';
@ -9,20 +10,24 @@ import * as db from '../../common/database';
const LOCALSTORAGE_KEY = 'insomnia::notifications::seen';
@autobind
class Toast extends PureComponent {
state = {notification: null, visible: false};
constructor (props) {
super(props);
this.state = {notification: null, visible: false};
}
_handlePostCTACleanup = () => {
_handlePostCTACleanup () {
trackEvent('Notification', 'Click', this.state.notification.key);
this._dismissNotification();
};
}
_handleCancelClick = () => {
_handleCancelClick () {
trackEvent('Notification', 'Dismiss', this.state.notification.key);
this._dismissNotification();
};
}
_handleCheckNotifications = async () => {
async _handleCheckNotifications () {
// If there is a notification open, skip check
if (this.state.notification) {
return;
@ -69,7 +74,7 @@ class Toast extends PureComponent {
// Fade the notification in
setTimeout(() => this.setState({visible: true}), 1000);
};
}
_loadSeen () {
try {

View File

@ -1,4 +1,5 @@
import React, {PropTypes, PureComponent} from 'react';
import autobind from 'autobind-decorator';
import classnames from 'classnames';
import {showModal, registerModal} from './modals/index';
import AlertModal from '../components/modals/AlertModal';
@ -12,10 +13,10 @@ import PromptModal from '../components/modals/PromptModal';
import RequestCreateModal from '../components/modals/RequestCreateModal';
import RequestPane from './RequestPane';
import RequestSwitcherModal from '../components/modals/RequestSwitcherModal';
import ResponsePane from './ResponsePane';
import SetupSyncModal from '../components/modals/SetupSyncModal';
import SettingsModal from '../components/modals/SettingsModal';
import ResponsePane from './ResponsePane';
import Sidebar from './sidebar/Sidebar';
import SyncLogsModal from '../components/modals/SyncLogsModal';
import WorkspaceEnvironmentsEditModal from '../components/modals/WorkspaceEnvironmentsEditModal';
import WorkspaceSettingsModal from '../components/modals/WorkspaceSettingsModal';
import WorkspaceShareSettingsModal from '../components/modals/WorkspaceShareSettingsModal';
@ -27,11 +28,17 @@ import * as importers from 'insomnia-importers';
const rUpdate = models.request.update;
const sUpdate = models.settings.update;
@autobind
class Wrapper extends PureComponent {
state = {forceRefreshRequestPaneCounter: Date.now()};
constructor (props) {
super(props);
this.state = {
forceRefreshRequestPaneCounter: Date.now()
};
}
// Request updaters
_handleForceUpdateRequest = async patch => {
async _handleForceUpdateRequest (patch) {
const newRequest = await rUpdate(this.props.activeRequest, patch);
// Give it a second for the app to render first. If we don't wait, it will refresh
@ -39,18 +46,38 @@ class Wrapper extends PureComponent {
window.setTimeout(this._forceRequestPaneRefresh, 100);
return newRequest;
};
}
_handleUpdateRequestBody = body => rUpdate(this.props.activeRequest, {body});
_handleUpdateRequestMethod = method => rUpdate(this.props.activeRequest, {method});
_handleUpdateRequestParameters = parameters => rUpdate(this.props.activeRequest, {parameters});
_handleUpdateRequestAuthentication = authentication => rUpdate(this.props.activeRequest, {authentication});
_handleUpdateRequestHeaders = headers => rUpdate(this.props.activeRequest, {headers});
_handleUpdateRequestUrl = url => rUpdate(this.props.activeRequest, {url});
_handleUpdateRequestBody (body) {
rUpdate(this.props.activeRequest, {body});
}
_handleUpdateRequestMethod (method) {
rUpdate(this.props.activeRequest, {method});
}
_handleUpdateRequestParameters (parameters) {
rUpdate(this.props.activeRequest, {parameters});
}
_handleUpdateRequestAuthentication (authentication) {
rUpdate(this.props.activeRequest, {authentication});
}
_handleUpdateRequestHeaders (headers) {
rUpdate(this.props.activeRequest, {headers});
}
_handleUpdateRequestUrl (url) {
rUpdate(this.props.activeRequest, {url});
}
// Special request updaters
_handleUpdateRequestMimeType = mimeType => updateMimeType(this.props.activeRequest, mimeType);
_handleImport = async text => {
_handleUpdateRequestMimeType (mimeType) {
updateMimeType(this.props.activeRequest, mimeType);
}
async _handleImport (text) {
// Allow user to paste any import file into the url. If it results in
// only one item, it will overwrite the current request.
try {
@ -76,29 +103,44 @@ class Wrapper extends PureComponent {
}
return null;
};
}
// Settings updaters
_handleUpdateSettingsShowPasswords = showPasswords => sUpdate(this.props.settings, {showPasswords});
_handleUpdateSettingsUseBulkHeaderEditor = useBulkHeaderEditor => sUpdate(this.props.settings, {useBulkHeaderEditor});
_handleUpdateSettingsShowPasswords (showPasswords) {
sUpdate(this.props.settings, {showPasswords});
}
_handleUpdateSettingsUseBulkHeaderEditor (useBulkHeaderEditor) {
sUpdate(this.props.settings, {useBulkHeaderEditor});
}
// Other Helpers
_handleImportFile = () => {
_handleImportFile () {
this.props.handleImportFileToWorkspace(this.props.activeWorkspace._id);
};
}
_handleExportWorkspaceToFile = () => this.props.handleExportFile(this.props.activeWorkspace._id);
_handleSetActiveResponse = responseId => this.props.handleSetActiveResponse(this.props.activeRequest._id, responseId);
_handleShowEnvironmentsModal = () => showModal(WorkspaceEnvironmentsEditModal, this.props.activeWorkspace);
_handleShowCookiesModal = () => showModal(CookiesModal, this.props.activeWorkspace);
_handleExportWorkspaceToFile () {
this.props.handleExportFile(this.props.activeWorkspace._id);
}
_handleDeleteResponses = () => {
_handleSetActiveResponse (responseId) {
this.props.handleSetActiveResponse(this.props.activeRequest._id, responseId);
}
_handleShowEnvironmentsModal () {
showModal(WorkspaceEnvironmentsEditModal, this.props.activeWorkspace);
}
_handleShowCookiesModal () {
showModal(CookiesModal, this.props.activeWorkspace);
}
_handleDeleteResponses () {
models.response.removeForRequest(this.props.activeRequest._id);
this._handleSetActiveResponse(null);
};
}
_handleRemoveActiveWorkspace = async () => {
async _handleRemoveActiveWorkspace () {
const {workspaces, activeWorkspace} = this.props;
if (workspaces.length <= 1) {
showModal(AlertModal, {
@ -113,37 +155,37 @@ class Wrapper extends PureComponent {
}
models.workspace.remove(activeWorkspace);
};
}
_handleSendRequestWithActiveEnvironment = () => {
_handleSendRequestWithActiveEnvironment () {
const {activeRequest, activeEnvironment, handleSendRequestWithEnvironment} = this.props;
const activeRequestId = activeRequest ? activeRequest._id : 'n/a';
const activeEnvironmentId = activeEnvironment ? activeEnvironment._id : 'n/a';
handleSendRequestWithEnvironment(activeRequestId, activeEnvironmentId);
};
}
_handleSendAndDownloadRequestWithActiveEnvironment = filename => {
_handleSendAndDownloadRequestWithActiveEnvironment (filename) {
const {activeRequest, activeEnvironment, handleSendAndDownloadRequestWithEnvironment} = this.props;
const activeRequestId = activeRequest ? activeRequest._id : 'n/a';
const activeEnvironmentId = activeEnvironment ? activeEnvironment._id : 'n/a';
handleSendAndDownloadRequestWithEnvironment(activeRequestId, activeEnvironmentId, filename);
};
}
_handleSetPreviewMode = previewMode => {
_handleSetPreviewMode (previewMode) {
const activeRequest = this.props.activeRequest;
const activeRequestId = activeRequest ? activeRequest._id : 'n/a';
this.props.handleSetResponsePreviewMode(activeRequestId, previewMode);
};
}
_handleSetResponseFilter = filter => {
_handleSetResponseFilter (filter) {
const activeRequest = this.props.activeRequest;
const activeRequestId = activeRequest ? activeRequest._id : 'n/a';
this.props.handleSetResponseFilter(activeRequestId, filter);
};
}
_forceRequestPaneRefresh = () => {
_forceRequestPaneRefresh () {
this.setState({forceRefreshRequestPaneCounter: Date.now()});
};
}
render () {
const {
@ -243,7 +285,6 @@ class Wrapper extends PureComponent {
editorFontSize={settings.editorFontSize}
editorKeyMap={settings.editorKeyMap}
editorLineWrapping={settings.editorLineWrapping}
environmentId={activeEnvironment ? activeEnvironment._id : 'n/a'}
workspace={activeWorkspace}
forceUpdateRequest={this._handleForceUpdateRequest}
handleCreateRequest={handleCreateRequestForWorkspace}
@ -288,7 +329,6 @@ class Wrapper extends PureComponent {
<AlertModal ref={registerModal}/>
<CookiesModal ref={registerModal}/>
<ChangelogModal ref={registerModal}/>
<SyncLogsModal ref={registerModal}/>
<LoginModal ref={registerModal}/>
<PromptModal ref={registerModal}/>
<RequestCreateModal ref={registerModal}/>
@ -330,6 +370,10 @@ class Wrapper extends PureComponent {
onChange={models.requestGroup.update}
render={handleRender}
/>
<SetupSyncModal
ref={registerModal}
workspace={activeWorkspace}
/>
<WorkspaceEnvironmentsEditModal
ref={registerModal}
onChange={models.workspace.update}

View File

@ -1,8 +1,10 @@
import React, {PureComponent, PropTypes} from 'react';
import autobind from 'autobind-decorator';
import {shell} from 'electron';
@autobind
class Button extends PureComponent {
_handleClick = e => {
_handleClick (e) {
const {onClick, onDisabledClick, disabled} = this.props;
const fn = disabled ? onDisabledClick : onClick;
@ -11,7 +13,7 @@ class Button extends PureComponent {
} else {
fn && fn(e);
}
};
}
render () {
const {children, value, ...props} = this.props;

View File

@ -1,10 +1,17 @@
import React, {PureComponent, PropTypes} from 'react';
import autobind from 'autobind-decorator';
const {clipboard} = require('electron');
@autobind
class CopyButton extends PureComponent {
state = {showConfirmation: false};
constructor (props) {
super(props);
this.state = {
showConfirmation: false,
};
}
_handleClick = e => {
_handleClick (e) {
e.preventDefault();
e.stopPropagation();

View File

@ -1,6 +1,8 @@
import React, {PureComponent, PropTypes} from 'react';
import autobind from 'autobind-decorator';
import {debounce} from '../../../common/misc';
@autobind
class DebouncedInput extends PureComponent {
constructor (props) {
super(props);
@ -12,8 +14,13 @@ class DebouncedInput extends PureComponent {
}
}
_handleChange = e => this._handleValueChange(e.target.value);
_setRef = n => this._input = n;
_handleChange (e) {
this._handleValueChange(e.target.value);
}
_setRef (n) {
this._input = n;
}
getSelectionStart () {
if (this._input) {

View File

@ -1,17 +1,26 @@
import React, {PureComponent, PropTypes} from 'react';
import autobind from 'autobind-decorator';
@autobind
class Editable extends PureComponent {
state = {editing: false};
constructor (props) {
super(props);
this.state = {
editing: false
};
}
_handleSetInputRef = n => this._input = n;
_handleSetInputRef (n) {
this._input = n;
}
_handleSingleClickEditStart = () => {
_handleSingleClickEditStart () {
if (this.props.singleClick) {
this._handleEditStart();
}
};
}
_handleEditStart = () => {
_handleEditStart () {
this.setState({editing: true});
setTimeout(() => {
@ -22,9 +31,9 @@ class Editable extends PureComponent {
if (this.props.onEditStart) {
this.props.onEditStart();
}
};
}
_handleEditEnd = () => {
_handleEditEnd () {
const value = this._input.value.trim();
if (!value) {
@ -37,9 +46,9 @@ class Editable extends PureComponent {
// This timeout prevents the UI from showing the old value after submit.
// It should give the UI enough time to redraw the new value.
setTimeout(async () => this.setState({editing: false}), 100);
};
}
_handleEditKeyDown = e => {
_handleEditKeyDown (e) {
if (e.keyCode === 13) {
// Pressed Enter
this._handleEditEnd();
@ -49,7 +58,7 @@ class Editable extends PureComponent {
// TODO: Make escape blur without saving
this._input && this._input.blur();
}
};
}
render () {
const {value, singleClick, onEditStart, className, ...extra} = this.props;

View File

@ -1,15 +1,23 @@
import React, {PureComponent, PropTypes} from 'react';
import autobind from 'autobind-decorator';
import {basename as pathBasename} from 'path';
import {remote} from 'electron';
@autobind
class FileInputButton extends PureComponent {
constructor (props) {
super(props);
}
focus () {
this._button.focus();
}
_setRef = n => this._button = n;
_setRef (n) {
this._button = n;
}
_handleChooseFile = () => {
_handleChooseFile () {
const options = {
title: 'Import File',
buttonLabel: 'Import',
@ -25,7 +33,7 @@ class FileInputButton extends PureComponent {
const path = paths[0];
this.props.onChange(path);
})
};
}
render () {
const {showFileName, path, name, ...extraProps} = this.props;

View File

@ -1,12 +1,18 @@
import React, {PureComponent, PropTypes} from 'react';
import autobind from 'autobind-decorator';
import {shell} from 'electron';
import {trackEvent} from '../../../analytics/index';
import * as querystring from '../../../common/querystring';
import {getAppVersion} from '../../../common/constants';
import {isDevelopment} from '../../../common/constants';
@autobind
class Link extends PureComponent {
_handleClick = e => {
constructor (props) {
super(props);
}
_handleClick (e) {
e && e.preventDefault();
const {href, onClick} = this.props;

View File

@ -1,4 +1,5 @@
import React, {PureComponent, PropTypes} from 'react';
import autobind from 'autobind-decorator';
import classnames from 'classnames';
import {isMac} from '../../../common/constants';
@ -6,16 +7,32 @@ import {isMac} from '../../../common/constants';
// appear over top of an existing one.
let globalZIndex = 1000;
@autobind
class Modal extends PureComponent {
state = {
open: false,
forceRefreshCounter: 0,
zIndex: globalZIndex
};
constructor (props) {
super(props);
_handleSetNodeRef = n => this._node = n;
this.state = {
open: false,
forceRefreshCounter: 0,
zIndex: globalZIndex
};
}
_handleKeyDown = e => {
_setModalRef (n) {
this._node = n;
this._addListener();
}
_addListener () {
this._node.addEventListener('keydown', this._handleKeyDown);
}
_removeListener () {
this._node.removeEventListener('keydown', this._handleKeyDown);
}
_handleKeyDown (e) {
if (!this.state.open) {
return;
}
@ -27,6 +44,11 @@ class Modal extends PureComponent {
e.stopPropagation();
}
// Don't check for close keys if we don't want them
if (this.props.noEscape) {
return;
}
const closeOnKeyCodes = this.props.closeOnKeyCodes || [];
const pressedEscape = e.keyCode === 27;
const pressedElse = closeOnKeyCodes.find(c => c === e.keyCode);
@ -36,9 +58,14 @@ class Modal extends PureComponent {
// Pressed escape
this.hide();
}
};
}
_handleClick (e) {
// Don't check for close keys if we don't want them
if (this.props.noEscape) {
return;
}
_handleClick = e => {
// Did we click a close button. Let's check a few parent nodes up as well
// because some buttons might have nested elements. Maybe there is a better
// way to check this?
@ -57,7 +84,7 @@ class Modal extends PureComponent {
if (shouldHide) {
this.hide();
}
};
}
show () {
const {freshState} = this.props;
@ -88,16 +115,12 @@ class Modal extends PureComponent {
this.setState({open: false});
}
componentDidMount () {
this._node.addEventListener('keydown', this._handleKeyDown);
}
componentWillUnmount () {
this._node.removeEventListener('keydown', this._handleKeyDown);
this._removeListener();
}
render () {
const {tall, top, wide, className} = this.props;
const {tall, top, wide, noEscape, className} = this.props;
const {open, zIndex, forceRefreshCounter} = this.state;
const classes = classnames(
@ -106,17 +129,18 @@ class Modal extends PureComponent {
{'modal--open': open},
{'modal--fixed-height': tall},
{'modal--fixed-top': top},
{'modal--noescape': noEscape},
{'modal--wide': wide},
);
return (
<div ref={this._handleSetNodeRef}
<div ref={this._setModalRef}
tabIndex="-1"
className={classes}
style={{zIndex: zIndex}}
onClick={this._handleClick}>
<div className="modal__content" key={forceRefreshCounter}>
<div className="modal__backdrop overlay" onClick={() => this.hide()}></div>
<div className="modal__backdrop overlay" data-close-modal></div>
{this.props.children}
</div>
</div>
@ -128,6 +152,7 @@ Modal.propTypes = {
tall: PropTypes.bool,
top: PropTypes.bool,
wide: PropTypes.bool,
noEscape: PropTypes.bool,
dontFocus: PropTypes.bool,
closeOnKeyCodes: PropTypes.array,
freshState: PropTypes.bool,

View File

@ -9,7 +9,7 @@ class ModalHeader extends PureComponent {
if (!hideCloseButton) {
closeButton = (
<button type="button" className="btn btn--compact modal__close-btn" data-close-modal="true">
<i className="fa fa-times"></i>
<i className="fa fa-times"/>
</button>
)
}

View File

@ -1,12 +1,20 @@
import React, {PureComponent, PropTypes} from 'react';
import autobind from 'autobind-decorator';
import Button from '../base/Button';
const STATE_DEFAULT = 'default';
const STATE_ASK = 'ask';
const STATE_DONE = 'done';
@autobind
class PromptButton extends PureComponent {
state = {state: STATE_DEFAULT};
constructor (props) {
super(props);
this.state = {
state: STATE_DEFAULT
};
}
_confirm (...args) {
// Clear existing timeouts
@ -42,7 +50,7 @@ class PromptButton extends PureComponent {
}, 2000);
}
_handleClick = (...args) => {
_handleClick (...args) {
const {state} = this.state;
if (state === STATE_ASK) {
this._confirm(...args)
@ -51,7 +59,7 @@ class PromptButton extends PureComponent {
} else {
// Do nothing
}
};
}
componentWillUnmount () {
clearTimeout(this._triggerTimeout);

View File

@ -1,18 +1,28 @@
import React, {PureComponent, PropTypes} from 'react';
import ReactDOM from 'react-dom';
import autobind from 'autobind-decorator';
import classnames from 'classnames';
import DropdownButton from './DropdownButton';
import DropdownItem from './DropdownItem';
import DropdownDivider from './DropdownDivider';
@autobind
class Dropdown extends PureComponent {
state = {
open: false,
dropUp: false,
focused: false,
constructor (props) {
super(props);
this.state = {
open: false,
dropUp: false,
focused: false,
};
}
_setRef (n) {
this._node = n;
};
_handleKeyDown = e => {
_handleKeyDown (e) {
// Catch all key presses if we're open
if (this.state.open) {
e.stopPropagation();
@ -23,30 +33,51 @@ class Dropdown extends PureComponent {
e.preventDefault();
this.hide();
}
};
}
_checkSize = () => {
_checkSize () {
if (!this.state.open) {
return;
}
// Make the dropdown scroll if it drops off screen.
const rect = this._dropdownList.getBoundingClientRect();
const rect = ReactDOM.findDOMNode(this._dropdownList).getBoundingClientRect();
const maxHeight = document.body.clientHeight - rect.top - 10;
this._dropdownList.style.maxHeight = `${maxHeight}px`;
};
}
_handleClick = () => {
_handleClick () {
this.toggle();
};
}
_handleMouseDown = e => {
_handleMouseDown (e) {
// Intercept mouse down so that clicks don't trigger things like
// drag and drop.
e.preventDefault();
};
_addDropdownListRef = n => this._dropdownList = n;
_addDropdownListRef (n) {
this._dropdownList = n;
};
_getFlattenedChildren (children) {
let newChildren = [];
for (const child of children) {
if (!child) {
// Ignore null components
continue;
}
if (Array.isArray(child)) {
newChildren = [...newChildren, ...this._getFlattenedChildren(child)];
} else {
newChildren.push(child);
}
}
return newChildren
}
componentDidUpdate () {
this._checkSize();
@ -69,7 +100,7 @@ class Dropdown extends PureComponent {
show () {
const bodyHeight = document.body.getBoundingClientRect().height;
const dropdownTop = ReactDOM.findDOMNode(this).getBoundingClientRect().top;
const dropdownTop = this._node.getBoundingClientRect().top;
const dropUp = dropdownTop > bodyHeight - 200;
this.setState({open: true, dropUp});
@ -84,25 +115,6 @@ class Dropdown extends PureComponent {
}
}
_getFlattenedChildren (children) {
let newChildren = [];
for (const child of children) {
if (!child) {
// Ignore null components
continue;
}
if (Array.isArray(child)) {
newChildren = [...newChildren, ...this._getFlattenedChildren(child)];
} else {
newChildren.push(child);
}
}
return newChildren
}
render () {
const {right, outline, wide, className, style, children} = this.props;
const {dropUp, open} = this.state;
@ -120,20 +132,24 @@ class Dropdown extends PureComponent {
const dropdownButtons = [];
const dropdownItems = [];
const allChildren = this._getFlattenedChildren(Array.isArray(children) ? children : [children]);
const listedChildren = Array.isArray(children) ? children : [children];
const allChildren = this._getFlattenedChildren(listedChildren);
for (const child of allChildren) {
if (child.type === DropdownButton) {
if (child.type.name === DropdownButton.name) {
dropdownButtons.push(child);
} else if (child.type === DropdownItem) {
} else if (child.type.name === DropdownItem.name) {
dropdownItems.push(child);
} else if (child.type === DropdownDivider) {
} else if (child.type.name === DropdownDivider.name) {
dropdownItems.push(child);
}
}
let finalChildren = [];
if (dropdownButtons.length !== 1) {
console.error(`Dropdown needs exactly one DropdownButton! Got ${dropdownButtons.length}`, this.props);
console.error(
`Dropdown needs exactly one DropdownButton! Got ${dropdownButtons.length}`,
{allChildren}
);
} else {
finalChildren = [
dropdownButtons[0],
@ -146,6 +162,7 @@ class Dropdown extends PureComponent {
return (
<div style={style}
className={classes}
ref={this._setRef}
onClick={this._handleClick}
onMouseDown={this._handleMouseDown}>
{finalChildren}

View File

@ -1,10 +1,15 @@
import React from 'react';
import React, {PureComponent} from 'react';
const DropdownButton = ({children, ...props}) => (
<button type="button" {...props}>
{children}
</button>
);
class DropdownButton extends PureComponent {
render () {
const {children, ...props} = this.props;
return (
<button type="button" {...props}>
{children}
</button>
)
}
}
DropdownButton.propTypes = {};

View File

@ -1,8 +1,14 @@
import React, {PureComponent, PropTypes} from 'react';
import autobind from 'autobind-decorator';
import classnames from 'classnames';
@autobind
class DropdownItem extends PureComponent {
_handleClick = e => {
constructor (props) {
super(props);
}
_handleClick (e) {
const {stayOpenAfterClick, onClick, disabled} = this.props;
if (stayOpenAfterClick) {
@ -18,7 +24,7 @@ class DropdownItem extends PureComponent {
} else {
onClick(e);
}
};
}
render () {
const {

View File

@ -2,12 +2,10 @@ import _Dropdown from './Dropdown';
import _DropdownButton from './DropdownButton';
import _DropdownDivider from './DropdownDivider';
import _DropdownHint from './DropdownHint';
import _DropdownRight from './DropdownRight';
import _DropdownItem from './DropdownItem';
export const Dropdown = _Dropdown;
export const DropdownButton = _DropdownButton;
export const DropdownDivider = _DropdownDivider;
export const DropdownHint = _DropdownHint;
export const DropdownRight = _DropdownRight;
export const DropdownItem = _DropdownItem;

View File

@ -1,4 +1,5 @@
import React, {PureComponent, PropTypes} from 'react';
import autobind from 'autobind-decorator';
import {getDOMNode} from 'react-dom';
import CodeMirror from 'codemirror';
import classnames from 'classnames';
@ -90,12 +91,15 @@ const BASE_CODEMIRROR_OPTIONS = {
}
};
@autobind
class Editor extends PureComponent {
constructor (props) {
super(props);
this.state = {
filter: props.filter || ''
};
this._originalCode = '';
}
@ -187,7 +191,7 @@ class Editor extends PureComponent {
}
}
_handleInitTextarea = textarea => {
_handleInitTextarea (textarea) {
if (!textarea) {
// Not mounted
return;
@ -232,6 +236,11 @@ class Editor extends PureComponent {
if (this.props.render) {
this.codeMirror.enableNunjucksTags(this.props.render);
}
// Make URLs clickable
if (this.props.onClickLink) {
this.codeMirror.makeLinksClickable(this.props.onClickLink);
}
};
// Do this a bit later for big values so we don't block the render process
@ -258,10 +267,10 @@ class Editor extends PureComponent {
return mode.indexOf('xml') !== -1
}
_handleBeautify = () => {
_handleBeautify () {
trackEvent('Request', 'Beautify');
this._prettify(this.codeMirror.getValue());
};
}
_prettify (code) {
this._codemirrorSetValue(code, true);
@ -311,7 +320,7 @@ class Editor extends PureComponent {
/**
* Sets options on the CodeMirror editor while also sanitizing them
*/
_codemirrorSetOptions = () => {
_codemirrorSetOptions () {
const {
mode: rawMode,
readOnly,
@ -328,16 +337,15 @@ class Editor extends PureComponent {
} = this.props;
let mode;
if (this.props.readOnly) {
// Should probably have an actual prop for this, but let's not
// enable nunjucks on editors that the user can modify
mode = this._normalizeMode(rawMode);
} else {
if (this.props.render) {
mode = {name: 'nunjucks', baseMode: this._normalizeMode(rawMode)};
} else {
// foo bar baz
mode = this._normalizeMode(rawMode);
}
let options = {
readOnly,
readOnly: !!readOnly,
placeholder: placeholder || '',
mode: mode,
tabIndex: typeof tabIndex === 'number' ? tabIndex : null,
@ -376,9 +384,6 @@ class Editor extends PureComponent {
cm.setOption(key, options[key]);
});
// Add overlays;
this.codeMirror.makeLinksClickable(this.props.onClickLink);
};
_normalizeMode (mode) {
@ -391,9 +396,9 @@ class Editor extends PureComponent {
} else {
return mimeType;
}
};
}
_codemirrorKeyDown = (doc, e) => {
_codemirrorKeyDown (doc, e) {
// Use default tab behaviour if we're told
if (this.props.defaultTabBehavior && e.keyCode === TAB_KEY) {
e.codemirrorIgnore = true;
@ -402,21 +407,21 @@ class Editor extends PureComponent {
if (this.props.onKeyDown) {
this.props.onKeyDown(e, doc.getValue());
}
};
}
_codemirrorFocus = (doc, e) => {
_codemirrorFocus (doc, e) {
if (this.props.onFocus) {
this.props.onFocus(e);
}
};
}
_codemirrorBlur = (doc, e) => {
_codemirrorBlur (doc, e) {
if (this.props.onBlur) {
this.props.onBlur(e);
}
};
}
_codemirrorValueBeforeChange = (doc, change) => {
_codemirrorValueBeforeChange (doc, change) {
// If we're in single-line mode, merge all changed lines into one
if (this.props.singleLine && change.text.length > 1) {
const text = change.text
@ -426,12 +431,12 @@ class Editor extends PureComponent {
const to = {ch: from.ch + text.length, line: 0};
change.update(from, to, [text]);
}
};
}
/**
* Wrapper function to add extra behaviour to our onChange event
*/
_codemirrorValueChanged = () => {
_codemirrorValueChanged () {
// Don't trigger change event if we're ignoring changes
if (this._ignoreNextChange || !this.props.onChange) {
this._ignoreNextChange = false;
@ -440,7 +445,7 @@ class Editor extends PureComponent {
const value = this.codeMirror.getDoc().getValue();
this.props.onChange(value);
};
}
/**
* Sets the CodeMirror value without triggering the onChange event
@ -468,7 +473,7 @@ class Editor extends PureComponent {
this.codeMirror.setValue(code || '');
}
_handleFilterChange = e => {
_handleFilterChange (e) {
const filter = e.target.value;
clearTimeout(this._filterTimeout);
@ -490,7 +495,7 @@ class Editor extends PureComponent {
`${filter ? 'Change' : 'Clear'}`
);
}, 2000);
};
}
_canPrettify () {
const {mode} = this.props;

View File

@ -1,4 +1,5 @@
import React, {PureComponent, PropTypes} from 'react';
import autobind from 'autobind-decorator';
import Editor from './Editor';
import Input from '../base/DebouncedInput';
@ -7,6 +8,7 @@ const MODE_EDITOR = 'editor';
const TYPE_TEXT = 'text';
const NUNJUCKS_REGEX = /({%|%}|{{|}})/;
@autobind
class OneLineEditor extends PureComponent {
constructor (props) {
super(props);
@ -43,12 +45,12 @@ class OneLineEditor extends PureComponent {
}
}
_handleEditorFocus = e => {
_handleEditorFocus (e) {
this._editor.focusEnd();
this.props.onFocus && this.props.onFocus(e);
};
}
_handleInputFocus = e => {
_handleInputFocus (e) {
if (this.props.blurOnFocus) {
e.target.blur();
} else {
@ -65,9 +67,9 @@ class OneLineEditor extends PureComponent {
// Also call the regular callback
this.props.onFocus && this.props.onFocus(e);
};
}
_handleInputChange = value => {
_handleInputChange (value) {
if (!this.props.forceInput && this._mayContainNunjucks(value)) {
const start = this._input.getSelectionStart();
const end = this._input.getSelectionEnd();
@ -87,15 +89,15 @@ class OneLineEditor extends PureComponent {
}
this.props.onChange && this.props.onChange(value);
};
}
_handleInputKeyDown = e => {
_handleInputKeyDown (e) {
if (this.props.onKeyDown) {
this.props.onKeyDown(e, e.target.value);
}
};
}
_handleEditorBlur = e => {
_handleEditorBlur () {
if (this.props.forceEditor) {
return;
}
@ -107,9 +109,9 @@ class OneLineEditor extends PureComponent {
this.setState({mode: MODE_INPUT});
this.props.onBlur && this.props.onBlur();
};
}
_handleEditorKeyDown = e => {
_handleEditorKeyDown (e) {
// submit form if needed
if (e.keyCode === 13) {
let node = e.target;
@ -124,11 +126,19 @@ class OneLineEditor extends PureComponent {
// Also call the original if there was one
this.props.onKeyDown && this.props.onKeyDown(e, this.getValue());
};
}
_setEditorRef = n => this._editor = n;
_setInputRef = n => this._input = n;
_mayContainNunjucks = text => !!text.match(NUNJUCKS_REGEX);
_setEditorRef (n) {
this._editor = n;
}
_setInputRef (n) {
this._input = n;
}
_mayContainNunjucks (text) {
return !!text.match(NUNJUCKS_REGEX);
}
render () {
const {
@ -162,8 +172,8 @@ class OneLineEditor extends PureComponent {
placeholder={placeholder}
onBlur={this._handleEditorBlur}
onKeyDown={this._handleEditorKeyDown}
onChange={onChange}
onFocus={this._handleEditorFocus}
onChange={onChange}
render={render}
className="editor--single-line"
defaultValue={defaultValue}

View File

@ -1,4 +1,5 @@
import React, {PropTypes, PureComponent} from 'react';
import autobind from 'autobind-decorator';
import {Dropdown, DropdownButton, DropdownItem, DropdownDivider} from '../base/dropdown';
import {contentTypesMap} from '../../../common/constants';
import {trackEvent} from '../../../analytics/index';
@ -7,11 +8,16 @@ import {getContentTypeName} from '../../../common/constants';
const EMPTY_MIME_TYPE = null;
@autobind
class ContentTypeDropdown extends PureComponent {
_handleChangeMimeType = mimeType => {
constructor (props) {
super(props);
}
_handleChangeMimeType (mimeType) {
this.props.onChange(mimeType);
trackEvent('Request', 'Content-Type Change', contentTypesMap[mimeType]);
};
}
_renderDropdownItem (mimeType, forcedName = null) {
const contentType = typeof this.props.contentType !== 'string' ?

View File

@ -1,13 +1,19 @@
import React, {PureComponent, PropTypes} from 'react';
import autobind from 'autobind-decorator';
import {Dropdown, DropdownDivider, DropdownButton, DropdownItem} from '../base/dropdown';
import {PREVIEW_MODES, getPreviewModeName} from '../../../common/constants';
import {trackEvent} from '../../../analytics/index';
@autobind
class PreviewModeDropdown extends PureComponent {
_handleClick = previewMode => {
constructor (props) {
super(props);
}
_handleClick (previewMode) {
this.props.updatePreviewMode(previewMode);
trackEvent('Response', 'Preview Mode Change', previewMode);
};
}
render () {
const {download, previewMode} = this.props;

View File

@ -1,4 +1,5 @@
import React, {PureComponent, PropTypes} from 'react';
import autobind from 'autobind-decorator';
import PromptButton from '../base/PromptButton';
import {Dropdown, DropdownHint, DropdownButton, DropdownItem} from '../base/dropdown';
import PromptModal from '../modals/PromptModal';
@ -6,22 +7,27 @@ import * as models from '../../../models';
import {showModal} from '../modals/index';
import {trackEvent} from '../../../analytics/index';
@autobind
class RequestActionsDropdown extends PureComponent {
_setDropdownRef = n => this._dropdown = n;
constructor (props) {
super(props);
}
_setDropdownRef (n) {
this._dropdown = n;
}
_handleDuplicate = () => {
_handleDuplicate () {
const {request, handleDuplicateRequest} = this.props;
handleDuplicateRequest(request);
trackEvent('Request', 'Duplicate', 'Request Action');
};
}
_handleGenerateCode = () => {
_handleGenerateCode () {
this.props.handleGenerateCode(this.props.request);
trackEvent('Request', 'Generate Code', 'Request Action');
};
}
_handlePromptUpdateName = async () => {
async _handlePromptUpdateName () {
const {request} = this.props;
const name = await showModal(PromptModal, {
@ -33,13 +39,13 @@ class RequestActionsDropdown extends PureComponent {
models.request.update(request, {name});
trackEvent('Request', 'Rename', 'Request Action');
};
}
_handleRemove = () => {
_handleRemove () {
const {request} = this.props;
models.request.remove(request);
trackEvent('Request', 'Delete', 'Action');
};
}
show () {
this._dropdown.show();

View File

@ -1,4 +1,5 @@
import React, {PureComponent, PropTypes} from 'react';
import autobind from 'autobind-decorator';
import PromptButton from '../base/PromptButton';
import {Dropdown, DropdownButton, DropdownItem, DropdownDivider, DropdownHint} from '../base/dropdown';
import EnvironmentEditModal from '../modals/EnvironmentEditModal';
@ -7,10 +8,17 @@ import * as models from '../../../models';
import {showModal} from '../modals';
import {trackEvent} from '../../../analytics/index';
@autobind
class RequestGroupActionsDropdown extends PureComponent {
_setDropdownRef = n => this._dropdown = n;
constructor (props) {
super(props);
}
_handleRename = async () => {
_setDropdownRef (n) {
this._dropdown = n;
}
async _handleRename () {
const {requestGroup} = this.props;
const name = await showModal(PromptModal, {
@ -21,31 +29,31 @@ class RequestGroupActionsDropdown extends PureComponent {
models.requestGroup.update(requestGroup, {name});
trackEvent('Folder', 'Rename', 'Folder Action');
};
}
_handleRequestCreate = async () => {
async _handleRequestCreate () {
this.props.handleCreateRequest(this.props.requestGroup._id);
trackEvent('Request', 'Create', 'Folder Action');
};
}
_handleRequestGroupDuplicate = () => {
_handleRequestGroupDuplicate () {
this.props.handleDuplicateRequestGroup(this.props.requestGroup);
trackEvent('Folder', 'Duplicate', 'Folder Action');
};
}
_handleRequestGroupCreate = async () => {
async _handleRequestGroupCreate () {
this.props.handleCreateRequestGroup(this.props.requestGroup._id);
trackEvent('Folder', 'Create', 'Folder Action');
};
}
_handleDeleteFolder = () => {
_handleDeleteFolder () {
models.requestGroup.remove(this.props.requestGroup);
trackEvent('Folder', 'Delete', 'Folder Action');
};
}
_handleEditEnvironment = () => {
_handleEditEnvironment () {
showModal(EnvironmentEditModal, this.props.requestGroup);
};
}
show () {
this._dropdown.show();

View File

@ -1,4 +1,5 @@
import React, {PropTypes, PureComponent} from 'react';
import autobind from 'autobind-decorator';
import {Dropdown, DropdownButton, DropdownItem, DropdownDivider} from '../base/dropdown';
import SizeTag from '../tags/SizeTag';
import StatusTag from '../tags/StatusTag';
@ -8,22 +9,28 @@ import PromptButton from '../base/PromptButton';
import {trackEvent} from '../../../analytics/index';
import * as misc from '../../../common/misc';
@autobind
class ResponseHistoryDropdown extends PureComponent {
state = {
responses: [],
};
constructor (props) {
super(props);
this.state = {
responses: [],
};
_handleDeleteResponses = () => {
this._load = misc.debounce(this._load);
}
_handleDeleteResponses () {
trackEvent('History', 'Delete Responses');
this.props.handleDeleteResponses(this.props.requestId);
};
}
_handleSetActiveResponse = responseId => {
_handleSetActiveResponse (responseId) {
trackEvent('History', 'Activate Response');
this.props.handleSetActiveResponse(responseId);
};
}
_load = misc.debounce(async requestId => {
async _load (requestId) {
const responses = await models.response.findRecentForRequest(requestId);
// NOTE: this is bad practice, but I can't figure out a better way.
@ -36,7 +43,7 @@ class ResponseHistoryDropdown extends PureComponent {
if (this.state.responses.length !== responses.length) {
this.setState({responses});
}
});
}
componentWillUnmount () {
this._unmounted = true;
@ -51,7 +58,7 @@ class ResponseHistoryDropdown extends PureComponent {
this._load(this.props.requestId);
}
renderDropdownItem = (response, i) => {
renderDropdownItem (response, i) {
const {activeResponseId} = this.props;
const active = response._id === activeResponseId;
return (
@ -68,7 +75,7 @@ class ResponseHistoryDropdown extends PureComponent {
<SizeTag bytes={response.bytesRead} small/>
</DropdownItem>
)
};
}
render () {
const {

View File

@ -1,35 +1,45 @@
import React, {PureComponent, PropTypes} from 'react';
import autobind from 'autobind-decorator';
import {Dropdown, DropdownDivider, DropdownItem, DropdownButton} from '../base/dropdown';
import {showModal} from '../modals';
import SyncLogsModal from '../modals/SyncLogsModal';
import * as syncStorage from '../../../sync/storage';
import * as session from '../../../sync/session';
import * as sync from '../../../sync';
import {trackEvent} from '../../../analytics';
import WorkspaceShareSettingsModal from '../modals/WorkspaceShareSettingsModal';
import SetupSyncModal from '../modals/SetupSyncModal';
@autobind
class SyncDropdown extends PureComponent {
state = {
loggedIn: null,
syncData: null,
loading: false,
};
constructor (props) {
super(props);
_trackShowMenu = () => trackEvent('Sync', 'Show Menu', 'Authenticated');
_handleShowLogs = () => showModal(SyncLogsModal);
this._hasPrompted = false;
_handleShowShareSettings = () => {
this.state = {
loggedIn: null,
syncData: null,
loading: false,
};
}
_trackShowMenu () {
trackEvent('Sync', 'Show Menu', 'Authenticated');
}
_handleShowShareSettings () {
showModal(WorkspaceShareSettingsModal, {workspace: this.props.workspace});
};
}
_handleToggleSyncMode = async () => {
async _handleToggleSyncMode () {
const {syncData} = this.state;
const resourceGroupId = syncData.resourceGroupId;
const config = await sync.getOrCreateConfig(resourceGroupId);
let syncMode = config.syncMode === syncStorage.SYNC_MODE_OFF ?
syncStorage.SYNC_MODE_ON : syncStorage.SYNC_MODE_OFF;
let syncMode = config.syncMode !== syncStorage.SYNC_MODE_ON ?
syncStorage.SYNC_MODE_ON :
syncStorage.SYNC_MODE_OFF;
await sync.createOrUpdateConfig(resourceGroupId, syncMode);
@ -41,9 +51,9 @@ class SyncDropdown extends PureComponent {
}
trackEvent('Sync', 'Change Mode', syncMode);
};
}
_handleSyncResourceGroupId = async () => {
async _handleSyncResourceGroupId () {
const {syncData} = this.state;
const resourceGroupId = syncData.resourceGroupId;
@ -60,7 +70,7 @@ class SyncDropdown extends PureComponent {
this.setState({loading: false});
trackEvent('Sync', 'Manual Sync');
};
}
async _reloadData () {
const loggedIn = session.isLoggedIn();
@ -94,6 +104,11 @@ class SyncDropdown extends PureComponent {
this.setState({syncData});
}
async _handleShowSyncModePrompt () {
await showModal(SetupSyncModal);
await this._reloadData();
};
componentWillMount () {
this._interval = setInterval(() => this._reloadData(), 2000);
this._reloadData();
@ -103,16 +118,40 @@ class SyncDropdown extends PureComponent {
clearInterval(this._interval);
}
_getSyncDescription (syncMode, syncPercentage) {
if (syncPercentage === 100) {
return 'Up To Date'
} else {
return syncMode === syncStorage.SYNC_MODE_ON ? 'Sync Pending' : 'Sync Required'
async componentDidUpdate () {
const {syncData} = this.state;
if (!syncData) {
return;
}
// Sync has not yet been configured for this workspace, so prompt the user to do so
const isModeUnset = syncData.syncMode === syncStorage.SYNC_MODE_UNSET;
if (isModeUnset && !this._hasPrompted) {
this._hasPrompted = true;
await this._handleShowSyncModePrompt();
}
}
_getSyncDescription (syncMode, syncPercentage) {
let el = null;
if (syncMode === syncStorage.SYNC_MODE_NEVER) {
el = <span>Sync Disabled</span>
} else if (syncPercentage === 100) {
el = <span>Sync Up To Date</span>
} else if (syncMode === syncStorage.SYNC_MODE_OFF) {
el = <span><i className="fa fa-pause-circle-o"/> Sync Required</span>
} else if (syncMode === syncStorage.SYNC_MODE_ON) {
el = <span>Sync Pending</span>
} else if (syncMode === syncStorage.SYNC_MODE_UNSET) {
el = <span><i className="fa fa-exclamation-circle"/> Configure Sync</span>
}
return el;
}
render () {
const {className, workspace} = this.props;
const {className} = this.props;
const {syncData, loading, loggedIn} = this.state;
// Don't show the sync menu unless we're logged in
@ -130,39 +169,51 @@ class SyncDropdown extends PureComponent {
)
} else {
const {syncMode, syncPercent} = syncData;
const description = this._getSyncDescription(syncMode, syncPercent);
const isPaused = syncMode === syncStorage.SYNC_MODE_OFF;
return (
<div className={className}>
<Dropdown wide className="wide tall">
<DropdownButton className="btn btn--compact wide" onClick={this._trackShowMenu}>
{isPaused ? <span><i className="fa fa-pause-circle"/>&nbsp;</span> : null}
{description}
{this._getSyncDescription(syncMode, syncPercent)}
</DropdownButton>
<DropdownDivider>Workspace Synced {syncPercent}%</DropdownDivider>
<DropdownItem onClick={this._handleToggleSyncMode} stayOpenAfterClick>
{syncMode === syncStorage.SYNC_MODE_OFF ?
<i className="fa fa-toggle-off"></i> :
<i className="fa fa-toggle-on"></i>}
Automatic Sync
</DropdownItem>
<DropdownItem onClick={this._handleSyncResourceGroupId} stayOpenAfterClick>
{loading ?
<i className="fa fa-refresh fa-spin"></i> :
<i className="fa fa-cloud-upload"></i>}
Sync Now
</DropdownItem>
<DropdownDivider>Other</DropdownDivider>
<DropdownItem onClick={this._handleShowShareSettings}>
<i className="fa fa-users"></i>
Configure Sharing
</DropdownItem>
<DropdownItem onClick={this._handleShowLogs}>
<i className="fa fa-bug"></i>
Show Debug Logs
</DropdownItem>
{/* SYNC DISABLED */}
{syncMode === syncStorage.SYNC_MODE_NEVER ?
<DropdownItem onClick={this._handleShowSyncModePrompt}>
<i className="fa fa-wrench"/>
Change Sync Mode
</DropdownItem> : null
}
{/* SYNCED */}
{syncMode !== syncStorage.SYNC_MODE_NEVER ?
<DropdownItem onClick={this._handleToggleSyncMode} stayOpenAfterClick={true}>
{syncMode === syncStorage.SYNC_MODE_ON ?
<i className="fa fa-toggle-on"/> :
<i className="fa fa-toggle-off"/>
}
Automatic Sync
</DropdownItem> : null
}
{syncMode !== syncStorage.SYNC_MODE_NEVER ?
<DropdownItem onClick={this._handleSyncResourceGroupId} stayOpenAfterClick>
{loading ?
<i className="fa fa-refresh fa-spin"/> :
<i className="fa fa-cloud-upload"/>
}
Sync Now
</DropdownItem> : null
}
{syncMode !== syncStorage.SYNC_MODE_NEVER ?
<DropdownItem onClick={this._handleShowShareSettings}>
<i className="fa fa-users"></i>
Share With Others
</DropdownItem> : null
}
</Dropdown>
</div>
);

View File

@ -1,7 +1,12 @@
import React, {PureComponent, PropTypes} from 'react';
import classnames from 'classnames';
import autobind from 'autobind-decorator';
import * as classnames from 'classnames';
import {ipcRenderer, shell} from 'electron';
import {Dropdown, DropdownDivider, DropdownButton, DropdownItem, DropdownHint, DropdownRight} from '../base/dropdown';
import Dropdown from '../base/dropdown/Dropdown';
import DropdownDivider from '../base/dropdown/DropdownDivider';
import DropdownButton from '../base/dropdown/DropdownButton';
import DropdownItem from '../base/dropdown/DropdownItem';
import DropdownHint from '../base/dropdown/DropdownHint';
import PromptModal from '../modals/PromptModal';
import SettingsModal, {TAB_INDEX_EXPORT} from '../modals/SettingsModal';
import * as models from '../../../models';
@ -15,41 +20,49 @@ import * as session from '../../../sync/session';
import PromptButton from '../base/PromptButton';
import LoginModal from '../modals/LoginModal';
@autobind
class WorkspaceDropdown extends PureComponent {
state = {
loggedIn: false
};
constructor (props) {
super(props);
this.state = {
loggedIn: false
};
}
_handleDropdownOpen = () => {
_handleDropdownOpen () {
if (this.state.loggedIn !== session.isLoggedIn()) {
this.setState({loggedIn: session.isLoggedIn()});
}
};
}
_handleShowLogin = () => {
_handleShowLogin () {
showModal(LoginModal);
};
}
_handleShowExport = () => showModal(SettingsModal, TAB_INDEX_EXPORT);
_handleShowSettings = () => showModal(SettingsModal);
_handleShowWorkspaceSettings = () => {
_handleShowExport () {
showModal(SettingsModal, TAB_INDEX_EXPORT);
}
_handleShowSettings () {
showModal(SettingsModal);
}
_handleShowWorkspaceSettings () {
showModal(WorkspaceSettingsModal, {
workspace: this.props.activeWorkspace,
});
};
}
_handleShowShareSettings = () => {
_handleShowShareSettings () {
showModal(WorkspaceShareSettingsModal, {
workspace: this.props.activeWorkspace,
});
};
}
_handleSwitchWorkspace = workspaceId => {
_handleSwitchWorkspace (workspaceId) {
this.props.handleSetActiveWorkspace(workspaceId);
trackEvent('Workspace', 'Switch');
};
}
_handleWorkspaceCreate = async noTrack => {
async _handleWorkspaceCreate (noTrack) {
const name = await showModal(PromptModal, {
headerName: 'Create New Workspace',
defaultValue: 'My Workspace',
@ -63,7 +76,7 @@ class WorkspaceDropdown extends PureComponent {
if (!noTrack) {
trackEvent('Workspace', 'Create');
}
};
}
render () {
const {

View File

@ -1,17 +1,28 @@
import React, {PropTypes, PureComponent} from 'react';
import autobind from 'autobind-decorator';
import KeyValueEditor from '../keyvalueeditor/Editor';
import {trackEvent} from '../../../analytics/index';
@autobind
class AuthEditor extends PureComponent {
_handleOnCreate = () => trackEvent('Auth Editor', 'Create');
_handleOnDelete = () => trackEvent('Auth Editor', 'Delete');
constructor (props) {
super(props);
}
_handleToggleDisable = pair => {
_handleOnCreate () {
trackEvent('Auth Editor', 'Create');
}
_handleOnDelete () {
trackEvent('Auth Editor', 'Delete');
}
_handleToggleDisable (pair) {
const label = pair.disabled ? 'Disable' : 'Enable';
trackEvent('Auth Editor', 'Toggle', label);
};
}
_handleChange = pairs => {
_handleChange (pairs) {
const pair = {
username: pairs.length ? pairs[0].name : '',
password: pairs.length ? pairs[0].value : '',
@ -19,7 +30,7 @@ class AuthEditor extends PureComponent {
};
this.props.onChange(pair);
};
}
render () {
const {authentication, showPasswords, handleRender} = this.props;

View File

@ -1,4 +1,5 @@
import React, {PropTypes, PureComponent} from 'react';
import autobind from 'autobind-decorator';
import {Cookie} from 'tough-cookie';
import PromptButton from '../base/PromptButton';
@ -6,9 +7,13 @@ import CookieInput from '../CookieInput';
import {cookieToString} from '../../../common/cookies';
import {DEBOUNCE_MILLIS} from '../../../common/constants';
@autobind
class CookiesEditor extends PureComponent {
_handleCookieAdd = () => {
constructor (props) {
super(props);
}
_handleCookieAdd () {
const newCookie = new Cookie({
key: 'foo',
value: 'bar',
@ -17,7 +22,7 @@ class CookiesEditor extends PureComponent {
});
this.props.onCookieAdd(newCookie);
};
}
_handleCookieUpdate (cookie, cookieStr) {
clearTimeout(this._cookieUpdateTimeout);

View File

@ -1,11 +1,20 @@
import React, {PropTypes, PureComponent} from 'react';
import autobind from 'autobind-decorator';
import Editor from '../codemirror/Editor';
import {DEBOUNCE_MILLIS} from '../../../common/constants';
@autobind
class EnvironmentEditor extends PureComponent {
_handleChange = () => this.props.didChange();
constructor (props) {
super(props);
}
_handleChange () {
this.props.didChange();
}
_setEditorRef = n => this._editor = n;
_setEditorRef (n) {
this._editor = n;
}
getValue () {
return JSON.parse(this._editor.getValue());

View File

@ -1,18 +1,31 @@
import React, {PureComponent, PropTypes} from 'react';
import autobind from 'autobind-decorator';
import KeyValueEditor from '../keyvalueeditor/Editor';
import Editor from '../codemirror/Editor';
import Lazy from '../base/Lazy';
import {trackEvent} from '../../../analytics/index';
@autobind
class RequestHeadersEditor extends PureComponent {
_handleBulkUpdate = headersString => {
this.props.onChange(this._getHeadersFromString(headersString));
};
constructor (props) {
super(props);
}
_handleTrackToggle = pair => trackEvent('Headers Editor', 'Toggle', pair.disabled ? 'Disable' : 'Enable');
_handleTrackCreate = () => trackEvent('Headers Editor', 'Create');
_handleTrackDelete = () => trackEvent('Headers Editor', 'Delete');
_handleBulkUpdate (headersString) {
this.props.onChange(this._getHeadersFromString(headersString));
}
_handleTrackToggle (pair) {
trackEvent('Headers Editor', 'Toggle', pair.disabled ? 'Disable' : 'Enable');
}
_handleTrackCreate () {
trackEvent('Headers Editor', 'Create');
}
_handleTrackDelete () {
trackEvent('Headers Editor', 'Delete');
}
_getHeadersFromString (headersString) {
const headers = [];

View File

@ -1,4 +1,5 @@
import React, {PropTypes, PureComponent} from 'react';
import autobind from 'autobind-decorator';
import RawEditor from './RawEditor';
import UrlEncodedEditor from './UrlEncodedEditor';
import FormEditor from './FormEditor';
@ -6,33 +7,38 @@ import FileEditor from './FileEditor';
import {getContentTypeFromHeaders, CONTENT_TYPE_FORM_URLENCODED, CONTENT_TYPE_FORM_DATA, CONTENT_TYPE_FILE} from '../../../../common/constants';
import {newBodyRaw, newBodyFormUrlEncoded, newBodyForm, newBodyFile} from '../../../../models/request';
@autobind
class BodyEditor extends PureComponent {
_handleRawChange = rawValue => {
constructor (props) {
super(props);
}
_handleRawChange (rawValue) {
const {onChange, request} = this.props;
const contentType = getContentTypeFromHeaders(request.headers);
const newBody = newBodyRaw(rawValue, contentType || '');
onChange(newBody);
};
}
_handleFormUrlEncodedChange = parameters => {
_handleFormUrlEncodedChange (parameters) {
const {onChange} = this.props;
const newBody = newBodyFormUrlEncoded(parameters);
onChange(newBody);
};
}
_handleFormChange = parameters => {
_handleFormChange (parameters) {
const {onChange} = this.props;
const newBody = newBodyForm(parameters);
onChange(newBody);
};
}
_handleFileChange = path => {
_handleFileChange (path) {
const {onChange} = this.props;
const newBody = newBodyFile(path);
onChange(newBody);
};
}
render () {
const {keyMap, fontSize, lineWrapping, request, handleRender} = this.props;

View File

@ -1,18 +1,24 @@
import React, {PropTypes, PureComponent} from 'react';
import autobind from 'autobind-decorator';
import fs from 'fs';
import electron from 'electron';
import React, {PropTypes, PureComponent} from 'react';
import FileInputButton from '../../base/FileInputButton';
import PromptButton from '../../base/PromptButton';
import * as misc from '../../../../common/misc';
import {trackEvent} from '../../../../analytics/index';
@autobind
class FileEditor extends PureComponent {
_handleResetFile = () => {
constructor (props) {
super(props);
}
_handleResetFile () {
this.props.onChange('');
trackEvent('File Editor', 'Reset');
};
}
_handleChooseFile = path => {
_handleChooseFile (path) {
this.props.onChange(path);
trackEvent('File Editor', 'Choose');
};

View File

@ -1,20 +1,37 @@
import React, {PropTypes, PureComponent} from 'react';
import autobind from 'autobind-decorator';
import KeyValueEditor from '../../keyvalueeditor/Editor';
import {trackEvent} from '../../../../analytics/index';
@autobind
class FormEditor extends PureComponent {
_handleTrackToggle = pair => {
constructor (props) {
super(props);
}
_handleTrackToggle (pair) {
trackEvent(
'Form Editor',
`Toggle ${pair.type || 'text'}`,
pair.disabled ? 'Disable' : 'Enable'
);
};
}
_handleTrackChangeType = type => trackEvent('Form Editor', 'Change Type', type);
_handleTrackChooseFile = () => trackEvent('Form Editor', 'Choose File');
_handleTrackCreate = () => trackEvent('Form Editor', 'Create');
_handleTrackDelete = () => trackEvent('Form Editor', 'Delete');
_handleTrackChangeType (type) {
trackEvent('Form Editor', 'Change Type', type);
}
_handleTrackChooseFile () {
trackEvent('Form Editor', 'Choose File');
}
_handleTrackCreate () {
trackEvent('Form Editor', 'Create');
}
_handleTrackDelete () {
trackEvent('Form Editor', 'Delete');
}
render () {
const {parameters, onChange, handleRender} = this.props;

View File

@ -1,17 +1,30 @@
import React, {PropTypes, PureComponent} from 'react';
import autobind from 'autobind-decorator';
import Lazy from '../../base/Lazy';
import KeyValueEditor from '../../keyvalueeditor/Editor';
import {trackEvent} from '../../../../analytics/index';
@autobind
class UrlEncodedEditor extends PureComponent {
_handleTrackToggle = pair => trackEvent(
'Url Encoded Editor',
'Toggle',
pair.disabled ? 'Disable' : 'Enable'
);
constructor (props) {
super(props);
}
_handleTrackCreate = () => trackEvent('Url Encoded Editor', 'Create');
_handleTrackDelete = () => trackEvent('Url Encoded Editor', 'Delete');
_handleTrackToggle (pair) {
trackEvent(
'Url Encoded Editor',
'Toggle',
pair.disabled ? 'Disable' : 'Enable'
);
}
_handleTrackCreate () {
trackEvent('Url Encoded Editor', 'Create');
}
_handleTrackDelete () {
trackEvent('Url Encoded Editor', 'Delete');
}
render () {
const {parameters, onChange, handleRender} = this.props;

View File

@ -1,4 +1,5 @@
import React, {PureComponent, PropTypes} from 'react';
import autobind from 'autobind-decorator';
import classnames from 'classnames';
import {DEBOUNCE_MILLIS} from '../../../common/constants';
import KeyValueEditorRow from './Row';
@ -13,6 +14,7 @@ const DOWN = 40;
const LEFT = 37;
const RIGHT = 39;
@autobind
class KeyValueEditor extends PureComponent {
constructor (props) {
super(props);
@ -29,10 +31,12 @@ class KeyValueEditor extends PureComponent {
}
}
this.state = {pairs};
this.state = {
pairs: pairs,
};
}
_handlePairChange = pair => {
_handlePairChange (pair) {
const i = this._getPairIndex(pair);
const pairs = [
...this.state.pairs.slice(0, i),
@ -41,9 +45,9 @@ class KeyValueEditor extends PureComponent {
];
this._onChange(pairs);
};
}
_handleMove = (pairToMove, pairToTarget, targetOffset) => {
_handleMove (pairToMove, pairToTarget, targetOffset) {
if (pairToMove.id === pairToTarget.id) {
// Nothing to do
return;
@ -66,41 +70,41 @@ class KeyValueEditor extends PureComponent {
this._onChange(pairs);
};
_handlePairDelete = pair => {
_handlePairDelete (pair) {
const i = this.state.pairs.findIndex(p => p.id === pair.id);
this._deletePair(i, true);
};
}
_handleFocusName = pair => {
_handleFocusName (pair) {
this._setFocusedPair(pair);
this._focusedField = NAME;
};
}
_handleFocusValue = pair => {
_handleFocusValue (pair) {
this._setFocusedPair(pair);
this._focusedField = VALUE;
};
}
_handleBlurName = () => {
_handleBlurName () {
this._setFocusedPair(null);
}
_handleBlurValue () {
this._setFocusedPair(null);
};
_handleBlurValue = () => {
this._setFocusedPair(null);
};
_handleAddFromName = () => {
_handleAddFromName () {
this._focusedField = NAME;
this._addPair();
};
}
// Sometimes multiple focus events come in, so lets debounce it
_handleAddFromValue = () => {
_handleAddFromValue () {
this._focusedField = VALUE;
this._addPair();
};
}
_handleKeyDown = (pair, e, value) => {
_handleKeyDown (pair, e, value) {
if (e.metaKey || e.ctrlKey) {
return;
}

View File

@ -1,5 +1,6 @@
import React, {PureComponent, PropTypes} from 'react';
import ReactDOM from 'react-dom';
import autobind from 'autobind-decorator';
import {DragSource, DropTarget} from 'react-dnd';
import classnames from 'classnames';
import FileInputButton from '../base/FileInputButton';
@ -7,14 +8,18 @@ import {Dropdown, DropdownItem, DropdownButton} from '../base/dropdown/index';
import PromptButton from '../base/PromptButton';
import Button from '../base/Button';
import OneLineEditor from '../codemirror/OneLineEditor';
import {preventDefault} from '../../../common/misc';
@autobind
class KeyValueEditorRow extends PureComponent {
_nameInput = null;
_valueInput = null;
state = {
dragDirection: 0
};
constructor (props) {
super(props);
this._nameInput = null;
this._valueInput = null;
this.state = {
dragDirection: 0
};
}
focusName () {
this._nameInput.focus();
@ -30,46 +35,64 @@ class KeyValueEditorRow extends PureComponent {
}
}
_setNameInputRef = n => this._nameInput = n;
_setValueInputRef = n => this._valueInput = n;
_setNameInputRef (n) {
this._nameInput = n;
}
_setValueInputRef (n) {
this._valueInput = n;
}
_sendChange = patch => {
_sendChange (patch) {
const pair = Object.assign({}, this.props.pair, patch);
this.props.onChange && this.props.onChange(pair);
};
}
_handleNameChange = name => this._sendChange({name});
_handleValueChange = value => this._sendChange({value});
_handleFileNameChange = filename => this._sendChange({fileName});
_handleTypeChange = type => this._sendChange({type});
_handleDisableChange = disabled => this._sendChange({disabled});
_handleNameChange (name) {
this._sendChange({name});
}
_handleValueChange (value) {
this._sendChange({value});
}
_handleFileNameChange (fileName) {
this._sendChange({fileName});
}
_handleTypeChange (type) {
this._sendChange({type});
}
_handleDisableChange (disabled) {
this._sendChange({disabled});
}
_handleFocusName = e => this.props.onFocusName(this.props.pair, e);
_handleFocusValue = e => this.props.onFocusValue(this.props.pair, e);
_handleFocusName (e) {
this.props.onFocusName(this.props.pair, e);
}
_handleFocusValue (e) {
this.props.onFocusValue(this.props.pair, e);
}
_handleBlurName = e => {
_handleBlurName (e) {
if (this.props.onBlurName) {
this.props.onBlurName(this.props.pair, e);
}
};
}
_handleBlurValue = e => {
_handleBlurValue (e) {
if (this.props.onBlurName) {
this.props.onBlurValue(this.props.pair, e);
}
};
}
_handleDelete = () => {
_handleDelete () {
if (this.props.onDelete) {
this.props.onDelete(this.props.pair);
}
};
}
_handleKeyDown = (e, value) => {
_handleKeyDown (e, value) {
if (this.props.onKeyDown) {
this.props.onKeyDown(this.props.pair, e, value);
}
};
}
render () {
const {

View File

@ -1,18 +1,26 @@
import React, {PureComponent} from 'react';
import autobind from 'autobind-decorator';
import Modal from '../base/Modal';
import ModalBody from '../base/ModalBody';
import ModalHeader from '../base/ModalHeader';
import ModalFooter from '../base/ModalFooter';
@autobind
class AlertModal extends PureComponent {
state = {
title: '',
message: '',
};
constructor (props) {
super(props);
_setModalRef = m => this.modal = m;
this.state = {
title: '',
message: '',
};
}
_handleOk = () => {
_setModalRef (m) {
this.modal = m;
}
_handleOk () {
this.hide();
this._okCallback();
};

View File

@ -1,4 +1,5 @@
import React, {PureComponent} from 'react';
import autobind from 'autobind-decorator';
import Link from '../base/Link';
import Modal from '../base/Modal';
import ModalBody from '../base/ModalBody';
@ -6,10 +7,19 @@ import ModalHeader from '../base/ModalHeader';
import ModalFooter from '../base/ModalFooter';
import {getAppVersion, CHANGELOG_URL, CHANGELOG_PAGE} from '../../../common/constants';
@autobind
class ChangelogModal extends PureComponent {
state = {changelog: null};
constructor (props) {
super(props);
this.state = {
changelog: null,
};
}
_setModalRef = m => this.modal = m;
_setModalRef (m) {
this.modal = m;
}
show () {
this.modal.show();

View File

@ -1,4 +1,5 @@
import React, {PropTypes, PureComponent} from 'react';
import autobind from 'autobind-decorator';
import Modal from '../base/Modal';
import ModalBody from '../base/ModalBody';
import ModalHeader from '../base/ModalHeader';
@ -7,23 +8,36 @@ import CookiesEditor from '../editors/CookiesEditor';
import * as models from '../../../models';
import {trackEvent} from '../../../analytics/index';
@autobind
class CookiesModal extends PureComponent {
state = {
cookieJar: null,
workspace: null,
filter: ''
};
constructor (props) {
super(props);
this.state = {
cookieJar: null,
workspace: null,
filter: ''
};
}
_setModalRef = n => this.modal = n;
_setFilterInputRef = n => this.filterInput = n;
_hide = () => this.modal.hide();
_setModalRef (n) {
this.modal = n;
}
_setFilterInputRef (n) {
this.filterInput = n;
}
_hide () {
this.modal.hide();
}
async _saveChanges () {
const {cookieJar} = this.state;
await models.cookieJar.update(cookieJar);
this._load(this.state.workspace);
}
_handleCookieUpdate = (oldCookie, cookie) => {
_handleCookieUpdate (oldCookie, cookie) {
const {cookieJar} = this.state;
const {cookies} = cookieJar;
const index = cookies.findIndex(c => c.domain === oldCookie.domain && c.key === oldCookie.key);
@ -36,17 +50,17 @@ class CookiesModal extends PureComponent {
this._saveChanges(cookieJar);
trackEvent('Cookie', 'Update');
};
}
_handleCookieAdd = cookie => {
_handleCookieAdd (cookie) {
const {cookieJar} = this.state;
const {cookies} = cookieJar;
cookieJar.cookies = [cookie, ...cookies];
this._saveChanges(cookieJar);
trackEvent('Cookie', 'Create');
};
}
_handleCookieDelete = cookie => {
_handleCookieDelete (cookie) {
const {cookieJar} = this.state;
const {cookies} = cookieJar;
@ -55,9 +69,9 @@ class CookiesModal extends PureComponent {
this._saveChanges(cookieJar);
trackEvent('Cookie', 'Delete');
};
}
_handleFilterChange = e => {
_handleFilterChange (e) {
const filter = e.target.value;
this.setState({filter});
trackEvent('Cookie Editor', 'Filter Change');

View File

@ -1,21 +1,32 @@
import React, {PropTypes, PureComponent} from 'react';
import autobind from 'autobind-decorator';
import EnvironmentEditor from '../editors/EnvironmentEditor';
import Modal from '../base/Modal';
import ModalBody from '../base/ModalBody';
import ModalHeader from '../base/ModalHeader';
import ModalFooter from '../base/ModalFooter';
@autobind
class EnvironmentEditModal extends PureComponent {
state = {
requestGroup: null,
isValid: true
};
constructor (props) {
super(props);
this.state = {
requestGroup: null,
isValid: true
};
}
_hide = () => this.modal.hide();
_setModalRef = n => this.modal = n;
_setEditorRef = n => this._envEditor = n;
_hide () {
this.modal.hide();
}
_setModalRef (n) {
this.modal = n;
}
_setEditorRef (n) {
this._envEditor = n;
}
_saveChanges () {
if (!this._envEditor.isValid()) {
@ -28,7 +39,7 @@ class EnvironmentEditModal extends PureComponent {
this.props.onChange(Object.assign({}, requestGroup, {environment}));
}
_didChange = () => {
_didChange () {
this._saveChanges();
const isValid = this._envEditor.isValid();

View File

@ -1,6 +1,6 @@
import React, {PureComponent, PropTypes} from 'react';
import autobind from 'autobind-decorator';
import HTTPSnippet, {availableTargets} from 'httpsnippet';
import CopyButton from '../base/CopyButton';
import {Dropdown, DropdownButton, DropdownItem} from '../base/dropdown';
import Editor from '../codemirror/Editor';
@ -26,7 +26,7 @@ const TO_ADD_CONTENT_LENGTH = {
node: ['native']
};
@autobind
class GenerateCodeModal extends PureComponent {
constructor (props) {
super(props);
@ -53,18 +53,24 @@ class GenerateCodeModal extends PureComponent {
};
}
_setModalRef = n => this.modal = n;
_setEditorRef = n => this._editor = n;
_setModalRef (n) {
this.modal = n;
}
_setEditorRef (n) {
this._editor = n;
}
_hide = () => this.modal.hide();
_hide () {
this.modal.hide();
}
_handleClientChange = client => {
_handleClientChange (client) {
const {target, request} = this.state;
this._generateCode(request, target, client);
trackEvent('Generate Code', 'Client Change', `${target.title}/${client.title}`);
};
}
_handleTargetChange = target => {
_handleTargetChange (target) {
const {target: currentTarget} = this.state;
if (currentTarget.key === target.key) {
// No change
@ -74,7 +80,7 @@ class GenerateCodeModal extends PureComponent {
const client = target.clients.find(c => c.key === target.default);
this._generateCode(this.state.request, target, client);
trackEvent('Generate Code', 'Target Change', target.title);
};
}
async _generateCode (request, target, client) {
// Some clients need a content-length for the request to succeed

View File

@ -1,4 +1,5 @@
import React, {PureComponent} from 'react';
import autobind from 'autobind-decorator';
import Link from '../base/Link';
import Modal from '../base/Modal';
import ModalBody from '../base/ModalBody';
@ -7,20 +8,33 @@ import ModalFooter from '../base/ModalFooter';
import * as session from '../../../sync/session';
import * as sync from '../../../sync';
@autobind
class LoginModal extends PureComponent {
state = {
step: 1,
loading: false,
error: '',
title: '',
message: '',
constructor (props) {
super(props);
this.state = {
step: 1,
loading: false,
error: '',
title: '',
message: '',
};
}
_setModalRef (n) {
this.modal = n;
}
_setPasswordInputRef (n) {
this._passwordInput = n;
}
_setEmailInputRef (n) {
return this._emailInput = n;
};
_setModalRef = n => this.modal = n;
_setEmailInputRef = n => this._emailInput = n;
_hide = () => this.hide();
_handleLogin = async e => {
async _handleLogin (e) {
e.preventDefault();
this.setState({error: '', loading: true});
@ -60,9 +74,7 @@ class LoginModal extends PureComponent {
inner = [
<ModalHeader key="header">{title || "Login to Your Account"}</ModalHeader>,
<ModalBody key="body" className="pad">
{message ? (
<p className="notice info">{message}</p>
) : null}
{message ? <p className="notice info">{message}</p> : null}
<div className="form-control form-control--outlined no-pad-top">
<label>Email
<input
@ -78,7 +90,7 @@ class LoginModal extends PureComponent {
<input type="password"
required="required"
placeholder="•••••••••••••••••"
ref={n => this._passwordInput = n}/>
ref={this._setPasswordInputRef}/>
</label>
</div>
{error ? <div className="danger pad-top">** {error}</div> : null}
@ -109,7 +121,7 @@ class LoginModal extends PureComponent {
</p>
</ModalBody>,
<ModalFooter key="footer">
<button type="button" className="btn" onClick={this._hide}>
<button type="button" className="btn" onClick={this.hide}>
Close
</button>
</ModalFooter>

View File

@ -1,4 +1,5 @@
import React, {PureComponent} from 'react';
import autobind from 'autobind-decorator';
import PromptButton from '../base/PromptButton';
import Link from '../base/Link';
import Modal from '../base/Modal';
@ -8,16 +9,22 @@ import {trackEvent} from '../../../analytics';
import * as session from '../../../sync/session';
import * as sync from '../../../sync/index';
let hidePaymentNotificationUntilNextLaunch = false;
@autobind
class PaymentNotificationModal extends PureComponent {
_handleCancel = async () => {
constructor (props) {
super(props);
}
async _handleCancel () {
await sync.cancelTrial();
this.hide();
};
}
_setModalRef = n => this.modal = n;
_setModalRef (n) {
this.modal = n;
}
show () {
// Don't trigger automatically if user has dismissed it already

View File

@ -1,27 +1,35 @@
import React, {PureComponent} from 'react';
import autobind from 'autobind-decorator';
import Modal from '../base/Modal';
import ModalBody from '../base/ModalBody';
import ModalHeader from '../base/ModalHeader';
import ModalFooter from '../base/ModalFooter';
@autobind
class PromptModal extends PureComponent {
state = {
headerName: 'Not Set',
defaultValue: '',
submitName: 'Not Set',
selectText: false,
hint: null,
inputType: 'text'
};
constructor (props) {
super(props);
_setModalRef = n => this.modal = n;
this.state = {
headerName: 'Not Set',
defaultValue: '',
submitName: 'Not Set',
selectText: false,
hint: null,
inputType: 'text'
};
}
_handleSubmit = e => {
_setModalRef (n) {
this.modal = n;
}
_handleSubmit (e) {
e.preventDefault();
this._onSubmitCallback && this._onSubmitCallback(this._input.value);
this.modal.hide();
};
}
show ({headerName, defaultValue, submitName, selectText, hint, inputType, placeholder, label}) {
this.modal.show();

View File

@ -1,4 +1,5 @@
import React, {PureComponent} from 'react';
import autobind from 'autobind-decorator';
import ContentTypeDropdown from '../dropdowns/ContentTypeDropdown';
import MethodDropdown from '../dropdowns/MethodDropdown';
import Modal from '../base/Modal';
@ -9,17 +10,27 @@ import {getContentTypeName, METHOD_GET, METHOD_HEAD, METHOD_OPTIONS, METHOD_DELE
import * as models from '../../../models/index';
import {trackEvent} from '../../../analytics/index';
@autobind
class RequestCreateModal extends PureComponent {
state = {
selectedContentType: null,
selectedMethod: METHOD_GET,
parentId: null,
};
constructor (props) {
super(props);
_setModalRef = n => this.modal = n;
_setInputRef = n => this._input = n;
this.state = {
selectedContentType: null,
selectedMethod: METHOD_GET,
parentId: null,
};
}
_handleSubmit = async e => {
_setModalRef (n) {
this.modal = n;
}
_setInputRef (n) {
this._input = n;
}
async _handleSubmit (e) {
e.preventDefault();
const {parentId, selectedContentType, selectedMethod} = this.state;
@ -38,17 +49,17 @@ class RequestCreateModal extends PureComponent {
this._onSubmitCallback(finalRequest);
this.hide();
};
}
_handleChangeSelectedContentType = selectedContentType => {
_handleChangeSelectedContentType (selectedContentType) {
this.setState({selectedContentType});
trackEvent('Request Create', 'Content Type Change', selectedContentType);
};
}
_handleChangeSelectedMethod = selectedMethod => {
_handleChangeSelectedMethod (selectedMethod) {
this.setState({selectedMethod});
trackEvent('Request Create', 'Method Change', selectedMethod);
};
}
_shouldNotHaveBody () {
const {selectedMethod} = this.state;
@ -107,19 +118,19 @@ class RequestCreateModal extends PureComponent {
/>
</div>
{!this._shouldNotHaveBody() ? (
<div className="form-control" style={{width: 'auto'}}>
<label htmlFor="nothing">&nbsp;
<ContentTypeDropdown className="btn btn--clicky no-wrap"
right
contentType={selectedContentType}
onChange={this._handleChangeSelectedContentType}>
{getContentTypeName(selectedContentType)}
{" "}
<i className="fa fa-caret-down"></i>
</ContentTypeDropdown>
</label>
</div>
) : null}
<div className="form-control" style={{width: 'auto'}}>
<label htmlFor="nothing">&nbsp;
<ContentTypeDropdown className="btn btn--clicky no-wrap"
right
contentType={selectedContentType}
onChange={this._handleChangeSelectedContentType}>
{getContentTypeName(selectedContentType)}
{" "}
<i className="fa fa-caret-down"></i>
</ContentTypeDropdown>
</label>
</div>
) : null}
</div>
</form>
</ModalBody>

View File

@ -1,4 +1,5 @@
import React, {PropTypes, PureComponent} from 'react';
import autobind from 'autobind-decorator';
import ReactDOM from 'react-dom';
import classnames from 'classnames';
import Button from '../base/Button';
@ -8,20 +9,29 @@ import ModalBody from '../base/ModalBody';
import MethodTag from '../tags/MethodTag';
import * as models from '../../../models';
@autobind
class RequestSwitcherModal extends PureComponent {
state = {
searchString: '',
requestGroups: [],
requests: [],
workspaces: [],
matchedRequests: [],
matchedWorkspaces: [],
activeIndex: -1
};
constructor (props) {
super(props);
_setModalRef = n => this.modal = n;
_focusRef = n => n && n.focus();
this.state = {
searchString: '',
requestGroups: [],
requests: [],
workspaces: [],
matchedRequests: [],
matchedWorkspaces: [],
activeIndex: -1
};
}
_setModalRef (n) {
this.modal = n;
}
_focusRef (n) {
n && n.focus();
}
_setActiveIndex (activeIndex) {
const maxIndex = this.state.matchedRequests.length + this.state.matchedWorkspaces.length;
@ -34,7 +44,7 @@ class RequestSwitcherModal extends PureComponent {
this.setState({activeIndex});
}
_activateCurrentIndex = () => {
_activateCurrentIndex () {
const {
activeIndex,
matchedRequests,
@ -54,7 +64,7 @@ class RequestSwitcherModal extends PureComponent {
// Create request if no match
this._createRequestFromSearch();
}
};
}
async _createRequestFromSearch () {
const {activeRequestParentId} = this.props;
@ -70,27 +80,29 @@ class RequestSwitcherModal extends PureComponent {
this._activateRequest(request);
}
_activateWorkspace = workspace => {
_activateWorkspace (workspace) {
if (!workspace) {
return;
}
this.props.handleSetActiveWorkspace(workspace._id);
this.modal.hide();
};
}
_activateRequest = request => {
_activateRequest (request) {
if (!request) {
return;
}
this.props.activateRequest(request._id);
this.modal.hide();
};
}
_handleChange = e => this._handleChangeValue(e.target.value);
_handleChange (e) {
this._handleChangeValue(e.target.value);
}
_handleChangeValue = async searchString => {
async _handleChangeValue (searchString) {
const {workspaceChildren, workspaces} = this.props;
const {workspaceId, activeRequestParentId} = this.props;

View File

@ -1,5 +1,6 @@
import React, {PureComponent, PropTypes} from 'react';
import {Tab, Tabs, TabList, TabPanel} from 'react-tabs';
import autobind from 'autobind-decorator';
import {shell} from 'electron';
import Modal from '../base/Modal';
import Button from '../base/Button';
@ -17,44 +18,53 @@ import * as session from '../../../sync/session';
export const TAB_INDEX_EXPORT = 1;
@autobind
class SettingsModal extends PureComponent {
constructor (props) {
super(props);
this.state = {};
this._currentTabIndex = -1;
this.state = {}
}
_setModalRef = n => this.modal = n;
_trackTab = name => trackEvent('Setting', `Tab ${name}`);
_handleTabSelect = currentTabIndex => this.setState({currentTabIndex});
_handleUpdateSetting = (key, value) => {
_setModalRef (n) {
this.modal = n;
}
_trackTab (name){
trackEvent('Setting', `Tab ${name}`);
}
_handleTabSelect (currentTabIndex) {
this.setState({currentTabIndex});
}
_handleUpdateSetting (key, value) {
models.settings.update(this.props.settings, {[key]: value});
trackEvent('Setting', 'Change', key)
};
}
_handleExportAllToFile = () => {
_handleExportAllToFile () {
this.props.handleExportAllToFile();
this.modal.hide()
};
}
_handleExportWorkspace = () => {
_handleExportWorkspace () {
this.props.handleExportWorkspaceToFile();
this.modal.hide()
};
}
_handleImport = () => {
_handleImport () {
this.props.handleImportFile();
this.modal.hide()
};
}
_handleChangeTheme = (theme, persist = true) => {
_handleChangeTheme (theme, persist = true) {
document.body.setAttribute('theme', theme);
if (persist) {
trackEvent('Setting', 'Change Theme', theme);
models.settings.update(this.props.settings, {theme});
}
};
}
componentDidMount () {
// Hacky way to set theme on launch
@ -72,6 +82,7 @@ class SettingsModal extends PureComponent {
}
toggle (currentTabIndex = 0) {
console.log('THIS', this);
this.setState({currentTabIndex});
this.modal.toggle();
}

View File

@ -0,0 +1,110 @@
import React, {PureComponent, PropTypes} from 'react';
import autobind from 'autobind-decorator';
import Modal from '../base/Modal';
import ModalBody from '../base/ModalBody';
import ModalHeader from '../base/ModalHeader';
import ModalFooter from '../base/ModalFooter';
import * as sync from '../../../sync';
import {SYNC_MODE_OFF, SYNC_MODE_ON, SYNC_MODE_NEVER, SYNC_MODE_UNSET} from '../../../sync/storage';
@autobind
class SetupSyncModal extends PureComponent {
constructor (props) {
super(props);
this.state = {
syncMode: SYNC_MODE_ON,
};
}
_setModalRef (n) {
this.modal = n;
}
async _handleDone () {
const {workspace} = this.props;
const {syncMode} = this.state;
const resource = await sync.getOrCreateResourceForDoc(workspace);
await sync.createOrUpdateConfig(resource.resourceGroupId, syncMode);
this.hide();
this._resolvePromise && this._resolvePromise(syncMode);
}
_handleSyncModeChange (e) {
this.setState({syncMode: e.target.value});
};
show () {
(async () => {
const {workspace} = this.props;
const resource = await sync.getOrCreateResourceForDoc(workspace);
const config = await sync.getOrCreateConfig(resource.resourceGroupId);
console.log('CONFIG', config);
this.setState({syncMode: config.syncMode});
})();
this.modal.show();
this._promise = new Promise(resolve => this._resolvePromise = resolve);
return this._promise;
}
hide () {
this.modal.hide();
}
render () {
const {workspace} = this.props;
const {syncMode} = this.state;
return (
<Modal ref={this._setModalRef} noEscape>
<ModalHeader>Workspace Sync Setup</ModalHeader>
<ModalBody className="wide pad">
{syncMode === SYNC_MODE_UNSET ?
<p className="notice info">
You have not yet configured sync for your <strong>{workspace.name}</strong> workspace.
</p> : null
}
<br/>
<div className="form-control form-control--outlined">
<label>Choose sync mode
<select onChange={this._handleSyncModeChange} value={syncMode}>
<option value={SYNC_MODE_ON}>
Automatically sync changes
</option>
<option value={SYNC_MODE_OFF}>
Manually sync changes
</option>
<option value={SYNC_MODE_NEVER}>
Disable sync for this workspace
</option>
</select>
</label>
</div>
<br/>
</ModalBody>
<ModalFooter>
<div className="margin-left faint italic txt-sm tall">
* This can be changed at any time
</div>
<button className="btn" onClick={this._handleDone}>
Continue
</button>
</ModalFooter>
</Modal>
)
}
}
SetupSyncModal.propTypes = {
workspace: PropTypes.object.isRequired,
};
export default SetupSyncModal;

View File

@ -1,107 +0,0 @@
import React, {PureComponent} from 'react';
import classnames from 'classnames';
import CopyButton from '../base/CopyButton';
import Modal from '../base/Modal';
import ModalBody from '../base/ModalBody';
import ModalHeader from '../base/ModalHeader';
import ModalFooter from '../base/ModalFooter';
import * as sync from '../../../sync';
class SyncLogsModal extends PureComponent {
state = {
logs: []
};
_setModalRef = n => this.modal = n;
show () {
clearInterval(this._interval);
this._interval = setInterval(() => this._updateModal(), 2000);
this._updateModal();
this.modal.show();
}
hide () {
clearInterval(this._interval);
this.modal.hide();
}
_updateModal () {
this.setState({logs: sync.logger.tail()})
}
_getColorClass (level) {
return {
debug: 'info',
warn: 'warning',
error: 'danger'
}[level] || '';
}
_formatLogs () {
const {logs: allLogs} = this.state;
const logs = allLogs.slice(allLogs.length - 1000);
function pad (n, length) {
let s = n + '';
while (s.length < length) {
s = '0' + s;
}
return s;
}
const rows = [];
let i = 0;
for (const entry of logs) {
const colorClass = this._getColorClass(entry.type);
const dateString =
pad(entry.date.getFullYear(), 4) + '/' +
pad(entry.date.getMonth() + 1, 2) + '/' +
pad(entry.date.getDate(), 2) + ' ' +
pad(entry.date.getHours(), 2) + ':' +
pad(entry.date.getMinutes(), 2) + ':' +
pad(entry.date.getSeconds(), 2);
rows.push({
jsx: (
<pre key={i++}>
<span className="faint">
{dateString}
</span>
{" "}
<span style={{minWidth: '4rem'}}
className={classnames(colorClass, 'inline-block')}>
[{entry.type}]
</span>
{" "}
{entry.message}
</pre>
),
text: `${dateString} [${entry.type}] ${entry.message}`
})
}
return rows;
}
render () {
const rows = this._formatLogs();
return (
<Modal ref={this._setModalRef} tall>
<ModalHeader>Sync Debug Logs</ModalHeader>
<ModalBody className="pad selectable txt-sm monospace">
{rows.map(row => row.jsx)}
</ModalBody>
<ModalFooter>
<CopyButton className="btn" content={rows.map(r => r.text).join('\n')}>
Copy To Clipboard
</CopyButton>
</ModalFooter>
</Modal>
)
}
}
SyncLogsModal.propTypes = {};
export default SyncLogsModal;

View File

@ -1,4 +1,5 @@
import React, {PropTypes, PureComponent} from 'react';
import autobind from 'autobind-decorator';
import classnames from 'classnames';
import {Dropdown, DropdownButton, DropdownItem} from '../base/dropdown';
import PromptButton from '../base/PromptButton';
@ -13,19 +14,31 @@ import ModalFooter from '../base/ModalFooter';
import * as models from '../../../models';
import {trackEvent} from '../../../analytics/index';
@autobind
class WorkspaceEnvironmentsEditModal extends PureComponent {
state = {
workspace: null,
isValid: true,
subEnvironments: [],
rootEnvironment: null,
activeEnvironmentId: null,
forceRefreshKey: 0,
};
constructor (props) {
super(props);
this.state = {
workspace: null,
isValid: true,
subEnvironments: [],
rootEnvironment: null,
activeEnvironmentId: null,
forceRefreshKey: 0,
};
}
_hide = () => this.modal.hide();
_setEditorRef = n => this._envEditor = n;
_setModalRef = n => this.modal = n;
_hide () {
this.modal.hide();
}
_setEditorRef (n) {
this._envEditor = n;
}
_setModalRef (n) {
this.modal = n;
}
async show (workspace) {
this.modal.show();
@ -64,7 +77,7 @@ class WorkspaceEnvironmentsEditModal extends PureComponent {
});
}
_handleAddEnvironment = async (isPrivate = false) => {
async _handleAddEnvironment (isPrivate = false) {
const {rootEnvironment, workspace} = this.state;
const parentId = rootEnvironment._id;
const environment = await models.environment.create({parentId, isPrivate});
@ -74,9 +87,9 @@ class WorkspaceEnvironmentsEditModal extends PureComponent {
'Environment',
isPrivate ? 'Create' : 'Create Private'
);
};
}
_handleShowEnvironment = async environment => {
async _handleShowEnvironment (environment) {
// Don't allow switching if the current one has errors
if (!this._envEditor.isValid()) {
return;
@ -89,9 +102,9 @@ class WorkspaceEnvironmentsEditModal extends PureComponent {
const {workspace} = this.state;
await this._load(workspace, environment);
trackEvent('Environment Editor', 'Show Environment');
};
}
_handleDeleteEnvironment = async () => {
async _handleDeleteEnvironment () {
const {rootEnvironment, workspace} = this.state;
const environment = this._getActiveEnvironment();
@ -105,9 +118,9 @@ class WorkspaceEnvironmentsEditModal extends PureComponent {
await this._load(workspace, rootEnvironment);
trackEvent('Environment', 'Delete');
};
}
_handleChangeEnvironmentName = async (environment, name) => {
async _handleChangeEnvironmentName (environment, name) {
const {workspace} = this.state;
// NOTE: Fetch the environment first because it might not be up to date.
@ -119,7 +132,7 @@ class WorkspaceEnvironmentsEditModal extends PureComponent {
trackEvent('Environment', 'Rename');
};
_didChange = () => {
_didChange () {
const isValid = this._envEditor.isValid();
if (this.state.isValid === isValid) {
@ -127,7 +140,7 @@ class WorkspaceEnvironmentsEditModal extends PureComponent {
}
this._saveChanges();
};
}
_getActiveEnvironment () {
const {activeEnvironmentId, subEnvironments, rootEnvironment} = this.state;
@ -178,8 +191,7 @@ class WorkspaceEnvironmentsEditModal extends PureComponent {
<DropdownItem onClick={this._handleAddEnvironment} value={false}>
<i className="fa fa-eye"/> Environment
</DropdownItem>
<DropdownItem onClick={this._handleAddEnvironment}
value
<DropdownItem onClick={this._handleAddEnvironment} value={true}
title="Environment will not be exported or synced">
<i className="fa fa-eye-slash"/> Private Environment
</DropdownItem>

View File

@ -1,4 +1,5 @@
import React, {PureComponent, PropTypes} from 'react';
import autobind from 'autobind-decorator';
import {Tab, Tabs, TabList, TabPanel} from 'react-tabs';
import DebouncedInput from '../base/DebouncedInput';
import FileInputButton from '../base/FileInputButton';
@ -10,38 +11,60 @@ import * as models from '../../../models/index';
import * as fs from 'fs';
import {trackEvent} from '../../../analytics/index';
@autobind
class WorkspaceSettingsModal extends PureComponent {
state = {
showAddCertificateForm: false,
crtPath: '',
keyPath: '',
pfxPath: '',
host: '',
passphrase: '',
};
constructor (props) {
super(props);
_workspaceUpdate = patch => models.workspace.update(this.props.workspace, patch);
this.state = {
showAddCertificateForm: false,
crtPath: '',
keyPath: '',
pfxPath: '',
host: '',
passphrase: '',
};
}
_handleSetModalRef = n => this.modal = n;
_handleRemoveWorkspace = () => {
_workspaceUpdate (patch) {
models.workspace.update(this.props.workspace, patch);
}
_handleSetModalRef (n) {
this.modal = n;
}
_handleRemoveWorkspace () {
this.props.handleRemoveWorkspace();
this.hide();
};
}
_handleToggleCertificateForm = () => {
_handleToggleCertificateForm () {
this.setState({showAddCertificateForm: !this.state.showAddCertificateForm})
};
}
_handleRename = name => this._workspaceUpdate({name});
_handleDescriptionChange = description => this._workspaceUpdate({description});
_handleRename (name) {
this._workspaceUpdate({name});
}
_handleDescriptionChange (description){
this._workspaceUpdate({description});
}
_handleCreateHostChange = e => this.setState({host: e.target.value});
_handleCreatePfxChange = pfxPath => this.setState({pfxPath});
_handleCreateCrtChange = crtPath => this.setState({crtPath});
_handleCreateKeyChange = keyPath => this.setState({keyPath});
_handleCreatePassphraseChange = e => this.setState({passphrase: e.target.value});
_handleSubmitCertificate = async e => {
_handleCreateHostChange (e) {
this.setState({host: e.target.value});
}
_handleCreatePfxChange (pfxPath) {
this.setState({pfxPath});
}
_handleCreateCrtChange (crtPath){
this.setState({crtPath});
}
_handleCreateKeyChange (keyPath) {
this.setState({keyPath});
}
_handleCreatePassphraseChange (e) {
this.setState({passphrase: e.target.value});
}
async _handleSubmitCertificate (e) {
e.preventDefault();
const {workspace} = this.props;
@ -59,23 +82,23 @@ class WorkspaceSettingsModal extends PureComponent {
await models.workspace.update(workspace, {certificates});
this._handleToggleCertificateForm();
trackEvent('Certificates', 'Create');
};
}
_handleDeleteCertificate = certificate => {
_handleDeleteCertificate (certificate) {
const {workspace} = this.props;
const certificates = workspace.certificates.filter(c => c.host !== certificate.host);
models.workspace.update(workspace, {certificates});
trackEvent('Certificates', 'Delete');
};
}
_handleToggleCertificate = certificate => {
_handleToggleCertificate (certificate) {
const {workspace} = this.props;
const certificates = workspace.certificates.map(
c => c === certificate ? Object.assign({}, c, {disabled: !c.disabled}) : c
);
models.workspace.update(workspace, {certificates});
trackEvent('Certificates', 'Toggle');
};
}
toggle (workspace) {
this.modal.toggle();

View File

@ -1,4 +1,5 @@
import React, {PureComponent, PropTypes} from 'react';
import autobind from 'autobind-decorator';
import {Dropdown, DropdownButton, DropdownItem, DropdownDivider} from '../base/dropdown';
import Link from '../base/Link';
import Modal from '../base/Modal';
@ -12,14 +13,24 @@ import PromptModal from './PromptModal';
import PromptButton from '../base/PromptButton';
import {trackEvent} from '../../../analytics/index';
@autobind
class WorkspaceShareSettingsModal extends PureComponent {
state = {};
constructor (props) {
super(props);
this.state = {};
}
_handleSubmit = e => e.preventDefault();
_handleClose = () => this.hide();
_setModalRef = n => this.modal = n;
_handleSubmit (e) {
e.preventDefault();
}
_handleClose () {
this.hide();
}
_setModalRef (n) {
this.modal = n;
}
_handleUnshare = async () => {
async _handleUnshare () {
if (!session.isLoggedIn()) {
return;
}
@ -35,9 +46,9 @@ class WorkspaceShareSettingsModal extends PureComponent {
console.warn('Failed to unshare workspace', err);
this._resetState({error: err.message, loading: false});
}
};
}
_handleShareWithTeam = async team => {
async _handleShareWithTeam (team) {
const passphrase = await showModal(PromptModal, {
headerName: 'Share Workspace',
label: 'Confirm password to share workspace',

View File

@ -1,7 +1,13 @@
import React, {PureComponent, PropTypes} from 'react';
import autobind from 'autobind-decorator';
@autobind
class SettingsGeneral extends PureComponent {
_handleUpdateSetting = e => {
constructor (props) {
super(props);
}
_handleUpdateSetting (e) {
let value = e.target.type === 'checkbox' ? e.target.checked : e.target.value;
if (e.target.type === 'number') {
@ -9,7 +15,7 @@ class SettingsGeneral extends PureComponent {
}
this.props.updateSetting(e.target.name, value);
};
}
render () {
const {settings} = this.props;

View File

@ -1,4 +1,5 @@
import React, {PureComponent, PropTypes} from 'react';
import autobind from 'autobind-decorator';
import Button from '../base/Button';
import Link from '../base/Link';
@ -26,10 +27,14 @@ const THEMES = [
{key: 'railscasts', name: 'Railscasts', img: imgRailscasts, paid: true},
];
@autobind
class SettingsTheme extends PureComponent {
state = {
isPremium: localStorage.getItem('settings.theme.isPremium') || false,
};
constructor (props) {
super(props);
this.state = {
isPremium: localStorage.getItem('settings.theme.isPremium') || false,
};
}
async componentDidMount () {
// NOTE: This is kind of sketchy because we're relying on our parent (tab view)
@ -48,7 +53,7 @@ class SettingsTheme extends PureComponent {
}
}
renderTheme = theme => {
renderTheme (theme) {
const {handleChangeTheme, activeTheme} = this.props;
const {isPremium} = this.state;
const isActive = activeTheme === theme.key;
@ -72,7 +77,7 @@ class SettingsTheme extends PureComponent {
)}
</div>
)
};
}
renderThemeRows (themes) {
const rows = [];

View File

@ -1,4 +1,5 @@
import React, {PureComponent, PropTypes} from 'react';
import autobind from 'autobind-decorator';
import classnames from 'classnames';
import EnvironmentsDropdown from '../dropdowns/EnvironmentsDropdown';
import SidebarFilter from './SidebarFilter';
@ -7,22 +8,25 @@ import SyncButton from '../dropdowns/SyncDropdown';
import WorkspaceDropdown from '../dropdowns/WorkspaceDropdown';
import {SIDEBAR_SKINNY_REMS, COLLAPSE_SIDEBAR_REMS} from '../../../common/constants';
@autobind
class Sidebar extends PureComponent {
_handleChangeEnvironment = id => {
constructor (props) {
super(props);
}
_handleChangeEnvironment (id) {
const {handleSetActiveEnvironment} = this.props;
handleSetActiveEnvironment(id);
};
}
_handleCreateRequestInWorkspace = () => {
_handleCreateRequestInWorkspace () {
const {workspace, handleCreateRequest} = this.props;
handleCreateRequest(workspace._id);
};
}
_handleCreateRequestGroupInWorkspace = () => {
_handleCreateRequestGroupInWorkspace () {
const {workspace, handleCreateRequestGroup} = this.props;
handleCreateRequestGroup(workspace._id);
};
}
render () {
const {

View File

@ -1,11 +1,16 @@
import React, {PureComponent, PropTypes} from 'react';
import autobind from 'autobind-decorator';
import {Dropdown, DropdownHint, DropdownButton, DropdownItem} from '../base/dropdown';
import {DEBOUNCE_MILLIS} from '../../../common/constants';
import {trackEvent} from '../../../analytics/index';
@autobind
class SidebarFilter extends PureComponent {
_handleOnChange = (e) => {
constructor (props) {
super(props);
}
_handleOnChange (e) {
const value = e.target.value;
clearTimeout(this._triggerTimeout);
@ -18,17 +23,17 @@ class SidebarFilter extends PureComponent {
this._analyticsTimeout = setTimeout(() => {
trackEvent('Sidebar', 'Filter', value ? 'Change' : 'Clear');
}, 2000);
};
}
_handleRequestGroupCreate = () => {
_handleRequestGroupCreate () {
this.props.requestGroupCreate();
trackEvent('Folder', 'Create', 'Sidebar Filter');
};
}
_handleRequestCreate = () => {
_handleRequestCreate () {
this.props.requestCreate();
trackEvent('Request', 'Create', 'Sidebar Filter');
};
}
render () {
return (

View File

@ -1,28 +1,36 @@
import React, {PropTypes, PureComponent} from 'react';
import autobind from 'autobind-decorator';
import ReactDOM from 'react-dom';
import {DragSource, DropTarget} from 'react-dnd';
import classnames from 'classnames';
import RequestGroupActionsDropdown from '../dropdowns/RequestGroupActionsDropdown';
import SidebarRequestRow from './SidebarRequestRow';
import {trackEvent} from '../../../analytics/index';
import * as misc from '../../../common/misc';
@autobind
class SidebarRequestGroupRow extends PureComponent {
state = {dragDirection: 0};
constructor (props) {
super(props);
this.state = {
dragDirection: 0
};
}
_setRequestGroupActionsDropdownRef = n => this._requestGroupActionsDropdown = n;
_setRequestGroupActionsDropdownRef (n) {
this._requestGroupActionsDropdown = n;
}
_handleCollapse = () => {
_handleCollapse () {
const {requestGroup, handleSetRequestGroupCollapsed, isCollapsed} = this.props;
handleSetRequestGroupCollapsed(requestGroup._id, !isCollapsed);
trackEvent('Folder', 'Toggle Visible', !isCollapsed ? 'Close' : 'Open')
};
}
_handleShowActions = e => {
_handleShowActions (e) {
e.preventDefault();
this._requestGroupActionsDropdown.show();
};
_nullFunction = () => null;
}
setDragDirection (dragDirection) {
if (dragDirection !== this.state.dragDirection) {
@ -93,9 +101,9 @@ class SidebarRequestGroupRow extends PureComponent {
<ul className={classnames('sidebar__list', {'sidebar__list--collapsed': isCollapsed})}>
{children.length > 0 ? children : (
<SidebarRequestRow
handleActivateRequest={this._nullFunction}
handleDuplicateRequest={this._nullFunction}
handleGenerateCode={this._nullFunction}
handleActivateRequest={misc.nullFn}
handleDuplicateRequest={misc.nullFn}
handleGenerateCode={misc.nullFn}
moveRequest={moveRequest}
isActive={false}
request={null}

View File

@ -1,4 +1,5 @@
import React, {PropTypes, PureComponent} from 'react';
import autobind from 'autobind-decorator';
import ReactDOM from 'react-dom';
import {DragSource, DropTarget} from 'react-dnd';
import classnames from 'classnames';
@ -9,36 +10,42 @@ import * as models from '../../../models';
import {trackEvent} from '../../../analytics/index';
@autobind
class SidebarRequestRow extends PureComponent {
state = {
dragDirection: 0,
isEditing: false,
};
constructor (props) {
super(props);
this.state = {
dragDirection: 0,
isEditing: false,
};
}
_setRequestActionsDropdownRef = n => this._requestActionsDropdown = n;
_setRequestActionsDropdownRef (n) {
this._requestActionsDropdown = n;
}
_handleShowRequestActions = e => {
_handleShowRequestActions (e) {
e.preventDefault();
this._requestActionsDropdown.show();
};
}
_handleEditStart = () => {
_handleEditStart () {
trackEvent('Request', 'Rename', 'In Place');
this.setState({isEditing: true});
};
}
_handleRequestUpdateName = name => {
_handleRequestUpdateName (name) {
models.request.update(this.props.request, {name})
this.setState({isEditing: false});
};
}
_handleRequestCreateFromEmpty = () => {
_handleRequestCreateFromEmpty () {
const parentId = this.props.requestGroup._id;
this.props.requestCreate(parentId);
trackEvent('Request', 'Create', 'Empty Folder');
};
}
_handleRequestActivate = () => {
_handleRequestActivate () {
const {isActive, request, handleActivateRequest} = this.props;
if (isActive) {
@ -47,7 +54,7 @@ class SidebarRequestRow extends PureComponent {
handleActivateRequest(request._id);
trackEvent('Request', 'Activate', 'Sidebar');
};
}
setDragDirection (dragDirection) {
if (dragDirection !== this.state.dragDirection) {

View File

@ -1,9 +1,15 @@
import React, {PureComponent, PropTypes} from 'react';
import autobind from 'autobind-decorator';
import {debounce} from '../../../common/misc';
@autobind
class ResponseRaw extends PureComponent {
// Use a timeout so it doesn't block the UI
_update = debounce(value => this._setTextAreaValue(value));
constructor (props) {
super(props);
// Use a timeout so it doesn't block the UI
this._update = debounce(this._setTextAreaValue);
}
_setTextAreaValue (value) {
// Bail if we're not mounted

View File

@ -1,4 +1,5 @@
import React, {PureComponent, PropTypes} from 'react';
import autobind from 'autobind-decorator';
import {shell} from 'electron';
import Editor from '../codemirror/Editor';
import ResponseWebView from './ResponseWebview';
@ -8,23 +9,27 @@ import {LARGE_RESPONSE_MB, PREVIEW_MODE_FRIENDLY, PREVIEW_MODE_SOURCE} from '../
let alwaysShowLargeResponses = false;
@autobind
class ResponseViewer extends PureComponent {
state = {
blockingBecauseTooLarge: false
};
constructor (props) {
super(props);
this.state = {
blockingBecauseTooLarge: false
};
}
_handleOpenLink = link => {
_handleOpenLink (link) {
shell.openExternal(link);
};
}
_handleDismissBlocker = () => {
_handleDismissBlocker () {
this.setState({blockingBecauseTooLarge: false});
};
_handleDisableBlocker = () => {
_handleDisableBlocker () {
alwaysShowLargeResponses = true;
this._handleDismissBlocker();
};
}
_checkResponseBlocker (props) {
if (alwaysShowLargeResponses) {

View File

@ -1,16 +1,21 @@
import React, {PureComponent, PropTypes} from 'react';
import autobind from 'autobind-decorator';
import contextMenu from 'electron-context-menu';
@autobind
class ResponseWebview extends PureComponent {
_handleSetWebviewRef = n => {
constructor (props) {
super(props);
}
_handleSetWebviewRef (n) {
this._webview = n;
contextMenu({window: this._webview});
};
}
_handleDOMReady = () => {
_handleDOMReady () {
this._webview.removeEventListener('dom-ready', this._handleDOMReady);
this._setBody();
};
}
_setBody () {
const {body, contentType, url} = this.props;

View File

@ -1,12 +1,11 @@
import React, {PureComponent, PropTypes} from 'react';
import autobind from 'autobind-decorator';
import fs from 'fs';
import {ipcRenderer} from 'electron';
import ReactDOM from 'react-dom';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {DragDropContext} from 'react-dnd';
import {toggleModal, showModal} from '../components/modals';
import HTML5Backend from 'react-dnd-html5-backend';
import Wrapper from '../components/Wrapper';
import WorkspaceEnvironmentsEditModal from '../components/modals/WorkspaceEnvironmentsEditModal';
import Toast from '../components/Toast';
@ -39,9 +38,11 @@ const KEY_L = 76;
const KEY_N = 78;
const KEY_P = 80;
@autobind
class App extends PureComponent {
constructor (props) {
super(props);
this.state = {
draggingSidebar: false,
draggingPane: false,
@ -51,6 +52,9 @@ class App extends PureComponent {
this._getRenderContextCache = {};
this._savePaneWidth = debounce(paneWidth => this._updateActiveWorkspaceMeta({paneWidth}));
this._saveSidebarWidth = debounce(sidebarWidth => this._updateActiveWorkspaceMeta({sidebarWidth}));
this._globalKeyMap = [
{ // Show Workspace Settings
meta: true,
@ -130,11 +134,19 @@ class App extends PureComponent {
];
}
_setRequestPaneRef = n => this._requestPane = n;
_setResponsePaneRef = n => this._responsePane = n;
_setSidebarRef = n => this._sidebar = n;
_setRequestPaneRef (n) {
this._requestPane = n;
}
_requestGroupCreate = async (parentId) => {
_setResponsePaneRef (n) {
this._responsePane = n;
}
_setSidebarRef (n) {
this._sidebar = n;
}
async _requestGroupCreate (parentId) {
const name = await showModal(PromptModal, {
headerName: 'New Folder',
defaultValue: 'My Folder',
@ -144,25 +156,25 @@ class App extends PureComponent {
});
models.requestGroup.create({parentId, name})
};
}
_requestCreate = async parentId => {
async _requestCreate (parentId) {
const request = await showModal(RequestCreateModal, {parentId});
this._handleSetActiveRequest(request._id)
};
}
_requestGroupDuplicate = async requestGroup => {
async _requestGroupDuplicate (requestGroup) {
models.requestGroup.duplicate(requestGroup);
};
}
_requestDuplicate = async request => {
async _requestDuplicate (request) {
if (!request) {
return;
}
const newRequest = await models.request.duplicate(request);
await this._handleSetActiveRequest(newRequest._id)
};
}
/**
* Heavily optimized render function
@ -173,7 +185,7 @@ class App extends PureComponent {
* @returns {Promise}
* @private
*/
_handleRenderText = async (text, strict = false, contextCacheKey = null) => {
async _handleRenderText (text, strict = false, contextCacheKey = null) {
if (!contextCacheKey || !this._getRenderContextCache[contextCacheKey]) {
const {activeEnvironment, activeRequest} = this.props;
const environmentId = activeEnvironment ? activeEnvironment._id : null;
@ -187,17 +199,17 @@ class App extends PureComponent {
const context = await this._getRenderContextCache[contextCacheKey];
return render.render(text, context, strict);
};
}
_handleGenerateCodeForActiveRequest = () => {
_handleGenerateCodeForActiveRequest () {
this._handleGenerateCode(this.props.activeRequest);
};
}
_handleGenerateCode = request => {
_handleGenerateCode (request) {
showModal(GenerateCodeModal, request);
};
}
_updateRequestGroupMetaByParentId = async (requestGroupId, patch) => {
async _updateRequestGroupMetaByParentId (requestGroupId, patch) {
const requestGroupMeta = await models.requestGroupMeta.getByParentId(requestGroupId);
if (requestGroupMeta) {
await models.requestGroupMeta.update(requestGroupMeta, patch);
@ -205,9 +217,9 @@ class App extends PureComponent {
const newPatch = Object.assign({parentId: requestGroupId}, patch);
await models.requestGroupMeta.create(newPatch);
}
};
}
_updateActiveWorkspaceMeta = async (patch) => {
async _updateActiveWorkspaceMeta (patch) {
const workspaceId = this.props.activeWorkspace._id;
const requestMeta = await models.workspaceMeta.getByParentId(workspaceId);
if (requestMeta) {
@ -216,9 +228,9 @@ class App extends PureComponent {
const newPatch = Object.assign({parentId: workspaceId}, patch);
await models.workspaceMeta.create(newPatch);
}
};
}
_updateRequestMetaByParentId = async (requestId, patch) => {
async _updateRequestMetaByParentId (requestId, patch) {
const requestMeta = await models.requestMeta.getByParentId(requestId);
if (requestMeta) {
await models.requestMeta.update(requestMeta, patch);
@ -226,50 +238,48 @@ class App extends PureComponent {
const newPatch = Object.assign({parentId: requestId}, patch);
await models.requestMeta.create(newPatch);
}
};
}
_savePaneWidth = debounce(paneWidth => this._updateActiveWorkspaceMeta({paneWidth}));
_handleSetPaneWidth = paneWidth => {
_handleSetPaneWidth (paneWidth) {
this.setState({paneWidth});
this._savePaneWidth(paneWidth);
};
}
_handleSetActiveRequest = async activeRequestId => {
async _handleSetActiveRequest (activeRequestId) {
await this._updateActiveWorkspaceMeta({activeRequestId});
};
_handleSetActiveEnvironment = async activeEnvironmentId => {
async _handleSetActiveEnvironment (activeEnvironmentId) {
await this._updateActiveWorkspaceMeta({activeEnvironmentId});
this._wrapper._forceRequestPaneRefresh();
};
}
_saveSidebarWidth = debounce(sidebarWidth => this._updateActiveWorkspaceMeta({sidebarWidth}));
_handleSetSidebarWidth = sidebarWidth => {
_handleSetSidebarWidth (sidebarWidth) {
this.setState({sidebarWidth});
this._saveSidebarWidth(sidebarWidth);
};
}
_handleSetSidebarHidden = async sidebarHidden => {
async _handleSetSidebarHidden (sidebarHidden) {
await this._updateActiveWorkspaceMeta({sidebarHidden});
};
_handleSetSidebarFilter = async sidebarFilter => {
async _handleSetSidebarFilter (sidebarFilter) {
await this._updateActiveWorkspaceMeta({sidebarFilter});
};
}
_handleSetRequestGroupCollapsed = (requestGroupId, collapsed) => {
_handleSetRequestGroupCollapsed (requestGroupId, collapsed) {
this._updateRequestGroupMetaByParentId(requestGroupId, {collapsed});
};
}
_handleSetResponsePreviewMode = (requestId, previewMode) => {
_handleSetResponsePreviewMode (requestId, previewMode) {
this._updateRequestMetaByParentId(requestId, {previewMode});
};
}
_handleSetResponseFilter = (requestId, responseFilter) => {
_handleSetResponseFilter (requestId, responseFilter) {
this._updateRequestMetaByParentId(requestId, {responseFilter});
};
}
_handleSendAndDownloadRequestWithEnvironment = async (requestId, environmentId, dir) => {
async _handleSendAndDownloadRequestWithEnvironment (requestId, environmentId, dir) {
const request = await models.request.getById(requestId);
if (!request) {
return;
@ -312,9 +322,9 @@ class App extends PureComponent {
// Stop loading
this.props.handleStopLoading(requestId);
};
}
_handleSendRequestWithEnvironment = async (requestId, environmentId) => {
async _handleSendRequestWithEnvironment (requestId, environmentId) {
const request = await models.request.getById(requestId);
if (!request) {
return;
@ -343,39 +353,39 @@ class App extends PureComponent {
// Stop loading
this.props.handleStopLoading(requestId);
};
}
_handleSetActiveResponse = (requestId, activeResponseId) => {
_handleSetActiveResponse (requestId, activeResponseId) {
this._updateRequestMetaByParentId(requestId, {activeResponseId});
};
}
_requestCreateForWorkspace = () => {
_requestCreateForWorkspace () {
this._requestCreate(this.props.activeWorkspace._id);
};
}
_startDragSidebar = () => {
_startDragSidebar () {
trackEvent('Sidebar', 'Drag');
this.setState({draggingSidebar: true})
};
}
_resetDragSidebar = () => {
_resetDragSidebar () {
trackEvent('Sidebar', 'Drag');
// TODO: Remove setTimeout need be not triggering drag on double click
setTimeout(() => this._handleSetSidebarWidth(DEFAULT_SIDEBAR_WIDTH), 50);
};
}
_startDragPane = () => {
_startDragPane () {
trackEvent('App Pane', 'Drag Start');
this.setState({draggingPane: true})
};
}
_resetDragPane = () => {
_resetDragPane () {
trackEvent('App Pane', 'Drag Reset');
// TODO: Remove setTimeout need be not triggering drag on double click
setTimeout(() => this._handleSetPaneWidth(DEFAULT_PANE_WIDTH), 50);
};
}
_handleMouseMove = (e) => {
_handleMouseMove (e) {
if (this.state.draggingPane) {
const requestPane = ReactDOM.findDOMNode(this._requestPane);
const responsePane = ReactDOM.findDOMNode(this._responsePane);
@ -394,9 +404,9 @@ class App extends PureComponent {
let sidebarWidth = Math.max(Math.min(width, MAX_SIDEBAR_REMS), MIN_SIDEBAR_REMS);
this._handleSetSidebarWidth(sidebarWidth);
}
};
}
_handleMouseUp = () => {
_handleMouseUp () {
if (this.state.draggingSidebar) {
this.setState({draggingSidebar: false});
}
@ -404,9 +414,9 @@ class App extends PureComponent {
if (this.state.draggingPane) {
this.setState({draggingPane: false});
}
};
}
_handleKeyDown = e => {
_handleKeyDown (e) {
const isMetaPressed = isMac() ? e.metaKey : e.ctrlKey;
const isShiftPressed = e.shiftKey;
@ -425,15 +435,17 @@ class App extends PureComponent {
callback();
}
};
}
_handleToggleSidebar = async () => {
async _handleToggleSidebar () {
const sidebarHidden = !this.props.sidebarHidden;
await this._handleSetSidebarHidden(sidebarHidden);
trackEvent('Sidebar', 'Toggle Visibility', sidebarHidden ? 'Hide' : 'Show');
};
}
_setWrapperRef = n => this._wrapper = n;
_setWrapperRef (n) {
this._wrapper = n;
}
async componentDidMount () {
// Bind mouse and key handlers
@ -747,6 +759,5 @@ async function _moveRequest (requestToMove, parentId, targetId, targetOffset) {
}
}
const reduxApp = connect(mapStateToProps, mapDispatchToProps)(App);
export default DragDropContext(HTML5Backend)(reduxApp);
export default connect(mapStateToProps, mapDispatchToProps)(App);

View File

@ -77,6 +77,10 @@
}
}
&.modal--noescape .modal__close-btn {
display: none;
}
.modal__body {
overflow: auto;
min-height: 2rem;

View File

@ -11,7 +11,7 @@ html, body, #root {
height: 100%;
}
html, h1, h2, h3, h4, h5, h6, input, p {
html, h1, h2, h3, h4, h5, h6, input {
font-weight: 400;
font-family: @font-default;
}
@ -435,7 +435,7 @@ i.fa {
}
strong {
font-weight: 600;
font-weight: 700;
}
.hide-above-lg {

View File

@ -1,5 +1,6 @@
import React from 'react';
import ReactDOM from 'react-dom';
import {AppContainer} from 'react-hot-loader';
import {Provider} from 'react-redux';
import {Tabs} from 'react-tabs';
import App from './containers/App';
@ -10,10 +11,13 @@ import {init as initSync} from '../sync';
import {init as initAnalytics} from '../analytics';
import {types as modelTypes} from '../models';
import {getAccountId} from '../sync/session';
import HTML5Backend from 'react-dnd-html5-backend';
import {DragDropContext} from 'react-dnd';
// Don't inject component styles (use our own)
Tabs.setUseDefaultStyles(false);
(async function () {
await initDB(modelTypes());
@ -22,11 +26,27 @@ Tabs.setUseDefaultStyles(false);
// Create Redux store
const store = await initStore();
// Actually render the app
ReactDOM.render(
<Provider store={store}><App /></Provider>,
document.getElementById('root')
);
const context = DragDropContext(HTML5Backend);
const DndComponent = context(App);
const render = Component => {
ReactDOM.render(
<AppContainer>
<Provider store={store}>
<Component/>
</Provider>
</AppContainer>,
document.getElementById('root')
);
};
render(DndComponent);
// Hot Module Replacement API
if (module.hot) {
module.hot.accept('./containers/App', () => {
render(DndComponent);
});
}
// Do things that can wait
process.nextTick(initSync);

View File

@ -13,7 +13,7 @@ export default function configureStore () {
const store = createStore(reducer, applyMiddleware(...middleware));
if (__DEV__ && module.hot) {
module.hot.accept('./modules/index', () => {
store.replaceReducer(require('./modules').reducer);
store.replaceReducer(reducer);
});
}

View File

@ -11,12 +11,13 @@
"url": "https://github.com/gschier/insomnia"
},
"scripts": {
"test:noisy": "jest",
"test:coverage": "jest --coverage --silent && open ./coverage/lcov-report/index.html",
"test:watch": "jest --silent --watch",
"test": "jest --silent",
"start-hot": "cross-env HOT=1 INSOMNIA_ENV=development electron -r babel-register ./app/main.development.js",
"hot-server": "babel-node ./webpack/server.js",
"test:noisy": "cross-env NODE_ENV=test jest",
"test:coverage": "cross-env NODE_ENV=test jest --coverage --silent && open ./coverage/lcov-report/index.html",
"test:watch": "cross-env NODE_ENV=test jest --silent --watch",
"test": "cross-env NODE_ENV=test jest --silent",
"start-hot": "npm run build-main && cross-env HOT=1 INSOMNIA_ENV=development electron -r babel-register ./app/main.tmp.js",
"build-main": "cross-env NODE_ENV=development webpack --config ./webpack/webpack.config.electron.babel.js",
"hot-server": "webpack-dev-server --config ./webpack/webpack.config.development.babel.js",
"dev": "concurrently --kill-others \"npm run hot-server\" \"npm run start-hot\"",
"build:clean": "rm -rf ./build && rm -rf ./dist && mkdirp ./build",
"build:renderer": "cross-env NODE_ENV=production webpack --config ./webpack/webpack.config.production.babel.js",
@ -34,26 +35,6 @@
"build-n-package:linux": "npm run build && npm run package:linux",
"sentry": "bash scripts/sentry-release.sh"
},
"babel": {
"presets": [
"es2015",
"react"
],
"plugins": [
"transform-object-rest-spread",
"transform-class-properties",
"transform-regenerator",
"transform-runtime",
"add-module-exports"
],
"env": {
"development": {
"presets": [
"react-hmre"
]
}
}
},
"jest": {
"setupFiles": [
"./__jest__/setup.js"
@ -99,6 +80,7 @@
},
"dependencies": {
"analytics-node": "~2.1.0",
"autobind-decorator": "^1.3.4",
"classnames": "~2.2.5",
"clone": "~2.1.0",
"codemirror": "~5.24.2",
@ -135,39 +117,36 @@
"xpath": "~0.0.23"
},
"devDependencies": {
"babel-cli": "~6.23.0",
"babel-cli": "^6.23.0",
"babel-core": "~6.23.1",
"babel-jest": "~19.0.0",
"babel-loader": "~6.3.2",
"babel-plugin-add-module-exports": "~0.2.1",
"babel-plugin-transform-class-properties": "~6.23.0",
"babel-plugin-transform-decorators-legacy": "^1.3.4",
"babel-plugin-transform-object-rest-spread": "~6.23.0",
"babel-plugin-transform-regenerator": "~6.22.0",
"babel-plugin-transform-runtime": "~6.15.0",
"babel-polyfill": "~6.23.0",
"babel-preset-es2015": "~6.22.0",
"babel-preset-es2015": "^6.22.0",
"babel-preset-react": "~6.23.0",
"babel-preset-react-hmre": "~1.1.1",
"concurrently": "~2.0.0",
"cross-env": "~2.0.0",
"css-loader": "~0.26.2",
"electron": "~1.4.15",
"electron": "~1.6.1",
"electron-builder": "~10.17.3",
"express": "~4.14.0",
"file-loader": "~0.10.1",
"jest": "~18.1.0",
"jsbn": "~0.1.0",
"less": "~2.7.2",
"less-loader": "~2.2.3",
"nock": "~9.0.2",
"react-addons-perf": "~15.3.0",
"react-addons-test-utils": "~15.1.0",
"react-hot-loader": "~1.3.1",
"react-hot-loader": "~3.0.0-beta.6",
"redux-mock-store": "~1.0.2",
"style-loader": "~0.13.2",
"url-loader": "^0.5.8",
"webpack": "~2.2.1",
"webpack-dev-middleware": "~1.10.1",
"webpack-dev-server": "~2.4.1",
"webpack-dev-server": "^2.4.1",
"webpack-hot-middleware": "~2.17.1",
"webpack-target-electron-renderer": "~0.4.0"
},

View File

@ -1,30 +0,0 @@
import express from 'express';
import webpack from 'webpack';
import webpackDevMiddleware from 'webpack-dev-middleware';
import webpackHotMiddleware from 'webpack-hot-middleware';
import {parse as urlParse} from 'url';
import config from './webpack.config.development.babel';
const app = express();
const compiler = webpack(config);
app.use(webpackDevMiddleware(compiler, {
publicPath: config.output.publicPath,
noInfo: true,
stats: {
colors: true
}
}));
app.use(webpackHotMiddleware(compiler));
const parsedUrl = urlParse(config.output.publicPath);
app.listen(parsedUrl.port, parsedUrl.hostname, err => {
if (err) {
console.error(err);
return;
}
console.log(`Listening at http://${parsedUrl.hostname}:${parsedUrl.port}`);
});

View File

@ -1,7 +1,7 @@
import path from 'path';
import * as pkg from '../app/package.json';
const path = require('path');
const pkg = require('../app/package.json');
export default {
module.exports = {
devtool: 'source-map',
context: path.join(__dirname, '../app'),
entry: [
@ -17,7 +17,7 @@ export default {
rules: [
{
test: /\.js$/,
loader: 'babel-loader',
use: ['babel-loader'],
exclude: [/node_modules/, /__fixtures__/, /__tests__/],
},
{
@ -29,11 +29,15 @@ export default {
],
},
{
test: /\.(html|png|woff2)$/,
test: /\.(html|woff2)$/,
loader: 'file-loader',
options: {
name: '[name].[ext]'
}
name: '[name].[ext]',
},
},
{
test: /\.(png)$/,
loader: 'url-loader',
}
]
},

View File

@ -1,28 +1,37 @@
import webpack from 'webpack';
import baseConfig from './webpack.config.base.babel';
const webpack = require('webpack');
const baseConfig = require('./webpack.config.base.babel');
const path = require('path');
const PORT = 3333;
export default {
module.exports = {
...baseConfig,
devtool: 'eval-source-map',
entry: [
'react-hot-loader/patch',
...baseConfig.entry,
`webpack-hot-middleware/client?path=http://localhost:${PORT}/__webpack_hmr`
],
output: {
...baseConfig.output,
publicPath: `http://localhost:${PORT}/build/`
publicPath: `http://localhost:${PORT}/`,
},
devServer: {
hot: true,
hotOnly: true,
noInfo: true,
port: PORT,
publicPath: `http://localhost:${PORT}/`,
},
plugins: [
...baseConfig.plugins,
new webpack.LoaderOptionsPlugin({debug: true}),
new webpack.LoaderOptionsPlugin({debug: true}), // Legacy global loader option
new webpack.HotModuleReplacementPlugin(),
new webpack.NoEmitOnErrorsPlugin(),
new webpack.NamedModulesPlugin(),
new webpack.DefinePlugin({
__DEV__: true,
'process.env.NODE_ENV': JSON.stringify('development'),
'process.env.INSOMNIA_ENV': JSON.stringify('development')
})
]
}
};

View File

@ -1,16 +1,24 @@
import path from 'path';
import productionConfig from './webpack.config.production.babel';
const path = require('path');
const productionConfig = require('./webpack.config.production.babel');
export default {
const output = {
libraryTarget: 'commonjs2'
};
if (process.env.NODE_ENV === 'development') {
output.path = path.join(__dirname, '../app');
output.filename = 'main.tmp.js';
} else {
output.path = path.join(__dirname, '../build');
output.filename = 'main.js';
}
module.exports = {
...productionConfig,
entry: [
'./main.development.js'
],
output: {
path: path.join(__dirname, '../build'),
filename: 'main.js',
libraryTarget: 'commonjs2'
},
output: output,
node: {
__dirname: false,
__filename: false

View File

@ -1,7 +1,7 @@
import webpack from 'webpack';
import baseConfig from './webpack.config.base.babel';
const webpack = require('webpack');
const baseConfig = require('./webpack.config.base.babel');
export default {
module.exports = {
...baseConfig,
devtool: 'source-map',
plugins: [
@ -15,4 +15,4 @@ export default {
'process.env.HOT': JSON.stringify(null),
})
]
}
};