settings panel restructuring

This commit is contained in:
f0x 2022-09-07 13:51:16 +02:00
parent c1585d5f8a
commit 8b4a848bc8
32 changed files with 583 additions and 62 deletions

View file

@ -27,6 +27,29 @@
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
)
func (m *Module) SettingsPanelHandler(c *gin.Context) {
host := config.GetHost()
instance, err := m.processor.InstanceGet(c.Request.Context(), host)
if err != nil {
api.ErrorHandler(c, gtserror.NewErrorInternalError(err), m.processor.InstanceGet)
return
}
c.HTML(http.StatusOK, "frontend.tmpl", gin.H{
"instance": instance,
"stylesheets": []string{
assetsPathPrefix + "/Fork-Awesome/css/fork-awesome.min.css",
assetsPathPrefix + "/dist/_colors.css",
assetsPathPrefix + "/dist/base.css",
assetsPathPrefix + "/dist/settings-panel-style.css",
},
"javascript": []string{
assetsPathPrefix + "/dist/bundle.js",
assetsPathPrefix + "/dist/settings.js",
},
})
}
func (m *Module) UserPanelHandler(c *gin.Context) {
host := config.GetHost()
instance, err := m.processor.InstanceGet(c.Request.Context(), host)

View file

@ -117,6 +117,7 @@ func (m *Module) profileGETHandler(c *gin.Context) {
"show_back_to_top": showBackToTop,
"stylesheets": stylesheets,
"javascript": []string{
"/assets/dist/bundle.js",
"/assets/dist/frontend.js",
},
})

View file

@ -119,6 +119,7 @@ func (m *Module) threadGETHandler(c *gin.Context) {
"ogMeta": ogBase(instance).withStatus(status),
"stylesheets": stylesheets,
"javascript": []string{
"/assets/dist/bundle.js",
"/assets/dist/frontend.js",
},
})

View file

