Skip to content

Commit

Permalink
Convert follow-users to typescript (#795)
Browse files Browse the repository at this point in the history
* First cut at converting follow-users to typescript

* Fix types.d.ts

* Fix final type errors

* Fix ESLint errors for FollowUsers (#798)

* Fix ESLint errors

* Fix types

* Fix input problem

* Fix "Save List"

Co-authored-by: Ishaan Shah <[email protected]>
  • Loading branch information
paramsingh and ishaanshah authored Apr 12, 2020
1 parent 96bd121 commit 00ffdf6
Show file tree
Hide file tree
Showing 3 changed files with 146 additions and 103 deletions.
Original file line number Diff line number Diff line change
@@ -1,10 +1,4 @@
/* eslint-disable */
// TODO: Make the code ESLint compliant
// TODO: Port to typescript

import {
faChevronDown,
faChevronUp,
faPlusCircle,
faSave,
faSitemap,
Expand All @@ -13,12 +7,41 @@ import {
} from "@fortawesome/free-solid-svg-icons";

import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import React from "react";
import { IconProp } from "@fortawesome/fontawesome-svg-core";
import * as React from "react";
import { isNil as _isNil } from "lodash";
import { getArtistLink, getPlayButton, getTrackLink } from "./utils";

export class FollowUsers extends React.Component {
constructor(props) {
type FollowUsersProps = {
followList?: Array<string>;
saveUrl?: string;
listId?: number;
listName?: string;
onUserListChange: (users: Array<string>, dontSendUpdate?: boolean) => void;
creator: ListenBrainzUser;
newAlert: (
type: AlertType,
title: string,
message?: string | JSX.Element
) => void;
playingNow: FollowUsersPlayingNow;
playListen: (listen: Listen) => void;
};

type FollowUsersState = {
users: Array<string>;
saveUrl: string;
listId: number | undefined;
listName: string | undefined;
};

export default class FollowUsers extends React.Component<
FollowUsersProps,
FollowUsersState
> {
textInput = React.createRef<HTMLInputElement>();
nameInput = React.createRef<HTMLInputElement>();
constructor(props: FollowUsersProps) {
super(props);
this.state = {
users: props.followList || [],
Expand All @@ -31,118 +54,138 @@ export class FollowUsers extends React.Component {
this.saveListOnEnter = this.saveListOnEnter.bind(this);
}

addUserToList(event) {
event.preventDefault();
addUserToList = (): void => {
if (!this.textInput) return;
if (!this.textInput.current) return;
const currentValue: string = this.textInput.current.value;
const { users } = this.state;
if (
this.textInput.value === "" ||
this.state.users.find((user) => user === this.textInput.value)
currentValue === "" ||
users.find((user: string) => user === currentValue)
) {
return;
}
this.setState(
(prevState) => {
return { users: prevState.users.concat([this.textInput.value]) };
(prevState: FollowUsersState) => {
return { users: prevState.users.concat([currentValue]) };
},
() => {
this.textInput.value = "";
this.props.onUserListChange(this.state.users);
if (this.textInput && this.textInput.current) {
this.textInput.current.value = "";
}
const { onUserListChange } = this.props;
onUserListChange(users);
}
);
}
};

removeUserFromList(index) {
removeUserFromList = (index: number): void => {
this.setState(
(prevState) => {
(prevState: FollowUsersState) => {
prevState.users.splice(index, 1);
return { users: prevState.users };
},
() => {
this.props.onUserListChange(this.state.users);
const { onUserListChange } = this.props;
const { users } = this.state;
onUserListChange(users);
}
);
}
};

saveFollowList = (): void => {
if (!this.nameInput) return;
if (!this.nameInput.current) return;

saveFollowList(event) {
let { listName } = this.state;
if (!_isNil(this.nameInput.value) && this.nameInput.value.length) {
listName = this.nameInput.value;
const { listName, users, listId, saveUrl } = this.state;
const { creator, newAlert } = this.props;

let finalListName = listName;
if (
!_isNil(this.nameInput.current.value) &&
this.nameInput.current.value.length
) {
finalListName = this.nameInput.current.value;
}
fetch(this.state.saveUrl, {
fetch(saveUrl, {
method: "POST",
body: JSON.stringify({
users: this.state.users,
name: listName,
id: this.state.listId,
users,
name: finalListName,
id: listId === undefined ? listId : null,
}),
headers: { Authorization: `Token ${this.props.creator.auth_token}` },
headers: { Authorization: `Token ${creator.auth_token}` },
})
.then((response) => {
if (!response.ok) {
this.props.newAlert(
"danger",
"Could not save list",
response.statusText
);
return;
throw Error(response.statusText);
}
this.props.newAlert("success", "Successfully saved list");
newAlert("success", "Successfully saved list", "Success");
return response.json();
})
.then((data) => {
console.debug(data);
console.debug(`old List ID: ${this.state.listId}`);
this.setState(
(prevState) => {
return { listId: data.list_id };
},
() => {
console.debug(`new List ID: ${this.state.listId}`);
}
);
this.setState({ listId: data.list_id });
})
.catch((error) => {
console.error(error);
this.props.newAlert("danger", "Could not save list", error.message);
newAlert("danger", "Could not save list", error.message);
});
}
};

newFollowList(event) {
this.setState((prevState) => {
return {
users: [],
listId: null,
listName: null,
};
newFollowList = (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
event.preventDefault();
this.setState({
users: [],
listId: undefined,
listName: "",
});
this.nameInput.value = null;
}
if (this.nameInput && this.nameInput.current) {
this.nameInput.current.value = "";
}
};

addFollowerOnEnter(event) {
addFollowerOnClick = (
event: React.MouseEvent<HTMLButtonElement, MouseEvent>
) => {
event.preventDefault();
this.addUserToList();
};

saveListOnClick = (
event: React.MouseEvent<HTMLButtonElement, MouseEvent>
) => {
event.preventDefault();
this.saveFollowList();
};

addFollowerOnEnter(event: React.KeyboardEvent<HTMLInputElement>) {
if (event.key === "Enter") {
this.addUserToList(event);
this.addUserToList();
}
}

saveListOnEnter(event) {
saveListOnEnter(event: React.KeyboardEvent<HTMLInputElement>) {
if (event.key === "Enter") {
this.saveFollowList(event);
this.saveFollowList();
}
}

render() {
const { listName, users } = this.state;
const { playingNow, playListen } = this.props;
const noTopBottomPadding = {
paddingTop: 0,
paddingBottom: 0,
};

return (
<div className="panel panel-primary">
<div className="panel-heading">
<FontAwesomeIcon icon={faSitemap} flip="vertical" />
<FontAwesomeIcon icon={faSitemap as IconProp} flip="vertical" />
<span
style={{
fontSize: "x-large",
marginLeft: "0.55em",
verticalAign: "middle",
verticalAlign: "middle",
}}
>
Follow users
Expand All @@ -160,7 +203,7 @@ export class FollowUsers extends React.Component {
type="text"
className="form-control"
placeholder="Username…"
ref={(input) => (this.textInput = input)}
ref={this.textInput}
onKeyPress={this.addFollowerOnEnter}
/>
<span className="input-group-btn">
Expand All @@ -169,7 +212,7 @@ export class FollowUsers extends React.Component {
type="button"
onClick={this.addUserToList}
>
<FontAwesomeIcon icon={faPlusCircle} /> Add
<FontAwesomeIcon icon={faPlusCircle as IconProp} /> Add
</button>
</span>
</div>
Expand All @@ -180,9 +223,9 @@ export class FollowUsers extends React.Component {
<input
type="text"
className="form-control"
defaultValue={this.state.listName}
defaultValue={listName}
placeholder="New list name"
ref={(input) => (this.nameInput = input)}
ref={this.nameInput}
onKeyPress={this.saveListOnEnter}
/>
<div className="input-group-btn">
Expand All @@ -191,14 +234,14 @@ export class FollowUsers extends React.Component {
type="button"
onClick={this.saveFollowList.bind(this)}
>
<FontAwesomeIcon icon={faSave} /> Save
<FontAwesomeIcon icon={faSave as IconProp} /> Save
</button>
<button
className="btn btn-danger"
type="button"
onClick={this.newFollowList.bind(this)}
onClick={this.newFollowList}
>
<FontAwesomeIcon icon={faTimes} /> Clear
<FontAwesomeIcon icon={faTimes as IconProp} /> Clear
</button>
</div>
</div>
Expand All @@ -210,41 +253,37 @@ export class FollowUsers extends React.Component {
<tr>
<th>User</th>
<th>Listening now</th>
<th width="50px" />
<th width="65px" />
{/* eslint-disable-next-line jsx-a11y/control-has-associated-label */}
<th style={{ width: "50px" }} />
{/* eslint-disable-next-line jsx-a11y/control-has-associated-label */}
<th style={{ width: "65px" }} />
</tr>
</thead>
<tbody>
{this.state.users.map((user, index) => {
{users.map((user, index) => {
return (
<tr
key={user}
className={this.props.playingNow[user] && "playing_now"}
onDoubleClick={this.props.playListen.bind(
this,
this.props.playingNow[user]
)}
className={playingNow[user] && "playing_now"}
onDoubleClick={playListen.bind(this, playingNow[user])}
>
<td>{user}</td>
<td>
{this.props.playingNow[user] && (
{playingNow[user] && (
<>
{getTrackLink(this.props.playingNow[user])}
{getTrackLink(playingNow[user])}
<span className="small">
{" "}
{getArtistLink(this.props.playingNow[user])}
{getArtistLink(playingNow[user])}
</span>
</>
)}
</td>
<td className="playButton">
{this.props.playingNow[user] &&
{playingNow[user] &&
getPlayButton(
this.props.playingNow[user],
this.props.playListen.bind(
this,
this.props.playingNow[user]
)
playingNow[user],
playListen.bind(this, playingNow[user])
)}
</td>
<td style={noTopBottomPadding}>
Expand All @@ -254,7 +293,7 @@ export class FollowUsers extends React.Component {
aria-label="Remove"
onClick={this.removeUserFromList.bind(this, index)}
>
<FontAwesomeIcon icon={faTrashAlt} />
<FontAwesomeIcon icon={faTrashAlt as IconProp} />
</button>
</td>
</tr>
Expand Down
Loading

0 comments on commit 00ffdf6

Please sign in to comment.