DEV: Remove admin-revamp and introduce foundations for admin config (#27293)

This commit removes the `/admin-revamp` routes which were introduced as a part of an experiment to revamp the admin pages. We still want to improve the admin/staff experience, but we're going to do them within the existing `/admin` routes instead of introducing a completely new route.

Our initial efforts to improve the Discourse admin experience is this commit which introduces the foundation for a new subroute `/admin/config` which will house various new pages for configuring Discourse. The first new page (or "config area") will be `/admin/config/about` that will house all the settings and controls for configuring the `/about` page of Discourse.

Internal topic: t/128544
This commit is contained in:
Osama Sayegh 2024-06-03 10:18:14 +03:00 committed by GitHub
parent aec892339e
commit fed9055818
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 51 additions and 402 deletions

View File

@ -1,46 +0,0 @@
<div
class="admin-config-area-sidebar-experiment"
{{did-insert this.loadDefaultNavConfig}}
>
<h4>Sidebar Experiment</h4>
<p>Changes you make here will be applied to the admin sidebar and persist
between reloads
<em>on this device only</em>. Note that in addition to the
<code>text</code>
and
<code>route</code>
options, you can also specify a
<code>icon</code>
or a
<code>href</code>, if you want to link to a specific page but don't know the
Ember route. You can also use the Ember Inspector extension to find route
names, for example for plugin routes which are not auto-generated here.
<br /><br />
<code>routeModels</code>
is an array of values that correspond to parts of the route; for example the
topic route may be
<code>/t/:id</code>, so to get a link to topic with ID 123 you would do
<code>routeModels: [123]</code>.</p>
<p>All configuration options for a section and its links looks like this:</p>
<pre><code>{{this.exampleJson}}</code></pre>
<DButton
@action={{this.resetToDefault}}
@translatedLabel="Reset to Default"
/>
<DButton
class="btn-primary"
@action={{this.applyConfig}}
@translatedLabel="Apply Config"
/>
<div class="admin-config-area-sidebar-experiment__editor">
<AceEditor
@content={{this.editedNavConfig}}
@editorId="admin-config-area-sidebar-experiment"
@save={{this.applyNav}}
/>
</div>
</div>

View File

@ -1,130 +0,0 @@
import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking";
import { action } from "@ember/object";
import { service } from "@ember/service";
import { ADMIN_NAV_MAP } from "discourse/lib/sidebar/admin-nav-map";
import {
buildAdminSidebar,
useAdminNavConfig,
} from "discourse/lib/sidebar/admin-sidebar";
import { resetPanelSections } from "discourse/lib/sidebar/custom-sections";
import { ADMIN_PANEL } from "discourse/lib/sidebar/panels";
// TODO (martin) (2024-02-01) Remove this experimental UI.
export default class AdminConfigAreaSidebarExperiment extends Component {
@service adminSidebarStateManager;
@service toasts;
@service router;
@tracked editedNavConfig;
validRouteNames = new Set();
get defaultAdminNav() {
return JSON.stringify(ADMIN_NAV_MAP, null, 2);
}
get exampleJson() {
return JSON.stringify(
{
name: "section-name",
text: "Section Name",
links: [
{
name: "admin-revamp",
route: "admin-revamp",
routeModels: [123],
text: "Revamp",
href: "https://forum.site.com/t/123",
icon: "rocket",
},
],
},
null,
2
);
}
@action
loadDefaultNavConfig() {
const savedConfig = this.adminSidebarStateManager.navConfig;
this.editedNavConfig = savedConfig
? JSON.stringify(savedConfig, null, 2)
: this.defaultAdminNav;
}
@action
resetToDefault() {
this.editedNavConfig = this.defaultAdminNav;
this.#saveConfig(ADMIN_NAV_MAP);
}
@action
applyConfig() {
let config = null;
try {
config = JSON.parse(this.editedNavConfig);
} catch {
this.toasts.error({
duration: 3000,
data: {
message: "There was an error, make sure the structure is valid JSON.",
},
});
return;
}
let invalidRoutes = [];
config.forEach((section) => {
section.links.forEach((link) => {
if (!link.route) {
return;
}
if (this.validRouteNames.has(link.route)) {
return;
}
// Using the private `_routerMicrolib` is not ideal, but Ember doesn't provide
// any other way for us to easily check for route validity.
try {
// eslint-disable-next-line ember/no-private-routing-service
this.router._router._routerMicrolib.recognizer.handlersFor(
link.route
);
this.validRouteNames.add(link.route);
} catch (err) {
// eslint-disable-next-line no-console
console.debug("[AdminSidebarExperiment]", err);
invalidRoutes.push(link.route);
}
});
});
if (invalidRoutes.length) {
this.toasts.error({
duration: 3000,
data: {
message: `There was an error with one or more of the routes provided: ${invalidRoutes.join(
", "
)}`,
},
});
return;
}
this.#saveConfig(config);
}
#saveConfig(config) {
this.adminSidebarStateManager.navConfig = config;
resetPanelSections(
ADMIN_PANEL,
useAdminNavConfig(config),
buildAdminSidebar
);
this.toasts.success({
duration: 3000,
data: { message: "Sidebar navigation applied successfully!" },
});
}
}

