From f9bef486589e13e038999db98d446ce692e1a0bf Mon Sep 17 00:00:00 2001 From: Marshu Date: Thu, 5 Jun 2025 00:24:17 +0800 Subject: [PATCH] Extract PublicGroupListSortDropdown into standalone component --- src/components/GroupSortDropdown.vue | 445 ++++++++++++++++++++++++++ src/components/PublicGroupList.vue | 461 +-------------------------- 2 files changed, 454 insertions(+), 452 deletions(-) create mode 100644 src/components/GroupSortDropdown.vue diff --git a/src/components/GroupSortDropdown.vue b/src/components/GroupSortDropdown.vue new file mode 100644 index 000000000..4ebd662dc --- /dev/null +++ b/src/components/GroupSortDropdown.vue @@ -0,0 +1,445 @@ + + + + + \ No newline at end of file diff --git a/src/components/PublicGroupList.vue b/src/components/PublicGroupList.vue index 95f36b043..9e5982810 100644 --- a/src/components/PublicGroupList.vue +++ b/src/components/PublicGroupList.vue @@ -16,94 +16,11 @@ -
- -
+
@@ -181,6 +98,7 @@ import Draggable from "vuedraggable"; import HeartbeatBar from "./HeartbeatBar.vue"; import Uptime from "./Uptime.vue"; import Tag from "./Tag.vue"; +import GroupSortDropdown from "./GroupSortDropdown.vue"; export default { components: { @@ -189,6 +107,7 @@ export default { HeartbeatBar, Uptime, Tag, + GroupSortDropdown, }, props: { /** Are we in edit mode? */ @@ -212,244 +131,15 @@ export default { computed: { showGroupDrag() { return (this.$root.publicGroupList.length >= 2); - }, - - /** - * Parse sort settings from URL query parameters - * @returns {object} Parsed sort settings for all groups - */ - sortSettingsFromURL() { - const sortSettings = {}; - if (this.$route && this.$route.query) { - for (const [key, value] of Object.entries(this.$route.query)) { - if (key.startsWith('sort_') && typeof value === 'string') { - const groupId = key.replace('sort_', ''); - const [sortKey, direction] = value.split('_'); - if (sortKey && ['status', 'name', 'uptime', 'cert'].includes(sortKey) && - direction && ['asc', 'desc'].includes(direction)) { - sortSettings[groupId] = { sortKey, direction }; - } - } - } - } - return sortSettings; } }, watch: { - // Watch for changes in heartbeat list, reapply sorting - "$root.heartbeatList": { - handler() { - if (this.$root && this.$root.publicGroupList) { - this.$root.publicGroupList.forEach(group => { - if (group) { - this.applySort(group); - } - }); - } - }, - deep: true, - }, - - // Watch for changes in uptime list, reapply sorting - "$root.uptimeList": { - handler() { - if (this.$root && this.$root.publicGroupList) { - this.$root.publicGroupList.forEach(group => { - if (group) { - this.applySort(group); - } - }); - } - }, - deep: true, - }, - - // Watch for URL changes and apply sort settings - sortSettingsFromURL: { - handler(newSortSettings) { - if (this.$root && this.$root.publicGroupList) { - this.$root.publicGroupList.forEach(group => { - if (!group) return; - - const groupId = this.getGroupIdentifier(group); - const urlSetting = newSortSettings[groupId]; - - if (urlSetting) { - group.sortKey = urlSetting.sortKey; - group.sortDirection = urlSetting.direction; - } else { - // Set defaults if not in URL - if (group.sortKey === undefined) { - group.sortKey = "status"; - } - if (group.sortDirection === undefined) { - group.sortDirection = "asc"; - } - } - - this.applySort(group); - }); - } - }, - immediate: true, - deep: true - } + // No watchers needed - sorting is handled by GroupSortDropdown component }, created() { - // Initialize sort settings - this.initializeSortSettings(); + // Sorting is now handled by GroupSortDropdown component }, methods: { - /** - * Initialize group sort settings - * @returns {void} - */ - initializeSortSettings() { - // Watch for new groups being added and initialize their sort state - if (this.$root) { - this.$root.$watch("publicGroupList", (newGroups) => { - if (newGroups) { - newGroups.forEach(group => { - if (group && group.sortKey === undefined) { - group.sortKey = "status"; - group.sortDirection = "asc"; - this.applySort(group); - } - }); - } - }, { deep: true }); - } - }, - - /** - * Get sort key for a group - * @param {object} group object - * @returns {string} sort key - */ - getSortKey(group) { - return group.sortKey || "status"; - }, - - /** - * Set group sort key and direction, then apply sorting - * @param {object} group object - * @param {string} key - sort key ('status', 'name', 'uptime', 'cert') - * @returns {void} - */ - setSort(group, key) { - if (group.sortKey === key) { - group.sortDirection = group.sortDirection === "asc" ? "desc" : "asc"; - } else { - group.sortKey = key; - group.sortDirection = "asc"; - } - - this.applySort(group); - this.updateRouterQuery(group); - }, - - /** - * Update router query parameters with sort settings - * @param {object} group object - * @returns {void} - */ - updateRouterQuery(group) { - if (!this.$router) return; - - const query = { ...this.$route.query }; - const groupId = this.getGroupIdentifier(group); - - if (group.sortKey && group.sortDirection) { - query[`sort_${groupId}`] = `${group.sortKey}_${group.sortDirection}`; - } else { - delete query[`sort_${groupId}`]; - } - - this.$router.push({ query }).catch(() => {}); - }, - - /** - * Apply sorting logic directly to the group's monitorList (in-place) - * @param {object} group object containing monitorList - * @returns {void} - */ - applySort(group) { - if (!group || !group.monitorList || !Array.isArray(group.monitorList)) { - return; - } - - const sortKey = group.sortKey || "status"; - const sortDirection = group.sortDirection || "desc"; - - group.monitorList.sort((a, b) => { - if (!a || !b) { - return 0; - } - - let comparison = 0; - let valueA; - let valueB; - - if (sortKey === "status") { - // Sort by status - const getStatusPriority = (monitor) => { - if (!monitor || !monitor.id) { - return 4; - } - - const hbList = this.$root.heartbeatList || {}; - const hbArr = hbList[monitor.id]; - if (hbArr && hbArr.length > 0) { - const lastStatus = hbArr.at(-1).status; - if (lastStatus === 0) { - return 0; - } // Down - if (lastStatus === 1) { - return 1; - } // Up - if (lastStatus === 2) { - return 2; - } // Pending - if (lastStatus === 3) { - return 3; - } // Maintenance - } - return 4; // Unknown/No data - }; - valueA = getStatusPriority(a); - valueB = getStatusPriority(b); - } else if (sortKey === "name") { - // Sort alphabetically by name - valueA = a.name ? a.name.toLowerCase() : ""; - valueB = b.name ? b.name.toLowerCase() : ""; - } else if (sortKey === "uptime") { - // Sort by uptime - const uptimeList = this.$root.uptimeList || {}; - const uptimeA = a.id ? parseFloat(uptimeList[`${a.id}_24`]) || 0 : 0; - const uptimeB = b.id ? parseFloat(uptimeList[`${b.id}_24`]) || 0 : 0; - valueA = uptimeA; - valueB = uptimeB; - } else if (sortKey === "cert") { - // Sort by certificate expiry time - valueA = a.validCert && a.certExpiryDaysRemaining ? a.certExpiryDaysRemaining : -1; - valueB = b.validCert && b.certExpiryDaysRemaining ? b.certExpiryDaysRemaining : -1; - } - - if (valueA < valueB) { - comparison = -1; - } else if (valueA > valueB) { - comparison = 1; - } - - // Special handling for status sorting - if (sortKey === "status") { - return sortDirection === "desc" ? (comparison * -1) : comparison; - } else { - return sortDirection === "asc" ? comparison : (comparison * -1); - } - }); - }, - /** * Remove the specified group * @param {number} index Index of group to remove @@ -607,133 +297,6 @@ export default { } } -.sort-dropdown { - margin-left: auto; -} - -.sort-button { - display: flex; - align-items: center; - justify-content: center; - padding: 0.3rem 0.6rem; - min-width: 40px; - border-radius: 10px; - background-color: white; - border: none; - box-shadow: 0 15px 70px rgba(0, 0, 0, 0.1); - transition: all ease-in-out 0.15s; - - &:hover { - background-color: #f8f9fa; - } - - &:focus, &:active { - box-shadow: 0 15px 70px rgba(0, 0, 0, 0.1); - border: none; - outline: none; - } - - .dark & { - background-color: $dark-bg; - color: $dark-font-color; - box-shadow: 0 15px 70px rgba(0, 0, 0, 0.3); - - &:hover { - background-color: $dark-bg2; - } - - &:focus, &:active { - box-shadow: 0 15px 70px rgba(0, 0, 0, 0.3); - } - } -} - -.sort-arrows { - display: flex; - flex-direction: row; - align-items: center; - justify-content: center; - gap: 6px; - padding: 0 2px; -} - -.arrow-inactive { - color: #aaa; - font-size: 0.7rem; - opacity: 0.5; - - .dark & { - color: #6c757d; - } -} - -.arrow-active { - color: #4caf50; - font-size: 0.8rem; - - .dark & { - color: $primary; - } -} - -.sort-menu { - min-width: auto; - width: auto; - padding: 0.2rem 0; - border-radius: 10px; - border: none; - box-shadow: 0 15px 70px rgba(0, 0, 0, 0.1); - overflow: hidden; - - .dark & { - background-color: $dark-bg; - color: $dark-font-color; - border-color: $dark-border-color; - box-shadow: 0 15px 70px rgba(0, 0, 0, 0.3); - } -} - -.sort-item { - padding: 0.4rem 0.8rem; - text-align: left; - width: 100%; - background: none; - border: none; - cursor: pointer; - - &:hover { - background-color: #f8f9fa; - } - - .dark & { - color: $dark-font-color; - - &:hover { - background-color: $dark-bg2; - } - } -} - -.sort-item-content { - display: flex; - justify-content: space-between; - align-items: center; - width: 100%; - min-width: 120px; -} - -.sort-indicators { - display: flex; - align-items: center; - margin-left: 10px; -} - -.sort-direction-indicator { - font-weight: bold; - display: inline-block; - margin-left: 2px; -} - .mobile { .item { padding: 13px 0 10px; @@ -742,12 +305,6 @@ export default { .group-title { flex-direction: column; align-items: flex-start; - - .sort-dropdown { - margin-left: 0; - margin-top: 0.5rem; - width: 100%; - } } }