Skip to content
This repository was archived by the owner on May 5, 2021. It is now read-only.

Commit 5017581

Browse files
author
Jacob Peddicord
committed
Support editing packages & usage info
Fixes #17
1 parent c8d614b commit 5017581

File tree

18 files changed

+254
-127
lines changed

18 files changed

+254
-127
lines changed

browser/components/projects/acl/ProjectAclEditor.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,10 @@
1414

1515
import * as React from 'react';
1616
import { connect } from 'react-redux';
17-
import { Link } from 'react-router-dom';
1817

1918
import { AccessLevel, AccessLevelStrength, WebProject } from '../../../../server/api/projects/interfaces';
2019
import history from '../../../history';
2120
import * as ProjectActions from '../../../modules/projects';
22-
import GroupSelect from './GroupSelect';
2321

2422
interface Props {
2523
dispatch: (action: any) => any;

browser/components/projects/editor/AddPackageForm.tsx renamed to browser/components/projects/editor/PackageEditor.tsx

Lines changed: 37 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,13 @@ import * as ProjectActions from '../../../modules/projects';
2222
import PackageFields, { PkgOutput } from './PackageFields';
2323
import UsageFields from './UsageFields';
2424

25-
interface Props {
25+
interface OwnProps {
26+
initialPackage?: PkgOutput;
27+
initialUsage?: Partial<PackageUsage>;
2628
onCompleted: () => any;
29+
}
2730

31+
interface Props extends OwnProps {
2832
dispatch: (action: any) => any;
2933
project: WebProject;
3034
licenses: Map<string, any>;
@@ -39,14 +43,19 @@ interface State {
3943
/**
4044
* A form used to attach (optionally create) a package to a project.
4145
*/
42-
class AddPackageForm extends Component<Props, Partial<State>> {
46+
class PackageEditor extends Component<Props, Partial<State>> {
47+
48+
static defaultProps = {
49+
initialPackage: null,
50+
initialUsage: {},
51+
};
4352

4453
constructor(props) {
4554
super(props);
4655

4756
this.state = {
48-
pkg: null,
49-
usage: {},
57+
pkg: props.initialPackage,
58+
usage: props.initialUsage,
5059
};
5160
}
5261

@@ -84,14 +93,14 @@ class AddPackageForm extends Component<Props, Partial<State>> {
8493
}
8594

8695
render() {
87-
const { licenses, tags } = this.props;
96+
const { initialPackage, initialUsage, licenses, tags } = this.props;
8897
const { pkg } = this.state;
8998

9099
// XXX: move this out of the render path?
91100
// collect questions from tags
92101
let questions = {};
93-
if (pkg && pkg.license) {
94-
const license = licenses.get(pkg.license);
102+
const license = pkg && pkg.license && licenses.get(pkg.license);
103+
if (license) {
95104
questions = license.tags
96105
.map((name) => tags[name].questions || {})
97106
.reduce((acc, curr) => ({
@@ -105,22 +114,35 @@ class AddPackageForm extends Component<Props, Partial<State>> {
105114

106115
return (
107116
<form id="add-package-form" className="form mt-4" onSubmit={this.handleSubmit}>
108-
109-
<h4>Package Details</h4>
110117
{this.renderPackageHelp()}
111-
<PackageFields onChange={this.handlePkgChanged} />
118+
119+
<h4>
120+
Package Details{' '}
121+
{initialPackage &&
122+
<span className="badge badge-warning">Editing <strong>{initialPackage.name}</strong></span>
123+
}
124+
</h4>
125+
<PackageFields
126+
initial={initialPackage}
127+
onChange={this.handlePkgChanged}
128+
/>
112129

113130
{pkg &&
114131
<div>
115132
<h4>Usage details <small className="text-muted">In your project</small></h4>
116133
<UsageFields
134+
initial={initialUsage}
135+
questions={questions}
117136
onChange={this.handleUsageChanged}
118-
questions={questions} />
137+
/>
119138
</div>
120139
}
121140

122141
<button type="submit" className="btn btn-primary">
123-
<i className="fa fa-plus" /> Add
142+
{initialPackage ?
143+
<span><i className="fa fa-floppy-o" /> Save Changes</span> :
144+
<span><i className="fa fa-plus" /> Add</span>
145+
}
124146
</button>
125147

126148
</form>
@@ -136,7 +158,7 @@ class AddPackageForm extends Component<Props, Partial<State>> {
136158
return (
137159
<div className="row">
138160
<div className="col-md-12">
139-
<div className="card">
161+
<div className="card mb-3">
140162
<div className="card-body">
141163
<h5 className="card-heading">How to identify licenses and copyrights</h5>
142164
<div className="card-text">
@@ -173,9 +195,8 @@ class AddPackageForm extends Component<Props, Partial<State>> {
173195

174196
}
175197

176-
export default connect((state: any) => ({
198+
export default connect((state: any, props: OwnProps) => ({
177199
project: state.projects.active,
178200
licenses: state.licenses.map,
179201
tags: state.licenses.tags,
180-
}))(AddPackageForm) as React.ComponentClass<Partial<Props>>;
181-
// type re-casted with prop information since not all props are redux-supplied
202+
}))(PackageEditor);

browser/components/projects/editor/PackageFields.tsx

Lines changed: 63 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,12 @@ import FreeformSelect from '../../util/FreeformSelect';
2525

2626
export type PkgOutput = Partial<WebPackage>;
2727

28-
interface Props {
28+
interface OwnProps {
2929
initial?: Partial<WebPackage>;
3030
onChange?: (usage: PkgOutput) => void;
31+
}
3132

33+
interface Props extends OwnProps {
3234
dispatch: (action: any) => any;
3335
completions: WebPackage[];
3436
packages: PackageActions.PackageSet;
@@ -38,7 +40,12 @@ interface Props {
3840

3941
interface State {
4042
pkg: Partial<WebPackage>;
41-
selectedPackage: any;
43+
selectedPackage: PackageOption;
44+
}
45+
46+
interface PackageOption extends Option {
47+
name: string;
48+
version: string;
4249
}
4350

4451
interface LicenseOption extends Option {
@@ -49,7 +56,6 @@ interface LicenseOption extends Option {
4956
}
5057

5158
class PackageFields extends React.Component<Props, State> {
52-
5359
licenseOptions: LicenseOption[] = [];
5460
licenseMap: {[name: string]: LicenseOption} = {};
5561

@@ -69,20 +75,12 @@ class PackageFields extends React.Component<Props, State> {
6975
const { dispatch, licenses } = this.props;
7076
if (licenses.length === 0) {
7177
dispatch(LicenseActions.fetchLicenses());
72-
} else {
73-
this.initLicenses();
74-
}
75-
}
76-
77-
componentWillReceiveProps() {
78-
// no need to re-compute if the list was already built
79-
if (this.licenseOptions.length === 0) {
80-
this.initLicenses();
8178
}
8279
}
8380

8481
componentDidMount() {
8582
this.attachBootstrap();
83+
this.setInitialPackage();
8684
}
8785

8886
componentDidUpdate() {
@@ -92,6 +90,10 @@ class PackageFields extends React.Component<Props, State> {
9290
initLicenses() {
9391
const { licenses, tags } = this.props;
9492

93+
if (licenses.length === 0 || this.licenseOptions.length > 0) {
94+
return;
95+
}
96+
9597
for (const license of licenses) {
9698
// gather tag presentation data
9799
const {shortText, longText, fixedText} = license.tags
@@ -145,37 +147,67 @@ class PackageFields extends React.Component<Props, State> {
145147

146148
return [
147149
...options,
148-
{value: filter, label: `Create package ${filter}`, create: true},
150+
{value: filter, label: filter, create: true},
149151
];
150152
}
151153

154+
packageOptionRenderer = (option: PackageOption) => {
155+
if (option.create) {
156+
return <span className="create-package-option">Create package <strong>{option.value}</strong></span>;
157+
}
158+
159+
return <span className="package-option">{option.name} <small>{option.version}</small></span>;
160+
}
161+
152162
mapPackageCompletions = () => {
153163
return this.props.completions.map((item) => {
154164
return {
155165
value: item.packageId,
156166
label: `${item.name} (${item.version})`,
167+
name: item.name,
168+
version: item.version,
157169
};
158170
});
159171
}
160172

161-
isCreating = () => {
162-
return this.state.pkg.packageId == null;
173+
setInitialPackage = () => {
174+
const { initial, packages } = this.props;
175+
if (initial == null || initial.packageId == null) {
176+
return;
177+
}
178+
179+
if (this.state.selectedPackage != null) {
180+
return;
181+
}
182+
183+
const pkg = packages[initial.packageId];
184+
if (pkg == null) {
185+
return;
186+
}
187+
188+
this.handlePackageChange({
189+
label: `Editing ${pkg.name}`,
190+
value: initial.packageId,
191+
name: pkg.name,
192+
version: pkg.version,
193+
});
163194
}
164195

165-
handlePackageChange = (selected) => {
196+
handlePackageChange = (selected: PackageOption) => {
166197
// empty input? clear it all
167198
if (selected == null || selected.value === '') {
168199
this.propagateState({pkg: {}, selectedPackage: null});
169200
return;
170201
}
171202

172-
const isCreating = selected.create === true;
203+
const value = selected.value as number;
173204

205+
const isCreating = selected.create === true;
174206
if (isCreating) {
175207
this.propagateState({
176208
selectedPackage: selected,
177209
pkg: {
178-
name: selected.value,
210+
name: selected.value as string, // value is a string when accepting custom input
179211
version: '',
180212
website: '',
181213
license: '',
@@ -184,7 +216,7 @@ class PackageFields extends React.Component<Props, State> {
184216
},
185217
});
186218
} else {
187-
const pkg = this.props.packages[selected.value];
219+
const pkg = this.props.packages[value];
188220
this.propagateState({
189221
selectedPackage: selected,
190222
pkg: {
@@ -202,7 +234,6 @@ class PackageFields extends React.Component<Props, State> {
202234

203235
handleChange = (e) => {
204236
const val = e.target.value;
205-
206237
this.propagateState({pkg: {
207238
...this.state.pkg,
208239
[e.target.name]: val,
@@ -245,13 +276,16 @@ class PackageFields extends React.Component<Props, State> {
245276
}
246277

247278
render() {
279+
248280
return <div>
249281
<div className="form-group" id="package-container">
250282
<label htmlFor="package">Package</label>
251283
<Select
252284
name="package"
253285
value={this.state.selectedPackage}
254286
options={this.mapPackageCompletions()}
287+
optionRenderer={this.packageOptionRenderer}
288+
valueRenderer={this.packageOptionRenderer}
255289
onInputChange={this.searchPackages}
256290
filterOptions={this.filterPackageList}
257291
onChange={this.handlePackageChange}
@@ -268,6 +302,8 @@ class PackageFields extends React.Component<Props, State> {
268302

269303
renderDetails() {
270304
const { pkg } = this.state;
305+
this.initLicenses();
306+
271307
const needsFullLicense = this.needsFullLicense();
272308
const largeCopyrightStatement = this.largeCopyrightStatement();
273309
const license: Partial<LicenseOption> = this.licenseMap[pkg.license] ||
@@ -276,13 +312,11 @@ class PackageFields extends React.Component<Props, State> {
276312
return <div className="row">
277313

278314
<div className="col-md-6">
279-
{this.isCreating() &&
280-
<div className="form-group">
281-
<label htmlFor="packageVersion">Version</label>
282-
<input type="text" name="version" id="packageVersion" className="form-control"
283-
value={pkg.version} onChange={this.handleChange} />
284-
</div>
285-
}
315+
<div className="form-group">
316+
<label htmlFor="packageVersion">Version</label>
317+
<input type="text" name="version" id="packageVersion" className="form-control"
318+
value={pkg.version} onChange={this.handleChange} />
319+
</div>
286320

287321
<div className="form-group">
288322
<label htmlFor="packageLicense">License</label>
@@ -333,9 +367,9 @@ class PackageFields extends React.Component<Props, State> {
333367

334368
}
335369

336-
export default connect((state: any) => ({
370+
export default connect((state: any, props: OwnProps) => ({
337371
completions: state.packages.completions,
338372
packages: state.packages.set,
339373
licenses: state.licenses.list,
340374
tags: state.licenses.tags,
341-
}))(PackageFields as any) as React.ComponentClass<Partial<Props>>;
375+
}))(PackageFields);

browser/components/projects/editor/ProjectOnboardingForm.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import * as React from 'react';
1616
import { Component } from 'react';
1717

1818
import { connect } from 'react-redux';
19-
import { WebProject } from '../../../../server/api/projects/interfaces';
2019
import * as ProjectActions from '../../../modules/projects';
2120
import GroupSelect from '../acl/GroupSelect';
2221

0 commit comments

Comments
 (0)