diff --git a/client/html/tag_categories.tpl b/client/html/tag_categories.tpl index fe6b8987..f9d28e15 100644 --- a/client/html/tag_categories.tpl +++ b/client/html/tag_categories.tpl @@ -7,6 +7,7 @@ Category name CSS color + Order Usages @@ -21,7 +22,7 @@
- <% if (ctx.canCreate || ctx.canEditName || ctx.canEditColor || ctx.canDelete) { %> + <% if (ctx.canCreate || ctx.canEditName || ctx.canEditColor || ctx.canEditOrder || ctx.canDelete) { %>
diff --git a/client/html/tag_category_row.tpl b/client/html/tag_category_row.tpl index dcc8c16a..ce7aa59c 100644 --- a/client/html/tag_category_row.tpl +++ b/client/html/tag_category_row.tpl @@ -17,6 +17,13 @@ <%- ctx.tagCategory.color %> <% } %> + + <% if (ctx.canEditOrder) { %> + <%= ctx.makeNumericInput({value: ctx.tagCategory.order}) %> + <% } else { %> + <%- ctx.tagCategory.order %> + <% } %> + <% if (ctx.tagCategory.name) { %> '> diff --git a/client/js/controllers/tag_categories_controller.js b/client/js/controllers/tag_categories_controller.js index 2470edbb..1ca743fd 100644 --- a/client/js/controllers/tag_categories_controller.js +++ b/client/js/controllers/tag_categories_controller.js @@ -26,6 +26,7 @@ class TagCategoriesController { tagCategories: this._tagCategories, canEditName: api.hasPrivilege("tagCategories:edit:name"), canEditColor: api.hasPrivilege("tagCategories:edit:color"), + canEditOrder: api.hasPrivilege("tagCategories:edit:order"), canDelete: api.hasPrivilege("tagCategories:delete"), canCreate: api.hasPrivilege("tagCategories:create"), canSetDefault: api.hasPrivilege( diff --git a/client/js/models/tag_category.js b/client/js/models/tag_category.js index a8d0e64c..cc81f674 100644 --- a/client/js/models/tag_category.js +++ b/client/js/models/tag_category.js @@ -9,10 +9,12 @@ class TagCategory extends events.EventTarget { super(); this._name = ""; this._color = "#000000"; + this._order = 1; this._tagCount = 0; this._isDefault = false; this._origName = null; this._origColor = null; + this._origOrder = null; } get name() { @@ -23,6 +25,10 @@ class TagCategory extends events.EventTarget { return this._color; } + get order() { + return this._order; + } + get tagCount() { return this._tagCount; } @@ -43,6 +49,10 @@ class TagCategory extends events.EventTarget { this._color = value; } + set order(value) { + this._order = value; + } + static fromResponse(response) { const ret = new TagCategory(); ret._updateFromResponse(response); @@ -58,6 +68,9 @@ class TagCategory extends events.EventTarget { if (this.color !== this._origColor) { detail.color = this.color; } + if (this.order !== this._origOrder) { + detail.order = this.order; + } if (!Object.keys(detail).length) { return Promise.resolve(); @@ -65,9 +78,9 @@ class TagCategory extends events.EventTarget { let promise = this._origName ? api.put( - uri.formatApiLink("tag-category", this._origName), - detail - ) + uri.formatApiLink("tag-category", this._origName), + detail + ) : api.post(uri.formatApiLink("tag-categories"), detail); return promise.then((response) => { @@ -104,10 +117,12 @@ class TagCategory extends events.EventTarget { this._version = response.version; this._name = response.name; this._color = response.color; + this._order = response.order; this._isDefault = response.default; this._tagCount = response.usages; this._origName = this.name; this._origColor = this.color; + this._origOrder = this.order; } } diff --git a/client/js/views/tag_categories_view.js b/client/js/views/tag_categories_view.js index 1f1a4dac..7cd5c19e 100644 --- a/client/js/views/tag_categories_view.js +++ b/client/js/views/tag_categories_view.js @@ -100,6 +100,13 @@ class TagCategoriesView extends events.EventTarget { ); } + const orderInput = rowNode.querySelector(".order input"); + if (orderInput) { + orderInput.addEventListener("change", (e) => + this._evtOrderChange(e, rowNode) + ); + } + const removeLinkNode = rowNode.querySelector(".remove a"); if (removeLinkNode) { removeLinkNode.addEventListener("click", (e) => @@ -147,6 +154,10 @@ class TagCategoriesView extends events.EventTarget { rowNode._tagCategory.color = e.target.value; } + _evtOrderChange(e, rowNode) { + rowNode._tagCategory.order = e.target.value; + } + _evtDeleteButtonClick(e, rowNode, link) { e.preventDefault(); if (e.target.classList.contains("inactive")) { diff --git a/server/config.yaml.dist b/server/config.yaml.dist index e3799a35..bc4e3630 100644 --- a/server/config.yaml.dist +++ b/server/config.yaml.dist @@ -130,6 +130,7 @@ privileges: 'tag_categories:create': moderator 'tag_categories:edit:name': moderator 'tag_categories:edit:color': moderator + 'tag_categories:edit:order': moderator 'tag_categories:list': anonymous 'tag_categories:view': anonymous 'tag_categories:delete': moderator diff --git a/server/szurubooru/api/tag_category_api.py b/server/szurubooru/api/tag_category_api.py index 498289a4..432d2d03 100644 --- a/server/szurubooru/api/tag_category_api.py +++ b/server/szurubooru/api/tag_category_api.py @@ -73,6 +73,11 @@ def update_tag_category( tag_categories.update_category_color( category, ctx.get_param_as_string("color") ) + if ctx.has_param("order"): + auth.verify_privilege(ctx.user, "tag_categories:edit:order") + tag_categories.update_category_order( + category, ctx.get_param_as_int("order") + ) ctx.session.flush() snapshots.modify(category, ctx.user) ctx.session.commit() diff --git a/server/szurubooru/func/tag_categories.py b/server/szurubooru/func/tag_categories.py index bbf72978..27c5e48e 100644 --- a/server/szurubooru/func/tag_categories.py +++ b/server/szurubooru/func/tag_categories.py @@ -48,6 +48,7 @@ class TagCategorySerializer(serialization.BaseSerializer): "color": self.serialize_color, "usages": self.serialize_usages, "default": self.serialize_default, + "order": self.serialize_order, } def serialize_name(self) -> Any: @@ -65,6 +66,9 @@ class TagCategorySerializer(serialization.BaseSerializer): def serialize_default(self) -> Any: return self.category.default + def serialize_order(self) -> Any: + return self.category.order + def serialize_category( category: Optional[model.TagCategory], options: List[str] = [] @@ -117,6 +121,11 @@ def update_category_color(category: model.TagCategory, color: str) -> None: category.color = color +def update_category_order(category: model.TagCategory, order: int) -> None: + assert category + category.order = order + + def try_get_category_by_name( name: str, lock: bool = False ) -> Optional[model.TagCategory]: diff --git a/server/szurubooru/func/tags.py b/server/szurubooru/func/tags.py index e15a78cc..28a2a76b 100644 --- a/server/szurubooru/func/tags.py +++ b/server/szurubooru/func/tags.py @@ -67,6 +67,7 @@ def sort_tags(tags: List[model.Tag]) -> List[model.Tag]: return sorted( tags, key=lambda tag: ( + tag.category.order, default_category_name == tag.category.name, tag.category.name, tag.names[0].name, diff --git a/server/szurubooru/migrations/versions/c97dc1bf184a_add_order_column_to_tag_categories.py b/server/szurubooru/migrations/versions/c97dc1bf184a_add_order_column_to_tag_categories.py new file mode 100644 index 00000000..e878355b --- /dev/null +++ b/server/szurubooru/migrations/versions/c97dc1bf184a_add_order_column_to_tag_categories.py @@ -0,0 +1,30 @@ +""" +Add order column to tag categories. + +Revision ID: c97dc1bf184a +Created at: 2020-09-19 17:08:03.225667 +""" + +import sqlalchemy as sa +from alembic import op + +revision = "c97dc1bf184a" +down_revision = "54de8acc6cef" +branch_labels = None +depends_on = None + + +def upgrade(): + op.add_column( + "tag_category", sa.Column("order", sa.Integer, nullable=True) + ) + op.execute( + sa.table("tag_category", sa.column("order")) + .update() + .values(order=1) + ) + op.alter_column("tag_category", "order", nullable=False) + + +def downgrade(): + op.drop_column("tag_category", "order") diff --git a/server/szurubooru/model/tag_category.py b/server/szurubooru/model/tag_category.py index 1faf74ca..a336a214 100644 --- a/server/szurubooru/model/tag_category.py +++ b/server/szurubooru/model/tag_category.py @@ -16,6 +16,7 @@ class TagCategory(Base): "color", sa.Unicode(32), nullable=False, default="#000000" ) default = sa.Column("default", sa.Boolean, nullable=False, default=False) + order = sa.Column("order", sa.Integer, nullable=False, default=1) def __init__(self, name: Optional[str] = None) -> None: self.name = name diff --git a/server/szurubooru/tests/api/test_tag_category_updating.py b/server/szurubooru/tests/api/test_tag_category_updating.py index 41346131..f12ce367 100644 --- a/server/szurubooru/tests/api/test_tag_category_updating.py +++ b/server/szurubooru/tests/api/test_tag_category_updating.py @@ -17,6 +17,7 @@ def inject_config(config_injector): "privileges": { "tag_categories:edit:name": model.User.RANK_REGULAR, "tag_categories:edit:color": model.User.RANK_REGULAR, + "tag_categories:edit:order": model.User.RANK_REGULAR, "tag_categories:set_default": model.User.RANK_REGULAR, }, }