Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/MHSanaei/3x-ui.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/web
diff options
context:
space:
mode:
authorSerge Pavlyuk <flops@users.noreply.github.com>2024-02-27 14:48:29 +0300
committerGitHub <noreply@github.com>2024-02-27 14:48:29 +0300
commit8b641801366e7ada09a5530a432a7d14b33abe7d (patch)
treeb197a2bda7e8416999bde7bfee675c365fc76054 /web
parent688ae4da106b30e152de8251d9670498a0db84ed (diff)
Added drag'n'drop for routes (#1915)
* Added drag'n'drop for routes * Drop handler works only for local dnd events * Cleanup console.log
Diffstat (limited to 'web')
-rw-r--r--web/html/xui/component/sortableTable.html218
-rw-r--r--web/html/xui/xray.html17
2 files changed, 229 insertions, 6 deletions
diff --git a/web/html/xui/component/sortableTable.html b/web/html/xui/component/sortableTable.html
new file mode 100644
index 00000000..010d8011
--- /dev/null
+++ b/web/html/xui/component/sortableTable.html
@@ -0,0 +1,218 @@
+{{define "component/sortableTableTrigger"}}
+ <a-icon type="drag"
+ style="cursor: move;"
+ @mouseup="mouseUpHandler"
+ @mousedown="mouseDownHandler"
+ @click="clickHandler" />
+{{end}}
+
+{{define "component/sortableTable"}}
+<script>
+ const DRAGGABLE_ROW_CLASS = 'draggable-row';
+
+ const findParentRowElement = (el) => {
+ if (!el || !el.tagName) {
+ return null;
+ } else if (el.classList.contains(DRAGGABLE_ROW_CLASS)) {
+ return el;
+ } else if (el.parentNode) {
+ return findParentRowElement(el.parentNode);
+ } else {
+ return null;
+ }
+ }
+
+ Vue.component('a-table-sortable', {
+ data() {
+ return {
+ sortingElementIndex: null,
+ newElementIndex: null,
+ };
+ },
+ props: ['data-source', 'customRow'],
+ inheritAttrs: false,
+ provide() {
+ const sortable = {}
+
+ Object.defineProperty(sortable, "setSortableIndex", {
+ enumerable: true,
+ get: () => this.setCurrentSortableIndex,
+ });
+
+ Object.defineProperty(sortable, "resetSortableIndex", {
+ enumerable: true,
+ get: () => this.resetSortableIndex,
+ });
+
+ return {
+ sortable,
+ }
+ },
+ render: function (createElement) {
+ return createElement(
+ 'a-table',
+ {
+ class: {
+ 'ant-table-is-sorting': this.isDragging(),
+ },
+ props: {
+ ...this.$attrs,
+ 'data-source': this.records,
+ customRow: (record, index) => this.customRowRender(record, index),
+ },
+ on: this.$listeners,
+ nativeOn: {
+ drop: (e) => this.dropHandler(e),
+ },
+ scopedSlots: this.$scopedSlots,
+ },
+ this.$slots.default,
+ )
+ },
+ created() {
+ this.$memoSort = {};
+ },
+ methods: {
+ isDragging() {
+ const currentIndex = this.sortingElementIndex;
+ return currentIndex !== null && currentIndex !== undefined;
+ },
+ resetSortableIndex(e, index) {
+ this.sortingElementIndex = null;
+ this.newElementIndex = null;
+ this.$memoSort = {};
+ },
+ setCurrentSortableIndex(e, index) {
+ this.sortingElementIndex = index;
+ },
+ dragStartHandler(e, index) {
+ if (!this.isDragging()) {
+ e.preventDefault();
+ return;
+ }
+ },
+ dragStopHandler(e, index) {
+ this.resetSortableIndex(e, index);
+ },
+ dragOverHandler(e, index) {
+ if (!this.isDragging()) {
+ return;
+ }
+
+ e.preventDefault();
+
+ const currentIndex = this.sortingElementIndex;
+ if (index === currentIndex) {
+ this.newElementIndex = null;
+ return;
+ }
+
+ const row = findParentRowElement(e.target);
+ if (!row) {
+ return;
+ }
+
+ const rect = row.getBoundingClientRect();
+ const offsetTop = e.pageY - rect.top;
+
+ if (offsetTop < rect.height / 2) {
+ this.newElementIndex = Math.max(index - 1, 0);
+ } else {
+ this.newElementIndex = index;
+ }
+ },
+ dropHandler(e) {
+ if (this.isDragging()) {
+ this.$emit('onsort', this.sortingElementIndex, this.newElementIndex);
+ }
+ },
+ customRowRender(record, index) {
+ const parentMethodResult = this.customRow?.(record, index) || {};
+ const newIndex = this.newElementIndex;
+ const currentIndex = this.sortingElementIndex;
+
+ return {
+ ...parentMethodResult,
+ attrs: {
+ ...(parentMethodResult?.attrs || {}),
+ draggable: true,
+ },
+ on: {
+ ...(parentMethodResult?.on || {}),
+ dragstart: (e) => this.dragStartHandler(e, index),
+ dragend: (e) => this.dragStopHandler(e, index),
+ dragover: (e) => this.dragOverHandler(e, index),
+ },
+ class: {
+ ...(parentMethodResult?.class || {}),
+ [DRAGGABLE_ROW_CLASS]: true,
+ ['dragging']: this.isDragging()
+ ? (newIndex === null ? index === currentIndex : index === newIndex)
+ : false,
+ },
+ };
+ }
+ },
+ computed: {
+ records() {
+ const newIndex = this.newElementIndex;
+ const currentIndex = this.sortingElementIndex;
+
+ if (!this.isDragging() || newIndex === null || currentIndex === newIndex) {
+ return this.dataSource;
+ }
+
+ if (this.$memoSort.newIndex === newIndex) {
+ return this.$memoSort.list;
+ }
+
+ let list = [...this.dataSource];
+ list.splice(newIndex, 0, list.splice(currentIndex, 1)[0]);
+
+ this.$memoSort = {
+ newIndex,
+ list,
+ };
+
+ return list;
+ }
+ }
+ });
+
+ Vue.component('table-sort-trigger', {
+ template: `{{template "component/sortableTableTrigger"}}`,
+ props: ['item-index'],
+ inject: ['sortable'],
+ methods: {
+ mouseDownHandler(e) {
+ if (this.sortable) {
+ this.sortable.setSortableIndex(e, this.itemIndex);
+ }
+ },
+ mouseUpHandler(e) {
+ if (this.sortable) {
+ this.sortable.resetSortableIndex(e, this.itemIndex);
+ }
+ },
+ clickHandler(e) {
+ e.preventDefault();
+ },
+ }
+ })
+</script>
+
+<style>
+ .ant-table-is-sorting .draggable-row td {
+ background-color: white !important;
+ }
+ .dark .ant-table-is-sorting .draggable-row td {
+ background-color: var(--dark-color-surface-100) !important;
+ }
+ .ant-table-is-sorting .dragging {
+ opacity: 0.5;
+ }
+ .ant-table-is-sorting .dragging .ant-table-row-index {
+ opacity: 0;
+ }
+</style>
+{{end}} \ No newline at end of file
diff --git a/web/html/xui/xray.html b/web/html/xui/xray.html
index 5ede3633..5afa77c8 100644
--- a/web/html/xui/xray.html
+++ b/web/html/xui/xray.html
@@ -290,15 +290,19 @@
<a-alert type="warning" style="margin-bottom: 10px; width: fit-content"
message='{{ i18n "pages.xray.RoutingsDesc"}}' show-icon></a-alert>
<a-button type="primary" icon="plus" @click="addRule">{{ i18n "pages.xray.rules.add" }}</a-button>
- <a-table :columns="isMobile ? rulesMobileColumns : rulesColumns" bordered
+ <a-table-sortable :columns="isMobile ? rulesMobileColumns : rulesColumns" bordered
:row-key="r => r.key"
:data-source="routingRuleData"
:scroll="isMobile ? {} : { x: 1000 }"
:pagination="false"
:indent-size="0"
- :style="isMobile ? 'padding: 5px 0' : 'margin-top: 10px;'">
+ :style="isMobile ? 'padding: 5px 0' : 'margin-top: 10px;'"
+ v-on:onSort="replaceRule">
<template slot="action" slot-scope="text, rule, index">
- [[ index+1 ]]
+ <table-sort-trigger :item-index="index"></table-sort-trigger>
+ <span class="ant-table-row-index">
+ [[ index+1 ]]
+ </span>
<a-dropdown :trigger="['click']">
<a-icon @click="e => e.preventDefault()" type="more" style="font-size: 16px; text-decoration: bold;"></a-icon>
<a-menu slot="overlay" :theme="themeSwitcher.currentTheme">
@@ -404,7 +408,7 @@
</a-button>
</a-popover>
</template>
- </a-table>
+ </a-table-sortable>
</a-tab-pane>
<a-tab-pane key="tpl-3" tab='{{ i18n "pages.xray.Outbounds"}}' style="padding-top: 20px;" force-render="true">
<a-row>
@@ -530,7 +534,7 @@
<template slot="selector" slot-scope="text, balancer, index">
<a-tag class="info-large-tag" style="margin:1;" v-for="sel in balancer.selector">[[ sel ]]</a-tag>
</template>
- </a-table>
+ </a-table>
</a-tab-pane>
<a-tab-pane key="tpl-6" tab='DNS' style="padding-top: 20px;" force-render="true">
<setting-list-item type="switch" title='{{ i18n "pages.xray.dns.enable" }}' desc='{{ i18n "pages.xray.dns.enableDesc" }}' v-model="enableDNS"></setting-list-item>
@@ -630,6 +634,7 @@
</a-layout>
{{template "js" .}}
{{template "component/themeSwitcher" .}}
+{{template "component/sortableTable" .}}
{{template "component/setting"}}
{{template "ruleModal"}}
{{template "outModal"}}
@@ -1269,7 +1274,7 @@
newRules = newTemplateSettings.routing.rules.filter(r => r.outboundTag != oldData.tag);
}
newTemplateSettings.routing.rules = newRules;
-
+
this.templateSettings = newTemplateSettings;
},
addDNSServer(){