View File

@ -0,0 +1,15 @@
import Component from "@glimmer/component";
export default class AdminConfigAreasAbout extends Component {
cards = [1, 2, 3];
<template>
<div class="admin-config-area">
<div class="admin-config-area__primary-content">
{{#each this.cards as |card|}}
<div>{{card}}</div>
{{/each}}
</div>
</div>
</template>
}

View File

@ -1,31 +0,0 @@
import Controller from "@ember/controller";
import { service } from "@ember/service";
import { dasherize } from "@ember/string";
import discourseComputed from "discourse-common/utils/decorators";
export default class AdminRevampController extends Controller {
@service router;
@discourseComputed("router._router.currentPath")
adminContentsClassName(currentPath) {
let cssClasses = currentPath
.split(".")
.filter((segment) => {
return (
segment !== "index" &&
segment !== "loading" &&
segment !== "show" &&
segment !== "admin"
);
})
.map(dasherize)
.join(" ");
// this is done to avoid breaking css customizations
if (cssClasses.includes("dashboard")) {
cssClasses = `${cssClasses} dashboard-next`;
}
return cssClasses;
}
}

View File

@ -1,19 +0,0 @@
import Route from "@ember/routing/route";
import { service } from "@ember/service";
import { dasherize } from "@ember/string";
import AdminConfigAreaSidebarExperiment from "admin/components/admin-config-area-sidebar-experiment";
const CONFIG_AREA_COMPONENT_MAP = {
"sidebar-experiment": AdminConfigAreaSidebarExperiment,
};
export default class AdminRevampConfigAreaRoute extends Route {
@service router;
async model(params) {
return {
area: params.area,
configAreaComponent: CONFIG_AREA_COMPONENT_MAP[dasherize(params.area)],
};
}
}

View File

@ -1,6 +0,0 @@
import Route from "@ember/routing/route";
import { service } from "@ember/service";
export default class AdminRevampConfigRoute extends Route {
@service router;
}

View File

@ -1,6 +0,0 @@
import Route from "@ember/routing/route";
import { service } from "@ember/service";
export default class AdminRevampLobbyRoute extends Route {
@service router;
}

View File

@ -1,40 +0,0 @@
import { service } from "@ember/service";
import { MAIN_PANEL } from "discourse/lib/sidebar/panels";
import DiscourseURL from "discourse/lib/url";
import DiscourseRoute from "discourse/routes/discourse";
import I18n from "discourse-i18n";
// DEPRECATED: (martin) This route is deprecated and will be removed in the near future.
export default class AdminRoute extends DiscourseRoute {
@service siteSettings;
@service currentUser;
@service sidebarState;
@service adminSidebarStateManager;
titleToken() {
return I18n.t("admin_title");
}
activate() {
if (!this.currentUser.use_admin_sidebar) {
return DiscourseURL.redirectTo("/admin");
}
this.adminSidebarStateManager.maybeForceAdminSidebar({
onlyIfAlreadyActive: false,
});
this.controllerFor("application").setProperties({
showTop: false,
});
}
deactivate(transition) {
this.controllerFor("application").set("showTop", true);
if (this.adminSidebarStateManager.currentUserUsingAdminSidebar) {
if (!transition?.to.name.startsWith("admin")) {
this.sidebarState.setPanel(MAIN_PANEL);
}
}
}
}

View File

@ -210,10 +210,14 @@ export default function () {
);
this.route(
"adminConfigFlags",
{ path: "/config/flags", resetNamespace: true },
"adminConfig",
{ path: "/config", resetNamespace: true },
function () {
this.route("flags", function () {
this.route("index", { path: "/" });
});
this.route("about");
}
);
@ -233,14 +237,4 @@ export default function () {
resetNamespace: true,
});
});
// EXPERIMENTAL: These admin routes are hidden behind an `admin_sidebar_enabled_groups`
// site setting and are subject to constant change.
this.route("admin-revamp", { resetNamespace: true }, function () {
this.route("lobby", { path: "/" }, function () {});
this.route("config", function () {
this.route("area", { path: "/:area" });
});
});
}

View File