@ -76,6 +76,9 @@ func (m *Module) Route(s router.Router) error {
c.Redirect(http.StatusMovedPermanently, adminPanelPath)
})
s.AttachHandler(http.MethodGet, "/settings", m.SettingsPanelHandler)
s.AttachHandler(http.MethodGet, "/settings/*panel", m.SettingsPanelHandler)
s.AttachHandler(http.MethodGet, userPanelpath, m.UserPanelHandler)
// redirect /user/ to /user
s.AttachHandler(http.MethodGet, userPanelpath+"/", func(c *gin.Context) {

View file

@ -77,3 +77,13 @@ $boxshadow_border: 0.08rem solid $sloth_gray2_darker5;
$profile_avatar_border: 0.2rem solid $border_accent;
$input_bg: $sloth_gray2_darker3;
$settings-nav-bg: $bg_accent;
$settings-nav-header-fg: $sloth_gray2_darker15;
$settings-nav-header-bg: $sloth_orange2;
$settings-nav-bg-active: $sloth_gray2_darker3;
$settings-nav-fg-active: $fg;
$settings-nav-bg-hover: $sloth_gray2;
/* $settings-nav-fg-hover: $sloth_gray2; */

View file

@ -18,11 +18,6 @@
"use strict";
// WARNING: currently dependencies get deduplicated with factor-bundle, but
// our frontend templates don't load the common bundle.js since it contains React etc
// so we can't use any dependencies that would deduplicate with the other files
const Photoswipe = require("photoswipe/dist/umd/photoswipe.umd.min.js");
const PhotoswipeLightbox = require("photoswipe/dist/umd/photoswipe-lightbox.umd.min.js");
const PhotoswipeCaptionPlugin = require("photoswipe-dynamic-caption-plugin").default;

View file

@ -38,8 +38,9 @@ const splitCSS = require("./lib/split-css.js");
const bundles = {
"./frontend/index.js": "frontend.js",
"./panels/admin/index.js": "admin-panel.js",
"./panels/user/index.js": "user-panel.js",
"./settings-panel/index.js": "settings.js",
// "./panels/admin/index.js": "admin-panel.js",
// "./panels/user/index.js": "user-panel.js",
};
const postcssPlugins = [
@ -50,6 +51,18 @@ const postcssPlugins = [
"postcss-color-mod-function"
].map((plugin) => require(plugin)());
let uglifyifyInProduction;
if (process.env.NODE_ENV != "development") {
console.log("uglifyify'ing production bundles");
uglifyifyInProduction = [
require("uglifyify"), {
global: true,
exts: ".js"
}
];
}
const browserifyConfig = {
transform: [
[
@ -69,10 +82,7 @@ const browserifyConfig = {
exclude: /node_modules\/(?!photoswipe-dynamic-caption-plugin)/,
}
],
[require("uglifyify"), {
global: true,
exts: ".js"
}]
uglifyifyInProduction
],
plugin: [
[require("icssify"), {

View file

@ -35,7 +35,8 @@
"react": "^17.0.1",
"react-dom": "^17.0.1",
"reactify": "^1.1.1",
"uglifyify": "^5.0.2"
"uglifyify": "^5.0.2",
"wouter": "^2.8.0-alpha.2"
},
"devDependencies": {
"@f0x52/eslint-config-react": "^1.1.0",

View file

@ -16,52 +16,8 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
body {
grid-template-rows: auto 1fr;
}
"use strict";
.capitalize {
text-transform: capitalize;
}
section {
margin-bottom: 1rem;
}
input, select, textarea {
box-sizing: border-box;
}
.error {
font-weight: bold;
}
.hidden {
display: none;
}
.messagebutton {
margin-top: 1rem;
display: flex;
gap: 1rem;
align-items: center;
button {
white-space: nowrap;
}
}
.notImplemented {
border: 2px solid rgb(70, 79, 88);
background: repeating-linear-gradient(
-45deg,
#525c66,
#525c66 10px,
rgb(70, 79, 88) 10px,
rgb(70, 79, 88) 20px
) !important;
}
.mono {
font-family: monospace;
}
module.exports = function AdminCustomization() {
return "admin customization";
};

View file

@ -0,0 +1,23 @@
/*
GoToSocial
Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
"use strict";
module.exports = function UserProfile() {
return "federation";
};

View file

@ -0,0 +1,23 @@
/*
GoToSocial
Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
"use strict";
module.exports = function UserProfile() {
return "admin settings";
};

View file

@ -0,0 +1,97 @@
/*
GoToSocial
Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
"use strict";
const Promise = require("bluebird");
const React = require("react");
const oauthLib = require("../lib/oauth");
module.exports = function Auth({setOauth}) {
const [ instance, setInstance ] = React.useState("");
React.useEffect(() => {
let isStillMounted = true;
// check if current domain runs an instance
let thisUrl = new URL(window.location.origin);
thisUrl.pathname = "/api/v1/instance";
Promise.try(() => {
return fetch(thisUrl.href);
}).then((res) => {
if (res.status == 200) {
return res.json();
}
}).then((json) => {
if (json && json.uri && isStillMounted) {
setInstance(json.uri);
}
}).catch((e) => {
console.log("error checking instance response:", e);
});
return () => {
// cleanup function
isStillMounted = false;
};
}, []);
function doAuth() {
return Promise.try(() => {
return new URL(instance);
}).catch(TypeError, () => {
return new URL(`https://${instance}`);
}).then((parsedURL) => {
let url = parsedURL.toString();
let oauth = oauthLib({
instance: url,
client_name: "GotoSocial",
scope: ["admin"],
website: window.location.href
});
setOauth(oauth);
setInstance(url);
return oauth.register().then(() => {
return oauth;
});
}).then((oauth) => {
return oauth.authorize();
}).catch((e) => {
console.log("error authenticating:", e);
});
}
function updateInstance(e) {
if (e.key == "Enter") {
doAuth();
} else {
setInstance(e.target.value);
}
}
return (
<section className="login">
<h1>OAUTH Login:</h1>
<form onSubmit={(e) => e.preventDefault()}>
<label htmlFor="instance">Instance: </label>
<input value={instance} onChange={updateInstance} id="instance"/>
<button onClick={doAuth}>Authenticate</button>
</form>
</section>
);
};

View file

@ -0,0 +1,145 @@
/*
GoToSocial
Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
"use strict";
const Promise = require("bluebird");
const React = require("react");
const ReactDom = require("react-dom");
const { Link, Route, Switch, useRoute, Redirect } = require("wouter");
const Auth = require("./components/auth");
const oauthLib = require("./lib/oauth");
require("./style.css");
const nav = {
"User": [
["Profile", require("./user/profile.js")],
["Settings", require("./user/settings.js")],
["Customization", require("./user/customization.js")]
],
"Admin": [
["Instance Settings", require("./admin/settings.js")],
["Federation", require("./admin/federation.js")],
["Customization", require("./admin/customization.js")]
]
};
function urlSafe(str) {
return str.toLowerCase().replace(/\s+/g, "-");
}
// TODO: nested categories?
const sidebar = [];
const panelRouter = [];
Object.entries(nav).forEach(([category, entries]) => {
let base = `/settings/${urlSafe(category)}`;
// Category header goes to first page in category
panelRouter.push(
<Route key={base} path={base}>
<Redirect to={`${base}/${urlSafe(entries[0][0])}`}/>
</Route>
);
let links = entries.map(([name, component]) => {
let url = `${base}/${urlSafe(name)}`;
panelRouter.push(
<Route key={url} path={url} component={component}/>
);
return <NavButton key={url} href={url} name={name} />;
});
sidebar.push(
<React.Fragment key={category}>
<Link href={`${base}/${urlSafe(entries[0][0])}`}>
<a>
<h2>{category}</h2>
</a>
</Link>
<nav>
{links}
</nav>
</React.Fragment>
);
});
function NavButton({href, name}) {
const [isActive] = useRoute(href);
return (
<Link href={href}>
<a className={isActive ? "active" : ""} data-content={name}>
{name}
</a>
</Link>
);
}
function App() {
const [oauth, setOauth] = React.useState();
const [hasAuth, setAuth] = React.useState(false);
const [oauthState, _setOauthState] = React.useState(localStorage.getItem("oauth"));
React.useEffect(() => {
let state = localStorage.getItem("oauth");
if (state != undefined) {
state = JSON.parse(state);
let restoredOauth = oauthLib(state.config, state);
Promise.try(() => {
return restoredOauth.callback();
}).then(() => {
setAuth(true);
});
setOauth(restoredOauth);
}
}, [setAuth, setOauth]);
if (!hasAuth && oauth && oauth.isAuthorized()) {
setAuth(true);
}
if (oauth && oauth.isAuthorized()) {
return (
<>
<div className="sidebar">
{sidebar}
<button className="logout" onClick={oauth.logout}>Log out</button>
</div>
<section>
<Switch>
{panelRouter}
</Switch>
</section>
</>
);
} else if (oauthState != undefined) {
return (
<section>
processing oauth...
</section>
);
} else {
return <Auth setOauth={setOauth}/>;
}
}
ReactDom.render(<React.StrictMode><App/></React.StrictMode>, document.getElementById("root"));

View file

@ -0,0 +1,158 @@
/*
GoToSocial
Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
body {
grid-template-rows: auto 1fr;
}
.content {
grid-column: 1 / span 3; /* stretch entire width, to fit panel + sidebar nav */
}
section {
grid-column: 2;
}
#root {
display: grid;
grid-template-columns: 1fr min(92%, 90ch) 1fr;
section {
border-left: none;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
.sidebar {
align-self: start; // vertical
justify-self: end; // horizontal
background: $settings-nav-bg;
border: $boxshadow_border;
box-shadow: $boxshadow;
border-radius: $br;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
display: flex;
flex-direction: column;
a {
text-decoration: none;
}
a:first-child h2 {
border-top-left-radius: $br;
}
h2 {
margin: 0;
padding: 0.5rem;
font-size: 0.9rem;
font-weight: bold;
text-transform: uppercase;
color: $settings-nav-header-fg;
background: $settings-nav-header-bg;
border-bottom: 0.1rem solid $sloth_gray2_darker7;
}
nav {
display: flex;
flex-direction: column;
a {
border-bottom: 0.1rem solid $sloth_gray2_darker3;
padding: 1rem;
text-decoration: none;
transition: 0.1s;
color: $fg;
&:hover {
color: $settings-nav-fg-hover;
background: $settings-nav-bg-hover;
}
&.active {
color: $settings-nav-fg-active;
background: $settings-nav-bg-active;
font-weight: bold;
text-decoration: underline;
}
/* reserve space for bold version of the element, so .active doesn't
change container size */
&::after {
font-weight: bold;
text-decoration: underline;
display: block;
content: attr(data-content);
height: 1px;
color: transparent;
overflow: hidden;
visibility: hidden;
}
}
}
nav:last-child a:last-child {
border-bottom-left-radius: $br;
border-bottom: none;
}
}
}
.capitalize {
text-transform: capitalize;
}
section {
margin-bottom: 1rem;
}
input, select, textarea {
box-sizing: border-box;
}
.error {
font-weight: bold;
}
.hidden {
display: none;
}
.messagebutton {
margin-top: 1rem;
display: flex;
gap: 1rem;
align-items: center;
button {
white-space: nowrap;
}
}
.notImplemented {
border: 2px solid rgb(70, 79, 88);
background: repeating-linear-gradient(
-45deg,
#525c66,
#525c66 10px,
rgb(70, 79, 88) 10px,
rgb(70, 79, 88) 20px
) !important;
}

View file

@ -0,0 +1,23 @@
/*
GoToSocial
Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
"use strict";
module.exports = function UserProfile() {
return "user customization";
};

View file

@ -0,0 +1,23 @@
/*
GoToSocial
Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
"use strict";
module.exports = function UserProfile() {
return "user profile";
};

View file

@ -0,0 +1,23 @@
/*
GoToSocial
Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
"use strict";
module.exports = function UserProfile() {
return "user settings";
};

View file

@ -6357,6 +6357,11 @@ word-wrap@^1.2.3, word-wrap@~1.2.3:
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"
integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==
wouter@^2.8.0-alpha.2:
version "2.8.0-alpha.2"
resolved "https://registry.yarnpkg.com/wouter/-/wouter-2.8.0-alpha.2.tgz#d57dfbd23b964b8bd848f2ed3eb2b38cf3c00e00"
integrity sha512-aPsL5m5rW9RiceClOmGj6t5gn9Ut2TJVr98UDi1u9MIRNYiYVflg6vFIjdDYJ4IAyH0JdnkSgGwfo0LQS3k2zg==
wrappy@1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"

View file

@ -1,5 +1,6 @@
{{ template "header.tmpl" .}}
<main class="lightgray">
<div id="root"></div>
<div id="root">
</div>
</main>
{{ template "footer.tmpl" .}}