Configuration settings

This commit is contained in:
Marquis Kurt 2019-04-08 14:31:20 -04:00
parent 2397dd6b56
commit e04fd587c8
7 changed files with 242 additions and 29 deletions

45
CONFIG.md Normal file
View File

@ -0,0 +1,45 @@
# Hyperspace 1.0 Configuration File
Hyperspace 1.0 comes with a new configuration file app providers can use to create a custom experience relatively easy. This is inspired from the way the [Riot](https://github.com/vector-im/riot-web) project handles configurations for their app. The following fields should be in the `config.json` folder at the root of the Hyperspace installation.
- `version`: The app's version using semantic versioning. This can be used to differentiate between versions of the main Hyperspace app or the custom deployment.
- `branding`: The custom branding for Hyperspace.
- `name`: The name for the brand/app. Affects title bar, about screens, and main interface by replacing the "Hyperspace" text.
- `logo`: The filepath of the brand's logo, relative to the deployment folder. Can be a relative path (`brand/logo.png`) or a URL (`https://www.test.com/brands/logo-hs.png`).
- `background`: The background used on the login page. Can be a relative path (`brand/bg.png`) or a URL (`https://www.test.com/brands/bg-hs.png`)
- `developer`: Whether the version is a developer version or should be put in developer mode. Used to signify unstable releases with new features to play around with.
- `federated`: Whether Hyperspace should enable federating features in its interface for Mastodon. Disabling federation disables the public timeline.
- `registration`: Information regarding registration of accounts.
- `defaultInstance`: The host name of the instance to default to when making accounts. Affects "well-known" sign-in and 'Create account' buttons
- `admin`: Information about the app provider/administrator:
- `name`: The name of the app provider
- `account`: The Account ID of the app provider on Mastodon, in-instance or not
- `license`: Licensing information about the app. Will default to Apache 2.0 if not listed (the standard license for Hyperspace source code).
- `name`: The name of the license.
- `url`: The link to the license for reviewing.
## Example Config File
```json
{
"version": "1.0.0beta1",
"branding": {
"name": "Hyperspace",
"logo": "logo.svg",
"background": "background.png"
},
"developer": "true",
"federated": "true",
"registration": {
"defaultInstance": "mastodon.social"
},
"admin": {
"name": "Eugen",
"account": "1"
},
"license": {
"name": "Apache 2.0 License",
"url": "https://www.apache.org/licenses/LICENSE-2.0"
}
}
```

View File

@ -1,4 +1,5 @@
{
"version": "1.0.0beta1",
"branding": {
"name": "Hyperspace",
"logo": "logo.svg",
@ -11,6 +12,10 @@
},
"admin": {
"name": "Marquis Kurt",
"account": "alicerunsonfedora@mastodon.social"
"account": "367895"
},
"license": {
"name": "Apache 2.0 License",
"url": "https://www.apache.org/licenses/LICENSE-2.0"
}
}

View File

@ -20,6 +20,7 @@ import Mastodon from 'megalodon';
import { Notification } from '../../types/Notification';
import {sendNotificationRequest} from '../../utilities/notifications';
import {withSnackbar} from 'notistack';
import { getConfig } from '../../utilities/settings';
interface IAppLayoutState {
acctMenuOpen: boolean;
@ -27,6 +28,9 @@ interface IAppLayoutState {
currentUser?: UAccount;
notificationCount: number;
logOutOpen: boolean;
enableFederation?: boolean;
brandName?: string;
developerMode?: boolean;
}
export class AppLayout extends Component<any, IAppLayoutState> {
@ -65,6 +69,18 @@ export class AppLayout extends Component<any, IAppLayoutState> {
})
}
getConfig().then((config: any) => {
this.setState({
enableFederation: config.federated === "true",
brandName: config.branding? config.branding.name: "Hyperspace",
developerMode: config.developer === "true"
});
})
this.streamNotifications()
}
streamNotifications() {
this.streamListener = this.client.stream('/streaming/user');
this.streamListener.on('notification', (notif: Notification) => {
@ -131,14 +147,17 @@ export class AppLayout extends Component<any, IAppLayoutState> {
logOutAndRestart() {
let loginData = localStorage.getItem("login");
if (loginData) {
localStorage.clear();
let items = ["login", "account", "baseurl", "access_token"];
items.forEach((entry) => {
localStorage.removeItem(entry);
})
window.location.reload();
}
}
titlebar() {
const { classes } = this.props;
if (process.env.NODE_ENV === "development") {
if (this.state.developerMode || process.env.NODE_ENV === "development") {
return (
<div className={classes.titleBarRoot}>
<Typography className={classes.titleBarText}>Careful: you're running in developer mode.</Typography>
@ -192,15 +211,22 @@ export class AppLayout extends Component<any, IAppLayoutState> {
<ListItemIcon><DomainIcon/></ListItemIcon>
<ListItemText primary="Local"/>
</LinkableListItem>
<LinkableListItem button key="public" to="/public">
<ListItemIcon><PublicIcon/></ListItemIcon>
<ListItemText primary="Public"/>
</LinkableListItem>
{
this.state.enableFederation?
<LinkableListItem button key="public" to="/public">
<ListItemIcon><PublicIcon/></ListItemIcon>
<ListItemText primary="Public"/>
</LinkableListItem>:
<ListItem disabled>
<ListItemIcon><PublicIcon/></ListItemIcon>
<ListItemText primary="Public" secondary="Disabled by admin"/>
</ListItem>
}
<Divider/>
<ListSubheader>More</ListSubheader>
<LinkableListItem button key="recommended" to="/recommended">
<LinkableListItem button key="recommended" to="/recommended" disabled>
<ListItemIcon><GroupIcon/></ListItemIcon>
<ListItemText primary="Who to follow"/>
<ListItemText primary="Who to follow" secondary="Coming soon!"/>
</LinkableListItem>
<LinkableListItem button key="settings" to="/settings">
<ListItemIcon><SettingsIcon/></ListItemIcon>
@ -232,7 +258,7 @@ export class AppLayout extends Component<any, IAppLayoutState> {
<MenuIcon/>
</IconButton>
<Typography className={classes.appBarTitle} variant="h6" color="inherit" noWrap>
Hyperspace
{this.state.brandName? this.state.brandName: "Hyperspace"}
</Typography>
<div className={classes.appBarFlexGrow}/>
<div className={classes.appBarSearch}>

View File

@ -17,28 +17,71 @@ import OpenInNewIcon from '@material-ui/icons/OpenInNew';
import DomainIcon from '@material-ui/icons/Domain';
import ChatIcon from '@material-ui/icons/Chat';
import PersonIcon from '@material-ui/icons/Person';
import NetworkCheckIcon from '@material-ui/icons/NetworkCheck';
import UpdateIcon from '@material-ui/icons/Update';
import InfoIcon from '@material-ui/icons/Info';
import NotesIcon from '@material-ui/icons/Notes';
import {styles} from './PageLayout.styles';
import {Instance} from '../types/Instance';
import {LinkableIconButton} from '../interfaces/overrides';
import Mastodon from 'megalodon';
import { UAccount } from '../types/Account';
import { getConfig } from '../utilities/settings';
import { License } from '../types/Config';
interface IAboutPageState {
instance: Instance | any;
instance?: Instance | any;
federated?: boolean;
developer?: boolean;
hyperspaceAdmin?: UAccount;
versionNumber?: string;
brandName?: string;
license: License;
}
class AboutPage extends Component<any, IAboutPageState> {
client: Mastodon;
constructor(props: any) {
super(props);
this.client = new Mastodon(localStorage.getItem('access_token') as string, localStorage.getItem('baseurl') + "/api/v1");
this.state = {
license: {
name: "Apache 2.0 License (inherited)",
url: "https://www.apache.org/licenses/LICENSE-2.0"
}
}
}
componentWillMount() {
let client = new Mastodon(localStorage.getItem('access_token') as string, localStorage.getItem('baseurl') + "/api/v1");
client.get('/instance').then((resp: any) => {
this.client.get('/instance').then((resp: any) => {
this.setState({
instance: resp.data
})
})
getConfig().then((config: any) => {
this.client.get('/accounts/' + (config.admin? config.admin.account: "0")).then((resp: any) => {
let account = resp.data;
console.log(config);
this.setState({
hyperspaceAdmin: account,
federated: config.federated? config.federated === "true": false,
developer: config.developer? config.developer === "true": false,
versionNumber: config.version,
brandName: config.branding? config.branding.name: "Hyperspace",
license: {
name: config.license.name,
url: config.license.url
}
})
}).catch((err: Error) => {
console.error(err.message);
})
})
}
render() {
@ -54,7 +97,7 @@ class AboutPage extends Component<any, IAboutPageState> {
<DomainIcon/>
</Avatar>
</ListItemAvatar>
<ListItemText primary="Instance location (URL)" secondary={this.state ? this.state.instance.uri: "Loading..."}/>
<ListItemText primary="Instance location (URL)" secondary={this.state.instance ? this.state.instance.uri: "Loading..."}/>
<ListItemSecondaryAction>
<IconButton href={localStorage.getItem("baseurl") as string} target="_blank" rel="noreferrer">
<OpenInNewIcon/>
@ -63,17 +106,17 @@ class AboutPage extends Component<any, IAboutPageState> {
</ListItem>
<ListItem>
<ListItemAvatar>
<Avatar alt="Instance admin" src={this.state? this.state.instance.contact_account.avatar_static: ""}/>
<Avatar alt="Instance admin" src={this.state.instance? this.state.instance.contact_account.avatar_static: ""}/>
</ListItemAvatar>
<ListItemText primary="Instance admin" secondary={
this.state ? `${this.state.instance.contact_account.display_name} (@${this.state.instance.contact_account.acct})`:
this.state.instance ? `${this.state.instance.contact_account.display_name} (@${this.state.instance.contact_account.acct})`:
"Loading..."
}/>
<ListItemSecondaryAction>
<LinkableIconButton to={`/compose?visibility=public&acct=${this.state? this.state.instance.contact_account.acct: ""}`}>
<LinkableIconButton to={`/compose?visibility=public&acct=${this.state.instance? this.state.instance.contact_account.acct: ""}`}>
<ChatIcon/>
</LinkableIconButton>
<LinkableIconButton to={`/profile/${this.state? this.state.instance.contact_account.id: 0}`}>
<LinkableIconButton to={`/profile/${this.state.instance? this.state.instance.contact_account.id: 0}`}>
<PersonIcon/>
</LinkableIconButton>
</ListItemSecondaryAction>
@ -85,20 +128,70 @@ class AboutPage extends Component<any, IAboutPageState> {
<Paper className={classes.pageListConstraints}>
<List>
<ListItem>
<ListItemText primary="App version" secondary="Hyperspace v1.0.0"/>
<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>
<ListItem>
<ListItemText primary="Release channel" secondary="Developer"/>
<ListItemAvatar>
<Avatar src={this.state.hyperspaceAdmin? this.state.hyperspaceAdmin.avatar_static: ""}>
<PersonIcon/>
</Avatar>
</ListItemAvatar>
<ListItemText primary="App provider" secondary={this.state.hyperspaceAdmin? (this.state.hyperspaceAdmin.display_name || "@" + this.state.hyperspaceAdmin.acct): "No provider set in config"}/>
<ListItemSecondaryAction>
<LinkableIconButton to={`/compose?visibility=${this.state.federated? "public": "private"}&acct=${this.state.hyperspaceAdmin? this.state.hyperspaceAdmin.acct: ""}`}>
<ChatIcon/>
</LinkableIconButton>
<LinkableIconButton to={`/profile/${this.state.hyperspaceAdmin? this.state.hyperspaceAdmin.id: 0}`}>
<PersonIcon/>
</LinkableIconButton>
</ListItemSecondaryAction>
</ListItem>
<ListItem>
<ListItemText primary="License" secondary="Apache 2.0 License (inherited)"/>
<ListItemAvatar>
<Avatar>
<NetworkCheckIcon/>
</Avatar>
</ListItemAvatar>
<ListItemText primary="Federation status" secondary={`This instance of ${this.state? this.state.brandName: "Hyperspace"} is ${this.state? this.state.federated? "": "not": "unknown"} federated.`}/>
</ListItem>
<ListItem>
<ListItemAvatar>
<Avatar>
<UpdateIcon/>
</Avatar>
</ListItemAvatar>
<ListItemText primary="Release channel" secondary={
this.state?
this.state.developer?
"Developer":
"Release":
"Loading..."
}/>
</ListItem>
<ListItem>
<ListItemAvatar>
<Avatar>
<NotesIcon/>
</Avatar>
</ListItemAvatar>
<ListItemText primary="License" secondary={this.state.license.name}/>
<ListItemSecondaryAction>
<IconButton href={this.state.license.url} target="_blank" rel="noreferrer">
<OpenInNewIcon/>
</IconButton>
</ListItemSecondaryAction>
</ListItem>
</List>
</Paper>
<br/>
<div>
<Typography variant="caption">(C) 2019 Hyperspace developers. All rights reserved.</Typography>
<Typography variant="caption" paragraph>Hyperspace is made possible by the <Link href={"https://material-ui.com"} target="_blank" rel="noreferrer">Material UI</Link> project, <Link href={"https://www.npmjs.com/package/megalodon"} target="_blank" rel="noreferrer">Megalodon</Link> library, and other <Link href={"https://github.com/hyperspacedev/hyperspace/blob/master/package.json"} target="_blank" rel="noreferrer">open source software</Link>.</Typography>
<Typography variant="caption">(C) 2019 {this.state? this.state.brandName: "Hyperspace"} developers. All rights reserved.</Typography>
<Typography variant="caption" paragraph>{this.state? this.state.brandName: "Hyperspace"} is made possible by the <Link href={"https://material-ui.com"} target="_blank" rel="noreferrer">Material UI</Link> project, <Link href={"https://www.npmjs.com/package/megalodon"} target="_blank" rel="noreferrer">Megalodon</Link> library, and other <Link href={"https://github.com/hyperspacedev/hyperspace/blob/master/package.json"} target="_blank" rel="noreferrer">open source software</Link>.</Typography>
</div>
</div>
);

View File

@ -16,7 +16,8 @@ import {
RadioGroup,
FormControlLabel,
Radio,
DialogActions
DialogActions,
DialogContentText
} from '@material-ui/core';
import OpenInNewIcon from '@material-ui/icons/OpenInNew';
import {styles} from './PageLayout.styles';
@ -29,6 +30,7 @@ interface ISettingsState {
pushNotificationsEnabled: boolean;
selectThemeName: string;
themeDialogOpen: boolean;
resetHyperspaceDialog: boolean;
}
class SettingsPage extends Component<any, ISettingsState> {
@ -40,7 +42,8 @@ class SettingsPage extends Component<any, ISettingsState> {
darkModeEnabled: getUserDefaultBool('darkModeEnabled'),
pushNotificationsEnabled: canSendNotifications(),
selectThemeName: getUserDefaultTheme().key,
themeDialogOpen: false
themeDialogOpen: false,
resetHyperspaceDialog: false
}
this.toggleDarkMode = this.toggleDarkMode.bind(this);
@ -65,6 +68,10 @@ class SettingsPage extends Component<any, ISettingsState> {
this.setState({ themeDialogOpen: !this.state.themeDialogOpen });
}
toggleResetDialog() {
this.setState({ resetHyperspaceDialog: !this.state.resetHyperspaceDialog });
}
changeTheme() {
setUserDefaultTheme(this.state.selectThemeName);
window.location.reload();
@ -74,6 +81,11 @@ class SettingsPage extends Component<any, ISettingsState> {
this.setState({ selectThemeName: theme});
}
reset() {
localStorage.clear();
window.location.reload();
}
showThemeDialog() {
return (
<Dialog
@ -110,6 +122,32 @@ class SettingsPage extends Component<any, ISettingsState> {
);
}
showResetDialog() {
return (
<Dialog
open={this.state.resetHyperspaceDialog}
onClose={() => this.toggleResetDialog()}
>
<DialogTitle id="alert-dialog-title">Reset Hyperspace?</DialogTitle>
<DialogContent>
<DialogContentText id="alert-dialog-description">
Are you sure you want to reset Hyperspace? You'll need to sign in again and grant Hyperspace access to use it again.
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={() => this.toggleResetDialog()} color="primary" autoFocus>
Cancel
</Button>
<Button onClick={() => {
this.reset();
}} color="primary">
Reset
</Button>
</DialogActions>
</Dialog>
);
}
render() {
const { classes } = this.props;
return (
@ -177,7 +215,7 @@ class SettingsPage extends Component<any, ISettingsState> {
<ListItem>
<ListItemText primary="Reset Hyperspace" secondary="Deletes all data and resets the app"/>
<ListItemSecondaryAction>
<Button>
<Button onClick={() => this.toggleResetDialog()}>
Reset
</Button>
</ListItemSecondaryAction>
@ -185,6 +223,7 @@ class SettingsPage extends Component<any, ISettingsState> {
</List>
</Paper>
{this.showThemeDialog()}
{this.showResetDialog()}
</div>
);
}

View File

@ -1,9 +1,7 @@
import React, { Component } from 'react';
import {withStyles, Paper, Typography, Button, TextField, Fade, Checkbox, FormControlLabel, Link, CircularProgress} from '@material-ui/core';
import {withStyles, Paper, Typography, Button, TextField, Fade, Link, CircularProgress} from '@material-ui/core';
import {styles} from './WelcomePage.styles';
import axios from 'axios';
import Mastodon from 'megalodon';
import {Config} from '../types/Config';
import {SaveClientSession} from '../types/SessionData';
import { createHyperspaceApp } from '../utilities/login';
import {parseUrl} from 'query-string';

View File

@ -1,4 +1,5 @@
export type Config = {
version: string;
branding?: {
name?: string;
logo?: string;
@ -13,4 +14,10 @@ export type Config = {
name?: string;
account?: string;
};
license: License;
}
export type License = {
name: string;
url: string;
}