1
0
mirror of https://github.com/flarum/core.git synced 2025-08-12 03:14:33 +02:00

Update for Admin UX Redesign (#103)

* Add composer metadata for admin ux redesign
* Admin js updates for admin ux redesign
This commit is contained in:
Alexander Skvortsov
2020-11-25 17:02:57 -05:00
committed by GitHub
parent 870c3e7c0a
commit 076893f861
12 changed files with 200 additions and 181 deletions

View File

@@ -22,6 +22,12 @@
}, },
"flarum-extension": { "flarum-extension": {
"title": "Tags", "title": "Tags",
"category": "discussion",
"info": {
"donate": "https://flarum.org/donate/",
"website": "https://flarum.org",
"support": "https://discuss.flarum.org"
},
"icon": { "icon": {
"name": "fas fa-tags", "name": "fas fa-tags",
"backgroundColor": "#F28326", "backgroundColor": "#F28326",

View File

@@ -1,12 +1,9 @@
import { extend } from 'flarum/extend'; export default function () {
import PermissionGrid from 'flarum/components/PermissionGrid'; app.extensionData
.for('flarum-tags')
export default function() { .registerPermission({
extend(PermissionGrid.prototype, 'moderateItems', items => {
items.add('tag', {
icon: 'fas fa-tag', icon: 'fas fa-tag',
label: app.translator.trans('flarum-tags.admin.permissions.tag_discussions_label'), label: app.translator.trans('flarum-tags.admin.permissions.tag_discussions_label'),
permission: 'discussion.tag' permission: 'discussion.tag',
}, 95); }, 'moderate', 95);
});
} }

View File

@@ -1,19 +0,0 @@
import { extend } from 'flarum/extend';
import AdminNav from 'flarum/components/AdminNav';
import AdminLinkButton from 'flarum/components/AdminLinkButton';
import TagsPage from './components/TagsPage';
export default function() {
app.routes.tags = {path: '/tags', component: TagsPage};
app.extensionSettings['flarum-tags'] = () => m.route.set(app.route('tags'));
extend(AdminNav.prototype, 'items', items => {
items.add('tags', AdminLinkButton.component({
href: app.route('tags'),
icon: 'fas fa-tags',
description: app.translator.trans('flarum-tags.admin.nav.tags_text')
}, app.translator.trans('flarum-tags.admin.nav.tags_button')));
});
}

View File

@@ -2,8 +2,6 @@ import compat from '../common/compat';
import addTagsHomePageOption from './addTagsHomePageOption'; import addTagsHomePageOption from './addTagsHomePageOption';
import addTagChangePermission from './addTagChangePermission'; import addTagChangePermission from './addTagChangePermission';
import addTagsPane from './addTagsPane';
import TagSettingsModal from './components/TagSettingsModal';
import TagsPage from './components/TagsPage'; import TagsPage from './components/TagsPage';
import EditTagModal from './components/EditTagModal'; import EditTagModal from './components/EditTagModal';
import addTagPermission from './addTagPermission'; import addTagPermission from './addTagPermission';
@@ -12,8 +10,6 @@ import addTagsPermissionScope from './addTagsPermissionScope';
export default Object.assign(compat, { export default Object.assign(compat, {
'tags/addTagsHomePageOption': addTagsHomePageOption, 'tags/addTagsHomePageOption': addTagsHomePageOption,
'tags/addTagChangePermission': addTagChangePermission, 'tags/addTagChangePermission': addTagChangePermission,
'tags/addTagsPane': addTagsPane,
'tags/components/TagSettingsModal': TagSettingsModal,
'tags/components/TagsPage': TagsPage, 'tags/components/TagsPage': TagsPage,
'tags/components/EditTagModal': EditTagModal, 'tags/components/EditTagModal': EditTagModal,
'tags/addTagPermission': addTagPermission, 'tags/addTagPermission': addTagPermission,

View File

@@ -22,6 +22,7 @@ export default class EditTagModal extends Modal {
this.color = Stream(this.tag.color() || ''); this.color = Stream(this.tag.color() || '');
this.icon = Stream(this.tag.icon() || ''); this.icon = Stream(this.tag.icon() || '');
this.isHidden = Stream(this.tag.isHidden() || false); this.isHidden = Stream(this.tag.isHidden() || false);
this.primary = Stream(this.attrs.primary || false);
} }
className() { className() {
@@ -114,7 +115,8 @@ export default class EditTagModal extends Modal {
description: this.description(), description: this.description(),
color: this.color(), color: this.color(),
icon: this.icon(), icon: this.icon(),
isHidden: this.isHidden() isHidden: this.isHidden(),
primary: this.primary(),
}; };
} }

View File

@@ -1,65 +0,0 @@
import SettingsModal from 'flarum/components/SettingsModal';
import withAttr from 'flarum/utils/withAttr';
export default class TagSettingsModal extends SettingsModal {
setMinTags(minTags, maxTags, value) {
minTags(value);
maxTags(Math.max(value, maxTags()));
}
className() {
return 'TagSettingsModal Modal--small';
}
title() {
return app.translator.trans('flarum-tags.admin.tag_settings.title');
}
form() {
const minPrimaryTags = this.setting('flarum-tags.min_primary_tags', 0);
const maxPrimaryTags = this.setting('flarum-tags.max_primary_tags', 0);
const minSecondaryTags = this.setting('flarum-tags.min_secondary_tags', 0);
const maxSecondaryTags = this.setting('flarum-tags.max_secondary_tags', 0);
return [
<div className="Form-group">
<label>{app.translator.trans('flarum-tags.admin.tag_settings.required_primary_heading')}</label>
<div className="helpText">
{app.translator.trans('flarum-tags.admin.tag_settings.required_primary_text')}
</div>
<div className="TagSettingsModal-rangeInput">
<input className="FormControl"
type="number"
min="0"
value={minPrimaryTags()}
oninput={withAttr('value', this.setMinTags.bind(this, minPrimaryTags, maxPrimaryTags))} />
{app.translator.trans('flarum-tags.admin.tag_settings.range_separator_text')}
<input className="FormControl"
type="number"
min={minPrimaryTags()}
bidi={maxPrimaryTags} />
</div>
</div>,
<div className="Form-group">
<label>{app.translator.trans('flarum-tags.admin.tag_settings.required_secondary_heading')}</label>
<div className="helpText">
{app.translator.trans('flarum-tags.admin.tag_settings.required_secondary_text')}
</div>
<div className="TagSettingsModal-rangeInput">
<input className="FormControl"
type="number"
min="0"
value={minSecondaryTags()}
oninput={withAttr('value', this.setMinTags.bind(this, minSecondaryTags, maxSecondaryTags))} />
{app.translator.trans('flarum-tags.admin.tag_settings.range_separator_text')}
<input className="FormControl"
type="number"
min={minSecondaryTags()}
bidi={maxSecondaryTags} />
</div>
</div>
];
}
}

View File

@@ -1,10 +1,10 @@
import sortable from 'sortablejs'; import sortable from 'sortablejs';
import Page from 'flarum/components/Page'; import ExtensionPage from 'flarum/components/ExtensionPage';
import Button from 'flarum/components/Button'; import Button from 'flarum/components/Button';
import withAttr from 'flarum/utils/withAttr';
import EditTagModal from './EditTagModal'; import EditTagModal from './EditTagModal';
import TagSettingsModal from './TagSettingsModal';
import tagIcon from '../../common/helpers/tagIcon'; import tagIcon from '../../common/helpers/tagIcon';
import sortTags from '../../common/utils/sortTags'; import sortTags from '../../common/utils/sortTags';
@@ -31,7 +31,7 @@ function tagItem(tag) {
); );
} }
export default class TagsPage extends Page { export default class TagsPage extends ExtensionPage {
oninit(vnode) { oninit(vnode) {
super.oninit(vnode); super.oninit(vnode);
@@ -42,44 +42,89 @@ export default class TagsPage extends Page {
this.forcedRefreshKey = 0; this.forcedRefreshKey = 0;
} }
view() { content() {
const minPrimaryTags = this.setting('flarum-tags.min_primary_tags', 0);
const maxPrimaryTags = this.setting('flarum-tags.max_primary_tags', 0);
const minSecondaryTags = this.setting('flarum-tags.min_secondary_tags', 0);
const maxSecondaryTags = this.setting('flarum-tags.max_secondary_tags', 0);
return ( return (
<div className="TagsPage"> <div className="TagsContent">
<div className="TagsPage-header"> <div className="TagsContent-list">
<div className="container"> <div className="container" key={this.forcedRefreshKey} oncreate={this.onListOnCreate.bind(this)}><div className="SettingsGroups">
<p>
{app.translator.trans('flarum-tags.admin.tags.about_tags_text')}
</p>
{Button.component({
className: 'Button Button--primary',
icon: 'fas fa-plus',
onclick: () => app.modal.show(EditTagModal)
}, app.translator.trans('flarum-tags.admin.tags.create_tag_button'))}
{Button.component({
className: 'Button',
onclick: () => app.modal.show(TagSettingsModal)
}, app.translator.trans('flarum-tags.admin.tags.settings_button'))}
</div>
</div>
<div className="TagsPage-list">
<div className="container" key={this.forcedRefreshKey} oncreate={this.onListOnCreate.bind(this)}>
<div className="TagGroup"> <div className="TagGroup">
<label>{app.translator.trans('flarum-tags.admin.tags.primary_heading')}</label> <label>{app.translator.trans('flarum-tags.admin.tags.primary_heading')}</label>
<ol className="TagList TagList--primary"> <ol className="TagList TagList--primary">
{sortTags(app.store.all('tags')) {sortTags(app.store.all('tags'))
.filter(tag => tag.position() !== null && !tag.isChild()) .filter((tag) => tag.position() !== null && !tag.isChild())
.map(tagItem)} .map(tagItem)}
</ol> </ol>
{Button.component(
{
className: 'Button TagList-button',
icon: 'fas fa-plus',
onclick: () => app.modal.show(EditTagModal, { primary: true }),
},
app.translator.trans('flarum-tags.admin.tags.create_primary_tag_button')
)}
</div> </div>
<div className="TagGroup"> <div className="TagGroup TagGroup--secondary">
<label>{app.translator.trans('flarum-tags.admin.tags.secondary_heading')}</label> <label>{app.translator.trans('flarum-tags.admin.tags.secondary_heading')}</label>
<ul className="TagList"> <ul className="TagList">
{app.store.all('tags') {app.store
.filter(tag => tag.position() === null) .all('tags')
.filter((tag) => tag.position() === null)
.sort((a, b) => a.name().localeCompare(b.name())) .sort((a, b) => a.name().localeCompare(b.name()))
.map(tagItem)} .map(tagItem)}
</ul> </ul>
{Button.component(
{
className: 'Button TagList-button',
icon: 'fas fa-plus',
onclick: () => app.modal.show(EditTagModal, { primary: false }),
},
app.translator.trans('flarum-tags.admin.tags.create_secondary_tag_button')
)}
</div>
<div className="Form">
<label>{app.translator.trans('flarum-tags.admin.tags.settings_heading')}</label>
<div className="Form-group">
<label>{app.translator.trans('flarum-tags.admin.tag_settings.required_primary_heading')}</label>
<div className="helpText">{app.translator.trans('flarum-tags.admin.tag_settings.required_primary_text')}</div>
<div className="TagSettings-rangeInput">
<input
className="FormControl"
type="number"
min="0"
value={minPrimaryTags()}
oninput={withAttr('value', this.setMinTags.bind(this, minPrimaryTags, maxPrimaryTags))}
/>
{app.translator.trans('flarum-tags.admin.tag_settings.range_separator_text')}
<input className="FormControl" type="number" min={minPrimaryTags()} bidi={maxPrimaryTags} />
</div>
</div>
<div className="Form-group">
<label>{app.translator.trans('flarum-tags.admin.tag_settings.required_secondary_heading')}</label>
<div className="helpText">{app.translator.trans('flarum-tags.admin.tag_settings.required_secondary_text')}</div>
<div className="TagSettings-rangeInput">
<input
className="FormControl"
type="number"
min="0"
value={minSecondaryTags()}
oninput={withAttr('value', this.setMinTags.bind(this, minSecondaryTags, maxSecondaryTags))}
/>
{app.translator.trans('flarum-tags.admin.tag_settings.range_separator_text')}
<input className="FormControl" type="number" min={minSecondaryTags()} bidi={maxSecondaryTags} />
</div>
</div>
<div className="Form-group">{this.submitButton()}</div>
</div>
</div>
<div className="TagsContent-footer">
<p>{app.translator.trans('flarum-tags.admin.tags.about_tags_text')}</p>
</div> </div>
</div> </div>
</div> </div>
@@ -100,6 +145,11 @@ export default class TagsPage extends Page {
}); });
} }
setMinTags(minTags, maxTags, value) {
minTags(value);
maxTags(Math.max(value, maxTags()));
}
onSortUpdate(e) { onSortUpdate(e) {
// If we've moved a tag from 'primary' to 'secondary', then we'll update // If we've moved a tag from 'primary' to 'secondary', then we'll update
// its attributes in our local store so that when we redraw the change // its attributes in our local store so that when we redraw the change

View File

@@ -1,16 +1,17 @@
import Tag from '../common/models/Tag'; import Tag from '../common/models/Tag';
import addTagsPermissionScope from './addTagsPermissionScope'; import addTagsPermissionScope from './addTagsPermissionScope';
import addTagPermission from './addTagPermission'; import addTagPermission from './addTagPermission';
import addTagsPane from './addTagsPane';
import addTagsHomePageOption from './addTagsHomePageOption'; import addTagsHomePageOption from './addTagsHomePageOption';
import addTagChangePermission from './addTagChangePermission'; import addTagChangePermission from './addTagChangePermission';
import TagsPage from './components/TagsPage';
app.initializers.add('flarum-tags', app => { app.initializers.add('flarum-tags', app => {
app.store.models.tags = Tag; app.store.models.tags = Tag;
app.extensionData.for('flarum-tags').registerPage(TagsPage);
addTagsPermissionScope(); addTagsPermissionScope();
addTagPermission(); addTagPermission();
addTagsPane();
addTagsHomePageOption(); addTagsHomePageOption();
addTagChangePermission(); addTagChangePermission();
}); });

View File

@@ -3,7 +3,6 @@
@import "admin/TagsPage"; @import "admin/TagsPage";
@import "admin/EditTagModal"; @import "admin/EditTagModal";
@import "admin/TagSettingsModal";
.Dropdown--restrictByTag .Dropdown-menu { .Dropdown--restrictByTag .Dropdown-menu {
max-height: 400px; max-height: 400px;

View File

@@ -1,16 +0,0 @@
.TagSettingsModal {
.Form-group:not(:last-child) {
margin-bottom: 30px;
}
}
.TagSettingsModal-rangeInput {
input {
width: 80px;
display: inline;
margin: 0 5px;
&:first-child {
margin-left: 0;
}
}
}

View File

@@ -1,43 +1,45 @@
.TagsPage-header { .flarum-tags-Page {
background: @control-bg; padding-bottom: 140px;
}
.TagsContent-footer {
color: @control-color; color: @control-color;
padding: 20px 0; padding: 20px 0;
.container {
max-width: 600px;
}
p { p {
margin-bottom: 20px; margin-top: 10px;
}
.Button {
margin-right: 10px;
} }
} }
.TagsPage-list {
padding: 20px 0;
.container { .TagsContent-list {
max-width: 600px; padding: 20px 0 0;
}
} }
.TagList, .TagList ol {
.TagList,
.TagList ol {
list-style: none; list-style: none;
padding: 10px 0; padding: 0;
margin: 0;
color: @muted-color; color: @muted-color;
font-size: 13px; font-size: 13px;
> li { >li {
display: inline-block;
max-height: 40px;
cursor: move; cursor: move;
width: 100%;
} }
.TagIcon, .icon { .TagIcon,
.icon {
margin-right: 10px; margin-right: 10px;
} }
} }
.TagListItem-info { .TagListItem-info {
padding: 5px 10px;
border-radius: @border-radius; border-radius: @border-radius;
padding: 5px;
max-width: max-content;
&:hover { &:hover {
background: @control-bg; background: @control-bg;
@@ -46,45 +48,110 @@
.Button { .Button {
float: right; float: right;
visibility: hidden; visibility: hidden;
margin: -8px -10px -8px 10px; margin: -8px -16px -8px 16px;
} }
} }
li:not(.sortable-dragging) > .TagListItem-info:hover > .Button {
li:not(.sortable-dragging)>.TagListItem-info:hover>.Button {
visibility: visible; visibility: visible;
} }
.TagList--primary { .TagList--primary {
font-size: 16px; font-size: 16px;
> .sortable-placeholder { >.sortable-placeholder {
height: 34px; height: 38px;
margin-bottom: 10px; margin-bottom: 10px;
} }
} }
.TagList ol { .TagList ol {
margin-left: 27px; margin-left: 27px;
min-height: 10px; min-height: 10px;
padding: 0; padding: 0;
& > :last-child { &> :last-child {
margin-bottom: 10px; margin-bottom: 10px;
} }
} }
.sortable-placeholder { .sortable-placeholder {
border: 2px dashed @control-bg; border: 2px dashed @control-bg;
border-radius: @border-radius; border-radius: @border-radius;
height: 29px; height: 34px;
max-width: max-content;
} }
.TagGroup { .SettingsGroups {
padding-left: 150px; display: flex;
column-count: 3;
column-gap: 30px;
flex-wrap: wrap;
&:first-child { @media (@tablet-up) {
border-bottom: 2px solid @control-bg; .TagGroup--secondary {
max-width: 250px !important;
}
} }
> label {
margin-left: -150px; .Form {
float: left; min-width: 300px;
font-weight: bold;
margin-top: 14px; >label {
margin-bottom: 10px;
}
.TagSettings-rangeInput {
input {
width: 80px;
display: inline;
margin: 0 5px;
&:first-child {
margin-left: 0;
}
}
}
}
.TagGroup,
.Form {
display: inline-grid;
padding: 10px 20px;
min-height: 20vh;
max-width: 400px;
grid-template-rows: min-content;
border: 1px solid @control-bg;
border-radius: @border-radius;
flex: 1 1 160px;
@media (max-width: 1209px) {
margin-bottom: 20px;
}
>ol {
>li {
margin-top: 8px;
.Button {
float: right;
visibility: hidden;
margin: -8px -16px -8px 16px;
}
}
}
.TagList-button {
background: none;
border: 1px dashed @control-bg;
height: 40px;
margin: auto auto 0 0;
}
>label {
float: left;
font-weight: bold;
color: @muted-color;
}
} }
} }

View File

@@ -50,11 +50,12 @@ class CreateTagHandler
); );
$parentId = Arr::get($data, 'relationships.parent.data.id'); $parentId = Arr::get($data, 'relationships.parent.data.id');
$primary = Arr::get($data, 'attributes.primary');
if ($parentId !== null) { if ($parentId !== null || $primary) {
$rootTags = Tag::whereNull('parent_id')->whereNotNull('position'); $rootTags = Tag::whereNull('parent_id')->whereNotNull('position');
if ($parentId === 0) { if ($parentId === 0 || $primary) {
$tag->position = $rootTags->max('position') + 1; $tag->position = $rootTags->max('position') + 1;
} elseif ($rootTags->find($parentId)) { } elseif ($rootTags->find($parentId)) {
$position = Tag::where('parent_id', $parentId)->max('position'); $position = Tag::where('parent_id', $parentId)->max('position');