insomnia/packages/insomnia-app/app/ui/components/modals/cookies-modal.tsx
Dimitri Mitropoulos 42341e6e6e
fixes 'previewHidden' of undefined error (#3409)
* readability improvements and reduced indirection

* adds type for handleShowModifyCookieModal

* correctly types wrapperProps property, thereby fixing bug.

if you add `console.log({ previewHidden, propsOne: this.props.wrapperProps.activeWorkspaceMeta });` to the third line of `_renderPreview()` you'll see that `activeWorkspaceMeta` is indeed, sometimes, `undefined.

Also, there's no reason to use `await` on `this.setState`.  I didn't find any more of these in the codebase, I just found this one.

* adds type for swaggerUiSpec

* undoes lifting props to state

almost always, this is done for performance reasons, but I removed it the app is working pretty quick-and-snappy for me without needing to introduced duplicated application state and keep track of it.

I went ahead and measured it before and after this commit (using performance.now):

before = [
  1.93500000750646,
  1.149999996414408,
  0.9499999869149178,
  0.9950000094249845,
  0.8650000090710819,
  1.560000004246831,
  1.5699999930802733,
  0.8450000023003668,
  1.4550000196322799,
  1.3299999991431832,
  1.3050000125076622,
  1.4099999971222132,
  1.3099999923724681,
  1.3100000214762986,
  1.1999999987892807,
  1.0099999781232327,
  0.830000004498288,
  1.2449999921955168,
  1.2500000011641532,
  1.4349999837577343,
]

after = [
  2.9400000057648867,
  2.449999999953434,
  2.33499999740161,
  2.2849999950267375,
  1.7700000025797635,
  1.8149999959859997,
  2.1249999990686774,
  1.9150000007357448,
  2.074999996693805,
  1.9899999897461385,
  2.0200000144541264,
  2.869999996619299,
  2.1450000058393925,
  2.33499999740161,
  2.130000008037314,
  2.119999990100041,
  2.144999976735562,
  2.130000008037314,
  2.380000009201467,
  2.8999999922234565,
]

> R.mean(before)
> 1.2480000004870817

> R.mean(after)
> 2.243749999080319

> R.median(before)
> 1.2775000068359077

> R.median(after)
> 2.137499992386438

So basically, considering a 16ms render rate (i.e. 60hz), 1ms saved by lifting props to state makes no difference in application performance.

This is committed separately so that if there's any reason we want to keep the prior implementation, we can just still do so.
2021-05-24 10:14:00 -04:00

209 lines
6.1 KiB
TypeScript

import React, { ChangeEvent, PureComponent } from 'react';
import deepEqual from 'deep-equal';
import { autoBindMethodsForReact } from 'class-autobind-decorator';
import { AUTOBIND_CFG } from '../../../common/constants';
import Modal, { ModalProps } from '../base/modal';
import ModalBody from '../base/modal-body';
import ModalHeader from '../base/modal-header';
import ModalFooter from '../base/modal-footer';
import CookieList, { CookieListProps } from '../cookie-list';
import * as models from '../../../models';
import type { Cookie, CookieJar } from '../../../models/cookie-jar';
import type { Workspace } from '../../../models/workspace';
import { fuzzyMatch } from '../../../common/misc';
import { HandleRender } from '../../../common/render';
interface Props extends ModalProps {
handleShowModifyCookieModal: CookieListProps['handleShowModifyCookieModal'];
handleRender: HandleRender;
cookieJar: CookieJar;
workspace: Workspace;
}
interface State {
filter: string;
visibleCookieIndexes: number[] | null;
}
@autoBindMethodsForReact(AUTOBIND_CFG)
class CookiesModal extends PureComponent<Props, State> {
modal: Modal | null = null;
filterInput: HTMLInputElement | null = null;
state: State = {
filter: '',
visibleCookieIndexes: null,
}
_setModalRef(n: Modal) {
this.modal = n;
}
_setFilterInputRef(n: HTMLInputElement) {
this.filterInput = n;
}
async _saveChanges() {
const { cookieJar } = this.props;
await models.cookieJar.update(cookieJar);
}
async _handleCookieAdd(cookie: Cookie) {
const { cookieJar } = this.props;
const { cookies } = cookieJar;
cookieJar.cookies = [cookie, ...cookies];
await this._saveChanges();
}
async _handleDeleteAllCookies() {
const { cookieJar } = this.props;
cookieJar.cookies = [];
await this._saveChanges();
}
async _handleCookieDelete(cookie: Cookie) {
const { cookieJar } = this.props;
const { cookies } = cookieJar;
// NOTE: This is sketchy because it relies on the same reference
cookieJar.cookies = cookies.filter(c => c.id !== cookie.id);
await this._saveChanges();
}
async _handleFilterChange(e: ChangeEvent<HTMLInputElement>) {
if (!(e.target instanceof HTMLInputElement)) {
return;
}
const filter = e.target.value;
this._applyFilter(filter, this.props.cookieJar.cookies);
}
// eslint-disable-next-line camelcase
UNSAFE_componentWillReceiveProps(nextProps: Props) {
// Re-filter if we received new cookies
// Compare cookies with Dates cast to strings
const sameCookies = deepEqual(this.props.cookieJar.cookies, nextProps.cookieJar.cookies);
if (!sameCookies) {
this._applyFilter(this.state.filter, nextProps.cookieJar.cookies);
}
}
async _applyFilter(filter: string, cookies: Cookie[]) {
const renderedCookies: Cookie[] = [];
for (const cookie of cookies) {
try {
const renderedCookie = await this.props.handleRender(cookie);
renderedCookies.push(renderedCookie);
} catch (err) {
// It's okay. Filter the raw version instead
renderedCookies.push(cookie);
}
}
let visibleCookieIndexes;
if (filter) {
visibleCookieIndexes = [];
for (let i = 0; i < renderedCookies.length; i++) {
const toSearch = JSON.stringify(renderedCookies[i]);
const match = fuzzyMatch(filter, toSearch, {
splitSpace: true,
});
if (match) {
visibleCookieIndexes.push(i);
}
}
} else {
visibleCookieIndexes = null;
}
this.setState({
filter,
visibleCookieIndexes,
});
}
_getVisibleCookies(): Cookie[] {
const { cookieJar } = this.props;
const { visibleCookieIndexes } = this.state;
if (visibleCookieIndexes === null) {
return cookieJar.cookies;
}
return cookieJar.cookies.filter((_, i) => visibleCookieIndexes.includes(i));
}
async show() {
setTimeout(() => {
this.filterInput && this.filterInput.focus();
}, 100);
// make sure the filter is up to date
await this._applyFilter(this.state.filter, this.props.cookieJar.cookies);
this.modal && this.modal.show();
}
hide() {
this.modal && this.modal.hide();
}
render() {
const { handleShowModifyCookieModal, handleRender, cookieJar } = this.props;
const { filter } = this.state;
const cookies = this._getVisibleCookies();
return (
<Modal ref={this._setModalRef} wide tall {...this.props}>
<ModalHeader>Manage Cookies</ModalHeader>
<ModalBody noScroll>
{cookieJar && (
<div className="cookie-list">
<div className="pad">
<div className="form-control form-control--outlined">
<label>
Filter Cookies
<input
ref={this._setFilterInputRef}
onChange={this._handleFilterChange}
type="text"
placeholder="twitter.com"
defaultValue=""
/>
</label>
</div>
</div>
<div className="cookie-list__list border-tops pad">
<CookieList
cookies={cookies}
handleShowModifyCookieModal={handleShowModifyCookieModal}
handleRender={handleRender}
handleDeleteAll={this._handleDeleteAllCookies}
handleCookieAdd={this._handleCookieAdd}
handleCookieDelete={this._handleCookieDelete} // Set the domain to the filter so that it shows up if we're filtering
newCookieDomainName={filter || 'domain.com'}
/>
</div>
</div>
)}
</ModalBody>
<ModalFooter>
<div className="margin-left faint italic txt-sm tall">
* cookies are automatically sent with relevant requests
</div>
<button className="btn" onClick={this.hide}>
Done
</button>
</ModalFooter>
</Modal>
);
}
} // export CookiesModal;
export default CookiesModal;