Skip to content

Commit ba41ba8

Browse files
committed
New Stuff:
- Added stream profile pic and info - Display preferences are now persisted Changes: - AppActions.js: Added setPreference action for storing display preference on app state - AppReducer.js: Added preference to app state and added action handler - StreamActions.js: Automatically fetch streamer profile data - StreamList.js: - Added profile image toggle - Shortened toggle names - Added channel profile image to list - Added partner badge to streamers with partner status - Replaced toggle handlers with app state actions - StreamList.scss: Tweaked color scheme and added styles for profile image layout and partner badges - UserActions.js: Added for user profile caching - UserReducer.js: Added for user profile caching - configureStore.js: Updated to persist app preferences and user profile cache - profile.svg: Added placeholder profile image I made for dirty flyer - reducers.js: Added user state - package.json: Bumped to 1.2.0
1 parent 3ea0b6c commit ba41ba8

File tree

11 files changed

+379
-43
lines changed

11 files changed

+379
-43
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "dirty-raid",
3-
"version": "1.1.0",
3+
"version": "1.2.0",
44
"repository": {
55
"type": "git",
66
"url": "https://github.com/kfitzgerald/dirty-raid"

src/app/AppActions.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,14 @@ export function crashApp(error, errorInfo) {
77
error,
88
errorInfo
99
};
10-
}
10+
}
11+
12+
export const SET_PREFERENCE = 'SET_PREFERENCE';
13+
export function setPreference(key, value) {
14+
return {
15+
type: SET_PREFERENCE,
16+
key,
17+
value
18+
};
19+
}
20+

src/app/AppReducer.js

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
1-
import {APP_CRASH} from "./AppActions";
1+
import {APP_CRASH, SET_PREFERENCE} from "./AppActions";
22

3-
const initialState = {
3+
export const initialState = {
44
version: process.env.REACT_APP_VERSION,
55
appCrashed: false,
6+
preferences: {
7+
showTitles: true,
8+
showTags: true,
9+
showProfileImg: true
10+
}
611
};
712

813
export default function AppReducer(state = initialState, action) {
@@ -17,6 +22,15 @@ export default function AppReducer(state = initialState, action) {
1722
}
1823
};
1924

25+
case SET_PREFERENCE:
26+
return {
27+
...state,
28+
preferences: {
29+
...state.preferences,
30+
[action.key]: action.value
31+
}
32+
};
33+
2034
default:
2135
return state;
2236
}

src/configureStore.js

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import Thunk from 'redux-thunk';
44

55
import rootReducer from './reducers';
66
import {initialSessionState} from "./session/SessionReducer";
7+
import {initialState as initialAppState} from './app/AppReducer';
8+
import {initialState as initialUserState} from './users/UserReducer';
79

810
let composer = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
911

