Skip to content

Commit ac132c1

Browse files
authored
Merge pull request #6243 from WoltLab/6.2-sort-order-api
Generic implementation for the editing of show orders via drag and drop
2 parents 55d847b + 95028e5 commit ac132c1

File tree

15 files changed

+509
-0
lines changed

15 files changed

+509
-0
lines changed
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/**
2+
* Sends a get request to the given endpoint.
3+
*
4+
* @author Marcel Werk
5+
* @copyright 2001-2025 WoltLab GmbH
6+
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
7+
* @since 6.2
8+
* @woltlabExcludeBundle tiny
9+
*/
10+
11+
import { prepareRequest } from "WoltLabSuite/Core/Ajax/Backend";
12+
import { ApiResult, apiResultFromError, apiResultFromValue } from "./Result";
13+
14+
export async function getObject<T = unknown>(endpoint: string): Promise<ApiResult<T>> {
15+
let response: T;
16+
17+
try {
18+
response = (await prepareRequest(endpoint).get().fetchAsJson()) as T;
19+
} catch (e) {
20+
return apiResultFromError(e);
21+
}
22+
23+
return apiResultFromValue(response);
24+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/**
2+
* Handles the change of the show order of elements.
3+
*
4+
* @author Marcel Werk
5+
* @copyright 2001-2025 WoltLab GmbH
6+
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
7+
* @since 6.2
8+
*/
9+
10+
import { getObject } from "../Api/GetObject";
11+
import { postObject } from "../Api/PostObject";
12+
import { promiseMutex } from "../Helper/PromiseMutex";
13+
import { getPhrase } from "../Language";
14+
import { dialogFactory } from "./Dialog";
15+
import Sortable from "sortablejs";
16+
import { showDefaultSuccessSnackbar } from "./Snackbar";
17+
18+
type Item = {
19+
id: number;
20+
label: string;
21+
};
22+
23+
async function showDialog(endpoint: string): Promise<void> {
24+
const items = await getItems(endpoint);
25+
26+
const dialog = dialogFactory().fromHtml(getHtml(items)).asPrompt();
27+
dialog.show(getPhrase("wcf.global.changeShowOrder"));
28+
29+
const sortable = new Sortable(dialog.content.querySelector(".sortableList")!, {
30+
direction: "vertical",
31+
animation: 150,
32+
fallbackOnBody: true,
33+
dataIdAttr: "data-object-id",
34+
draggable: "li",
35+
handle: ".sortableList__handle",
36+
});
37+
38+
dialog.addEventListener("primary", () => {
39+
void saveItems(endpoint, sortable.toArray().map(Number)).then(() => {
40+
showDefaultSuccessSnackbar().addEventListener("snackbar:close", () => {
41+
window.location.reload();
42+
});
43+
});
44+
});
45+
}
46+
47+
async function getItems(endpoint: string): Promise<Item[]> {
48+
return (await getObject<Item[]>(`${window.WSC_RPC_API_URL}${endpoint}`)).unwrap();
49+
}
50+
51+
async function saveItems(endpoint: string, values: number[]): Promise<void> {
52+
await postObject(`${window.WSC_RPC_API_URL}${endpoint}`, { values });
53+
}
54+
55+
function getHtml(items: Item[]): string {
56+
const list = document.createElement("ol");
57+
list.classList.add("sortableList");
58+
59+
items.forEach((item) => {
60+
const listItem = document.createElement("li");
61+
listItem.dataset.objectId = item.id.toString();
62+
listItem.textContent = item.label;
63+
64+
const icon = document.createElement("fa-icon");
65+
icon.setIcon("up-down");
66+
const handle = document.createElement("span");
67+
handle.append(icon);
68+
handle.classList.add("sortableList__handle");
69+
listItem.prepend(handle);
70+
71+
list.append(listItem);
72+
});
73+
74+
return list.outerHTML;
75+
}
76+
77+
export function setup(button: HTMLElement, endpoint: string): void {
78+
button.addEventListener(
79+
"click",
80+
promiseMutex(() => showDialog(endpoint)),
81+
);
82+
}

wcfsetup/install/files/acp/templates/labelGroupAdd.tpl

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
<nav class="contentHeaderNavigation">
2424
<ul>
2525
{if $action == 'edit'}
26+
<li><button type="button" class="button jsChangeShowOrder">{icon name='up-down'} <span>{lang}wcf.global.changeShowOrder{/lang}</span></a></li>
2627
<li><a href="{link controller='LabelList' id=$groupID}{/link}" class="button">{icon name='list'} <span>{lang}wcf.acp.label.list{/lang}</span></a></li>
2728
{/if}
2829
<li><a href="{link controller='LabelGroupList'}{/link}" class="button">{icon name='list'} <span>{lang}wcf.acp.label.group.list{/lang}</span></a></li>
@@ -123,4 +124,15 @@
123124
</div>
124125
</form>
125126

127+
<script data-relocate="true">
128+
require(["WoltLabSuite/Core/Component/ChangeShowOrder"], ({ setup }) => {
129+
{jsphrase name='wcf.global.changeShowOrder'}
130+
131+
setup(
132+
document.querySelector('.jsChangeShowOrder'),
133+
'core/labels/groups/{$labelGroup->groupID}/labels/show-order'
134+
);
135+
});
136+
</script>
137+
126138
{include file='footer'}

wcfsetup/install/files/acp/templates/labelGroupList.tpl

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77

88
<nav class="contentHeaderNavigation">
99
<ul>
10+
{if $gridView->countRows() > 1}
11+
<li><button type="button" class="button jsChangeShowOrder">{icon name='up-down'} <span>{lang}wcf.global.changeShowOrder{/lang}</span></a></li>
12+
{/if}
1013
<li><a href="{link controller='LabelGroupAdd'}{/link}" class="button">{icon name='plus'} <span>{lang}wcf.acp.label.group.add{/lang}</span></a></li>
1114

1215
{event name='contentHeaderNavigation'}
@@ -18,4 +21,17 @@
1821
{unsafe:$gridView->render()}
1922
</div>
2023

24+
{if $gridView->countRows() > 1}
25+
<script data-relocate="true">
26+
require(["WoltLabSuite/Core/Component/ChangeShowOrder"], ({ setup }) => {
27+
{jsphrase name='wcf.global.changeShowOrder'}
28+
29+
setup(
30+
document.querySelector('.jsChangeShowOrder'),
31+
'core/labels/groups/show-order'
32+
);
33+
});
34+
</script>
35+
{/if}
36+
2137
{include file='footer'}

wcfsetup/install/files/js/WoltLabSuite/Core/Api/GetObject.js

Lines changed: 24 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

wcfsetup/install/files/js/WoltLabSuite/Core/Component/ChangeShowOrder.js

Lines changed: 60 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

wcfsetup/install/files/lib/bootstrap/com.woltlab.wcf.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,11 @@ static function (\wcf\event\endpoint\ControllerCollecting $event) {
188188
$event->register(new \wcf\system\endpoint\controller\core\languages\EnableLanguage());
189189
$event->register(new \wcf\system\endpoint\controller\core\languages\SetAsDefaultLanguage());
190190
$event->register(new \wcf\system\endpoint\controller\core\languages\items\DeleteItem());
191+
$event->register(new \wcf\system\endpoint\controller\core\labels\groups\ChangeLabelShowOrder());
192+
$event->register(new \wcf\system\endpoint\controller\core\labels\groups\ChangeShowOrder());
191193
$event->register(new \wcf\system\endpoint\controller\core\labels\groups\DeleteGroup());
194+
$event->register(new \wcf\system\endpoint\controller\core\labels\groups\GetLabelShowOrder());
195+
$event->register(new \wcf\system\endpoint\controller\core\labels\groups\GetShowOrder());
192196
$event->register(new \wcf\system\endpoint\controller\core\pages\DeletePage());
193197
$event->register(new \wcf\system\endpoint\controller\core\pages\DisablePage());
194198
$event->register(new \wcf\system\endpoint\controller\core\pages\EnablePage());
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<?php
2+
3+
namespace wcf\system\endpoint\controller\core\labels\groups;
4+
5+
use Laminas\Diactoros\Response\JsonResponse;
6+
use Psr\Http\Message\ResponseInterface;
7+
use Psr\Http\Message\ServerRequestInterface;
8+
use wcf\data\label\group\LabelGroup;
9+
use wcf\data\label\Label;
10+
use wcf\data\label\LabelEditor;
11+
use wcf\http\Helper;
12+
use wcf\system\endpoint\IController;
13+
use wcf\system\endpoint\PostRequest;
14+
use wcf\system\label\LabelHandler;
15+
use wcf\system\showOrder\ShowOrderHandler;
16+
use wcf\system\showOrder\ShowOrderItem;
17+
use wcf\system\WCF;
18+
19+
/**
20+
* API endpoint for changing the show order of the labels in a label group.
21+
*
22+
* @author Marcel Werk
23+
* @copyright 2001-2025 WoltLab GmbH
24+
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
25+
* @since 6.2
26+
*/
27+
#[PostRequest('/core/labels/groups/{id:\d+}/labels/show-order')]
28+
final class ChangeLabelShowOrder implements IController
29+
{
30+
public function __invoke(ServerRequestInterface $request, array $variables): ResponseInterface
31+
{
32+
WCF::getSession()->checkPermissions(['admin.content.label.canManageLabel']);
33+
34+
$labelGroup = Helper::fetchObjectFromRequestParameter($variables['id'], LabelGroup::class);
35+
$items = \array_map(
36+
static fn(Label $label) => new ShowOrderItem($label->labelID, $label->getTitle()),
37+
LabelHandler::getInstance()->getLabelGroup($labelGroup->groupID)->getLabels()
38+
);
39+
40+
$sortedItems = (new ShowOrderHandler($items))->getSortedItemsFromRequest($request);
41+
$this->saveShowOrder($sortedItems);
42+
43+
return new JsonResponse([]);
44+
}
45+
46+
/**
47+
* @param list<ShowOrderItem> $items
48+
*/
49+
private function saveShowOrder(array $items): void
50+
{
51+
$sql = "UPDATE wcf1_label
52+
SET showOrder = ?
53+
WHERE labelID = ?";
54+
$statement = WCF::getDB()->prepare($sql);
55+
56+
WCF::getDB()->beginTransaction();
57+
for ($i = 0, $length = \count($items); $i < $length; $i++) {
58+
$statement->execute([
59+
$i + 1,
60+
$items[$i]->id,
61+
]);
62+
}
63+
WCF::getDB()->commitTransaction();
64+
65+
LabelEditor::resetCache();
66+
}
67+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<?php
2+
3+
namespace wcf\system\endpoint\controller\core\labels\groups;
4+
5+
use Laminas\Diactoros\Response\JsonResponse;
6+
use Psr\Http\Message\ResponseInterface;
7+
use Psr\Http\Message\ServerRequestInterface;
8+
use wcf\data\label\group\ViewableLabelGroup;
9+
use wcf\system\endpoint\IController;
10+
use wcf\system\endpoint\PostRequest;
11+
use wcf\system\label\LabelHandler;
12+
use wcf\system\showOrder\ShowOrderHandler;
13+
use wcf\system\showOrder\ShowOrderItem;
14+
use wcf\system\WCF;
15+
16+
/**
17+
* API endpoint for changing the show order of label groups.
18+
*
19+
* @author Marcel Werk
20+
* @copyright 2001-2025 WoltLab GmbH
21+
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
22+
* @since 6.2
23+
*/
24+
#[PostRequest('/core/labels/groups/show-order')]
25+
final class ChangeShowOrder implements IController
26+
{
27+
public function __invoke(ServerRequestInterface $request, array $variables): ResponseInterface
28+
{
29+
WCF::getSession()->checkPermissions(['admin.content.label.canManageLabel']);
30+
31+
$items = \array_map(
32+
static fn(ViewableLabelGroup $labelGroup) => new ShowOrderItem($labelGroup->groupID, $labelGroup->getTitle()),
33+
LabelHandler::getInstance()->getLabelGroups()
34+
);
35+
36+
$sortedItems = (new ShowOrderHandler($items))->getSortedItemsFromRequest($request);
37+
$this->saveShowOrder($sortedItems);
38+
39+
return new JsonResponse([]);
40+
}
41+
42+
/**
43+
* @param list<ShowOrderItem> $items
44+
*/
45+
private function saveShowOrder(array $items): void
46+
{
47+
WCF::getDB()->beginTransaction();
48+
$sql = "UPDATE wcf1_label_group
49+
SET showOrder = ?
50+
WHERE groupID = ?";
51+
$statement = WCF::getDB()->prepare($sql);
52+
for ($i = 0, $length = \count($items); $i < $length; $i++) {
53+
$statement->execute([
54+
$i + 1,
55+
$items[$i]->id,
56+
]);
57+
}
58+
WCF::getDB()->commitTransaction();
59+
}
60+
}

0 commit comments

Comments
 (0)