@ -1,5 +0,0 @@
<div class="admin-revamp__config-area">
{{#if @model.configAreaComponent}}
<@model.configAreaComponent />
{{/if}}
</div>

View File

@ -1,3 +0,0 @@
<div class="admin-revamp__config">
{{outlet}}
</div>

View File

@ -1 +0,0 @@
Admin Revamp Lobby

View File

@ -1,13 +0,0 @@
{{hide-application-footer}}
{{html-class "admin-area"}}
{{body-class "admin-interface"}}
<div class="row">
<div class="full-width">
<div class="boxed white admin-content">
<div class="admin-contents {{this.adminContentsClassName}}">
{{outlet}}
</div>
</div>
</div>
</div>

View File

@ -0,0 +1 @@
<AdminConfigAreas::About />

View File

@ -112,7 +112,7 @@ export const ADMIN_NAV_MAP = [
},
{
name: "admin_moderation_flags",
route: "adminConfigFlags",
route: "adminConfig.flags",
label: "admin.community.sidebar_link.moderation_flags",
icon: "flag",
},

View File

@ -12,7 +12,6 @@ import {
secondaryCustomSectionLinks,
} from "discourse/lib/sidebar/custom-community-section-links";
import SectionLink from "discourse/lib/sidebar/section-link";
import AdminRevampSectionLink from "discourse/lib/sidebar/user/community-section/admin-revamp-section-link";
import AdminSectionLink from "discourse/lib/sidebar/user/community-section/admin-section-link";
import MyPostsSectionLink from "discourse/lib/sidebar/user/community-section/my-posts-section-link";
import ReviewSectionLink from "discourse/lib/sidebar/user/community-section/review-section-link";
@ -26,7 +25,6 @@ const SPECIAL_LINKS_MAP = {
"/review": ReviewSectionLink,
"/badges": BadgesSectionLink,
"/admin": AdminSectionLink,
"/admin-revamp": AdminRevampSectionLink,
"/g": GroupsSectionLink,
};

View File

@ -1,38 +0,0 @@
import { service } from "@ember/service";
import BaseSectionLink from "discourse/lib/sidebar/base-community-section-link";
import I18n from "discourse-i18n";
export default class AdminRevampSectionLink extends BaseSectionLink {
@service siteSettings;
get name() {
return "admin-revamp";
}
get route() {
return "admin-revamp";
}
get title() {
return I18n.t("sidebar.sections.community.links.admin.content");
}
get text() {
return I18n.t(
`sidebar.sections.community.links.${this.overridenName.toLowerCase()}.content`,
{ defaultValue: this.overridenName }
);
}
get shouldDisplay() {
if (!this.currentUser) {
return false;
}
return this.currentUser.use_admin_sidebar;
}
get defaultPrefixValue() {
return "star";
}
}

View File

@ -1065,6 +1065,7 @@ a.inline-editable-field {
@import "common/admin/api";
@import "common/admin/backups";
@import "common/admin/plugins";
@import "common/admin/admin_config_area";
@import "common/admin/admin_reports";
@import "common/admin/admin_report";
@import "common/admin/admin_report_counters";
@ -1078,6 +1079,3 @@ a.inline-editable-field {
@import "common/admin/mini_profiler";
@import "common/admin/schema_theme_setting_editor";
@import "common/admin/customize_themes_show_schema";
// EXPERIMENTAL: Revamped admin styles, probably can be split up later down the line.
@import "common/admin/admin_revamp";

View File

@ -0,0 +1,12 @@
.admin-config-area {
display: grid;
grid-template-columns: 2fr 1fr;
column-gap: 1em;
&__primary-content {
background: red;
}
&__help-inset {
background: green;
}
}

View File

@ -1,34 +0,0 @@
.admin-revamp {
&__config-area {
padding: 1em;
margin: 1em 0;
background-color: var(--primary-very-low);
}
}
.admin-config-area-sidebar-experiment {
&__editor {
margin-top: 1em;
.ace-wrapper {
position: relative;
width: 100%;
height: calc(100vh);
min-height: 500px;
.ace_editor {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
}
}
}
}
.admin-area .sidebar-wrapper .admin-panel {
background-color: var(--d-sidebar-admin-background);
.sidebar-section-header-text {
font-weight: bold;
}
}

View File

@ -0,0 +1,9 @@
# frozen_string_literal: true
class Admin::Config::AboutController < Admin::AdminController
def index
end
def update
end
end

View File

@ -803,9 +803,7 @@ class ApplicationController < ActionController::Base
def check_xhr
# bypass xhr check on PUT / POST / DELETE provided api key is there, otherwise calling api is annoying
return if !request.get? && (is_api? || is_user_api?)
unless ((request.format && request.format.json?) || request.xhr?)
raise ApplicationController::RenderEmpty.new
end
raise ApplicationController::RenderEmpty.new if !request.format&.json? && !request.xhr?
end
def apply_cdn_headers

View File

@ -98,14 +98,6 @@ Discourse::Application.routes.draw do
get "wizard/steps/:id" => "wizard#index"
put "wizard/steps/:id" => "steps#update"
namespace :admin_revamp,
path: "admin-revamp",
module: "admin",
constraints: StaffConstraint.new do
get "" => "admin#index"
get "config/:area" => "admin#index"
end
namespace :admin, constraints: StaffConstraint.new do
get "" => "admin#index"
@ -397,6 +389,10 @@ Discourse::Application.routes.draw do
resources :flags, only: %i[index] do
put "toggle"
end
resources :about, constraints: AdminConstraint.new, only: %i[index] do
collection { put "/" => "about#update" }
end
end
end # admin namespace