@@ -13,7 +15,21 @@ let enhancers = composer(
1315
slicer: (/*paths*/) => {
1416
return (state) => {
1517
return {
16-
session: state.session,
18+
app: {
19+
preferences: {
20+
...state.app.preferences,
21+
}
22+
},
23+
session: {
24+
token: {
25+
...state.session.token || {},
26+
}
27+
},
28+
users: {
29+
cache: {
30+
...state.users.cache,
31+
}
32+
}
1733
};
1834
};
1935
},
@@ -22,12 +38,28 @@ let enhancers = composer(
2238
// TODO: add a check for app version, and only merge if versions match
2339
return {
2440
...initialState, // ignore stored fetching/error states
41+
42+
app: {
43+
...initialAppState,
44+
preferences: {
45+
...initialAppState.preferences,
46+
...persistedState.app.preferences
47+
}
48+
},
49+
2550
session: {
2651
...initialSessionState, // default session state
2752
token: {
2853
...persistedState.session.token, // only persist the token, validation will be done on load
2954
}
55+
},
3056

57+
users: {
58+
...initialUserState,
59+
cache: {
60+
...initialUserState.cache,
61+
...persistedState.users.cache
62+
}
3163
}
3264
};
3365
}

src/profile.svg

Lines changed: 29 additions & 0 deletions
Loading

src/reducers.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@ import { combineReducers } from 'redux';
22
import AppReducer from "./app/AppReducer";
33
import SessionReducer from "./session/SessionReducer";
44
import StreamReducer from "./streams/StreamReducer";
5+
import UserReducer from "./users/UserReducer";
56

67
// noinspection JSUnusedGlobalSymbols
78
const reducers = combineReducers({
89
app: AppReducer,
910
session: SessionReducer,
10-
streams: StreamReducer
11+
streams: StreamReducer,
12+
users: UserReducer
1113
});
1214

1315
export default reducers;

src/streams/StreamActions.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {apiDelete, apiGet, apiPost} from "../common/API";
22
import {revokeToken} from "../session/SessionActions";
3+
import {fetchUsers} from "../users/UserActions";
34

45
//region Twitch Followed Streams
56

@@ -83,6 +84,7 @@ export function fetchFollowedStreams(callback=() => {}) {
8384
}
8485

8586
// success
87+
dispatch(fetchUsers(streamList.map(s => s.user_id))); // fetch user info
8688
dispatch(requestFollowedStreamsSuccess(streamList));
8789
callback(null, streamList);
8890

src/streams/StreamList.js

Lines changed: 57 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import {TimeDuration} from "./TimeDuration";
1010
import {SORT_BY, SORT_DIRECTION, SortableField} from "./SortableField";
1111
import {revokeToken} from "../session/SessionActions";
1212
import Moment from "moment";
13+
import profileImage from '../profile.svg';
14+
import {setPreference} from "../app/AppActions";
1315

1416
/**
1517
* Stream list view (the main screen of the app)
@@ -22,13 +24,16 @@ function StreamList() {
2224
// todo - consider persisting local preference state to the app state
2325
const [sortBy, setSortBy] = useState(SORT_BY.VIEWER_COUNT); // default api sort
2426
const [sortDir, setSortDir] = useState(SORT_DIRECTION.DESC);
25-
const [showTitles, setShowTitles] = useState(true);
26-
const [showTags, setShowTags] = useState(true);
27+
const { showTitles, showTags, showProfileImg } = useSelector(state => state.app.preferences);
28+
// const [showTitles, setShowTitles] = useState(true);
29+
// const [showTags, setShowTags] = useState(true);
30+
// const [showProfileImg, setShowProfileImg] = useState(true);
2731
const [selectedStreamId, setSelectedStream] = useState(null);
2832
const [showModal, setShowModal] = useState(null);
2933

3034
const streams = useSelector(state => state.streams);
31-
const login = useSelector(state => state.session.data.login);
35+
const { user_id, login } = useSelector(state => state.session.data);
36+
const userCache = useSelector(state => state.users.cache);
3237

3338
const { isFetching, lastError, data, raid, lastUpdated } = streams;
3439
const { isFetching: isRaidFetching, /*isCancelled, lastError: raidLastError,*/ lastUpdated: raidStartedAt } = raid;
@@ -62,12 +67,16 @@ function StreamList() {
6267
}, [setSortBy, setSortDir]);
6368

6469
const handleToggleTitle = useCallback((e) => {
65-
setShowTitles(e.target.checked);
66-
}, [setShowTitles]);
70+
dispatch(setPreference('showTitles', e.target.checked));
71+
}, [dispatch]);
6772

6873
const handleToggleTags = useCallback((e) => {
69-
setShowTags(e.target.checked);
70-
}, [setShowTags]);
74+
dispatch(setPreference('showTags', e.target.checked));
75+
}, [dispatch]);
76+
77+
const handleToggleProfileImg = useCallback((e) => {
78+
dispatch(setPreference('showProfileImg', e.target.checked));
79+
}, [dispatch]);
7180

