Pull latest changes from Beta 4

This commit is contained in:
Marquis Kurt 2019-05-08 09:02:34 -04:00
commit 9403cd14b2
13 changed files with 451 additions and 86 deletions

4
.github/CODEOWNERS vendored
View File

@ -1,3 +1 @@
* @alicerunsonfedora * @alicerunsonfedora @Nomad1556 @Bio-ico
* @Nomad1556
* @Bio-ico

10
.gitignore vendored
View File

@ -63,9 +63,9 @@ typings/
# VSCode dirs # VSCode dirs
.vscode/ .vscode/
# Signing certificates # Production dirs
desktop/*.provisionprofile build/
desktop/*.plist
# Prod dirs # Electron app files
build/ desktop/*.plist
desktop/*.provisionprofile

5
package-lock.json generated
View File

@ -11875,6 +11875,11 @@
"safe-buffer": "^5.1.2" "safe-buffer": "^5.1.2"
} }
}, },
"mdi-material-ui": {
"version": "5.11.0",
"resolved": "https://registry.npmjs.org/mdi-material-ui/-/mdi-material-ui-5.11.0.tgz",
"integrity": "sha512-9fIvdiKCKAfBoW11LqZsgaxZtu9WCQEd8FL9/8ceLHvStSf+fZM6sC7exwXaXZmzfwtJMfN1KiMGsPBPSTQFQg=="
},
"mdn-data": { "mdn-data": {
"version": "1.1.4", "version": "1.1.4",
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-1.1.4.tgz", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-1.1.4.tgz",

View File

@ -18,6 +18,7 @@
"emoji-mart": "^2.8.2", "emoji-mart": "^2.8.2",
"file-dialog": "^0.0.7", "file-dialog": "^0.0.7",
"material-ui-pickers": "^2.2.4", "material-ui-pickers": "^2.2.4",
"mdi-material-ui": "^5.11.0",
"megalodon": "^0.6.3", "megalodon": "^0.6.3",
"moment": "^2.24.0", "moment": "^2.24.0",
"notistack": "^0.5.1", "notistack": "^0.5.1",

View File

@ -1,5 +1,5 @@
{ {
"version": "1.0.0beta2u3", "version": "1.0.0beta4",
"location": "https://hyperspaceapp-next.herokuapp.com", "location": "https://hyperspaceapp-next.herokuapp.com",
"branding": { "branding": {
"name": "Hyperspace", "name": "Hyperspace",

View File

@ -19,6 +19,7 @@ import WelcomePage from './pages/Welcome';
import MessagesPage from './pages/Messages'; import MessagesPage from './pages/Messages';
import RecommendationsPage from './pages/Recommendations'; import RecommendationsPage from './pages/Recommendations';
import Missingno from './pages/Missingno'; import Missingno from './pages/Missingno';
import You from './pages/You';
import {withSnackbar} from 'notistack'; import {withSnackbar} from 'notistack';
import {PrivateRoute} from './interfaces/overrides'; import {PrivateRoute} from './interfaces/overrides';
import { userLoggedIn } from './utilities/accounts'; import { userLoggedIn } from './utilities/accounts';
@ -60,6 +61,7 @@ class App extends Component<any, any> {
<PrivateRoute path="/conversation/:conversationId" component={Conversation}/> <PrivateRoute path="/conversation/:conversationId" component={Conversation}/>
<PrivateRoute path="/search" component={SearchPage}/> <PrivateRoute path="/search" component={SearchPage}/>
<PrivateRoute path="/settings" component={Settings}/> <PrivateRoute path="/settings" component={Settings}/>
<PrivateRoute path="/you" component={You}/>
<PrivateRoute path="/about" component={AboutPage}/> <PrivateRoute path="/about" component={AboutPage}/>
<PrivateRoute path="/compose" component={Composer}/> <PrivateRoute path="/compose" component={Composer}/>
<PrivateRoute path="/recommended" component={RecommendationsPage}/> <PrivateRoute path="/recommended" component={RecommendationsPage}/>

View File

@ -22,7 +22,9 @@ export const styles = (theme: Theme) => createStyles({
backgroundColor: theme.palette.primary.dark, backgroundColor: theme.palette.primary.dark,
textAlign: 'center', textAlign: 'center',
zIndex: 1000, zIndex: 1000,
verticalAlign: 'middle' verticalAlign: 'middle',
WebkitUserSelect: 'none',
WebkitAppRegion: "drag"
}, },
titleBarText: { titleBarText: {
color: theme.palette.common.white, color: theme.palette.common.white,
@ -148,5 +150,5 @@ export const styles = (theme: Theme) => createStyles({
bottom: theme.spacing.unit * 2, bottom: theme.spacing.unit * 2,
right: theme.spacing.unit * 2, right: theme.spacing.unit * 2,
zIndex: 50 zIndex: 50
} },
}); });

View File

@ -10,7 +10,7 @@ import PublicIcon from '@material-ui/icons/Public';
import GroupIcon from '@material-ui/icons/Group'; import GroupIcon from '@material-ui/icons/Group';
import SettingsIcon from '@material-ui/icons/Settings'; import SettingsIcon from '@material-ui/icons/Settings';
import InfoIcon from '@material-ui/icons/Info'; import InfoIcon from '@material-ui/icons/Info';
import EditIcon from '@material-ui/icons/Edit'; import CreateIcon from '@material-ui/icons/Create';
//import SupervisedUserCircleIcon from '@material-ui/icons/SupervisedUserCircle'; //import SupervisedUserCircleIcon from '@material-ui/icons/SupervisedUserCircle';
import ExitToAppIcon from '@material-ui/icons/ExitToApp'; import ExitToAppIcon from '@material-ui/icons/ExitToApp';
import {styles} from './AppLayout.styles'; import {styles} from './AppLayout.styles';
@ -395,7 +395,7 @@ export class AppLayout extends Component<any, IAppLayoutState> {
</Dialog> </Dialog>
<Tooltip title="Create a new post"> <Tooltip title="Create a new post">
<LinkableFab to="/compose" className={classes.composeButton} color="secondary" aria-label="Compose"> <LinkableFab to="/compose" className={classes.composeButton} color="secondary" aria-label="Compose">
<EditIcon/> <CreateIcon/>
</LinkableFab> </LinkableFab>
</Tooltip> </Tooltip>
</div> </div>

View File

@ -12,18 +12,23 @@ import {
withStyles, withStyles,
Typography, Typography,
Link, Link,
Tooltip Tooltip,
Button
} from '@material-ui/core'; } from '@material-ui/core';
import OpenInNewIcon from '@material-ui/icons/OpenInNew'; import OpenInNewIcon from '@material-ui/icons/OpenInNew';
import DomainIcon from '@material-ui/icons/Domain';
import ChatIcon from '@material-ui/icons/Chat'; import ChatIcon from '@material-ui/icons/Chat';
import PersonIcon from '@material-ui/icons/Person'; import PersonIcon from '@material-ui/icons/Person';
import AssignmentIcon from '@material-ui/icons/Assignment';
import AssignmentIndIcon from '@material-ui/icons/AssignmentInd'; import AssignmentIndIcon from '@material-ui/icons/AssignmentInd';
import NetworkCheckIcon from '@material-ui/icons/NetworkCheck'; import NetworkCheckIcon from '@material-ui/icons/NetworkCheck';
import UpdateIcon from '@material-ui/icons/Update'; import UpdateIcon from '@material-ui/icons/Update';
import InfoIcon from '@material-ui/icons/Info'; import InfoIcon from '@material-ui/icons/Info';
import NotesIcon from '@material-ui/icons/Notes'; import NotesIcon from '@material-ui/icons/Notes';
import CodeIcon from '@material-ui/icons/Code'; import CodeIcon from '@material-ui/icons/Code';
import TicketAccountIcon from 'mdi-material-ui/TicketAccount';
import MastodonIcon from 'mdi-material-ui/Mastodon';
import {styles} from './PageLayout.styles'; import {styles} from './PageLayout.styles';
import {Instance} from '../types/Instance'; import {Instance} from '../types/Instance';
import {LinkableIconButton, LinkableAvatar} from '../interfaces/overrides'; import {LinkableIconButton, LinkableAvatar} from '../interfaces/overrides';
@ -33,13 +38,14 @@ import { getConfig } from '../utilities/settings';
import { License } from '../types/Config'; import { License } from '../types/Config';
interface IAboutPageState { interface IAboutPageState {
instance?: Instance | any; instance?: Instance;
federated?: boolean; federated?: boolean;
developer?: boolean; developer?: boolean;
hyperspaceAdmin?: UAccount; hyperspaceAdmin?: UAccount;
hyperspaceAdminName?: string; hyperspaceAdminName?: string;
versionNumber?: string; versionNumber?: string;
brandName?: string; brandName?: string;
brandBg?: string;
license: License; license: License;
repository?: string; repository?: string;
} }
@ -64,14 +70,13 @@ class AboutPage extends Component<any, IAboutPageState> {
componentWillMount() { componentWillMount() {
this.client.get('/instance').then((resp: any) => { this.client.get('/instance').then((resp: any) => {
this.setState({ this.setState({
instance: resp.data instance: resp.data as Instance
}) })
}) })
getConfig().then((config: any) => { getConfig().then((config: any) => {
this.client.get('/accounts/' + (config.admin? config.admin.account: "0")).then((resp: any) => { this.client.get('/accounts/' + (config.admin? config.admin.account: "0")).then((resp: any) => {
let account = resp.data; let account = resp.data;
console.log(config);
this.setState({ this.setState({
hyperspaceAdmin: account, hyperspaceAdmin: account,
hyperspaceAdminName: config.admin.name, hyperspaceAdminName: config.admin.name,
@ -79,6 +84,7 @@ class AboutPage extends Component<any, IAboutPageState> {
developer: config.developer? config.developer === "true": false, developer: config.developer? config.developer === "true": false,
versionNumber: config.version, versionNumber: config.version,
brandName: config.branding? config.branding.name: "Hyperspace", brandName: config.branding? config.branding.name: "Hyperspace",
brandBg: config.branding.background,
license: { license: {
name: config.license.name, name: config.license.name,
url: config.license.url url: config.license.url
@ -95,24 +101,19 @@ class AboutPage extends Component<any, IAboutPageState> {
const { classes } = this.props; const { classes } = this.props;
return ( return (
<div className={classes.pageLayoutConstraints}> <div className={classes.pageLayoutConstraints}>
<ListSubheader>About your instance</ListSubheader> <Paper>
<Paper className={classes.pageListConstraints}> <div
<List> className={classes.instanceHeaderPaper}
<ListItem> style={{
<ListItemAvatar> backgroundImage: `url("${this.state.instance && this.state.instance.thumbnail? this.state.instance.thumbnail: ""}")`
<Avatar> }}
<DomainIcon/> >
</Avatar> <IconButton className={classes.instanceToolbar} href={localStorage.getItem("baseurl") as string} target="_blank" rel="noreferrer" color="inherit">
</ListItemAvatar> <OpenInNewIcon/>
<ListItemText primary="Instance location (URL)" secondary={this.state.instance ? this.state.instance.uri: "Loading..."}/> </IconButton>
<ListItemSecondaryAction> <Typography className={classes.instanceHeaderText} variant="h4" component="p">{this.state.instance ? this.state.instance.uri: "Loading..."}</Typography>
<Tooltip title="Open in browser"> </div>
<IconButton href={localStorage.getItem("baseurl") as string} target="_blank" rel="noreferrer"> <List className={classes.pageListConstraints}>
<OpenInNewIcon/>
</IconButton>
</Tooltip>
</ListItemSecondaryAction>
</ListItem>
<ListItem> <ListItem>
<ListItemAvatar> <ListItemAvatar>
<LinkableAvatar to={`/profile/${this.state.instance? this.state.instance.contact_account.id: 0}`} alt="Instance admin" src={this.state.instance? this.state.instance.contact_account.avatar_static: ""}/> <LinkableAvatar to={`/profile/${this.state.instance? this.state.instance.contact_account.id: 0}`} alt="Instance admin" src={this.state.instance? this.state.instance.contact_account.avatar_static: ""}/>
@ -134,64 +135,80 @@ class AboutPage extends Component<any, IAboutPageState> {
</Tooltip> </Tooltip>
</ListItemSecondaryAction> </ListItemSecondaryAction>
</ListItem> </ListItem>
</List>
</Paper>
<br/>
<ListSubheader>About this app</ListSubheader>
<Paper className={classes.pageListConstraints}>
<List>
<ListItem> <ListItem>
<ListItemAvatar> <ListItemAvatar>
<Avatar> <Avatar>
<InfoIcon/> <AssignmentIcon/>
</Avatar> </Avatar>
</ListItemAvatar> </ListItemAvatar>
<ListItemText primary="App version" secondary={`${this.state? this.state.brandName: "Hyperspace"} v${this.state? this.state.versionNumber: "1.0.x"} ${this.state && this.state.brandName !== "Hyperspace"? "(Hyperspace-like)": ""}`}/> <ListItemText
</ListItem> primary="Terms of service"
<ListItem> secondary="View the rules and privacy policies"
<ListItemAvatar> />
<Avatar>
<NotesIcon/>
</Avatar>
</ListItemAvatar>
<ListItemText primary="License" secondary={this.state.license.name}/>
<ListItemSecondaryAction> <ListItemSecondaryAction>
<Tooltip title = "View license"> <Tooltip title="Open in browser">
<IconButton href={this.state.license.url} target="_blank" rel="noreferrer"> <IconButton href={localStorage.getItem("baseurl") as string + "/terms"} target="_blank" rel="noreferrer">
<OpenInNewIcon/> <OpenInNewIcon/>
</IconButton> </IconButton>
</Tooltip> </Tooltip>
</ListItemSecondaryAction> </ListItemSecondaryAction>
</ListItem> </ListItem>
{ <ListItem>
this.state.repository? <ListItemAvatar>
<ListItem> <Avatar>
<ListItemAvatar> <TicketAccountIcon/>
<Avatar> </Avatar>
<CodeIcon/> </ListItemAvatar>
</Avatar> <ListItemText
</ListItemAvatar> primary="Invite a friend"
<ListItemText primary="Source code repository" secondary={this.state.repository? secondary="Invite a friend to this instance"
<span> />
<Typography className={classes.mobileOnly} color='textSecondary'>{this.state.repository.slice(0, 25) + "..."}</Typography> <ListItemSecondaryAction>
<Typography className={classes.desktopOnly} color='textSecondary'>{this.state.repository}</Typography> <Tooltip title="Go to invite settings">
</span>: <Button href={localStorage.getItem("baseurl") as string + "/invites"} target="_blank" rel="noreferrer">
"No repository in config"}/> Invite
<ListItemSecondaryAction> </Button>
<Tooltip title="View source code"> </Tooltip>
<IconButton href={this.state.repository? this.state.repository: ""} target="_blank" rel="noreferrer"> </ListItemSecondaryAction>
<OpenInNewIcon/> </ListItem>
</IconButton> <ListItem>
</Tooltip> <ListItemAvatar>
</ListItemSecondaryAction> <Avatar>
</ListItem>: null <MastodonIcon/>
} </Avatar>
</ListItemAvatar>
<ListItemText
primary="Mastodon version"
secondary={this.state.instance? this.state.instance.version: "x.x.x"}
/>
</ListItem>
</List> </List>
</Paper> </Paper>
<br/> <br/>
<ListSubheader>Advanced app info</ListSubheader>
<Paper className={classes.pageListConstraints}> <Paper>
<List> <div
className={classes.instanceHeaderPaper}
style={{
backgroundImage: `url("${this.state.brandBg? this.state.brandBg: ""}")`
}}
>
<div className={classes.instanceToolbar}>
{
this.state.repository?
<Tooltip title="View source code">
<IconButton href={this.state.repository} target="_blank" rel="noreferrer" color="inherit">
<CodeIcon/>
</IconButton>
</Tooltip>: null
}
</div>
<Typography className={classes.instanceHeaderText} variant="h4" component="p">
{this.state.brandName? this.state.brandName: "Hyperspace"}
</Typography>
</div>
<List className={classes.pageListConstraints}>
<ListItem> <ListItem>
<ListItemAvatar> <ListItemAvatar>
<LinkableAvatar to={`/profile/${this.state.hyperspaceAdmin? this.state.hyperspaceAdmin.id: 0}`} src={this.state.hyperspaceAdmin? this.state.hyperspaceAdmin.avatar_static: ""}> <LinkableAvatar to={`/profile/${this.state.hyperspaceAdmin? this.state.hyperspaceAdmin.id: 0}`} src={this.state.hyperspaceAdmin? this.state.hyperspaceAdmin.avatar_static: ""}>
@ -215,10 +232,17 @@ class AboutPage extends Component<any, IAboutPageState> {
<ListItem> <ListItem>
<ListItemAvatar> <ListItemAvatar>
<Avatar> <Avatar>
<NetworkCheckIcon/> <NotesIcon/>
</Avatar> </Avatar>
</ListItemAvatar> </ListItemAvatar>
<ListItemText primary="Federation status" secondary={`This instance of ${this.state? this.state.brandName: "Hyperspace"} ${this.state? this.state.federated? "supports": "doesn't support": "might support"} federation.`}/> <ListItemText primary="License" secondary={this.state.license.name}/>
<ListItemSecondaryAction>
<Tooltip title = "View license">
<IconButton href={this.state.license.url} target="_blank" rel="noreferrer">
<OpenInNewIcon/>
</IconButton>
</Tooltip>
</ListItemSecondaryAction>
</ListItem> </ListItem>
<ListItem> <ListItem>
<ListItemAvatar> <ListItemAvatar>
@ -234,6 +258,28 @@ class AboutPage extends Component<any, IAboutPageState> {
"Loading..." "Loading..."
}/> }/>
</ListItem> </ListItem>
<ListItem>
<ListItemAvatar>
<Avatar>
<InfoIcon/>
</Avatar>
</ListItemAvatar>
<ListItemText primary="App version" secondary={`${this.state? this.state.brandName: "Hyperspace"} v${this.state? this.state.versionNumber: "1.0.x"} ${this.state && this.state.brandName !== "Hyperspace"? "(Hyperspace-like)": ""}`}/>
</ListItem>
</List>
</Paper>
<br/>
<ListSubheader>Federation status</ListSubheader>
<Paper>
<List className={classes.pageListConstraints}>
<ListItem>
<ListItemAvatar>
<Avatar>
<NetworkCheckIcon/>
</Avatar>
</ListItemAvatar>
<ListItemText primary="Federation status" secondary={`This instance of ${this.state? this.state.brandName: "Hyperspace"} ${this.state? this.state.federated? "supports": "doesn't support": "might support"} federation.`}/>
</ListItem>
</List> </List>
</Paper> </Paper>
<br/> <br/>

View File

@ -86,8 +86,14 @@ export const styles = (theme: Theme) => createStyles({
paddingLeft: '25%', paddingLeft: '25%',
paddingRight: '25%', paddingRight: '25%',
}, },
position: "relative",
zIndex: 1 zIndex: 1
}, },
pageHeroToolbar: {
position: "absolute",
right: theme.spacing.unit * 2,
marginTop: -16,
},
pageListConstraints: { pageListConstraints: {
paddingLeft: theme.spacing.unit, paddingLeft: theme.spacing.unit,
paddingRight: theme.spacing.unit, paddingRight: theme.spacing.unit,
@ -179,4 +185,50 @@ export const styles = (theme: Theme) => createStyles({
color: theme.palette.primary.light color: theme.palette.primary.light
} }
}, },
youHeadingAvatar: {
height: 88,
width: 88
},
youPaper: {
padding: theme.spacing.unit * 2,
},
youGrid: {
textAlign: "center",
'& *': {
marginLeft: "auto",
marginRight: "auto",
}
},
youGridAvatar: {
height: 128,
width: 128
},
youGridImage: {
width: 'auto',
height: 128
},
instanceHeaderPaper: {
height: 200,
backgroundPosition: "center",
backgroundRepeat: "no-repeat",
backgroundSize: "cover",
position: "relative",
backgroundColor: theme.palette.primary.dark,
borderTopLeftRadius: theme.shape.borderRadius,
borderTopRightRadius: theme.shape.borderRadius
},
instanceHeaderText: {
position: "absolute",
bottom: theme.spacing.unit,
left: theme.spacing.unit * 2,
color: theme.palette.common.white,
textShadow: `0 0 4px ${theme.palette.grey[700]}`,
fontWeight: 600
},
instanceToolbar: {
position: "absolute",
top: theme.spacing.unit,
right: theme.spacing.unit,
color: theme.palette.common.white
}
}); });

View File

@ -7,9 +7,11 @@ import { Status } from '../types/Status';
import { Relationship } from '../types/Relationship'; import { Relationship } from '../types/Relationship';
import Post from '../components/Post'; import Post from '../components/Post';
import {withSnackbar} from 'notistack'; import {withSnackbar} from 'notistack';
import { LinkableButton } from '../interfaces/overrides'; import { LinkableButton, LinkableIconButton } from '../interfaces/overrides';
import { emojifyString } from '../utilities/emojis'; import { emojifyString } from '../utilities/emojis';
import AccountEditIcon from 'mdi-material-ui/AccountEdit';
interface IProfilePageState { interface IProfilePageState {
account?: Account; account?: Account;
relationship?: Relationship; relationship?: Relationship;
@ -89,6 +91,14 @@ class ProfilePage extends Component<any, IProfilePageState> {
this.getAccountData(params.profileId); this.getAccountData(params.profileId);
} }
isItMe(): boolean {
if (this.state.account) {
return this.state.account.id === JSON.parse(localStorage.getItem('account') as string).id;
} else {
return false;
}
}
getRelationships() { getRelationships() {
this.client.get("/accounts/relationships", {id: this.props.match.params.profileId }).then((resp: any) => { this.client.get("/accounts/relationships", {id: this.props.match.params.profileId }).then((resp: any) => {
let relationship: Relationship = resp.data[0]; let relationship: Relationship = resp.data[0];
@ -216,6 +226,14 @@ class ProfilePage extends Component<any, IProfilePageState> {
<div className={classes.pageHeroBackground}> <div className={classes.pageHeroBackground}>
<div className={classes.pageHeroBackgroundImage} style={{ backgroundImage: this.state.account? `url("${this.state.account.header}")`: `url("")`}}/> <div className={classes.pageHeroBackgroundImage} style={{ backgroundImage: this.state.account? `url("${this.state.account.header}")`: `url("")`}}/>
<div className={classes.pageHeroContent}> <div className={classes.pageHeroContent}>
{
this.isItMe()?
<Tooltip title="Edit profile">
<LinkableIconButton to="/you" color="inherit" className={classes.pageHeroToolbar}>
<AccountEditIcon/>
</LinkableIconButton>
</Tooltip>: null
}
<Avatar className={classes.pageProfileAvatar} src={this.state.account ? this.state.account.avatar: ""}/> <Avatar className={classes.pageProfileAvatar} src={this.state.account ? this.state.account.avatar: ""}/>
<Typography variant="h4" color="inherit" dangerouslySetInnerHTML={{__html: this.state.account? emojifyString(this.state.account.display_name, this.state.account.emojis, classes.pageProfileNameEmoji): ""}}></Typography> <Typography variant="h4" color="inherit" dangerouslySetInnerHTML={{__html: this.state.account? emojifyString(this.state.account.display_name, this.state.account.emojis, classes.pageProfileNameEmoji): ""}}></Typography>
<Typography variant="caption" color="inherit">{this.state.account ? '@' + this.state.account.acct: ""}</Typography> <Typography variant="caption" color="inherit">{this.state.account ? '@' + this.state.account.acct: ""}</Typography>

View File

@ -1,7 +1,8 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { import {
List, List,
ListItem, ListItem,
ListItemAvatar,
ListItemText, ListItemText,
ListSubheader, ListSubheader,
ListItemSecondaryAction, ListItemSecondaryAction,
@ -22,7 +23,6 @@ import {
Theme, Theme,
Typography Typography
} from '@material-ui/core'; } from '@material-ui/core';
import OpenInNewIcon from '@material-ui/icons/OpenInNew';
import {styles} from './PageLayout.styles'; import {styles} from './PageLayout.styles';
import {setUserDefaultBool, getUserDefaultBool, getUserDefaultTheme, setUserDefaultTheme, getUserDefaultVisibility, setUserDefaultVisibility, getConfig} from '../utilities/settings'; import {setUserDefaultBool, getUserDefaultBool, getUserDefaultTheme, setUserDefaultTheme, getUserDefaultVisibility, setUserDefaultVisibility, getConfig} from '../utilities/settings';
import {canSendNotifications, browserSupportsNotificationRequests} from '../utilities/notifications'; import {canSendNotifications, browserSupportsNotificationRequests} from '../utilities/notifications';
@ -30,6 +30,19 @@ import {themes, defaultTheme} from '../types/HyperspaceTheme';
import ThemePreview from '../components/ThemePreview'; import ThemePreview from '../components/ThemePreview';
import {setHyperspaceTheme, getHyperspaceTheme} from '../utilities/themes'; import {setHyperspaceTheme, getHyperspaceTheme} from '../utilities/themes';
import { Visibility } from '../types/Visibility'; import { Visibility } from '../types/Visibility';
import {LinkableButton} from '../interfaces/overrides';
import OpenInNewIcon from '@material-ui/icons/OpenInNew';
import DevicesIcon from '@material-ui/icons/Devices';
import Brightness3Icon from '@material-ui/icons/Brightness3';
import PaletteIcon from '@material-ui/icons/Palette';
import AccountEditIcon from 'mdi-material-ui/AccountEdit';
import MastodonIcon from 'mdi-material-ui/Mastodon';
import VisibilityIcon from '@material-ui/icons/Visibility';
import NotificationsIcon from '@material-ui/icons/Notifications';
import BellAlertIcon from 'mdi-material-ui/BellAlert';
import RefreshIcon from '@material-ui/icons/Refresh';
import UndoIcon from '@material-ui/icons/Undo';
interface ISettingsState { interface ISettingsState {
darkModeEnabled: boolean; darkModeEnabled: boolean;
@ -302,6 +315,9 @@ class SettingsPage extends Component<any, ISettingsState> {
<Paper className={classes.pageListConstraints}> <Paper className={classes.pageListConstraints}>
<List> <List>
<ListItem> <ListItem>
<ListItemAvatar>
<DevicesIcon color="action"/>
</ListItemAvatar>
<ListItemText primary="Match system appearance" secondary="Obey light/dark theme from your system"/> <ListItemText primary="Match system appearance" secondary="Obey light/dark theme from your system"/>
<ListItemSecondaryAction> <ListItemSecondaryAction>
<Switch <Switch
@ -311,6 +327,9 @@ class SettingsPage extends Component<any, ISettingsState> {
</ListItemSecondaryAction> </ListItemSecondaryAction>
</ListItem> </ListItem>
<ListItem> <ListItem>
<ListItemAvatar>
<Brightness3Icon color="action"/>
</ListItemAvatar>
<ListItemText primary="Dark mode" secondary="Toggles light or dark theme"/> <ListItemText primary="Dark mode" secondary="Toggles light or dark theme"/>
<ListItemSecondaryAction> <ListItemSecondaryAction>
<Switch <Switch
@ -321,6 +340,9 @@ class SettingsPage extends Component<any, ISettingsState> {
</ListItemSecondaryAction> </ListItemSecondaryAction>
</ListItem> </ListItem>
<ListItem> <ListItem>
<ListItemAvatar>
<PaletteIcon color="action"/>
</ListItemAvatar>
<ListItemText primary="Interface theme" secondary="The color palette used for the interface"/> <ListItemText primary="Interface theme" secondary="The color palette used for the interface"/>
<ListItemSecondaryAction> <ListItemSecondaryAction>
<Button onClick={this.toggleThemeDialog}> <Button onClick={this.toggleThemeDialog}>
@ -331,10 +353,22 @@ class SettingsPage extends Component<any, ISettingsState> {
</List> </List>
</Paper> </Paper>
<br/> <br/>
<ListSubheader>Accounts</ListSubheader> <ListSubheader>Your Account</ListSubheader>
<Paper className={classes.pageListConstraints}> <Paper className={classes.pageListConstraints}>
<List> <List>
<ListItem> <ListItem>
<ListItemAvatar>
<AccountEditIcon color="action"/>
</ListItemAvatar>
<ListItemText primary="Edit your profile" secondary="Change your bio, display name, and images"/>
<ListItemSecondaryAction>
<LinkableButton to="/you">Edit</LinkableButton>
</ListItemSecondaryAction>
</ListItem>
<ListItem>
<ListItemAvatar>
<MastodonIcon color="action"/>
</ListItemAvatar>
<ListItemText primary="Configure on Mastodon"/> <ListItemText primary="Configure on Mastodon"/>
<ListItemSecondaryAction> <ListItemSecondaryAction>
<IconButton href={(localStorage.getItem("baseurl") as string) + "/settings/preferences"} target="_blank" rel="noreferrer"> <IconButton href={(localStorage.getItem("baseurl") as string) + "/settings/preferences"} target="_blank" rel="noreferrer">
@ -349,6 +383,9 @@ class SettingsPage extends Component<any, ISettingsState> {
<Paper className={classes.pageListConstraints}> <Paper className={classes.pageListConstraints}>
<List> <List>
<ListItem> <ListItem>
<ListItemAvatar>
<VisibilityIcon color="action"/>
</ListItemAvatar>
<ListItemText primary="Default visibility" secondary="New posts in composer will use this visiblity"/> <ListItemText primary="Default visibility" secondary="New posts in composer will use this visiblity"/>
<ListItemSecondaryAction> <ListItemSecondaryAction>
<Button onClick={this.toggleVisibilityDialog}> <Button onClick={this.toggleVisibilityDialog}>
@ -363,6 +400,9 @@ class SettingsPage extends Component<any, ISettingsState> {
<Paper className={classes.pageListConstraints}> <Paper className={classes.pageListConstraints}>
<List> <List>
<ListItem> <ListItem>
<ListItemAvatar>
<NotificationsIcon color="action"/>
</ListItemAvatar>
<ListItemText <ListItemText
primary="Enable push notifications" primary="Enable push notifications"
secondary={ secondary={
@ -382,6 +422,9 @@ class SettingsPage extends Component<any, ISettingsState> {
</ListItemSecondaryAction> </ListItemSecondaryAction>
</ListItem> </ListItem>
<ListItem> <ListItem>
<ListItemAvatar>
<BellAlertIcon color="action"/>
</ListItemAvatar>
<ListItemText <ListItemText
primary="Notification badge counts all notifications" primary="Notification badge counts all notifications"
secondary={ secondary={
@ -402,6 +445,9 @@ class SettingsPage extends Component<any, ISettingsState> {
<Paper className={classes.pageListConstraints}> <Paper className={classes.pageListConstraints}>
<List> <List>
<ListItem> <ListItem>
<ListItemAvatar>
<RefreshIcon color="action"/>
</ListItemAvatar>
<ListItemText primary="Refresh settings" secondary="Reset the settings to defaults."/> <ListItemText primary="Refresh settings" secondary="Reset the settings to defaults."/>
<ListItemSecondaryAction> <ListItemSecondaryAction>
<Button onClick={() => this.toggleResetSettingsDialog()}> <Button onClick={() => this.toggleResetSettingsDialog()}>
@ -410,6 +456,9 @@ class SettingsPage extends Component<any, ISettingsState> {
</ListItemSecondaryAction> </ListItemSecondaryAction>
</ListItem> </ListItem>
<ListItem> <ListItem>
<ListItemAvatar>
<UndoIcon color="action"/>
</ListItemAvatar>
<ListItemText primary={`Reset ${this.state.brandName}`} secondary="Deletes all data and resets the app"/> <ListItemText primary={`Reset ${this.state.brandName}`} secondary="Deletes all data and resets the app"/>
<ListItemSecondaryAction> <ListItemSecondaryAction>
<Button onClick={() => this.toggleResetDialog()}> <Button onClick={() => this.toggleResetDialog()}>

192
src/pages/You.tsx Normal file
View File

@ -0,0 +1,192 @@
import React, {Component} from 'react';
import {withStyles, Typography, Paper, Avatar, Button, TextField, ListItem, ListItemText, ListItemAvatar, List, Grid} from '@material-ui/core';
import {withSnackbar, withSnackbarProps} from 'notistack';
import {styles} from './PageLayout.styles';
import { Account } from '../types/Account';
import Mastodon from 'megalodon';
import filedialog from 'file-dialog';
import PersonIcon from '@material-ui/icons/Person';
interface IYouProps extends withSnackbarProps {
classes: any;
}
interface IYouState {
currentAccount: Account;
newDisplayName?: string;
newBio?: string;
}
class You extends Component<IYouProps, IYouState> {
client: Mastodon;
constructor(props: any) {
super(props);
this.client = new Mastodon(localStorage.getItem('access_token') as string, localStorage.getItem('baseurl') as string + "/api/v1");
this.state = {
currentAccount: this.getAccount()
}
}
getAccount() {
let acct = localStorage.getItem('account');
if (acct) {
return JSON.parse(acct);
}
}
updateAvatar() {
filedialog({
multiple: false,
accept: "image/*"
}).then((images: FileList) => {
if (images.length > 0) {
this.props.enqueueSnackbar("Updating avatar...", { persist: true, key: "persistAvatar" });
let upload = new FormData();
upload.append("avatar", images[0]);
this.client.patch("/accounts/update_credentials", upload).then((acct: any) => {
let currentAccount: Account = acct.data;
this.setState({ currentAccount });
localStorage.setItem("account", JSON.stringify(currentAccount));
this.props.closeSnackbar("persistAvatar");
this.props.enqueueSnackbar("Avatar updated successfully.");
}).catch((err: Error) => {
this.props.closeSnackbar("persistAvatar");
this.props.enqueueSnackbar("Couldn't update avatar: " + err.name, { variant: "error" });
})
}
}).catch((err: Error) => {
this.props.enqueueSnackbar("Couldn't update avatar: " + err.name);
})
}
updateHeader() {
filedialog({
multiple: false,
accept: "image/*"
}).then((images: FileList) => {
if (images.length > 0) {
this.props.enqueueSnackbar("Updating header...", { persist: true, key: "persistHeader" });
let upload = new FormData();
upload.append("header", images[0]);
this.client.patch("/accounts/update_credentials", upload).then((acct: any) => {
let currentAccount: Account = acct.data;
this.setState({ currentAccount });
localStorage.setItem("account", JSON.stringify(currentAccount));
this.props.closeSnackbar("persistHeader");
this.props.enqueueSnackbar("Header updated successfully.");
}).catch((err: Error) => {
this.props.closeSnackbar("persistHeader");
this.props.enqueueSnackbar("Couldn't update header: " + err.name, { variant: "error" });
})
}
}).catch((err: Error) => {
this.props.enqueueSnackbar("Couldn't update header: " + err.name);
})
}
removeHTMLContent(text: string) {
const div = document.createElement('div');
div.innerHTML = text;
let innerContent = div.textContent || div.innerText || "";
return innerContent;
}
changeDisplayName() {
this.client.patch('/accounts/update_credentials', {
display_name: this.state.newDisplayName? this.state.newDisplayName: this.state.currentAccount.display_name
})
.then((acct: any) =>{
let currentAccount: Account = acct.data
this.setState({currentAccount});
localStorage.setItem('account', JSON.stringify(currentAccount));
this.props.closeSnackbar("persistHeader");
this.props.enqueueSnackbar("Display name updated to " + this.state.newDisplayName);
} ).catch((err:Error) => {
console.error(err.name)
this.props.closeSnackbar("persistHeader");
this.props.enqueueSnackbar("Couldn't update display name: " + err.name, { variant: "error" })
})
}
updateDisplayname(name: string) {
this.setState({ newDisplayName: name });
};
changeBio() {
this.client.patch('/accounts/update_credentials', {note: this.state.newBio? this.state.newBio: this.state.currentAccount.note})
.then((acct:any) => {
let currentAccount: Account = acct.data
this.setState({currentAccount});
localStorage.setItem('account', JSON.stringify(currentAccount));
this.props.closeSnackbar("persistHeader");
this.props.enqueueSnackbar("Bio updated successfully.");
}).catch((err: Error) => {
console.error(err.name)
this.props.closeSnackbar("persistHeader");
this.props.enqueueSnackbar("Couldn't update bio: " + err.name, { variant: "error"});
})
}
updateBio(bio:string){
this.setState({newBio:bio})
}
render() {
const {classes} = this.props;
return (
<div className={classes.pageLayoutMinimalConstraints}>
<div className={classes.pageHeroBackground}>
<div className={classes.pageHeroBackgroundImage} style={{ backgroundImage: `url("${this.state.currentAccount.header_static}")`}}/>
<div className={classes.pageHeroContent}>
<Avatar className={classes.pageProfileAvatar} src={this.state.currentAccount.avatar_static}/>
<Typography variant="h4" color="inherit" component="h1">Edit your profile</Typography>
<br/>
<div>
<Button className={classes.pageProfileFollowButton} variant="contained" onClick={() => this.updateAvatar()}>Change Avatar</Button>
<Button className={classes.pageProfileFollowButton} variant="contained" onClick={() => this.updateHeader()}>Change Header</Button>
</div>
<br/>
</div>
</div>
<div className={classes.pageContentLayoutConstraints}>
<Paper className={classes.youPaper}>
<Typography variant="h5" component="h2">Display Name</Typography>
<br/>
<TextField className = {classes.TextField}
defaultValue = {this.state.currentAccount.display_name}
rowsMax = "1"
variant = "outlined"
fullWidth
onChange = {(event: any) => this.updateDisplayname(event.target.value)}>
</TextField>
<div style = {{textAlign: "right"}}>
<Button className={classes.pageProfileFollowButton} color = "primary" onClick = {() => this.changeDisplayName()}>Update display Name</Button>
</div>
</Paper>
<br/>
<Paper className={classes.youPaper}>
<Typography variant="h5" component="h2">About you</Typography>
<br/>
<TextField className = {classes.TextField}
defaultValue = {this.state.currentAccount.note? this.removeHTMLContent(this.state.currentAccount.note): "Tell a little bit about yourself"}
multiline
variant = "outlined"
rows = "2"
rowsMax = "5"
fullWidth
onChange = {(event:any) =>this.updateBio(event.target.value)}>
</TextField>
<div style={{textAlign: "right"}}>
<Button className={classes.pageProfileFollowButton} color = "primary" onClick = {() => this.changeBio()}>Update biography</Button>
</div>
</Paper>
</div>
</div>
);
}
}
export default withStyles(styles)(withSnackbar(You));