mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-07-08 11:56:36 +00:00
Implemented the Network tab.
Signed-off-by: Akos Kitta <kittaakos@typefox.io>
This commit is contained in:
parent
e957ac4331
commit
68b1f8d4f2
@ -41,6 +41,7 @@
|
||||
"@types/ncp": "^2.0.4",
|
||||
"@types/ps-tree": "^1.1.0",
|
||||
"@types/react-select": "^3.0.0",
|
||||
"@types/react-tabs": "^2.3.2",
|
||||
"@types/sinon": "^7.5.2",
|
||||
"@types/temp": "^0.8.34",
|
||||
"@types/which": "^1.3.1",
|
||||
@ -56,7 +57,9 @@
|
||||
"ncp": "^2.0.0",
|
||||
"p-queue": "^5.0.0",
|
||||
"ps-tree": "^1.2.0",
|
||||
"react-disable": "^0.1.0",
|
||||
"react-select": "^3.0.4",
|
||||
"react-tabs": "^3.1.2",
|
||||
"semver": "^7.3.2",
|
||||
"string-natural-compare": "^2.0.3",
|
||||
"temp": "^0.9.1",
|
||||
@ -120,7 +123,7 @@
|
||||
],
|
||||
"arduino": {
|
||||
"cli": {
|
||||
"version": "0.15.0-rc1"
|
||||
"version": "20210203"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,9 @@ import * as React from 'react';
|
||||
import { injectable, inject, postConstruct } from 'inversify';
|
||||
import { Widget } from '@phosphor/widgets';
|
||||
import { Message } from '@phosphor/messaging';
|
||||
import { Tab, Tabs, TabList, TabPanel } from 'react-tabs';
|
||||
import 'react-tabs/style/react-tabs.css';
|
||||
import { Disable } from 'react-disable';
|
||||
import URI from '@theia/core/lib/common/uri';
|
||||
import { Emitter } from '@theia/core/lib/common/event';
|
||||
import { Deferred } from '@theia/core/lib/common/promise-util';
|
||||
@ -15,7 +18,7 @@ import { DisposableCollection } from '@theia/core/lib/common/disposable';
|
||||
import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
|
||||
import { AbstractDialog, DialogProps, PreferenceService, PreferenceScope, DialogError, ReactWidget } from '@theia/core/lib/browser';
|
||||
import { Index } from '../common/types';
|
||||
import { ConfigService, FileSystemExt } from '../common/protocol';
|
||||
import { ConfigService, FileSystemExt, Network, ProxySettings } from '../common/protocol';
|
||||
|
||||
export interface Settings extends Index {
|
||||
editorFontSize: number; // `editor.fontSize`
|
||||
@ -32,6 +35,7 @@ export interface Settings extends Index {
|
||||
|
||||
sketchbookPath: string; // CLI
|
||||
additionalUrls: string[]; // CLI
|
||||
network: Network; // CLI
|
||||
}
|
||||
export namespace Settings {
|
||||
|
||||
@ -40,7 +44,6 @@ export namespace Settings {
|
||||
}
|
||||
|
||||
}
|
||||
export type SettingsKey = keyof Settings;
|
||||
|
||||
@injectable()
|
||||
export class SettingsService {
|
||||
@ -101,7 +104,7 @@ export class SettingsService {
|
||||
this.preferenceService.get<boolean>('arduino.language.log', true),
|
||||
this.configService.getConfiguration()
|
||||
]);
|
||||
const { additionalUrls, sketchDirUri } = cliConfig;
|
||||
const { additionalUrls, sketchDirUri, network } = cliConfig;
|
||||
const sketchbookPath = await this.fileService.fsPath(new URI(sketchDirUri));
|
||||
return {
|
||||
editorFontSize,
|
||||
@ -115,7 +118,8 @@ export class SettingsService {
|
||||
verifyAfterUpload,
|
||||
enableLsLogs,
|
||||
additionalUrls,
|
||||
sketchbookPath
|
||||
sketchbookPath,
|
||||
network
|
||||
};
|
||||
}
|
||||
|
||||
@ -175,7 +179,8 @@ export class SettingsService {
|
||||
verifyAfterUpload,
|
||||
enableLsLogs,
|
||||
sketchbookPath,
|
||||
additionalUrls
|
||||
additionalUrls,
|
||||
network
|
||||
} = this._settings;
|
||||
const [config, sketchDirUri] = await Promise.all([
|
||||
this.configService.getConfiguration(),
|
||||
@ -183,6 +188,7 @@ export class SettingsService {
|
||||
]);
|
||||
(config as any).additionalUrls = additionalUrls;
|
||||
(config as any).sketchDirUri = sketchDirUri;
|
||||
(config as any).network = network;
|
||||
|
||||
await Promise.all([
|
||||
this.preferenceService.set('editor.fontSize', editorFontSize, PreferenceScope.User),
|
||||
@ -230,6 +236,21 @@ export class SettingsComponent extends React.Component<SettingsComponent.Props,
|
||||
if (!this.state) {
|
||||
return <div />;
|
||||
}
|
||||
return <Tabs>
|
||||
<TabList>
|
||||
<Tab>Settings</Tab>
|
||||
<Tab>Network</Tab>
|
||||
</TabList>
|
||||
<TabPanel>
|
||||
{this.renderSettings()}
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
{this.renderNetwork()}
|
||||
</TabPanel>
|
||||
</Tabs>;
|
||||
}
|
||||
|
||||
protected renderSettings(): React.ReactNode {
|
||||
return <div className='content noselect'>
|
||||
Sketchbook location:
|
||||
<div className='flex-line'>
|
||||
@ -343,12 +364,106 @@ export class SettingsComponent extends React.Component<SettingsComponent.Props,
|
||||
</div>;
|
||||
}
|
||||
|
||||
protected renderNetwork(): React.ReactNode {
|
||||
return <div className='content noselect'>
|
||||
<form>
|
||||
<label className='flex-line'>
|
||||
<input
|
||||
type='radio'
|
||||
checked={this.state.network === 'none'}
|
||||
onChange={this.noProxyDidChange} />
|
||||
No proxy
|
||||
</label>
|
||||
<label className='flex-line'>
|
||||
<input
|
||||
type='radio'
|
||||
checked={this.state.network !== 'none'}
|
||||
onChange={this.manualProxyDidChange} />
|
||||
Manual proxy configuration
|
||||
</label>
|
||||
</form>
|
||||
{this.renderProxySettings()}
|
||||
</div>;
|
||||
}
|
||||
|
||||
protected renderProxySettings(): React.ReactNode {
|
||||
const disabled = this.state.network === 'none';
|
||||
return <Disable disabled={disabled}>
|
||||
<div className='proxy-settings' aria-disabled={disabled}>
|
||||
<form className='flex-line'>
|
||||
<input
|
||||
type='radio'
|
||||
checked={this.state.network === 'none' ? true : this.state.network.protocol === 'http'}
|
||||
onChange={this.httpProtocolDidChange} />
|
||||
HTTP
|
||||
<label className='flex-line'>
|
||||
<input
|
||||
type='radio'
|
||||
checked={this.state.network === 'none' ? false : this.state.network.protocol !== 'http'}
|
||||
onChange={this.socksProtocolDidChange} />
|
||||
SOCKS
|
||||
</label>
|
||||
</form>
|
||||
<div className='flex-line proxy-settings'>
|
||||
<div className='column'>
|
||||
<div className='flex-line'>Host name:</div>
|
||||
<div className='flex-line'>Port number:</div>
|
||||
<div className='flex-line'>Username:</div>
|
||||
<div className='flex-line'>Password:</div>
|
||||
</div>
|
||||
<div className='column stretch'>
|
||||
<div className='flex-line'>
|
||||
<input
|
||||
className='theia-input stretch with-margin'
|
||||
type='text'
|
||||
value={this.state.network === 'none' ? '' : this.state.network.hostname}
|
||||
onChange={this.hostnameDidChange} />
|
||||
</div>
|
||||
<div className='flex-line'>
|
||||
<input
|
||||
className='theia-input small with-margin'
|
||||
type='number'
|
||||
pattern='[0-9]'
|
||||
value={this.state.network === 'none' ? '' : this.state.network.port}
|
||||
onKeyDown={this.numbersOnlyKeyDown}
|
||||
onChange={this.portDidChange} />
|
||||
</div>
|
||||
<div className='flex-line'>
|
||||
<input
|
||||
className='theia-input stretch with-margin'
|
||||
type='text'
|
||||
value={this.state.network === 'none' ? '' : this.state.network.username}
|
||||
onChange={this.usernameDidChange} />
|
||||
</div>
|
||||
<div className='flex-line'>
|
||||
<input
|
||||
className='theia-input stretch with-margin'
|
||||
type='password'
|
||||
value={this.state.network === 'none' ? '' : this.state.network.password}
|
||||
onChange={this.passwordDidChange} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Disable>;
|
||||
}
|
||||
|
||||
private isControlKey(event: React.KeyboardEvent<HTMLInputElement>): boolean {
|
||||
return !!event.key && ['tab', 'delete', 'backspace', 'arrowleft', 'arrowright'].some(key => event.key.toLocaleLowerCase() === key);
|
||||
}
|
||||
|
||||
protected noopKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (this.isControlKey(event)) {
|
||||
return;
|
||||
}
|
||||
event.nativeEvent.preventDefault();
|
||||
event.nativeEvent.returnValue = false;
|
||||
}
|
||||
|
||||
protected numbersOnlyKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (this.isControlKey(event)) {
|
||||
return;
|
||||
}
|
||||
const key = Number(event.key)
|
||||
if (isNaN(key) || event.key === null || event.key === ' ') {
|
||||
event.nativeEvent.preventDefault();
|
||||
@ -444,6 +559,79 @@ export class SettingsComponent extends React.Component<SettingsComponent.Props,
|
||||
}
|
||||
};
|
||||
|
||||
protected noProxyDidChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (event.target.checked) {
|
||||
this.setState({ network: 'none' });
|
||||
} else {
|
||||
this.setState({ network: Network.Default() });
|
||||
}
|
||||
};
|
||||
|
||||
protected manualProxyDidChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (event.target.checked) {
|
||||
this.setState({ network: Network.Default() });
|
||||
} else {
|
||||
this.setState({ network: 'none' });
|
||||
}
|
||||
};
|
||||
|
||||
protected httpProtocolDidChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (this.state.network !== 'none') {
|
||||
const network = this.cloneProxySettings;
|
||||
network.protocol = event.target.checked ? 'http' : 'socks';
|
||||
this.setState({ network });
|
||||
}
|
||||
};
|
||||
|
||||
protected socksProtocolDidChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (this.state.network !== 'none') {
|
||||
const network = this.cloneProxySettings;
|
||||
network.protocol = event.target.checked ? 'socks' : 'http';
|
||||
this.setState({ network });
|
||||
}
|
||||
};
|
||||
|
||||
protected hostnameDidChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (this.state.network !== 'none') {
|
||||
const network = this.cloneProxySettings;
|
||||
network.hostname = event.target.value;
|
||||
this.setState({ network });
|
||||
}
|
||||
};
|
||||
|
||||
protected portDidChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (this.state.network !== 'none') {
|
||||
const network = this.cloneProxySettings;
|
||||
network.port = event.target.value;
|
||||
this.setState({ network });
|
||||
}
|
||||
};
|
||||
|
||||
protected usernameDidChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (this.state.network !== 'none') {
|
||||
const network = this.cloneProxySettings;
|
||||
network.username = event.target.value;
|
||||
this.setState({ network });
|
||||
}
|
||||
};
|
||||
|
||||
protected passwordDidChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (this.state.network !== 'none') {
|
||||
const network = this.cloneProxySettings;
|
||||
network.password = event.target.value;
|
||||
this.setState({ network });
|
||||
}
|
||||
};
|
||||
|
||||
private get cloneProxySettings(): ProxySettings {
|
||||
const { network } = this.state;
|
||||
if (network === 'none') {
|
||||
throw new Error('Must be called when proxy is enabled.');
|
||||
}
|
||||
const copyNetwork = deepClone(network);
|
||||
return copyNetwork;
|
||||
}
|
||||
|
||||
}
|
||||
export namespace SettingsComponent {
|
||||
export interface Props {
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
.arduino-settings-dialog .content {
|
||||
padding: 5px;
|
||||
height: 250px;
|
||||
}
|
||||
|
||||
.arduino-settings-dialog .flex-line {
|
||||
@ -25,19 +26,32 @@
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.arduino-settings-dialog .stretch {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.arduino-settings-dialog .flex-line .theia-button.shrink {
|
||||
min-width: unset;
|
||||
}
|
||||
|
||||
.arduino-settings-dialog .theia-input.stretch {
|
||||
width: 100% !important;
|
||||
.arduino-settings-dialog .proxy-settings {
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.arduino-settings-dialog input[type="radio"] {
|
||||
margin: 3px !important;
|
||||
}
|
||||
|
||||
.arduino-settings-dialog .theia-input.small {
|
||||
max-width: 40px;
|
||||
width: 40px;
|
||||
max-width: 50px;
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
.additional-urls-dialog .link:hover {
|
||||
color: var(--theia-textLink-activeForeground);
|
||||
}
|
||||
|
||||
.arduino-settings-dialog .react-tabs__tab-list {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
@ -8,11 +8,79 @@ export interface ConfigService {
|
||||
isInSketchDir(uri: string): Promise<boolean>;
|
||||
}
|
||||
|
||||
export interface ProxySettings {
|
||||
protocol: string;
|
||||
hostname: string;
|
||||
port: string;
|
||||
username: string;
|
||||
password: string;
|
||||
}
|
||||
export type Network = 'none' | ProxySettings;
|
||||
export namespace Network {
|
||||
|
||||
export function Default(): Network {
|
||||
return {
|
||||
protocol: 'http',
|
||||
hostname: '',
|
||||
port: '',
|
||||
username: '',
|
||||
password: ''
|
||||
}
|
||||
}
|
||||
|
||||
export function parse(raw: string | undefined): Network {
|
||||
if (!raw) {
|
||||
return 'none';
|
||||
}
|
||||
try {
|
||||
// Patter: PROTOCOL://USER:PASS@HOSTNAME:PORT/
|
||||
const { protocol, hostname, password, username, port } = new URL(raw);
|
||||
return {
|
||||
protocol,
|
||||
hostname,
|
||||
password,
|
||||
username,
|
||||
port
|
||||
};
|
||||
} catch {
|
||||
return 'none';
|
||||
}
|
||||
};
|
||||
|
||||
export function stringify(network: Network): string | undefined {
|
||||
if (network === 'none') {
|
||||
return undefined;
|
||||
}
|
||||
const { protocol, hostname, password, username, port } = network;
|
||||
try {
|
||||
const defaultUrl = new URL(`${protocol ? protocol : 'http'}://${hostname ? hostname : '_'}`);
|
||||
return Object.assign(defaultUrl, { protocol, hostname, password, username, port }).toString();
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export function sameAs(left: Network, right: Network): boolean {
|
||||
if (left === 'none') {
|
||||
return right === 'none';
|
||||
}
|
||||
if (right === 'none') {
|
||||
return false;
|
||||
}
|
||||
return left.hostname === right.hostname
|
||||
&& left.password === right.password
|
||||
&& left.protocol === right.protocol
|
||||
&& left.username === right.username;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
export interface Config {
|
||||
readonly sketchDirUri: string;
|
||||
readonly dataDirUri: string;
|
||||
readonly downloadsDirUri: string;
|
||||
readonly additionalUrls: string[];
|
||||
readonly network: Network;
|
||||
}
|
||||
export namespace Config {
|
||||
export function sameAs(left: Config, right: Config): boolean {
|
||||
@ -28,6 +96,7 @@ export namespace Config {
|
||||
}
|
||||
return left.dataDirUri === right.dataDirUri
|
||||
&& left.downloadsDirUri === right.downloadsDirUri
|
||||
&& left.sketchDirUri === right.sketchDirUri;
|
||||
&& left.sketchDirUri === right.sketchDirUri
|
||||
&& Network.sameAs(left.network, right.network);
|
||||
}
|
||||
}
|
||||
|
@ -92,11 +92,16 @@ export namespace Logging {
|
||||
|
||||
}
|
||||
|
||||
export interface Network {
|
||||
proxy?: string;
|
||||
}
|
||||
|
||||
// Arduino CLI config scheme
|
||||
export interface CliConfig {
|
||||
board_manager?: RecursivePartial<BoardManager>;
|
||||
directories?: RecursivePartial<Directories>;
|
||||
logging?: RecursivePartial<Logging>;
|
||||
network?: RecursivePartial<Network>;
|
||||
}
|
||||
|
||||
// Bare minimum required CLI config.
|
||||
|
@ -11,9 +11,9 @@ import { ILogger } from '@theia/core/lib/common/logger';
|
||||
import { FileUri } from '@theia/core/lib/node/file-uri';
|
||||
import { Event, Emitter } from '@theia/core/lib/common/event';
|
||||
import { BackendApplicationContribution } from '@theia/core/lib/node/backend-application';
|
||||
import { ConfigService, Config, NotificationServiceServer } from '../common/protocol';
|
||||
import { ConfigService, Config, NotificationServiceServer, Network } from '../common/protocol';
|
||||
import { spawnCommand } from './exec-util';
|
||||
import { RawData, WriteRequest } from './cli-protocol/settings/settings_pb';
|
||||
import { WriteRequest, RawData } from './cli-protocol/settings/settings_pb';
|
||||
import { SettingsClient } from './cli-protocol/settings/settings_grpc_pb';
|
||||
import * as serviceGrpcPb from './cli-protocol/settings/settings_grpc_pb';
|
||||
import { ArduinoDaemonImpl } from './arduino-daemon-impl';
|
||||
@ -78,7 +78,7 @@ export class ConfigServiceImpl implements BackendApplicationContribution, Config
|
||||
if (!copyDefaultCliConfig) {
|
||||
copyDefaultCliConfig = await this.getFallbackCliConfig();
|
||||
}
|
||||
const { additionalUrls, dataDirUri, downloadsDirUri, sketchDirUri } = config;
|
||||
const { additionalUrls, dataDirUri, downloadsDirUri, sketchDirUri, network } = config;
|
||||
copyDefaultCliConfig.directories = {
|
||||
data: FileUri.fsPath(dataDirUri),
|
||||
downloads: FileUri.fsPath(downloadsDirUri),
|
||||
@ -89,6 +89,8 @@ export class ConfigServiceImpl implements BackendApplicationContribution, Config
|
||||
...additionalUrls
|
||||
]
|
||||
};
|
||||
const proxy = Network.stringify(network);
|
||||
copyDefaultCliConfig.network = { proxy };
|
||||
const { port } = copyDefaultCliConfig.daemon;
|
||||
await this.updateDaemon(port, copyDefaultCliConfig);
|
||||
await this.writeDaemonState(port);
|
||||
@ -175,11 +177,13 @@ export class ConfigServiceImpl implements BackendApplicationContribution, Config
|
||||
if (cliConfig.board_manager && cliConfig.board_manager.additional_urls) {
|
||||
additionalUrls.push(...Array.from(new Set(cliConfig.board_manager.additional_urls)));
|
||||
}
|
||||
const network = Network.parse(cliConfig.network?.proxy);
|
||||
return {
|
||||
dataDirUri: FileUri.create(data).toString(),
|
||||
sketchDirUri: FileUri.create(user).toString(),
|
||||
downloadsDirUri: FileUri.create(downloads).toString(),
|
||||
additionalUrls,
|
||||
network
|
||||
};
|
||||
}
|
||||
|
||||
@ -195,7 +199,9 @@ export class ConfigServiceImpl implements BackendApplicationContribution, Config
|
||||
protected async updateDaemon(port: string | number, config: DefaultCliConfig): Promise<void> {
|
||||
const client = this.createClient(port);
|
||||
const data = new RawData();
|
||||
data.setJsondata(JSON.stringify(config, null, 2));
|
||||
const json = JSON.stringify(config, null, 2);
|
||||
data.setJsondata(json);
|
||||
console.log(`Updating daemon with 'data': ${json}`);
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
client.merge(data, error => {
|
||||
try {
|
||||
|
24
yarn.lock
24
yarn.lock
@ -2825,6 +2825,13 @@
|
||||
"@types/react-dom" "*"
|
||||
"@types/react-transition-group" "*"
|
||||
|
||||
"@types/react-tabs@^2.3.2":
|
||||
version "2.3.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-tabs/-/react-tabs-2.3.2.tgz#99fb6866bbc6912d44f7bbc99eca03fbbd217960"
|
||||
integrity sha512-QfMelaJSdMcp+CenKhATp12XFFqqUcLXILgwpX3dgWfVYNZPtsLXZDDCRsVn+kwmBOWB+DFPKpQorxbUhnXINw==
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react-transition-group@*":
|
||||
version "4.4.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.0.tgz#882839db465df1320e4753e6e9f70ca7e9b4d46d"
|
||||
@ -5527,7 +5534,7 @@ cloneable-readable@^1.0.0:
|
||||
process-nextick-args "^2.0.0"
|
||||
readable-stream "^2.3.5"
|
||||
|
||||
clsx@^1.0.4:
|
||||
clsx@^1.0.4, clsx@^1.1.0:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.1.tgz#98b3134f9abbdf23b2663491ace13c5c03a73188"
|
||||
integrity sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==
|
||||
@ -12290,7 +12297,7 @@ promzard@^0.3.0:
|
||||
dependencies:
|
||||
read "1"
|
||||
|
||||
prop-types@^15.5.6, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2:
|
||||
prop-types@^15.5.0, prop-types@^15.5.6, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2:
|
||||
version "15.7.2"
|
||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
|
||||
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
|
||||
@ -12560,6 +12567,11 @@ react-autosize-textarea@^7.0.0:
|
||||
line-height "^0.3.1"
|
||||
prop-types "^15.5.6"
|
||||
|
||||
react-disable@^0.1.0:
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/react-disable/-/react-disable-0.1.0.tgz#e3474aefcb2b91fcb534693c66b7497fb28b85af"
|
||||
integrity sha512-3RbSYuUtakIy1ulCfDf6yoJAsjHFNLv467bSBwyxkNDyez/z7CbcTZ6QUWCrhfybvfm81IN6QT3pUfDoKcvaFQ==
|
||||
|
||||
react-dom@^16.8.0:
|
||||
version "16.14.0"
|
||||
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.14.0.tgz#7ad838ec29a777fb3c75c3a190f661cf92ab8b89"
|
||||
@ -12609,6 +12621,14 @@ react-select@^3.0.4:
|
||||
react-input-autosize "^2.2.2"
|
||||
react-transition-group "^4.3.0"
|
||||
|
||||
react-tabs@^3.1.2:
|
||||
version "3.1.2"
|
||||
resolved "https://registry.yarnpkg.com/react-tabs/-/react-tabs-3.1.2.tgz#9047ad7d8a53d357a67c14ad4c4a64cc16660fa8"
|
||||
integrity sha512-OKS1l7QzSNcn+L2uFsxyGFHdXp9YsPGf/YOURWcImp7xLN36n0Wz+/j9HwlwGtlXCZexwshScR5BrcFbw/3P9Q==
|
||||
dependencies:
|
||||
clsx "^1.1.0"
|
||||
prop-types "^15.5.0"
|
||||
|
||||
react-transition-group@^4.3.0:
|
||||
version "4.4.1"
|
||||
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.1.tgz#63868f9325a38ea5ee9535d828327f85773345c9"
|
||||
|
Loading…
x
Reference in New Issue
Block a user