FEATURE: created edit and delete flags (#27484)

Allow admins to create edit and delete flags.
This commit is contained in:
Krzysztof Kotlarek
2024-07-03 08:45:37 +10:00
committed by GitHub
parent a86590ffd6
commit c3fadc7330
43 changed files with 1112 additions and 42 deletions

View File

@@ -6,9 +6,10 @@ import { ajax } from "discourse/lib/ajax";
import { popupAjaxError } from "discourse/lib/ajax-error";
import i18n from "discourse-common/helpers/i18n";
import { bind } from "discourse-common/utils/decorators";
import AdminConfigHeader from "admin/components/admin-config-header";
import AdminFlagItem from "admin/components/admin-flag-item";
export default class AdminFlags extends Component {
export default class AdminConfigAreasFlags extends Component {
@service site;
@tracked flags = this.site.flagTypes;
@@ -46,19 +47,39 @@ export default class AdminFlags extends Component {
});
}
@action
deleteFlagCallback(flag) {
return ajax(`/admin/config/flags/${flag.id}`, {
type: "DELETE",
})
.then(() => {
this.flags.removeObject(flag);
})
.catch((error) => popupAjaxError(error));
}
<template>
<div class="container admin-flags">
<h1>{{i18n "admin.flags.title"}}</h1>
<table class="flags grid">
<AdminConfigHeader
@name="flags"
@heading="admin.config_areas.flags.header"
@subheading="admin.config_areas.flags.subheader"
@primaryActionRoute="adminConfig.flags.new"
@primaryActionCssClass="admin-flags__header-add-flag"
@primaryActionIcon="plus"
@primaryActionLabel="admin.config_areas.flags.add"
/>
<table class="admin-flags__items grid">
<thead>
<th>{{i18n "admin.flags.description"}}</th>
<th>{{i18n "admin.flags.enabled"}}</th>
<th>{{i18n "admin.config_areas.flags.description"}}</th>
<th>{{i18n "admin.config_areas.flags.enabled"}}</th>
</thead>
<tbody>
{{#each this.flags as |flag|}}
<AdminFlagItem
@flag={{flag}}
@moveFlagCallback={{this.moveFlagCallback}}
@deleteFlagCallback={{this.deleteFlagCallback}}
@isFirstFlag={{this.isFirstFlag flag}}
@isLastFlag={{this.isLastFlag flag}}
/>

View File

@@ -0,0 +1,34 @@
import Component from "@glimmer/component";
import { LinkTo } from "@ember/routing";
import concatClass from "discourse/helpers/concat-class";
import dIcon from "discourse-common/helpers/d-icon";
import i18n from "discourse-common/helpers/i18n";
export default class AdminFlagItem extends Component {
get headerCssClass() {
return `admin-${this.args.name}__header`;
}
<template>
<div class={{this.headerCssClass}}>
<h2>{{i18n @heading}}</h2>
{{#if @primaryActionRoute}}
<LinkTo
@route={{@primaryActionRoute}}
class={{concatClass
"btn-primary"
"btn"
"btn-icon-text"
@primaryActionCssClass
}}
>
{{dIcon @primaryActionIcon}}
{{i18n @primaryActionLabel}}
</LinkTo>
{{/if}}
{{#if @subheading}}
<h3>{{i18n @subheading}}</h3>
{{/if}}
</div>
</template>
}

View File

@@ -3,7 +3,9 @@ import { tracked } from "@glimmer/tracking";
import { fn } from "@ember/helper";
import { on } from "@ember/modifier";
import { action } from "@ember/object";
import { inject as service } from "@ember/service";
import { htmlSafe } from "@ember/template";
import { not } from "truth-helpers";
import DButton from "discourse/components/d-button";
import DToggleSwitch from "discourse/components/d-toggle-switch";
import DropdownMenu from "discourse/components/dropdown-menu";
@@ -14,22 +16,48 @@ import i18n from "discourse-common/helpers/i18n";
import DMenu from "float-kit/components/d-menu";
export default class AdminFlagItem extends Component {
@service dialog;
@service router;
@tracked enabled = this.args.flag.enabled;
get canMove() {
return this.args.flag.id !== SYSTEM_FLAG_IDS.notify_user;
}
get canEdit() {
return (
!Object.values(SYSTEM_FLAG_IDS).includes(this.args.flag.id) &&
!this.args.flag.is_used
);
}
get editTitle() {
return this.canEdit
? "admin.config_areas.flags.form.edit_flag"
: "admin.config_areas.flags.form.non_editable";
}
get deleteTitle() {
return this.canEdit
? "admin.config_areas.flags.form.edit_flag"
: "admin.config_areas.flags.form.non_editable";
}
@action
toggleFlagEnabled(flag) {
this.enabled = !this.enabled;
return ajax(`/admin/config/flags/${flag.id}/toggle`, {
type: "PUT",
}).catch((error) => {
this.enabled = !this.enabled;
return popupAjaxError(error);
});
})
.then(() => {
this.args.flag.enabled = this.enabled;
})
.catch((error) => {
this.enabled = !this.enabled;
return popupAjaxError(error);
});
}
@action
@@ -48,6 +76,23 @@ export default class AdminFlagItem extends Component {
this.args.moveFlagCallback(this.args.flag, "down");
this.dMenu.close();
}
@action
edit() {
this.router.transitionTo("adminConfig.flags.edit", this.args.flag);
}
@action
delete() {
this.dialog.yesNoConfirm({
message: i18n("admin.config_areas.flags.delete_confirm", {
name: this.args.flag.name,
}),
didConfirm: () => {
this.args.deleteFlagCallback(this.args.flag);
},
});
this.dMenu.close();
}
<template>
<tr class="admin-flag-item {{@flag.name_key}}">
@@ -64,10 +109,19 @@ export default class AdminFlagItem extends Component {
class="admin-flag-item__toggle {{@flag.name_key}}"
{{on "click" (fn this.toggleFlagEnabled @flag)}}
/>
<DButton
class="btn btn-secondary admin-flag-item__edit"
@action={{this.edit}}
@label="admin.config_areas.flags.edit"
@disabled={{not this.canEdit}}
@title={{this.editTitle}}
/>
{{#if this.canMove}}
<DMenu
@identifier="flag-menu"
@title={{i18n "admin.flags.more_options.title"}}
@title={{i18n "admin.config_areas.flags.more_options.title"}}
@icon="ellipsis-v"
@onRegisterApi={{this.onRegisterApi}}
>
@@ -76,9 +130,9 @@ export default class AdminFlagItem extends Component {
{{#unless @isFirstFlag}}
<dropdown.item>
<DButton
@label="admin.flags.more_options.move_up"
@label="admin.config_areas.flags.more_options.move_up"
@icon="arrow-up"
@class="btn-transparent move-up"
@class="btn-transparent admin-flag-item__move-up"
@action={{this.moveUp}}
/>
</dropdown.item>
@@ -86,13 +140,24 @@ export default class AdminFlagItem extends Component {
{{#unless @isLastFlag}}
<dropdown.item>
<DButton
@label="admin.flags.more_options.move_down"
@label="admin.config_areas.flags.more_options.move_down"
@icon="arrow-down"
@class="btn-transparent move-down"
@class="btn-transparent admin-flag-item__move-down"
@action={{this.moveDown}}
/>
</dropdown.item>
{{/unless}}
<dropdown.item>
<DButton
@label="admin.config_areas.flags.delete"
@icon="trash-alt"
class="btn-transparent admin-flag-item__delete"
@action={{this.delete}}
@disabled={{not this.canEdit}}
@title={{this.deleteTitle}}
/>
</dropdown.item>
</DropdownMenu>
</:content>
</DMenu>

View File

@@ -0,0 +1,190 @@
import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking";
import { Input } from "@ember/component";
import { hash } from "@ember/helper";
import { TextArea } from "@ember/legacy-built-in-components";
import { action } from "@ember/object";
import { LinkTo } from "@ember/routing";
import { service } from "@ember/service";
import { isEmpty } from "@ember/utils";
import { not } from "truth-helpers";
import DButton from "discourse/components/d-button";
import { ajax } from "discourse/lib/ajax";
import { popupAjaxError } from "discourse/lib/ajax-error";
import dIcon from "discourse-common/helpers/d-icon";
import i18n from "discourse-common/helpers/i18n";
import { bind } from "discourse-common/utils/decorators";
import I18n from "discourse-i18n";
import AdminConfigAreaCard from "admin/components/admin-config-area-card";
import MultiSelect from "select-kit/components/multi-select";
export default class AdminFlagsForm extends Component {
@service router;
@service site;
@tracked enabled = true;
@tracked name;
@tracked description;
@tracked appliesTo;
constructor() {
super(...arguments);
if (this.isUpdate) {
this.name = this.args.flag.name;
this.description = this.args.flag.description;
this.appliesTo = this.args.flag.applies_to;
this.enabled = this.args.flag.enabled;
}
}
get isUpdate() {
return this.args.flag;
}
get isValid() {
return (
!isEmpty(this.name) &&
!isEmpty(this.description) &&
!isEmpty(this.appliesTo)
);
}
get header() {
return this.isUpdate
? "admin.config_areas.flags.form.edit_header"
: "admin.config_areas.flags.form.add_header";
}
get appliesToValues() {
return this.site.valid_flag_applies_to_types.map((type) => {
return {
name: I18n.t(
`admin.config_areas.flags.form.${type
.toLowerCase()
.replace("::", "_")}`
),
id: type,
};
});
}
@action
save() {
this.isUpdate ? this.update() : this.create();
}
@bind
create() {
return ajax(`/admin/config/flags`, {
type: "POST",
data: this.#formData,
})
.then((response) => {
this.site.flagTypes.push(response.flag);
this.router.transitionTo("adminConfig.flags");
})
.catch((error) => {
return popupAjaxError(error);
});
}
@bind
update() {
return ajax(`/admin/config/flags/${this.args.flag.id}`, {
type: "PUT",
data: this.#formData,
})
.then((response) => {
this.args.flag.name = response.flag.name;
this.args.flag.description = response.flag.description;
this.args.flag.applies_to = response.flag.applies_to;
this.args.flag.enabled = response.flag.enabled;
this.router.transitionTo("adminConfig.flags");
})
.catch((error) => {
return popupAjaxError(error);
});
}
@bind
get #formData() {
return {
name: this.name,
description: this.description,
applies_to: this.appliesTo,
enabled: this.enabled,
};
}
<template>
<div class="admin-config-area">
<h2>{{i18n "admin.config_areas.flags.header"}}</h2>
<LinkTo
@route="adminConfig.flags"
class="btn-default btn btn-icon-text btn-back"
>
{{dIcon "chevron-left"}}
{{i18n "admin.config_areas.flags.back"}}
</LinkTo>
<div class="admin-config-area__primary-content admin-flag-form">
<AdminConfigAreaCard @heading={{this.header}}>
<div class="control-group">
<label for="name">
{{i18n "admin.config_areas.flags.form.name"}}
</label>
<Input
name="name"
@type="text"
@value={{this.name}}
maxlength="200"
class="admin-flag-form__name"
/>
</div>
<div class="control-group">
<label for="description">
{{i18n "admin.config_areas.flags.form.description"}}
</label>
<TextArea
@value={{this.description}}
maxlength="1000"
class="admin-flag-form__description"
/>
</div>
<div class="control-group">
<label for="applies-to">
{{i18n "admin.config_areas.flags.form.applies_to"}}
</label>
<MultiSelect
@value={{this.appliesTo}}
@content={{this.appliesToValues}}
@options={{hash allowAny=false}}
class="admin-flag-form__applies-to"
/>
</div>
<div class="control-group">
<label class="checkbox-label admin-flag-form__enabled">
<Input @type="checkbox" @checked={{this.enabled}} />
{{i18n "admin.config_areas.flags.form.enabled"}}
</label>
</div>
<div class="alert alert-info admin_flag_form__info">
{{dIcon "info-circle"}}
{{i18n "admin.config_areas.flags.form.alert"}}
</div>
<DButton
@action={{this.save}}
@label="admin.config_areas.flags.form.save"
@ariaLabel="admin.config_areas.flags.form.save"
@disabled={{not this.isValid}}
class="btn-primary admin-flag-form__save"
/>
</AdminConfigAreaCard>
</div>
</div>
</template>
}

View File

@@ -0,0 +1,10 @@
import Route from "@ember/routing/route";
import { service } from "@ember/service";
export default class AdminConfigFlagsEditRoute extends Route {
@service site;
model(params) {
return this.site.flagTypes.findBy("id", parseInt(params.flag_id, 10));
}
}

View File

@@ -215,6 +215,8 @@ export default function () {
function () {
this.route("flags", function () {
this.route("index", { path: "/" });
this.route("new");
this.route("edit", { path: "/:flag_id" });
});
this.route("about");

View File

@@ -0,0 +1 @@
<AdminFlagsForm @flag={{@model}} />

View File

@@ -1 +1 @@
<AdminFlags />
<AdminConfigAreas::Flags />

View File

@@ -0,0 +1 @@
<AdminFlagsForm />