From 00ffdf68bcc836d01031a3f762ef0959d813d393 Mon Sep 17 00:00:00 2001 From: Param Singh Date: Sun, 12 Apr 2020 17:32:51 +0100 Subject: [PATCH] Convert follow-users to typescript (#795) * 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 --- .../src/{follow-users.jsx => FollowUsers.tsx} | 221 ++++++++++-------- .../webserver/static/js/src/RecentListens.tsx | 22 +- .../webserver/static/js/src/types.d.ts | 6 + 3 files changed, 146 insertions(+), 103 deletions(-) rename listenbrainz/webserver/static/js/src/{follow-users.jsx => FollowUsers.tsx} (50%) diff --git a/listenbrainz/webserver/static/js/src/follow-users.jsx b/listenbrainz/webserver/static/js/src/FollowUsers.tsx similarity index 50% rename from listenbrainz/webserver/static/js/src/follow-users.jsx rename to listenbrainz/webserver/static/js/src/FollowUsers.tsx index e8d06f3cdb..6ebcf07dd8 100644 --- a/listenbrainz/webserver/static/js/src/follow-users.jsx +++ b/listenbrainz/webserver/static/js/src/FollowUsers.tsx @@ -1,10 +1,4 @@ -/* eslint-disable */ -// TODO: Make the code ESLint compliant -// TODO: Port to typescript - import { - faChevronDown, - faChevronUp, faPlusCircle, faSave, faSitemap, @@ -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; + saveUrl?: string; + listId?: number; + listName?: string; + onUserListChange: (users: Array, 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; + saveUrl: string; + listId: number | undefined; + listName: string | undefined; +}; + +export default class FollowUsers extends React.Component< + FollowUsersProps, + FollowUsersState +> { + textInput = React.createRef(); + nameInput = React.createRef(); + constructor(props: FollowUsersProps) { super(props); this.state = { users: props.followList || [], @@ -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) => { + 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 + ) => { + event.preventDefault(); + this.addUserToList(); + }; + + saveListOnClick = ( + event: React.MouseEvent + ) => { + event.preventDefault(); + this.saveFollowList(); + }; + + addFollowerOnEnter(event: React.KeyboardEvent) { if (event.key === "Enter") { - this.addUserToList(event); + this.addUserToList(); } } - saveListOnEnter(event) { + saveListOnEnter(event: React.KeyboardEvent) { 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 (
- + Follow users @@ -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} /> @@ -169,7 +212,7 @@ export class FollowUsers extends React.Component { type="button" onClick={this.addUserToList} > - Add + Add
@@ -180,9 +223,9 @@ export class FollowUsers extends React.Component { (this.nameInput = input)} + ref={this.nameInput} onKeyPress={this.saveListOnEnter} />
@@ -191,14 +234,14 @@ export class FollowUsers extends React.Component { type="button" onClick={this.saveFollowList.bind(this)} > - Save + Save
@@ -210,41 +253,37 @@ export class FollowUsers extends React.Component { User Listening now - - + {/* eslint-disable-next-line jsx-a11y/control-has-associated-label */} + + {/* eslint-disable-next-line jsx-a11y/control-has-associated-label */} + - {this.state.users.map((user, index) => { + {users.map((user, index) => { return ( {user} - {this.props.playingNow[user] && ( + {playingNow[user] && ( <> - {getTrackLink(this.props.playingNow[user])} + {getTrackLink(playingNow[user])} {" "} - — {getArtistLink(this.props.playingNow[user])} + — {getArtistLink(playingNow[user])} )} - {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]) )} @@ -254,7 +293,7 @@ export class FollowUsers extends React.Component { aria-label="Remove" onClick={this.removeUserFromList.bind(this, index)} > - + diff --git a/listenbrainz/webserver/static/js/src/RecentListens.tsx b/listenbrainz/webserver/static/js/src/RecentListens.tsx index 21c593820e..f39b10ba80 100644 --- a/listenbrainz/webserver/static/js/src/RecentListens.tsx +++ b/listenbrainz/webserver/static/js/src/RecentListens.tsx @@ -10,8 +10,7 @@ import * as ReactDOM from "react-dom"; import * as _ from "lodash"; import * as io from "socket.io-client"; import SpotifyPlayer from "./SpotifyPlayer"; -// @ts-ignore -import { FollowUsers } from "./follow-users"; +import FollowUsers from "./FollowUsers"; import APIService from "./APIService"; import { getArtistLink, @@ -26,7 +25,7 @@ export interface RecentListensProps { apiUrl: string; artistCount?: number | null | undefined; followList?: string[]; - followListId?: string; + followListId?: number; followListName?: string; haveListenCount?: boolean; latestListenTs?: number; @@ -39,7 +38,7 @@ export interface RecentListensProps { profileUrl?: string; saveUrl?: string; spotify: SpotifyUser; - user: User; + user: ListenBrainzUser; webSocketsServerUrl: string; } @@ -49,7 +48,7 @@ export interface RecentListensState { currentListen: Listen; direction: SpotifyPlayDirection; followList: Array; - listId: string; + listId?: number; listName: string; listens: Array; mode: "listens" | "follow" | "recent"; @@ -79,7 +78,7 @@ export default class RecentListens extends React.Component< playingNowByUser: {}, saveUrl: props.saveUrl || "", listName: props.followListName || "", - listId: props.followListId || "", + listId: props.followListId || undefined, direction: "down", }; @@ -124,7 +123,7 @@ export default class RecentListens extends React.Component< handleFollowUserListChange = ( userList: string[], - dontSendUpdate: boolean + dontSendUpdate?: boolean ): void => { const { mode } = this.state; const { user } = this.props; @@ -252,7 +251,7 @@ export default class RecentListens extends React.Component< newAlert = ( type: AlertType, title: string, - message: string | JSX.Element + message?: string | JSX.Element ): void => { const newAlert = { id: new Date().getTime(), @@ -394,7 +393,7 @@ export default class RecentListens extends React.Component< User )} {/* eslint-disable-next-line jsx-a11y/control-has-associated-label */} - + @@ -408,11 +407,10 @@ export default class RecentListens extends React.Component< } return 0; }) - .map((listen, index) => { + .map((listen) => { return (