7281
const handleRefresh = useCallback(() => {
7382
dispatch(fetchFollowedStreams());
@@ -143,7 +152,7 @@ function StreamList() {
143152
<Navbar.Brand>DirtyRaid™</Navbar.Brand>
144153
<Navbar.Toggle />
145154
<Navbar.Collapse className="justify-content-end">
146-
<NavDropdown title={<><img src={twitchLogo} alt="" /> {login}</>} id="user-dropdown" align="end">
155+
<NavDropdown title={<><img src={twitchLogo} alt="" /> {userCache[user_id]?.display_name || login}</>} id="user-dropdown" align="end">
147156
<NavDropdown.Item onClick={handleSignOut}>Sign out</NavDropdown.Item>
148157
</NavDropdown>
149158
<Navbar.Text>
@@ -162,10 +171,13 @@ function StreamList() {
162171
<label>Display Options:</label>
163172
<div>
164173
<div>
165-
<Form.Check type="switch" id="show-title" label="Show titles" checked={showTitles} onChange={handleToggleTitle} />
174+
<Form.Check type="switch" id="show-title" label="Titles" checked={showTitles} onChange={handleToggleTitle} />
175+
</div>
176+
<div>
177+
<Form.Check type="switch" id="show-tags" label="Tags" checked={showTags} onChange={handleToggleTags} />
166178
</div>
167179
<div>
168-
<Form.Check type="switch" id="show-tags" label="Show tags" checked={showTags} onChange={handleToggleTags} />
180+
<Form.Check type="switch" id="show-tags" label="Pic" checked={showProfileImg} onChange={handleToggleProfileImg} />
169181
</div>
170182
<div className="refresh text-end flex-grow-1">
171183
<Button disabled={isFetching} onClick={handleRefresh}><i className="bi bi-arrow-clockwise"/></Button>
@@ -184,26 +196,36 @@ function StreamList() {
184196
<div className="stream-list">
185197
{
186198
streamList.map((stream, i) => {
199+
const profile = userCache[stream.user_id];
187200
return (
188201
<div className="stream" key={i} data-id={stream.id} onClick={handleStreamClick}>
189-
190-
<Row className="whodis">
191-
<Col>
192-
<div className="d-flex justify-content-between">
193-
<div className="flex-grow-1 user-name"><span>{stream.user_name}</span></div>
194-
<div className="flex-grow-0 participation">
195-
<span className="viewers"><i className="bi bi-eye-fill"/> {
196-
CondensedFormatter(stream.viewer_count, 0)}</span>
197-
<TimeDuration time={stream.started_at} />
198-
</div>
202+
<div className="info-container">
203+
{showProfileImg && (
204+
<div className="profile-container">
205+
<img src={profile?.profile_image_url || profileImage} alt="" />
199206
</div>
200-
</Col>
201-
</Row>
202-
{showTitles && (
203-
<Row>
204-
<Col className="title">{stream.title}</Col>
205-
</Row>
206-
)}
207+
)}
208+
<div className="stream-info">
209+
<Row className="whodis">
210+
<Col>
211+
<div className="d-flex justify-content-between">
212+
<div className="flex-grow-1 user-name"><span>{profile && profile.broadcaster_type === 'partner' &&
213+
<i className="bi bi-patch-check-fill partner"/>}{stream.user_name}</span></div>
214+
<div className="flex-grow-0 participation">
215+
<span className="viewers"><i className="bi bi-eye-fill"/> {
216+
CondensedFormatter(stream.viewer_count, 0)}</span>
217+
<TimeDuration time={stream.started_at} />
218+
</div>
219+
</div>
220+
</Col>
221+
</Row>
222+
{showTitles && (
223+
<Row>
224+
<Col className="title">{stream.title}</Col>
225+
</Row>
226+
)}
227+
</div>
228+
</div>
207229
{showTags && (
208230
<Row>
209231
<Col className="tags">
@@ -225,16 +247,19 @@ function StreamList() {
225247
<Modal show={showModal} onHide={handleCloseModal} size="lg" centered className="stream-modal">
226248
<Modal.Header closeButton>
227249
<Modal.Title id="stuffs">
250+
{userCache[selectedStream?.user_id] && userCache[selectedStream?.user_id].broadcaster_type === 'partner' &&
251+
<i className="bi bi-patch-check-fill partner"/>}
228252
{selectedStream?.user_name || 'Streamer Offline!'}
229253
</Modal.Title>
230254
</Modal.Header>
231255
<Modal.Body>
232256
{selectedStream ? (
233-
<><Row>
234-
<Col>
235-
<img src={selectedStream.thumbnail_url.replace(/{width}x{height}/, '400x225')+'?nonce='+lastUpdated} alt="" />
236-
</Col>
237-
</Row>
257+
<>
258+
<Row>
259+
<Col>
260+
<img src={selectedStream.thumbnail_url.replace(/{width}x{height}/, '400x225')+'?nonce='+lastUpdated} alt="" />
261+
</Col>
262+
</Row>
238263
<Row>
239264
<Col className="title">{selectedStream.title}</Col>
240265
</Row>

0 commit comments

Comments
 (0)