Skip to content

Instantly share code, notes, and snippets.

@dawsbot
Created March 22, 2020 14:42
Show Gist options
  • Save dawsbot/f3244ee6dcbe549e72f03d06281752e8 to your computer and use it in GitHub Desktop.
Save dawsbot/f3244ee6dcbe549e72f03d06281752e8 to your computer and use it in GitHub Desktop.
Breaks vscode insiders syntax highlighting in relation to https://github.com/microsoft/vscode/issues/92308
import Skeleton from 'antd/lib/skeleton';
import * as React from 'react';
import InfiniteScroll from 'react-infinite-scroll-component';
import { defineMessages, FormattedMessage as FM } from 'react-intl';
import { connect } from 'react-redux';
import { bindActionCreators, Dispatch } from 'redux';
import styled from '@emotion/styled';
import { ActivityActions } from '../../actions/activities';
import { ArticleActions } from '../../actions/articles';
import { ProfilesActions } from '../../actions/profiles';
import { Tabs } from '../../components/tabs';
import {
SelectCurrentlySelectedCountryCode,
SelectCurrentlySelectedDayJSLocaleCode,
SelectLanguagePacksKeyedByCountry,
SelectMessagesForLocale,
} from '../../reducers/i18n/intl-selectors';
import {
BrowserIsMobile,
BrowserIsSmallScreen,
BrowserIsTablet,
GetBrowserInfo,
} from '../../selectors/browser.selectors';
import { ChatExpandedStatus } from '../../selectors/chat.selectors';
import { SelectedProposalProperties } from '../../selectors/proposal.selectors';
import { AllProposalsObjectState, UserProfilesRoot } from '../../selectors/root.selectors';
import { WalletAccountName } from '../../selectors/wallet.selectors';
import { fetchUserStreaks, recentProposals } from '../../utils/endpoints';
import { omit } from '../../utils/general';
import { DarkGray } from '../styledComponents/Colors';
import { FlexCustom } from '../styledComponents/Flex';
import { ActivityCard } from './ActivityCard';
import { ActivityCardsOwnProps, ActivityCardsState } from './activitycards.types';
const messages = defineMessages({
mostRecent: {
id: 'ActivityCards.MostRecent',
defaultMessage: 'MOST RECENT',
},
expiringSoon: {
id: 'ActivityCards.ExpiringSoon',
defaultMessage: 'EXPIRING SOON',
},
myActivity: {
id: 'ActivityCards.MyActivity',
defaultMessage: 'MY ACTIVITY',
},
myEdits: {
id: 'ActivityCards.MyEdits',
defaultMessage: 'MY EDITS',
},
edits: {
id: 'ActivityCards.edits',
defaultMessage: 'EDITS',
},
votes: {
id: 'ActivityCards.Votes',
defaultMessage: 'VOTES',
},
my_votes: {
id: 'ActivityCards.MyVotes',
defaultMessage: 'MY VOTES',
},
});
const ActivityCardsContainer = styled.div<{ chatExpanded?: boolean; isMobile: boolean; isTablet: boolean }>`
margin-right: ${(props) => (props.chatExpanded && !props.isMobile && !props.isTablet ? '25%' : '0px')};
max-width: 1100px;
width: 100%;
width: ${(props) => (props.chatExpanded && !props.isMobile && !props.isTablet ? '75%' : '100%')};
`;
const CardsWrapper = styled(InfiniteScroll)<{ padding: number }>`
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
padding: ${(props) => props.padding}px;
`;
const SortingContainer = styled(FlexCustom)`
min-width: 300px;
max-width: 1100px;
margin: 20px 20px 0px 20px;
`;
const BlankCard = styled(FlexCustom)<{
padding: string;
borderBottom: string;
marginBottom: number;
isMobile: boolean;
}>`
justify-content: space-between;
align-items: center;
width: 100%;
min-width: 300px;
max-width: 1100px;
padding: ${(props) => props.padding};
margin-bottom: ${(props) => props.marginBottom}px;
background-color: #ffffff;
border-radius: ${(props) => (props.isMobile ? 0 : 8)}px;
border-bottom: ${(props) => props.borderBottom};
`;
const mapStateToProps = (state: RootState) => ({
rdxIntlMsgs: SelectMessagesForLocale(state),
selectedDayJSCode: SelectCurrentlySelectedDayJSLocaleCode(state),
proposalsObject: AllProposalsObjectState(state),
accountName: WalletAccountName(state),
proposalsProperties: SelectedProposalProperties(state),
chatExpanded: ChatExpandedStatus(state),
selectedCountryCode: SelectCurrentlySelectedCountryCode(state),
countryToLocaleMap: SelectLanguagePacksKeyedByCountry(state),
isMobile: BrowserIsMobile(state),
isTablet: BrowserIsTablet(state),
isSmallScreen: BrowserIsSmallScreen(state),
browserInfo: GetBrowserInfo(state),
userProfiles: UserProfilesRoot(state),
});
const mapDispatchToProps = (dispatch: Dispatch) => ({
articleActions: bindActionCreators(omit(ArticleActions, 'Type'), dispatch),
activityActions: bindActionCreators(omit(ActivityActions, 'Type'), dispatch),
profilesActions: bindActionCreators(omit(ProfilesActions, 'Type'), dispatch),
});
export interface ActivityCardsUnconnected {
timer: any;
}
type ActivityCardsProps = ActivityCardsOwnProps &
ReturnType<typeof mapStateToProps> &
ReturnType<typeof mapDispatchToProps>;
export class ActivityCardsUnconnected extends React.PureComponent<ActivityCardsProps, ActivityCardsState> {
fetchedUserStreaks = {};
constructor(props: ActivityCardsProps) {
super(props);
this.timer;
this.state = { retrievedItems: false }; // Used on dashboard to prevent tabs from disappearing when navigate between tabs.
}
componentDidMount() {
if (this.props.dashboard) this.props.activityActions.setProposalProperty({ sortBy: 'my-activity' });
if (!this.props.dashboard) {
this.props.activityActions.setProposalProperty({ sortBy: 'latest' });
this.timer = setInterval(() => this.loadData(0), 10000); // Check for new edits every 10 seconds and add them into the activity feed.
}
setTimeout(() => this.loadData(0), 1000);
// Create object of streaks already pulled
Object.keys(this.props.userProfiles).map((profileName) => {
if (this.props.userProfiles[profileName].streaks) this.fetchedUserStreaks[profileName] = true;
});
}
componentDidUpdate(prevProps: ActivityCardsProps) {
if (
prevProps.proposalsProperties.sortBy !== this.props.proposalsProperties.sortBy ||
prevProps.selectedCountryCode !== this.props.selectedCountryCode ||
prevProps.profile !== this.props.profile // From profile page
) {
this.props.activityActions.clearProposals();
this.loadData(0);
}
}
componentWillUnmount() {
this.props.activityActions.clearProposals();
clearInterval(this.timer);
}
selectProposal = (proposal: number) => {
this.props.activityActions.selectProposal({ proposal_id: proposal });
};
articleNav = (tab) => {
this.props.articleActions.articleNav(tab);
};
private loadData = async (offset: number) => {
const { proposalsProperties, selectedCountryCode, countryToLocaleMap, profile, dashboard } = this.props;
const limit: number = 12;
const language = countryToLocaleMap[selectedCountryCode].lang_code;
const accountNameToUse =
proposalsProperties.sortBy === 'my-activity' || proposalsProperties.sortBy === 'my-votes' ? profile : null;
// console.log(proposalsProperties.sortBy);
if (dashboard && !profile) return; // Shouldn't pull activity feed if on dashboard with nonexistant profile;
const retrievedActivity = await recentProposals(
limit,
offset,
proposalsProperties.sortBy,
[language],
null,
accountNameToUse,
this.props.browserInfo.name,
proposalsProperties.sortBy === 'my-votes',
);
if (retrievedActivity && retrievedActivity.length > 0) {
this.props.activityActions.fetchProposals(retrievedActivity);
this.setState({ retrievedItems: true });
}
};
public changeRecentPage = () => {
if (this.props.proposalsObject) this.loadData(Math.floor(Object.keys(this.props.proposalsObject).length / 9));
};
public renderActivityCards = (proposalsObject) => {
const {
accountName,
proposalsProperties,
articleActions,
isMobile,
chatExpanded,
isTablet,
isSmallScreen,
dashboard,
} = this.props;
if (!proposalsObject || !Object.keys(proposalsObject).reduce || Object.keys(proposalsObject).length < 1) {
const blankCardArray = [];
const length = 10;
for (let i = 0; i < length; i++) {
blankCardArray.push('');
}
return (
blankCardArray.map &&
blankCardArray.map((nothing, index) => {
return (
<BlankCard
padding={isMobile ? '20px 5px 10px 5px' : '20px 20px 10px 20px'}
borderBottom={isMobile ? `1px solid ${DarkGray}` : 'none'}
marginBottom={isMobile ? 0 : 15}
isMobile={isMobile}
key={`blank-card-${index}`}
>
<Skeleton
active
avatar={{ size: 'large', shape: 'square' }}
paragraph={{ rows: 2, width: isMobile ? ['40%', '75%'] : ['20%', '50%'] }}
title={false}
/>
</BlankCard>
);
})
);
}
const sorted: ProposalModel[] = Object.keys(proposalsObject).reduce((result, currentValue) => {
const val: ProposalModel = proposalsObject[currentValue];
const endtime: number = val.info.endtime * 1000; // Convert from unix to milisecond time to compare to new Date().valueOf().
const now: number = new Date().valueOf();
if (!val.hide) {
if (
proposalsProperties.sortBy === 'latest' ||
proposalsProperties.sortBy === 'my-activity' ||
proposalsProperties.sortBy === 'my-votes'
)
result.unshift(val); // Sorts by most recent edit
if (proposalsProperties.sortBy === 'expiring' && endtime > now) result.push(val); // Sorts by most recent edit
}
return result;
}, []);
const streaksToFetch = [];
const toReturn =
sorted.map &&
sorted.map((item, index) => {
if (!this.fetchedUserStreaks[item.info.proposer]) streaksToFetch.push(item.info.proposer);
this.fetchedUserStreaks[item.info.proposer] = true;
const slug = item && item.info && item.info.slug;
return (
<ActivityCard
key={`activity-card-${index}-${slug}`}
activity={item}
selectProposal={this.selectProposal}
articleNav={this.articleNav}
accountName={accountName}
dayjsLang={this.props.selectedDayJSCode}
passedMessages={this.props.rdxIntlMsgs}
articleActions={articleActions}
isMobile={isMobile}
isTablet={isTablet}
isSmallScreen={isSmallScreen}
chatExpanded={chatExpanded}
browserInfo={this.props.browserInfo}
proposerProfile={this.props.userProfiles && this.props.userProfiles[item.info.proposer]}
/>
);
});
if (streaksToFetch.length > 0) this.getUserStreaks(streaksToFetch);
return toReturn;
};
getTabProperty = (index: number) => {
if (!this.props.dashboard) {
switch (index) {
case 0:
return 'latest';
case 1:
return 'expiring';
default:
return 'latest';
}
}
if (this.props.dashboard) {
switch (index) {
case 0:
return 'my-activity';
case 1:
return 'my-votes';
default:
return 'my-activity';
}
}
};
getTabIndex = (tabString: string) => {
if (!this.props.dashboard) {
switch (tabString) {
case 'latest':
return 0;
case 'expiring':
return 1;
}
}
if (this.props.dashboard) {
switch (tabString) {
case 'my-activity':
return 0;
case 'my-votes':
return 1;
}
}
};
getUserStreaks = async (accountNames: string[]) => {
const { profilesActions } = this.props;
const userStreaks = await fetchUserStreaks(accountNames);
profilesActions.getStreakInfo(userStreaks);
profilesActions.getProfilesFromStreaks(userStreaks);
profilesActions.getProfileActivity(userStreaks);
};
renderSelectedTab = () => {
const {
proposalsObject,
activityActions,
proposalsProperties,
chatExpanded,
isMobile,
isTablet,
dashboard,
accountName,
profile,
} = this.props;
const { sortBy } = proposalsProperties;
const selectedIndex = this.getTabIndex(sortBy);
// switch (selectedIndex) {
// case 0: {
// console.log(proposalsObject);
return (
<CardsWrapper
dataLength={proposalsObject ? Object.keys(proposalsObject).length : 10}
next={this.changeRecentPage}
hasMore
scrollThreshold={0.6}
padding={isMobile ? 15 : 20}
loader={
<div className="loader" key={0}>
<FM defaultMessage="Loading" id="ActivityCards.Loading" /> ...
</div>
}
>
{this.renderActivityCards(proposalsObject)}
</CardsWrapper>
);
// break;
// }
// }
};
render() {
const {
proposalsObject,
activityActions,
proposalsProperties,
chatExpanded,
isMobile,
isTablet,
dashboard,
accountName,
profile,
} = this.props;
const { retrievedItems } = this.state;
const { sortBy } = proposalsProperties;
const selectedIndex = this.getTabIndex(sortBy);
if (dashboard && !proposalsObject && !retrievedItems) return <span />; // Shouldn't have tabs or skeleton on dashboard if no items loaded
const tabOptions = !dashboard
? [this.props.rdxIntlMsgs['ActivityCards.MostRecent'], this.props.rdxIntlMsgs['ActivityCards.ExpiringSoon']]
: [
profile === accountName
? this.props.rdxIntlMsgs['ActivityCards.MyEdits']
: `${this.props.rdxIntlMsgs['ActivityCards.edits']} ${
profile ? '(' + this.props.profile.toUpperCase() + ')' : ''
}`,
profile === accountName
? this.props.rdxIntlMsgs['ActivityCards.MyVotes']
: `${this.props.rdxIntlMsgs['ActivityCards.Votes']} ${
profile ? '(' + this.props.profile.toUpperCase() + ')' : ''
}`,
];
// if (this.state.dayjsLoaded) {
return (
<ActivityCardsContainer chatExpanded={chatExpanded} isMobile={isMobile} isTablet={isTablet} role="tabpanel">
{!isMobile && (
<SortingContainer>
<Tabs
selectedIndex={selectedIndex}
tabOptions={tabOptions}
selectTab={(index) =>
activityActions.setProposalProperty({ sortBy: this.getTabProperty(index) })
}
tabMinWidth={'0px'}
borderBottom
isMobile={isMobile}
/>
</SortingContainer>
)}
{isMobile && (
<Tabs
selectedIndex={selectedIndex}
tabOptions={tabOptions}
selectTab={(index) =>
activityActions.setProposalProperty({ sortBy: this.getTabProperty(index) })
}
tabMinWidth={'50vw'}
borderBottom
marginTop={15}
isMobile={isMobile}
/>
)}
{this.renderSelectedTab()}
</ActivityCardsContainer>
);
}
}
export const ActivityCards = connect(mapStateToProps, mapDispatchToProps)(ActivityCardsUnconnected as any);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment