Compare commits

..

No commits in common. "9a6474b9474a0ea5ef502fd026d2a8cd36857b43" and "979e770057d761fea13be3e47655274fb6694947" have entirely different histories.

15 changed files with 192 additions and 1200 deletions

View File

@ -6,7 +6,7 @@ insert_final_newline = true
indent_style = space indent_style = space
charset = utf-8 charset = utf-8
[*.{js,ts,json}] [*.{js,json}]
indent_size = 4 indent_size = 4
trim_trailing_whitespace = true trim_trailing_whitespace = true

1
.gitignore vendored
View File

@ -1,3 +1,2 @@
/node_modules/ /node_modules/
/dist/
top-bar-organizer@julian.gse.jsts.xyz.shell-extension.zip top-bar-organizer@julian.gse.jsts.xyz.shell-extension.zip

4
ambient.d.ts vendored
View File

@ -1,4 +0,0 @@
import "@girs/gjs"
import "@girs/gjs/dom"
import "@girs/gnome-shell/ambient"
import "@girs/gnome-shell/extensions/global"

996
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -2,7 +2,6 @@
"name": "top-bar-organizer", "name": "top-bar-organizer",
"version": "1.0.0", "version": "1.0.0",
"description": "A Gnome Shell Extension for organizing your Gnome Shell top bar.", "description": "A Gnome Shell Extension for organizing your Gnome Shell top bar.",
"type": "module",
"directories": { "directories": {
"doc": "docs" "doc": "docs"
}, },
@ -13,12 +12,6 @@
"author": "June", "author": "June",
"license": "GPL-3.0-or-later", "license": "GPL-3.0-or-later",
"devDependencies": { "devDependencies": {
"eslint": "^8.57.1", "eslint": "^8.50.0"
"eslint-plugin-jsdoc": "^50.7.1",
"typescript": "^5.8.3"
},
"dependencies": {
"@girs/gjs": "^4.0.0-beta.23",
"@girs/gnome-shell": "^48.0.2"
} }
} }

View File

@ -4,11 +4,7 @@ set -e
REAL_BASE_DIR=$( dirname $( readlink -f "$0" )) REAL_BASE_DIR=$( dirname $( readlink -f "$0" ))
rm -rf "$REAL_BASE_DIR/dist" gnome-extensions pack "$REAL_BASE_DIR/src" \
cd "$REAL_BASE_DIR"
npx tsc
cp "$REAL_BASE_DIR/src/metadata.json" "$REAL_BASE_DIR/dist/metadata.json"
gnome-extensions pack "$REAL_BASE_DIR/dist" \
--force \ --force \
--extra-source extensionModules \ --extra-source extensionModules \
--extra-source prefsModules \ --extra-source prefsModules \

View File

@ -1,27 +1,13 @@
"use strict"; "use strict";
import St from "gi://St"
import type Gio from "gi://Gio"
import * as Main from "resource:///org/gnome/shell/ui/main.js"; import * as Main from "resource:///org/gnome/shell/ui/main.js";
import * as Panel from "resource:///org/gnome/shell/ui/panel.js"; import * as Panel from "resource:///org/gnome/shell/ui/panel.js";
import { Extension } from "resource:///org/gnome/shell/extensions/extension.js"; import { Extension } from "resource:///org/gnome/shell/extensions/extension.js";
import BoxOrderManager from "./extensionModules/BoxOrderManager.js"; import BoxOrderManager from "./extensionModules/BoxOrderManager.js";
import type { Box } from "./extensionModules/BoxOrderManager.js";
export interface CustomPanel extends Panel.Panel {
_leftBox: St.BoxLayout;
_centerBox: St.BoxLayout;
_rightBox: St.BoxLayout;
}
export default class TopBarOrganizerExtension extends Extension { export default class TopBarOrganizerExtension extends Extension {
_settings!: Gio.Settings; enable() {
_boxOrderManager!: BoxOrderManager;
_settingsHandlerIds!: number[];
enable(): void {
this._settings = this.getSettings(); this._settings = this.getSettings();
this._boxOrderManager = new BoxOrderManager({}, this._settings); this._boxOrderManager = new BoxOrderManager({}, this._settings);
@ -40,7 +26,7 @@ export default class TopBarOrganizerExtension extends Extension {
// Handle changes of settings. // Handle changes of settings.
this._settingsHandlerIds = []; this._settingsHandlerIds = [];
const addSettingsChangeHandler = (settingsName: string) => { const addSettingsChangeHandler = (settingsName) => {
const handlerId = this._settings.connect(`changed::${settingsName}`, () => { const handlerId = this._settings.connect(`changed::${settingsName}`, () => {
this.#handleNewItemsAndOrderTopBar(); this.#handleNewItemsAndOrderTopBar();
}); });
@ -53,12 +39,10 @@ export default class TopBarOrganizerExtension extends Extension {
addSettingsChangeHandler("show"); addSettingsChangeHandler("show");
} }
disable(): void { disable() {
// Revert the overwrite of `Panel._addToPanelBox`. // Revert the overwrite of `Panel._addToPanelBox`.
// @ts-ignore
Panel.Panel.prototype._addToPanelBox = Panel.Panel.prototype._originalAddToPanelBox; Panel.Panel.prototype._addToPanelBox = Panel.Panel.prototype._originalAddToPanelBox;
// Set `Panel._originalAddToPanelBox` to `undefined`. // Set `Panel._originalAddToPanelBox` to `undefined`.
// @ts-ignore
Panel.Panel.prototype._originalAddToPanelBox = undefined; Panel.Panel.prototype._originalAddToPanelBox = undefined;
// Disconnect signals. // Disconnect signals.
@ -67,9 +51,7 @@ export default class TopBarOrganizerExtension extends Extension {
} }
this._boxOrderManager.disconnectSignals(); this._boxOrderManager.disconnectSignals();
// @ts-ignore
this._settings = null; this._settings = null;
// @ts-ignore
this._boxOrderManager = null; this._boxOrderManager = null;
} }
@ -81,10 +63,9 @@ export default class TopBarOrganizerExtension extends Extension {
* Overwrite `Panel._addToPanelBox` with a custom method, which simply calls * Overwrite `Panel._addToPanelBox` with a custom method, which simply calls
* the original one and handles new items and orders the top bar afterwards. * the original one and handles new items and orders the top bar afterwards.
*/ */
#overwritePanelAddToPanelBox(): void { #overwritePanelAddToPanelBox() {
// Add the original `Panel._addToPanelBox` method as // Add the original `Panel._addToPanelBox` method as
// `Panel._originalAddToPanelBox`. // `Panel._originalAddToPanelBox`.
// @ts-ignore
Panel.Panel.prototype._originalAddToPanelBox = Panel.Panel.prototype._addToPanelBox; Panel.Panel.prototype._originalAddToPanelBox = Panel.Panel.prototype._addToPanelBox;
const handleNewItemsAndOrderTopBar = () => { const handleNewItemsAndOrderTopBar = () => {
@ -95,7 +76,6 @@ export default class TopBarOrganizerExtension extends Extension {
Panel.Panel.prototype._addToPanelBox = function(role, indicator, position, box) { Panel.Panel.prototype._addToPanelBox = function(role, indicator, position, box) {
// Simply call the original `_addToPanelBox` and order the top bar // Simply call the original `_addToPanelBox` and order the top bar
// and handle new items afterwards. // and handle new items afterwards.
// @ts-ignore
this._originalAddToPanelBox(role, indicator, position, box); this._originalAddToPanelBox(role, indicator, position, box);
handleNewItemsAndOrderTopBar(); handleNewItemsAndOrderTopBar();
}; };
@ -108,9 +88,9 @@ export default class TopBarOrganizerExtension extends Extension {
/** /**
* This method orders the top bar items of the specified box according to * This method orders the top bar items of the specified box according to
* the configured box orders. * the configured box orders.
* @param {Box} box - The box to order. * @param {string} box - The box to order.
*/ */
#orderTopBarItems(box: Box): void { #orderTopBarItems(box) {
// Only run, when the session mode is "user" or the parent session mode // Only run, when the session mode is "user" or the parent session mode
// is "user". // is "user".
if(Main.sessionMode.currentMode !== "user" && Main.sessionMode.parentMode !== "user") { if(Main.sessionMode.currentMode !== "user" && Main.sessionMode.parentMode !== "user") {
@ -124,13 +104,13 @@ export default class TopBarOrganizerExtension extends Extension {
let panelBox; let panelBox;
switch (box) { switch (box) {
case "left": case "left":
panelBox = (Main.panel as CustomPanel)._leftBox; panelBox = Main.panel._leftBox;
break; break;
case "center": case "center":
panelBox = (Main.panel as CustomPanel)._centerBox; panelBox = Main.panel._centerBox;
break; break;
case "right": case "right":
panelBox = (Main.panel as CustomPanel)._rightBox; panelBox = Main.panel._rightBox;
break; break;
} }
@ -139,19 +119,12 @@ export default class TopBarOrganizerExtension extends Extension {
for (let i = 0; i < validBoxOrder.length; i++) { for (let i = 0; i < validBoxOrder.length; i++) {
const item = validBoxOrder[i]; const item = validBoxOrder[i];
// Get the indicator container associated with the current role. // Get the indicator container associated with the current role.
const associatedIndicatorContainer = (Main.panel.statusArea as any)[item.role]?.container; const associatedIndicatorContainer = Main.panel.statusArea[item.role].container;
if (!(associatedIndicatorContainer instanceof St.Bin)) {
// TODO: maybe add logging
continue;
}
// Save whether or not the indicator container is visible. // Save whether or not the indicator container is visible.
const isVisible = associatedIndicatorContainer.visible; const isVisible = associatedIndicatorContainer.visible;
const parent = associatedIndicatorContainer.get_parent(); associatedIndicatorContainer.get_parent().remove_child(associatedIndicatorContainer);
if (parent !== null) {
parent.remove_child(associatedIndicatorContainer);
}
if (box === "right") { if (box === "right") {
// If the target panel box is the right panel box, insert the // If the target panel box is the right panel box, insert the
// indicator container at index `-1`, which just adds it to the // indicator container at index `-1`, which just adds it to the
@ -192,7 +165,7 @@ export default class TopBarOrganizerExtension extends Extension {
* This method handles all new items currently present in the top bar and * This method handles all new items currently present in the top bar and
* orders the items of all top bar boxes. * orders the items of all top bar boxes.
*/ */
#handleNewItemsAndOrderTopBar(): void { #handleNewItemsAndOrderTopBar() {
// Only run, when the session mode is "user" or the parent session mode // Only run, when the session mode is "user" or the parent session mode
// is "user". // is "user".
if(Main.sessionMode.currentMode !== "user" && Main.sessionMode.parentMode !== "user") { if(Main.sessionMode.currentMode !== "user" && Main.sessionMode.parentMode !== "user") {

View File

@ -1,24 +1,18 @@
"use strict"; "use strict";
import GObject from "gi://GObject"; import GObject from "gi://GObject";
import St from "gi://St";
import type Gio from "gi://Gio";
import * as Main from "resource:///org/gnome/shell/ui/main.js"; import * as Main from "resource:///org/gnome/shell/ui/main.js";
import type { CustomPanel } from "../extension.js"
export type Box = "left" | "center" | "right";
type Hide = "hide" | "show" | "default";
/** /**
* A resolved box order item containing the items role, settings identifier and * A resolved box order item containing the items role, settings identifier and
* additional information. * additional information.
* @typedef {Object} ResolvedBoxOrderItem
* @property {string} settingsId - The settings identifier of the item.
* @property {string} role - The role of the item.
* @property {string} hide - Whether the item should be (forcefully) hidden
* (hide), shown (show) or just be left as is (default).
*/ */
interface ResolvedBoxOrderItem {
settingsId: string // The settings identifier of the item.
role: string // The role of the item.
hide: Hide // Whether the item should be (forcefully) hidden, (forcefully) shown or just be left as is.
}
/** /**
* This class provides an interfaces to the box orders stored in settings. * This class provides an interfaces to the box orders stored in settings.
@ -36,15 +30,12 @@ export default class BoxOrderManager extends GObject.Object {
}, this); }, this);
} }
// Can't have type guarantees here, since this is working with types from #appIndicatorReadyHandlerIdMap;
// the KStatusNotifier/AppIndicator extension. #appIndicatorItemSettingsIdToRolesMap;
#appIndicatorReadyHandlerIdMap: Map<any, any>; #taskUpUltraLiteItemRoles;
#appIndicatorItemSettingsIdToRolesMap: Map<string, string[]>; #settings;
#taskUpUltraLiteItemRoles: string[];
#settings: Gio.Settings;
constructor(params = {}, settings: Gio.Settings) { constructor(params = {}, settings) {
// @ts-ignore Params should be passed, see: https://gjs.guide/guides/gobject/subclassing.html#subclassing-gobject
super(params); super(params);
this.#appIndicatorReadyHandlerIdMap = new Map(); this.#appIndicatorReadyHandlerIdMap = new Map();
@ -56,22 +47,30 @@ export default class BoxOrderManager extends GObject.Object {
/** /**
* Gets a box order for the given top bar box from settings. * Gets a box order for the given top bar box from settings.
* @param {Box} box - The top bar box for which to get the box order. * @param {string} box - The top bar box for which to get the box order.
* Must be one of the following values:
* - "left"
* - "center"
* - "right"
* @returns {string[]} - The box order consisting of an array of item * @returns {string[]} - The box order consisting of an array of item
* settings identifiers. * settings identifiers.
*/ */
#getBoxOrder(box: Box): string[] { #getBoxOrder(box) {
return this.#settings.get_strv(`${box}-box-order`); return this.#settings.get_strv(`${box}-box-order`);
} }
/** /**
* Save the given box order to settings, making sure to only save a changed * Save the given box order to settings, making sure to only save a changed
* box order, to avoid loops when listening on settings changes. * box order, to avoid loops when listening on settings changes.
* @param {Box} box - The top bar box for which to save the box order. * @param {string} box - The top bar box for which to save the box order.
* Must be one of the following values:
* - "left"
* - "center"
* - "right"
* @param {string[]} boxOrder - The box order to save. Must be an array of * @param {string[]} boxOrder - The box order to save. Must be an array of
* item settings identifiers. * item settings identifiers.
*/ */
#saveBoxOrder(box: Box, boxOrder: string[]): void { #saveBoxOrder(box, boxOrder) {
const currentBoxOrder = this.#getBoxOrder(box); const currentBoxOrder = this.#getBoxOrder(box);
// Only save the given box order to settings, if it is different, to // Only save the given box order to settings, if it is different, to
@ -91,18 +90,14 @@ export default class BoxOrderManager extends GObject.Object {
* then also makes sure that once the app indicators "ready" signal emits, * then also makes sure that once the app indicators "ready" signal emits,
* this classes "appIndicatorReady" signal emits as well, such that it and * this classes "appIndicatorReady" signal emits as well, such that it and
* other methods can be called again to properly handle the item. * other methods can be called again to properly handle the item.
* @param {St.Bin} indicatorContainer - The container of the indicator of the * @param {string} indicatorContainer - The container of the indicator of the
* AppIndicator/KStatusNotifierItem item. * AppIndicator/KStatusNotifierItem item.
* @param {string} role - The role of the AppIndicator/KStatusNotifierItem * @param {string} role - The role of the AppIndicator/KStatusNotifierItem
* item. * item.
* @returns {string} The derived items settings identifier. * @returns {string} The derived items settings identifier.
*/ */
#handleAppIndicatorItem(indicatorContainer: St.Bin, role: string): string { #handleAppIndicatorItem(indicatorContainer, role) {
// Since this is working with types from the const appIndicator = indicatorContainer.get_child()._indicator;
// AppIndicator/KStatusNotifierItem extension, we loose a bunch of type
// safety here.
// https://github.com/ubuntu/gnome-shell-extension-appindicator
const appIndicator = (indicatorContainer.get_child() as any)._indicator;
let application = appIndicator.id; let application = appIndicator.id;
if (!application && this.#appIndicatorReadyHandlerIdMap) { if (!application && this.#appIndicatorReadyHandlerIdMap) {
@ -151,7 +146,7 @@ export default class BoxOrderManager extends GObject.Object {
* @param {string} role - The role of the Task Up UltraLite item. * @param {string} role - The role of the Task Up UltraLite item.
* @returns {string} The settings identifier to use. * @returns {string} The settings identifier to use.
*/ */
#handleTaskUpUltraLiteItem(role: string): string { #handleTaskUpUltraLiteItem(role) {
const roles = this.#taskUpUltraLiteItemRoles; const roles = this.#taskUpUltraLiteItemRoles;
if (!roles.includes(role)) { if (!roles.includes(role)) {
@ -167,10 +162,14 @@ export default class BoxOrderManager extends GObject.Object {
* meaning they might be present multiple times or not at all depending on * meaning they might be present multiple times or not at all depending on
* the roles stored. * the roles stored.
* The items of the box order also have additional information stored. * The items of the box order also have additional information stored.
* @param {Box} box - The top bar box for which to get the resolved box order. * @param {string} box - The top bar box for which to get the resolved box order.
* Must be one of the following values:
* - "left"
* - "center"
* - "right"
* @returns {ResolvedBoxOrderItem[]} - The resolved box order. * @returns {ResolvedBoxOrderItem[]} - The resolved box order.
*/ */
#getResolvedBoxOrder(box: Box): ResolvedBoxOrderItem[] { #getResolvedBoxOrder(box) {
let boxOrder = this.#getBoxOrder(box); let boxOrder = this.#getBoxOrder(box);
const itemsToHide = this.#settings.get_strv("hide"); const itemsToHide = this.#settings.get_strv("hide");
@ -208,15 +207,15 @@ export default class BoxOrderManager extends GObject.Object {
// the item specially. // the item specially.
// Get the roles associated with the items settings id. // Get the roles associated with the items settings id.
let roles: string[] = []; let roles = [];
if (itemSettingsId.startsWith("appindicator-kstatusnotifieritem-")) { if (itemSettingsId.startsWith("appindicator-kstatusnotifieritem-")) {
roles = this.#appIndicatorItemSettingsIdToRolesMap.get(resolvedBoxOrderItem.settingsId) ?? []; roles = this.#appIndicatorItemSettingsIdToRolesMap.get(resolvedBoxOrderItem.settingsId);
} else if (itemSettingsId === "item-role-group-task-up-ultralite") { } else if (itemSettingsId === "item-role-group-task-up-ultralite") {
roles = this.#taskUpUltraLiteItemRoles; roles = this.#taskUpUltraLiteItemRoles;
} }
// If there are no roles associated, continue. // If there are no roles associated, continue.
if (roles.length === 0) { if (!roles) {
continue; continue;
} }
@ -237,13 +236,12 @@ export default class BoxOrderManager extends GObject.Object {
* This is typically used before nulling an instance of this class to make * This is typically used before nulling an instance of this class to make
* sure all signals are disconnected. * sure all signals are disconnected.
*/ */
disconnectSignals(): void { disconnectSignals() {
for (const [handlerId, appIndicator] of this.#appIndicatorReadyHandlerIdMap) { for (const [handlerId, appIndicator] of this.#appIndicatorReadyHandlerIdMap) {
if (handlerId && appIndicator?.signalHandlerIsConnected(handlerId)) { if (handlerId && appIndicator?.signalHandlerIsConnected(handlerId)) {
appIndicator.disconnect(handlerId); appIndicator.disconnect(handlerId);
} }
} }
// @ts-ignore
this.#appIndicatorReadyHandlerIdMap = null; this.#appIndicatorReadyHandlerIdMap = null;
} }
@ -252,23 +250,25 @@ export default class BoxOrderManager extends GObject.Object {
* and Task Up UltraLite items got resolved and where only items are * and Task Up UltraLite items got resolved and where only items are
* included, which are in some GNOME Shell top bar box. * included, which are in some GNOME Shell top bar box.
* The items of the box order also have additional information stored. * The items of the box order also have additional information stored.
* @param {Box} box - The top bar box to return the valid box order for. * @param {string} box - The top bar box to return the valid box order for.
* Must be one of the following values:
* - "left"
* - "center"
* - "right"
* @returns {ResolvedBoxOrderItem[]} - The valid box order. * @returns {ResolvedBoxOrderItem[]} - The valid box order.
*/ */
getValidBoxOrder(box: Box): ResolvedBoxOrderItem[] { getValidBoxOrder(box) {
// Get a resolved box order. // Get a resolved box order.
let resolvedBoxOrder = this.#getResolvedBoxOrder(box); let resolvedBoxOrder = this.#getResolvedBoxOrder(box);
// ToDo: simplify. // ToDo: simplify.
// Get the indicator containers (of the items) currently present in the // Get the indicator containers (of the items) currently present in the
// GNOME Shell top bar. // GNOME Shell top bar.
// They should be St.Bins (see link), so ensure that using a filter.
// https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/48.2/js/ui/panelMenu.js?ref_type=tags#L21
const indicatorContainers = [ const indicatorContainers = [
(Main.panel as CustomPanel)._leftBox.get_children(), Main.panel._leftBox.get_children(),
(Main.panel as CustomPanel)._centerBox.get_children(), Main.panel._centerBox.get_children(),
(Main.panel as CustomPanel)._rightBox.get_children(), Main.panel._rightBox.get_children(),
].flat().filter(ic => ic instanceof St.Bin); ].flat();
// Create an indicator containers set from the indicator containers for // Create an indicator containers set from the indicator containers for
// fast easy access. // fast easy access.
@ -277,13 +277,10 @@ export default class BoxOrderManager extends GObject.Object {
// Go through the resolved box order and only add items to the valid box // Go through the resolved box order and only add items to the valid box
// order, where their indicator is currently present in the GNOME Shell // order, where their indicator is currently present in the GNOME Shell
// top bar. // top bar.
let validBoxOrder: ResolvedBoxOrderItem[] = []; let validBoxOrder = [];
for (const item of resolvedBoxOrder) { for (const item of resolvedBoxOrder) {
const associatedIndicatorContainer = (Main.panel.statusArea as any)[item.role]?.container; // Get the indicator container associated with the items role.
if (!(associatedIndicatorContainer instanceof St.Bin)) { const associatedIndicatorContainer = Main.panel.statusArea[item.role]?.container;
// TODO: maybe add logging
continue;
}
if (indicatorContainerSet.has(associatedIndicatorContainer)) { if (indicatorContainerSet.has(associatedIndicatorContainer)) {
validBoxOrder.push(item); validBoxOrder.push(item);
@ -297,7 +294,7 @@ export default class BoxOrderManager extends GObject.Object {
* This method saves all new items currently present in the GNOME Shell top * This method saves all new items currently present in the GNOME Shell top
* bar to the settings. * bar to the settings.
*/ */
saveNewTopBarItems(): void { saveNewTopBarItems() {
// Only run, when the session mode is "user" or the parent session mode // Only run, when the session mode is "user" or the parent session mode
// is "user". // is "user".
if (Main.sessionMode.currentMode !== "user" && Main.sessionMode.parentMode !== "user") { if (Main.sessionMode.currentMode !== "user" && Main.sessionMode.parentMode !== "user") {
@ -313,31 +310,24 @@ export default class BoxOrderManager extends GObject.Object {
// Get roles (of items) currently present in the GNOME Shell top bar and // Get roles (of items) currently present in the GNOME Shell top bar and
// index them using their associated indicator container. // index them using their associated indicator container.
let indicatorContainerRoleMap = new Map<St.Bin, string>(); let indicatorContainerRoleMap = new Map();
for (const role in (Main.panel.statusArea as any)) { for (const role in Main.panel.statusArea) {
const associatedIndicatorContainer = (Main.panel.statusArea as any)[role]?.container; indicatorContainerRoleMap.set(Main.panel.statusArea[role].container, role);
if (!(associatedIndicatorContainer instanceof St.Bin)) {
// TODO: maybe add logging
continue;
}
indicatorContainerRoleMap.set(associatedIndicatorContainer, role);
} }
// Get the indicator containers (of the items) currently present in the // Get the indicator containers (of the items) currently present in the
// GNOME Shell top bar boxes. // GNOME Shell top bar boxes.
// They should be St.Bins (see link), so ensure that using a filter.
// https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/48.2/js/ui/panelMenu.js?ref_type=tags#L21
const boxIndicatorContainers = { const boxIndicatorContainers = {
left: (Main.panel as CustomPanel)._leftBox.get_children().filter(ic => ic instanceof St.Bin), left: Main.panel._leftBox.get_children(),
center: (Main.panel as CustomPanel)._centerBox.get_children().filter(ic => ic instanceof St.Bin), center: Main.panel._centerBox.get_children(),
// Reverse this array, since the items in the left and center box // Reverse this array, since the items in the left and center box
// are logically LTR, while the items in the right box are RTL. // are logically LTR, while the items in the right box are RTL.
right: (Main.panel as CustomPanel)._rightBox.get_children().filter(ic => ic instanceof St.Bin).reverse(), right: Main.panel._rightBox.get_children().reverse(),
}; };
// This function goes through the indicator containers of the given box // This function goes through the indicator containers of the given box
// and adds new item settings identifiers to the given box order. // and adds new item settings identifiers to the given box order.
const addNewItemSettingsIdsToBoxOrder = (indicatorContainers: St.Bin[], boxOrder: string[], box: Box) => { const addNewItemSettingsIdsToBoxOrder = (indicatorContainers, boxOrder, box) => {
for (const indicatorContainer of indicatorContainers) { for (const indicatorContainer of indicatorContainers) {
// First get the role associated with the current indicator // First get the role associated with the current indicator
// container. // container.
@ -355,9 +345,6 @@ export default class BoxOrderManager extends GObject.Object {
try { try {
itemSettingsId = this.#handleAppIndicatorItem(indicatorContainer, role); itemSettingsId = this.#handleAppIndicatorItem(indicatorContainer, role);
} catch (e) { } catch (e) {
if (!(e instanceof Error)) {
throw(e);
}
if (e.message !== "Application can't be determined.") { if (e.message !== "Application can't be determined.") {
throw(e); throw(e);
} }

View File

@ -13,7 +13,7 @@ export default class TopBarOrganizerPreferences extends ExtensionPreferences {
provider.load_from_path(this.metadata.dir.get_path() + "/css/prefs.css"); provider.load_from_path(this.metadata.dir.get_path() + "/css/prefs.css");
const defaultGdkDisplay = Gdk.Display.get_default(); const defaultGdkDisplay = Gdk.Display.get_default();
Gtk.StyleContext.add_provider_for_display( Gtk.StyleContext.add_provider_for_display(
(defaultGdkDisplay as Gdk.Display), defaultGdkDisplay,
provider, provider,
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
); );
@ -22,7 +22,7 @@ export default class TopBarOrganizerPreferences extends ExtensionPreferences {
prefsPage.connect("destroy", () => { prefsPage.connect("destroy", () => {
Gtk.StyleContext.remove_provider_for_display( Gtk.StyleContext.remove_provider_for_display(
(defaultGdkDisplay as Gdk.Display), defaultGdkDisplay,
provider provider
); );
}); });

View File

@ -6,8 +6,6 @@ import GObject from "gi://GObject";
import Adw from "gi://Adw"; import Adw from "gi://Adw";
import GLib from "gi://GLib"; import GLib from "gi://GLib";
import type PrefsBoxOrderListBox from "./PrefsBoxOrderListBox.js";
export default class PrefsBoxOrderItemRow extends Adw.ActionRow { export default class PrefsBoxOrderItemRow extends Adw.ActionRow {
static { static {
GObject.registerClass({ GObject.registerClass({
@ -20,8 +18,8 @@ export default class PrefsBoxOrderItemRow extends Adw.ActionRow {
}, },
}, this); }, this);
this.install_action("row.forget", null, (self, _actionName, _param) => { this.install_action("row.forget", null, (self, _actionName, _param) => {
const parentListBox = self.get_parent() as PrefsBoxOrderListBox; const parentListBox = self.get_parent();
parentListBox.removeRow(self as PrefsBoxOrderItemRow); parentListBox.removeRow(self);
parentListBox.saveBoxOrderToSettings(); parentListBox.saveBoxOrderToSettings();
parentListBox.determineRowMoveActionEnable(); parentListBox.determineRowMoveActionEnable();
}); });
@ -29,32 +27,39 @@ export default class PrefsBoxOrderItemRow extends Adw.ActionRow {
this.install_action("row.move-down", null, (self, _actionName, _param) => self.emit("move", "down")); this.install_action("row.move-down", null, (self, _actionName, _param) => self.emit("move", "down"));
} }
item: string; #drag_starting_point_x;
#drag_starting_point_x?: number; #drag_starting_point_y;
#drag_starting_point_y?: number;
constructor(params = {}, item: string) { constructor(params = {}, item) {
super(params); super(params);
// Associate `this` with an item. this.#associateItem(item);
}
/**
* Associate `this` with an item.
* @param {String} item
*/
#associateItem(item) {
this.item = item; this.item = item;
if (this.item.startsWith("appindicator-kstatusnotifieritem-")) {
if (item.startsWith("appindicator-kstatusnotifieritem-")) {
// Set the title to something nicer, if the associated item is an // Set the title to something nicer, if the associated item is an
// AppIndicator/KStatusNotifierItem item. // AppIndicator/KStatusNotifierItem item.
this.set_title(this.item.replace("appindicator-kstatusnotifieritem-", "")); this.set_title(item.replace("appindicator-kstatusnotifieritem-", ""));
} else if (this.item === "item-role-group-task-up-ultralite") { } else if (item === "item-role-group-task-up-ultralite") {
// Set the title to something nicer, if the item in question is the // Set the title to something nicer, if the item in question is the
// Task Up UltraLite item role group. // Task Up UltraLite item role group.
this.set_title("Task Up UltraLite Items"); this.set_title("Task Up UltraLite Items");
} else { } else {
// Otherwise just set it to `item`. // Otherwise just set it to `item`.
this.set_title(this.item); this.set_title(item);
} }
} }
onDragPrepare(_source: Gtk.DragSource, x: number, y: number): Gdk.ContentProvider { onDragPrepare(_source, x, y) {
const value = new GObject.Value(); const value = new GObject.Value();
value.init(PrefsBoxOrderItemRow.$gtype); value.init(PrefsBoxOrderItemRow);
value.set_object(this); value.set_object(this);
this.#drag_starting_point_x = x; this.#drag_starting_point_x = x;
@ -62,7 +67,7 @@ export default class PrefsBoxOrderItemRow extends Adw.ActionRow {
return Gdk.ContentProvider.new_for_value(value); return Gdk.ContentProvider.new_for_value(value);
} }
onDragBegin(_source: Gtk.DragSource, drag: Gdk.Drag): void { onDragBegin(_source, drag) {
let dragWidget = new Gtk.ListBox(); let dragWidget = new Gtk.ListBox();
let allocation = this.get_allocation(); let allocation = this.get_allocation();
dragWidget.set_size_request(allocation.width, allocation.height); dragWidget.set_size_request(allocation.width, allocation.height);
@ -73,32 +78,20 @@ export default class PrefsBoxOrderItemRow extends Adw.ActionRow {
let currentDragIcon = Gtk.DragIcon.get_for_drag(drag); let currentDragIcon = Gtk.DragIcon.get_for_drag(drag);
currentDragIcon.set_child(dragWidget); currentDragIcon.set_child(dragWidget);
// Even tho this should always be the case, ensure the values for the hotspot aren't undefined. drag.set_hotspot(this.#drag_starting_point_x, this.#drag_starting_point_y);
if (typeof this.#drag_starting_point_x !== "undefined" &&
typeof this.#drag_starting_point_y !== "undefined") {
drag.set_hotspot(this.#drag_starting_point_x, this.#drag_starting_point_y);
}
} }
// Handle a new drop on `this` properly. // Handle a new drop on `this` properly.
// `value` is the thing getting dropped. // `value` is the thing getting dropped.
onDrop(_target: Gtk.DropTarget, value: any, _x: number, _y: number): boolean { onDrop(_target, value, _x, _y) {
// According to the type annotations of Gtk.DropTarget, value is of type
// GObject.Value, so ensure the one we work with is of type
// PrefsBoxOrderItemRow.
if (!(value instanceof PrefsBoxOrderItemRow)) {
// TODO: maybe add logging
return false;
}
// If `this` got dropped onto itself, do nothing. // If `this` got dropped onto itself, do nothing.
if (value === this) { if (value === this) {
return false; return;
} }
// Get the GtkListBoxes of `this` and the drop value. // Get the GtkListBoxes of `this` and the drop value.
const ownListBox = this.get_parent() as PrefsBoxOrderListBox; const ownListBox = this.get_parent();
const valueListBox = value.get_parent() as PrefsBoxOrderListBox; const valueListBox = value.get_parent();
// Get the position of `this` and the drop value. // Get the position of `this` and the drop value.
const ownPosition = this.get_index(); const ownPosition = this.get_index();
@ -145,7 +138,5 @@ export default class PrefsBoxOrderItemRow extends Adw.ActionRow {
valueListBox.saveBoxOrderToSettings(); valueListBox.saveBoxOrderToSettings();
valueListBox.determineRowMoveActionEnable(); valueListBox.determineRowMoveActionEnable();
} }
return true;
} }
} }

View File

@ -3,7 +3,6 @@
import Gtk from "gi://Gtk"; import Gtk from "gi://Gtk";
import GObject from "gi://GObject"; import GObject from "gi://GObject";
import GLib from "gi://GLib"; import GLib from "gi://GLib";
import type Gio from "gi://Gio";
import { ExtensionPreferences } from "resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js"; import { ExtensionPreferences } from "resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js";
@ -26,15 +25,14 @@ export default class PrefsBoxOrderListBox extends Gtk.ListBox {
}, },
Signals: { Signals: {
"row-move": { "row-move": {
param_types: [PrefsBoxOrderItemRow.$gtype, GObject.TYPE_STRING], param_types: [PrefsBoxOrderItemRow, GObject.TYPE_STRING],
}, },
}, },
}, this); }, this);
} }
_boxOrder!: string; #settings;
#settings: Gio.Settings; #rowSignalHandlerIds = new Map();
#rowSignalHandlerIds = new Map<PrefsBoxOrderItemRow, number[]>();
/** /**
* @param {Object} params * @param {Object} params
@ -43,18 +41,18 @@ export default class PrefsBoxOrderListBox extends Gtk.ListBox {
super(params); super(params);
// Load the settings. // Load the settings.
this.#settings = ExtensionPreferences.lookupByURL(import.meta.url)!.getSettings(); this.#settings = ExtensionPreferences.lookupByURL(import.meta.url).getSettings();
// Add a placeholder widget for the case, where no GtkListBoxRows are // Add a placeholder widget for the case, where no GtkListBoxRows are
// present. // present.
this.set_placeholder(new PrefsBoxOrderListEmptyPlaceholder()); this.set_placeholder(new PrefsBoxOrderListEmptyPlaceholder());
} }
get boxOrder(): string { get boxOrder() {
return this._boxOrder; return this._boxOrder;
} }
set boxOrder(value: string) { set boxOrder(value) {
this._boxOrder = value; this._boxOrder = value;
// Get the actual box order for the given box order name from settings. // Get the actual box order for the given box order name from settings.
@ -75,10 +73,10 @@ export default class PrefsBoxOrderListBox extends Gtk.ListBox {
* position. * position.
* Also handles stuff like connecting signals. * Also handles stuff like connecting signals.
*/ */
insertRow(row: PrefsBoxOrderItemRow, position: number): void { insertRow(row, position) {
this.insert(row, position); this.insert(row, position);
const signalHandlerIds: number[] = []; const signalHandlerIds = [];
signalHandlerIds.push(row.connect("move", (row, direction) => { signalHandlerIds.push(row.connect("move", (row, direction) => {
this.emit("row-move", row, direction); this.emit("row-move", row, direction);
})); }));
@ -90,8 +88,8 @@ export default class PrefsBoxOrderListBox extends Gtk.ListBox {
* Removes the given PrefsBoxOrderItemRow from this list box. * Removes the given PrefsBoxOrderItemRow from this list box.
* Also handles stuff like disconnecting signals. * Also handles stuff like disconnecting signals.
*/ */
removeRow(row: PrefsBoxOrderItemRow): void { removeRow(row) {
const signalHandlerIds = this.#rowSignalHandlerIds.get(row) ?? []; const signalHandlerIds = this.#rowSignalHandlerIds.get(row);
for (const id of signalHandlerIds) { for (const id of signalHandlerIds) {
row.disconnect(id); row.disconnect(id);
@ -104,11 +102,11 @@ export default class PrefsBoxOrderListBox extends Gtk.ListBox {
* Saves the box order represented by `this` (and its * Saves the box order represented by `this` (and its
* `PrefsBoxOrderItemRows`) to settings. * `PrefsBoxOrderItemRows`) to settings.
*/ */
saveBoxOrderToSettings(): void { saveBoxOrderToSettings() {
let currentBoxOrder: string[] = []; let currentBoxOrder = [];
for (let potentialPrefsBoxOrderItemRow of this) { for (let potentialPrefsBoxOrderItemRow of this) {
// Only process PrefsBoxOrderItemRows. // Only process PrefsBoxOrderItemRows.
if (!(potentialPrefsBoxOrderItemRow instanceof PrefsBoxOrderItemRow)) { if (potentialPrefsBoxOrderItemRow.constructor.$gtype.name !== "PrefsBoxOrderItemRow") {
continue; continue;
} }
@ -122,10 +120,10 @@ export default class PrefsBoxOrderListBox extends Gtk.ListBox {
* Determines whether or not each move action of each PrefsBoxOrderItemRow * Determines whether or not each move action of each PrefsBoxOrderItemRow
* should be enabled or disabled. * should be enabled or disabled.
*/ */
determineRowMoveActionEnable(): void { determineRowMoveActionEnable() {
for (let potentialPrefsBoxOrderItemRow of this) { for (let potentialPrefsBoxOrderItemRow of this) {
// Only process PrefsBoxOrderItemRows. // Only process PrefsBoxOrderItemRows.
if (!(potentialPrefsBoxOrderItemRow instanceof PrefsBoxOrderItemRow)) { if (potentialPrefsBoxOrderItemRow.constructor.$gtype.name !== "PrefsBoxOrderItemRow") {
continue; continue;
} }

View File

@ -4,9 +4,6 @@ import Gtk from "gi://Gtk";
import GObject from "gi://GObject"; import GObject from "gi://GObject";
import GLib from "gi://GLib"; import GLib from "gi://GLib";
import PrefsBoxOrderItemRow from "./PrefsBoxOrderItemRow.js";
import type PrefsBoxOrderListBox from "./PrefsBoxOrderListBox.js";
export default class PrefsBoxOrderListEmptyPlaceholder extends Gtk.Box { export default class PrefsBoxOrderListEmptyPlaceholder extends Gtk.Box {
static { static {
GObject.registerClass({ GObject.registerClass({
@ -17,18 +14,10 @@ export default class PrefsBoxOrderListEmptyPlaceholder extends Gtk.Box {
// Handle a new drop on `this` properly. // Handle a new drop on `this` properly.
// `value` is the thing getting dropped. // `value` is the thing getting dropped.
onDrop(_target: Gtk.DropTarget, value: any, _x: number, _y: number): boolean { onDrop(_target, value, _x, _y) {
// According to the type annotations of Gtk.DropTarget, value is of type
// GObject.Value, so ensure the one we work with is of type
// PrefsBoxOrderItemRow.
if (!(value instanceof PrefsBoxOrderItemRow)) {
// TODO: maybe add logging
return false;
}
// Get the GtkListBoxes of `this` and the drop value. // Get the GtkListBoxes of `this` and the drop value.
const ownListBox = this.get_parent() as PrefsBoxOrderListBox; const ownListBox = this.get_parent();
const valueListBox = value.get_parent() as PrefsBoxOrderListBox; const valueListBox = value.get_parent();
// Remove the drop value from its list box. // Remove the drop value from its list box.
valueListBox.removeRow(value); valueListBox.removeRow(value);
@ -42,7 +31,5 @@ export default class PrefsBoxOrderListEmptyPlaceholder extends Gtk.Box {
ownListBox.determineRowMoveActionEnable(); ownListBox.determineRowMoveActionEnable();
valueListBox.saveBoxOrderToSettings(); valueListBox.saveBoxOrderToSettings();
valueListBox.determineRowMoveActionEnable(); valueListBox.determineRowMoveActionEnable();
return true;
} }
} }

View File

@ -1,6 +1,5 @@
"use strict"; "use strict";
import Gdk from "gi://Gdk";
import Gtk from "gi://Gtk"; import Gtk from "gi://Gtk";
import GObject from "gi://GObject"; import GObject from "gi://GObject";
import Adw from "gi://Adw"; import Adw from "gi://Adw";
@ -8,7 +7,6 @@ import GLib from "gi://GLib";
import ScrollManager from "./ScrollManager.js"; import ScrollManager from "./ScrollManager.js";
import PrefsBoxOrderListEmptyPlaceholder from "./PrefsBoxOrderListEmptyPlaceholder.js"; import PrefsBoxOrderListEmptyPlaceholder from "./PrefsBoxOrderListEmptyPlaceholder.js";
import type PrefsBoxOrderItemRow from "./PrefsBoxOrderItemRow.js";
// Imports to make UI file work. // Imports to make UI file work.
// eslint-disable-next-line // eslint-disable-next-line
@ -27,11 +25,6 @@ export default class PrefsPage extends Adw.PreferencesPage {
}, this); }, this);
} }
_dndEnded?: boolean;
_left_box_order_list_box!: PrefsBoxOrderListBox;
_center_box_order_list_box!: PrefsBoxOrderListBox;
_right_box_order_list_box!: PrefsBoxOrderListBox;
constructor(params = {}) { constructor(params = {}) {
super(params); super(params);
@ -44,27 +37,23 @@ export default class PrefsPage extends Adw.PreferencesPage {
* operation is in progress and the user has their cursor either in the * operation is in progress and the user has their cursor either in the
* upper or lower 10% of this widget respectively. * upper or lower 10% of this widget respectively.
*/ */
#setupDNDScroll(): void { #setupDNDScroll() {
// Pass `this.get_first_child()` to the ScrollManager, since this // Pass `this.get_first_child()` to the ScrollManager, since this
// `PrefsPage` extends an `Adw.PreferencesPage` and the first child of // `PrefsPage` extends an `Adw.PreferencesPage` and the first child of
// an `Adw.PreferencesPage` is the built-in `Gtk.ScrolledWindow`. // an `Adw.PreferencesPage` is the built-in `Gtk.ScrolledWindow`.
const scrollManager = new ScrollManager(this.get_first_child() as Gtk.ScrolledWindow); const scrollManager = new ScrollManager(this.get_first_child());
/// Setup GtkDropControllerMotion event controller and make use of its /// Setup GtkDropControllerMotion event controller and make use of its
/// events. /// events.
let controller = new Gtk.DropControllerMotion(); let controller = new Gtk.DropControllerMotion();
// Make sure scrolling stops, when DND operation ends. // Scroll, when the pointer is in the right places.
this._dndEnded = true;
// Scroll, when the pointer is in the right places and a DND operation
// is properly set up (this._dndEnded is false).
controller.connect("motion", (_, _x, y) => { controller.connect("motion", (_, _x, y) => {
if ((y <= this.get_allocated_height() * 0.1) && !this._dndEnded) { if (y <= this.get_allocated_height() * 0.1) {
// If the pointer is currently in the upper ten percent of this // If the pointer is currently in the upper ten percent of this
// widget, then scroll up. // widget, then scroll up.
scrollManager.startScrollUp(); scrollManager.startScrollUp();
} else if ((y >= this.get_allocated_height() * 0.9) && !this._dndEnded) { } else if (y >= this.get_allocated_height() * 0.9) {
// If the pointer is currently in the lower ten percent of this // If the pointer is currently in the lower ten percent of this
// widget, then scroll down. // widget, then scroll down.
scrollManager.startScrollDown(); scrollManager.startScrollDown();
@ -74,9 +63,11 @@ export default class PrefsPage extends Adw.PreferencesPage {
} }
}); });
// Make sure scrolling stops, when DND operation ends.
this._dndEnded = true;
const stopScrollAllAtDNDEnd = () => { const stopScrollAllAtDNDEnd = () => {
this._dndEnded = true;
scrollManager.stopScrollAll(); scrollManager.stopScrollAll();
this._dndEnded = true;
}; };
controller.connect("leave", () => { controller.connect("leave", () => {
stopScrollAllAtDNDEnd(); stopScrollAllAtDNDEnd();
@ -85,14 +76,7 @@ export default class PrefsPage extends Adw.PreferencesPage {
// Make use of `this._dndEnded` to setup stopScrollAtDNDEnd only // Make use of `this._dndEnded` to setup stopScrollAtDNDEnd only
// once per DND operation. // once per DND operation.
if (this._dndEnded) { if (this._dndEnded) {
const drag = controller.get_drop()?.get_drag() ?? null; let drag = controller.get_drop().get_drag();
// Ensure we have a Gdk.Drag.
// If this is not the case for whatever reason, then don't start
// DND scrolling and just return.
if (!(drag instanceof Gdk.Drag)) {
// TODO: maybe add logging
return;
}
drag.connect("drop-performed", () => { drag.connect("drop-performed", () => {
stopScrollAllAtDNDEnd(); stopScrollAllAtDNDEnd();
}); });
@ -109,7 +93,7 @@ export default class PrefsPage extends Adw.PreferencesPage {
this.add_controller(controller); this.add_controller(controller);
} }
onRowMove(listBox: PrefsBoxOrderListBox, row: PrefsBoxOrderItemRow, direction: string): void { onRowMove(listBox, row, direction) {
const rowPosition = row.get_index(); const rowPosition = row.get_index();
if (direction === "up") { // If the direction of the move is up. if (direction === "up") { // If the direction of the move is up.

View File

@ -1,21 +1,23 @@
"use strict"; "use strict";
import GLib from "gi://GLib"; import GLib from "gi://GLib";
import type Gtk from "gi://Gtk";
export default class ScrollManager { export default class ScrollManager {
#gtkScrolledWindow: Gtk.ScrolledWindow; #gtkScrolledWindow;
#scrollUp: boolean; #scrollUp;
#scrollDown: boolean; #scrollDown;
constructor(gtkScrolledWindow: Gtk.ScrolledWindow) { /**
* @param {Gtk.ScrolledWindow} gtkScrolledWindow
*/
constructor(gtkScrolledWindow) {
this.#gtkScrolledWindow = gtkScrolledWindow; this.#gtkScrolledWindow = gtkScrolledWindow;
this.#scrollUp = false; this.#scrollUp = false;
this.#scrollDown = false; this.#scrollDown = false;
} }
startScrollUp(): void { startScrollUp() {
// If the scroll up is already started, don't do anything. // If the scroll up is already started, don't do anything.
if (this.#scrollUp) { if (this.#scrollUp) {
return; return;
@ -42,7 +44,7 @@ export default class ScrollManager {
}); });
} }
startScrollDown(): void { startScrollDown() {
// If the scroll down is already started, don't do anything. // If the scroll down is already started, don't do anything.
if (this.#scrollDown) { if (this.#scrollDown) {
return; return;
@ -72,15 +74,15 @@ export default class ScrollManager {
}); });
} }
stopScrollUp(): void { stopScrollUp() {
this.#scrollUp = false; this.#scrollUp = false;
} }
stopScrollDown(): void { stopScrollDown() {
this.#scrollDown = false; this.#scrollDown = false;
} }
stopScrollAll(): void { stopScrollAll() {
this.stopScrollUp(); this.stopScrollUp();
this.stopScrollDown(); this.stopScrollDown();
} }

View File

@ -1,22 +0,0 @@
{
"compilerOptions": {
"module": "NodeNext",
"moduleResolution": "NodeNext",
"outDir": "./dist",
"sourceMap": false,
"strict": true,
// To preserve imports for UI files.
"verbatimModuleSyntax": true,
"target": "es2022",
"lib": [
"ES2022"
],
},
"include": [
"ambient.d.ts",
],
"files": [
"src/extension.ts",
"src/prefs.ts"
]
}