diff --git a/app/renderer/css/main.css b/app/renderer/css/main.css index 0898191f5..6b9b5aa50 100644 --- a/app/renderer/css/main.css +++ b/app/renderer/css/main.css @@ -419,6 +419,10 @@ webview.focus { left: -5px; } +.sortable-chosen .server-tooltip { + display: none; +} + #collapse-button { bottom: 30px; left: 20px; diff --git a/app/renderer/js/main.ts b/app/renderer/js/main.ts index 7b4abce90..ff0cdffe2 100644 --- a/app/renderer/js/main.ts +++ b/app/renderer/js/main.ts @@ -6,6 +6,7 @@ import url from "node:url"; import {Menu, app, dialog, session} from "@electron/remote"; import * as remote from "@electron/remote"; import * as Sentry from "@sentry/electron/renderer"; +import SortableJS from "sortablejs"; import type {Config} from "../../common/config-util.js"; import * as ConfigUtil from "../../common/config-util.js"; @@ -57,7 +58,7 @@ const dingSound = new Audio( export class ServerManagerView { $addServerButton: HTMLButtonElement; - $tabsContainer: Element; + $tabsContainer: HTMLElement; $reloadButton: HTMLButtonElement; $loadingIndicator: HTMLButtonElement; $settingsButton: HTMLButtonElement; @@ -81,6 +82,7 @@ export class ServerManagerView { tabIndex: number; presetOrgs: string[]; preferenceView?: PreferenceView; + sortableSidebar: SortableJS | null; constructor() { this.$addServerButton = document.querySelector("#add-tab")!; this.$tabsContainer = document.querySelector("#tabs-container")!; @@ -123,6 +125,7 @@ export class ServerManagerView { this.presetOrgs = []; this.functionalTabs = new Map(); this.tabIndex = 0; + this.sortableSidebar = null; } async init(): Promise { @@ -235,6 +238,20 @@ export class ServerManagerView { initSidebar(): void { const showSidebar = ConfigUtil.getConfigItem("showSidebar", true); this.toggleSidebar(showSidebar); + this.sortableSidebar = new SortableJS(this.$tabsContainer, { + animation: 150, + onEnd: (event: SortableJS.SortableEvent) => { + // Update the domain order in the database + DomainUtil.updateDomainOrder(event.oldIndex ?? 0, event.newIndex ?? 0); + + // Update the current active tab index + this.activeTabIndex = event.newIndex ?? 0; + ConfigUtil.setConfigItem("lastActiveTab", event.newIndex ?? 0); + + // Reload the app to give the tabs their new indexes + ipcRenderer.send("reload-full-app"); + }, + }); } // Remove the stale UA string from the disk if the app is not freshly diff --git a/app/renderer/js/utils/domain-util.ts b/app/renderer/js/utils/domain-util.ts index 465bb3b55..bdded99a4 100644 --- a/app/renderer/js/utils/domain-util.ts +++ b/app/renderer/js/utils/domain-util.ts @@ -72,6 +72,20 @@ export function updateDomain(index: number, server: ServerConf): void { db.push(`/domains[${index}]`, server, true); } +export function updateDomainOrder(oldIndex: number, newIndex: number) { + const domains = serverConfSchema + .array() + .parse(db.getObject("/domains")); + + const [movedDomain] = domains.splice(oldIndex, 1); + domains.splice(newIndex, 0, movedDomain); + + // Update each domain in the database with its new order + for (const [index, domain] of domains.entries()) { + updateDomain(index, domain); + } +} + export async function addDomain(server: { url: string; alias: string; diff --git a/package-lock.json b/package-lock.json index 80cd10cd0..fc714eb5a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,9 @@ "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "gatemaker": "https://github.com/andersk/gatemaker/archive/d31890ae1cb293faabcb1e4e465c673458f6eed2.tar.gz" + "@types/sortablejs": "^1.15.8", + "gatemaker": "https://github.com/andersk/gatemaker/archive/d31890ae1cb293faabcb1e4e465c673458f6eed2.tar.gz", + "sortablejs": "^1.15.2" }, "devDependencies": { "@electron/remote": "^2.0.8", @@ -2207,6 +2209,11 @@ "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==", "dev": true }, + "node_modules/@types/sortablejs": { + "version": "1.15.8", + "resolved": "https://registry.npmjs.org/@types/sortablejs/-/sortablejs-1.15.8.tgz", + "integrity": "sha512-b79830lW+RZfwaztgs1aVPgbasJ8e7AXtZYHTELNXZPsERt4ymJdjV4OccDbHQAvHrCcFpbF78jkm0R6h/pZVg==" + }, "node_modules/@types/verror": { "version": "1.10.9", "resolved": "https://registry.npmjs.org/@types/verror/-/verror-1.10.9.tgz", @@ -9508,6 +9515,11 @@ "npm": ">= 3.0.0" } }, + "node_modules/sortablejs": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.2.tgz", + "integrity": "sha512-FJF5jgdfvoKn1MAKSdGs33bIqLi3LmsgVTliuX6iITj834F+JRQZN90Z93yql8h0K2t0RwDPBmxwlbZfDcxNZA==" + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", diff --git a/package.json b/package.json index 6076f3a4c..214150d65 100644 --- a/package.json +++ b/package.json @@ -143,7 +143,9 @@ "InstantMessaging" ], "dependencies": { - "gatemaker": "https://github.com/andersk/gatemaker/archive/d31890ae1cb293faabcb1e4e465c673458f6eed2.tar.gz" + "@types/sortablejs": "^1.15.8", + "gatemaker": "https://github.com/andersk/gatemaker/archive/d31890ae1cb293faabcb1e4e465c673458f6eed2.tar.gz", + "sortablejs": "^1.15.2" }, "devDependencies": { "@electron/remote": "^2.0.8",