Merge pull request #85 from hyperspacedev/small-refinements-b
Small refinements and touch-ups
This commit is contained in:
commit
1a4d4120aa
|
@ -1297,6 +1297,12 @@
|
||||||
"@types/react": "*"
|
"@types/react": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@types/events": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"@types/history": {
|
"@types/history": {
|
||||||
"version": "4.7.2",
|
"version": "4.7.2",
|
||||||
"resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.2.tgz",
|
||||||
|
@ -1491,12 +1497,13 @@
|
||||||
"@types/unist": "*"
|
"@types/unist": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@types/ws": {
|
"@types/websocket": {
|
||||||
"version": "6.0.3",
|
"version": "0.0.40",
|
||||||
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-6.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/@types/websocket/-/websocket-0.0.40.tgz",
|
||||||
"integrity": "sha512-yBTM0P05Tx9iXGq00BbJPo37ox68R5vaGTXivs6RGh/BQ6QP5zqZDGWdAO6JbRE/iR1l80xeGAwCQS2nMV9S/w==",
|
"integrity": "sha512-ldteZwWIgl9cOy7FyvYn+39Ah4+PfpVE72eYKw75iy2L0zTbhbcwvzeJ5IOu6DQP93bjfXq0NGHY6FYtmYoqFQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
|
"@types/events": "*",
|
||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -12191,19 +12198,19 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"megalodon": {
|
"megalodon": {
|
||||||
"version": "1.0.3",
|
"version": "0.6.4",
|
||||||
"resolved": "https://registry.npmjs.org/megalodon/-/megalodon-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/megalodon/-/megalodon-0.6.4.tgz",
|
||||||
"integrity": "sha512-RcJT3HRWCXQcE5ZQUpLEjJ+HgWvwoTpXr4XLUf0tWrtmxrDnIW43pOASDb3G7MOBKYonzM1pMfAmR2Yfh3Qw/g==",
|
"integrity": "sha512-WGYhcSxGYlBwZSm5VebxLqnbpPemum9/6lJUi1HBsVzF5jXc9fdumhXH0vqGhWdovdqRT86iXBDJl5SwUrbr2A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@types/oauth": "^0.9.0",
|
"@types/oauth": "^0.9.0",
|
||||||
"@types/request": "^2.47.0",
|
"@types/request": "^2.47.0",
|
||||||
"@types/ws": "^6.0.1",
|
"@types/websocket": "0.0.40",
|
||||||
"axios": "^0.18.1",
|
"axios": "^0.18.0",
|
||||||
"oauth": "^0.9.15",
|
"oauth": "^0.9.15",
|
||||||
"request": "^2.87.0",
|
"request": "^2.87.0",
|
||||||
"typescript": "^3.4.5",
|
"typescript": "^2.9.1",
|
||||||
"ws": "^7.0.1"
|
"websocket": "^1.0.28"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": {
|
"axios": {
|
||||||
|
@ -12247,19 +12254,10 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"typescript": {
|
"typescript": {
|
||||||
"version": "3.6.3",
|
"version": "2.9.2",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.6.3.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-2.9.2.tgz",
|
||||||
"integrity": "sha512-N7bceJL1CtRQ2RiG0AQME13ksR7DiuQh/QehubYcghzv20tnh+MQnQIuJddTmsbqYj+dztchykemz0zFzlvdQw==",
|
"integrity": "sha512-Gr4p6nFNaoufRIY4NMdpQRNmgxVIGMs4Fcu/ujdYk3nAZqk7supzBE9idmvfZIlH/Cuj//dvi+019qEue9lV0w==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
|
||||||
"ws": {
|
|
||||||
"version": "7.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.1.2.tgz",
|
|
||||||
"integrity": "sha512-gftXq3XI81cJCgkUiAVixA0raD9IVmXqsylCrjRygw4+UOOGzPoxnQ6r/CnVL9i+mDncJo94tSkyrtuuQVBmrg==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"async-limiter": "^1.0.0"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -19851,6 +19849,15 @@
|
||||||
"integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=",
|
"integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"typedarray-to-buffer": {
|
||||||
|
"version": "3.1.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz",
|
||||||
|
"integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"is-typedarray": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"typescript": {
|
"typescript": {
|
||||||
"version": "3.4.1",
|
"version": "3.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.4.1.tgz",
|
||||||
|
@ -21010,6 +21017,41 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"websocket": {
|
||||||
|
"version": "1.0.30",
|
||||||
|
"resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.30.tgz",
|
||||||
|
"integrity": "sha512-aO6klgaTdSMkhfl5VVJzD5fm+Srhh5jLYbS15+OiI1sN6h/RU/XW6WN9J1uVIpUKNmsTvT3Hs35XAFjn9NMfOw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"debug": "^2.2.0",
|
||||||
|
"nan": "^2.14.0",
|
||||||
|
"typedarray-to-buffer": "^3.1.5",
|
||||||
|
"yaeti": "^0.0.6"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"debug": {
|
||||||
|
"version": "2.6.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||||
|
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"ms": "2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ms": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||||
|
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"nan": {
|
||||||
|
"version": "2.14.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz",
|
||||||
|
"integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==",
|
||||||
|
"dev": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"websocket-driver": {
|
"websocket-driver": {
|
||||||
"version": "0.7.0",
|
"version": "0.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.0.tgz",
|
||||||
|
@ -21368,6 +21410,12 @@
|
||||||
"integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=",
|
"integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"yaeti": {
|
||||||
|
"version": "0.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz",
|
||||||
|
"integrity": "sha1-8m9ITXJoTPQr7ft2lwqhYI+/lXc=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"yallist": {
|
"yallist": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
|
||||||
|
|
|
@ -25,10 +25,10 @@
|
||||||
"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.13.0",
|
"mdi-material-ui": "^5.13.0",
|
||||||
"megalodon": "^1.0.3",
|
"megalodon": "^0.6.4",
|
||||||
"moment": "^2.24.0",
|
"moment": "^2.24.0",
|
||||||
"notistack": "^0.5.1",
|
"notistack": "^0.5.1",
|
||||||
"prettier": "^1.18.2",
|
"prettier": "1.18.2",
|
||||||
"query-string": "^6.8.2",
|
"query-string": "^6.8.2",
|
||||||
"react": "^16.8.6",
|
"react": "^16.8.6",
|
||||||
"react-dom": "^16.8.6",
|
"react-dom": "^16.8.6",
|
||||||
|
|
BIN
screenshot.png
BIN
screenshot.png
Binary file not shown.
Before Width: | Height: | Size: 276 KiB After Width: | Height: | Size: 3.3 MiB |
|
@ -2,23 +2,23 @@ import { Theme, createStyles } from "@material-ui/core";
|
||||||
import { isDarwinApp } from "./utilities/desktop";
|
import { isDarwinApp } from "./utilities/desktop";
|
||||||
|
|
||||||
export const styles = (theme: Theme) =>
|
export const styles = (theme: Theme) =>
|
||||||
createStyles({
|
createStyles({
|
||||||
root: {
|
root: {
|
||||||
width: "100%",
|
width: "100%",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
height: "100%",
|
height: "100%",
|
||||||
minHeight: "100vh",
|
minHeight: "100vh",
|
||||||
backgroundColor: isDarwinApp()
|
backgroundColor: isDarwinApp()
|
||||||
? "transparent"
|
? "transparent"
|
||||||
: theme.palette.background.default
|
: theme.palette.background.default
|
||||||
},
|
},
|
||||||
content: {
|
content: {
|
||||||
marginTop: 72,
|
marginTop: 72,
|
||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
padding: theme.spacing.unit * 3,
|
padding: theme.spacing.unit * 3,
|
||||||
[theme.breakpoints.up("md")]: {
|
[theme.breakpoints.up("md")]: {
|
||||||
marginLeft: 250,
|
marginLeft: 250,
|
||||||
marginTop: 88
|
marginTop: 88
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -20,7 +20,6 @@ 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 You from "./pages/You";
|
||||||
import Blocked from "./pages/Blocked";
|
|
||||||
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";
|
||||||
|
@ -57,8 +56,6 @@ class App extends Component<any, any> {
|
||||||
removeBodyBackground() {
|
removeBodyBackground() {
|
||||||
if (isDarwinApp()) {
|
if (isDarwinApp()) {
|
||||||
document.body.style.backgroundColor = "transparent";
|
document.body.style.backgroundColor = "transparent";
|
||||||
console.log("Changed!");
|
|
||||||
console.log(`New color: ${document.body.style.backgroundColor}`);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,9 +88,7 @@ class App extends Component<any, any> {
|
||||||
component={Conversation}
|
component={Conversation}
|
||||||
/>
|
/>
|
||||||
<PrivateRoute path="/search" component={SearchPage} />
|
<PrivateRoute path="/search" component={SearchPage} />
|
||||||
<PrivateRoute path="/blocked" component={Blocked} />
|
|
||||||
<PrivateRoute path="/settings" component={Settings} />
|
<PrivateRoute path="/settings" component={Settings} />
|
||||||
|
|
||||||
<PrivateRoute path="/you" component={You} />
|
<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} />
|
||||||
|
|
|
@ -5,167 +5,167 @@ import { isDarwinApp } from "../../utilities/desktop";
|
||||||
import { fade } from "@material-ui/core/styles/colorManipulator";
|
import { fade } from "@material-ui/core/styles/colorManipulator";
|
||||||
|
|
||||||
export const styles = (theme: Theme) =>
|
export const styles = (theme: Theme) =>
|
||||||
createStyles({
|
createStyles({
|
||||||
root: {
|
root: {
|
||||||
width: "100%",
|
width: "100%",
|
||||||
display: "flex"
|
display: "flex"
|
||||||
},
|
},
|
||||||
stickyArea: {
|
stickyArea: {
|
||||||
position: "fixed",
|
position: "fixed",
|
||||||
width: "100%",
|
width: "100%",
|
||||||
top: 0,
|
top: 0,
|
||||||
left: 0,
|
left: 0,
|
||||||
zIndex: 1000
|
zIndex: 1000
|
||||||
},
|
},
|
||||||
titleBarRoot: {
|
titleBarRoot: {
|
||||||
top: 0,
|
top: 0,
|
||||||
left: 0,
|
left: 0,
|
||||||
height: 24,
|
height: 24,
|
||||||
width: "100%",
|
width: "100%",
|
||||||
backgroundColor: isDarwinApp()
|
backgroundColor: isDarwinApp()
|
||||||
? theme.palette.primary.main
|
? theme.palette.primary.main
|
||||||
: theme.palette.primary.dark,
|
: theme.palette.primary.dark,
|
||||||
textAlign: "center",
|
textAlign: "center",
|
||||||
zIndex: 1000,
|
zIndex: 1000,
|
||||||
verticalAlign: "middle",
|
verticalAlign: "middle",
|
||||||
WebkitUserSelect: "none",
|
WebkitUserSelect: "none",
|
||||||
WebkitAppRegion: "drag"
|
WebkitAppRegion: "drag"
|
||||||
},
|
},
|
||||||
titleBarText: {
|
titleBarText: {
|
||||||
color: theme.palette.common.white,
|
color: theme.palette.common.white,
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
paddingTop: 2,
|
paddingTop: 2,
|
||||||
paddingBottom: 1
|
paddingBottom: 1
|
||||||
},
|
},
|
||||||
appBar: {
|
appBar: {
|
||||||
zIndex: 1000,
|
zIndex: 1000,
|
||||||
backgroundImage: isDarwinApp()
|
backgroundImage: isDarwinApp()
|
||||||
? `linear-gradient(${theme.palette.primary.main}, ${theme.palette.primary.dark})`
|
? `linear-gradient(${theme.palette.primary.main}, ${theme.palette.primary.dark})`
|
||||||
: undefined,
|
: undefined,
|
||||||
backgroundColor: theme.palette.primary.main,
|
backgroundColor: theme.palette.primary.main,
|
||||||
borderBottomColor: darken(theme.palette.primary.dark, 0.2),
|
borderBottomColor: darken(theme.palette.primary.dark, 0.2),
|
||||||
borderBottomWidth: 1,
|
borderBottomWidth: 1,
|
||||||
borderBottomStyle: isDarwinApp() ? "solid" : "none",
|
borderBottomStyle: isDarwinApp() ? "solid" : "none",
|
||||||
boxShadow: isDarwinApp() ? "none" : "inherit"
|
boxShadow: isDarwinApp() ? "none" : theme.shadows["4"]
|
||||||
},
|
},
|
||||||
appBarMenuButton: {
|
appBarMenuButton: {
|
||||||
marginLeft: -12,
|
marginLeft: -12,
|
||||||
marginRight: 20,
|
marginRight: 20,
|
||||||
[theme.breakpoints.up("md")]: {
|
[theme.breakpoints.up("md")]: {
|
||||||
display: "none"
|
display: "none"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
appBarTitle: {
|
appBarTitle: {
|
||||||
display: "none",
|
display: "none",
|
||||||
[theme.breakpoints.up("md")]: {
|
[theme.breakpoints.up("md")]: {
|
||||||
display: "block"
|
display: "block"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
appBarSearch: {
|
appBarSearch: {
|
||||||
position: "relative",
|
position: "relative",
|
||||||
borderRadius: theme.shape.borderRadius,
|
borderRadius: theme.shape.borderRadius,
|
||||||
backgroundColor: fade(theme.palette.common.white, 0.15),
|
backgroundColor: fade(theme.palette.common.white, 0.15),
|
||||||
"&:hover": {
|
"&:hover": {
|
||||||
backgroundColor: fade(theme.palette.common.white, 0.25)
|
backgroundColor: fade(theme.palette.common.white, 0.25)
|
||||||
},
|
},
|
||||||
width: "100%",
|
width: "100%",
|
||||||
marginLeft: 0,
|
marginLeft: 0,
|
||||||
marginRight: theme.spacing.unit,
|
marginRight: theme.spacing.unit,
|
||||||
[theme.breakpoints.up("md")]: {
|
[theme.breakpoints.up("md")]: {
|
||||||
marginLeft: theme.spacing.unit * 6,
|
marginLeft: theme.spacing.unit * 6,
|
||||||
width: "50%"
|
width: "50%"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
appBarSearchIcon: {
|
appBarSearchIcon: {
|
||||||
width: theme.spacing.unit * 9,
|
width: theme.spacing.unit * 9,
|
||||||
height: "100%",
|
height: "100%",
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
pointerEvents: "none",
|
pointerEvents: "none",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
justifyContent: "center"
|
justifyContent: "center"
|
||||||
},
|
},
|
||||||
appBarSearchInputRoot: {
|
appBarSearchInputRoot: {
|
||||||
color: "inherit",
|
color: "inherit",
|
||||||
width: "100%"
|
width: "100%"
|
||||||
},
|
},
|
||||||
appBarSearchInputInput: {
|
appBarSearchInputInput: {
|
||||||
paddingTop: theme.spacing.unit,
|
paddingTop: theme.spacing.unit,
|
||||||
paddingBottom: theme.spacing.unit,
|
paddingBottom: theme.spacing.unit,
|
||||||
paddingLeft: theme.spacing.unit * 10,
|
paddingLeft: theme.spacing.unit * 10,
|
||||||
paddingRight: theme.spacing.unit,
|
paddingRight: theme.spacing.unit,
|
||||||
transition: theme.transitions.create("width"),
|
transition: theme.transitions.create("width"),
|
||||||
width: "100%"
|
width: "100%"
|
||||||
},
|
},
|
||||||
appBarFlexGrow: {
|
appBarFlexGrow: {
|
||||||
flexGrow: 1
|
flexGrow: 1
|
||||||
},
|
},
|
||||||
appBarActionButtons: {
|
appBarActionButtons: {
|
||||||
display: "none",
|
display: "none",
|
||||||
[theme.breakpoints.up("sm")]: {
|
[theme.breakpoints.up("sm")]: {
|
||||||
display: "flex"
|
display: "flex"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
appBarAcctMenuIcon: {
|
appBarAcctMenuIcon: {
|
||||||
backgroundColor: theme.palette.primary.dark
|
backgroundColor: theme.palette.primary.dark
|
||||||
},
|
},
|
||||||
acctMenu: {},
|
acctMenu: {},
|
||||||
drawer: {
|
drawer: {
|
||||||
[theme.breakpoints.up("sm")]: {
|
[theme.breakpoints.up("sm")]: {
|
||||||
width: 250,
|
width: 250,
|
||||||
flexShrink: 0
|
flexShrink: 0
|
||||||
},
|
},
|
||||||
zIndex: 1
|
zIndex: 1
|
||||||
},
|
},
|
||||||
drawerPaper: {
|
drawerPaper: {
|
||||||
width: 250,
|
width: 250,
|
||||||
zIndex: 1
|
zIndex: 1
|
||||||
},
|
},
|
||||||
drawerPaperWithAppBar: {
|
drawerPaperWithAppBar: {
|
||||||
width: 250,
|
width: 250,
|
||||||
zIndex: -1,
|
zIndex: -1,
|
||||||
marginTop: 64,
|
marginTop: 64,
|
||||||
backgroundColor: isDarwinApp()
|
backgroundColor: isDarwinApp()
|
||||||
? "transparent"
|
? "transparent"
|
||||||
: theme.palette.background.paper
|
: theme.palette.background.paper
|
||||||
},
|
},
|
||||||
drawerPaperWithTitleAndAppBar: {
|
drawerPaperWithTitleAndAppBar: {
|
||||||
width: 250,
|
width: 250,
|
||||||
zIndex: -1,
|
zIndex: -1,
|
||||||
marginTop: 88,
|
marginTop: 88,
|
||||||
backgroundColor: isDarwinApp()
|
backgroundColor: isDarwinApp()
|
||||||
? "transparent"
|
? "transparent"
|
||||||
: theme.palette.background.paper
|
: theme.palette.background.paper
|
||||||
},
|
},
|
||||||
drawerDisplayMobile: {
|
drawerDisplayMobile: {
|
||||||
[theme.breakpoints.up("md")]: {
|
[theme.breakpoints.up("md")]: {
|
||||||
display: "none"
|
display: "none"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
toolbar: theme.mixins.toolbar,
|
toolbar: theme.mixins.toolbar,
|
||||||
sectionDesktop: {
|
sectionDesktop: {
|
||||||
display: "none",
|
display: "none",
|
||||||
[theme.breakpoints.up("md")]: {
|
[theme.breakpoints.up("md")]: {
|
||||||
display: "flex"
|
display: "flex"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
sectionMobile: {
|
sectionMobile: {
|
||||||
display: "flex",
|
display: "flex",
|
||||||
[theme.breakpoints.up("md")]: {
|
[theme.breakpoints.up("md")]: {
|
||||||
display: "none"
|
display: "none"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
content: {
|
content: {
|
||||||
padding: theme.spacing.unit * 3,
|
padding: theme.spacing.unit * 3,
|
||||||
[theme.breakpoints.up("md")]: {
|
[theme.breakpoints.up("md")]: {
|
||||||
marginLeft: 250
|
marginLeft: 250
|
||||||
},
|
},
|
||||||
overflowY: "auto"
|
overflowY: "auto"
|
||||||
},
|
},
|
||||||
composeButton: {
|
composeButton: {
|
||||||
position: "fixed",
|
position: "fixed",
|
||||||
bottom: theme.spacing.unit * 2,
|
bottom: theme.spacing.unit * 2,
|
||||||
right: theme.spacing.unit * 2,
|
right: theme.spacing.unit * 2,
|
||||||
zIndex: 50
|
zIndex: 50
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,17 +1,17 @@
|
||||||
import { Theme, createStyles } from "@material-ui/core";
|
import { Theme, createStyles } from "@material-ui/core";
|
||||||
|
|
||||||
export const styles = (theme: Theme) =>
|
export const styles = (theme: Theme) =>
|
||||||
createStyles({
|
createStyles({
|
||||||
mediaContainer: {
|
mediaContainer: {
|
||||||
padding: theme.spacing.unit * 2
|
padding: theme.spacing.unit * 2
|
||||||
},
|
},
|
||||||
mediaObject: {
|
mediaObject: {
|
||||||
width: "100%",
|
width: "100%",
|
||||||
height: "100%"
|
height: "100%"
|
||||||
},
|
},
|
||||||
mediaSlide: {
|
mediaSlide: {
|
||||||
backgroundColor: theme.palette.primary.light,
|
backgroundColor: theme.palette.primary.light,
|
||||||
width: "100%",
|
width: "100%",
|
||||||
height: "auto"
|
height: "auto"
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,141 +1,146 @@
|
||||||
import React, { Component } from "react";
|
import React, { Component } from "react";
|
||||||
import {
|
import {
|
||||||
withStyles,
|
withStyles,
|
||||||
Typography,
|
Typography,
|
||||||
MobileStepper,
|
MobileStepper,
|
||||||
Button
|
Button
|
||||||
} from "@material-ui/core";
|
} from "@material-ui/core";
|
||||||
import { styles } from "./Attachment.styles";
|
import { styles } from "./Attachment.styles";
|
||||||
import { Attachment } from "../../types/Attachment";
|
import { Attachment } from "../../types/Attachment";
|
||||||
import SwipeableViews from "react-swipeable-views";
|
import SwipeableViews from "react-swipeable-views";
|
||||||
|
|
||||||
interface IAttachmentProps {
|
interface IAttachmentProps {
|
||||||
media: [Attachment];
|
media: [Attachment];
|
||||||
classes?: any;
|
classes?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IAttachmentState {
|
interface IAttachmentState {
|
||||||
totalSteps: number;
|
totalSteps: number;
|
||||||
currentStep: number;
|
currentStep: number;
|
||||||
attachments: [Attachment];
|
attachments: [Attachment];
|
||||||
}
|
}
|
||||||
|
|
||||||
class AttachmentComponent extends Component<
|
class AttachmentComponent extends Component<
|
||||||
IAttachmentProps,
|
IAttachmentProps,
|
||||||
IAttachmentState
|
IAttachmentState
|
||||||
> {
|
> {
|
||||||
constructor(props: IAttachmentProps) {
|
constructor(props: IAttachmentProps) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
attachments: this.props.media,
|
attachments: this.props.media,
|
||||||
totalSteps: this.props.media.length,
|
totalSteps: this.props.media.length,
|
||||||
currentStep: 0
|
currentStep: 0
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
|
||||||
moveBack() {
|
|
||||||
let nextStep = this.state.currentStep - 1;
|
|
||||||
if (nextStep < 0) {
|
|
||||||
nextStep = 0;
|
|
||||||
}
|
}
|
||||||
this.setState({ currentStep: nextStep });
|
|
||||||
}
|
|
||||||
|
|
||||||
moveForward() {
|
moveBack() {
|
||||||
let nextStep = this.state.currentStep + 1;
|
let nextStep = this.state.currentStep - 1;
|
||||||
if (nextStep > this.state.totalSteps) {
|
if (nextStep < 0) {
|
||||||
nextStep = this.state.totalSteps;
|
nextStep = 0;
|
||||||
|
}
|
||||||
|
this.setState({ currentStep: nextStep });
|
||||||
}
|
}
|
||||||
this.setState({ currentStep: nextStep });
|
|
||||||
}
|
|
||||||
|
|
||||||
handleStepChange(currentStep: number) {
|
moveForward() {
|
||||||
this.setState({
|
let nextStep = this.state.currentStep + 1;
|
||||||
currentStep
|
if (nextStep > this.state.totalSteps) {
|
||||||
});
|
nextStep = this.state.totalSteps;
|
||||||
}
|
}
|
||||||
|
this.setState({ currentStep: nextStep });
|
||||||
getSlide(slide: Attachment) {
|
|
||||||
const { classes } = this.props;
|
|
||||||
switch (slide.type) {
|
|
||||||
case "image":
|
|
||||||
return (
|
|
||||||
<img
|
|
||||||
src={slide.url}
|
|
||||||
alt={slide.description ? slide.description : ""}
|
|
||||||
className={classes.mediaObject}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
case "video":
|
|
||||||
return (
|
|
||||||
<video
|
|
||||||
controls
|
|
||||||
autoPlay={false}
|
|
||||||
src={slide.url}
|
|
||||||
className={classes.mediaObject}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
case "gifv":
|
|
||||||
return (
|
|
||||||
<img
|
|
||||||
src={slide.url}
|
|
||||||
alt={slide.description ? slide.description : ""}
|
|
||||||
className={classes.mediaObject}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
case "unknown":
|
|
||||||
return <object data={slide.url} className={classes.mediaObject} />;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
handleStepChange(currentStep: number) {
|
||||||
const { classes } = this.props;
|
this.setState({
|
||||||
const step = this.state.currentStep;
|
currentStep
|
||||||
const mediaItem = this.state.attachments[step];
|
});
|
||||||
return (
|
}
|
||||||
<div className={classes.mediaContainer}>
|
|
||||||
<SwipeableViews index={this.state.currentStep}>
|
getSlide(slide: Attachment) {
|
||||||
{this.state.attachments.map((slide: Attachment) => {
|
const { classes } = this.props;
|
||||||
return (
|
switch (slide.type) {
|
||||||
<div key={slide.id} className={classes.mediaSlide}>
|
case "image":
|
||||||
{this.getSlide(slide)}
|
return (
|
||||||
</div>
|
<img
|
||||||
);
|
src={slide.url}
|
||||||
})}
|
alt={slide.description ? slide.description : ""}
|
||||||
</SwipeableViews>
|
className={classes.mediaObject}
|
||||||
<MobileStepper
|
/>
|
||||||
steps={this.state.totalSteps}
|
);
|
||||||
position="static"
|
case "video":
|
||||||
activeStep={this.state.currentStep}
|
return (
|
||||||
className={classes.mobileStepper}
|
<video
|
||||||
nextButton={
|
controls
|
||||||
<Button
|
autoPlay={false}
|
||||||
size="small"
|
src={slide.url}
|
||||||
onClick={() => this.moveForward()}
|
className={classes.mediaObject}
|
||||||
disabled={this.state.currentStep === this.state.totalSteps - 1}
|
/>
|
||||||
>
|
);
|
||||||
Next
|
case "gifv":
|
||||||
</Button>
|
return (
|
||||||
}
|
<img
|
||||||
backButton={
|
src={slide.url}
|
||||||
<Button
|
alt={slide.description ? slide.description : ""}
|
||||||
size="small"
|
className={classes.mediaObject}
|
||||||
onClick={() => this.moveBack()}
|
/>
|
||||||
disabled={this.state.currentStep === 0}
|
);
|
||||||
>
|
case "unknown":
|
||||||
Back
|
return (
|
||||||
</Button>
|
<object data={slide.url} className={classes.mediaObject} />
|
||||||
}
|
);
|
||||||
/>
|
}
|
||||||
<Typography variant="caption">
|
}
|
||||||
{mediaItem.description
|
|
||||||
? mediaItem.description
|
render() {
|
||||||
: "No description provided."}
|
const { classes } = this.props;
|
||||||
</Typography>
|
const step = this.state.currentStep;
|
||||||
</div>
|
const mediaItem = this.state.attachments[step];
|
||||||
);
|
return (
|
||||||
}
|
<div className={classes.mediaContainer}>
|
||||||
|
<SwipeableViews index={this.state.currentStep}>
|
||||||
|
{this.state.attachments.map((slide: Attachment) => {
|
||||||
|
return (
|
||||||
|
<div key={slide.id} className={classes.mediaSlide}>
|
||||||
|
{this.getSlide(slide)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</SwipeableViews>
|
||||||
|
<MobileStepper
|
||||||
|
steps={this.state.totalSteps}
|
||||||
|
position="static"
|
||||||
|
activeStep={this.state.currentStep}
|
||||||
|
className={classes.mobileStepper}
|
||||||
|
nextButton={
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
onClick={() => this.moveForward()}
|
||||||
|
disabled={
|
||||||
|
this.state.currentStep ===
|
||||||
|
this.state.totalSteps - 1
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Next
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
backButton={
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
onClick={() => this.moveBack()}
|
||||||
|
disabled={this.state.currentStep === 0}
|
||||||
|
>
|
||||||
|
Back
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Typography variant="caption">
|
||||||
|
{mediaItem.description
|
||||||
|
? mediaItem.description
|
||||||
|
: "No description provided."}
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withStyles(styles)(AttachmentComponent);
|
export default withStyles(styles)(AttachmentComponent);
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
import { Theme, createStyles } from "@material-ui/core";
|
import { Theme, createStyles } from "@material-ui/core";
|
||||||
|
|
||||||
export const styles = (theme: Theme) =>
|
export const styles = (theme: Theme) =>
|
||||||
createStyles({
|
createStyles({
|
||||||
attachmentArea: {
|
attachmentArea: {
|
||||||
height: 175,
|
height: 175,
|
||||||
width: 268,
|
width: 268,
|
||||||
backgroundColor: theme.palette.primary.light,
|
backgroundColor: theme.palette.primary.light,
|
||||||
color: theme.palette.common.white
|
color: theme.palette.common.white
|
||||||
},
|
},
|
||||||
attachmentBar: {
|
attachmentBar: {
|
||||||
marginLeft: 0
|
marginLeft: 0
|
||||||
},
|
},
|
||||||
attachmentText: {
|
attachmentText: {
|
||||||
backgroundColor: theme.palette.background.paper,
|
backgroundColor: theme.palette.background.paper,
|
||||||
opacity: 0.5
|
opacity: 0.5
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import React, { Component } from "react";
|
import React, { Component } from "react";
|
||||||
import {
|
import {
|
||||||
GridListTile,
|
GridListTile,
|
||||||
GridListTileBar,
|
GridListTileBar,
|
||||||
TextField,
|
TextField,
|
||||||
withStyles,
|
withStyles,
|
||||||
IconButton
|
IconButton
|
||||||
} from "@material-ui/core";
|
} from "@material-ui/core";
|
||||||
import { styles } from "./ComposeMediaAttachment.styles";
|
import { styles } from "./ComposeMediaAttachment.styles";
|
||||||
import { withSnackbar, withSnackbarProps } from "notistack";
|
import { withSnackbar, withSnackbarProps } from "notistack";
|
||||||
|
@ -13,82 +13,92 @@ import { Attachment } from "../../types/Attachment";
|
||||||
import DeleteIcon from "@material-ui/icons/Delete";
|
import DeleteIcon from "@material-ui/icons/Delete";
|
||||||
|
|
||||||
interface IComposeMediaAttachmentProps extends withSnackbarProps {
|
interface IComposeMediaAttachmentProps extends withSnackbarProps {
|
||||||
classes: any;
|
classes: any;
|
||||||
client: Mastodon;
|
client: Mastodon;
|
||||||
attachment: Attachment;
|
attachment: Attachment;
|
||||||
onDeleteCallback: any;
|
onDeleteCallback: any;
|
||||||
onAttachmentUpdate: any;
|
onAttachmentUpdate: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IComposeMediaAttachmentState {
|
interface IComposeMediaAttachmentState {
|
||||||
attachment: Attachment;
|
attachment: Attachment;
|
||||||
}
|
}
|
||||||
|
|
||||||
class ComposeMediaAttachment extends Component<
|
class ComposeMediaAttachment extends Component<
|
||||||
IComposeMediaAttachmentProps,
|
IComposeMediaAttachmentProps,
|
||||||
IComposeMediaAttachmentState
|
IComposeMediaAttachmentState
|
||||||
> {
|
> {
|
||||||
client: Mastodon;
|
client: Mastodon;
|
||||||
|
|
||||||
constructor(props: IComposeMediaAttachmentProps) {
|
constructor(props: IComposeMediaAttachmentProps) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.client = this.props.client;
|
this.client = this.props.client;
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
attachment: this.props.attachment
|
attachment: this.props.attachment
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
updateAttachmentText(text: string) {
|
updateAttachmentText(text: string) {
|
||||||
this.client
|
this.client
|
||||||
.put(`/media/${this.state.attachment.id}`, { description: text })
|
.put(`/media/${this.state.attachment.id}`, { description: text })
|
||||||
.then((resp: any) => {
|
.then((resp: any) => {
|
||||||
this.props.onAttachmentUpdate(resp.data);
|
this.props.onAttachmentUpdate(resp.data);
|
||||||
this.props.enqueueSnackbar("Description updated.");
|
this.props.enqueueSnackbar("Description updated.");
|
||||||
})
|
})
|
||||||
.catch((err: Error) => {
|
.catch((err: Error) => {
|
||||||
this.props.enqueueSnackbar("Couldn't update description: " + err.name);
|
this.props.enqueueSnackbar(
|
||||||
});
|
"Couldn't update description: " + err.name
|
||||||
}
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { classes, attachment } = this.props;
|
const { classes, attachment } = this.props;
|
||||||
return (
|
return (
|
||||||
<GridListTile className={classes.attachmentArea}>
|
<GridListTile className={classes.attachmentArea}>
|
||||||
{attachment.type === "image" || attachment.type === "gifv" ? (
|
{attachment.type === "image" || attachment.type === "gifv" ? (
|
||||||
<img
|
<img
|
||||||
src={attachment.url}
|
src={attachment.url}
|
||||||
alt={attachment.description ? attachment.description : ""}
|
alt={
|
||||||
/>
|
attachment.description ? attachment.description : ""
|
||||||
) : attachment.type === "video" ? (
|
}
|
||||||
<video autoPlay={false} src={attachment.url} />
|
/>
|
||||||
) : (
|
) : attachment.type === "video" ? (
|
||||||
<object data={attachment.url} />
|
<video autoPlay={false} src={attachment.url} />
|
||||||
)}
|
) : (
|
||||||
<GridListTileBar
|
<object data={attachment.url} />
|
||||||
classes={{ title: classes.attachmentBar }}
|
)}
|
||||||
title={
|
<GridListTileBar
|
||||||
<TextField
|
classes={{ title: classes.attachmentBar }}
|
||||||
variant="filled"
|
title={
|
||||||
label="Description"
|
<TextField
|
||||||
margin="dense"
|
variant="filled"
|
||||||
className={classes.attachmentText}
|
label="Description"
|
||||||
onBlur={event => this.updateAttachmentText(event.target.value)}
|
margin="dense"
|
||||||
></TextField>
|
className={classes.attachmentText}
|
||||||
}
|
onBlur={event =>
|
||||||
actionIcon={
|
this.updateAttachmentText(event.target.value)
|
||||||
<IconButton
|
}
|
||||||
color="inherit"
|
></TextField>
|
||||||
onClick={() => this.props.onDeleteCallback(this.state.attachment)}
|
}
|
||||||
>
|
actionIcon={
|
||||||
<DeleteIcon />
|
<IconButton
|
||||||
</IconButton>
|
color="inherit"
|
||||||
}
|
onClick={() =>
|
||||||
/>
|
this.props.onDeleteCallback(
|
||||||
</GridListTile>
|
this.state.attachment
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
>
|
||||||
|
<DeleteIcon />
|
||||||
|
</IconButton>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</GridListTile>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withStyles(styles)(withSnackbar(ComposeMediaAttachment));
|
export default withStyles(styles)(withSnackbar(ComposeMediaAttachment));
|
||||||
|
|
|
@ -3,30 +3,30 @@ import { Picker, PickerProps, CustomEmoji } from "emoji-mart";
|
||||||
import "emoji-mart/css/emoji-mart.css";
|
import "emoji-mart/css/emoji-mart.css";
|
||||||
|
|
||||||
interface IEmojiPickerProps extends PickerProps {
|
interface IEmojiPickerProps extends PickerProps {
|
||||||
onGetEmoji: any;
|
onGetEmoji: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class EmojiPicker extends Component<IEmojiPickerProps, any> {
|
export class EmojiPicker extends Component<IEmojiPickerProps, any> {
|
||||||
retrieveFromLocal() {
|
retrieveFromLocal() {
|
||||||
return JSON.parse(localStorage.getItem("emojis") as string);
|
return JSON.parse(localStorage.getItem("emojis") as string);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<Picker
|
<Picker
|
||||||
custom={this.retrieveFromLocal()}
|
custom={this.retrieveFromLocal()}
|
||||||
emoji=""
|
emoji=""
|
||||||
title=""
|
title=""
|
||||||
onClick={this.props.onGetEmoji}
|
onClick={this.props.onGetEmoji}
|
||||||
style={{
|
style={{
|
||||||
borderColor: "transparent"
|
borderColor: "transparent"
|
||||||
}}
|
}}
|
||||||
perLine={10}
|
perLine={10}
|
||||||
emojiSize={20}
|
emojiSize={20}
|
||||||
set={"google"}
|
set={"google"}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default EmojiPicker;
|
export default EmojiPicker;
|
||||||
|
|
|
@ -1,99 +1,99 @@
|
||||||
import { Theme, createStyles } from "@material-ui/core";
|
import { Theme, createStyles } from "@material-ui/core";
|
||||||
|
|
||||||
export const styles = (theme: Theme) =>
|
export const styles = (theme: Theme) =>
|
||||||
createStyles({
|
createStyles({
|
||||||
post: {
|
post: {
|
||||||
marginTop: theme.spacing.unit,
|
marginTop: theme.spacing.unit,
|
||||||
marginBottom: theme.spacing.unit
|
marginBottom: theme.spacing.unit
|
||||||
},
|
|
||||||
postReblogChip: {
|
|
||||||
color: theme.palette.common.white,
|
|
||||||
"&:hover": {
|
|
||||||
backgroundColor: theme.palette.secondary.light
|
|
||||||
},
|
|
||||||
backgroundColor: theme.palette.secondary.main,
|
|
||||||
marginBottom: theme.spacing.unit
|
|
||||||
},
|
|
||||||
postContent: {
|
|
||||||
paddingTop: 0,
|
|
||||||
paddingBottom: 0,
|
|
||||||
"& a": {
|
|
||||||
textDecoration: "none",
|
|
||||||
color: theme.palette.secondary.light,
|
|
||||||
"&:hover": {
|
|
||||||
textDecoration: "underline"
|
|
||||||
},
|
},
|
||||||
"&.u-url.mention": {
|
postReblogChip: {
|
||||||
textDecoration: "none",
|
color: theme.palette.common.white,
|
||||||
color: "inherit",
|
"&:hover": {
|
||||||
fontWeight: "bold"
|
backgroundColor: theme.palette.secondary.light
|
||||||
|
},
|
||||||
|
backgroundColor: theme.palette.secondary.main,
|
||||||
|
marginBottom: theme.spacing.unit
|
||||||
},
|
},
|
||||||
"&.mention.hashtag": {
|
postContent: {
|
||||||
textDecoration: "none",
|
paddingTop: 0,
|
||||||
color: "inherit",
|
paddingBottom: 0,
|
||||||
fontWeight: "bold"
|
"& a": {
|
||||||
|
textDecoration: "none",
|
||||||
|
color: theme.palette.secondary.light,
|
||||||
|
"&:hover": {
|
||||||
|
textDecoration: "underline"
|
||||||
|
},
|
||||||
|
"&.u-url.mention": {
|
||||||
|
textDecoration: "none",
|
||||||
|
color: "inherit",
|
||||||
|
fontWeight: "bold"
|
||||||
|
},
|
||||||
|
"&.mention.hashtag": {
|
||||||
|
textDecoration: "none",
|
||||||
|
color: "inherit",
|
||||||
|
fontWeight: "bold"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
postCard: {
|
||||||
|
"& a:hover": {
|
||||||
|
textDecoration: "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
postEmoji: {
|
||||||
|
height: theme.typography.fontSize
|
||||||
|
},
|
||||||
|
postMedia: {
|
||||||
|
height: 0,
|
||||||
|
paddingTop: "56.25%" // 16:9
|
||||||
|
},
|
||||||
|
postActionsReply: {
|
||||||
|
marginLeft: theme.spacing.unit,
|
||||||
|
marginRight: theme.spacing.unit
|
||||||
|
},
|
||||||
|
postFlexGrow: {
|
||||||
|
flexGrow: 1
|
||||||
|
},
|
||||||
|
postTypeIconDiv: {
|
||||||
|
marginRight: theme.spacing.unit * 2
|
||||||
|
},
|
||||||
|
postTypeIcon: {
|
||||||
|
color: theme.palette.grey[500]
|
||||||
|
},
|
||||||
|
postWarningIcon: {
|
||||||
|
marginRight: theme.spacing.unit,
|
||||||
|
color: "inherit"
|
||||||
|
},
|
||||||
|
postDidAction: {
|
||||||
|
color: theme.palette.secondary.main
|
||||||
|
},
|
||||||
|
postMention: {
|
||||||
|
marginRight: theme.spacing.unit,
|
||||||
|
marginBottom: theme.spacing.unit
|
||||||
|
},
|
||||||
|
nsfwCard: {
|
||||||
|
backgroundColor: theme.palette.error.main
|
||||||
|
},
|
||||||
|
postTags: {
|
||||||
|
paddingTop: theme.spacing.unit,
|
||||||
|
paddingBottom: theme.spacing.unit
|
||||||
|
},
|
||||||
|
postAuthorEmoji: {
|
||||||
|
height: theme.typography.fontSize,
|
||||||
|
verticalAlign: "middle"
|
||||||
|
},
|
||||||
|
heading: {
|
||||||
|
color: "inherit"
|
||||||
|
},
|
||||||
|
mobileOnly: {
|
||||||
|
[theme.breakpoints.up("sm")]: {
|
||||||
|
display: "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
desktopOnly: {
|
||||||
|
display: "none",
|
||||||
|
[theme.breakpoints.up("sm")]: {
|
||||||
|
display: "block"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
},
|
|
||||||
postCard: {
|
|
||||||
"& a:hover": {
|
|
||||||
textDecoration: "none"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
postEmoji: {
|
|
||||||
height: theme.typography.fontSize
|
|
||||||
},
|
|
||||||
postMedia: {
|
|
||||||
height: 0,
|
|
||||||
paddingTop: "56.25%" // 16:9
|
|
||||||
},
|
|
||||||
postActionsReply: {
|
|
||||||
marginLeft: theme.spacing.unit,
|
|
||||||
marginRight: theme.spacing.unit
|
|
||||||
},
|
|
||||||
postFlexGrow: {
|
|
||||||
flexGrow: 1
|
|
||||||
},
|
|
||||||
postTypeIconDiv: {
|
|
||||||
marginRight: theme.spacing.unit * 2
|
|
||||||
},
|
|
||||||
postTypeIcon: {
|
|
||||||
color: theme.palette.grey[500]
|
|
||||||
},
|
|
||||||
postWarningIcon: {
|
|
||||||
marginRight: theme.spacing.unit,
|
|
||||||
color: "inherit"
|
|
||||||
},
|
|
||||||
postDidAction: {
|
|
||||||
color: theme.palette.secondary.main
|
|
||||||
},
|
|
||||||
postMention: {
|
|
||||||
marginRight: theme.spacing.unit,
|
|
||||||
marginBottom: theme.spacing.unit
|
|
||||||
},
|
|
||||||
nsfwCard: {
|
|
||||||
backgroundColor: theme.palette.error.main
|
|
||||||
},
|
|
||||||
postTags: {
|
|
||||||
paddingTop: theme.spacing.unit,
|
|
||||||
paddingBottom: theme.spacing.unit
|
|
||||||
},
|
|
||||||
postAuthorEmoji: {
|
|
||||||
height: theme.typography.fontSize,
|
|
||||||
verticalAlign: "middle"
|
|
||||||
},
|
|
||||||
heading: {
|
|
||||||
color: "inherit"
|
|
||||||
},
|
|
||||||
mobileOnly: {
|
|
||||||
[theme.breakpoints.up("sm")]: {
|
|
||||||
display: "none"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
desktopOnly: {
|
|
||||||
display: "none",
|
|
||||||
[theme.breakpoints.up("sm")]: {
|
|
||||||
display: "block"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -3,18 +3,18 @@ import webShare, { WebShareInterface } from "react-web-share-api";
|
||||||
import { MenuItem } from "@material-ui/core";
|
import { MenuItem } from "@material-ui/core";
|
||||||
|
|
||||||
export interface OwnProps {
|
export interface OwnProps {
|
||||||
style: object;
|
style: object;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ShareMenu: React.FunctionComponent<WebShareInterface & OwnProps> = ({
|
const ShareMenu: React.FunctionComponent<WebShareInterface & OwnProps> = ({
|
||||||
share,
|
share,
|
||||||
isSupported,
|
isSupported,
|
||||||
style
|
style
|
||||||
}) =>
|
}) =>
|
||||||
isSupported ? (
|
isSupported ? (
|
||||||
<MenuItem onClick={share} style={style}>
|
<MenuItem onClick={share} style={style}>
|
||||||
Share
|
Share
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
) : null;
|
) : null;
|
||||||
|
|
||||||
export default webShare<OwnProps>()(ShareMenu);
|
export default webShare<OwnProps>()(ShareMenu);
|
||||||
|
|
|
@ -1,88 +1,93 @@
|
||||||
import React, { Component } from "react";
|
import React, { Component } from "react";
|
||||||
import {
|
import {
|
||||||
MuiThemeProvider,
|
MuiThemeProvider,
|
||||||
Theme,
|
Theme,
|
||||||
AppBar,
|
AppBar,
|
||||||
Typography,
|
Typography,
|
||||||
CssBaseline,
|
CssBaseline,
|
||||||
Toolbar,
|
Toolbar,
|
||||||
Fab,
|
Fab,
|
||||||
Paper
|
Paper
|
||||||
} from "@material-ui/core";
|
} from "@material-ui/core";
|
||||||
import EditIcon from "@material-ui/icons/Edit";
|
import EditIcon from "@material-ui/icons/Edit";
|
||||||
import MenuIcon from "@material-ui/icons/Menu";
|
import MenuIcon from "@material-ui/icons/Menu";
|
||||||
|
|
||||||
interface IThemePreviewProps {
|
interface IThemePreviewProps {
|
||||||
theme: Theme;
|
theme: Theme;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IThemePreviewState {
|
interface IThemePreviewState {
|
||||||
theme: Theme;
|
theme: Theme;
|
||||||
}
|
}
|
||||||
|
|
||||||
class ThemePreview extends Component<IThemePreviewProps, IThemePreviewState> {
|
class ThemePreview extends Component<IThemePreviewProps, IThemePreviewState> {
|
||||||
constructor(props: IThemePreviewProps) {
|
constructor(props: IThemePreviewProps) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
theme: this.props.theme
|
theme: this.props.theme
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div style={{ position: "relative" }}>
|
<div style={{ position: "relative" }}>
|
||||||
<MuiThemeProvider theme={this.props.theme}>
|
<MuiThemeProvider theme={this.props.theme}>
|
||||||
<CssBaseline />
|
<CssBaseline />
|
||||||
<Paper>
|
<Paper>
|
||||||
<AppBar color="primary" position="static">
|
<AppBar color="primary" position="static">
|
||||||
<Toolbar>
|
<Toolbar>
|
||||||
<MenuIcon style={{ marginRight: 20, marginLeft: -4 }} />
|
<MenuIcon
|
||||||
<Typography variant="h6" color="inherit">
|
style={{ marginRight: 20, marginLeft: -4 }}
|
||||||
Hyperspace
|
/>
|
||||||
</Typography>
|
<Typography variant="h6" color="inherit">
|
||||||
</Toolbar>
|
Hyperspace
|
||||||
</AppBar>
|
</Typography>
|
||||||
<div
|
</Toolbar>
|
||||||
style={{
|
</AppBar>
|
||||||
paddingLeft: 16,
|
<div
|
||||||
paddingTop: 16,
|
style={{
|
||||||
paddingRight: 16,
|
paddingLeft: 16,
|
||||||
paddingBottom: 16,
|
paddingTop: 16,
|
||||||
flexGrow: 1
|
paddingRight: 16,
|
||||||
}}
|
paddingBottom: 16,
|
||||||
>
|
flexGrow: 1
|
||||||
<Typography variant="h4" component="p">
|
}}
|
||||||
This is your theme.
|
>
|
||||||
</Typography>
|
<Typography variant="h4" component="p">
|
||||||
<br />
|
This is your theme.
|
||||||
<Typography paragraph>
|
</Typography>
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc
|
<br />
|
||||||
vestibulum congue sem ac ornare. In nec imperdiet neque. In
|
<Typography paragraph>
|
||||||
eleifend laoreet efficitur. Vestibulum vel odio mattis,
|
Lorem ipsum dolor sit amet, consectetur
|
||||||
scelerisque nibh a, ornare lectus. Phasellus sollicitudin erat
|
adipiscing elit. Nunc vestibulum congue sem ac
|
||||||
et turpis pellentesque consequat. In maximus luctus purus, eu
|
ornare. In nec imperdiet neque. In eleifend
|
||||||
molestie elit euismod eu. Pellentesque quam lectus, sagittis
|
laoreet efficitur. Vestibulum vel odio mattis,
|
||||||
eget accumsan in, consequat ut sapien. Morbi aliquet ligula
|
scelerisque nibh a, ornare lectus. Phasellus
|
||||||
erat, id dapibus nunc laoreet at. Integer sodales lacinia
|
sollicitudin erat et turpis pellentesque
|
||||||
finibus. Aliquam augue nibh, eleifend quis consectetur et,
|
consequat. In maximus luctus purus, eu molestie
|
||||||
rhoncus ut odio. Lorem ipsum dolor sit amet, consectetur
|
elit euismod eu. Pellentesque quam lectus,
|
||||||
adipiscing elit.
|
sagittis eget accumsan in, consequat ut sapien.
|
||||||
</Typography>
|
Morbi aliquet ligula erat, id dapibus nunc
|
||||||
|
laoreet at. Integer sodales lacinia finibus.
|
||||||
|
Aliquam augue nibh, eleifend quis consectetur
|
||||||
|
et, rhoncus ut odio. Lorem ipsum dolor sit amet,
|
||||||
|
consectetur adipiscing elit.
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
<div style={{ textAlign: "right" }}>
|
||||||
|
<Fab
|
||||||
|
color="secondary"
|
||||||
|
style={{ marginRight: 8, marginBottom: 8 }}
|
||||||
|
>
|
||||||
|
<EditIcon />
|
||||||
|
</Fab>
|
||||||
|
</div>
|
||||||
|
</Paper>
|
||||||
|
</MuiThemeProvider>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ textAlign: "right" }}>
|
);
|
||||||
<Fab
|
}
|
||||||
color="secondary"
|
|
||||||
style={{ marginRight: 8, marginBottom: 8 }}
|
|
||||||
>
|
|
||||||
<EditIcon />
|
|
||||||
</Fab>
|
|
||||||
</div>
|
|
||||||
</Paper>
|
|
||||||
</MuiThemeProvider>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ThemePreview;
|
export default ThemePreview;
|
||||||
|
|
|
@ -9,31 +9,31 @@ import { SnackbarProvider } from "notistack";
|
||||||
import { userLoggedIn, refreshUserAccountData } from "./utilities/accounts";
|
import { userLoggedIn, refreshUserAccountData } from "./utilities/accounts";
|
||||||
|
|
||||||
getConfig()
|
getConfig()
|
||||||
.then((config: any) => {
|
.then((config: any) => {
|
||||||
document.title = config.branding.name || "Hyperspace";
|
document.title = config.branding.name || "Hyperspace";
|
||||||
})
|
})
|
||||||
.catch((err: Error) => {
|
.catch((err: Error) => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
});
|
});
|
||||||
|
|
||||||
createUserDefaults();
|
createUserDefaults();
|
||||||
if (userLoggedIn()) {
|
if (userLoggedIn()) {
|
||||||
collectEmojisFromServer();
|
collectEmojisFromServer();
|
||||||
refreshUserAccountData();
|
refreshUserAccountData();
|
||||||
}
|
}
|
||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<HashRouter>
|
<HashRouter>
|
||||||
<SnackbarProvider
|
<SnackbarProvider
|
||||||
anchorOrigin={{
|
anchorOrigin={{
|
||||||
vertical: "bottom",
|
vertical: "bottom",
|
||||||
horizontal: "left"
|
horizontal: "left"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<App />
|
<App />
|
||||||
</SnackbarProvider>
|
</SnackbarProvider>
|
||||||
</HashRouter>,
|
</HashRouter>,
|
||||||
document.getElementById("root")
|
document.getElementById("root")
|
||||||
);
|
);
|
||||||
|
|
||||||
// If you want your app to work offline and load faster, you can change
|
// If you want your app to work offline and load faster, you can change
|
||||||
|
|
|
@ -11,87 +11,87 @@ import Avatar, { AvatarProps } from "@material-ui/core/Avatar";
|
||||||
import { userLoggedIn } from "../utilities/accounts";
|
import { userLoggedIn } from "../utilities/accounts";
|
||||||
|
|
||||||
export interface ILinkableListItemProps extends ListItemProps {
|
export interface ILinkableListItemProps extends ListItemProps {
|
||||||
to: string;
|
to: string;
|
||||||
replace?: boolean;
|
replace?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ILinkableIconButtonProps extends IconButtonProps {
|
export interface ILinkableIconButtonProps extends IconButtonProps {
|
||||||
to: string;
|
to: string;
|
||||||
replace?: boolean;
|
replace?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ILinkableChipProps extends ChipProps {
|
export interface ILinkableChipProps extends ChipProps {
|
||||||
to: string;
|
to: string;
|
||||||
replace?: boolean;
|
replace?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ILinkableMenuItemProps extends MenuItemProps {
|
export interface ILinkableMenuItemProps extends MenuItemProps {
|
||||||
to: string;
|
to: string;
|
||||||
replace?: boolean;
|
replace?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ILinkableButtonProps extends ButtonProps {
|
export interface ILinkableButtonProps extends ButtonProps {
|
||||||
to: string;
|
to: string;
|
||||||
replace?: boolean;
|
replace?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ILinkableFabProps extends FabProps {
|
export interface ILinkableFabProps extends FabProps {
|
||||||
to: string;
|
to: string;
|
||||||
replace?: boolean;
|
replace?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ILinkableAvatarProps extends AvatarProps {
|
export interface ILinkableAvatarProps extends AvatarProps {
|
||||||
to: string;
|
to: string;
|
||||||
replace?: boolean;
|
replace?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const LinkableListItem = (props: ILinkableListItemProps) => (
|
export const LinkableListItem = (props: ILinkableListItemProps) => (
|
||||||
<ListItem {...props} component={Link as any} />
|
<ListItem {...props} component={Link as any} />
|
||||||
);
|
);
|
||||||
|
|
||||||
export const LinkableIconButton = (props: ILinkableIconButtonProps) => (
|
export const LinkableIconButton = (props: ILinkableIconButtonProps) => (
|
||||||
<IconButton {...props} component={Link as any} />
|
<IconButton {...props} component={Link as any} />
|
||||||
);
|
);
|
||||||
|
|
||||||
export const LinkableChip = (props: ILinkableChipProps) => (
|
export const LinkableChip = (props: ILinkableChipProps) => (
|
||||||
<Chip {...props} component={Link as any} />
|
<Chip {...props} component={Link as any} />
|
||||||
);
|
);
|
||||||
|
|
||||||
export const LinkableMenuItem = (props: ILinkableMenuItemProps) => (
|
export const LinkableMenuItem = (props: ILinkableMenuItemProps) => (
|
||||||
<MenuItem {...props} component={Link as any} />
|
<MenuItem {...props} component={Link as any} />
|
||||||
);
|
);
|
||||||
|
|
||||||
export const LinkableButton = (props: ILinkableButtonProps) => (
|
export const LinkableButton = (props: ILinkableButtonProps) => (
|
||||||
<Button {...props} component={Link as any} />
|
<Button {...props} component={Link as any} />
|
||||||
);
|
);
|
||||||
|
|
||||||
export const LinkableFab = (props: ILinkableFabProps) => (
|
export const LinkableFab = (props: ILinkableFabProps) => (
|
||||||
<Fab {...props} component={Link as any} />
|
<Fab {...props} component={Link as any} />
|
||||||
);
|
);
|
||||||
export const LinkableAvatar = (props: ILinkableAvatarProps) => (
|
export const LinkableAvatar = (props: ILinkableAvatarProps) => (
|
||||||
<Avatar {...props} component={Link as any} />
|
<Avatar {...props} component={Link as any} />
|
||||||
);
|
);
|
||||||
|
|
||||||
export const ProfileRoute = (rest: any, component: Component) => (
|
export const ProfileRoute = (rest: any, component: Component) => (
|
||||||
<Route {...rest} render={props => <Component {...props} />} />
|
<Route {...rest} render={props => <Component {...props} />} />
|
||||||
);
|
);
|
||||||
|
|
||||||
export const PrivateRoute = (props: IPrivateRouteProps) => {
|
export const PrivateRoute = (props: IPrivateRouteProps) => {
|
||||||
const { component, render, ...rest } = props;
|
const { component, render, ...rest } = props;
|
||||||
return (
|
return (
|
||||||
<Route
|
<Route
|
||||||
{...rest}
|
{...rest}
|
||||||
render={(compProps: any) =>
|
render={(compProps: any) =>
|
||||||
userLoggedIn() ? (
|
userLoggedIn() ? (
|
||||||
React.createElement(component, compProps)
|
React.createElement(component, compProps)
|
||||||
) : (
|
) : (
|
||||||
<Redirect to="/welcome" />
|
<Redirect to="/welcome" />
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
interface IPrivateRouteProps extends RouteProps {
|
interface IPrivateRouteProps extends RouteProps {
|
||||||
component: any;
|
component: any;
|
||||||
}
|
}
|
||||||
|
|
|
@ -110,6 +110,135 @@ class AboutPage extends Component<any, IAboutPageState> {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes.pageLayoutConstraints}>
|
<div className={classes.pageLayoutConstraints}>
|
||||||
|
<Paper>
|
||||||
|
<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>
|
||||||
|
<div className={classes.instanceHeaderText}>
|
||||||
|
<Typography variant="h4" component="p">
|
||||||
|
{this.state.brandName? this.state.brandName: "Hyperspace"}
|
||||||
|
</Typography>
|
||||||
|
<Typography>Version {`${this.state? this.state.versionNumber: "1.0.x"} ${this.state && this.state.brandName !== "Hyperspace"? "(Hyperspace-like)": ""}`}</Typography>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<List className={classes.pageListConstraints}>
|
||||||
|
<ListItem>
|
||||||
|
<ListItemAvatar>
|
||||||
|
<LinkableAvatar
|
||||||
|
to={`/profile/${
|
||||||
|
this.state.hyperspaceAdmin
|
||||||
|
? this.state.hyperspaceAdmin.id
|
||||||
|
: 0
|
||||||
|
}`}
|
||||||
|
src={
|
||||||
|
this.state.hyperspaceAdmin
|
||||||
|
? this.state.hyperspaceAdmin.avatar_static
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<PersonIcon />
|
||||||
|
</LinkableAvatar>
|
||||||
|
</ListItemAvatar>
|
||||||
|
<ListItemText
|
||||||
|
primary="App provider"
|
||||||
|
secondary={
|
||||||
|
this.state.hyperspaceAdmin && this.state.hyperspaceAdminName
|
||||||
|
? this.state.hyperspaceAdminName ||
|
||||||
|
this.state.hyperspaceAdmin.display_name ||
|
||||||
|
"@" + this.state.hyperspaceAdmin.acct
|
||||||
|
: "No provider set in config"
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<ListItemSecondaryAction>
|
||||||
|
<Tooltip title="Send a post or message">
|
||||||
|
<LinkableIconButton
|
||||||
|
to={`/compose?visibility=${
|
||||||
|
this.state.federated ? "public" : "private"
|
||||||
|
}&acct=${
|
||||||
|
this.state.hyperspaceAdmin
|
||||||
|
? this.state.hyperspaceAdmin.acct
|
||||||
|
: ""
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<ChatIcon />
|
||||||
|
</LinkableIconButton>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip title="View profile">
|
||||||
|
<LinkableIconButton
|
||||||
|
to={`/profile/${
|
||||||
|
this.state.hyperspaceAdmin
|
||||||
|
? this.state.hyperspaceAdmin.id
|
||||||
|
: 0
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<AssignmentIndIcon />
|
||||||
|
</LinkableIconButton>
|
||||||
|
</Tooltip>
|
||||||
|
</ListItemSecondaryAction>
|
||||||
|
</ListItem>
|
||||||
|
<ListItem>
|
||||||
|
<ListItemAvatar>
|
||||||
|
<Avatar>
|
||||||
|
<NotesIcon />
|
||||||
|
</Avatar>
|
||||||
|
</ListItemAvatar>
|
||||||
|
<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>
|
||||||
|
<ListItemAvatar>
|
||||||
|
<Avatar>
|
||||||
|
<UpdateIcon />
|
||||||
|
</Avatar>
|
||||||
|
</ListItemAvatar>
|
||||||
|
<ListItemText
|
||||||
|
primary="Release channel"
|
||||||
|
secondary={
|
||||||
|
this.state
|
||||||
|
? this.state.developer
|
||||||
|
? "Developer"
|
||||||
|
: "Release"
|
||||||
|
: "Loading..."
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
</List>
|
||||||
|
</Paper>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
|
||||||
<Paper>
|
<Paper>
|
||||||
<div
|
<div
|
||||||
className={classes.instanceHeaderPaper}
|
className={classes.instanceHeaderPaper}
|
||||||
|
@ -130,13 +259,10 @@ class AboutPage extends Component<any, IAboutPageState> {
|
||||||
>
|
>
|
||||||
<OpenInNewIcon />
|
<OpenInNewIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<Typography
|
<div className={classes.instanceHeaderText}>
|
||||||
className={classes.instanceHeaderText}
|
<Typography variant="h4" component="p">{this.state.instance ? this.state.instance.uri: "Loading..."}</Typography>
|
||||||
variant="h4"
|
<Typography>Server version {this.state.instance? this.state.instance.version: "x.x.x"}</Typography>
|
||||||
component="p"
|
</div>
|
||||||
>
|
|
||||||
{this.state.instance ? this.state.instance.uri : "Loading..."}
|
|
||||||
</Typography>
|
|
||||||
</div>
|
</div>
|
||||||
<List className={classes.pageListConstraints}>
|
<List className={classes.pageListConstraints}>
|
||||||
{localStorage["isPleroma"] == "false" && (
|
{localStorage["isPleroma"] == "false" && (
|
||||||
|
@ -238,168 +364,9 @@ class AboutPage extends Component<any, IAboutPageState> {
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</ListItemSecondaryAction>
|
</ListItemSecondaryAction>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
<ListItem>
|
|
||||||
<ListItemAvatar>
|
|
||||||
<Avatar>
|
|
||||||
<MastodonIcon />
|
|
||||||
</Avatar>
|
|
||||||
</ListItemAvatar>
|
|
||||||
<ListItemText
|
|
||||||
primary="Mastodon version"
|
|
||||||
secondary={
|
|
||||||
this.state.instance ? this.state.instance.version : "x.x.x"
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</ListItem>
|
|
||||||
</List>
|
</List>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
<br />
|
|
||||||
|
|
||||||
<Paper>
|
|
||||||
<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>
|
|
||||||
<ListItemAvatar>
|
|
||||||
<LinkableAvatar
|
|
||||||
to={`/profile/${
|
|
||||||
this.state.hyperspaceAdmin
|
|
||||||
? this.state.hyperspaceAdmin.id
|
|
||||||
: 0
|
|
||||||
}`}
|
|
||||||
src={
|
|
||||||
this.state.hyperspaceAdmin
|
|
||||||
? this.state.hyperspaceAdmin.avatar_static
|
|
||||||
: ""
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<PersonIcon />
|
|
||||||
</LinkableAvatar>
|
|
||||||
</ListItemAvatar>
|
|
||||||
<ListItemText
|
|
||||||
primary="App provider"
|
|
||||||
secondary={
|
|
||||||
this.state.hyperspaceAdmin && this.state.hyperspaceAdminName
|
|
||||||
? this.state.hyperspaceAdminName ||
|
|
||||||
this.state.hyperspaceAdmin.display_name ||
|
|
||||||
"@" + this.state.hyperspaceAdmin.acct
|
|
||||||
: "No provider set in config"
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<ListItemSecondaryAction>
|
|
||||||
<Tooltip title="Send a post or message">
|
|
||||||
<LinkableIconButton
|
|
||||||
to={`/compose?visibility=${
|
|
||||||
this.state.federated ? "public" : "private"
|
|
||||||
}&acct=${
|
|
||||||
this.state.hyperspaceAdmin
|
|
||||||
? this.state.hyperspaceAdmin.acct
|
|
||||||
: ""
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<ChatIcon />
|
|
||||||
</LinkableIconButton>
|
|
||||||
</Tooltip>
|
|
||||||
<Tooltip title="View profile">
|
|
||||||
<LinkableIconButton
|
|
||||||
to={`/profile/${
|
|
||||||
this.state.hyperspaceAdmin
|
|
||||||
? this.state.hyperspaceAdmin.id
|
|
||||||
: 0
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<AssignmentIndIcon />
|
|
||||||
</LinkableIconButton>
|
|
||||||
</Tooltip>
|
|
||||||
</ListItemSecondaryAction>
|
|
||||||
</ListItem>
|
|
||||||
<ListItem>
|
|
||||||
<ListItemAvatar>
|
|
||||||
<Avatar>
|
|
||||||
<NotesIcon />
|
|
||||||
</Avatar>
|
|
||||||
</ListItemAvatar>
|
|
||||||
<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>
|
|
||||||
<ListItemAvatar>
|
|
||||||
<Avatar>
|
|
||||||
<UpdateIcon />
|
|
||||||
</Avatar>
|
|
||||||
</ListItemAvatar>
|
|
||||||
<ListItemText
|
|
||||||
primary="Release channel"
|
|
||||||
secondary={
|
|
||||||
this.state
|
|
||||||
? this.state.developer
|
|
||||||
? "Developer"
|
|
||||||
: "Release"
|
|
||||||
: "Loading..."
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</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 />
|
<br />
|
||||||
<ListSubheader>Federation status</ListSubheader>
|
<ListSubheader>Federation status</ListSubheader>
|
||||||
<Paper>
|
<Paper>
|
||||||
|
|
|
@ -1,49 +1,49 @@
|
||||||
import { Theme, createStyles } from "@material-ui/core";
|
import { Theme, createStyles } from "@material-ui/core";
|
||||||
|
|
||||||
export const styles = (theme: Theme) =>
|
export const styles = (theme: Theme) =>
|
||||||
createStyles({
|
createStyles({
|
||||||
dialog: {
|
dialog: {
|
||||||
minHeight: 400
|
minHeight: 400
|
||||||
},
|
},
|
||||||
dialogContent: {
|
dialogContent: {
|
||||||
paddingBottom: 0
|
paddingBottom: 0
|
||||||
},
|
},
|
||||||
dialogActions: {
|
dialogActions: {
|
||||||
paddingLeft: theme.spacing.unit * 1.25
|
paddingLeft: theme.spacing.unit * 1.25
|
||||||
},
|
},
|
||||||
charsReachingLimit: {
|
charsReachingLimit: {
|
||||||
color: theme.palette.error.main
|
color: theme.palette.error.main
|
||||||
},
|
},
|
||||||
warningCaption: {
|
warningCaption: {
|
||||||
height: 16,
|
height: 16,
|
||||||
verticalAlign: "text-bottom"
|
verticalAlign: "text-bottom"
|
||||||
},
|
},
|
||||||
composeAttachmentArea: {
|
composeAttachmentArea: {
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexWrap: "wrap",
|
flexWrap: "wrap",
|
||||||
justifyContent: "space-around",
|
justifyContent: "space-around",
|
||||||
overflow: "hidden"
|
overflow: "hidden"
|
||||||
},
|
},
|
||||||
composeAttachmentAreaGridList: {
|
composeAttachmentAreaGridList: {
|
||||||
height: 250,
|
height: 250,
|
||||||
width: "100%"
|
width: "100%"
|
||||||
},
|
},
|
||||||
composeEmoji: {
|
composeEmoji: {
|
||||||
marginTop: theme.spacing.unit * 8
|
marginTop: theme.spacing.unit * 8
|
||||||
},
|
},
|
||||||
desktopOnly: {
|
desktopOnly: {
|
||||||
display: "none",
|
display: "none",
|
||||||
[theme.breakpoints.up("sm")]: {
|
[theme.breakpoints.up("sm")]: {
|
||||||
display: "block"
|
display: "block"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
pollWizardOptionIcon: {
|
pollWizardOptionIcon: {
|
||||||
marginRight: theme.spacing.unit * 2,
|
marginRight: theme.spacing.unit * 2,
|
||||||
marginTop: 4,
|
marginTop: 4,
|
||||||
marginBottom: 4,
|
marginBottom: 4,
|
||||||
color: theme.palette.grey[700]
|
color: theme.palette.grey[700]
|
||||||
},
|
},
|
||||||
pollWizardFlexGrow: {
|
pollWizardFlexGrow: {
|
||||||
flexGrow: 1
|
flexGrow: 1
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,9 +1,9 @@
|
||||||
import React, { Component } from "react";
|
import React, { Component } from "react";
|
||||||
import {
|
import {
|
||||||
withStyles,
|
withStyles,
|
||||||
CircularProgress,
|
CircularProgress,
|
||||||
Typography,
|
Typography,
|
||||||
Paper
|
Paper
|
||||||
} from "@material-ui/core";
|
} from "@material-ui/core";
|
||||||
import { styles } from "./PageLayout.styles";
|
import { styles } from "./PageLayout.styles";
|
||||||
import Post from "../components/Post";
|
import Post from "../components/Post";
|
||||||
|
@ -13,139 +13,152 @@ import Mastodon from "megalodon";
|
||||||
import { withSnackbar } from "notistack";
|
import { withSnackbar } from "notistack";
|
||||||
|
|
||||||
interface IConversationPageState {
|
interface IConversationPageState {
|
||||||
posts?: [Status];
|
posts?: [Status];
|
||||||
viewIsLoading: boolean;
|
viewIsLoading: boolean;
|
||||||
viewDidLoad?: boolean;
|
viewDidLoad?: boolean;
|
||||||
viewDidError?: boolean;
|
viewDidError?: boolean;
|
||||||
viewDidErrorCode?: any;
|
viewDidErrorCode?: any;
|
||||||
conversationId: string;
|
conversationId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
class Conversation extends Component<any, IConversationPageState> {
|
class Conversation extends Component<any, IConversationPageState> {
|
||||||
client: Mastodon;
|
client: Mastodon;
|
||||||
streamListener: any;
|
streamListener: any;
|
||||||
|
|
||||||
constructor(props: any) {
|
constructor(props: any) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
viewIsLoading: true,
|
viewIsLoading: true,
|
||||||
conversationId: props.match.params.conversationId
|
conversationId: props.match.params.conversationId
|
||||||
};
|
};
|
||||||
|
|
||||||
this.client = new Mastodon(
|
this.client = new Mastodon(
|
||||||
localStorage.getItem("access_token") as string,
|
localStorage.getItem("access_token") as string,
|
||||||
(localStorage.getItem("baseurl") as string) + "/api/v1"
|
(localStorage.getItem("baseurl") as string) + "/api/v1"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getContext() {
|
getContext() {
|
||||||
this.client
|
this.client
|
||||||
.get(`/statuses/${this.state.conversationId}`)
|
.get(`/statuses/${this.state.conversationId}`)
|
||||||
.then((resp: any) => {
|
.then((resp: any) => {
|
||||||
let result: Status = resp.data;
|
let result: Status = resp.data;
|
||||||
this.setState({ posts: [result] });
|
this.setState({ posts: [result] });
|
||||||
})
|
})
|
||||||
.catch((err: Error) => {
|
.catch((err: Error) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
viewIsLoading: false,
|
viewIsLoading: false,
|
||||||
viewDidError: true,
|
viewDidError: true,
|
||||||
viewDidErrorCode: err.message
|
viewDidErrorCode: err.message
|
||||||
});
|
});
|
||||||
this.props.enqueueSnackbar("Couldn't get conversation: " + err.name, {
|
this.props.enqueueSnackbar(
|
||||||
variant: "error"
|
"Couldn't get conversation: " + err.name,
|
||||||
});
|
{ variant: "error" }
|
||||||
});
|
);
|
||||||
this.client
|
});
|
||||||
.get(`/statuses/${this.state.conversationId}/context`)
|
this.client
|
||||||
.then((resp: any) => {
|
.get(`/statuses/${this.state.conversationId}/context`)
|
||||||
let context: Context = resp.data;
|
.then((resp: any) => {
|
||||||
let posts = this.state.posts;
|
let context: Context = resp.data;
|
||||||
let array: any[] = [];
|
let posts = this.state.posts;
|
||||||
if (posts) {
|
let array: any[] = [];
|
||||||
array = array
|
if (posts) {
|
||||||
.concat(context.ancestors)
|
array = array
|
||||||
.concat(posts)
|
.concat(context.ancestors)
|
||||||
.concat(context.descendants);
|
.concat(posts)
|
||||||
|
.concat(context.descendants);
|
||||||
|
}
|
||||||
|
this.setState({
|
||||||
|
posts: array as [Status],
|
||||||
|
viewIsLoading: false,
|
||||||
|
viewDidLoad: true,
|
||||||
|
viewDidError: false
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((err: Error) => {
|
||||||
|
this.setState({
|
||||||
|
viewIsLoading: false,
|
||||||
|
viewDidError: true,
|
||||||
|
viewDidErrorCode: err.message
|
||||||
|
});
|
||||||
|
this.props.enqueueSnackbar(
|
||||||
|
"Couldn't get conversation: " + err.name,
|
||||||
|
{ variant: "error" }
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps(props: any) {
|
||||||
|
if (props.match.params.conversationId !== this.state.conversationId) {
|
||||||
|
this.getContext();
|
||||||
}
|
}
|
||||||
this.setState({
|
|
||||||
posts: array as [Status],
|
|
||||||
viewIsLoading: false,
|
|
||||||
viewDidLoad: true,
|
|
||||||
viewDidError: false
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch((err: Error) => {
|
|
||||||
this.setState({
|
|
||||||
viewIsLoading: false,
|
|
||||||
viewDidError: true,
|
|
||||||
viewDidErrorCode: err.message
|
|
||||||
});
|
|
||||||
this.props.enqueueSnackbar("Couldn't get conversation: " + err.name, {
|
|
||||||
variant: "error"
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillReceiveProps(props: any) {
|
|
||||||
if (props.match.params.conversationId !== this.state.conversationId) {
|
|
||||||
this.getContext();
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
this.getContext();
|
this.getContext();
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate() {
|
|
||||||
const where: HTMLElement | null = document.getElementById(
|
|
||||||
`post_${this.state.conversationId}`
|
|
||||||
);
|
|
||||||
if (
|
|
||||||
where &&
|
|
||||||
this.state.posts &&
|
|
||||||
this.state.posts[0].id !== this.state.conversationId
|
|
||||||
) {
|
|
||||||
window.scrollTo(0, where.getBoundingClientRect().top);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
componentDidUpdate() {
|
||||||
const { classes } = this.props;
|
const where: HTMLElement | null = document.getElementById(
|
||||||
return (
|
`post_${this.state.conversationId}`
|
||||||
<div className={classes.pageLayoutMaxConstraints}>
|
);
|
||||||
{this.state.posts ? (
|
if (
|
||||||
<div>
|
where &&
|
||||||
{this.state.posts.map((post: Status) => {
|
this.state.posts &&
|
||||||
return <Post key={post.id} post={post} client={this.client} />;
|
this.state.posts[0].id !== this.state.conversationId
|
||||||
})}
|
) {
|
||||||
</div>
|
window.scrollTo(0, where.getBoundingClientRect().top);
|
||||||
) : (
|
}
|
||||||
<span />
|
}
|
||||||
)}
|
|
||||||
{this.state.viewDidError ? (
|
render() {
|
||||||
<Paper className={classes.errorCard}>
|
const { classes } = this.props;
|
||||||
<Typography variant="h4">Bummer.</Typography>
|
return (
|
||||||
<Typography variant="h6">
|
<div className={classes.pageLayoutMaxConstraints}>
|
||||||
Something went wrong when loading this conversation.
|
{this.state.posts ? (
|
||||||
</Typography>
|
<div>
|
||||||
<Typography>
|
{this.state.posts.map((post: Status) => {
|
||||||
{this.state.viewDidErrorCode ? this.state.viewDidErrorCode : ""}
|
return (
|
||||||
</Typography>
|
<Post
|
||||||
</Paper>
|
key={post.id}
|
||||||
) : (
|
post={post}
|
||||||
<span />
|
client={this.client}
|
||||||
)}
|
/>
|
||||||
{this.state.viewIsLoading ? (
|
);
|
||||||
<div style={{ textAlign: "center" }}>
|
})}
|
||||||
<CircularProgress className={classes.progress} color="primary" />
|
</div>
|
||||||
</div>
|
) : (
|
||||||
) : (
|
<span />
|
||||||
<span />
|
)}
|
||||||
)}
|
{this.state.viewDidError ? (
|
||||||
</div>
|
<Paper className={classes.errorCard}>
|
||||||
);
|
<Typography variant="h4">Bummer.</Typography>
|
||||||
}
|
<Typography variant="h6">
|
||||||
|
Something went wrong when loading this conversation.
|
||||||
|
</Typography>
|
||||||
|
<Typography>
|
||||||
|
{this.state.viewDidErrorCode
|
||||||
|
? this.state.viewDidErrorCode
|
||||||
|
: ""}
|
||||||
|
</Typography>
|
||||||
|
</Paper>
|
||||||
|
) : (
|
||||||
|
<span />
|
||||||
|
)}
|
||||||
|
{this.state.viewIsLoading ? (
|
||||||
|
<div style={{ textAlign: "center" }}>
|
||||||
|
<CircularProgress
|
||||||
|
className={classes.progress}
|
||||||
|
color="primary"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<span />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withStyles(styles)(withSnackbar(Conversation));
|
export default withStyles(styles)(withSnackbar(Conversation));
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import React, { Component } from "react";
|
import React, { Component } from "react";
|
||||||
import {
|
import {
|
||||||
withStyles,
|
withStyles,
|
||||||
CircularProgress,
|
CircularProgress,
|
||||||
Typography,
|
Typography,
|
||||||
Paper,
|
Paper,
|
||||||
Button,
|
Button,
|
||||||
Chip,
|
Chip,
|
||||||
Avatar,
|
Avatar,
|
||||||
Slide
|
Slide
|
||||||
} from "@material-ui/core";
|
} from "@material-ui/core";
|
||||||
import { styles } from "./PageLayout.styles";
|
import { styles } from "./PageLayout.styles";
|
||||||
import Post from "../components/Post";
|
import Post from "../components/Post";
|
||||||
|
@ -17,209 +17,224 @@ import { withSnackbar } from "notistack";
|
||||||
import ArrowUpwardIcon from "@material-ui/icons/ArrowUpward";
|
import ArrowUpwardIcon from "@material-ui/icons/ArrowUpward";
|
||||||
|
|
||||||
interface IHomePageState {
|
interface IHomePageState {
|
||||||
posts?: [Status];
|
posts?: [Status];
|
||||||
backlogPosts?: [Status] | null;
|
backlogPosts?: [Status] | null;
|
||||||
viewIsLoading: boolean;
|
viewIsLoading: boolean;
|
||||||
viewDidLoad?: boolean;
|
viewDidLoad?: boolean;
|
||||||
viewDidError?: boolean;
|
viewDidError?: boolean;
|
||||||
viewDidErrorCode?: any;
|
viewDidErrorCode?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
class HomePage extends Component<any, IHomePageState> {
|
class HomePage extends Component<any, IHomePageState> {
|
||||||
client: Mastodon;
|
client: Mastodon;
|
||||||
streamListener: StreamListener;
|
streamListener: StreamListener;
|
||||||
|
|
||||||
constructor(props: any) {
|
constructor(props: any) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
viewIsLoading: true,
|
viewIsLoading: true,
|
||||||
backlogPosts: null
|
backlogPosts: null
|
||||||
};
|
};
|
||||||
|
|
||||||
this.client = new Mastodon(
|
this.client = new Mastodon(
|
||||||
localStorage.getItem("access_token") as string,
|
localStorage.getItem("access_token") as string,
|
||||||
(localStorage.getItem("baseurl") as string) + "/api/v1"
|
(localStorage.getItem("baseurl") as string) + "/api/v1"
|
||||||
);
|
);
|
||||||
this.streamListener = this.client.stream("/streaming/user");
|
this.streamListener = this.client.stream("/streaming/user");
|
||||||
}
|
|
||||||
|
|
||||||
componentWillMount() {
|
|
||||||
this.streamListener.on("connect", () => {
|
|
||||||
this.client
|
|
||||||
.get("/timelines/home", { limit: 40 })
|
|
||||||
.then((resp: any) => {
|
|
||||||
let statuses: [Status] = resp.data;
|
|
||||||
this.setState({
|
|
||||||
posts: statuses,
|
|
||||||
viewIsLoading: false,
|
|
||||||
viewDidLoad: true,
|
|
||||||
viewDidError: false
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch((resp: any) => {
|
|
||||||
this.setState({
|
|
||||||
viewIsLoading: false,
|
|
||||||
viewDidLoad: true,
|
|
||||||
viewDidError: true,
|
|
||||||
viewDidErrorCode: String(resp)
|
|
||||||
});
|
|
||||||
this.props.enqueueSnackbar("Failed to get posts.", {
|
|
||||||
variant: "error"
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
this.streamListener.on("update", (status: Status) => {
|
|
||||||
let queue = this.state.backlogPosts;
|
|
||||||
if (queue !== null && queue !== undefined) {
|
|
||||||
queue.unshift(status);
|
|
||||||
} else {
|
|
||||||
queue = [status];
|
|
||||||
}
|
|
||||||
this.setState({ backlogPosts: queue });
|
|
||||||
});
|
|
||||||
|
|
||||||
this.streamListener.on("delete", (id: number) => {
|
|
||||||
let posts = this.state.posts;
|
|
||||||
if (posts) {
|
|
||||||
posts.forEach((post: Status) => {
|
|
||||||
if (posts && parseInt(post.id) === id) {
|
|
||||||
posts.splice(posts.indexOf(post), 1);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.setState({ posts });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.streamListener.on("error", (err: Error) => {
|
|
||||||
this.setState({
|
|
||||||
viewDidError: true,
|
|
||||||
viewDidErrorCode: err.message
|
|
||||||
});
|
|
||||||
this.props.enqueueSnackbar("An error occured.", {
|
|
||||||
variant: "error"
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
this.streamListener.on("heartbeat", () => {});
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
this.streamListener.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
insertBacklog() {
|
|
||||||
window.scrollTo(0, 0);
|
|
||||||
let posts = this.state.posts;
|
|
||||||
let backlog = this.state.backlogPosts;
|
|
||||||
if (posts && backlog && backlog.length > 0) {
|
|
||||||
let push = backlog.concat(posts);
|
|
||||||
this.setState({ posts: push as [Status], backlogPosts: null });
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
loadMoreTimelinePieces() {
|
componentWillMount() {
|
||||||
this.setState({ viewDidLoad: false, viewIsLoading: true });
|
this.streamListener.on("connect", () => {
|
||||||
if (this.state.posts) {
|
this.client
|
||||||
this.client
|
.get("/timelines/home", { limit: 40 })
|
||||||
.get("/timelines/home", {
|
.then((resp: any) => {
|
||||||
max_id: this.state.posts[this.state.posts.length - 1].id,
|
let statuses: [Status] = resp.data;
|
||||||
limit: 20
|
this.setState({
|
||||||
})
|
posts: statuses,
|
||||||
.then((resp: any) => {
|
viewIsLoading: false,
|
||||||
let newPosts: [Status] = resp.data;
|
viewDidLoad: true,
|
||||||
let posts = this.state.posts as [Status];
|
viewDidError: false
|
||||||
newPosts.forEach((post: Status) => {
|
});
|
||||||
posts.push(post);
|
})
|
||||||
});
|
.catch((resp: any) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
viewIsLoading: false,
|
viewIsLoading: false,
|
||||||
viewDidLoad: true,
|
viewDidLoad: true,
|
||||||
posts
|
viewDidError: true,
|
||||||
});
|
viewDidErrorCode: String(resp)
|
||||||
})
|
});
|
||||||
.catch((err: Error) => {
|
this.props.enqueueSnackbar("Failed to get posts.", {
|
||||||
this.setState({
|
variant: "error"
|
||||||
viewIsLoading: false,
|
});
|
||||||
viewDidError: true,
|
});
|
||||||
viewDidErrorCode: err.message
|
|
||||||
});
|
|
||||||
this.props.enqueueSnackbar("Failed to get posts", {
|
|
||||||
variant: "error"
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.streamListener.on("update", (status: Status) => {
|
||||||
|
let queue = this.state.backlogPosts;
|
||||||
|
if (queue !== null && queue !== undefined) {
|
||||||
|
queue.unshift(status);
|
||||||
|
} else {
|
||||||
|
queue = [status];
|
||||||
|
}
|
||||||
|
this.setState({ backlogPosts: queue });
|
||||||
|
});
|
||||||
|
|
||||||
|
this.streamListener.on("delete", (id: number) => {
|
||||||
|
let posts = this.state.posts;
|
||||||
|
if (posts) {
|
||||||
|
posts.forEach((post: Status) => {
|
||||||
|
if (posts && parseInt(post.id) === id) {
|
||||||
|
posts.splice(posts.indexOf(post), 1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.setState({ posts });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.streamListener.on("error", (err: Error) => {
|
||||||
|
this.setState({
|
||||||
|
viewDidError: true,
|
||||||
|
viewDidErrorCode: err.message
|
||||||
|
});
|
||||||
|
this.props.enqueueSnackbar("An error occured.", {
|
||||||
|
variant: "error"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this.streamListener.on("heartbeat", () => {});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
componentWillUnmount() {
|
||||||
const { classes } = this.props;
|
this.streamListener.stop();
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
insertBacklog() {
|
||||||
<div className={classes.pageLayoutMaxConstraints}>
|
window.scrollTo(0, 0);
|
||||||
{this.state.backlogPosts ? (
|
let posts = this.state.posts;
|
||||||
<div className={classes.pageTopChipContainer}>
|
let backlog = this.state.backlogPosts;
|
||||||
<div className={classes.pageTopChips}>
|
if (posts && backlog && backlog.length > 0) {
|
||||||
<Slide direction="down" in={true}>
|
let push = backlog.concat(posts);
|
||||||
<Chip
|
this.setState({ posts: push as [Status], backlogPosts: null });
|
||||||
avatar={
|
}
|
||||||
<Avatar>
|
}
|
||||||
<ArrowUpwardIcon />
|
|
||||||
</Avatar>
|
loadMoreTimelinePieces() {
|
||||||
}
|
this.setState({ viewDidLoad: false, viewIsLoading: true });
|
||||||
label={`View ${this.state.backlogPosts.length} new post${
|
if (this.state.posts) {
|
||||||
this.state.backlogPosts.length > 1 ? "s" : ""
|
this.client
|
||||||
}`}
|
.get("/timelines/home", {
|
||||||
color="primary"
|
max_id: this.state.posts[this.state.posts.length - 1].id,
|
||||||
className={classes.pageTopChip}
|
limit: 20
|
||||||
onClick={() => this.insertBacklog()}
|
})
|
||||||
clickable
|
.then((resp: any) => {
|
||||||
/>
|
let newPosts: [Status] = resp.data;
|
||||||
</Slide>
|
let posts = this.state.posts as [Status];
|
||||||
|
newPosts.forEach((post: Status) => {
|
||||||
|
posts.push(post);
|
||||||
|
});
|
||||||
|
this.setState({
|
||||||
|
viewIsLoading: false,
|
||||||
|
viewDidLoad: true,
|
||||||
|
posts
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((err: Error) => {
|
||||||
|
this.setState({
|
||||||
|
viewIsLoading: false,
|
||||||
|
viewDidError: true,
|
||||||
|
viewDidErrorCode: err.message
|
||||||
|
});
|
||||||
|
this.props.enqueueSnackbar("Failed to get posts", {
|
||||||
|
variant: "error"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { classes } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classes.pageLayoutMaxConstraints}>
|
||||||
|
{this.state.backlogPosts ? (
|
||||||
|
<div className={classes.pageTopChipContainer}>
|
||||||
|
<div className={classes.pageTopChips}>
|
||||||
|
<Slide direction="down" in={true}>
|
||||||
|
<Chip
|
||||||
|
avatar={
|
||||||
|
<Avatar>
|
||||||
|
<ArrowUpwardIcon />
|
||||||
|
</Avatar>
|
||||||
|
}
|
||||||
|
label={`View ${
|
||||||
|
this.state.backlogPosts.length
|
||||||
|
} new post${
|
||||||
|
this.state.backlogPosts.length > 1
|
||||||
|
? "s"
|
||||||
|
: ""
|
||||||
|
}`}
|
||||||
|
color="primary"
|
||||||
|
className={classes.pageTopChip}
|
||||||
|
onClick={() => this.insertBacklog()}
|
||||||
|
clickable
|
||||||
|
/>
|
||||||
|
</Slide>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
{this.state.posts ? (
|
||||||
|
<div>
|
||||||
|
{this.state.posts.map((post: Status) => {
|
||||||
|
return (
|
||||||
|
<Post
|
||||||
|
key={post.id}
|
||||||
|
post={post}
|
||||||
|
client={this.client}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
<br />
|
||||||
|
{this.state.viewDidLoad && !this.state.viewDidError ? (
|
||||||
|
<div
|
||||||
|
style={{ textAlign: "center" }}
|
||||||
|
onClick={() => this.loadMoreTimelinePieces()}
|
||||||
|
>
|
||||||
|
<Button variant="contained">Load more</Button>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<span />
|
||||||
|
)}
|
||||||
|
{this.state.viewDidError ? (
|
||||||
|
<Paper className={classes.errorCard}>
|
||||||
|
<Typography variant="h4">Bummer.</Typography>
|
||||||
|
<Typography variant="h6">
|
||||||
|
Something went wrong when loading this timeline.
|
||||||
|
</Typography>
|
||||||
|
<Typography>
|
||||||
|
{this.state.viewDidErrorCode
|
||||||
|
? this.state.viewDidErrorCode
|
||||||
|
: ""}
|
||||||
|
</Typography>
|
||||||
|
</Paper>
|
||||||
|
) : (
|
||||||
|
<span />
|
||||||
|
)}
|
||||||
|
{this.state.viewIsLoading ? (
|
||||||
|
<div style={{ textAlign: "center" }}>
|
||||||
|
<CircularProgress
|
||||||
|
className={classes.progress}
|
||||||
|
color="primary"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<span />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
);
|
||||||
) : null}
|
}
|
||||||
{this.state.posts ? (
|
|
||||||
<div>
|
|
||||||
{this.state.posts.map((post: Status) => {
|
|
||||||
return <Post key={post.id} post={post} client={this.client} />;
|
|
||||||
})}
|
|
||||||
<br />
|
|
||||||
{this.state.viewDidLoad && !this.state.viewDidError ? (
|
|
||||||
<div
|
|
||||||
style={{ textAlign: "center" }}
|
|
||||||
onClick={() => this.loadMoreTimelinePieces()}
|
|
||||||
>
|
|
||||||
<Button variant="contained">Load more</Button>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<span />
|
|
||||||
)}
|
|
||||||
{this.state.viewDidError ? (
|
|
||||||
<Paper className={classes.errorCard}>
|
|
||||||
<Typography variant="h4">Bummer.</Typography>
|
|
||||||
<Typography variant="h6">
|
|
||||||
Something went wrong when loading this timeline.
|
|
||||||
</Typography>
|
|
||||||
<Typography>
|
|
||||||
{this.state.viewDidErrorCode ? this.state.viewDidErrorCode : ""}
|
|
||||||
</Typography>
|
|
||||||
</Paper>
|
|
||||||
) : (
|
|
||||||
<span />
|
|
||||||
)}
|
|
||||||
{this.state.viewIsLoading ? (
|
|
||||||
<div style={{ textAlign: "center" }}>
|
|
||||||
<CircularProgress className={classes.progress} color="primary" />
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<span />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withStyles(styles)(withSnackbar(HomePage));
|
export default withStyles(styles)(withSnackbar(HomePage));
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import React, { Component } from "react";
|
import React, { Component } from "react";
|
||||||
import {
|
import {
|
||||||
withStyles,
|
withStyles,
|
||||||
CircularProgress,
|
CircularProgress,
|
||||||
Typography,
|
Typography,
|
||||||
Paper,
|
Paper,
|
||||||
Button,
|
Button,
|
||||||
Chip,
|
Chip,
|
||||||
Avatar,
|
Avatar,
|
||||||
Slide
|
Slide
|
||||||
} from "@material-ui/core";
|
} from "@material-ui/core";
|
||||||
import { styles } from "./PageLayout.styles";
|
import { styles } from "./PageLayout.styles";
|
||||||
import Post from "../components/Post";
|
import Post from "../components/Post";
|
||||||
|
@ -17,210 +17,225 @@ import { withSnackbar } from "notistack";
|
||||||
import ArrowUpwardIcon from "@material-ui/icons/ArrowUpward";
|
import ArrowUpwardIcon from "@material-ui/icons/ArrowUpward";
|
||||||
|
|
||||||
interface ILocalPageState {
|
interface ILocalPageState {
|
||||||
posts?: [Status];
|
posts?: [Status];
|
||||||
backlogPosts?: [Status] | null;
|
backlogPosts?: [Status] | null;
|
||||||
viewIsLoading: boolean;
|
viewIsLoading: boolean;
|
||||||
viewDidLoad?: boolean;
|
viewDidLoad?: boolean;
|
||||||
viewDidError?: boolean;
|
viewDidError?: boolean;
|
||||||
viewDidErrorCode?: any;
|
viewDidErrorCode?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
class LocalPage extends Component<any, ILocalPageState> {
|
class LocalPage extends Component<any, ILocalPageState> {
|
||||||
client: Mastodon;
|
client: Mastodon;
|
||||||
streamListener: StreamListener;
|
streamListener: StreamListener;
|
||||||
|
|
||||||
constructor(props: any) {
|
constructor(props: any) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
viewIsLoading: true,
|
viewIsLoading: true,
|
||||||
backlogPosts: null
|
backlogPosts: null
|
||||||
};
|
};
|
||||||
|
|
||||||
this.client = new Mastodon(
|
this.client = new Mastodon(
|
||||||
localStorage.getItem("access_token") as string,
|
localStorage.getItem("access_token") as string,
|
||||||
(localStorage.getItem("baseurl") as string) + "/api/v1"
|
(localStorage.getItem("baseurl") as string) + "/api/v1"
|
||||||
);
|
);
|
||||||
this.streamListener = this.client.stream("/streaming/public/local");
|
this.streamListener = this.client.stream("/streaming/public/local");
|
||||||
}
|
|
||||||
|
|
||||||
componentWillMount() {
|
|
||||||
this.streamListener.on("connect", () => {
|
|
||||||
this.client
|
|
||||||
.get("/timelines/public", { limit: 40, local: true })
|
|
||||||
.then((resp: any) => {
|
|
||||||
let statuses: [Status] = resp.data;
|
|
||||||
this.setState({
|
|
||||||
posts: statuses,
|
|
||||||
viewIsLoading: false,
|
|
||||||
viewDidLoad: true,
|
|
||||||
viewDidError: false
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch((resp: any) => {
|
|
||||||
this.setState({
|
|
||||||
viewIsLoading: false,
|
|
||||||
viewDidLoad: true,
|
|
||||||
viewDidError: true,
|
|
||||||
viewDidErrorCode: String(resp)
|
|
||||||
});
|
|
||||||
this.props.enqueueSnackbar("Failed to get posts.", {
|
|
||||||
variant: "error"
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
this.streamListener.on("update", (status: Status) => {
|
|
||||||
let queue = this.state.backlogPosts;
|
|
||||||
if (queue !== null && queue !== undefined) {
|
|
||||||
queue.unshift(status);
|
|
||||||
} else {
|
|
||||||
queue = [status];
|
|
||||||
}
|
|
||||||
this.setState({ backlogPosts: queue });
|
|
||||||
});
|
|
||||||
|
|
||||||
this.streamListener.on("delete", (id: number) => {
|
|
||||||
let posts = this.state.posts;
|
|
||||||
if (posts) {
|
|
||||||
posts.forEach((post: Status) => {
|
|
||||||
if (posts && parseInt(post.id) === id) {
|
|
||||||
posts.splice(posts.indexOf(post), 1);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.setState({ posts });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.streamListener.on("error", (err: Error) => {
|
|
||||||
this.setState({
|
|
||||||
viewDidError: true,
|
|
||||||
viewDidErrorCode: err.message
|
|
||||||
});
|
|
||||||
this.props.enqueueSnackbar("An error occured.", {
|
|
||||||
variant: "error"
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
this.streamListener.on("heartbeat", () => {});
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
this.streamListener.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
insertBacklog() {
|
|
||||||
window.scrollTo(0, 0);
|
|
||||||
let posts = this.state.posts;
|
|
||||||
let backlog = this.state.backlogPosts;
|
|
||||||
if (posts && backlog && backlog.length > 0) {
|
|
||||||
let push = backlog.concat(posts);
|
|
||||||
this.setState({ posts: push as [Status], backlogPosts: null });
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
loadMoreTimelinePieces() {
|
componentWillMount() {
|
||||||
this.setState({ viewDidLoad: false, viewIsLoading: true });
|
this.streamListener.on("connect", () => {
|
||||||
if (this.state.posts) {
|
this.client
|
||||||
this.client
|
.get("/timelines/public", { limit: 40, local: true })
|
||||||
.get("/timelines/public", {
|
.then((resp: any) => {
|
||||||
max_id: this.state.posts[this.state.posts.length - 1].id,
|
let statuses: [Status] = resp.data;
|
||||||
limit: 20,
|
this.setState({
|
||||||
local: true
|
posts: statuses,
|
||||||
})
|
viewIsLoading: false,
|
||||||
.then((resp: any) => {
|
viewDidLoad: true,
|
||||||
let newPosts: [Status] = resp.data;
|
viewDidError: false
|
||||||
let posts = this.state.posts as [Status];
|
});
|
||||||
newPosts.forEach((post: Status) => {
|
})
|
||||||
posts.push(post);
|
.catch((resp: any) => {
|
||||||
});
|
this.setState({
|
||||||
this.setState({
|
viewIsLoading: false,
|
||||||
viewIsLoading: false,
|
viewDidLoad: true,
|
||||||
viewDidLoad: true,
|
viewDidError: true,
|
||||||
posts
|
viewDidErrorCode: String(resp)
|
||||||
});
|
});
|
||||||
})
|
this.props.enqueueSnackbar("Failed to get posts.", {
|
||||||
.catch((err: Error) => {
|
variant: "error"
|
||||||
this.setState({
|
});
|
||||||
viewIsLoading: false,
|
});
|
||||||
viewDidError: true,
|
|
||||||
viewDidErrorCode: err.message
|
|
||||||
});
|
|
||||||
this.props.enqueueSnackbar("Failed to get posts", {
|
|
||||||
variant: "error"
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.streamListener.on("update", (status: Status) => {
|
||||||
|
let queue = this.state.backlogPosts;
|
||||||
|
if (queue !== null && queue !== undefined) {
|
||||||
|
queue.unshift(status);
|
||||||
|
} else {
|
||||||
|
queue = [status];
|
||||||
|
}
|
||||||
|
this.setState({ backlogPosts: queue });
|
||||||
|
});
|
||||||
|
|
||||||
|
this.streamListener.on("delete", (id: number) => {
|
||||||
|
let posts = this.state.posts;
|
||||||
|
if (posts) {
|
||||||
|
posts.forEach((post: Status) => {
|
||||||
|
if (posts && parseInt(post.id) === id) {
|
||||||
|
posts.splice(posts.indexOf(post), 1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.setState({ posts });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.streamListener.on("error", (err: Error) => {
|
||||||
|
this.setState({
|
||||||
|
viewDidError: true,
|
||||||
|
viewDidErrorCode: err.message
|
||||||
|
});
|
||||||
|
this.props.enqueueSnackbar("An error occured.", {
|
||||||
|
variant: "error"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this.streamListener.on("heartbeat", () => {});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
componentWillUnmount() {
|
||||||
const { classes } = this.props;
|
this.streamListener.stop();
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
insertBacklog() {
|
||||||
<div className={classes.pageLayoutMaxConstraints}>
|
window.scrollTo(0, 0);
|
||||||
{this.state.backlogPosts ? (
|
let posts = this.state.posts;
|
||||||
<div className={classes.pageTopChipContainer}>
|
let backlog = this.state.backlogPosts;
|
||||||
<div className={classes.pageTopChips}>
|
if (posts && backlog && backlog.length > 0) {
|
||||||
<Slide direction="down" in={true}>
|
let push = backlog.concat(posts);
|
||||||
<Chip
|
this.setState({ posts: push as [Status], backlogPosts: null });
|
||||||
avatar={
|
}
|
||||||
<Avatar>
|
}
|
||||||
<ArrowUpwardIcon />
|
|
||||||
</Avatar>
|
loadMoreTimelinePieces() {
|
||||||
}
|
this.setState({ viewDidLoad: false, viewIsLoading: true });
|
||||||
label={`View ${this.state.backlogPosts.length} new post${
|
if (this.state.posts) {
|
||||||
this.state.backlogPosts.length > 1 ? "s" : ""
|
this.client
|
||||||
}`}
|
.get("/timelines/public", {
|
||||||
color="primary"
|
max_id: this.state.posts[this.state.posts.length - 1].id,
|
||||||
className={classes.pageTopChip}
|
limit: 20,
|
||||||
onClick={() => this.insertBacklog()}
|
local: true
|
||||||
clickable
|
})
|
||||||
/>
|
.then((resp: any) => {
|
||||||
</Slide>
|
let newPosts: [Status] = resp.data;
|
||||||
|
let posts = this.state.posts as [Status];
|
||||||
|
newPosts.forEach((post: Status) => {
|
||||||
|
posts.push(post);
|
||||||
|
});
|
||||||
|
this.setState({
|
||||||
|
viewIsLoading: false,
|
||||||
|
viewDidLoad: true,
|
||||||
|
posts
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((err: Error) => {
|
||||||
|
this.setState({
|
||||||
|
viewIsLoading: false,
|
||||||
|
viewDidError: true,
|
||||||
|
viewDidErrorCode: err.message
|
||||||
|
});
|
||||||
|
this.props.enqueueSnackbar("Failed to get posts", {
|
||||||
|
variant: "error"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { classes } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classes.pageLayoutMaxConstraints}>
|
||||||
|
{this.state.backlogPosts ? (
|
||||||
|
<div className={classes.pageTopChipContainer}>
|
||||||
|
<div className={classes.pageTopChips}>
|
||||||
|
<Slide direction="down" in={true}>
|
||||||
|
<Chip
|
||||||
|
avatar={
|
||||||
|
<Avatar>
|
||||||
|
<ArrowUpwardIcon />
|
||||||
|
</Avatar>
|
||||||
|
}
|
||||||
|
label={`View ${
|
||||||
|
this.state.backlogPosts.length
|
||||||
|
} new post${
|
||||||
|
this.state.backlogPosts.length > 1
|
||||||
|
? "s"
|
||||||
|
: ""
|
||||||
|
}`}
|
||||||
|
color="primary"
|
||||||
|
className={classes.pageTopChip}
|
||||||
|
onClick={() => this.insertBacklog()}
|
||||||
|
clickable
|
||||||
|
/>
|
||||||
|
</Slide>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
{this.state.posts ? (
|
||||||
|
<div>
|
||||||
|
{this.state.posts.map((post: Status) => {
|
||||||
|
return (
|
||||||
|
<Post
|
||||||
|
key={post.id}
|
||||||
|
post={post}
|
||||||
|
client={this.client}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
<br />
|
||||||
|
{this.state.viewDidLoad && !this.state.viewDidError ? (
|
||||||
|
<div
|
||||||
|
style={{ textAlign: "center" }}
|
||||||
|
onClick={() => this.loadMoreTimelinePieces()}
|
||||||
|
>
|
||||||
|
<Button variant="contained">Load more</Button>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<span />
|
||||||
|
)}
|
||||||
|
{this.state.viewDidError ? (
|
||||||
|
<Paper className={classes.errorCard}>
|
||||||
|
<Typography variant="h4">Bummer.</Typography>
|
||||||
|
<Typography variant="h6">
|
||||||
|
Something went wrong when loading this timeline.
|
||||||
|
</Typography>
|
||||||
|
<Typography>
|
||||||
|
{this.state.viewDidErrorCode
|
||||||
|
? this.state.viewDidErrorCode
|
||||||
|
: ""}
|
||||||
|
</Typography>
|
||||||
|
</Paper>
|
||||||
|
) : (
|
||||||
|
<span />
|
||||||
|
)}
|
||||||
|
{this.state.viewIsLoading ? (
|
||||||
|
<div style={{ textAlign: "center" }}>
|
||||||
|
<CircularProgress
|
||||||
|
className={classes.progress}
|
||||||
|
color="primary"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<span />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
);
|
||||||
) : null}
|
}
|
||||||
{this.state.posts ? (
|
|
||||||
<div>
|
|
||||||
{this.state.posts.map((post: Status) => {
|
|
||||||
return <Post key={post.id} post={post} client={this.client} />;
|
|
||||||
})}
|
|
||||||
<br />
|
|
||||||
{this.state.viewDidLoad && !this.state.viewDidError ? (
|
|
||||||
<div
|
|
||||||
style={{ textAlign: "center" }}
|
|
||||||
onClick={() => this.loadMoreTimelinePieces()}
|
|
||||||
>
|
|
||||||
<Button variant="contained">Load more</Button>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<span />
|
|
||||||
)}
|
|
||||||
{this.state.viewDidError ? (
|
|
||||||
<Paper className={classes.errorCard}>
|
|
||||||
<Typography variant="h4">Bummer.</Typography>
|
|
||||||
<Typography variant="h6">
|
|
||||||
Something went wrong when loading this timeline.
|
|
||||||
</Typography>
|
|
||||||
<Typography>
|
|
||||||
{this.state.viewDidErrorCode ? this.state.viewDidErrorCode : ""}
|
|
||||||
</Typography>
|
|
||||||
</Paper>
|
|
||||||
) : (
|
|
||||||
<span />
|
|
||||||
)}
|
|
||||||
{this.state.viewIsLoading ? (
|
|
||||||
<div style={{ textAlign: "center" }}>
|
|
||||||
<CircularProgress className={classes.progress} color="primary" />
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<span />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withStyles(styles)(withSnackbar(LocalPage));
|
export default withStyles(styles)(withSnackbar(LocalPage));
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
import React, { Component } from "react";
|
import React, { Component } from "react";
|
||||||
import {
|
import {
|
||||||
withStyles,
|
withStyles,
|
||||||
ListSubheader,
|
ListSubheader,
|
||||||
Paper,
|
Paper,
|
||||||
List,
|
List,
|
||||||
ListItem,
|
ListItem,
|
||||||
ListItemText,
|
ListItemText,
|
||||||
CircularProgress,
|
CircularProgress,
|
||||||
ListItemAvatar,
|
ListItemAvatar,
|
||||||
Avatar,
|
Avatar,
|
||||||
ListItemSecondaryAction,
|
ListItemSecondaryAction,
|
||||||
Tooltip
|
Tooltip
|
||||||
} from "@material-ui/core";
|
} from "@material-ui/core";
|
||||||
import PersonIcon from "@material-ui/icons/Person";
|
import PersonIcon from "@material-ui/icons/Person";
|
||||||
import ForumIcon from "@material-ui/icons/Forum";
|
import ForumIcon from "@material-ui/icons/Forum";
|
||||||
|
@ -20,111 +20,130 @@ import { Status } from "../types/Status";
|
||||||
import { LinkableIconButton, LinkableAvatar } from "../interfaces/overrides";
|
import { LinkableIconButton, LinkableAvatar } from "../interfaces/overrides";
|
||||||
|
|
||||||
interface IMessagesState {
|
interface IMessagesState {
|
||||||
posts?: [Status];
|
posts?: [Status];
|
||||||
viewIsLoading: boolean;
|
viewIsLoading: boolean;
|
||||||
viewDidLoad?: boolean;
|
viewDidLoad?: boolean;
|
||||||
viewDidError?: boolean;
|
viewDidError?: boolean;
|
||||||
viewDidErrorCode?: any;
|
viewDidErrorCode?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
class MessagesPage extends Component<any, IMessagesState> {
|
class MessagesPage extends Component<any, IMessagesState> {
|
||||||
client: Mastodon;
|
client: Mastodon;
|
||||||
|
|
||||||
constructor(props: any) {
|
constructor(props: any) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.client = new Mastodon(
|
this.client = new Mastodon(
|
||||||
localStorage.getItem("access_token") as string,
|
localStorage.getItem("access_token") as string,
|
||||||
(localStorage.getItem("baseurl") as string) + "/api/v1"
|
(localStorage.getItem("baseurl") as string) + "/api/v1"
|
||||||
);
|
);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
viewIsLoading: true
|
viewIsLoading: true
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
this.client.get("/conversations").then(resp => {
|
this.client.get("/conversations").then(resp => {
|
||||||
let data: any = resp.data;
|
let data: any = resp.data;
|
||||||
let messages: any = [];
|
let messages: any = [];
|
||||||
|
|
||||||
data.forEach((message: any) => {
|
data.forEach((message: any) => {
|
||||||
if (message.last_status !== null) {
|
if (message.last_status !== null) {
|
||||||
messages.push(message.last_status);
|
messages.push(message.last_status);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
posts: messages,
|
posts: messages,
|
||||||
viewIsLoading: false,
|
viewIsLoading: false,
|
||||||
viewDidLoad: true
|
viewDidLoad: true
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
removeHTMLContent(text: string) {
|
removeHTMLContent(text: string) {
|
||||||
const div = document.createElement("div");
|
const div = document.createElement("div");
|
||||||
div.innerHTML = text;
|
div.innerHTML = text;
|
||||||
let innerContent = div.textContent || div.innerText || "";
|
let innerContent = div.textContent || div.innerText || "";
|
||||||
innerContent = innerContent.slice(0, 100) + "...";
|
innerContent = innerContent.slice(0, 100) + "...";
|
||||||
return innerContent;
|
return innerContent;
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { classes } = this.props;
|
const { classes } = this.props;
|
||||||
return (
|
return (
|
||||||
<div className={classes.pageLayoutConstraints}>
|
<div className={classes.pageLayoutConstraints}>
|
||||||
{this.state.viewDidLoad ? (
|
{this.state.viewDidLoad ? (
|
||||||
<div className={classes.pageListContsraints}>
|
<div className={classes.pageListContsraints}>
|
||||||
<ListSubheader>Recent messages</ListSubheader>
|
<ListSubheader>Recent messages</ListSubheader>
|
||||||
<Paper className={classes.pageListConstraints}>
|
<Paper className={classes.pageListConstraints}>
|
||||||
<List>
|
<List>
|
||||||
{this.state.posts
|
{this.state.posts
|
||||||
? this.state.posts.map((message: Status) => {
|
? this.state.posts.map(
|
||||||
return (
|
(message: Status) => {
|
||||||
<ListItem>
|
return (
|
||||||
<ListItemAvatar>
|
<ListItem>
|
||||||
<LinkableAvatar
|
<ListItemAvatar>
|
||||||
to={`/profile/${message.account.id}`}
|
<LinkableAvatar
|
||||||
alt={message.account.username}
|
to={`/profile/${message.account.id}`}
|
||||||
src={message.account.avatar_static}
|
alt={
|
||||||
>
|
message
|
||||||
<PersonIcon />
|
.account
|
||||||
</LinkableAvatar>
|
.username
|
||||||
</ListItemAvatar>
|
}
|
||||||
<ListItemText
|
src={
|
||||||
primary={
|
message
|
||||||
message.account.display_name ||
|
.account
|
||||||
"@" + message.account.acct
|
.avatar_static
|
||||||
}
|
}
|
||||||
secondary={this.removeHTMLContent(message.content)}
|
>
|
||||||
/>
|
<PersonIcon />
|
||||||
<ListItemSecondaryAction>
|
</LinkableAvatar>
|
||||||
<Tooltip title="View conversation">
|
</ListItemAvatar>
|
||||||
<LinkableIconButton
|
<ListItemText
|
||||||
to={`/conversation/${message.id}`}
|
primary={
|
||||||
>
|
message.account
|
||||||
<ForumIcon />
|
.display_name ||
|
||||||
</LinkableIconButton>
|
"@" +
|
||||||
</Tooltip>
|
message
|
||||||
</ListItemSecondaryAction>
|
.account
|
||||||
</ListItem>
|
.acct
|
||||||
);
|
}
|
||||||
})
|
secondary={this.removeHTMLContent(
|
||||||
: null}
|
message.content
|
||||||
</List>
|
)}
|
||||||
</Paper>
|
/>
|
||||||
<br />
|
<ListItemSecondaryAction>
|
||||||
</div>
|
<Tooltip title="View conversation">
|
||||||
) : null}
|
<LinkableIconButton
|
||||||
{this.state.viewIsLoading ? (
|
to={`/conversation/${message.id}`}
|
||||||
<div style={{ textAlign: "center" }}>
|
>
|
||||||
<CircularProgress className={classes.progress} color="primary" />
|
<ForumIcon />
|
||||||
</div>
|
</LinkableIconButton>
|
||||||
) : null}
|
</Tooltip>
|
||||||
</div>
|
</ListItemSecondaryAction>
|
||||||
);
|
</ListItem>
|
||||||
}
|
);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
: null}
|
||||||
|
</List>
|
||||||
|
</Paper>
|
||||||
|
<br />
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
{this.state.viewIsLoading ? (
|
||||||
|
<div style={{ textAlign: "center" }}>
|
||||||
|
<CircularProgress
|
||||||
|
className={classes.progress}
|
||||||
|
color="primary"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withStyles(styles)(MessagesPage);
|
export default withStyles(styles)(MessagesPage);
|
||||||
|
|
|
@ -4,25 +4,29 @@ import { styles } from "./PageLayout.styles";
|
||||||
import { LinkableButton } from "../interfaces/overrides";
|
import { LinkableButton } from "../interfaces/overrides";
|
||||||
|
|
||||||
class Missingno extends Component<any, any> {
|
class Missingno extends Component<any, any> {
|
||||||
render() {
|
render() {
|
||||||
const { classes } = this.props;
|
const { classes } = this.props;
|
||||||
return (
|
return (
|
||||||
<div className={classes.pageLayoutConstraints}>
|
<div className={classes.pageLayoutConstraints}>
|
||||||
<div>
|
<div>
|
||||||
<Typography variant="h4" component="h1">
|
<Typography variant="h4" component="h1">
|
||||||
<b>Uh oh!</b>
|
<b>Uh oh!</b>
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="h6" component="p">
|
<Typography variant="h6" component="p">
|
||||||
The part of Hyperspace you're looking for isn't here.
|
The part of Hyperspace you're looking for isn't here.
|
||||||
</Typography>
|
</Typography>
|
||||||
<br />
|
<br />
|
||||||
<LinkableButton to="/home" color="primary" variant="contained">
|
<LinkableButton
|
||||||
Go back to home timeline
|
to="/home"
|
||||||
</LinkableButton>
|
color="primary"
|
||||||
</div>
|
variant="contained"
|
||||||
</div>
|
>
|
||||||
);
|
Go back to home timeline
|
||||||
}
|
</LinkableButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withStyles(styles)(Missingno);
|
export default withStyles(styles)(Missingno);
|
||||||
|
|
|
@ -1,23 +1,23 @@
|
||||||
import React, { Component } from "react";
|
import React, { Component } from "react";
|
||||||
import {
|
import {
|
||||||
List,
|
List,
|
||||||
ListItem,
|
ListItem,
|
||||||
ListItemText,
|
ListItemText,
|
||||||
ListSubheader,
|
ListSubheader,
|
||||||
ListItemSecondaryAction,
|
ListItemSecondaryAction,
|
||||||
ListItemAvatar,
|
ListItemAvatar,
|
||||||
Paper,
|
Paper,
|
||||||
IconButton,
|
IconButton,
|
||||||
withStyles,
|
withStyles,
|
||||||
Typography,
|
Typography,
|
||||||
CircularProgress,
|
CircularProgress,
|
||||||
Button,
|
Button,
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogContentText,
|
DialogContentText,
|
||||||
DialogActions,
|
DialogActions,
|
||||||
Tooltip
|
Tooltip
|
||||||
} from "@material-ui/core";
|
} from "@material-ui/core";
|
||||||
import AssignmentIndIcon from "@material-ui/icons/AssignmentInd";
|
import AssignmentIndIcon from "@material-ui/icons/AssignmentInd";
|
||||||
import PersonIcon from "@material-ui/icons/Person";
|
import PersonIcon from "@material-ui/icons/Person";
|
||||||
|
@ -33,344 +33,380 @@ import { Account } from "../types/Account";
|
||||||
import { withSnackbar } from "notistack";
|
import { withSnackbar } from "notistack";
|
||||||
|
|
||||||
interface INotificationsPageState {
|
interface INotificationsPageState {
|
||||||
notifications?: [Notification];
|
notifications?: [Notification];
|
||||||
viewIsLoading: boolean;
|
viewIsLoading: boolean;
|
||||||
viewDidLoad?: boolean;
|
viewDidLoad?: boolean;
|
||||||
viewDidError?: boolean;
|
viewDidError?: boolean;
|
||||||
viewDidErrorCode?: string;
|
viewDidErrorCode?: string;
|
||||||
deleteDialogOpen: boolean;
|
deleteDialogOpen: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
class NotificationsPage extends Component<any, INotificationsPageState> {
|
class NotificationsPage extends Component<any, INotificationsPageState> {
|
||||||
client: Mastodon;
|
client: Mastodon;
|
||||||
streamListener: any;
|
streamListener: any;
|
||||||
|
|
||||||
constructor(props: any) {
|
constructor(props: any) {
|
||||||
super(props);
|
super(props);
|
||||||
this.client = new Mastodon(
|
this.client = new Mastodon(
|
||||||
localStorage.getItem("access_token") as string,
|
localStorage.getItem("access_token") as string,
|
||||||
localStorage.getItem("baseurl") + "/api/v1"
|
localStorage.getItem("baseurl") + "/api/v1"
|
||||||
);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
viewIsLoading: true,
|
|
||||||
deleteDialogOpen: false
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillMount() {
|
|
||||||
this.client
|
|
||||||
.get("/notifications")
|
|
||||||
.then((resp: any) => {
|
|
||||||
let notifications: [Notification] = resp.data;
|
|
||||||
this.setState({
|
|
||||||
notifications,
|
|
||||||
viewIsLoading: false,
|
|
||||||
viewDidLoad: true
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch((err: Error) => {
|
|
||||||
this.setState({
|
|
||||||
viewDidLoad: true,
|
|
||||||
viewIsLoading: false,
|
|
||||||
viewDidError: true,
|
|
||||||
viewDidErrorCode: err.message
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
this.streamNotifications();
|
|
||||||
}
|
|
||||||
|
|
||||||
streamNotifications() {
|
|
||||||
this.streamListener = this.client.stream("/streaming/user");
|
|
||||||
|
|
||||||
this.streamListener.on("notification", (notif: Notification) => {
|
|
||||||
let notifications = this.state.notifications;
|
|
||||||
if (notifications) {
|
|
||||||
notifications.unshift(notif);
|
|
||||||
this.setState({ notifications });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleDeleteDialog() {
|
|
||||||
this.setState({ deleteDialogOpen: !this.state.deleteDialogOpen });
|
|
||||||
}
|
|
||||||
|
|
||||||
removeHTMLContent(text: string) {
|
|
||||||
const div = document.createElement("div");
|
|
||||||
div.innerHTML = text;
|
|
||||||
let innerContent = div.textContent || div.innerText || "";
|
|
||||||
if (innerContent.length > 65)
|
|
||||||
innerContent = innerContent.slice(0, 65) + "...";
|
|
||||||
return innerContent;
|
|
||||||
}
|
|
||||||
|
|
||||||
removeNotification(id: string) {
|
|
||||||
this.client
|
|
||||||
.post("/notifications/dismiss", { id: id })
|
|
||||||
.then((resp: any) => {
|
|
||||||
let notifications = this.state.notifications;
|
|
||||||
if (notifications !== undefined && notifications.length > 0) {
|
|
||||||
notifications.forEach((notification: Notification) => {
|
|
||||||
if (notifications !== undefined && notification.id === id) {
|
|
||||||
notifications.splice(notifications.indexOf(notification), 1);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
this.setState({ notifications });
|
|
||||||
this.props.enqueueSnackbar("Notification deleted.");
|
|
||||||
})
|
|
||||||
.catch((err: Error) => {
|
|
||||||
this.props.enqueueSnackbar(
|
|
||||||
"Couldn't delete notification: " + err.name,
|
|
||||||
{
|
|
||||||
variant: "error"
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
removeAllNotifications() {
|
this.state = {
|
||||||
this.client
|
viewIsLoading: true,
|
||||||
.post("/notifications/clear")
|
deleteDialogOpen: false
|
||||||
.then((resp: any) => {
|
};
|
||||||
this.setState({ notifications: undefined });
|
|
||||||
this.props.enqueueSnackbar("All notifications deleted.");
|
|
||||||
})
|
|
||||||
.catch((err: Error) => {
|
|
||||||
this.props.enqueueSnackbar(
|
|
||||||
"Couldn't delete notifications: " + err.name,
|
|
||||||
{
|
|
||||||
variant: "error"
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
createNotification(notif: Notification) {
|
|
||||||
const { classes } = this.props;
|
|
||||||
let primary = "";
|
|
||||||
let secondary = "";
|
|
||||||
switch (notif.type) {
|
|
||||||
case "follow":
|
|
||||||
primary = `${notif.account.display_name ||
|
|
||||||
notif.account.username} is now following you!`;
|
|
||||||
break;
|
|
||||||
case "mention":
|
|
||||||
primary = `${notif.account.display_name ||
|
|
||||||
notif.account.username} mentioned you in a post.`;
|
|
||||||
secondary = this.removeHTMLContent(
|
|
||||||
notif.status ? notif.status.content : ""
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case "reblog":
|
|
||||||
primary = `${notif.account.display_name ||
|
|
||||||
notif.account.username} reblogged your post.`;
|
|
||||||
secondary = this.removeHTMLContent(
|
|
||||||
notif.status ? notif.status.content : ""
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case "favourite":
|
|
||||||
primary = `${notif.account.display_name ||
|
|
||||||
notif.account.username} favorited your post.`;
|
|
||||||
secondary = this.removeHTMLContent(
|
|
||||||
notif.status ? notif.status.content : ""
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
if (notif.status && notif.status.poll) {
|
|
||||||
primary = "A poll you voted in or created has ended.";
|
|
||||||
secondary = this.removeHTMLContent(
|
|
||||||
notif.status ? notif.status.content : ""
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
primary = "A magical thing happened!";
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
return (
|
|
||||||
<ListItem key={notif.id}>
|
|
||||||
<ListItemAvatar>
|
|
||||||
<LinkableAvatar
|
|
||||||
alt={notif.account.username}
|
|
||||||
src={notif.account.avatar_static}
|
|
||||||
to={`/profile/${notif.account.id}`}
|
|
||||||
>
|
|
||||||
<PersonIcon />
|
|
||||||
</LinkableAvatar>
|
|
||||||
</ListItemAvatar>
|
|
||||||
<ListItemText
|
|
||||||
primary={primary}
|
|
||||||
secondary={
|
|
||||||
<span>
|
|
||||||
<Typography color="textSecondary" className={classes.mobileOnly}>
|
|
||||||
{secondary.slice(0, 35) + "..."}
|
|
||||||
</Typography>
|
|
||||||
<Typography color="textSecondary" className={classes.desktopOnly}>
|
|
||||||
{secondary}
|
|
||||||
</Typography>
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<ListItemSecondaryAction>
|
|
||||||
{notif.type === "follow" ? (
|
|
||||||
<span>
|
|
||||||
<Tooltip title="View profile">
|
|
||||||
<LinkableIconButton to={`/profile/${notif.account.id}`}>
|
|
||||||
<AssignmentIndIcon />
|
|
||||||
</LinkableIconButton>
|
|
||||||
</Tooltip>
|
|
||||||
<Tooltip title="Follow account">
|
|
||||||
<IconButton onClick={() => this.followMember(notif.account)}>
|
|
||||||
<PersonAddIcon />
|
|
||||||
</IconButton>
|
|
||||||
</Tooltip>
|
|
||||||
</span>
|
|
||||||
) : notif.status ? (
|
|
||||||
<span>
|
|
||||||
<Tooltip title="View conversation">
|
|
||||||
<LinkableIconButton to={`/conversation/${notif.status.id}`}>
|
|
||||||
<ForumIcon />
|
|
||||||
</LinkableIconButton>
|
|
||||||
</Tooltip>
|
|
||||||
{notif.type === "mention" ? (
|
|
||||||
<Tooltip title="Reply">
|
|
||||||
<LinkableIconButton
|
|
||||||
to={`/compose?reply=${
|
|
||||||
notif.status.reblog
|
|
||||||
? notif.status.reblog.id
|
|
||||||
: notif.status.id
|
|
||||||
}&visibility=${notif.status.visibility}&acct=${
|
|
||||||
notif.status.reblog
|
|
||||||
? notif.status.reblog.account.acct
|
|
||||||
: notif.status.account.acct
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<ReplyIcon />
|
|
||||||
</LinkableIconButton>
|
|
||||||
</Tooltip>
|
|
||||||
) : null}
|
|
||||||
</span>
|
|
||||||
) : null}
|
|
||||||
<Tooltip title="Remove notification">
|
|
||||||
<IconButton onClick={() => this.removeNotification(notif.id)}>
|
|
||||||
<DeleteIcon />
|
|
||||||
</IconButton>
|
|
||||||
</Tooltip>
|
|
||||||
</ListItemSecondaryAction>
|
|
||||||
</ListItem>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
followMember(acct: Account) {
|
componentWillMount() {
|
||||||
this.client
|
this.client
|
||||||
.post(`/accounts/${acct.id}/follow`)
|
.get("/notifications")
|
||||||
.then((resp: any) => {
|
.then((resp: any) => {
|
||||||
this.props.enqueueSnackbar("You are now following this account.");
|
let notifications: [Notification] = resp.data;
|
||||||
})
|
this.setState({
|
||||||
.catch((err: Error) => {
|
notifications,
|
||||||
this.props.enqueueSnackbar("Couldn't follow account: " + err.name, {
|
viewIsLoading: false,
|
||||||
variant: "error"
|
viewDidLoad: true
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((err: Error) => {
|
||||||
|
this.setState({
|
||||||
|
viewDidLoad: true,
|
||||||
|
viewIsLoading: false,
|
||||||
|
viewDidError: true,
|
||||||
|
viewDidErrorCode: err.message
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.streamNotifications();
|
||||||
|
}
|
||||||
|
|
||||||
|
streamNotifications() {
|
||||||
|
this.streamListener = this.client.stream("/streaming/user");
|
||||||
|
|
||||||
|
this.streamListener.on("notification", (notif: Notification) => {
|
||||||
|
let notifications = this.state.notifications;
|
||||||
|
if (notifications) {
|
||||||
|
notifications.unshift(notif);
|
||||||
|
this.setState({ notifications });
|
||||||
|
}
|
||||||
});
|
});
|
||||||
console.error(err.message);
|
}
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
toggleDeleteDialog() {
|
||||||
const { classes } = this.props;
|
this.setState({ deleteDialogOpen: !this.state.deleteDialogOpen });
|
||||||
return (
|
}
|
||||||
<div className={classes.pageLayoutConstraints}>
|
|
||||||
{this.state.viewDidLoad ? (
|
removeHTMLContent(text: string) {
|
||||||
this.state.notifications && this.state.notifications.length > 0 ? (
|
const div = document.createElement("div");
|
||||||
<div>
|
div.innerHTML = text;
|
||||||
<ListSubheader>Recent notifications</ListSubheader>
|
let innerContent = div.textContent || div.innerText || "";
|
||||||
<Button
|
if (innerContent.length > 65)
|
||||||
className={classes.clearAllButton}
|
innerContent = innerContent.slice(0, 65) + "...";
|
||||||
variant="text"
|
return innerContent;
|
||||||
onClick={() => this.toggleDeleteDialog()}
|
}
|
||||||
>
|
|
||||||
{" "}
|
removeNotification(id: string) {
|
||||||
Clear All
|
this.client
|
||||||
</Button>
|
.post("/notifications/dismiss", { id: id })
|
||||||
<Paper className={classes.pageListConstraints}>
|
.then((resp: any) => {
|
||||||
<List>
|
let notifications = this.state.notifications;
|
||||||
{this.state.notifications.map(
|
if (notifications !== undefined && notifications.length > 0) {
|
||||||
(notification: Notification) => {
|
notifications.forEach((notification: Notification) => {
|
||||||
return this.createNotification(notification);
|
if (
|
||||||
|
notifications !== undefined &&
|
||||||
|
notification.id === id
|
||||||
|
) {
|
||||||
|
notifications.splice(
|
||||||
|
notifications.indexOf(notification),
|
||||||
|
1
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.setState({ notifications });
|
||||||
|
this.props.enqueueSnackbar("Notification deleted.");
|
||||||
|
})
|
||||||
|
.catch((err: Error) => {
|
||||||
|
this.props.enqueueSnackbar(
|
||||||
|
"Couldn't delete notification: " + err.name,
|
||||||
|
{
|
||||||
|
variant: "error"
|
||||||
}
|
}
|
||||||
)}
|
);
|
||||||
</List>
|
});
|
||||||
</Paper>
|
}
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className={classes.pageLayoutEmptyTextConstraints}>
|
|
||||||
<Typography variant="h4">All clear!</Typography>
|
|
||||||
<Typography paragraph>
|
|
||||||
It looks like you have no notifications. Why not get the
|
|
||||||
conversation going with a new post?
|
|
||||||
</Typography>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
) : null}
|
|
||||||
{this.state.viewDidError ? (
|
|
||||||
<Paper className={classes.errorCard}>
|
|
||||||
<Typography variant="h4">Bummer.</Typography>
|
|
||||||
<Typography variant="h6">
|
|
||||||
Something went wrong when loading this timeline.
|
|
||||||
</Typography>
|
|
||||||
<Typography>
|
|
||||||
{this.state.viewDidErrorCode ? this.state.viewDidErrorCode : ""}
|
|
||||||
</Typography>
|
|
||||||
</Paper>
|
|
||||||
) : (
|
|
||||||
<span />
|
|
||||||
)}
|
|
||||||
{this.state.viewIsLoading ? (
|
|
||||||
<div style={{ textAlign: "center" }}>
|
|
||||||
<CircularProgress className={classes.progress} color="primary" />
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<span />
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Dialog
|
removeAllNotifications() {
|
||||||
open={this.state.deleteDialogOpen}
|
this.client
|
||||||
onClose={() => this.toggleDeleteDialog()}
|
.post("/notifications/clear")
|
||||||
>
|
.then((resp: any) => {
|
||||||
<DialogTitle id="alert-dialog-title">
|
this.setState({ notifications: undefined });
|
||||||
Delete all notifications?
|
this.props.enqueueSnackbar("All notifications deleted.");
|
||||||
</DialogTitle>
|
})
|
||||||
<DialogContent>
|
.catch((err: Error) => {
|
||||||
<DialogContentText id="alert-dialog-description">
|
this.props.enqueueSnackbar(
|
||||||
Are you sure you want to delete all notifications? This action
|
"Couldn't delete notifications: " + err.name,
|
||||||
cannot be undone.
|
{
|
||||||
</DialogContentText>
|
variant: "error"
|
||||||
</DialogContent>
|
}
|
||||||
<DialogActions>
|
);
|
||||||
<Button
|
});
|
||||||
onClick={() => this.toggleDeleteDialog()}
|
}
|
||||||
color="primary"
|
|
||||||
autoFocus
|
createNotification(notif: Notification) {
|
||||||
>
|
const { classes } = this.props;
|
||||||
Cancel
|
let primary = "";
|
||||||
</Button>
|
let secondary = "";
|
||||||
<Button
|
switch (notif.type) {
|
||||||
onClick={() => {
|
case "follow":
|
||||||
this.removeAllNotifications();
|
primary = `${notif.account.display_name ||
|
||||||
this.toggleDeleteDialog();
|
notif.account.username} is now following you!`;
|
||||||
}}
|
break;
|
||||||
color="primary"
|
case "mention":
|
||||||
>
|
primary = `${notif.account.display_name ||
|
||||||
Delete
|
notif.account.username} mentioned you in a post.`;
|
||||||
</Button>
|
secondary = this.removeHTMLContent(
|
||||||
</DialogActions>
|
notif.status ? notif.status.content : ""
|
||||||
</Dialog>
|
);
|
||||||
</div>
|
break;
|
||||||
);
|
case "reblog":
|
||||||
}
|
primary = `${notif.account.display_name ||
|
||||||
|
notif.account.username} reblogged your post.`;
|
||||||
|
secondary = this.removeHTMLContent(
|
||||||
|
notif.status ? notif.status.content : ""
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case "favourite":
|
||||||
|
primary = `${notif.account.display_name ||
|
||||||
|
notif.account.username} favorited your post.`;
|
||||||
|
secondary = this.removeHTMLContent(
|
||||||
|
notif.status ? notif.status.content : ""
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (notif.status && notif.status.poll) {
|
||||||
|
primary = "A poll you voted in or created has ended.";
|
||||||
|
secondary = this.removeHTMLContent(
|
||||||
|
notif.status ? notif.status.content : ""
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
primary = "A magical thing happened!";
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<ListItem key={notif.id}>
|
||||||
|
<ListItemAvatar>
|
||||||
|
<LinkableAvatar
|
||||||
|
alt={notif.account.username}
|
||||||
|
src={notif.account.avatar_static}
|
||||||
|
to={`/profile/${notif.account.id}`}
|
||||||
|
>
|
||||||
|
<PersonIcon />
|
||||||
|
</LinkableAvatar>
|
||||||
|
</ListItemAvatar>
|
||||||
|
<ListItemText
|
||||||
|
primary={primary}
|
||||||
|
secondary={
|
||||||
|
<span>
|
||||||
|
<Typography
|
||||||
|
color="textSecondary"
|
||||||
|
className={classes.mobileOnly}
|
||||||
|
>
|
||||||
|
{secondary.slice(0, 35) + "..."}
|
||||||
|
</Typography>
|
||||||
|
<Typography
|
||||||
|
color="textSecondary"
|
||||||
|
className={classes.desktopOnly}
|
||||||
|
>
|
||||||
|
{secondary}
|
||||||
|
</Typography>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<ListItemSecondaryAction>
|
||||||
|
{notif.type === "follow" ? (
|
||||||
|
<span>
|
||||||
|
<Tooltip title="View profile">
|
||||||
|
<LinkableIconButton
|
||||||
|
to={`/profile/${notif.account.id}`}
|
||||||
|
>
|
||||||
|
<AssignmentIndIcon />
|
||||||
|
</LinkableIconButton>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip title="Follow account">
|
||||||
|
<IconButton
|
||||||
|
onClick={() =>
|
||||||
|
this.followMember(notif.account)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<PersonAddIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
</span>
|
||||||
|
) : notif.status ? (
|
||||||
|
<span>
|
||||||
|
<Tooltip title="View conversation">
|
||||||
|
<LinkableIconButton
|
||||||
|
to={`/conversation/${notif.status.id}`}
|
||||||
|
>
|
||||||
|
<ForumIcon />
|
||||||
|
</LinkableIconButton>
|
||||||
|
</Tooltip>
|
||||||
|
{notif.type === "mention" ? (
|
||||||
|
<Tooltip title="Reply">
|
||||||
|
<LinkableIconButton
|
||||||
|
to={`/compose?reply=${
|
||||||
|
notif.status.reblog
|
||||||
|
? notif.status.reblog.id
|
||||||
|
: notif.status.id
|
||||||
|
}&visibility=${
|
||||||
|
notif.status.visibility
|
||||||
|
}&acct=${
|
||||||
|
notif.status.reblog
|
||||||
|
? notif.status.reblog.account
|
||||||
|
.acct
|
||||||
|
: notif.status.account.acct
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<ReplyIcon />
|
||||||
|
</LinkableIconButton>
|
||||||
|
</Tooltip>
|
||||||
|
) : null}
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
|
<Tooltip title="Remove notification">
|
||||||
|
<IconButton
|
||||||
|
onClick={() => this.removeNotification(notif.id)}
|
||||||
|
>
|
||||||
|
<DeleteIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
</ListItemSecondaryAction>
|
||||||
|
</ListItem>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
followMember(acct: Account) {
|
||||||
|
this.client
|
||||||
|
.post(`/accounts/${acct.id}/follow`)
|
||||||
|
.then((resp: any) => {
|
||||||
|
this.props.enqueueSnackbar(
|
||||||
|
"You are now following this account."
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.catch((err: Error) => {
|
||||||
|
this.props.enqueueSnackbar(
|
||||||
|
"Couldn't follow account: " + err.name,
|
||||||
|
{ variant: "error" }
|
||||||
|
);
|
||||||
|
console.error(err.message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { classes } = this.props;
|
||||||
|
return (
|
||||||
|
<div className={classes.pageLayoutConstraints}>
|
||||||
|
{this.state.viewDidLoad ? (
|
||||||
|
this.state.notifications &&
|
||||||
|
this.state.notifications.length > 0 ? (
|
||||||
|
<div>
|
||||||
|
<ListSubheader>Recent notifications</ListSubheader>
|
||||||
|
<Button
|
||||||
|
className={classes.clearAllButton}
|
||||||
|
variant="text"
|
||||||
|
onClick={() => this.toggleDeleteDialog()}
|
||||||
|
>
|
||||||
|
{" "}
|
||||||
|
Clear All
|
||||||
|
</Button>
|
||||||
|
<Paper className={classes.pageListConstraints}>
|
||||||
|
<List>
|
||||||
|
{this.state.notifications.map(
|
||||||
|
(notification: Notification) => {
|
||||||
|
return this.createNotification(
|
||||||
|
notification
|
||||||
|
);
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
</List>
|
||||||
|
</Paper>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className={classes.pageLayoutEmptyTextConstraints}>
|
||||||
|
<Typography variant="h4">All clear!</Typography>
|
||||||
|
<Typography paragraph>
|
||||||
|
It looks like you have no notifications. Why not
|
||||||
|
get the conversation going with a new post?
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
) : null}
|
||||||
|
{this.state.viewDidError ? (
|
||||||
|
<Paper className={classes.errorCard}>
|
||||||
|
<Typography variant="h4">Bummer.</Typography>
|
||||||
|
<Typography variant="h6">
|
||||||
|
Something went wrong when loading this timeline.
|
||||||
|
</Typography>
|
||||||
|
<Typography>
|
||||||
|
{this.state.viewDidErrorCode
|
||||||
|
? this.state.viewDidErrorCode
|
||||||
|
: ""}
|
||||||
|
</Typography>
|
||||||
|
</Paper>
|
||||||
|
) : (
|
||||||
|
<span />
|
||||||
|
)}
|
||||||
|
{this.state.viewIsLoading ? (
|
||||||
|
<div style={{ textAlign: "center" }}>
|
||||||
|
<CircularProgress
|
||||||
|
className={classes.progress}
|
||||||
|
color="primary"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<span />
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Dialog
|
||||||
|
open={this.state.deleteDialogOpen}
|
||||||
|
onClose={() => this.toggleDeleteDialog()}
|
||||||
|
>
|
||||||
|
<DialogTitle id="alert-dialog-title">
|
||||||
|
Delete all notifications?
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogContentText id="alert-dialog-description">
|
||||||
|
Are you sure you want to delete all notifications?
|
||||||
|
This action cannot be undone.
|
||||||
|
</DialogContentText>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button
|
||||||
|
onClick={() => this.toggleDeleteDialog()}
|
||||||
|
color="primary"
|
||||||
|
autoFocus
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
this.removeAllNotifications();
|
||||||
|
this.toggleDeleteDialog();
|
||||||
|
}}
|
||||||
|
color="primary"
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withStyles(styles)(withSnackbar(NotificationsPage));
|
export default withStyles(styles)(withSnackbar(NotificationsPage));
|
||||||
|
|
|
@ -3,281 +3,282 @@ import { isDarwinApp } from "../utilities/desktop";
|
||||||
import { isAppbarExpanded } from "../utilities/appbar";
|
import { isAppbarExpanded } from "../utilities/appbar";
|
||||||
|
|
||||||
export const styles = (theme: Theme) =>
|
export const styles = (theme: Theme) =>
|
||||||
createStyles({
|
createStyles({
|
||||||
root: {
|
root: {
|
||||||
width: "100%",
|
width: "100%",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
height: "100%"
|
height: "100%"
|
||||||
},
|
},
|
||||||
pageLayoutConstraints: {
|
pageLayoutConstraints: {
|
||||||
marginTop: 72,
|
marginTop: 72,
|
||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
padding: theme.spacing.unit * 3,
|
padding: theme.spacing.unit * 3,
|
||||||
paddingLeft: theme.spacing.unit,
|
paddingLeft: theme.spacing.unit,
|
||||||
paddingRight: theme.spacing.unit,
|
paddingRight: theme.spacing.unit,
|
||||||
[theme.breakpoints.up("md")]: {
|
[theme.breakpoints.up("md")]: {
|
||||||
marginLeft: 250,
|
marginLeft: 250,
|
||||||
marginTop: 88,
|
marginTop: 88,
|
||||||
paddingLeft: theme.spacing.unit * 24,
|
paddingLeft: theme.spacing.unit * 24,
|
||||||
paddingRight: theme.spacing.unit * 24
|
paddingRight: theme.spacing.unit * 24
|
||||||
},
|
},
|
||||||
backgroundColor: theme.palette.background.default,
|
backgroundColor: theme.palette.background.default,
|
||||||
minHeight: isDarwinApp() ? "100vh" : "auto"
|
minHeight: isDarwinApp() ? "100vh" : "auto"
|
||||||
},
|
},
|
||||||
pageLayoutMaxConstraints: {
|
pageLayoutMaxConstraints: {
|
||||||
marginTop: 72,
|
marginTop: 72,
|
||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
paddingTop: theme.spacing.unit * 2,
|
paddingTop: theme.spacing.unit * 2,
|
||||||
padding: theme.spacing.unit,
|
padding: theme.spacing.unit,
|
||||||
[theme.breakpoints.up("md")]: {
|
[theme.breakpoints.up("md")]: {
|
||||||
marginLeft: 250,
|
marginLeft: 250,
|
||||||
marginTop: 88,
|
marginTop: 88,
|
||||||
padding: theme.spacing.unit * 3,
|
padding: theme.spacing.unit * 3,
|
||||||
paddingLeft: theme.spacing.unit * 16,
|
paddingLeft: theme.spacing.unit * 16,
|
||||||
paddingRight: theme.spacing.unit * 16
|
paddingRight: theme.spacing.unit * 16
|
||||||
},
|
},
|
||||||
[theme.breakpoints.up("lg")]: {
|
[theme.breakpoints.up("lg")]: {
|
||||||
marginLeft: 250,
|
marginLeft: 250,
|
||||||
marginTop: 88,
|
marginTop: 88,
|
||||||
padding: theme.spacing.unit * 3,
|
padding: theme.spacing.unit * 3,
|
||||||
paddingLeft: theme.spacing.unit * 32,
|
paddingLeft: theme.spacing.unit * 32,
|
||||||
paddingRight: theme.spacing.unit * 32
|
paddingRight: theme.spacing.unit * 32
|
||||||
},
|
},
|
||||||
[theme.breakpoints.up("xl")]: {
|
[theme.breakpoints.up("xl")]: {
|
||||||
marginLeft: 250,
|
marginLeft: 250,
|
||||||
marginTop: 88,
|
marginTop: 88,
|
||||||
padding: theme.spacing.unit * 3,
|
padding: theme.spacing.unit * 3,
|
||||||
paddingLeft: theme.spacing.unit * 40,
|
paddingLeft: theme.spacing.unit * 40,
|
||||||
paddingRight: theme.spacing.unit * 40
|
paddingRight: theme.spacing.unit * 40
|
||||||
},
|
},
|
||||||
backgroundColor: theme.palette.background.default,
|
backgroundColor: theme.palette.background.default,
|
||||||
minHeight: isDarwinApp() ? "100vh" : "auto"
|
minHeight: isDarwinApp() ? "100vh" : "auto"
|
||||||
},
|
},
|
||||||
pageLayoutMinimalConstraints: {
|
pageLayoutMinimalConstraints: {
|
||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
[theme.breakpoints.up("md")]: {
|
[theme.breakpoints.up("md")]: {
|
||||||
marginLeft: 250
|
marginLeft: 250
|
||||||
},
|
},
|
||||||
backgroundColor: theme.palette.background.default,
|
backgroundColor: theme.palette.background.default,
|
||||||
minHeight: isDarwinApp() ? "100vh" : "auto"
|
minHeight: isDarwinApp() ? "100vh" : "auto"
|
||||||
},
|
},
|
||||||
pageLayoutEmptyTextConstraints: {
|
pageLayoutEmptyTextConstraints: {
|
||||||
paddingLeft: theme.spacing.unit * 2,
|
paddingLeft: theme.spacing.unit * 2,
|
||||||
paddingRight: theme.spacing.unit * 2
|
paddingRight: theme.spacing.unit * 2
|
||||||
},
|
},
|
||||||
pageHeroBackground: {
|
pageHeroBackground: {
|
||||||
position: "relative",
|
position: "relative",
|
||||||
height: "intrinsic",
|
height: "intrinsic",
|
||||||
backgroundColor: theme.palette.primary.dark,
|
backgroundColor: theme.palette.primary.dark,
|
||||||
width: "100%",
|
width: "100%",
|
||||||
color: theme.palette.common.white,
|
color: theme.palette.common.white,
|
||||||
zIndex: 1,
|
zIndex: 1,
|
||||||
top: isAppbarExpanded() ? 80 : 64
|
top: isAppbarExpanded() ? 80 : 64
|
||||||
},
|
},
|
||||||
pageHeroBackgroundImage: {
|
pageHeroBackgroundImage: {
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
left: 0,
|
left: 0,
|
||||||
backgroundPosition: "center",
|
backgroundPosition: "center",
|
||||||
backgroundRepeat: "no-repeat",
|
backgroundRepeat: "no-repeat",
|
||||||
backgroundSize: "cover",
|
backgroundSize: "cover",
|
||||||
height: "100%",
|
height: "100%",
|
||||||
width: "100%",
|
width: "100%",
|
||||||
opacity: 0.35,
|
opacity: 0.35,
|
||||||
zIndex: -1,
|
zIndex: -1,
|
||||||
filter: "blur(2px)"
|
filter: "blur(2px)"
|
||||||
},
|
},
|
||||||
pageHeroContent: {
|
pageHeroContent: {
|
||||||
padding: 16,
|
padding: 16,
|
||||||
paddingTop: 8,
|
paddingTop: 8,
|
||||||
textAlign: "center",
|
width: "100%",
|
||||||
width: "100%",
|
height: "100%",
|
||||||
height: "100%",
|
[theme.breakpoints.up("md")]: {
|
||||||
[theme.breakpoints.up("md")]: {
|
paddingLeft: "5%",
|
||||||
paddingLeft: "5%",
|
paddingRight: "5%"
|
||||||
paddingRight: "5%"
|
},
|
||||||
},
|
position: "relative",
|
||||||
position: "relative",
|
zIndex: 1
|
||||||
zIndex: 1
|
},
|
||||||
},
|
pageHeroToolbar: {
|
||||||
pageHeroToolbar: {
|
position: "absolute",
|
||||||
position: "absolute",
|
right: theme.spacing.unit * 2,
|
||||||
right: theme.spacing.unit * 2,
|
marginTop: -16
|
||||||
marginTop: -16
|
},
|
||||||
},
|
pageListConstraints: {
|
||||||
pageListConstraints: {
|
paddingLeft: theme.spacing.unit,
|
||||||
paddingLeft: theme.spacing.unit,
|
paddingRight: theme.spacing.unit,
|
||||||
paddingRight: theme.spacing.unit,
|
[theme.breakpoints.up("sm")]: {
|
||||||
[theme.breakpoints.up("sm")]: {
|
paddingLeft: theme.spacing.unit * 2,
|
||||||
paddingLeft: theme.spacing.unit * 2,
|
paddingRight: theme.spacing.unit * 2
|
||||||
paddingRight: theme.spacing.unit * 2
|
}
|
||||||
}
|
//backgroundColor: theme.palette.background.default
|
||||||
//backgroundColor: theme.palette.background.default
|
},
|
||||||
},
|
profileToolbar: {
|
||||||
profileToolbar: {
|
zIndex: 2,
|
||||||
zIndex: 2,
|
paddingTop: 8
|
||||||
paddingTop: 8
|
},
|
||||||
},
|
profileContent: {
|
||||||
profileContent: {
|
padding: 16,
|
||||||
padding: 16,
|
[theme.breakpoints.up("md")]: {
|
||||||
[theme.breakpoints.up("md")]: {
|
paddingLeft: "5%",
|
||||||
paddingLeft: "5%",
|
paddingRight: "5%",
|
||||||
paddingRight: "5%",
|
paddingBottom: 48,
|
||||||
paddingBottom: 48,
|
paddingTop: 24
|
||||||
paddingTop: 24
|
},
|
||||||
},
|
width: "100%",
|
||||||
width: "100%",
|
height: "100%",
|
||||||
height: "100%",
|
position: "relative",
|
||||||
position: "relative",
|
zIndex: 1,
|
||||||
zIndex: 1,
|
display: "flex",
|
||||||
display: "flex",
|
paddingBottom: 24,
|
||||||
paddingBottom: 24,
|
paddingTop: 24
|
||||||
paddingTop: 24
|
},
|
||||||
},
|
profileAvatar: {
|
||||||
profileAvatar: {
|
width: 64,
|
||||||
width: 64,
|
height: 64,
|
||||||
height: 64,
|
[theme.breakpoints.up("md")]: {
|
||||||
[theme.breakpoints.up("md")]: {
|
width: 128,
|
||||||
width: 128,
|
height: 128
|
||||||
height: 128
|
},
|
||||||
},
|
backgroundColor: theme.palette.primary.main
|
||||||
backgroundColor: theme.palette.primary.main
|
},
|
||||||
},
|
profileUserBox: {
|
||||||
profileUserBox: {
|
paddingLeft: theme.spacing.unit * 2
|
||||||
paddingLeft: theme.spacing.unit * 2
|
},
|
||||||
},
|
pageProfileAvatar: {
|
||||||
pageProfileAvatar: {
|
width: 128,
|
||||||
width: 128,
|
height: 128,
|
||||||
height: 128,
|
marginLeft: "auto",
|
||||||
marginLeft: "auto",
|
marginRight: "auto",
|
||||||
marginRight: "auto",
|
marginBottom: theme.spacing.unit,
|
||||||
marginBottom: theme.spacing.unit,
|
backgroundColor: theme.palette.primary.main
|
||||||
backgroundColor: theme.palette.primary.main
|
},
|
||||||
},
|
pageProfileNameEmoji: {
|
||||||
pageProfileNameEmoji: {
|
height: theme.typography.h4.fontSize,
|
||||||
height: theme.typography.h4.fontSize,
|
fontWeight: theme.typography.fontWeightMedium
|
||||||
fontWeight: theme.typography.fontWeightMedium
|
},
|
||||||
},
|
pageProfileStatsDiv: {
|
||||||
pageProfileStatsDiv: {
|
display: "inline-flex",
|
||||||
display: "inline-flex",
|
marginTop: theme.spacing.unit * 2,
|
||||||
marginTop: theme.spacing.unit * 2,
|
marginBottom: theme.spacing.unit * 2
|
||||||
marginBottom: theme.spacing.unit * 2
|
},
|
||||||
},
|
pageProfileStat: {
|
||||||
pageProfileStat: {
|
marginLeft: theme.spacing.unit,
|
||||||
marginLeft: theme.spacing.unit,
|
marginRight: theme.spacing.unit
|
||||||
marginRight: theme.spacing.unit
|
},
|
||||||
},
|
pageProfileFollowButton: {
|
||||||
pageProfileFollowButton: {
|
marginTop: theme.spacing.unit,
|
||||||
marginTop: theme.spacing.unit,
|
marginLeft: theme.spacing.unit,
|
||||||
marginLeft: theme.spacing.unit,
|
marginRight: theme.spacing.unit,
|
||||||
marginRight: theme.spacing.unit,
|
zIndex: 3
|
||||||
zIndex: 3
|
},
|
||||||
},
|
pageContentLayoutConstraints: {
|
||||||
pageContentLayoutConstraints: {
|
paddingLeft: theme.spacing.unit,
|
||||||
paddingLeft: theme.spacing.unit,
|
paddingRight: theme.spacing.unit,
|
||||||
paddingRight: theme.spacing.unit,
|
paddingTop: theme.spacing.unit * 12,
|
||||||
paddingTop: theme.spacing.unit * 12,
|
paddingBottom: theme.spacing.unit * 2,
|
||||||
paddingBottom: theme.spacing.unit * 2,
|
[theme.breakpoints.up("lg")]: {
|
||||||
[theme.breakpoints.up("lg")]: {
|
paddingLeft: theme.spacing.unit * 32,
|
||||||
paddingLeft: theme.spacing.unit * 32,
|
paddingRight: theme.spacing.unit * 32
|
||||||
paddingRight: theme.spacing.unit * 32
|
}
|
||||||
}
|
//backgroundColor: theme.palette.background.default,
|
||||||
//backgroundColor: theme.palette.background.default,
|
},
|
||||||
},
|
errorCard: {
|
||||||
errorCard: {
|
padding: theme.spacing.unit * 4,
|
||||||
padding: theme.spacing.unit * 4,
|
backgroundColor: theme.palette.error.main
|
||||||
backgroundColor: theme.palette.error.main
|
},
|
||||||
},
|
pageTopChipContainer: {
|
||||||
pageTopChipContainer: {
|
zIndex: 24,
|
||||||
zIndex: 24,
|
position: "fixed",
|
||||||
position: "fixed",
|
width: "100%"
|
||||||
width: "100%"
|
},
|
||||||
},
|
pageTopChips: {
|
||||||
pageTopChips: {
|
textAlign: "center",
|
||||||
textAlign: "center",
|
[theme.breakpoints.up("md")]: {
|
||||||
[theme.breakpoints.up("md")]: {
|
marginRight: "55%"
|
||||||
marginRight: "55%"
|
},
|
||||||
},
|
[theme.breakpoints.up("xl")]: {
|
||||||
[theme.breakpoints.up("xl")]: {
|
marginRight: "50%"
|
||||||
marginRight: "50%"
|
}
|
||||||
}
|
},
|
||||||
},
|
pageTopChip: {
|
||||||
pageTopChip: {
|
boxShadow: theme.shadows[10]
|
||||||
boxShadow: theme.shadows[10]
|
},
|
||||||
},
|
clearAllButton: {
|
||||||
clearAllButton: {
|
zIndex: 3,
|
||||||
zIndex: 3,
|
position: "absolute",
|
||||||
position: "absolute",
|
right: 24,
|
||||||
right: 24,
|
top: 100,
|
||||||
top: 100,
|
[theme.breakpoints.up("md")]: {
|
||||||
[theme.breakpoints.up("md")]: {
|
top: 116,
|
||||||
top: 116,
|
right: theme.spacing.unit * 24
|
||||||
right: theme.spacing.unit * 24
|
}
|
||||||
}
|
},
|
||||||
},
|
mobileOnly: {
|
||||||
mobileOnly: {
|
[theme.breakpoints.up("sm")]: {
|
||||||
[theme.breakpoints.up("sm")]: {
|
display: "none"
|
||||||
display: "none"
|
}
|
||||||
}
|
},
|
||||||
},
|
desktopOnly: {
|
||||||
desktopOnly: {
|
display: "none",
|
||||||
display: "none",
|
[theme.breakpoints.up("sm")]: {
|
||||||
[theme.breakpoints.up("sm")]: {
|
display: "block"
|
||||||
display: "block"
|
}
|
||||||
}
|
},
|
||||||
},
|
pageLayoutFooter: {
|
||||||
pageLayoutFooter: {
|
"& a": {
|
||||||
"& a": {
|
color: theme.palette.primary.light
|
||||||
color: theme.palette.primary.light
|
}
|
||||||
}
|
},
|
||||||
},
|
youHeadingAvatar: {
|
||||||
youHeadingAvatar: {
|
height: 88,
|
||||||
height: 88,
|
width: 88
|
||||||
width: 88
|
},
|
||||||
},
|
youPaper: {
|
||||||
youPaper: {
|
padding: theme.spacing.unit * 2
|
||||||
padding: theme.spacing.unit * 2
|
},
|
||||||
},
|
youGrid: {
|
||||||
youGrid: {
|
textAlign: "center",
|
||||||
textAlign: "center",
|
"& *": {
|
||||||
"& *": {
|
marginLeft: "auto",
|
||||||
marginLeft: "auto",
|
marginRight: "auto"
|
||||||
marginRight: "auto"
|
}
|
||||||
}
|
},
|
||||||
},
|
youGridAvatar: {
|
||||||
youGridAvatar: {
|
height: 128,
|
||||||
height: 128,
|
width: 128
|
||||||
width: 128
|
},
|
||||||
},
|
youGridImage: {
|
||||||
youGridImage: {
|
width: "auto",
|
||||||
width: "auto",
|
height: 128
|
||||||
height: 128
|
},
|
||||||
},
|
instanceHeaderPaper: {
|
||||||
instanceHeaderPaper: {
|
height: 150,
|
||||||
height: 200,
|
backgroundPosition: "center",
|
||||||
backgroundPosition: "center",
|
backgroundRepeat: "no-repeat",
|
||||||
backgroundRepeat: "no-repeat",
|
backgroundSize: "cover",
|
||||||
backgroundSize: "cover",
|
position: "relative",
|
||||||
position: "relative",
|
backgroundColor: theme.palette.primary.dark,
|
||||||
backgroundColor: theme.palette.primary.dark,
|
borderTopLeftRadius: theme.shape.borderRadius,
|
||||||
borderTopLeftRadius: theme.shape.borderRadius,
|
borderTopRightRadius: theme.shape.borderRadius
|
||||||
borderTopRightRadius: theme.shape.borderRadius
|
},
|
||||||
},
|
instanceHeaderText: {
|
||||||
instanceHeaderText: {
|
position: "absolute",
|
||||||
position: "absolute",
|
bottom: theme.spacing.unit,
|
||||||
bottom: theme.spacing.unit,
|
left: theme.spacing.unit * 2,
|
||||||
left: theme.spacing.unit * 2,
|
"& *": {
|
||||||
color: theme.palette.common.white,
|
color: theme.palette.common.white,
|
||||||
textShadow: `0 0 4px ${theme.palette.grey[700]}`,
|
textShadow: `0 0 4px ${theme.palette.grey[700]}`,
|
||||||
fontWeight: 600
|
fontWeight: 600
|
||||||
},
|
}
|
||||||
instanceToolbar: {
|
},
|
||||||
position: "absolute",
|
instanceToolbar: {
|
||||||
top: theme.spacing.unit,
|
position: "absolute",
|
||||||
right: theme.spacing.unit,
|
top: theme.spacing.unit,
|
||||||
color: theme.palette.common.white
|
right: theme.spacing.unit,
|
||||||
},
|
color: theme.palette.common.white
|
||||||
pageGrow: {
|
},
|
||||||
flexGrow: 1
|
pageGrow: {
|
||||||
}
|
flexGrow: 1
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
|
|
@ -1,20 +1,20 @@
|
||||||
import React, { Component } from "react";
|
import React, { Component } from "react";
|
||||||
import {
|
import {
|
||||||
withStyles,
|
withStyles,
|
||||||
Typography,
|
Typography,
|
||||||
Avatar,
|
Avatar,
|
||||||
Divider,
|
Divider,
|
||||||
Button,
|
Button,
|
||||||
CircularProgress,
|
CircularProgress,
|
||||||
Paper,
|
Paper,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogContentText,
|
DialogContentText,
|
||||||
DialogActions,
|
DialogActions,
|
||||||
Toolbar,
|
Toolbar,
|
||||||
IconButton
|
IconButton
|
||||||
} from "@material-ui/core";
|
} from "@material-ui/core";
|
||||||
import { styles } from "./PageLayout.styles";
|
import { styles } from "./PageLayout.styles";
|
||||||
import Mastodon from "megalodon";
|
import Mastodon from "megalodon";
|
||||||
|
@ -36,469 +36,518 @@ import AccountHeartIcon from "mdi-material-ui/AccountHeart";
|
||||||
import OpenInNewIcon from "@material-ui/icons/OpenInNew";
|
import OpenInNewIcon from "@material-ui/icons/OpenInNew";
|
||||||
|
|
||||||
interface IProfilePageState {
|
interface IProfilePageState {
|
||||||
account?: Account;
|
account?: Account;
|
||||||
relationship?: Relationship;
|
relationship?: Relationship;
|
||||||
posts?: [Status];
|
posts?: [Status];
|
||||||
viewIsLoading: boolean;
|
viewIsLoading: boolean;
|
||||||
viewDidLoad?: boolean;
|
viewDidLoad?: boolean;
|
||||||
viewDidError?: boolean;
|
viewDidError?: boolean;
|
||||||
viewDidErrorCode?: string;
|
viewDidErrorCode?: string;
|
||||||
blockDialogOpen: boolean;
|
blockDialogOpen: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
class ProfilePage extends Component<any, IProfilePageState> {
|
class ProfilePage extends Component<any, IProfilePageState> {
|
||||||
client: Mastodon;
|
client: Mastodon;
|
||||||
|
|
||||||
constructor(props: any) {
|
constructor(props: any) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.client = new Mastodon(
|
this.client = new Mastodon(
|
||||||
localStorage.getItem("access_token") as string,
|
localStorage.getItem("access_token") as string,
|
||||||
localStorage.getItem("baseurl") + "/api/v1"
|
localStorage.getItem("baseurl") + "/api/v1"
|
||||||
);
|
);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
viewIsLoading: true,
|
viewIsLoading: true,
|
||||||
blockDialogOpen: false
|
blockDialogOpen: false
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
|
||||||
toggleBlockDialog() {
|
|
||||||
if (this.state.relationship && !this.state.relationship.blocking)
|
|
||||||
this.setState({ blockDialogOpen: !this.state.blockDialogOpen });
|
|
||||||
else this.toggleBlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
getAccountData(id: string) {
|
|
||||||
this.client
|
|
||||||
.get(`/accounts/${id}`)
|
|
||||||
.then((resp: any) => {
|
|
||||||
let profile: Account = resp.data;
|
|
||||||
|
|
||||||
const div = document.createElement("div");
|
|
||||||
div.innerHTML = profile.note;
|
|
||||||
profile.note = div.textContent || div.innerText || "";
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
account: profile
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch((error: Error) => {
|
|
||||||
this.setState({
|
|
||||||
viewIsLoading: false,
|
|
||||||
viewDidError: true,
|
|
||||||
viewDidErrorCode: error.message
|
|
||||||
});
|
|
||||||
});
|
|
||||||
this.getRelationships();
|
|
||||||
this.client
|
|
||||||
.get(`/accounts/${id}/statuses`)
|
|
||||||
.then((resp: any) => {
|
|
||||||
this.setState({
|
|
||||||
posts: resp.data,
|
|
||||||
viewIsLoading: false,
|
|
||||||
viewDidLoad: true,
|
|
||||||
viewDidError: false
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch((err: Error) => {
|
|
||||||
this.setState({
|
|
||||||
viewIsLoading: false,
|
|
||||||
viewDidError: true,
|
|
||||||
viewDidErrorCode: err.message
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillReceiveProps(props: any) {
|
|
||||||
this.getAccountData(props.match.params.profileId);
|
|
||||||
window.scrollTo(0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillMount() {
|
|
||||||
const {
|
|
||||||
match: { params }
|
|
||||||
} = this.props;
|
|
||||||
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() {
|
toggleBlockDialog() {
|
||||||
this.client
|
if (this.state.relationship && !this.state.relationship.blocking)
|
||||||
.get("/accounts/relationships", { id: this.props.match.params.profileId })
|
this.setState({ blockDialogOpen: !this.state.blockDialogOpen });
|
||||||
.then((resp: any) => {
|
else this.toggleBlock();
|
||||||
let relationship: Relationship = resp.data[0];
|
}
|
||||||
this.setState({ relationship });
|
|
||||||
})
|
|
||||||
.catch((error: Error) => {
|
|
||||||
this.setState({
|
|
||||||
viewIsLoading: false,
|
|
||||||
viewDidError: true,
|
|
||||||
viewDidErrorCode: error.message
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
loadMoreTimelinePieces() {
|
getAccountData(id: string) {
|
||||||
const {
|
this.client
|
||||||
match: { params }
|
.get(`/accounts/${id}`)
|
||||||
} = this.props;
|
.then((resp: any) => {
|
||||||
this.setState({ viewDidLoad: false, viewIsLoading: true });
|
let profile: Account = resp.data;
|
||||||
if (this.state.posts && this.state.posts.length > 0) {
|
|
||||||
this.client
|
const div = document.createElement("div");
|
||||||
.get(`/accounts/${params.profileId}/statuses`, {
|
div.innerHTML = profile.note;
|
||||||
max_id: this.state.posts[this.state.posts.length - 1].id,
|
profile.note = div.textContent || div.innerText || "";
|
||||||
limit: 20
|
|
||||||
})
|
this.setState({
|
||||||
.then((resp: any) => {
|
account: profile
|
||||||
let newPosts: [Status] = resp.data;
|
});
|
||||||
let posts = this.state.posts as [Status];
|
})
|
||||||
if (newPosts.length <= 0) {
|
.catch((error: Error) => {
|
||||||
|
this.setState({
|
||||||
|
viewIsLoading: false,
|
||||||
|
viewDidError: true,
|
||||||
|
viewDidErrorCode: error.message
|
||||||
|
});
|
||||||
|
});
|
||||||
|
this.getRelationships();
|
||||||
|
this.client
|
||||||
|
.get(`/accounts/${id}/statuses`)
|
||||||
|
.then((resp: any) => {
|
||||||
|
this.setState({
|
||||||
|
posts: resp.data,
|
||||||
|
viewIsLoading: false,
|
||||||
|
viewDidLoad: true,
|
||||||
|
viewDidError: false
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((err: Error) => {
|
||||||
|
this.setState({
|
||||||
|
viewIsLoading: false,
|
||||||
|
viewDidError: true,
|
||||||
|
viewDidErrorCode: err.message
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps(props: any) {
|
||||||
|
this.getAccountData(props.match.params.profileId);
|
||||||
|
window.scrollTo(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillMount() {
|
||||||
|
const {
|
||||||
|
match: { params }
|
||||||
|
} = this.props;
|
||||||
|
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() {
|
||||||
|
this.client
|
||||||
|
.get("/accounts/relationships", {
|
||||||
|
id: this.props.match.params.profileId
|
||||||
|
})
|
||||||
|
.then((resp: any) => {
|
||||||
|
let relationship: Relationship = resp.data[0];
|
||||||
|
this.setState({ relationship });
|
||||||
|
})
|
||||||
|
.catch((error: Error) => {
|
||||||
|
this.setState({
|
||||||
|
viewIsLoading: false,
|
||||||
|
viewDidError: true,
|
||||||
|
viewDidErrorCode: error.message
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
loadMoreTimelinePieces() {
|
||||||
|
const {
|
||||||
|
match: { params }
|
||||||
|
} = this.props;
|
||||||
|
this.setState({ viewDidLoad: false, viewIsLoading: true });
|
||||||
|
if (this.state.posts && this.state.posts.length > 0) {
|
||||||
|
this.client
|
||||||
|
.get(`/accounts/${params.profileId}/statuses`, {
|
||||||
|
max_id: this.state.posts[this.state.posts.length - 1].id,
|
||||||
|
limit: 20
|
||||||
|
})
|
||||||
|
.then((resp: any) => {
|
||||||
|
let newPosts: [Status] = resp.data;
|
||||||
|
let posts = this.state.posts as [Status];
|
||||||
|
if (newPosts.length <= 0) {
|
||||||
|
this.props.enqueueSnackbar("Reached end of posts", {
|
||||||
|
variant: "error"
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
newPosts.forEach((post: Status) => {
|
||||||
|
posts.push(post);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.setState({
|
||||||
|
viewIsLoading: false,
|
||||||
|
viewDidLoad: true,
|
||||||
|
posts
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((err: Error) => {
|
||||||
|
this.setState({
|
||||||
|
viewIsLoading: false,
|
||||||
|
viewDidError: true,
|
||||||
|
viewDidErrorCode: err.message
|
||||||
|
});
|
||||||
|
this.props.enqueueSnackbar("Failed to get posts", {
|
||||||
|
variant: "error"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
this.props.enqueueSnackbar("Reached end of posts", {
|
this.props.enqueueSnackbar("Reached end of posts", {
|
||||||
variant: "error"
|
variant: "error"
|
||||||
});
|
});
|
||||||
} else {
|
this.setState({
|
||||||
newPosts.forEach((post: Status) => {
|
viewIsLoading: false,
|
||||||
posts.push(post);
|
viewDidLoad: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
this.setState({
|
|
||||||
viewIsLoading: false,
|
|
||||||
viewDidLoad: true,
|
|
||||||
posts
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch((err: Error) => {
|
|
||||||
this.setState({
|
|
||||||
viewIsLoading: false,
|
|
||||||
viewDidError: true,
|
|
||||||
viewDidErrorCode: err.message
|
|
||||||
});
|
|
||||||
this.props.enqueueSnackbar("Failed to get posts", {
|
|
||||||
variant: "error"
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.props.enqueueSnackbar("Reached end of posts", { variant: "error" });
|
|
||||||
this.setState({
|
|
||||||
viewIsLoading: false,
|
|
||||||
viewDidLoad: true
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
toggleFollow() {
|
toggleFollow() {
|
||||||
if (this.state.relationship) {
|
if (this.state.relationship) {
|
||||||
if (this.state.relationship.following) {
|
if (this.state.relationship.following) {
|
||||||
this.client
|
this.client
|
||||||
.post(
|
.post(
|
||||||
`/accounts/${
|
`/accounts/${
|
||||||
this.state.account
|
this.state.account
|
||||||
? this.state.account.id
|
? this.state.account.id
|
||||||
: this.props.match.params.profileId
|
: this.props.match.params.profileId
|
||||||
}/unfollow`
|
}/unfollow`
|
||||||
)
|
)
|
||||||
.then((resp: any) => {
|
.then((resp: any) => {
|
||||||
let relationship: Relationship = resp.data;
|
let relationship: Relationship = resp.data;
|
||||||
this.setState({ relationship });
|
this.setState({ relationship });
|
||||||
this.props.enqueueSnackbar(
|
this.props.enqueueSnackbar(
|
||||||
"You are no longer following this account."
|
"You are no longer following this account."
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
.catch((err: Error) => {
|
.catch((err: Error) => {
|
||||||
this.props.enqueueSnackbar(
|
this.props.enqueueSnackbar(
|
||||||
"Couldn't unfollow account: " + err.name,
|
"Couldn't unfollow account: " + err.name,
|
||||||
{ variant: "error" }
|
{ variant: "error" }
|
||||||
);
|
);
|
||||||
console.error(err.message);
|
console.error(err.message);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.client
|
this.client
|
||||||
.post(
|
.post(
|
||||||
`/accounts/${
|
`/accounts/${
|
||||||
this.state.account
|
this.state.account
|
||||||
? this.state.account.id
|
? this.state.account.id
|
||||||
: this.props.match.params.profileId
|
: this.props.match.params.profileId
|
||||||
}/follow`
|
}/follow`
|
||||||
)
|
)
|
||||||
.then((resp: any) => {
|
.then((resp: any) => {
|
||||||
let relationship: Relationship = resp.data;
|
let relationship: Relationship = resp.data;
|
||||||
this.setState({ relationship });
|
this.setState({ relationship });
|
||||||
this.props.enqueueSnackbar("You are now following this account.");
|
this.props.enqueueSnackbar(
|
||||||
})
|
"You are now following this account."
|
||||||
.catch((err: Error) => {
|
);
|
||||||
this.props.enqueueSnackbar("Couldn't follow account: " + err.name, {
|
})
|
||||||
variant: "error"
|
.catch((err: Error) => {
|
||||||
});
|
this.props.enqueueSnackbar(
|
||||||
console.error(err.message);
|
"Couldn't follow account: " + err.name,
|
||||||
});
|
{ variant: "error" }
|
||||||
}
|
);
|
||||||
|
console.error(err.message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
toggleBlock() {
|
toggleBlock() {
|
||||||
if (this.state.relationship) {
|
if (this.state.relationship) {
|
||||||
if (this.state.relationship.blocking) {
|
if (this.state.relationship.blocking) {
|
||||||
this.client
|
this.client
|
||||||
.post(
|
.post(
|
||||||
`/accounts/${
|
`/accounts/${
|
||||||
this.state.account
|
this.state.account
|
||||||
? this.state.account.id
|
? this.state.account.id
|
||||||
: this.props.match.params.profileId
|
: this.props.match.params.profileId
|
||||||
}/unblock`
|
}/unblock`
|
||||||
)
|
)
|
||||||
.then((resp: any) => {
|
.then((resp: any) => {
|
||||||
let relationship: Relationship = resp.data;
|
let relationship: Relationship = resp.data;
|
||||||
this.setState({ relationship });
|
this.setState({ relationship });
|
||||||
this.props.enqueueSnackbar(
|
this.props.enqueueSnackbar(
|
||||||
"You are no longer blocking this account."
|
"You are no longer blocking this account."
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
.catch((err: Error) => {
|
.catch((err: Error) => {
|
||||||
this.props.enqueueSnackbar(
|
this.props.enqueueSnackbar(
|
||||||
"Couldn't unblock account: " + err.name,
|
"Couldn't unblock account: " + err.name,
|
||||||
{ variant: "error" }
|
{ variant: "error" }
|
||||||
);
|
);
|
||||||
console.error(err.message);
|
console.error(err.message);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.client
|
this.client
|
||||||
.post(
|
.post(
|
||||||
`/accounts/${
|
`/accounts/${
|
||||||
this.state.account
|
this.state.account
|
||||||
? this.state.account.id
|
? this.state.account.id
|
||||||
: this.props.match.params.profileId
|
: this.props.match.params.profileId
|
||||||
}/block`
|
}/block`
|
||||||
)
|
)
|
||||||
.then((resp: any) => {
|
.then((resp: any) => {
|
||||||
let relationship: Relationship = resp.data;
|
let relationship: Relationship = resp.data;
|
||||||
this.setState({ relationship });
|
this.setState({ relationship });
|
||||||
this.props.enqueueSnackbar("You are now blocking this account.");
|
this.props.enqueueSnackbar(
|
||||||
})
|
"You are now blocking this account."
|
||||||
.catch((err: Error) => {
|
);
|
||||||
this.props.enqueueSnackbar("Couldn't block account: " + err.name, {
|
})
|
||||||
variant: "error"
|
.catch((err: Error) => {
|
||||||
});
|
this.props.enqueueSnackbar(
|
||||||
console.error(err.message);
|
"Couldn't block account: " + err.name,
|
||||||
});
|
{ variant: "error" }
|
||||||
}
|
);
|
||||||
|
console.error(err.message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { classes } = this.props;
|
const { classes } = this.props;
|
||||||
return (
|
return (
|
||||||
<div className={classes.pageLayoutMinimalConstraints}>
|
<div className={classes.pageLayoutMinimalConstraints}>
|
||||||
<div className={classes.pageHeroBackground}>
|
<div className={classes.pageHeroBackground}>
|
||||||
<div
|
<div
|
||||||
className={classes.pageHeroBackgroundImage}
|
className={classes.pageHeroBackgroundImage}
|
||||||
style={{
|
style={{
|
||||||
backgroundImage: this.state.account
|
backgroundImage: this.state.account
|
||||||
? `url("${this.state.account.header}")`
|
? `url("${this.state.account.header}")`
|
||||||
: `url("")`
|
: `url("")`
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Toolbar className={classes.profileToolbar}>
|
<Toolbar className={classes.profileToolbar}>
|
||||||
<div className={classes.pageGrow} />
|
<div className={classes.pageGrow} />
|
||||||
<Tooltip
|
<Tooltip
|
||||||
title={
|
title={
|
||||||
this.isItMe()
|
this.isItMe()
|
||||||
? "You can't follow yourself."
|
? "You can't follow yourself."
|
||||||
: this.state.relationship && this.state.relationship.following
|
: this.state.relationship &&
|
||||||
? "Unfollow"
|
this.state.relationship.following
|
||||||
: "Follow"
|
? "Unfollow"
|
||||||
}
|
: "Follow"
|
||||||
>
|
}
|
||||||
<IconButton
|
>
|
||||||
color={"inherit"}
|
<IconButton
|
||||||
disabled={this.isItMe()}
|
color={"inherit"}
|
||||||
onClick={() => this.toggleFollow()}
|
disabled={this.isItMe()}
|
||||||
>
|
onClick={() => this.toggleFollow()}
|
||||||
{this.isItMe() ? (
|
>
|
||||||
<PersonAddDisabledIcon />
|
{this.isItMe() ? (
|
||||||
) : this.state.relationship &&
|
<PersonAddDisabledIcon />
|
||||||
this.state.relationship.following ? (
|
) : this.state.relationship &&
|
||||||
<AccountMinusIcon />
|
this.state.relationship.following ? (
|
||||||
) : (
|
<AccountMinusIcon />
|
||||||
<PersonAddIcon />
|
) : (
|
||||||
)}
|
<PersonAddIcon />
|
||||||
</IconButton>
|
)}
|
||||||
</Tooltip>
|
</IconButton>
|
||||||
<Tooltip title={"Send a message or post"}>
|
</Tooltip>
|
||||||
<LinkableIconButton
|
<Tooltip title={"Send a message or post"}>
|
||||||
to={`/compose?acct=${
|
<LinkableIconButton
|
||||||
this.state.account ? this.state.account.acct : ""
|
to={`/compose?acct=${
|
||||||
}`}
|
this.state.account
|
||||||
color={"inherit"}
|
? this.state.account.acct
|
||||||
>
|
: ""
|
||||||
<ChatIcon />
|
}`}
|
||||||
</LinkableIconButton>
|
color={"inherit"}
|
||||||
</Tooltip>
|
>
|
||||||
<Tooltip
|
<ChatIcon />
|
||||||
title={
|
</LinkableIconButton>
|
||||||
this.state.relationship && this.state.relationship.blocking
|
</Tooltip>
|
||||||
? "Unblock this account"
|
<Tooltip
|
||||||
: "Block this account"
|
title={
|
||||||
}
|
this.state.relationship &&
|
||||||
>
|
this.state.relationship.blocking
|
||||||
<IconButton
|
? "Unblock this account"
|
||||||
color={"inherit"}
|
: "Block this account"
|
||||||
disabled={this.isItMe()}
|
}
|
||||||
onClick={() => this.toggleBlockDialog()}
|
>
|
||||||
>
|
<IconButton
|
||||||
{this.state.relationship && this.state.relationship.blocking ? (
|
color={"inherit"}
|
||||||
<AccountHeartIcon />
|
disabled={this.isItMe()}
|
||||||
) : (
|
onClick={() => this.toggleBlockDialog()}
|
||||||
<AccountRemoveIcon />
|
>
|
||||||
)}
|
{this.state.relationship &&
|
||||||
</IconButton>
|
this.state.relationship.blocking ? (
|
||||||
</Tooltip>
|
<AccountHeartIcon />
|
||||||
<Tooltip title="Open in web">
|
) : (
|
||||||
<IconButton
|
<AccountRemoveIcon />
|
||||||
href={this.state.account ? this.state.account.url : ""}
|
)}
|
||||||
target="_blank"
|
</IconButton>
|
||||||
rel={"nofollower noreferrer noopener"}
|
</Tooltip>
|
||||||
color={"inherit"}
|
<Tooltip title="Open in web">
|
||||||
>
|
<IconButton
|
||||||
<OpenInNewIcon />
|
href={
|
||||||
</IconButton>
|
this.state.account
|
||||||
</Tooltip>
|
? this.state.account.url
|
||||||
{this.isItMe() ? (
|
: ""
|
||||||
<Tooltip title="Edit profile">
|
}
|
||||||
<LinkableIconButton to="/you" color="inherit">
|
target="_blank"
|
||||||
<AccountEditIcon />
|
rel={"nofollower noreferrer noopener"}
|
||||||
</LinkableIconButton>
|
color={"inherit"}
|
||||||
</Tooltip>
|
>
|
||||||
) : null}
|
<OpenInNewIcon />
|
||||||
</Toolbar>
|
</IconButton>
|
||||||
<div className={classes.profileContent}>
|
</Tooltip>
|
||||||
<Avatar
|
{this.isItMe() ? (
|
||||||
className={classes.profileAvatar}
|
<Tooltip title="Edit profile">
|
||||||
src={this.state.account ? this.state.account.avatar : ""}
|
<LinkableIconButton to="/you" color="inherit">
|
||||||
/>
|
<AccountEditIcon />
|
||||||
<div className={classes.profileUserBox}>
|
</LinkableIconButton>
|
||||||
<Typography
|
</Tooltip>
|
||||||
variant="h4"
|
) : null}
|
||||||
color="inherit"
|
</Toolbar>
|
||||||
dangerouslySetInnerHTML={{
|
<div className={classes.profileContent}>
|
||||||
__html: this.state.account
|
<Avatar
|
||||||
? this.state.account.display_name
|
className={classes.profileAvatar}
|
||||||
? emojifyString(
|
src={
|
||||||
this.state.account.display_name,
|
this.state.account
|
||||||
this.state.account.emojis,
|
? this.state.account.avatar
|
||||||
classes.pageProfileNameEmoji
|
: ""
|
||||||
)
|
}
|
||||||
: this.state.account.username
|
/>
|
||||||
: ""
|
<div className={classes.profileUserBox}>
|
||||||
}}
|
<Typography
|
||||||
className={classes.pageProfileNameEmoji}
|
variant="h4"
|
||||||
/>
|
color="inherit"
|
||||||
<Typography variant="caption" color="inherit">
|
dangerouslySetInnerHTML={{
|
||||||
{this.state.account ? "@" + this.state.account.acct : ""}
|
__html: this.state.account
|
||||||
</Typography>
|
? this.state.account.display_name
|
||||||
<Typography paragraph color="inherit">
|
? emojifyString(
|
||||||
{this.state.account
|
this.state.account
|
||||||
? this.state.account.note
|
.display_name,
|
||||||
? this.state.account.note
|
this.state.account.emojis,
|
||||||
: "No bio provided by user."
|
classes.pageProfileNameEmoji
|
||||||
: "No bio available."}
|
)
|
||||||
</Typography>
|
: this.state.account.username
|
||||||
<Typography color={"inherit"}>
|
: ""
|
||||||
{this.state.account ? this.state.account.followers_count : 0}{" "}
|
}}
|
||||||
followers |{" "}
|
className={classes.pageProfileNameEmoji}
|
||||||
{this.state.account ? this.state.account.following_count : 0}{" "}
|
/>
|
||||||
following |{" "}
|
<Typography variant="caption" color="inherit">
|
||||||
{this.state.account ? this.state.account.statuses_count : 0}{" "}
|
{this.state.account
|
||||||
posts
|
? "@" + this.state.account.acct
|
||||||
</Typography>
|
: ""}
|
||||||
</div>
|
</Typography>
|
||||||
</div>
|
<Typography paragraph color="inherit">
|
||||||
</div>
|
{this.state.account
|
||||||
<div className={classes.pageContentLayoutConstraints}>
|
? this.state.account.note
|
||||||
{this.state.viewDidError ? (
|
? this.state.account.note
|
||||||
<Paper className={classes.errorCard}>
|
: "No bio provided by user."
|
||||||
<Typography variant="h4">Bummer.</Typography>
|
: "No bio available."}
|
||||||
<Typography variant="h6">
|
</Typography>
|
||||||
Something went wrong when loading this profile.
|
<Typography color={"inherit"}>
|
||||||
</Typography>
|
{this.state.account
|
||||||
<Typography>
|
? this.state.account.followers_count
|
||||||
{this.state.viewDidErrorCode ? this.state.viewDidErrorCode : ""}
|
: 0}{" "}
|
||||||
</Typography>
|
followers |{" "}
|
||||||
</Paper>
|
{this.state.account
|
||||||
) : (
|
? this.state.account.following_count
|
||||||
<span />
|
: 0}{" "}
|
||||||
)}
|
following |{" "}
|
||||||
{this.state.posts ? (
|
{this.state.account
|
||||||
<div>
|
? this.state.account.statuses_count
|
||||||
{this.state.posts.map((post: Status) => {
|
: 0}{" "}
|
||||||
return <Post key={post.id} post={post} client={this.client} />;
|
posts
|
||||||
})}
|
</Typography>
|
||||||
<br />
|
</div>
|
||||||
{this.state.viewDidLoad && !this.state.viewDidError ? (
|
</div>
|
||||||
<div
|
</div>
|
||||||
style={{ textAlign: "center" }}
|
<div className={classes.pageContentLayoutConstraints}>
|
||||||
onClick={() => this.loadMoreTimelinePieces()}
|
{this.state.viewDidError ? (
|
||||||
>
|
<Paper className={classes.errorCard}>
|
||||||
<Button variant="contained">Load more</Button>
|
<Typography variant="h4">Bummer.</Typography>
|
||||||
|
<Typography variant="h6">
|
||||||
|
Something went wrong when loading this profile.
|
||||||
|
</Typography>
|
||||||
|
<Typography>
|
||||||
|
{this.state.viewDidErrorCode
|
||||||
|
? this.state.viewDidErrorCode
|
||||||
|
: ""}
|
||||||
|
</Typography>
|
||||||
|
</Paper>
|
||||||
|
) : (
|
||||||
|
<span />
|
||||||
|
)}
|
||||||
|
{this.state.posts ? (
|
||||||
|
<div>
|
||||||
|
{this.state.posts.map((post: Status) => {
|
||||||
|
return (
|
||||||
|
<Post
|
||||||
|
key={post.id}
|
||||||
|
post={post}
|
||||||
|
client={this.client}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
<br />
|
||||||
|
{this.state.viewDidLoad &&
|
||||||
|
!this.state.viewDidError ? (
|
||||||
|
<div
|
||||||
|
style={{ textAlign: "center" }}
|
||||||
|
onClick={() =>
|
||||||
|
this.loadMoreTimelinePieces()
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Button variant="contained">
|
||||||
|
Load more
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<span />
|
||||||
|
)}
|
||||||
|
{this.state.viewIsLoading ? (
|
||||||
|
<div style={{ textAlign: "center" }}>
|
||||||
|
<CircularProgress
|
||||||
|
className={classes.progress}
|
||||||
|
color="primary"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<span />
|
||||||
|
)}
|
||||||
|
<Dialog
|
||||||
|
open={this.state.blockDialogOpen}
|
||||||
|
onClose={() => this.toggleBlockDialog()}
|
||||||
|
>
|
||||||
|
<DialogTitle id="alert-dialog-title">
|
||||||
|
Block this person?
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogContentText id="alert-dialog-description">
|
||||||
|
Are you sure you want to block this person? You
|
||||||
|
won't see their posts on your home feed, local
|
||||||
|
timeline, or public timeline.
|
||||||
|
</DialogContentText>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button
|
||||||
|
onClick={() => this.toggleBlockDialog()}
|
||||||
|
color="primary"
|
||||||
|
autoFocus
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
this.toggleBlock();
|
||||||
|
this.toggleBlockDialog();
|
||||||
|
}}
|
||||||
|
color="primary"
|
||||||
|
>
|
||||||
|
Block
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
|
||||||
</div>
|
</div>
|
||||||
) : (
|
);
|
||||||
<span />
|
}
|
||||||
)}
|
|
||||||
{this.state.viewIsLoading ? (
|
|
||||||
<div style={{ textAlign: "center" }}>
|
|
||||||
<CircularProgress className={classes.progress} color="primary" />
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<span />
|
|
||||||
)}
|
|
||||||
<Dialog
|
|
||||||
open={this.state.blockDialogOpen}
|
|
||||||
onClose={() => this.toggleBlockDialog()}
|
|
||||||
>
|
|
||||||
<DialogTitle id="alert-dialog-title">
|
|
||||||
Block this person?
|
|
||||||
</DialogTitle>
|
|
||||||
<DialogContent>
|
|
||||||
<DialogContentText id="alert-dialog-description">
|
|
||||||
Are you sure you want to block this person? You won't see their
|
|
||||||
posts on your home feed, local timeline, or public timeline.
|
|
||||||
</DialogContentText>
|
|
||||||
</DialogContent>
|
|
||||||
<DialogActions>
|
|
||||||
<Button
|
|
||||||
onClick={() => this.toggleBlockDialog()}
|
|
||||||
color="primary"
|
|
||||||
autoFocus
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
this.toggleBlock();
|
|
||||||
this.toggleBlockDialog();
|
|
||||||
}}
|
|
||||||
color="primary"
|
|
||||||
>
|
|
||||||
Block
|
|
||||||
</Button>
|
|
||||||
</DialogActions>
|
|
||||||
</Dialog>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withStyles(styles)(withSnackbar(ProfilePage));
|
export default withStyles(styles)(withSnackbar(ProfilePage));
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import React, { Component } from "react";
|
import React, { Component } from "react";
|
||||||
import {
|
import {
|
||||||
withStyles,
|
withStyles,
|
||||||
CircularProgress,
|
CircularProgress,
|
||||||
Typography,
|
Typography,
|
||||||
Paper,
|
Paper,
|
||||||
Button,
|
Button,
|
||||||
Chip,
|
Chip,
|
||||||
Avatar,
|
Avatar,
|
||||||
Slide
|
Slide
|
||||||
} from "@material-ui/core";
|
} from "@material-ui/core";
|
||||||
import { styles } from "./PageLayout.styles";
|
import { styles } from "./PageLayout.styles";
|
||||||
import Post from "../components/Post";
|
import Post from "../components/Post";
|
||||||
|
@ -17,209 +17,224 @@ import { withSnackbar } from "notistack";
|
||||||
import ArrowUpwardIcon from "@material-ui/icons/ArrowUpward";
|
import ArrowUpwardIcon from "@material-ui/icons/ArrowUpward";
|
||||||
|
|
||||||
interface IPublicPageState {
|
interface IPublicPageState {
|
||||||
posts?: [Status];
|
posts?: [Status];
|
||||||
backlogPosts?: [Status] | null;
|
backlogPosts?: [Status] | null;
|
||||||
viewIsLoading: boolean;
|
viewIsLoading: boolean;
|
||||||
viewDidLoad?: boolean;
|
viewDidLoad?: boolean;
|
||||||
viewDidError?: boolean;
|
viewDidError?: boolean;
|
||||||
viewDidErrorCode?: any;
|
viewDidErrorCode?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
class PublicPage extends Component<any, IPublicPageState> {
|
class PublicPage extends Component<any, IPublicPageState> {
|
||||||
client: Mastodon;
|
client: Mastodon;
|
||||||
streamListener: StreamListener;
|
streamListener: StreamListener;
|
||||||
|
|
||||||
constructor(props: any) {
|
constructor(props: any) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
viewIsLoading: true,
|
viewIsLoading: true,
|
||||||
backlogPosts: null
|
backlogPosts: null
|
||||||
};
|
};
|
||||||
|
|
||||||
this.client = new Mastodon(
|
this.client = new Mastodon(
|
||||||
localStorage.getItem("access_token") as string,
|
localStorage.getItem("access_token") as string,
|
||||||
(localStorage.getItem("baseurl") as string) + "/api/v1"
|
(localStorage.getItem("baseurl") as string) + "/api/v1"
|
||||||
);
|
);
|
||||||
this.streamListener = this.client.stream("/streaming/public");
|
this.streamListener = this.client.stream("/streaming/public");
|
||||||
}
|
|
||||||
|
|
||||||
componentWillMount() {
|
|
||||||
this.streamListener.on("connect", () => {
|
|
||||||
this.client
|
|
||||||
.get("/timelines/public", { limit: 40 })
|
|
||||||
.then((resp: any) => {
|
|
||||||
let statuses: [Status] = resp.data;
|
|
||||||
this.setState({
|
|
||||||
posts: statuses,
|
|
||||||
viewIsLoading: false,
|
|
||||||
viewDidLoad: true,
|
|
||||||
viewDidError: false
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch((resp: any) => {
|
|
||||||
this.setState({
|
|
||||||
viewIsLoading: false,
|
|
||||||
viewDidLoad: true,
|
|
||||||
viewDidError: true,
|
|
||||||
viewDidErrorCode: String(resp)
|
|
||||||
});
|
|
||||||
this.props.enqueueSnackbar("Failed to get posts.", {
|
|
||||||
variant: "error"
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
this.streamListener.on("update", (status: Status) => {
|
|
||||||
let queue = this.state.backlogPosts;
|
|
||||||
if (queue !== null && queue !== undefined) {
|
|
||||||
queue.unshift(status);
|
|
||||||
} else {
|
|
||||||
queue = [status];
|
|
||||||
}
|
|
||||||
this.setState({ backlogPosts: queue });
|
|
||||||
});
|
|
||||||
|
|
||||||
this.streamListener.on("delete", (id: number) => {
|
|
||||||
let posts = this.state.posts;
|
|
||||||
if (posts) {
|
|
||||||
posts.forEach((post: Status) => {
|
|
||||||
if (posts && parseInt(post.id) === id) {
|
|
||||||
posts.splice(posts.indexOf(post), 1);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.setState({ posts });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.streamListener.on("error", (err: Error) => {
|
|
||||||
this.setState({
|
|
||||||
viewDidError: true,
|
|
||||||
viewDidErrorCode: err.message
|
|
||||||
});
|
|
||||||
this.props.enqueueSnackbar("An error occured.", {
|
|
||||||
variant: "error"
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
this.streamListener.on("heartbeat", () => {});
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
this.streamListener.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
insertBacklog() {
|
|
||||||
window.scrollTo(0, 0);
|
|
||||||
let posts = this.state.posts;
|
|
||||||
let backlog = this.state.backlogPosts;
|
|
||||||
if (posts && backlog && backlog.length > 0) {
|
|
||||||
let push = backlog.concat(posts);
|
|
||||||
this.setState({ posts: push as [Status], backlogPosts: null });
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
loadMoreTimelinePieces() {
|
componentWillMount() {
|
||||||
this.setState({ viewDidLoad: false, viewIsLoading: true });
|
this.streamListener.on("connect", () => {
|
||||||
if (this.state.posts) {
|
this.client
|
||||||
this.client
|
.get("/timelines/public", { limit: 40 })
|
||||||
.get("/timelines/public", {
|
.then((resp: any) => {
|
||||||
max_id: this.state.posts[this.state.posts.length - 1].id,
|
let statuses: [Status] = resp.data;
|
||||||
limit: 20
|
this.setState({
|
||||||
})
|
posts: statuses,
|
||||||
.then((resp: any) => {
|
viewIsLoading: false,
|
||||||
let newPosts: [Status] = resp.data;
|
viewDidLoad: true,
|
||||||
let posts = this.state.posts as [Status];
|
viewDidError: false
|
||||||
newPosts.forEach((post: Status) => {
|
});
|
||||||
posts.push(post);
|
})
|
||||||
});
|
.catch((resp: any) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
viewIsLoading: false,
|
viewIsLoading: false,
|
||||||
viewDidLoad: true,
|
viewDidLoad: true,
|
||||||
posts
|
viewDidError: true,
|
||||||
});
|
viewDidErrorCode: String(resp)
|
||||||
})
|
});
|
||||||
.catch((err: Error) => {
|
this.props.enqueueSnackbar("Failed to get posts.", {
|
||||||
this.setState({
|
variant: "error"
|
||||||
viewIsLoading: false,
|
});
|
||||||
viewDidError: true,
|
});
|
||||||
viewDidErrorCode: err.message
|
|
||||||
});
|
|
||||||
this.props.enqueueSnackbar("Failed to get posts", {
|
|
||||||
variant: "error"
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.streamListener.on("update", (status: Status) => {
|
||||||
|
let queue = this.state.backlogPosts;
|
||||||
|
if (queue !== null && queue !== undefined) {
|
||||||
|
queue.unshift(status);
|
||||||
|
} else {
|
||||||
|
queue = [status];
|
||||||
|
}
|
||||||
|
this.setState({ backlogPosts: queue });
|
||||||
|
});
|
||||||
|
|
||||||
|
this.streamListener.on("delete", (id: number) => {
|
||||||
|
let posts = this.state.posts;
|
||||||
|
if (posts) {
|
||||||
|
posts.forEach((post: Status) => {
|
||||||
|
if (posts && parseInt(post.id) === id) {
|
||||||
|
posts.splice(posts.indexOf(post), 1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.setState({ posts });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.streamListener.on("error", (err: Error) => {
|
||||||
|
this.setState({
|
||||||
|
viewDidError: true,
|
||||||
|
viewDidErrorCode: err.message
|
||||||
|
});
|
||||||
|
this.props.enqueueSnackbar("An error occured.", {
|
||||||
|
variant: "error"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this.streamListener.on("heartbeat", () => {});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
componentWillUnmount() {
|
||||||
const { classes } = this.props;
|
this.streamListener.stop();
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
insertBacklog() {
|
||||||
<div className={classes.pageLayoutMaxConstraints}>
|
window.scrollTo(0, 0);
|
||||||
{this.state.backlogPosts ? (
|
let posts = this.state.posts;
|
||||||
<div className={classes.pageTopChipContainer}>
|
let backlog = this.state.backlogPosts;
|
||||||
<div className={classes.pageTopChips}>
|
if (posts && backlog && backlog.length > 0) {
|
||||||
<Slide direction="down" in={true}>
|
let push = backlog.concat(posts);
|
||||||
<Chip
|
this.setState({ posts: push as [Status], backlogPosts: null });
|
||||||
avatar={
|
}
|
||||||
<Avatar>
|
}
|
||||||
<ArrowUpwardIcon />
|
|
||||||
</Avatar>
|
loadMoreTimelinePieces() {
|
||||||
}
|
this.setState({ viewDidLoad: false, viewIsLoading: true });
|
||||||
label={`View ${this.state.backlogPosts.length} new post${
|
if (this.state.posts) {
|
||||||
this.state.backlogPosts.length > 1 ? "s" : ""
|
this.client
|
||||||
}`}
|
.get("/timelines/public", {
|
||||||
color="primary"
|
max_id: this.state.posts[this.state.posts.length - 1].id,
|
||||||
className={classes.pageTopChip}
|
limit: 20
|
||||||
onClick={() => this.insertBacklog()}
|
})
|
||||||
clickable
|
.then((resp: any) => {
|
||||||
/>
|
let newPosts: [Status] = resp.data;
|
||||||
</Slide>
|
let posts = this.state.posts as [Status];
|
||||||
|
newPosts.forEach((post: Status) => {
|
||||||
|
posts.push(post);
|
||||||
|
});
|
||||||
|
this.setState({
|
||||||
|
viewIsLoading: false,
|
||||||
|
viewDidLoad: true,
|
||||||
|
posts
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((err: Error) => {
|
||||||
|
this.setState({
|
||||||
|
viewIsLoading: false,
|
||||||
|
viewDidError: true,
|
||||||
|
viewDidErrorCode: err.message
|
||||||
|
});
|
||||||
|
this.props.enqueueSnackbar("Failed to get posts", {
|
||||||
|
variant: "error"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { classes } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classes.pageLayoutMaxConstraints}>
|
||||||
|
{this.state.backlogPosts ? (
|
||||||
|
<div className={classes.pageTopChipContainer}>
|
||||||
|
<div className={classes.pageTopChips}>
|
||||||
|
<Slide direction="down" in={true}>
|
||||||
|
<Chip
|
||||||
|
avatar={
|
||||||
|
<Avatar>
|
||||||
|
<ArrowUpwardIcon />
|
||||||
|
</Avatar>
|
||||||
|
}
|
||||||
|
label={`View ${
|
||||||
|
this.state.backlogPosts.length
|
||||||
|
} new post${
|
||||||
|
this.state.backlogPosts.length > 1
|
||||||
|
? "s"
|
||||||
|
: ""
|
||||||
|
}`}
|
||||||
|
color="primary"
|
||||||
|
className={classes.pageTopChip}
|
||||||
|
onClick={() => this.insertBacklog()}
|
||||||
|
clickable
|
||||||
|
/>
|
||||||
|
</Slide>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
{this.state.posts ? (
|
||||||
|
<div>
|
||||||
|
{this.state.posts.map((post: Status) => {
|
||||||
|
return (
|
||||||
|
<Post
|
||||||
|
key={post.id}
|
||||||
|
post={post}
|
||||||
|
client={this.client}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
<br />
|
||||||
|
{this.state.viewDidLoad && !this.state.viewDidError ? (
|
||||||
|
<div
|
||||||
|
style={{ textAlign: "center" }}
|
||||||
|
onClick={() => this.loadMoreTimelinePieces()}
|
||||||
|
>
|
||||||
|
<Button variant="contained">Load more</Button>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<span />
|
||||||
|
)}
|
||||||
|
{this.state.viewDidError ? (
|
||||||
|
<Paper className={classes.errorCard}>
|
||||||
|
<Typography variant="h4">Bummer.</Typography>
|
||||||
|
<Typography variant="h6">
|
||||||
|
Something went wrong when loading this timeline.
|
||||||
|
</Typography>
|
||||||
|
<Typography>
|
||||||
|
{this.state.viewDidErrorCode
|
||||||
|
? this.state.viewDidErrorCode
|
||||||
|
: ""}
|
||||||
|
</Typography>
|
||||||
|
</Paper>
|
||||||
|
) : (
|
||||||
|
<span />
|
||||||
|
)}
|
||||||
|
{this.state.viewIsLoading ? (
|
||||||
|
<div style={{ textAlign: "center" }}>
|
||||||
|
<CircularProgress
|
||||||
|
className={classes.progress}
|
||||||
|
color="primary"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<span />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
);
|
||||||
) : null}
|
}
|
||||||
{this.state.posts ? (
|
|
||||||
<div>
|
|
||||||
{this.state.posts.map((post: Status) => {
|
|
||||||
return <Post key={post.id} post={post} client={this.client} />;
|
|
||||||
})}
|
|
||||||
<br />
|
|
||||||
{this.state.viewDidLoad && !this.state.viewDidError ? (
|
|
||||||
<div
|
|
||||||
style={{ textAlign: "center" }}
|
|
||||||
onClick={() => this.loadMoreTimelinePieces()}
|
|
||||||
>
|
|
||||||
<Button variant="contained">Load more</Button>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<span />
|
|
||||||
)}
|
|
||||||
{this.state.viewDidError ? (
|
|
||||||
<Paper className={classes.errorCard}>
|
|
||||||
<Typography variant="h4">Bummer.</Typography>
|
|
||||||
<Typography variant="h6">
|
|
||||||
Something went wrong when loading this timeline.
|
|
||||||
</Typography>
|
|
||||||
<Typography>
|
|
||||||
{this.state.viewDidErrorCode ? this.state.viewDidErrorCode : ""}
|
|
||||||
</Typography>
|
|
||||||
</Paper>
|
|
||||||
) : (
|
|
||||||
<span />
|
|
||||||
)}
|
|
||||||
{this.state.viewIsLoading ? (
|
|
||||||
<div style={{ textAlign: "center" }}>
|
|
||||||
<CircularProgress className={classes.progress} color="primary" />
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<span />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withStyles(styles)(withSnackbar(PublicPage));
|
export default withStyles(styles)(withSnackbar(PublicPage));
|
||||||
|
|
|
@ -1,19 +1,19 @@
|
||||||
import React, { Component } from "react";
|
import React, { Component } from "react";
|
||||||
import {
|
import {
|
||||||
withStyles,
|
withStyles,
|
||||||
Typography,
|
Typography,
|
||||||
List,
|
List,
|
||||||
ListItem,
|
ListItem,
|
||||||
Paper,
|
Paper,
|
||||||
ListItemText,
|
ListItemText,
|
||||||
Avatar,
|
Avatar,
|
||||||
ListItemSecondaryAction,
|
ListItemSecondaryAction,
|
||||||
ListItemAvatar,
|
ListItemAvatar,
|
||||||
ListSubheader,
|
ListSubheader,
|
||||||
CircularProgress,
|
CircularProgress,
|
||||||
IconButton,
|
IconButton,
|
||||||
Divider,
|
Divider,
|
||||||
Tooltip
|
Tooltip
|
||||||
} from "@material-ui/core";
|
} from "@material-ui/core";
|
||||||
import { styles } from "./PageLayout.styles";
|
import { styles } from "./PageLayout.styles";
|
||||||
import Mastodon from "megalodon";
|
import Mastodon from "megalodon";
|
||||||
|
@ -27,286 +27,339 @@ import CloseIcon from "@material-ui/icons/Close";
|
||||||
import { withSnackbar, withSnackbarProps } from "notistack";
|
import { withSnackbar, withSnackbarProps } from "notistack";
|
||||||
|
|
||||||
interface IRecommendationsPageProps extends withSnackbarProps {
|
interface IRecommendationsPageProps extends withSnackbarProps {
|
||||||
classes: any;
|
classes: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IRecommendationsPageState {
|
interface IRecommendationsPageState {
|
||||||
viewIsLoading: boolean;
|
viewIsLoading: boolean;
|
||||||
viewDidLoad?: boolean;
|
viewDidLoad?: boolean;
|
||||||
viewDidError?: Boolean;
|
viewDidError?: Boolean;
|
||||||
viewDidErrorCode?: string;
|
viewDidErrorCode?: string;
|
||||||
requestedFollows?: [Account];
|
requestedFollows?: [Account];
|
||||||
followSuggestions?: [Account];
|
followSuggestions?: [Account];
|
||||||
}
|
}
|
||||||
|
|
||||||
class RecommendationsPage extends Component<
|
class RecommendationsPage extends Component<
|
||||||
IRecommendationsPageProps,
|
IRecommendationsPageProps,
|
||||||
IRecommendationsPageState
|
IRecommendationsPageState
|
||||||
> {
|
> {
|
||||||
client: Mastodon;
|
client: Mastodon;
|
||||||
|
|
||||||
constructor(props: any) {
|
constructor(props: any) {
|
||||||
super(props);
|
super(props);
|
||||||
this.client = new Mastodon(
|
this.client = new Mastodon(
|
||||||
localStorage.getItem("access_token") as string,
|
localStorage.getItem("access_token") as string,
|
||||||
localStorage.getItem("baseurl") + "/api/v1"
|
localStorage.getItem("baseurl") + "/api/v1"
|
||||||
);
|
|
||||||
this.state = {
|
|
||||||
viewIsLoading: true
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
this.client
|
|
||||||
.get("/follow_requests")
|
|
||||||
.then((resp: any) => {
|
|
||||||
let requestedFollows: [Account] = resp.data;
|
|
||||||
this.setState({ requestedFollows });
|
|
||||||
})
|
|
||||||
.catch((err: Error) => {
|
|
||||||
this.setState({
|
|
||||||
viewIsLoading: false,
|
|
||||||
viewDidError: true,
|
|
||||||
viewDidErrorCode: err.name
|
|
||||||
});
|
|
||||||
console.error(err.message);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.client
|
|
||||||
.get("/suggestions")
|
|
||||||
.then((resp: any) => {
|
|
||||||
let followSuggestions: [Account] = resp.data;
|
|
||||||
this.setState({
|
|
||||||
viewIsLoading: false,
|
|
||||||
viewDidLoad: true,
|
|
||||||
followSuggestions
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch((err: Error) => {
|
|
||||||
this.setState({
|
|
||||||
viewIsLoading: false,
|
|
||||||
viewDidError: true,
|
|
||||||
viewDidErrorCode: err.name
|
|
||||||
});
|
|
||||||
console.error(err.message);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
followMember(acct: Account) {
|
|
||||||
this.client
|
|
||||||
.post(`/accounts/${acct.id}/follow`)
|
|
||||||
.then((resp: any) => {
|
|
||||||
this.props.enqueueSnackbar("You are now following this account.");
|
|
||||||
this.client.del(`/suggestions/${acct.id}`).then((resp: any) => {
|
|
||||||
let followSuggestions = this.state.followSuggestions;
|
|
||||||
if (followSuggestions) {
|
|
||||||
followSuggestions.forEach((suggestion: Account, index: number) => {
|
|
||||||
if (followSuggestions && suggestion.id === acct.id) {
|
|
||||||
followSuggestions.splice(index, 1);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.setState({ followSuggestions });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch((err: Error) => {
|
|
||||||
this.props.enqueueSnackbar("Couldn't follow account: " + err.name, {
|
|
||||||
variant: "error"
|
|
||||||
});
|
|
||||||
console.error(err.message);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
handleFollowRequest(acct: Account, type: "authorize" | "reject") {
|
|
||||||
this.client
|
|
||||||
.post(`/follow_requests/${acct.id}/${type}`)
|
|
||||||
.then((resp: any) => {
|
|
||||||
let requestedFollows = this.state.requestedFollows;
|
|
||||||
if (requestedFollows) {
|
|
||||||
requestedFollows.forEach((request: Account, index: number) => {
|
|
||||||
if (requestedFollows && request.id === acct.id) {
|
|
||||||
requestedFollows.splice(index, 1);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
this.setState({ requestedFollows });
|
|
||||||
|
|
||||||
let verb: string = type;
|
|
||||||
verb === "authorize" ? (verb = "authorized") : (verb = "rejected");
|
|
||||||
this.props.enqueueSnackbar(`You have ${verb} this request.`);
|
|
||||||
})
|
|
||||||
.catch((err: Error) => {
|
|
||||||
this.props.enqueueSnackbar(
|
|
||||||
`Couldn't ${type} this request: ${err.name}`,
|
|
||||||
{ variant: "error" }
|
|
||||||
);
|
);
|
||||||
console.error(err.message);
|
this.state = {
|
||||||
});
|
viewIsLoading: true
|
||||||
}
|
};
|
||||||
|
}
|
||||||
|
|
||||||
showFollowRequests() {
|
componentDidMount() {
|
||||||
const { classes } = this.props;
|
this.client
|
||||||
return (
|
.get("/follow_requests")
|
||||||
<div>
|
.then((resp: any) => {
|
||||||
<ListSubheader>Follow requests</ListSubheader>
|
let requestedFollows: [Account] = resp.data;
|
||||||
<Paper className={classes.pageListConstraints}>
|
this.setState({ requestedFollows });
|
||||||
<List>
|
})
|
||||||
{this.state.requestedFollows
|
.catch((err: Error) => {
|
||||||
? this.state.requestedFollows.map((request: Account) => {
|
this.setState({
|
||||||
return (
|
viewIsLoading: false,
|
||||||
<ListItem key={request.id}>
|
viewDidError: true,
|
||||||
<ListItemAvatar>
|
viewDidErrorCode: err.name
|
||||||
<LinkableAvatar
|
});
|
||||||
to={`/profile/${request.id}`}
|
console.error(err.message);
|
||||||
alt={request.username}
|
});
|
||||||
src={request.avatar_static}
|
|
||||||
/>
|
this.client
|
||||||
</ListItemAvatar>
|
.get("/suggestions")
|
||||||
<ListItemText
|
.then((resp: any) => {
|
||||||
primary={request.display_name || request.acct}
|
let followSuggestions: [Account] = resp.data;
|
||||||
secondary={request.acct}
|
this.setState({
|
||||||
/>
|
viewIsLoading: false,
|
||||||
<ListItemSecondaryAction>
|
viewDidLoad: true,
|
||||||
<Tooltip title="Accept request">
|
followSuggestions
|
||||||
<IconButton
|
});
|
||||||
onClick={() =>
|
})
|
||||||
this.handleFollowRequest(request, "authorize")
|
.catch((err: Error) => {
|
||||||
|
this.setState({
|
||||||
|
viewIsLoading: false,
|
||||||
|
viewDidError: true,
|
||||||
|
viewDidErrorCode: err.name
|
||||||
|
});
|
||||||
|
console.error(err.message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
followMember(acct: Account) {
|
||||||
|
this.client
|
||||||
|
.post(`/accounts/${acct.id}/follow`)
|
||||||
|
.then((resp: any) => {
|
||||||
|
this.props.enqueueSnackbar(
|
||||||
|
"You are now following this account."
|
||||||
|
);
|
||||||
|
this.client.del(`/suggestions/${acct.id}`).then((resp: any) => {
|
||||||
|
let followSuggestions = this.state.followSuggestions;
|
||||||
|
if (followSuggestions) {
|
||||||
|
followSuggestions.forEach(
|
||||||
|
(suggestion: Account, index: number) => {
|
||||||
|
if (
|
||||||
|
followSuggestions &&
|
||||||
|
suggestion.id === acct.id
|
||||||
|
) {
|
||||||
|
followSuggestions.splice(index, 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
>
|
);
|
||||||
<CheckIcon />
|
this.setState({ followSuggestions });
|
||||||
</IconButton>
|
}
|
||||||
</Tooltip>
|
});
|
||||||
<Tooltip title="Reject request">
|
})
|
||||||
<IconButton
|
.catch((err: Error) => {
|
||||||
onClick={() =>
|
this.props.enqueueSnackbar(
|
||||||
this.handleFollowRequest(request, "reject")
|
"Couldn't follow account: " + err.name,
|
||||||
|
{ variant: "error" }
|
||||||
|
);
|
||||||
|
console.error(err.message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleFollowRequest(acct: Account, type: "authorize" | "reject") {
|
||||||
|
this.client
|
||||||
|
.post(`/follow_requests/${acct.id}/${type}`)
|
||||||
|
.then((resp: any) => {
|
||||||
|
let requestedFollows = this.state.requestedFollows;
|
||||||
|
if (requestedFollows) {
|
||||||
|
requestedFollows.forEach(
|
||||||
|
(request: Account, index: number) => {
|
||||||
|
if (requestedFollows && request.id === acct.id) {
|
||||||
|
requestedFollows.splice(index, 1);
|
||||||
}
|
}
|
||||||
>
|
}
|
||||||
<CloseIcon />
|
);
|
||||||
</IconButton>
|
}
|
||||||
</Tooltip>
|
this.setState({ requestedFollows });
|
||||||
<Tooltip title="View profile">
|
|
||||||
<LinkableIconButton to={`/profile/${request.id}`}>
|
|
||||||
<AccountCircleIcon />
|
|
||||||
</LinkableIconButton>
|
|
||||||
</Tooltip>
|
|
||||||
</ListItemSecondaryAction>
|
|
||||||
</ListItem>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
: null}
|
|
||||||
</List>
|
|
||||||
</Paper>
|
|
||||||
<br />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
showFollowSuggestions() {
|
let verb: string = type;
|
||||||
const { classes } = this.props;
|
verb === "authorize"
|
||||||
return (
|
? (verb = "authorized")
|
||||||
<div>
|
: (verb = "rejected");
|
||||||
<ListSubheader>Suggested accounts</ListSubheader>
|
this.props.enqueueSnackbar(`You have ${verb} this request.`);
|
||||||
<Paper className={classes.pageListConstraints}>
|
})
|
||||||
<List>
|
.catch((err: Error) => {
|
||||||
{this.state.followSuggestions
|
this.props.enqueueSnackbar(
|
||||||
? this.state.followSuggestions.map((suggestion: Account) => {
|
`Couldn't ${type} this request: ${err.name}`,
|
||||||
return (
|
{ variant: "error" }
|
||||||
<ListItem key={suggestion.id}>
|
);
|
||||||
<ListItemAvatar>
|
console.error(err.message);
|
||||||
<LinkableAvatar
|
});
|
||||||
to={`/profile/${suggestion.id}`}
|
}
|
||||||
alt={suggestion.username}
|
|
||||||
src={suggestion.avatar_static}
|
|
||||||
/>
|
|
||||||
</ListItemAvatar>
|
|
||||||
<ListItemText
|
|
||||||
primary={suggestion.display_name || suggestion.acct}
|
|
||||||
secondary={suggestion.acct}
|
|
||||||
/>
|
|
||||||
<ListItemSecondaryAction>
|
|
||||||
<Tooltip title="View profile">
|
|
||||||
<LinkableIconButton to={`/profile/${suggestion.id}`}>
|
|
||||||
<AssignmentIndIcon />
|
|
||||||
</LinkableIconButton>
|
|
||||||
</Tooltip>
|
|
||||||
<Tooltip title="Follow">
|
|
||||||
<IconButton
|
|
||||||
onClick={() => this.followMember(suggestion)}
|
|
||||||
>
|
|
||||||
<PersonAddIcon />
|
|
||||||
</IconButton>
|
|
||||||
</Tooltip>
|
|
||||||
</ListItemSecondaryAction>
|
|
||||||
</ListItem>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
: null}
|
|
||||||
</List>
|
|
||||||
</Paper>
|
|
||||||
<br />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
showFollowRequests() {
|
||||||
const { classes } = this.props;
|
const { classes } = this.props;
|
||||||
return (
|
return (
|
||||||
<div className={classes.pageLayoutConstraints}>
|
<div>
|
||||||
{this.state.viewDidLoad ? (
|
<ListSubheader>Follow requests</ListSubheader>
|
||||||
<div>
|
<Paper className={classes.pageListConstraints}>
|
||||||
{this.state.requestedFollows &&
|
<List>
|
||||||
this.state.requestedFollows.length > 0 ? (
|
{this.state.requestedFollows
|
||||||
this.showFollowRequests()
|
? this.state.requestedFollows.map(
|
||||||
) : (
|
(request: Account) => {
|
||||||
<div className={classes.pageLayoutEmptyTextConstraints}>
|
return (
|
||||||
<Typography variant="h6">
|
<ListItem key={request.id}>
|
||||||
You don't have any follow requests.
|
<ListItemAvatar>
|
||||||
</Typography>
|
<LinkableAvatar
|
||||||
|
to={`/profile/${request.id}`}
|
||||||
|
alt={request.username}
|
||||||
|
src={
|
||||||
|
request.avatar_static
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</ListItemAvatar>
|
||||||
|
<ListItemText
|
||||||
|
primary={
|
||||||
|
request.display_name ||
|
||||||
|
request.acct
|
||||||
|
}
|
||||||
|
secondary={request.acct}
|
||||||
|
/>
|
||||||
|
<ListItemSecondaryAction>
|
||||||
|
<Tooltip title="Accept request">
|
||||||
|
<IconButton
|
||||||
|
onClick={() =>
|
||||||
|
this.handleFollowRequest(
|
||||||
|
request,
|
||||||
|
"authorize"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<CheckIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip title="Reject request">
|
||||||
|
<IconButton
|
||||||
|
onClick={() =>
|
||||||
|
this.handleFollowRequest(
|
||||||
|
request,
|
||||||
|
"reject"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<CloseIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip title="View profile">
|
||||||
|
<LinkableIconButton
|
||||||
|
to={`/profile/${request.id}`}
|
||||||
|
>
|
||||||
|
<AccountCircleIcon />
|
||||||
|
</LinkableIconButton>
|
||||||
|
</Tooltip>
|
||||||
|
</ListItemSecondaryAction>
|
||||||
|
</ListItem>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
: null}
|
||||||
|
</List>
|
||||||
|
</Paper>
|
||||||
<br />
|
<br />
|
||||||
</div>
|
</div>
|
||||||
)}
|
);
|
||||||
<Divider />
|
}
|
||||||
<br />
|
|
||||||
{this.state.followSuggestions &&
|
showFollowSuggestions() {
|
||||||
this.state.followSuggestions.length > 0 ? (
|
const { classes } = this.props;
|
||||||
this.showFollowSuggestions()
|
return (
|
||||||
) : (
|
<div>
|
||||||
<div className={classes.pageLayoutEmptyTextConstraints}>
|
<ListSubheader>Suggested accounts</ListSubheader>
|
||||||
<Typography variant="h5">
|
<Paper className={classes.pageListConstraints}>
|
||||||
We don't have any suggestions for you.
|
<List>
|
||||||
</Typography>
|
{this.state.followSuggestions
|
||||||
<Typography paragraph>
|
? this.state.followSuggestions.map(
|
||||||
Why not interact with the fediverse a bit by creating a new
|
(suggestion: Account) => {
|
||||||
post?
|
return (
|
||||||
</Typography>
|
<ListItem key={suggestion.id}>
|
||||||
</div>
|
<ListItemAvatar>
|
||||||
)}
|
<LinkableAvatar
|
||||||
</div>
|
to={`/profile/${suggestion.id}`}
|
||||||
) : null}
|
alt={suggestion.username}
|
||||||
{this.state.viewDidError ? (
|
src={
|
||||||
<Paper className={classes.errorCard}>
|
suggestion.avatar_static
|
||||||
<Typography variant="h4">Bummer.</Typography>
|
}
|
||||||
<Typography variant="h6">
|
/>
|
||||||
Something went wrong when loading this timeline.
|
</ListItemAvatar>
|
||||||
</Typography>
|
<ListItemText
|
||||||
<Typography>
|
primary={
|
||||||
{this.state.viewDidErrorCode ? this.state.viewDidErrorCode : ""}
|
suggestion.display_name ||
|
||||||
</Typography>
|
suggestion.acct
|
||||||
</Paper>
|
}
|
||||||
) : (
|
secondary={suggestion.acct}
|
||||||
<span />
|
/>
|
||||||
)}
|
<ListItemSecondaryAction>
|
||||||
{this.state.viewIsLoading ? (
|
<Tooltip title="View profile">
|
||||||
<div style={{ textAlign: "center" }}>
|
<LinkableIconButton
|
||||||
<CircularProgress className={classes.progress} color="primary" />
|
to={`/profile/${suggestion.id}`}
|
||||||
</div>
|
>
|
||||||
) : (
|
<AssignmentIndIcon />
|
||||||
<span />
|
</LinkableIconButton>
|
||||||
)}
|
</Tooltip>
|
||||||
</div>
|
<Tooltip title="Follow">
|
||||||
);
|
<IconButton
|
||||||
}
|
onClick={() =>
|
||||||
|
this.followMember(
|
||||||
|
suggestion
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<PersonAddIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
</ListItemSecondaryAction>
|
||||||
|
</ListItem>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
: null}
|
||||||
|
</List>
|
||||||
|
</Paper>
|
||||||
|
<br />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { classes } = this.props;
|
||||||
|
return (
|
||||||
|
<div className={classes.pageLayoutConstraints}>
|
||||||
|
{this.state.viewDidLoad ? (
|
||||||
|
<div>
|
||||||
|
{this.state.requestedFollows &&
|
||||||
|
this.state.requestedFollows.length > 0 ? (
|
||||||
|
this.showFollowRequests()
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
classes.pageLayoutEmptyTextConstraints
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Typography variant="h6">
|
||||||
|
You don't have any follow requests.
|
||||||
|
</Typography>
|
||||||
|
<br />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<Divider />
|
||||||
|
<br />
|
||||||
|
{this.state.followSuggestions &&
|
||||||
|
this.state.followSuggestions.length > 0 ? (
|
||||||
|
this.showFollowSuggestions()
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
classes.pageLayoutEmptyTextConstraints
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Typography variant="h5">
|
||||||
|
We don't have any suggestions for you.
|
||||||
|
</Typography>
|
||||||
|
<Typography paragraph>
|
||||||
|
Why not interact with the fediverse a bit by
|
||||||
|
creating a new post?
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
{this.state.viewDidError ? (
|
||||||
|
<Paper className={classes.errorCard}>
|
||||||
|
<Typography variant="h4">Bummer.</Typography>
|
||||||
|
<Typography variant="h6">
|
||||||
|
Something went wrong when loading this timeline.
|
||||||
|
</Typography>
|
||||||
|
<Typography>
|
||||||
|
{this.state.viewDidErrorCode
|
||||||
|
? this.state.viewDidErrorCode
|
||||||
|
: ""}
|
||||||
|
</Typography>
|
||||||
|
</Paper>
|
||||||
|
) : (
|
||||||
|
<span />
|
||||||
|
)}
|
||||||
|
{this.state.viewIsLoading ? (
|
||||||
|
<div style={{ textAlign: "center" }}>
|
||||||
|
<CircularProgress
|
||||||
|
className={classes.progress}
|
||||||
|
color="primary"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<span />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withStyles(styles)(withSnackbar(RecommendationsPage));
|
export default withStyles(styles)(withSnackbar(RecommendationsPage));
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
import React, { Component } from "react";
|
import React, { Component } from "react";
|
||||||
import {
|
import {
|
||||||
List,
|
List,
|
||||||
ListItem,
|
ListItem,
|
||||||
ListItemText,
|
ListItemText,
|
||||||
ListSubheader,
|
ListSubheader,
|
||||||
ListItemSecondaryAction,
|
ListItemSecondaryAction,
|
||||||
ListItemAvatar,
|
ListItemAvatar,
|
||||||
Avatar,
|
Avatar,
|
||||||
Paper,
|
Paper,
|
||||||
withStyles,
|
withStyles,
|
||||||
Typography,
|
Typography,
|
||||||
CircularProgress,
|
CircularProgress,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
IconButton
|
IconButton
|
||||||
} from "@material-ui/core";
|
} from "@material-ui/core";
|
||||||
import PersonIcon from "@material-ui/icons/Person";
|
import PersonIcon from "@material-ui/icons/Person";
|
||||||
import AssignmentIndIcon from "@material-ui/icons/AssignmentInd";
|
import AssignmentIndIcon from "@material-ui/icons/AssignmentInd";
|
||||||
|
@ -28,304 +28,336 @@ import { Status } from "../types/Status";
|
||||||
import { Account } from "../types/Account";
|
import { Account } from "../types/Account";
|
||||||
|
|
||||||
interface ISearchPageState {
|
interface ISearchPageState {
|
||||||
query: string[] | string;
|
query: string[] | string;
|
||||||
type?: string[] | string;
|
type?: string[] | string;
|
||||||
results?: Results;
|
results?: Results;
|
||||||
tagResults?: [Status];
|
tagResults?: [Status];
|
||||||
viewIsLoading: boolean;
|
viewIsLoading: boolean;
|
||||||
viewDidLoad?: boolean;
|
viewDidLoad?: boolean;
|
||||||
viewDidError?: boolean;
|
viewDidError?: boolean;
|
||||||
viewDidErrorCode?: string;
|
viewDidErrorCode?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
class SearchPage extends Component<any, ISearchPageState> {
|
class SearchPage extends Component<any, ISearchPageState> {
|
||||||
client: Mastodon;
|
client: Mastodon;
|
||||||
|
|
||||||
constructor(props: any) {
|
constructor(props: any) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.client = new Mastodon(
|
this.client = new Mastodon(
|
||||||
localStorage.getItem("access_token") as string,
|
localStorage.getItem("access_token") as string,
|
||||||
localStorage.getItem("baseurl") + "/api/v2"
|
localStorage.getItem("baseurl") + "/api/v2"
|
||||||
);
|
|
||||||
|
|
||||||
let searchParams = this.getQueryAndType(props);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
viewIsLoading: true,
|
|
||||||
query: searchParams.query,
|
|
||||||
type: searchParams.type
|
|
||||||
};
|
|
||||||
|
|
||||||
if (searchParams.type === "tag") {
|
|
||||||
this.searchForPostsWithTags(searchParams.query);
|
|
||||||
} else {
|
|
||||||
this.searchQuery(searchParams.query);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillReceiveProps(props: any) {
|
|
||||||
this.setState({
|
|
||||||
viewDidLoad: false,
|
|
||||||
viewIsLoading: true,
|
|
||||||
viewDidError: false,
|
|
||||||
viewDidErrorCode: "",
|
|
||||||
results: undefined
|
|
||||||
});
|
|
||||||
let searchParams = this.getQueryAndType(props);
|
|
||||||
this.setState({ query: searchParams.query, type: searchParams.type });
|
|
||||||
if (searchParams.type === "tag") {
|
|
||||||
this.searchForPostsWithTags(searchParams.query);
|
|
||||||
} else {
|
|
||||||
this.searchQuery(searchParams.query);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
runQueryCheck(newLocation?: string): ParsedQuery {
|
|
||||||
let searchParams = "";
|
|
||||||
if (newLocation !== undefined && typeof newLocation === "string") {
|
|
||||||
searchParams = newLocation.replace("#/search", "");
|
|
||||||
} else {
|
|
||||||
searchParams = location.hash.replace("#/search", "");
|
|
||||||
}
|
|
||||||
return parseParams(searchParams);
|
|
||||||
}
|
|
||||||
|
|
||||||
getQueryAndType(props: any) {
|
|
||||||
let newSearch = this.runQueryCheck(props.location);
|
|
||||||
let query: string | string[];
|
|
||||||
let type;
|
|
||||||
|
|
||||||
if (newSearch.query) {
|
|
||||||
if (newSearch.query.toString().startsWith("tag:")) {
|
|
||||||
type = "tag";
|
|
||||||
query = newSearch.query.toString().replace("tag:", "");
|
|
||||||
} else {
|
|
||||||
query = newSearch.query;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
query = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newSearch.type && newSearch.type !== undefined) {
|
|
||||||
type = newSearch.type;
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
query: query,
|
|
||||||
type: type
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
searchQuery(query: string | string[]) {
|
|
||||||
this.client
|
|
||||||
.get("/search", { q: query })
|
|
||||||
.then((resp: any) => {
|
|
||||||
let results: Results = resp.data;
|
|
||||||
this.setState({
|
|
||||||
results,
|
|
||||||
viewDidLoad: true,
|
|
||||||
viewIsLoading: false
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch((err: Error) => {
|
|
||||||
this.setState({
|
|
||||||
viewIsLoading: false,
|
|
||||||
viewDidError: true,
|
|
||||||
viewDidErrorCode: err.message
|
|
||||||
});
|
|
||||||
|
|
||||||
this.props.enqueueSnackbar(
|
|
||||||
`Couldn't search for ${this.state.query}: ${err.name}`,
|
|
||||||
{ variant: "error" }
|
|
||||||
);
|
);
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
searchForPostsWithTags(query: string | string[]) {
|
let searchParams = this.getQueryAndType(props);
|
||||||
let client = new Mastodon(
|
|
||||||
localStorage.getItem("access_token") as string,
|
this.state = {
|
||||||
localStorage.getItem("baseurl") + "/api/v1"
|
viewIsLoading: true,
|
||||||
);
|
query: searchParams.query,
|
||||||
client
|
type: searchParams.type
|
||||||
.get(`/timelines/tag/${query}`)
|
};
|
||||||
.then((resp: any) => {
|
|
||||||
let tagResults: [Status] = resp.data;
|
if (searchParams.type === "tag") {
|
||||||
|
this.searchForPostsWithTags(searchParams.query);
|
||||||
|
} else {
|
||||||
|
this.searchQuery(searchParams.query);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps(props: any) {
|
||||||
this.setState({
|
this.setState({
|
||||||
tagResults,
|
viewDidLoad: false,
|
||||||
viewDidLoad: true,
|
viewIsLoading: true,
|
||||||
viewIsLoading: false
|
viewDidError: false,
|
||||||
});
|
viewDidErrorCode: "",
|
||||||
console.log(this.state.tagResults);
|
results: undefined
|
||||||
})
|
|
||||||
.catch((err: Error) => {
|
|
||||||
this.setState({
|
|
||||||
viewIsLoading: false,
|
|
||||||
viewDidError: true,
|
|
||||||
viewDidErrorCode: err.message
|
|
||||||
});
|
});
|
||||||
|
let searchParams = this.getQueryAndType(props);
|
||||||
|
this.setState({ query: searchParams.query, type: searchParams.type });
|
||||||
|
if (searchParams.type === "tag") {
|
||||||
|
this.searchForPostsWithTags(searchParams.query);
|
||||||
|
} else {
|
||||||
|
this.searchQuery(searchParams.query);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.props.enqueueSnackbar(
|
runQueryCheck(newLocation?: string): ParsedQuery {
|
||||||
`Couldn't search for posts with tag ${this.state.query}: ${err.name}`,
|
let searchParams = "";
|
||||||
{ variant: "error" }
|
if (newLocation !== undefined && typeof newLocation === "string") {
|
||||||
);
|
searchParams = newLocation.replace("#/search", "");
|
||||||
});
|
} else {
|
||||||
}
|
searchParams = location.hash.replace("#/search", "");
|
||||||
|
}
|
||||||
|
return parseParams(searchParams);
|
||||||
|
}
|
||||||
|
|
||||||
followMemberFromQuery(acct: Account) {
|
getQueryAndType(props: any) {
|
||||||
let client = new Mastodon(
|
let newSearch = this.runQueryCheck(props.location);
|
||||||
localStorage.getItem("access_token") as string,
|
let query: string | string[];
|
||||||
localStorage.getItem("baseurl") + "/api/v1"
|
let type;
|
||||||
);
|
|
||||||
client
|
|
||||||
.post(`/accounts/${acct.id}/follow`)
|
|
||||||
.then((resp: any) => {
|
|
||||||
this.props.enqueueSnackbar("You are now following this account.");
|
|
||||||
})
|
|
||||||
.catch((err: Error) => {
|
|
||||||
this.props.enqueueSnackbar("Couldn't follow account: " + err.name, {
|
|
||||||
variant: "error"
|
|
||||||
});
|
|
||||||
console.error(err.message);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
showAllAccountsFromQuery() {
|
if (newSearch.query) {
|
||||||
const { classes } = this.props;
|
if (newSearch.query.toString().startsWith("tag:")) {
|
||||||
return (
|
type = "tag";
|
||||||
<div>
|
query = newSearch.query.toString().replace("tag:", "");
|
||||||
<ListSubheader>Accounts</ListSubheader>
|
} else {
|
||||||
|
query = newSearch.query;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
query = "";
|
||||||
|
}
|
||||||
|
|
||||||
{this.state.results && this.state.results.accounts.length > 0 ? (
|
if (newSearch.type && newSearch.type !== undefined) {
|
||||||
<Paper className={classes.pageListConstraints}>
|
type = newSearch.type;
|
||||||
<List>
|
}
|
||||||
{this.state.results.accounts.map((acct: Account) => {
|
return {
|
||||||
return (
|
query: query,
|
||||||
<ListItem key={acct.id}>
|
type: type
|
||||||
<ListItemAvatar>
|
};
|
||||||
<LinkableAvatar
|
}
|
||||||
to={`/profile/${acct.id}`}
|
|
||||||
alt={acct.username}
|
searchQuery(query: string | string[]) {
|
||||||
src={acct.avatar_static}
|
this.client
|
||||||
/>
|
.get("/search", { q: query })
|
||||||
</ListItemAvatar>
|
.then((resp: any) => {
|
||||||
<ListItemText
|
let results: Results = resp.data;
|
||||||
primary={acct.display_name || acct.acct}
|
this.setState({
|
||||||
secondary={acct.acct}
|
results,
|
||||||
/>
|
viewDidLoad: true,
|
||||||
<ListItemSecondaryAction>
|
viewIsLoading: false
|
||||||
<Tooltip title="View profile">
|
});
|
||||||
<LinkableIconButton to={`/profile/${acct.id}`}>
|
})
|
||||||
<AssignmentIndIcon />
|
.catch((err: Error) => {
|
||||||
</LinkableIconButton>
|
this.setState({
|
||||||
</Tooltip>
|
viewIsLoading: false,
|
||||||
<Tooltip title="Follow">
|
viewDidError: true,
|
||||||
<IconButton
|
viewDidErrorCode: err.message
|
||||||
onClick={() => this.followMemberFromQuery(acct)}
|
});
|
||||||
>
|
|
||||||
<PersonAddIcon />
|
this.props.enqueueSnackbar(
|
||||||
</IconButton>
|
`Couldn't search for ${this.state.query}: ${err.name}`,
|
||||||
</Tooltip>
|
{ variant: "error" }
|
||||||
</ListItemSecondaryAction>
|
|
||||||
</ListItem>
|
|
||||||
);
|
);
|
||||||
})}
|
});
|
||||||
</List>
|
}
|
||||||
</Paper>
|
|
||||||
) : (
|
|
||||||
<Typography
|
|
||||||
variant="caption"
|
|
||||||
className={classes.pageLayoutEmptyTextConstraints}
|
|
||||||
>
|
|
||||||
No results found
|
|
||||||
</Typography>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<br />
|
searchForPostsWithTags(query: string | string[]) {
|
||||||
</div>
|
let client = new Mastodon(
|
||||||
);
|
localStorage.getItem("access_token") as string,
|
||||||
}
|
localStorage.getItem("baseurl") + "/api/v1"
|
||||||
|
);
|
||||||
showAllPostsFromQuery() {
|
client
|
||||||
const { classes } = this.props;
|
.get(`/timelines/tag/${query}`)
|
||||||
return (
|
.then((resp: any) => {
|
||||||
<div>
|
let tagResults: [Status] = resp.data;
|
||||||
<ListSubheader>Posts</ListSubheader>
|
this.setState({
|
||||||
{this.state.results ? (
|
tagResults,
|
||||||
this.state.results.statuses.length > 0 ? (
|
viewDidLoad: true,
|
||||||
this.state.results.statuses.map((post: Status) => {
|
viewIsLoading: false
|
||||||
return <Post key={post.id} post={post} client={this.client} />;
|
});
|
||||||
|
console.log(this.state.tagResults);
|
||||||
})
|
})
|
||||||
) : (
|
.catch((err: Error) => {
|
||||||
<Typography
|
this.setState({
|
||||||
variant="caption"
|
viewIsLoading: false,
|
||||||
className={classes.pageLayoutEmptyTextConstraints}
|
viewDidError: true,
|
||||||
>
|
viewDidErrorCode: err.message
|
||||||
No results found.
|
});
|
||||||
</Typography>
|
|
||||||
)
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
showAllPostsWithTag() {
|
this.props.enqueueSnackbar(
|
||||||
const { classes } = this.props;
|
`Couldn't search for posts with tag ${this.state.query}: ${err.name}`,
|
||||||
return (
|
{ variant: "error" }
|
||||||
<div>
|
);
|
||||||
<ListSubheader>Tagged posts</ListSubheader>
|
});
|
||||||
{this.state.tagResults ? (
|
}
|
||||||
this.state.tagResults.length > 0 ? (
|
|
||||||
this.state.tagResults.map((post: Status) => {
|
followMemberFromQuery(acct: Account) {
|
||||||
return <Post key={post.id} post={post} client={this.client} />;
|
let client = new Mastodon(
|
||||||
|
localStorage.getItem("access_token") as string,
|
||||||
|
localStorage.getItem("baseurl") + "/api/v1"
|
||||||
|
);
|
||||||
|
client
|
||||||
|
.post(`/accounts/${acct.id}/follow`)
|
||||||
|
.then((resp: any) => {
|
||||||
|
this.props.enqueueSnackbar(
|
||||||
|
"You are now following this account."
|
||||||
|
);
|
||||||
})
|
})
|
||||||
) : (
|
.catch((err: Error) => {
|
||||||
<Typography
|
this.props.enqueueSnackbar(
|
||||||
variant="caption"
|
"Couldn't follow account: " + err.name,
|
||||||
className={classes.pageLayoutEmptyTextConstraints}
|
{ variant: "error" }
|
||||||
>
|
);
|
||||||
No results found.
|
console.error(err.message);
|
||||||
</Typography>
|
});
|
||||||
)
|
}
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
showAllAccountsFromQuery() {
|
||||||
const { classes } = this.props;
|
const { classes } = this.props;
|
||||||
return (
|
return (
|
||||||
<div className={classes.pageLayoutConstraints}>
|
<div>
|
||||||
{this.state.type && this.state.type === "tag" ? (
|
<ListSubheader>Accounts</ListSubheader>
|
||||||
this.showAllPostsWithTag()
|
|
||||||
) : (
|
{this.state.results &&
|
||||||
<div>
|
this.state.results.accounts.length > 0 ? (
|
||||||
{this.showAllAccountsFromQuery()}
|
<Paper className={classes.pageListConstraints}>
|
||||||
{this.showAllPostsFromQuery()}
|
<List>
|
||||||
</div>
|
{this.state.results.accounts.map(
|
||||||
)}
|
(acct: Account) => {
|
||||||
{this.state.viewDidError ? (
|
return (
|
||||||
<Paper className={classes.errorCard}>
|
<ListItem key={acct.id}>
|
||||||
<Typography variant="h4">Bummer.</Typography>
|
<ListItemAvatar>
|
||||||
<Typography variant="h6">
|
<LinkableAvatar
|
||||||
Something went wrong when loading this timeline.
|
to={`/profile/${acct.id}`}
|
||||||
</Typography>
|
alt={acct.username}
|
||||||
<Typography>
|
src={acct.avatar_static}
|
||||||
{this.state.viewDidErrorCode ? this.state.viewDidErrorCode : ""}
|
/>
|
||||||
</Typography>
|
</ListItemAvatar>
|
||||||
</Paper>
|
<ListItemText
|
||||||
) : (
|
primary={
|
||||||
<span />
|
acct.display_name ||
|
||||||
)}
|
acct.acct
|
||||||
{this.state.viewIsLoading ? (
|
}
|
||||||
<div style={{ textAlign: "center" }}>
|
secondary={acct.acct}
|
||||||
<CircularProgress className={classes.progress} color="primary" />
|
/>
|
||||||
</div>
|
<ListItemSecondaryAction>
|
||||||
) : (
|
<Tooltip title="View profile">
|
||||||
<span />
|
<LinkableIconButton
|
||||||
)}
|
to={`/profile/${acct.id}`}
|
||||||
</div>
|
>
|
||||||
);
|
<AssignmentIndIcon />
|
||||||
}
|
</LinkableIconButton>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip title="Follow">
|
||||||
|
<IconButton
|
||||||
|
onClick={() =>
|
||||||
|
this.followMemberFromQuery(
|
||||||
|
acct
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<PersonAddIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
</ListItemSecondaryAction>
|
||||||
|
</ListItem>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
</List>
|
||||||
|
</Paper>
|
||||||
|
) : (
|
||||||
|
<Typography
|
||||||
|
variant="caption"
|
||||||
|
className={classes.pageLayoutEmptyTextConstraints}
|
||||||
|
>
|
||||||
|
No results found
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<br />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
showAllPostsFromQuery() {
|
||||||
|
const { classes } = this.props;
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<ListSubheader>Posts</ListSubheader>
|
||||||
|
{this.state.results ? (
|
||||||
|
this.state.results.statuses.length > 0 ? (
|
||||||
|
this.state.results.statuses.map((post: Status) => {
|
||||||
|
return (
|
||||||
|
<Post
|
||||||
|
key={post.id}
|
||||||
|
post={post}
|
||||||
|
client={this.client}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
) : (
|
||||||
|
<Typography
|
||||||
|
variant="caption"
|
||||||
|
className={classes.pageLayoutEmptyTextConstraints}
|
||||||
|
>
|
||||||
|
No results found.
|
||||||
|
</Typography>
|
||||||
|
)
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
showAllPostsWithTag() {
|
||||||
|
const { classes } = this.props;
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<ListSubheader>Tagged posts</ListSubheader>
|
||||||
|
{this.state.tagResults ? (
|
||||||
|
this.state.tagResults.length > 0 ? (
|
||||||
|
this.state.tagResults.map((post: Status) => {
|
||||||
|
return (
|
||||||
|
<Post
|
||||||
|
key={post.id}
|
||||||
|
post={post}
|
||||||
|
client={this.client}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
) : (
|
||||||
|
<Typography
|
||||||
|
variant="caption"
|
||||||
|
className={classes.pageLayoutEmptyTextConstraints}
|
||||||
|
>
|
||||||
|
No results found.
|
||||||
|
</Typography>
|
||||||
|
)
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { classes } = this.props;
|
||||||
|
return (
|
||||||
|
<div className={classes.pageLayoutConstraints}>
|
||||||
|
{this.state.type && this.state.type === "tag" ? (
|
||||||
|
this.showAllPostsWithTag()
|
||||||
|
) : (
|
||||||
|
<div>
|
||||||
|
{this.showAllAccountsFromQuery()}
|
||||||
|
{this.showAllPostsFromQuery()}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{this.state.viewDidError ? (
|
||||||
|
<Paper className={classes.errorCard}>
|
||||||
|
<Typography variant="h4">Bummer.</Typography>
|
||||||
|
<Typography variant="h6">
|
||||||
|
Something went wrong when loading this timeline.
|
||||||
|
</Typography>
|
||||||
|
<Typography>
|
||||||
|
{this.state.viewDidErrorCode
|
||||||
|
? this.state.viewDidErrorCode
|
||||||
|
: ""}
|
||||||
|
</Typography>
|
||||||
|
</Paper>
|
||||||
|
) : (
|
||||||
|
<span />
|
||||||
|
)}
|
||||||
|
{this.state.viewIsLoading ? (
|
||||||
|
<div style={{ textAlign: "center" }}>
|
||||||
|
<CircularProgress
|
||||||
|
className={classes.progress}
|
||||||
|
color="primary"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<span />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withStyles(styles)(withSnackbar(SearchPage));
|
export default withStyles(styles)(withSnackbar(SearchPage));
|
||||||
|
|
|
@ -46,7 +46,6 @@ import {
|
||||||
} from "../utilities/themes";
|
} from "../utilities/themes";
|
||||||
import { Visibility } from "../types/Visibility";
|
import { Visibility } from "../types/Visibility";
|
||||||
import { LinkableButton } from "../interfaces/overrides";
|
import { LinkableButton } from "../interfaces/overrides";
|
||||||
import { Config } from "../types/Config";
|
|
||||||
|
|
||||||
import OpenInNewIcon from "@material-ui/icons/OpenInNew";
|
import OpenInNewIcon from "@material-ui/icons/OpenInNew";
|
||||||
import DevicesIcon from "@material-ui/icons/Devices";
|
import DevicesIcon from "@material-ui/icons/Devices";
|
||||||
|
@ -59,7 +58,7 @@ import NotificationsIcon from "@material-ui/icons/Notifications";
|
||||||
import BellAlertIcon from "mdi-material-ui/BellAlert";
|
import BellAlertIcon from "mdi-material-ui/BellAlert";
|
||||||
import RefreshIcon from "@material-ui/icons/Refresh";
|
import RefreshIcon from "@material-ui/icons/Refresh";
|
||||||
import UndoIcon from "@material-ui/icons/Undo";
|
import UndoIcon from "@material-ui/icons/Undo";
|
||||||
import DomainDisablbedIcon from "@material-ui/icons/DomainDisabled";
|
import { Config } from "../types/Config";
|
||||||
|
|
||||||
interface ISettingsState {
|
interface ISettingsState {
|
||||||
darkModeEnabled: boolean;
|
darkModeEnabled: boolean;
|
||||||
|
@ -500,20 +499,6 @@ class SettingsPage extends Component<any, ISettingsState> {
|
||||||
<LinkableButton to="/you">Edit</LinkableButton>
|
<LinkableButton to="/you">Edit</LinkableButton>
|
||||||
</ListItemSecondaryAction>
|
</ListItemSecondaryAction>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
<ListItem>
|
|
||||||
<ListItemAvatar>
|
|
||||||
<DomainDisablbedIcon color="action" />
|
|
||||||
</ListItemAvatar>
|
|
||||||
<ListItemText
|
|
||||||
primary="Manage blocked servers"
|
|
||||||
secondary="View and manage servers that you've blocked"
|
|
||||||
/>
|
|
||||||
<ListItemSecondaryAction>
|
|
||||||
<LinkableButton to="/blocked">
|
|
||||||
Manage
|
|
||||||
</LinkableButton>
|
|
||||||
</ListItemSecondaryAction>
|
|
||||||
</ListItem>
|
|
||||||
<ListItem>
|
<ListItem>
|
||||||
<ListItemAvatar>
|
<ListItemAvatar>
|
||||||
<MastodonIcon color="action" />
|
<MastodonIcon color="action" />
|
||||||
|
|
|
@ -17,11 +17,7 @@ import {
|
||||||
import { styles } from "./WelcomePage.styles";
|
import { styles } from "./WelcomePage.styles";
|
||||||
import Mastodon from "megalodon";
|
import Mastodon from "megalodon";
|
||||||
import { SaveClientSession } from "../types/SessionData";
|
import { SaveClientSession } from "../types/SessionData";
|
||||||
import {
|
import { createHyperspaceApp, getRedirectAddress } from "../utilities/login";
|
||||||
createHyperspaceApp,
|
|
||||||
getRedirectAddress,
|
|
||||||
inDisallowedDomains
|
|
||||||
} from "../utilities/login";
|
|
||||||
import { parseUrl } from "query-string";
|
import { parseUrl } from "query-string";
|
||||||
import { getConfig } from "../utilities/settings";
|
import { getConfig } from "../utilities/settings";
|
||||||
import { isDarwinApp } from "../utilities/desktop";
|
import { isDarwinApp } from "../utilities/desktop";
|
||||||
|
@ -83,17 +79,9 @@ class WelcomePage extends Component<IWelcomeProps, IWelcomeState> {
|
||||||
let config: Config = result;
|
let config: Config = result;
|
||||||
if (result.location === "dynamic") {
|
if (result.location === "dynamic") {
|
||||||
console.warn(
|
console.warn(
|
||||||
"Redirect URI is set to dynamic, which may affect how sign-in works for some users. Careful!"
|
"Recirect URI is set to dynamic, which may affect how sign-in works for some users. Careful!"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (
|
|
||||||
inDisallowedDomains(result.registration.defaultInstance)
|
|
||||||
) {
|
|
||||||
console.warn(
|
|
||||||
`The default instance field in config.json contains an unsupported domain (${result.registration.defaultInstance}), so it's been reset to mastodon.social.`
|
|
||||||
);
|
|
||||||
result.registration.defaultInstance = "mastodon.social";
|
|
||||||
}
|
|
||||||
this.setState({
|
this.setState({
|
||||||
logoUrl: config.branding
|
logoUrl: config.branding
|
||||||
? result.branding.logo
|
? result.branding.logo
|
||||||
|
@ -322,30 +310,18 @@ class WelcomePage extends Component<IWelcomeProps, IWelcomeState> {
|
||||||
if (this.state.user.includes("@")) {
|
if (this.state.user.includes("@")) {
|
||||||
if (this.state.federates && this.state.federates === true) {
|
if (this.state.federates && this.state.federates === true) {
|
||||||
let baseUrl = this.state.user.split("@")[1];
|
let baseUrl = this.state.user.split("@")[1];
|
||||||
if (inDisallowedDomains(baseUrl)) {
|
axios
|
||||||
this.setState({
|
.get("https://" + baseUrl + "/api/v1/timelines/public")
|
||||||
userInputError: true,
|
.catch((err: Error) => {
|
||||||
userInputErrorMessage: `Signing in with an account from ${baseUrl} isn't supported.`
|
let userInputError = true;
|
||||||
});
|
let userInputErrorMessage =
|
||||||
return true;
|
"Instance name is invalid.";
|
||||||
} else {
|
this.setState({
|
||||||
axios
|
userInputError,
|
||||||
.get(
|
userInputErrorMessage
|
||||||
"https://" +
|
|
||||||
baseUrl +
|
|
||||||
"/api/v1/timelines/public"
|
|
||||||
)
|
|
||||||
.catch((err: Error) => {
|
|
||||||
let userInputError = true;
|
|
||||||
let userInputErrorMessage =
|
|
||||||
"Instance name is invalid.";
|
|
||||||
this.setState({
|
|
||||||
userInputError,
|
|
||||||
userInputErrorMessage
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
});
|
});
|
||||||
}
|
return true;
|
||||||
|
});
|
||||||
} else if (
|
} else if (
|
||||||
this.state.user.includes(
|
this.state.user.includes(
|
||||||
this.state.registerBase
|
this.state.registerBase
|
||||||
|
@ -726,8 +702,7 @@ class WelcomePage extends Component<IWelcomeProps, IWelcomeState> {
|
||||||
{this.state.brandName
|
{this.state.brandName
|
||||||
? this.state.brandName
|
? this.state.brandName
|
||||||
: "Hypersapce"}{" "}
|
: "Hypersapce"}{" "}
|
||||||
v.
|
v.{this.state.version}{" "}
|
||||||
{this.state.version}{" "}
|
|
||||||
{this.state.brandName &&
|
{this.state.brandName &&
|
||||||
this.state.brandName !== "Hyperspace"
|
this.state.brandName !== "Hyperspace"
|
||||||
? "(Hyperspace-like)"
|
? "(Hyperspace-like)"
|
||||||
|
|
|
@ -1,73 +1,73 @@
|
||||||
import { Theme, createStyles } from "@material-ui/core";
|
import { Theme, createStyles } from "@material-ui/core";
|
||||||
|
|
||||||
export const styles = (theme: Theme) =>
|
export const styles = (theme: Theme) =>
|
||||||
createStyles({
|
createStyles({
|
||||||
root: {
|
root: {
|
||||||
width: "100%",
|
width: "100%",
|
||||||
height: "100%",
|
height: "100%",
|
||||||
backgroundPosition: "center",
|
backgroundPosition: "center",
|
||||||
backgroundRepeat: "no-repeat",
|
backgroundRepeat: "no-repeat",
|
||||||
backgroundSize: "cover",
|
backgroundSize: "cover",
|
||||||
top: 0,
|
top: 0,
|
||||||
left: 0,
|
left: 0,
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
[theme.breakpoints.up("sm")]: {
|
[theme.breakpoints.up("sm")]: {
|
||||||
paddingTop: theme.spacing.unit * 4,
|
paddingTop: theme.spacing.unit * 4,
|
||||||
paddingLeft: "25%",
|
paddingLeft: "25%",
|
||||||
paddingRight: "25%"
|
paddingRight: "25%"
|
||||||
},
|
},
|
||||||
[theme.breakpoints.up("lg")]: {
|
[theme.breakpoints.up("lg")]: {
|
||||||
paddingTop: theme.spacing.unit * 12,
|
paddingTop: theme.spacing.unit * 12,
|
||||||
paddingLeft: "35%",
|
paddingLeft: "35%",
|
||||||
paddingRight: "35%"
|
paddingRight: "35%"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
titleBarRoot: {
|
titleBarRoot: {
|
||||||
top: 0,
|
top: 0,
|
||||||
left: 0,
|
left: 0,
|
||||||
height: 24,
|
height: 24,
|
||||||
width: "100%",
|
width: "100%",
|
||||||
backgroundColor: "rgba(0, 0, 0, 0.2)",
|
backgroundColor: "rgba(0, 0, 0, 0.2)",
|
||||||
textAlign: "center",
|
textAlign: "center",
|
||||||
zIndex: 1000,
|
zIndex: 1000,
|
||||||
verticalAlign: "middle",
|
verticalAlign: "middle",
|
||||||
WebkitUserSelect: "none",
|
WebkitUserSelect: "none",
|
||||||
WebkitAppRegion: "drag",
|
WebkitAppRegion: "drag",
|
||||||
position: "absolute"
|
position: "absolute"
|
||||||
},
|
},
|
||||||
titleBarText: {
|
titleBarText: {
|
||||||
color: theme.palette.common.white,
|
color: theme.palette.common.white,
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
paddingTop: 2,
|
paddingTop: 2,
|
||||||
paddingBottom: 1
|
paddingBottom: 1
|
||||||
},
|
},
|
||||||
paper: {
|
paper: {
|
||||||
height: "100%",
|
height: "100%",
|
||||||
[theme.breakpoints.up("sm")]: {
|
[theme.breakpoints.up("sm")]: {
|
||||||
height: "auto",
|
height: "auto",
|
||||||
paddingLeft: theme.spacing.unit * 8,
|
paddingLeft: theme.spacing.unit * 8,
|
||||||
paddingRight: theme.spacing.unit * 8,
|
paddingRight: theme.spacing.unit * 8,
|
||||||
paddingTop: theme.spacing.unit * 6
|
paddingTop: theme.spacing.unit * 6
|
||||||
},
|
},
|
||||||
paddingTop: theme.spacing.unit * 12,
|
paddingTop: theme.spacing.unit * 12,
|
||||||
paddingLeft: theme.spacing.unit * 4,
|
paddingLeft: theme.spacing.unit * 4,
|
||||||
paddingRight: theme.spacing.unit * 4,
|
paddingRight: theme.spacing.unit * 4,
|
||||||
paddingBottom: theme.spacing.unit * 6,
|
paddingBottom: theme.spacing.unit * 6,
|
||||||
textAlign: "center"
|
textAlign: "center"
|
||||||
},
|
},
|
||||||
welcomeLink: {
|
welcomeLink: {
|
||||||
color: theme.palette.primary.light
|
color: theme.palette.primary.light
|
||||||
},
|
},
|
||||||
flexGrow: {
|
flexGrow: {
|
||||||
flexGrow: 1
|
flexGrow: 1
|
||||||
},
|
},
|
||||||
middlePadding: {
|
middlePadding: {
|
||||||
height: theme.spacing.unit * 6
|
height: theme.spacing.unit * 6
|
||||||
},
|
},
|
||||||
logo: {
|
logo: {
|
||||||
[theme.breakpoints.up("sm")]: {
|
[theme.breakpoints.up("sm")]: {
|
||||||
height: 64,
|
height: 64,
|
||||||
width: "auto"
|
width: "auto"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
import React, { Component } from "react";
|
import React, { Component } from "react";
|
||||||
import {
|
import {
|
||||||
withStyles,
|
withStyles,
|
||||||
Typography,
|
Typography,
|
||||||
Paper,
|
Paper,
|
||||||
Avatar,
|
Avatar,
|
||||||
Button,
|
Button,
|
||||||
TextField,
|
TextField,
|
||||||
ListItem,
|
ListItem,
|
||||||
ListItemText,
|
ListItemText,
|
||||||
ListItemAvatar,
|
ListItemAvatar,
|
||||||
List,
|
List,
|
||||||
Grid
|
Grid
|
||||||
} from "@material-ui/core";
|
} from "@material-ui/core";
|
||||||
import { withSnackbar, withSnackbarProps } from "notistack";
|
import { withSnackbar, withSnackbarProps } from "notistack";
|
||||||
import { styles } from "./PageLayout.styles";
|
import { styles } from "./PageLayout.styles";
|
||||||
|
@ -21,271 +21,303 @@ import filedialog from "file-dialog";
|
||||||
import PersonIcon from "@material-ui/icons/Person";
|
import PersonIcon from "@material-ui/icons/Person";
|
||||||
|
|
||||||
interface IYouProps extends withSnackbarProps {
|
interface IYouProps extends withSnackbarProps {
|
||||||
classes: any;
|
classes: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IYouState {
|
interface IYouState {
|
||||||
currentAccount: Account;
|
currentAccount: Account;
|
||||||
newDisplayName?: string;
|
newDisplayName?: string;
|
||||||
newBio?: string;
|
newBio?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
class You extends Component<IYouProps, IYouState> {
|
class You extends Component<IYouProps, IYouState> {
|
||||||
client: Mastodon;
|
client: Mastodon;
|
||||||
|
|
||||||
constructor(props: any) {
|
constructor(props: any) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.client = new Mastodon(
|
this.client = new Mastodon(
|
||||||
localStorage.getItem("access_token") as string,
|
localStorage.getItem("access_token") as string,
|
||||||
(localStorage.getItem("baseurl") as string) + "/api/v1"
|
(localStorage.getItem("baseurl") as string) + "/api/v1"
|
||||||
);
|
);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
currentAccount: this.getAccount()
|
currentAccount: this.getAccount()
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
|
||||||
getAccount() {
|
|
||||||
let acct = localStorage.getItem("account");
|
|
||||||
if (acct) {
|
|
||||||
return JSON.parse(acct);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
updateAvatar() {
|
getAccount() {
|
||||||
filedialog({
|
let acct = localStorage.getItem("account");
|
||||||
multiple: false,
|
if (acct) {
|
||||||
accept: "image/*"
|
return JSON.parse(acct);
|
||||||
})
|
}
|
||||||
.then((images: FileList) => {
|
}
|
||||||
if (images.length > 0) {
|
|
||||||
this.props.enqueueSnackbar("Updating avatar...", {
|
updateAvatar() {
|
||||||
persist: true,
|
filedialog({
|
||||||
key: "persistAvatar"
|
multiple: false,
|
||||||
});
|
accept: "image/*"
|
||||||
let upload = new FormData();
|
})
|
||||||
upload.append("avatar", images[0]);
|
.then((images: FileList) => {
|
||||||
this.client
|
if (images.length > 0) {
|
||||||
.patch("/accounts/update_credentials", upload)
|
this.props.enqueueSnackbar("Updating avatar...", {
|
||||||
.then((acct: any) => {
|
persist: true,
|
||||||
let currentAccount: Account = acct.data;
|
key: "persistAvatar"
|
||||||
this.setState({ currentAccount });
|
});
|
||||||
localStorage.setItem("account", JSON.stringify(currentAccount));
|
let upload = new FormData();
|
||||||
this.props.closeSnackbar("persistAvatar");
|
upload.append("avatar", images[0]);
|
||||||
this.props.enqueueSnackbar("Avatar updated successfully.");
|
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) => {
|
.catch((err: Error) => {
|
||||||
this.props.closeSnackbar("persistAvatar");
|
this.props.enqueueSnackbar(
|
||||||
this.props.enqueueSnackbar(
|
"Couldn't update avatar: " + err.name
|
||||||
"Couldn't update avatar: " + err.name,
|
);
|
||||||
{ variant: "error" }
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
})
|
|
||||||
.catch((err: Error) => {
|
|
||||||
this.props.enqueueSnackbar("Couldn't update avatar: " + err.name);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
updateHeader() {
|
updateHeader() {
|
||||||
filedialog({
|
filedialog({
|
||||||
multiple: false,
|
multiple: false,
|
||||||
accept: "image/*"
|
accept: "image/*"
|
||||||
})
|
})
|
||||||
.then((images: FileList) => {
|
.then((images: FileList) => {
|
||||||
if (images.length > 0) {
|
if (images.length > 0) {
|
||||||
this.props.enqueueSnackbar("Updating header...", {
|
this.props.enqueueSnackbar("Updating header...", {
|
||||||
persist: true,
|
persist: true,
|
||||||
key: "persistHeader"
|
key: "persistHeader"
|
||||||
});
|
});
|
||||||
let upload = new FormData();
|
let upload = new FormData();
|
||||||
upload.append("header", images[0]);
|
upload.append("header", images[0]);
|
||||||
this.client
|
this.client
|
||||||
.patch("/accounts/update_credentials", upload)
|
.patch("/accounts/update_credentials", upload)
|
||||||
.then((acct: any) => {
|
.then((acct: any) => {
|
||||||
let currentAccount: Account = acct.data;
|
let currentAccount: Account = acct.data;
|
||||||
this.setState({ currentAccount });
|
this.setState({ currentAccount });
|
||||||
localStorage.setItem("account", JSON.stringify(currentAccount));
|
localStorage.setItem(
|
||||||
this.props.closeSnackbar("persistHeader");
|
"account",
|
||||||
this.props.enqueueSnackbar("Header updated successfully.");
|
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) => {
|
.catch((err: Error) => {
|
||||||
this.props.closeSnackbar("persistHeader");
|
this.props.enqueueSnackbar(
|
||||||
this.props.enqueueSnackbar(
|
"Couldn't update header: " + err.name
|
||||||
"Couldn't update header: " + err.name,
|
);
|
||||||
{ variant: "error" }
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
})
|
|
||||||
.catch((err: Error) => {
|
|
||||||
this.props.enqueueSnackbar("Couldn't update header: " + err.name);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
removeHTMLContent(text: string) {
|
removeHTMLContent(text: string) {
|
||||||
const div = document.createElement("div");
|
const div = document.createElement("div");
|
||||||
div.innerHTML = text;
|
div.innerHTML = text;
|
||||||
let innerContent = div.textContent || div.innerText || "";
|
let innerContent = div.textContent || div.innerText || "";
|
||||||
return innerContent;
|
return innerContent;
|
||||||
}
|
}
|
||||||
changeDisplayName() {
|
changeDisplayName() {
|
||||||
this.client
|
this.client
|
||||||
.patch("/accounts/update_credentials", {
|
.patch("/accounts/update_credentials", {
|
||||||
display_name: this.state.newDisplayName
|
display_name: this.state.newDisplayName
|
||||||
? this.state.newDisplayName
|
? this.state.newDisplayName
|
||||||
: this.state.currentAccount.display_name
|
: this.state.currentAccount.display_name
|
||||||
})
|
})
|
||||||
.then((acct: any) => {
|
.then((acct: any) => {
|
||||||
let currentAccount: Account = acct.data;
|
let currentAccount: Account = acct.data;
|
||||||
this.setState({ currentAccount });
|
this.setState({ currentAccount });
|
||||||
localStorage.setItem("account", JSON.stringify(currentAccount));
|
localStorage.setItem("account", JSON.stringify(currentAccount));
|
||||||
this.props.closeSnackbar("persistHeader");
|
this.props.closeSnackbar("persistHeader");
|
||||||
this.props.enqueueSnackbar(
|
this.props.enqueueSnackbar(
|
||||||
"Display name updated to " + this.state.newDisplayName
|
"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.profileContent}>
|
||||||
|
<br />
|
||||||
|
<Avatar
|
||||||
|
className={classes.profileAvatar}
|
||||||
|
src={this.state.currentAccount.avatar_static}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className={classes.profileUserBox}
|
||||||
|
style={{ paddingTop: 8, paddingBottom: 8 }}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
variant="h4"
|
||||||
|
color="inherit"
|
||||||
|
component="h1"
|
||||||
|
>
|
||||||
|
Edit your profile
|
||||||
|
</Typography>
|
||||||
|
<Typography color="inherit">
|
||||||
|
Change information such as your display name,
|
||||||
|
bio, and images used here.
|
||||||
|
</Typography>
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
</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)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<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)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<div style={{ textAlign: "right" }}>
|
||||||
|
<Button
|
||||||
|
className={classes.pageProfileFollowButton}
|
||||||
|
color="primary"
|
||||||
|
onClick={() => this.changeBio()}
|
||||||
|
>
|
||||||
|
Update biography
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Paper>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
})
|
}
|
||||||
.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));
|
export default withStyles(styles)(withSnackbar(You));
|
||||||
|
|
|
@ -5,30 +5,30 @@ import { Field } from "./Field";
|
||||||
* Basic type for an account on Mastodon
|
* Basic type for an account on Mastodon
|
||||||
*/
|
*/
|
||||||
export type Account = {
|
export type Account = {
|
||||||
id: string;
|
id: string;
|
||||||
username: string;
|
username: string;
|
||||||
acct: string;
|
acct: string;
|
||||||
display_name: string;
|
display_name: string;
|
||||||
locked: boolean;
|
locked: boolean;
|
||||||
created_at: string;
|
created_at: string;
|
||||||
followers_count: number;
|
followers_count: number;
|
||||||
following_count: number;
|
following_count: number;
|
||||||
statuses_count: number;
|
statuses_count: number;
|
||||||
note: string;
|
note: string;
|
||||||
url: string;
|
url: string;
|
||||||
avatar: string;
|
avatar: string;
|
||||||
avatar_static: string;
|
avatar_static: string;
|
||||||
header: string;
|
header: string;
|
||||||
header_static: string;
|
header_static: string;
|
||||||
emojis: [MastodonEmoji];
|
emojis: [MastodonEmoji];
|
||||||
moved: Account | null;
|
moved: Account | null;
|
||||||
fields: [Field];
|
fields: [Field];
|
||||||
bot: boolean | null;
|
bot: boolean | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type UAccount = {
|
export type UAccount = {
|
||||||
id: string;
|
id: string;
|
||||||
acct: string;
|
acct: string;
|
||||||
display_name: string;
|
display_name: string;
|
||||||
avatar_static: string;
|
avatar_static: string;
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,12 +2,12 @@
|
||||||
* Basic type for an attachment, usually on Statuses
|
* Basic type for an attachment, usually on Statuses
|
||||||
*/
|
*/
|
||||||
export type Attachment = {
|
export type Attachment = {
|
||||||
id: string;
|
id: string;
|
||||||
type: "unknown" | "image" | "gifv" | "video";
|
type: "unknown" | "image" | "gifv" | "video";
|
||||||
url: string;
|
url: string;
|
||||||
remote_url: string | null;
|
remote_url: string | null;
|
||||||
preview_url: string;
|
preview_url: string;
|
||||||
text_url: string | null;
|
text_url: string | null;
|
||||||
meta: any | null;
|
meta: any | null;
|
||||||
description: string | null;
|
description: string | null;
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,16 +2,16 @@
|
||||||
* Basic type for Cards, usually in Statuses
|
* Basic type for Cards, usually in Statuses
|
||||||
*/
|
*/
|
||||||
export type Card = {
|
export type Card = {
|
||||||
url: string;
|
url: string;
|
||||||
title: string;
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
image: string | null;
|
image: string | null;
|
||||||
type: "link" | "photo" | "video" | "rich";
|
type: "link" | "photo" | "video" | "rich";
|
||||||
author_name: string | null;
|
author_name: string | null;
|
||||||
author_url: string | null;
|
author_url: string | null;
|
||||||
provider_name: string | null;
|
provider_name: string | null;
|
||||||
provider_url: string | null;
|
provider_url: string | null;
|
||||||
html: string | null;
|
html: string | null;
|
||||||
width: number | null;
|
width: number | null;
|
||||||
height: number | null;
|
height: number | null;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,31 +1,31 @@
|
||||||
export type Config = {
|
export type Config = {
|
||||||
version: string;
|
version: string;
|
||||||
location: string;
|
location: string;
|
||||||
branding?: {
|
branding?: {
|
||||||
name?: string;
|
name?: string;
|
||||||
logo?: string;
|
logo?: string;
|
||||||
background?: string;
|
background?: string;
|
||||||
};
|
};
|
||||||
developer?: boolean;
|
developer?: boolean;
|
||||||
federation: Federation;
|
federation: Federation;
|
||||||
registration?: {
|
registration?: {
|
||||||
defaultInstance?: string;
|
defaultInstance?: string;
|
||||||
};
|
};
|
||||||
admin?: {
|
admin?: {
|
||||||
name?: string;
|
name?: string;
|
||||||
account?: string;
|
account?: string;
|
||||||
};
|
};
|
||||||
license: License;
|
license: License;
|
||||||
repository?: string;
|
repository?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type License = {
|
export type License = {
|
||||||
name: string;
|
name: string;
|
||||||
url: string;
|
url: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Federation = {
|
export type Federation = {
|
||||||
universalLogin: boolean;
|
universalLogin: boolean;
|
||||||
allowPublicPosts: boolean;
|
allowPublicPosts: boolean;
|
||||||
enablePublicTimeline: boolean;
|
enablePublicTimeline: boolean;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Status } from "./Status";
|
import { Status } from "./Status";
|
||||||
|
|
||||||
export type Context = {
|
export type Context = {
|
||||||
ancestors: [Status];
|
ancestors: [Status];
|
||||||
descendants: [Status];
|
descendants: [Status];
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,16 +2,16 @@
|
||||||
* Basic type for Emojis on Mastodon.
|
* Basic type for Emojis on Mastodon.
|
||||||
*/
|
*/
|
||||||
export type MastodonEmoji = {
|
export type MastodonEmoji = {
|
||||||
shortcode: string;
|
shortcode: string;
|
||||||
static_url: string;
|
static_url: string;
|
||||||
url: string;
|
url: string;
|
||||||
visible_in_picker: boolean;
|
visible_in_picker: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Trimmed type of Emoji from emoji-mart
|
* Trimmed type of Emoji from emoji-mart
|
||||||
*/
|
*/
|
||||||
export type Emoji = {
|
export type Emoji = {
|
||||||
name: string;
|
name: string;
|
||||||
imageUrl: string;
|
imageUrl: string;
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
* Basic type for a table entry, usually in Account
|
* Basic type for a table entry, usually in Account
|
||||||
*/
|
*/
|
||||||
export type Field = {
|
export type Field = {
|
||||||
name: string;
|
name: string;
|
||||||
value: string;
|
value: string;
|
||||||
verified_at: string | null;
|
verified_at: string | null;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,19 +1,19 @@
|
||||||
import { Color } from "@material-ui/core";
|
import { Color } from "@material-ui/core";
|
||||||
import {
|
import {
|
||||||
deepPurple,
|
deepPurple,
|
||||||
red,
|
red,
|
||||||
lightGreen,
|
lightGreen,
|
||||||
yellow,
|
yellow,
|
||||||
purple,
|
purple,
|
||||||
deepOrange,
|
deepOrange,
|
||||||
indigo,
|
indigo,
|
||||||
lightBlue,
|
lightBlue,
|
||||||
orange,
|
orange,
|
||||||
blue,
|
blue,
|
||||||
amber,
|
amber,
|
||||||
pink,
|
pink,
|
||||||
brown,
|
brown,
|
||||||
blueGrey
|
blueGrey
|
||||||
} from "@material-ui/core/colors";
|
} from "@material-ui/core/colors";
|
||||||
import { isDarwinApp } from "../utilities/desktop";
|
import { isDarwinApp } from "../utilities/desktop";
|
||||||
|
|
||||||
|
@ -21,141 +21,141 @@ import { isDarwinApp } from "../utilities/desktop";
|
||||||
* Basic theme colors for Hyperspace.
|
* Basic theme colors for Hyperspace.
|
||||||
*/
|
*/
|
||||||
export type HyperspaceTheme = {
|
export type HyperspaceTheme = {
|
||||||
key: string;
|
key: string;
|
||||||
name: string;
|
name: string;
|
||||||
palette: {
|
palette: {
|
||||||
primary:
|
primary:
|
||||||
| {
|
| {
|
||||||
main: string;
|
main: string;
|
||||||
}
|
}
|
||||||
| Color;
|
| Color;
|
||||||
secondary:
|
secondary:
|
||||||
| {
|
| {
|
||||||
main: string;
|
main: string;
|
||||||
}
|
}
|
||||||
| Color;
|
| Color;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const defaultTheme: HyperspaceTheme = {
|
export const defaultTheme: HyperspaceTheme = {
|
||||||
key: "defaultTheme",
|
key: "defaultTheme",
|
||||||
name: "Royal (Default)",
|
name: "Royal (Default)",
|
||||||
palette: {
|
palette: {
|
||||||
primary: deepPurple,
|
primary: deepPurple,
|
||||||
secondary: red
|
secondary: red
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const gardenerTheme: HyperspaceTheme = {
|
export const gardenerTheme: HyperspaceTheme = {
|
||||||
key: "gardnerTheme",
|
key: "gardnerTheme",
|
||||||
name: "Botanical",
|
name: "Botanical",
|
||||||
palette: {
|
palette: {
|
||||||
primary: lightGreen,
|
primary: lightGreen,
|
||||||
secondary: yellow
|
secondary: yellow
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const teacherTheme: HyperspaceTheme = {
|
export const teacherTheme: HyperspaceTheme = {
|
||||||
key: "teacherTheme",
|
key: "teacherTheme",
|
||||||
name: "Compassionate",
|
name: "Compassionate",
|
||||||
palette: {
|
palette: {
|
||||||
primary: purple,
|
primary: purple,
|
||||||
secondary: deepOrange
|
secondary: deepOrange
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const jokerTheme: HyperspaceTheme = {
|
export const jokerTheme: HyperspaceTheme = {
|
||||||
key: "jokerTheme",
|
key: "jokerTheme",
|
||||||
name: "Joker",
|
name: "Joker",
|
||||||
palette: {
|
palette: {
|
||||||
primary: indigo,
|
primary: indigo,
|
||||||
secondary: lightBlue
|
secondary: lightBlue
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const guardTheme: HyperspaceTheme = {
|
export const guardTheme: HyperspaceTheme = {
|
||||||
key: "guardTheme",
|
key: "guardTheme",
|
||||||
name: "Enthusiastic",
|
name: "Enthusiastic",
|
||||||
palette: {
|
palette: {
|
||||||
primary: blue,
|
primary: blue,
|
||||||
secondary: deepOrange
|
secondary: deepOrange
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const entertainerTheme: HyperspaceTheme = {
|
export const entertainerTheme: HyperspaceTheme = {
|
||||||
key: "entertainerTheme",
|
key: "entertainerTheme",
|
||||||
name: "Animated",
|
name: "Animated",
|
||||||
palette: {
|
palette: {
|
||||||
primary: pink,
|
primary: pink,
|
||||||
secondary: purple
|
secondary: purple
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const classicTheme: HyperspaceTheme = {
|
export const classicTheme: HyperspaceTheme = {
|
||||||
key: "classicTheme",
|
key: "classicTheme",
|
||||||
name: "Classic",
|
name: "Classic",
|
||||||
palette: {
|
palette: {
|
||||||
primary: {
|
primary: {
|
||||||
main: "#555555"
|
main: "#555555"
|
||||||
},
|
},
|
||||||
secondary: {
|
secondary: {
|
||||||
main: "#5c2d91"
|
main: "#5c2d91"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const dragonTheme: HyperspaceTheme = {
|
export const dragonTheme: HyperspaceTheme = {
|
||||||
key: "dragonTheme",
|
key: "dragonTheme",
|
||||||
name: "Adventurous",
|
name: "Adventurous",
|
||||||
palette: {
|
palette: {
|
||||||
primary: purple,
|
primary: purple,
|
||||||
secondary: purple
|
secondary: purple
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const memoriumTheme: HyperspaceTheme = {
|
export const memoriumTheme: HyperspaceTheme = {
|
||||||
key: "memoriumTheme",
|
key: "memoriumTheme",
|
||||||
name: "Memorial",
|
name: "Memorial",
|
||||||
palette: {
|
palette: {
|
||||||
primary: red,
|
primary: red,
|
||||||
secondary: red
|
secondary: red
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const blissTheme: HyperspaceTheme = {
|
export const blissTheme: HyperspaceTheme = {
|
||||||
key: "blissTheme",
|
key: "blissTheme",
|
||||||
name: "Bliss",
|
name: "Bliss",
|
||||||
palette: {
|
palette: {
|
||||||
primary: {
|
primary: {
|
||||||
main: "#3e2723"
|
main: "#3e2723"
|
||||||
},
|
},
|
||||||
secondary: lightBlue
|
secondary: lightBlue
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const attractTheme: HyperspaceTheme = {
|
export const attractTheme: HyperspaceTheme = {
|
||||||
key: "attractTheme",
|
key: "attractTheme",
|
||||||
name: "Attract",
|
name: "Attract",
|
||||||
palette: {
|
palette: {
|
||||||
primary: {
|
primary: {
|
||||||
main: "#E57373"
|
main: "#E57373"
|
||||||
},
|
},
|
||||||
secondary: {
|
secondary: {
|
||||||
main: "#78909C"
|
main: "#78909C"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const themes = [
|
export const themes = [
|
||||||
defaultTheme,
|
defaultTheme,
|
||||||
gardenerTheme,
|
gardenerTheme,
|
||||||
teacherTheme,
|
teacherTheme,
|
||||||
jokerTheme,
|
jokerTheme,
|
||||||
guardTheme,
|
guardTheme,
|
||||||
entertainerTheme,
|
entertainerTheme,
|
||||||
classicTheme,
|
classicTheme,
|
||||||
dragonTheme,
|
dragonTheme,
|
||||||
memoriumTheme,
|
memoriumTheme,
|
||||||
blissTheme,
|
blissTheme,
|
||||||
attractTheme
|
attractTheme
|
||||||
];
|
];
|
||||||
|
|
|
@ -2,14 +2,14 @@ import { Field } from "./Field";
|
||||||
import { Account } from "./Account";
|
import { Account } from "./Account";
|
||||||
|
|
||||||
export type Instance = {
|
export type Instance = {
|
||||||
uri: string;
|
uri: string;
|
||||||
title: string;
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
email: string;
|
email: string;
|
||||||
version: string;
|
version: string;
|
||||||
thumbnail: string | null;
|
thumbnail: string | null;
|
||||||
urls: Field;
|
urls: Field;
|
||||||
stats: Field;
|
stats: Field;
|
||||||
languages: [string];
|
languages: [string];
|
||||||
contact_account: Account;
|
contact_account: Account;
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
* Basic type for a person mentioned in a Status
|
* Basic type for a person mentioned in a Status
|
||||||
*/
|
*/
|
||||||
export type Mention = {
|
export type Mention = {
|
||||||
url: string;
|
url: string;
|
||||||
username: string;
|
username: string;
|
||||||
acct: string;
|
acct: string;
|
||||||
id: string;
|
id: string;
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,9 +2,9 @@ import { Account } from "./Account";
|
||||||
import { Status } from "./Status";
|
import { Status } from "./Status";
|
||||||
|
|
||||||
export type Notification = {
|
export type Notification = {
|
||||||
id: string;
|
id: string;
|
||||||
type: "follow" | "mention" | "reblog" | "favourite";
|
type: "follow" | "mention" | "reblog" | "favourite";
|
||||||
created_at: string;
|
created_at: string;
|
||||||
account: Account;
|
account: Account;
|
||||||
status: Status | null;
|
status: Status | null;
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,29 +2,29 @@
|
||||||
* Basic type for a Poll on Mastodon
|
* Basic type for a Poll on Mastodon
|
||||||
*/
|
*/
|
||||||
export type Poll = {
|
export type Poll = {
|
||||||
id: string;
|
id: string;
|
||||||
expires_at: string | null;
|
expires_at: string | null;
|
||||||
expired: boolean;
|
expired: boolean;
|
||||||
multiple: boolean;
|
multiple: boolean;
|
||||||
votes_count: number;
|
votes_count: number;
|
||||||
options: [PollOption];
|
options: [PollOption];
|
||||||
voted: boolean | null;
|
voted: boolean | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Basic type for a Poll option in a Poll
|
* Basic type for a Poll option in a Poll
|
||||||
*/
|
*/
|
||||||
export type PollOption = {
|
export type PollOption = {
|
||||||
title: string;
|
title: string;
|
||||||
votes_count: number | null;
|
votes_count: number | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PollWizard = {
|
export type PollWizard = {
|
||||||
expires_at: string;
|
expires_at: string;
|
||||||
multiple: boolean;
|
multiple: boolean;
|
||||||
options: PollWizardOption[];
|
options: PollWizardOption[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PollWizardOption = {
|
export type PollWizardOption = {
|
||||||
title: string;
|
title: string;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
export type Relationship = {
|
export type Relationship = {
|
||||||
id: string;
|
id: string;
|
||||||
following: boolean;
|
following: boolean;
|
||||||
followed_by: boolean;
|
followed_by: boolean;
|
||||||
blocking: boolean;
|
blocking: boolean;
|
||||||
muting: boolean;
|
muting: boolean;
|
||||||
muting_notifications: boolean;
|
muting_notifications: boolean;
|
||||||
requested: boolean;
|
requested: boolean;
|
||||||
domain_blocking: boolean;
|
domain_blocking: boolean;
|
||||||
showing_reblogs: boolean;
|
showing_reblogs: boolean;
|
||||||
endorsed: boolean;
|
endorsed: boolean;
|
||||||
};
|
};
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { Status } from "./Status";
|
||||||
import { Tag } from "./Tag";
|
import { Tag } from "./Tag";
|
||||||
|
|
||||||
export type Results = {
|
export type Results = {
|
||||||
accounts: [Account];
|
accounts: [Account];
|
||||||
statuses: [Status];
|
statuses: [Status];
|
||||||
hashtags: [Tag];
|
hashtags: [Tag];
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
export type SaveClientSession = {
|
export type SaveClientSession = {
|
||||||
clientId: string;
|
clientId: string;
|
||||||
clientSecret: string;
|
clientSecret: string;
|
||||||
authUrl: string;
|
authUrl: string;
|
||||||
emergency: boolean;
|
emergency: boolean;
|
||||||
};
|
};
|
||||||
|
|
|
@ -11,30 +11,30 @@ import { Tag } from "./Tag";
|
||||||
* Basic type for a status on Mastodon
|
* Basic type for a status on Mastodon
|
||||||
*/
|
*/
|
||||||
export type Status = {
|
export type Status = {
|
||||||
id: string;
|
id: string;
|
||||||
uri: string;
|
uri: string;
|
||||||
url: string | null;
|
url: string | null;
|
||||||
account: Account;
|
account: Account;
|
||||||
in_reply_to_id: string | null;
|
in_reply_to_id: string | null;
|
||||||
in_reply_to_account_id: string | null;
|
in_reply_to_account_id: string | null;
|
||||||
reblog: Status | null;
|
reblog: Status | null;
|
||||||
content: string;
|
content: string;
|
||||||
created_at: string;
|
created_at: string;
|
||||||
emojis: [MastodonEmoji];
|
emojis: [MastodonEmoji];
|
||||||
replies_count: number;
|
replies_count: number;
|
||||||
reblogs_count: number;
|
reblogs_count: number;
|
||||||
favourites_count: number;
|
favourites_count: number;
|
||||||
reblogged: boolean | null;
|
reblogged: boolean | null;
|
||||||
favourited: boolean | null;
|
favourited: boolean | null;
|
||||||
muted: boolean | null;
|
muted: boolean | null;
|
||||||
sensitive: boolean;
|
sensitive: boolean;
|
||||||
spoiler_text: string;
|
spoiler_text: string;
|
||||||
visibility: Visibility;
|
visibility: Visibility;
|
||||||
media_attachments: [Attachment];
|
media_attachments: [Attachment];
|
||||||
mentions: [Mention];
|
mentions: [Mention];
|
||||||
tags: [Tag];
|
tags: [Tag];
|
||||||
card: Card | null;
|
card: Card | null;
|
||||||
poll: Poll | null;
|
poll: Poll | null;
|
||||||
application: any;
|
application: any;
|
||||||
pinned: boolean | null;
|
pinned: boolean | null;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
export type Tag = {
|
export type Tag = {
|
||||||
name: string;
|
name: string;
|
||||||
url: string;
|
url: string;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,30 +1,33 @@
|
||||||
import Mastodon from "megalodon";
|
import Mastodon from "megalodon";
|
||||||
|
|
||||||
export function userLoggedIn(): boolean {
|
export function userLoggedIn(): boolean {
|
||||||
if (localStorage.getItem("baseurl") && localStorage.getItem("access_token")) {
|
if (
|
||||||
return true;
|
localStorage.getItem("baseurl") &&
|
||||||
} else {
|
localStorage.getItem("access_token")
|
||||||
return false;
|
) {
|
||||||
}
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function refreshUserAccountData() {
|
export function refreshUserAccountData() {
|
||||||
let client = new Mastodon(
|
let client = new Mastodon(
|
||||||
localStorage.getItem("access_token") as string,
|
localStorage.getItem("access_token") as string,
|
||||||
(localStorage.getItem("baseurl") as string) + "/api/v1"
|
(localStorage.getItem("baseurl") as string) + "/api/v1"
|
||||||
);
|
|
||||||
client
|
|
||||||
.get("/accounts/verify_credentials")
|
|
||||||
.then((resp: any) => {
|
|
||||||
localStorage.setItem("account", JSON.stringify(resp.data));
|
|
||||||
})
|
|
||||||
.catch((err: Error) => {
|
|
||||||
console.error(err.message);
|
|
||||||
});
|
|
||||||
client.get("/instance").then((resp: any) => {
|
|
||||||
localStorage.setItem(
|
|
||||||
"isPleroma",
|
|
||||||
resp.data.version.match(/Pleroma/) ? "true" : "false"
|
|
||||||
);
|
);
|
||||||
});
|
client
|
||||||
|
.get("/accounts/verify_credentials")
|
||||||
|
.then((resp: any) => {
|
||||||
|
localStorage.setItem("account", JSON.stringify(resp.data));
|
||||||
|
})
|
||||||
|
.catch((err: Error) => {
|
||||||
|
console.error(err.message);
|
||||||
|
});
|
||||||
|
client.get("/instance").then((resp: any) => {
|
||||||
|
localStorage.setItem(
|
||||||
|
"isPleroma",
|
||||||
|
resp.data.version.match(/Pleroma/) ? "true" : "false"
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,5 +7,5 @@ import { isDarwinApp } from "./desktop";
|
||||||
* @returns Boolean dictating if the title bar is visible
|
* @returns Boolean dictating if the title bar is visible
|
||||||
*/
|
*/
|
||||||
export function isAppbarExpanded(): boolean {
|
export function isAppbarExpanded(): boolean {
|
||||||
return isDarwinApp() || process.env.NODE_ENV === "development";
|
return isDarwinApp() || process.env.NODE_ENV === "development";
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
* A Window interface with extra properties for Electron
|
* A Window interface with extra properties for Electron
|
||||||
*/
|
*/
|
||||||
interface ElectronWindow extends Window {
|
interface ElectronWindow extends Window {
|
||||||
require?: any;
|
require?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -10,22 +10,22 @@ interface ElectronWindow extends Window {
|
||||||
* @returns Boolean of whether it is in desktop mode or not
|
* @returns Boolean of whether it is in desktop mode or not
|
||||||
*/
|
*/
|
||||||
export function isDesktopApp(): boolean {
|
export function isDesktopApp(): boolean {
|
||||||
return navigator.userAgent.includes("Hyperspace" || "Electron");
|
return navigator.userAgent.includes("Hyperspace" || "Electron");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines whether the app is the macOS application
|
* Determines whether the app is the macOS application
|
||||||
*/
|
*/
|
||||||
export function isDarwinApp(): boolean {
|
export function isDarwinApp(): boolean {
|
||||||
return isDesktopApp() && navigator.userAgent.includes("Macintosh");
|
return isDesktopApp() && navigator.userAgent.includes("Macintosh");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine whether the system is in dark mode or not (macOS)
|
* Determine whether the system is in dark mode or not (macOS)
|
||||||
*/
|
*/
|
||||||
export function isDarkMode() {
|
export function isDarkMode() {
|
||||||
// Lift window to an ElectronWindow and add use require()
|
// Lift window to an ElectronWindow and add use require()
|
||||||
const eWin = window as ElectronWindow;
|
const eWin = window as ElectronWindow;
|
||||||
const { remote } = eWin.require("electron");
|
const { remote } = eWin.require("electron");
|
||||||
return remote.systemPreferences.isDarkMode();
|
return remote.systemPreferences.isDarkMode();
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,50 +9,50 @@ import Mastodon from "megalodon";
|
||||||
* @returns String with image tags for emojis
|
* @returns String with image tags for emojis
|
||||||
*/
|
*/
|
||||||
export function emojifyString(
|
export function emojifyString(
|
||||||
contents: string,
|
contents: string,
|
||||||
emojis: [MastodonEmoji],
|
emojis: [MastodonEmoji],
|
||||||
className?: any
|
className?: any
|
||||||
): string {
|
): string {
|
||||||
let newContents: string = contents;
|
let newContents: string = contents;
|
||||||
|
|
||||||
emojis.forEach((emoji: MastodonEmoji) => {
|
emojis.forEach((emoji: MastodonEmoji) => {
|
||||||
let filter = new RegExp(`:${emoji.shortcode}:`, "g");
|
let filter = new RegExp(`:${emoji.shortcode}:`, "g");
|
||||||
newContents = newContents.replace(
|
newContents = newContents.replace(
|
||||||
filter,
|
filter,
|
||||||
`<img src=${emoji.static_url} ${
|
`<img src=${emoji.static_url} ${
|
||||||
className ? `class="${className}"` : ""
|
className ? `class="${className}"` : ""
|
||||||
}/>`
|
}/>`
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
return newContents;
|
return newContents;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function collectEmojisFromServer() {
|
export function collectEmojisFromServer() {
|
||||||
let client = new Mastodon(
|
let client = new Mastodon(
|
||||||
localStorage.getItem("access_token") as string,
|
localStorage.getItem("access_token") as string,
|
||||||
localStorage.getItem("baseurl") + "/api/v1"
|
localStorage.getItem("baseurl") + "/api/v1"
|
||||||
);
|
);
|
||||||
let emojisPath = localStorage.getItem("emojis");
|
let emojisPath = localStorage.getItem("emojis");
|
||||||
let emojis: any[] = [];
|
let emojis: any[] = [];
|
||||||
if (emojisPath === null) {
|
if (emojisPath === null) {
|
||||||
client
|
client
|
||||||
.get("/custom_emojis")
|
.get("/custom_emojis")
|
||||||
.then((resp: any) => {
|
.then((resp: any) => {
|
||||||
resp.data.forEach((emoji: MastodonEmoji) => {
|
resp.data.forEach((emoji: MastodonEmoji) => {
|
||||||
let customEmoji = {
|
let customEmoji = {
|
||||||
name: emoji.shortcode,
|
name: emoji.shortcode,
|
||||||
emoticons: [""],
|
emoticons: [""],
|
||||||
short_names: [emoji.shortcode],
|
short_names: [emoji.shortcode],
|
||||||
imageUrl: emoji.static_url,
|
imageUrl: emoji.static_url,
|
||||||
keywords: ["mastodon", "custom"]
|
keywords: ["mastodon", "custom"]
|
||||||
};
|
};
|
||||||
emojis.push(customEmoji);
|
emojis.push(customEmoji);
|
||||||
localStorage.setItem("emojis", JSON.stringify(emojis));
|
localStorage.setItem("emojis", JSON.stringify(emojis));
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch((err: Error) => {
|
.catch((err: Error) => {
|
||||||
console.error(err.message);
|
console.error(err.message);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,13 +55,3 @@ export function getRedirectAddress(
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Determine whether a base URL is in the 'disallowed' domains section.
|
|
||||||
* @param domain The URL to test
|
|
||||||
* @returns Boolean dictating the URL's presence in disallowed domains
|
|
||||||
*/
|
|
||||||
export function inDisallowedDomains(domain: string): boolean {
|
|
||||||
let disallowed = ["gab.com"];
|
|
||||||
return disallowed.includes(domain);
|
|
||||||
}
|
|
||||||
|
|
|
@ -4,22 +4,22 @@ import { getUserDefaultBool, setUserDefaultBool } from "./settings";
|
||||||
* Get the person's permission to send notification requests.
|
* Get the person's permission to send notification requests.
|
||||||
*/
|
*/
|
||||||
export function getNotificationRequestPermission() {
|
export function getNotificationRequestPermission() {
|
||||||
if ("Notification" in window) {
|
if ("Notification" in window) {
|
||||||
Notification.requestPermission();
|
Notification.requestPermission();
|
||||||
let request = Notification.permission;
|
let request = Notification.permission;
|
||||||
if (request === "granted") {
|
if (request === "granted") {
|
||||||
setUserDefaultBool("enablePushNotifications", true);
|
setUserDefaultBool("enablePushNotifications", true);
|
||||||
setUserDefaultBool("userDeniedNotification", false);
|
setUserDefaultBool("userDeniedNotification", false);
|
||||||
|
} else {
|
||||||
|
setUserDefaultBool("enablePushNotifications", false);
|
||||||
|
setUserDefaultBool("userDeniedNotification", true);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
setUserDefaultBool("enablePushNotifications", false);
|
console.warn(
|
||||||
setUserDefaultBool("userDeniedNotification", true);
|
"Notifications aren't supported in this browser. The setting will be disabled."
|
||||||
|
);
|
||||||
|
setUserDefaultBool("enablePushNotifications", false);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
console.warn(
|
|
||||||
"Notifications aren't supported in this browser. The setting will be disabled."
|
|
||||||
);
|
|
||||||
setUserDefaultBool("enablePushNotifications", false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -27,7 +27,7 @@ export function getNotificationRequestPermission() {
|
||||||
* @returns Boolean value that determines whether the browser supports the Notification API
|
* @returns Boolean value that determines whether the browser supports the Notification API
|
||||||
*/
|
*/
|
||||||
export function browserSupportsNotificationRequests(): boolean {
|
export function browserSupportsNotificationRequests(): boolean {
|
||||||
return "Notification" in window;
|
return "Notification" in window;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -35,7 +35,7 @@ export function browserSupportsNotificationRequests(): boolean {
|
||||||
* @returns Boolean value of `enablePushNotifications`
|
* @returns Boolean value of `enablePushNotifications`
|
||||||
*/
|
*/
|
||||||
export function canSendNotifications() {
|
export function canSendNotifications() {
|
||||||
return getUserDefaultBool("enablePushNotifications");
|
return getUserDefaultBool("enablePushNotifications");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -44,15 +44,15 @@ export function canSendNotifications() {
|
||||||
* @param body The contents of the push notification
|
* @param body The contents of the push notification
|
||||||
*/
|
*/
|
||||||
export function sendNotificationRequest(title: string, body: string) {
|
export function sendNotificationRequest(title: string, body: string) {
|
||||||
if (canSendNotifications()) {
|
if (canSendNotifications()) {
|
||||||
let notif = new Notification(title, {
|
let notif = new Notification(title, {
|
||||||
body: body
|
body: body
|
||||||
});
|
});
|
||||||
|
|
||||||
notif.onclick = () => {
|
notif.onclick = () => {
|
||||||
window.focus();
|
window.focus();
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
console.warn("The person has opted to not receive push notifications.");
|
console.warn("The person has opted to not receive push notifications.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,13 +5,13 @@ import { Config } from "../types/Config";
|
||||||
import { Visibility } from "../types/Visibility";
|
import { Visibility } from "../types/Visibility";
|
||||||
|
|
||||||
type SettingsTemplate = {
|
type SettingsTemplate = {
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
darkModeEnabled: boolean;
|
darkModeEnabled: boolean;
|
||||||
systemDecidesDarkMode: boolean;
|
systemDecidesDarkMode: boolean;
|
||||||
enablePushNotifications: boolean;
|
enablePushNotifications: boolean;
|
||||||
clearNotificationsOnRead: boolean;
|
clearNotificationsOnRead: boolean;
|
||||||
displayAllOnNotificationBadge: boolean;
|
displayAllOnNotificationBadge: boolean;
|
||||||
defaultVisibility: string;
|
defaultVisibility: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -20,14 +20,14 @@ type SettingsTemplate = {
|
||||||
* @returns The boolean value associated with the key
|
* @returns The boolean value associated with the key
|
||||||
*/
|
*/
|
||||||
export function getUserDefaultBool(key: string): boolean {
|
export function getUserDefaultBool(key: string): boolean {
|
||||||
if (localStorage.getItem(key) === null) {
|
if (localStorage.getItem(key) === null) {
|
||||||
console.warn(
|
console.warn(
|
||||||
"This key has not been set before, so the default value is FALSE for now."
|
"This key has not been set before, so the default value is FALSE for now."
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
return localStorage.getItem(key) === "true";
|
return localStorage.getItem(key) === "true";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -36,10 +36,10 @@ export function getUserDefaultBool(key: string): boolean {
|
||||||
* @param value The boolean value for the key
|
* @param value The boolean value for the key
|
||||||
*/
|
*/
|
||||||
export function setUserDefaultBool(key: string, value: boolean) {
|
export function setUserDefaultBool(key: string, value: boolean) {
|
||||||
if (localStorage.getItem(key) === null) {
|
if (localStorage.getItem(key) === null) {
|
||||||
console.warn("This key has not been set before.");
|
console.warn("This key has not been set before.");
|
||||||
}
|
}
|
||||||
localStorage.setItem(key, value.toString());
|
localStorage.setItem(key, value.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -47,14 +47,14 @@ export function setUserDefaultBool(key: string, value: boolean) {
|
||||||
* @returns The Visibility value associated with the key
|
* @returns The Visibility value associated with the key
|
||||||
*/
|
*/
|
||||||
export function getUserDefaultVisibility(): Visibility {
|
export function getUserDefaultVisibility(): Visibility {
|
||||||
if (localStorage.getItem("defaultVisibility") === null) {
|
if (localStorage.getItem("defaultVisibility") === null) {
|
||||||
console.warn(
|
console.warn(
|
||||||
"This key has not been set before, so the default value is PUBLIC for now."
|
"This key has not been set before, so the default value is PUBLIC for now."
|
||||||
);
|
);
|
||||||
return "public";
|
return "public";
|
||||||
} else {
|
} else {
|
||||||
return localStorage.getItem("defaultVisibility") as Visibility;
|
return localStorage.getItem("defaultVisibility") as Visibility;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -62,23 +62,23 @@ export function getUserDefaultVisibility(): Visibility {
|
||||||
* @param key The settings key in localStorage to change
|
* @param key The settings key in localStorage to change
|
||||||
*/
|
*/
|
||||||
export function setUserDefaultVisibility(key: string) {
|
export function setUserDefaultVisibility(key: string) {
|
||||||
if (localStorage.getItem("defaultVisibility") === null) {
|
if (localStorage.getItem("defaultVisibility") === null) {
|
||||||
console.warn("This key has not been set before.");
|
console.warn("This key has not been set before.");
|
||||||
}
|
}
|
||||||
localStorage.setItem("defaultVisibility", key.toString());
|
localStorage.setItem("defaultVisibility", key.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the user's default theme or the default theme
|
* Gets the user's default theme or the default theme
|
||||||
*/
|
*/
|
||||||
export function getUserDefaultTheme() {
|
export function getUserDefaultTheme() {
|
||||||
let returnTheme = defaultTheme;
|
let returnTheme = defaultTheme;
|
||||||
themes.forEach(theme => {
|
themes.forEach(theme => {
|
||||||
if (theme.key === localStorage.getItem("theme")) {
|
if (theme.key === localStorage.getItem("theme")) {
|
||||||
returnTheme = theme;
|
returnTheme = theme;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return returnTheme;
|
return returnTheme;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -86,42 +86,42 @@ export function getUserDefaultTheme() {
|
||||||
* @param themeName The name of the theme
|
* @param themeName The name of the theme
|
||||||
*/
|
*/
|
||||||
export function setUserDefaultTheme(themeName: string) {
|
export function setUserDefaultTheme(themeName: string) {
|
||||||
localStorage.setItem("theme", themeName);
|
localStorage.setItem("theme", themeName);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates the user defaults if they do not exist already.
|
* Creates the user defaults if they do not exist already.
|
||||||
*/
|
*/
|
||||||
export function createUserDefaults() {
|
export function createUserDefaults() {
|
||||||
let defaults: SettingsTemplate = {
|
let defaults: SettingsTemplate = {
|
||||||
darkModeEnabled: false,
|
darkModeEnabled: false,
|
||||||
systemDecidesDarkMode: true,
|
systemDecidesDarkMode: true,
|
||||||
enablePushNotifications: true,
|
enablePushNotifications: true,
|
||||||
clearNotificationsOnRead: false,
|
clearNotificationsOnRead: false,
|
||||||
displayAllOnNotificationBadge: false,
|
displayAllOnNotificationBadge: false,
|
||||||
defaultVisibility: "public"
|
defaultVisibility: "public"
|
||||||
};
|
};
|
||||||
|
|
||||||
let settings = [
|
let settings = [
|
||||||
"darkModeEnabled",
|
"darkModeEnabled",
|
||||||
"systemDecidesDarkMode",
|
"systemDecidesDarkMode",
|
||||||
"clearNotificationsOnRead",
|
"clearNotificationsOnRead",
|
||||||
"displayAllOnNotificationBadge",
|
"displayAllOnNotificationBadge",
|
||||||
"defaultVisibility"
|
"defaultVisibility"
|
||||||
];
|
];
|
||||||
|
|
||||||
migrateExistingSettings();
|
migrateExistingSettings();
|
||||||
|
|
||||||
settings.forEach((setting: string) => {
|
settings.forEach((setting: string) => {
|
||||||
if (localStorage.getItem(setting) === null) {
|
if (localStorage.getItem(setting) === null) {
|
||||||
if (typeof defaults[setting] === "boolean") {
|
if (typeof defaults[setting] === "boolean") {
|
||||||
setUserDefaultBool(setting, defaults[setting]);
|
setUserDefaultBool(setting, defaults[setting]);
|
||||||
} else {
|
} else {
|
||||||
localStorage.setItem(setting, defaults[setting].toString());
|
localStorage.setItem(setting, defaults[setting].toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
getNotificationRequestPermission();
|
getNotificationRequestPermission();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -129,22 +129,23 @@ export function createUserDefaults() {
|
||||||
* @returns The Promise data from getting the config.
|
* @returns The Promise data from getting the config.
|
||||||
*/
|
*/
|
||||||
export async function getConfig(): Promise<Config | undefined> {
|
export async function getConfig(): Promise<Config | undefined> {
|
||||||
try {
|
try {
|
||||||
const resp = await axios.get("config.json");
|
const resp = await axios.get("config.json");
|
||||||
let config: Config = resp.data;
|
let config: Config = resp.data;
|
||||||
return config;
|
return config;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(
|
console.error(
|
||||||
"Couldn't configure Hyperspace with the config file. Reason: " + err.name
|
"Couldn't configure Hyperspace with the config file. Reason: " +
|
||||||
);
|
err.name
|
||||||
}
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function migrateExistingSettings() {
|
export function migrateExistingSettings() {
|
||||||
if (localStorage.getItem("prefers-dark-mode")) {
|
if (localStorage.getItem("prefers-dark-mode")) {
|
||||||
setUserDefaultBool(
|
setUserDefaultBool(
|
||||||
"darkModeEnabled",
|
"darkModeEnabled",
|
||||||
localStorage.getItem("prefers-dark-mode") === "true"
|
localStorage.getItem("prefers-dark-mode") === "true"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { createMuiTheme, Theme } from "@material-ui/core";
|
import { createMuiTheme, Theme } from "@material-ui/core";
|
||||||
import {
|
import {
|
||||||
HyperspaceTheme,
|
HyperspaceTheme,
|
||||||
themes,
|
themes,
|
||||||
defaultTheme
|
defaultTheme
|
||||||
} from "../types/HyperspaceTheme";
|
} from "../types/HyperspaceTheme";
|
||||||
import { getUserDefaultBool } from "./settings";
|
import { getUserDefaultBool } from "./settings";
|
||||||
import { isDarwinApp, isDarkMode } from "./desktop";
|
import { isDarwinApp, isDarkMode } from "./desktop";
|
||||||
|
@ -13,13 +13,13 @@ import { isDarwinApp, isDarkMode } from "./desktop";
|
||||||
* @returns Hyperspace theme with name or the default
|
* @returns Hyperspace theme with name or the default
|
||||||
*/
|
*/
|
||||||
export function getHyperspaceTheme(name: string): HyperspaceTheme {
|
export function getHyperspaceTheme(name: string): HyperspaceTheme {
|
||||||
let theme: HyperspaceTheme = defaultTheme;
|
let theme: HyperspaceTheme = defaultTheme;
|
||||||
themes.forEach((themeItem: HyperspaceTheme) => {
|
themes.forEach((themeItem: HyperspaceTheme) => {
|
||||||
if (themeItem.key === name) {
|
if (themeItem.key === name) {
|
||||||
theme = themeItem;
|
theme = themeItem;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return theme;
|
return theme;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -28,48 +28,48 @@ export function getHyperspaceTheme(name: string): HyperspaceTheme {
|
||||||
* @returns A Material-UI theme with the Hyperspace theme's palette colors
|
* @returns A Material-UI theme with the Hyperspace theme's palette colors
|
||||||
*/
|
*/
|
||||||
export function setHyperspaceTheme(theme: HyperspaceTheme): Theme {
|
export function setHyperspaceTheme(theme: HyperspaceTheme): Theme {
|
||||||
return createMuiTheme({
|
return createMuiTheme({
|
||||||
typography: {
|
typography: {
|
||||||
fontFamily: [
|
fontFamily: [
|
||||||
"-apple-system",
|
"-apple-system",
|
||||||
"BlinkMacSystemFont",
|
"BlinkMacSystemFont",
|
||||||
'"Segoe UI"',
|
'"Segoe UI"',
|
||||||
"Roboto",
|
"Roboto",
|
||||||
'"Helvetica Neue"',
|
'"Helvetica Neue"',
|
||||||
"Arial",
|
"Arial",
|
||||||
"sans-serif",
|
"sans-serif",
|
||||||
'"Apple Color Emoji"',
|
'"Apple Color Emoji"',
|
||||||
'"Segoe UI Emoji"',
|
'"Segoe UI Emoji"',
|
||||||
'"Segoe UI Symbol"'
|
'"Segoe UI Symbol"'
|
||||||
].join(","),
|
].join(","),
|
||||||
useNextVariants: true
|
useNextVariants: true
|
||||||
},
|
},
|
||||||
palette: {
|
palette: {
|
||||||
primary: theme.palette.primary,
|
primary: theme.palette.primary,
|
||||||
secondary: theme.palette.secondary,
|
secondary: theme.palette.secondary,
|
||||||
type: getUserDefaultBool("darkModeEnabled")
|
type: getUserDefaultBool("darkModeEnabled")
|
||||||
? "dark"
|
? "dark"
|
||||||
: getDarkModeFromSystem() === "dark"
|
: getDarkModeFromSystem() === "dark"
|
||||||
? "dark"
|
? "dark"
|
||||||
: "light"
|
: "light"
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getDarkModeFromSystem(): string {
|
export function getDarkModeFromSystem(): string {
|
||||||
if (getUserDefaultBool("systemDecidesDarkMode")) {
|
if (getUserDefaultBool("systemDecidesDarkMode")) {
|
||||||
if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
|
if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
|
||||||
return "dark";
|
return "dark";
|
||||||
|
} else {
|
||||||
|
if (isDarwinApp()) {
|
||||||
|
return isDarkMode() ? "dark" : "light";
|
||||||
|
} else {
|
||||||
|
return "light";
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (isDarwinApp()) {
|
|
||||||
return isDarkMode() ? "dark" : "light";
|
|
||||||
} else {
|
|
||||||
return "light";
|
return "light";
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
return "light";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -78,10 +78,10 @@ export function getDarkModeFromSystem(): string {
|
||||||
* @param setting Whether dark mode should be on (`true`) or off (`false`)
|
* @param setting Whether dark mode should be on (`true`) or off (`false`)
|
||||||
*/
|
*/
|
||||||
export function darkMode(theme: Theme, setting: boolean): Theme {
|
export function darkMode(theme: Theme, setting: boolean): Theme {
|
||||||
if (setting) {
|
if (setting) {
|
||||||
theme.palette.type = "dark";
|
theme.palette.type = "dark";
|
||||||
} else {
|
} else {
|
||||||
theme.palette.type = "light";
|
theme.palette.type = "light";
|
||||||
}
|
}
|
||||||
return theme;
|
return theme;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue