mirror of
https://gitlab.gnome.org/julianschacher/top-bar-organizer.git
synced 2025-10-27 15:19:09 +00:00
Compare commits
2 Commits
979e770057
...
9a6474b947
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9a6474b947 | ||
|
|
ff75debabc |
@ -6,7 +6,7 @@ insert_final_newline = true
|
||||
indent_style = space
|
||||
charset = utf-8
|
||||
|
||||
[*.{js,json}]
|
||||
[*.{js,ts,json}]
|
||||
indent_size = 4
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,2 +1,3 @@
|
||||
/node_modules/
|
||||
/dist/
|
||||
top-bar-organizer@julian.gse.jsts.xyz.shell-extension.zip
|
||||
|
||||
4
ambient.d.ts
vendored
Normal file
4
ambient.d.ts
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
import "@girs/gjs"
|
||||
import "@girs/gjs/dom"
|
||||
import "@girs/gnome-shell/ambient"
|
||||
import "@girs/gnome-shell/extensions/global"
|
||||
1002
package-lock.json
generated
1002
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -2,6 +2,7 @@
|
||||
"name": "top-bar-organizer",
|
||||
"version": "1.0.0",
|
||||
"description": "A Gnome Shell Extension for organizing your Gnome Shell top bar.",
|
||||
"type": "module",
|
||||
"directories": {
|
||||
"doc": "docs"
|
||||
},
|
||||
@ -12,6 +13,12 @@
|
||||
"author": "June",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"devDependencies": {
|
||||
"eslint": "^8.50.0"
|
||||
"eslint": "^8.57.1",
|
||||
"eslint-plugin-jsdoc": "^50.7.1",
|
||||
"typescript": "^5.8.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@girs/gjs": "^4.0.0-beta.23",
|
||||
"@girs/gnome-shell": "^48.0.2"
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,7 +4,11 @@ set -e
|
||||
|
||||
REAL_BASE_DIR=$( dirname $( readlink -f "$0" ))
|
||||
|
||||
gnome-extensions pack "$REAL_BASE_DIR/src" \
|
||||
rm -rf "$REAL_BASE_DIR/dist"
|
||||
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 \
|
||||
--extra-source extensionModules \
|
||||
--extra-source prefsModules \
|
||||
|
||||
@ -1,13 +1,27 @@
|
||||
"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 Panel from "resource:///org/gnome/shell/ui/panel.js";
|
||||
import { Extension } from "resource:///org/gnome/shell/extensions/extension.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 {
|
||||
enable() {
|
||||
_settings!: Gio.Settings;
|
||||
_boxOrderManager!: BoxOrderManager;
|
||||
_settingsHandlerIds!: number[];
|
||||
|
||||
enable(): void {
|
||||
this._settings = this.getSettings();
|
||||
|
||||
this._boxOrderManager = new BoxOrderManager({}, this._settings);
|
||||
@ -26,7 +40,7 @@ export default class TopBarOrganizerExtension extends Extension {
|
||||
|
||||
// Handle changes of settings.
|
||||
this._settingsHandlerIds = [];
|
||||
const addSettingsChangeHandler = (settingsName) => {
|
||||
const addSettingsChangeHandler = (settingsName: string) => {
|
||||
const handlerId = this._settings.connect(`changed::${settingsName}`, () => {
|
||||
this.#handleNewItemsAndOrderTopBar();
|
||||
});
|
||||
@ -39,10 +53,12 @@ export default class TopBarOrganizerExtension extends Extension {
|
||||
addSettingsChangeHandler("show");
|
||||
}
|
||||
|
||||
disable() {
|
||||
disable(): void {
|
||||
// Revert the overwrite of `Panel._addToPanelBox`.
|
||||
// @ts-ignore
|
||||
Panel.Panel.prototype._addToPanelBox = Panel.Panel.prototype._originalAddToPanelBox;
|
||||
// Set `Panel._originalAddToPanelBox` to `undefined`.
|
||||
// @ts-ignore
|
||||
Panel.Panel.prototype._originalAddToPanelBox = undefined;
|
||||
|
||||
// Disconnect signals.
|
||||
@ -51,7 +67,9 @@ export default class TopBarOrganizerExtension extends Extension {
|
||||
}
|
||||
this._boxOrderManager.disconnectSignals();
|
||||
|
||||
// @ts-ignore
|
||||
this._settings = null;
|
||||
// @ts-ignore
|
||||
this._boxOrderManager = null;
|
||||
}
|
||||
|
||||
@ -63,9 +81,10 @@ export default class TopBarOrganizerExtension extends Extension {
|
||||
* Overwrite `Panel._addToPanelBox` with a custom method, which simply calls
|
||||
* the original one and handles new items and orders the top bar afterwards.
|
||||
*/
|
||||
#overwritePanelAddToPanelBox() {
|
||||
#overwritePanelAddToPanelBox(): void {
|
||||
// Add the original `Panel._addToPanelBox` method as
|
||||
// `Panel._originalAddToPanelBox`.
|
||||
// @ts-ignore
|
||||
Panel.Panel.prototype._originalAddToPanelBox = Panel.Panel.prototype._addToPanelBox;
|
||||
|
||||
const handleNewItemsAndOrderTopBar = () => {
|
||||
@ -76,6 +95,7 @@ export default class TopBarOrganizerExtension extends Extension {
|
||||
Panel.Panel.prototype._addToPanelBox = function(role, indicator, position, box) {
|
||||
// Simply call the original `_addToPanelBox` and order the top bar
|
||||
// and handle new items afterwards.
|
||||
// @ts-ignore
|
||||
this._originalAddToPanelBox(role, indicator, position, box);
|
||||
handleNewItemsAndOrderTopBar();
|
||||
};
|
||||
@ -88,9 +108,9 @@ export default class TopBarOrganizerExtension extends Extension {
|
||||
/**
|
||||
* This method orders the top bar items of the specified box according to
|
||||
* the configured box orders.
|
||||
* @param {string} box - The box to order.
|
||||
* @param {Box} box - The box to order.
|
||||
*/
|
||||
#orderTopBarItems(box) {
|
||||
#orderTopBarItems(box: Box): void {
|
||||
// Only run, when the session mode is "user" or the parent session mode
|
||||
// is "user".
|
||||
if(Main.sessionMode.currentMode !== "user" && Main.sessionMode.parentMode !== "user") {
|
||||
@ -104,13 +124,13 @@ export default class TopBarOrganizerExtension extends Extension {
|
||||
let panelBox;
|
||||
switch (box) {
|
||||
case "left":
|
||||
panelBox = Main.panel._leftBox;
|
||||
panelBox = (Main.panel as CustomPanel)._leftBox;
|
||||
break;
|
||||
case "center":
|
||||
panelBox = Main.panel._centerBox;
|
||||
panelBox = (Main.panel as CustomPanel)._centerBox;
|
||||
break;
|
||||
case "right":
|
||||
panelBox = Main.panel._rightBox;
|
||||
panelBox = (Main.panel as CustomPanel)._rightBox;
|
||||
break;
|
||||
}
|
||||
|
||||
@ -119,12 +139,19 @@ export default class TopBarOrganizerExtension extends Extension {
|
||||
for (let i = 0; i < validBoxOrder.length; i++) {
|
||||
const item = validBoxOrder[i];
|
||||
// Get the indicator container associated with the current role.
|
||||
const associatedIndicatorContainer = Main.panel.statusArea[item.role].container;
|
||||
const associatedIndicatorContainer = (Main.panel.statusArea as any)[item.role]?.container;
|
||||
if (!(associatedIndicatorContainer instanceof St.Bin)) {
|
||||
// TODO: maybe add logging
|
||||
continue;
|
||||
}
|
||||
|
||||
// Save whether or not the indicator container is visible.
|
||||
const isVisible = associatedIndicatorContainer.visible;
|
||||
|
||||
associatedIndicatorContainer.get_parent().remove_child(associatedIndicatorContainer);
|
||||
const parent = associatedIndicatorContainer.get_parent();
|
||||
if (parent !== null) {
|
||||
parent.remove_child(associatedIndicatorContainer);
|
||||
}
|
||||
if (box === "right") {
|
||||
// If the target panel box is the right panel box, insert the
|
||||
// indicator container at index `-1`, which just adds it to the
|
||||
@ -165,7 +192,7 @@ export default class TopBarOrganizerExtension extends Extension {
|
||||
* This method handles all new items currently present in the top bar and
|
||||
* orders the items of all top bar boxes.
|
||||
*/
|
||||
#handleNewItemsAndOrderTopBar() {
|
||||
#handleNewItemsAndOrderTopBar(): void {
|
||||
// Only run, when the session mode is "user" or the parent session mode
|
||||
// is "user".
|
||||
if(Main.sessionMode.currentMode !== "user" && Main.sessionMode.parentMode !== "user") {
|
||||
@ -1,18 +1,24 @@
|
||||
"use strict";
|
||||
|
||||
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 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
|
||||
* 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.
|
||||
@ -30,12 +36,15 @@ export default class BoxOrderManager extends GObject.Object {
|
||||
}, this);
|
||||
}
|
||||
|
||||
#appIndicatorReadyHandlerIdMap;
|
||||
#appIndicatorItemSettingsIdToRolesMap;
|
||||
#taskUpUltraLiteItemRoles;
|
||||
#settings;
|
||||
// Can't have type guarantees here, since this is working with types from
|
||||
// the KStatusNotifier/AppIndicator extension.
|
||||
#appIndicatorReadyHandlerIdMap: Map<any, any>;
|
||||
#appIndicatorItemSettingsIdToRolesMap: Map<string, string[]>;
|
||||
#taskUpUltraLiteItemRoles: string[];
|
||||
#settings: Gio.Settings;
|
||||
|
||||
constructor(params = {}, settings) {
|
||||
constructor(params = {}, settings: Gio.Settings) {
|
||||
// @ts-ignore Params should be passed, see: https://gjs.guide/guides/gobject/subclassing.html#subclassing-gobject
|
||||
super(params);
|
||||
|
||||
this.#appIndicatorReadyHandlerIdMap = new Map();
|
||||
@ -47,30 +56,22 @@ export default class BoxOrderManager extends GObject.Object {
|
||||
|
||||
/**
|
||||
* Gets a box order for the given top bar box from settings.
|
||||
* @param {string} box - The top bar box for which to get the box order.
|
||||
* Must be one of the following values:
|
||||
* - "left"
|
||||
* - "center"
|
||||
* - "right"
|
||||
* @param {Box} box - The top bar box for which to get the box order.
|
||||
* @returns {string[]} - The box order consisting of an array of item
|
||||
* settings identifiers.
|
||||
*/
|
||||
#getBoxOrder(box) {
|
||||
#getBoxOrder(box: Box): string[] {
|
||||
return this.#settings.get_strv(`${box}-box-order`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the given box order to settings, making sure to only save a changed
|
||||
* box order, to avoid loops when listening on settings changes.
|
||||
* @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 {Box} box - The top bar box for which to save the box order.
|
||||
* @param {string[]} boxOrder - The box order to save. Must be an array of
|
||||
* item settings identifiers.
|
||||
*/
|
||||
#saveBoxOrder(box, boxOrder) {
|
||||
#saveBoxOrder(box: Box, boxOrder: string[]): void {
|
||||
const currentBoxOrder = this.#getBoxOrder(box);
|
||||
|
||||
// Only save the given box order to settings, if it is different, to
|
||||
@ -90,14 +91,18 @@ export default class BoxOrderManager extends GObject.Object {
|
||||
* then also makes sure that once the app indicators "ready" signal emits,
|
||||
* this classes "appIndicatorReady" signal emits as well, such that it and
|
||||
* other methods can be called again to properly handle the item.
|
||||
* @param {string} indicatorContainer - The container of the indicator of the
|
||||
* @param {St.Bin} indicatorContainer - The container of the indicator of the
|
||||
* AppIndicator/KStatusNotifierItem item.
|
||||
* @param {string} role - The role of the AppIndicator/KStatusNotifierItem
|
||||
* item.
|
||||
* @returns {string} The derived items settings identifier.
|
||||
*/
|
||||
#handleAppIndicatorItem(indicatorContainer, role) {
|
||||
const appIndicator = indicatorContainer.get_child()._indicator;
|
||||
#handleAppIndicatorItem(indicatorContainer: St.Bin, role: string): string {
|
||||
// Since this is working with types from the
|
||||
// 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;
|
||||
|
||||
if (!application && this.#appIndicatorReadyHandlerIdMap) {
|
||||
@ -146,7 +151,7 @@ export default class BoxOrderManager extends GObject.Object {
|
||||
* @param {string} role - The role of the Task Up UltraLite item.
|
||||
* @returns {string} The settings identifier to use.
|
||||
*/
|
||||
#handleTaskUpUltraLiteItem(role) {
|
||||
#handleTaskUpUltraLiteItem(role: string): string {
|
||||
const roles = this.#taskUpUltraLiteItemRoles;
|
||||
|
||||
if (!roles.includes(role)) {
|
||||
@ -162,14 +167,10 @@ export default class BoxOrderManager extends GObject.Object {
|
||||
* meaning they might be present multiple times or not at all depending on
|
||||
* the roles stored.
|
||||
* The items of the box order also have additional information stored.
|
||||
* @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"
|
||||
* @param {Box} box - The top bar box for which to get the resolved box order.
|
||||
* @returns {ResolvedBoxOrderItem[]} - The resolved box order.
|
||||
*/
|
||||
#getResolvedBoxOrder(box) {
|
||||
#getResolvedBoxOrder(box: Box): ResolvedBoxOrderItem[] {
|
||||
let boxOrder = this.#getBoxOrder(box);
|
||||
|
||||
const itemsToHide = this.#settings.get_strv("hide");
|
||||
@ -207,15 +208,15 @@ export default class BoxOrderManager extends GObject.Object {
|
||||
// the item specially.
|
||||
|
||||
// Get the roles associated with the items settings id.
|
||||
let roles = [];
|
||||
let roles: string[] = [];
|
||||
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") {
|
||||
roles = this.#taskUpUltraLiteItemRoles;
|
||||
}
|
||||
|
||||
// If there are no roles associated, continue.
|
||||
if (!roles) {
|
||||
if (roles.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -236,12 +237,13 @@ export default class BoxOrderManager extends GObject.Object {
|
||||
* This is typically used before nulling an instance of this class to make
|
||||
* sure all signals are disconnected.
|
||||
*/
|
||||
disconnectSignals() {
|
||||
disconnectSignals(): void {
|
||||
for (const [handlerId, appIndicator] of this.#appIndicatorReadyHandlerIdMap) {
|
||||
if (handlerId && appIndicator?.signalHandlerIsConnected(handlerId)) {
|
||||
appIndicator.disconnect(handlerId);
|
||||
}
|
||||
}
|
||||
// @ts-ignore
|
||||
this.#appIndicatorReadyHandlerIdMap = null;
|
||||
}
|
||||
|
||||
@ -250,25 +252,23 @@ export default class BoxOrderManager extends GObject.Object {
|
||||
* and Task Up UltraLite items got resolved and where only items are
|
||||
* included, which are in some GNOME Shell top bar box.
|
||||
* The items of the box order also have additional information stored.
|
||||
* @param {string} box - The top bar box to return the valid box order for.
|
||||
* Must be one of the following values:
|
||||
* - "left"
|
||||
* - "center"
|
||||
* - "right"
|
||||
* @param {Box} box - The top bar box to return the valid box order for.
|
||||
* @returns {ResolvedBoxOrderItem[]} - The valid box order.
|
||||
*/
|
||||
getValidBoxOrder(box) {
|
||||
getValidBoxOrder(box: Box): ResolvedBoxOrderItem[] {
|
||||
// Get a resolved box order.
|
||||
let resolvedBoxOrder = this.#getResolvedBoxOrder(box);
|
||||
|
||||
// ToDo: simplify.
|
||||
// Get the indicator containers (of the items) currently present in the
|
||||
// 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 = [
|
||||
Main.panel._leftBox.get_children(),
|
||||
Main.panel._centerBox.get_children(),
|
||||
Main.panel._rightBox.get_children(),
|
||||
].flat();
|
||||
(Main.panel as CustomPanel)._leftBox.get_children(),
|
||||
(Main.panel as CustomPanel)._centerBox.get_children(),
|
||||
(Main.panel as CustomPanel)._rightBox.get_children(),
|
||||
].flat().filter(ic => ic instanceof St.Bin);
|
||||
|
||||
// Create an indicator containers set from the indicator containers for
|
||||
// fast easy access.
|
||||
@ -277,10 +277,13 @@ export default class BoxOrderManager extends GObject.Object {
|
||||
// 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
|
||||
// top bar.
|
||||
let validBoxOrder = [];
|
||||
let validBoxOrder: ResolvedBoxOrderItem[] = [];
|
||||
for (const item of resolvedBoxOrder) {
|
||||
// Get the indicator container associated with the items role.
|
||||
const associatedIndicatorContainer = Main.panel.statusArea[item.role]?.container;
|
||||
const associatedIndicatorContainer = (Main.panel.statusArea as any)[item.role]?.container;
|
||||
if (!(associatedIndicatorContainer instanceof St.Bin)) {
|
||||
// TODO: maybe add logging
|
||||
continue;
|
||||
}
|
||||
|
||||
if (indicatorContainerSet.has(associatedIndicatorContainer)) {
|
||||
validBoxOrder.push(item);
|
||||
@ -294,7 +297,7 @@ export default class BoxOrderManager extends GObject.Object {
|
||||
* This method saves all new items currently present in the GNOME Shell top
|
||||
* bar to the settings.
|
||||
*/
|
||||
saveNewTopBarItems() {
|
||||
saveNewTopBarItems(): void {
|
||||
// Only run, when the session mode is "user" or the parent session mode
|
||||
// is "user".
|
||||
if (Main.sessionMode.currentMode !== "user" && Main.sessionMode.parentMode !== "user") {
|
||||
@ -310,24 +313,31 @@ export default class BoxOrderManager extends GObject.Object {
|
||||
|
||||
// Get roles (of items) currently present in the GNOME Shell top bar and
|
||||
// index them using their associated indicator container.
|
||||
let indicatorContainerRoleMap = new Map();
|
||||
for (const role in Main.panel.statusArea) {
|
||||
indicatorContainerRoleMap.set(Main.panel.statusArea[role].container, role);
|
||||
let indicatorContainerRoleMap = new Map<St.Bin, string>();
|
||||
for (const role in (Main.panel.statusArea as any)) {
|
||||
const associatedIndicatorContainer = (Main.panel.statusArea as any)[role]?.container;
|
||||
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
|
||||
// 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 = {
|
||||
left: Main.panel._leftBox.get_children(),
|
||||
center: Main.panel._centerBox.get_children(),
|
||||
left: (Main.panel as CustomPanel)._leftBox.get_children().filter(ic => ic instanceof St.Bin),
|
||||
center: (Main.panel as CustomPanel)._centerBox.get_children().filter(ic => ic instanceof St.Bin),
|
||||
// Reverse this array, since the items in the left and center box
|
||||
// are logically LTR, while the items in the right box are RTL.
|
||||
right: Main.panel._rightBox.get_children().reverse(),
|
||||
right: (Main.panel as CustomPanel)._rightBox.get_children().filter(ic => ic instanceof St.Bin).reverse(),
|
||||
};
|
||||
|
||||
// This function goes through the indicator containers of the given box
|
||||
// and adds new item settings identifiers to the given box order.
|
||||
const addNewItemSettingsIdsToBoxOrder = (indicatorContainers, boxOrder, box) => {
|
||||
const addNewItemSettingsIdsToBoxOrder = (indicatorContainers: St.Bin[], boxOrder: string[], box: Box) => {
|
||||
for (const indicatorContainer of indicatorContainers) {
|
||||
// First get the role associated with the current indicator
|
||||
// container.
|
||||
@ -345,6 +355,9 @@ export default class BoxOrderManager extends GObject.Object {
|
||||
try {
|
||||
itemSettingsId = this.#handleAppIndicatorItem(indicatorContainer, role);
|
||||
} catch (e) {
|
||||
if (!(e instanceof Error)) {
|
||||
throw(e);
|
||||
}
|
||||
if (e.message !== "Application can't be determined.") {
|
||||
throw(e);
|
||||
}
|
||||
@ -13,7 +13,7 @@ export default class TopBarOrganizerPreferences extends ExtensionPreferences {
|
||||
provider.load_from_path(this.metadata.dir.get_path() + "/css/prefs.css");
|
||||
const defaultGdkDisplay = Gdk.Display.get_default();
|
||||
Gtk.StyleContext.add_provider_for_display(
|
||||
defaultGdkDisplay,
|
||||
(defaultGdkDisplay as Gdk.Display),
|
||||
provider,
|
||||
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
|
||||
);
|
||||
@ -22,7 +22,7 @@ export default class TopBarOrganizerPreferences extends ExtensionPreferences {
|
||||
|
||||
prefsPage.connect("destroy", () => {
|
||||
Gtk.StyleContext.remove_provider_for_display(
|
||||
defaultGdkDisplay,
|
||||
(defaultGdkDisplay as Gdk.Display),
|
||||
provider
|
||||
);
|
||||
});
|
||||
@ -6,6 +6,8 @@ import GObject from "gi://GObject";
|
||||
import Adw from "gi://Adw";
|
||||
import GLib from "gi://GLib";
|
||||
|
||||
import type PrefsBoxOrderListBox from "./PrefsBoxOrderListBox.js";
|
||||
|
||||
export default class PrefsBoxOrderItemRow extends Adw.ActionRow {
|
||||
static {
|
||||
GObject.registerClass({
|
||||
@ -18,8 +20,8 @@ export default class PrefsBoxOrderItemRow extends Adw.ActionRow {
|
||||
},
|
||||
}, this);
|
||||
this.install_action("row.forget", null, (self, _actionName, _param) => {
|
||||
const parentListBox = self.get_parent();
|
||||
parentListBox.removeRow(self);
|
||||
const parentListBox = self.get_parent() as PrefsBoxOrderListBox;
|
||||
parentListBox.removeRow(self as PrefsBoxOrderItemRow);
|
||||
parentListBox.saveBoxOrderToSettings();
|
||||
parentListBox.determineRowMoveActionEnable();
|
||||
});
|
||||
@ -27,39 +29,32 @@ export default class PrefsBoxOrderItemRow extends Adw.ActionRow {
|
||||
this.install_action("row.move-down", null, (self, _actionName, _param) => self.emit("move", "down"));
|
||||
}
|
||||
|
||||
#drag_starting_point_x;
|
||||
#drag_starting_point_y;
|
||||
item: string;
|
||||
#drag_starting_point_x?: number;
|
||||
#drag_starting_point_y?: number;
|
||||
|
||||
constructor(params = {}, item) {
|
||||
constructor(params = {}, item: string) {
|
||||
super(params);
|
||||
|
||||
this.#associateItem(item);
|
||||
}
|
||||
|
||||
/**
|
||||
* Associate `this` with an item.
|
||||
* @param {String} item
|
||||
*/
|
||||
#associateItem(item) {
|
||||
// Associate `this` with an item.
|
||||
this.item = item;
|
||||
|
||||
if (item.startsWith("appindicator-kstatusnotifieritem-")) {
|
||||
if (this.item.startsWith("appindicator-kstatusnotifieritem-")) {
|
||||
// Set the title to something nicer, if the associated item is an
|
||||
// AppIndicator/KStatusNotifierItem item.
|
||||
this.set_title(item.replace("appindicator-kstatusnotifieritem-", ""));
|
||||
} else if (item === "item-role-group-task-up-ultralite") {
|
||||
this.set_title(this.item.replace("appindicator-kstatusnotifieritem-", ""));
|
||||
} else if (this.item === "item-role-group-task-up-ultralite") {
|
||||
// Set the title to something nicer, if the item in question is the
|
||||
// Task Up UltraLite item role group.
|
||||
this.set_title("Task Up UltraLite Items");
|
||||
} else {
|
||||
// Otherwise just set it to `item`.
|
||||
this.set_title(item);
|
||||
this.set_title(this.item);
|
||||
}
|
||||
}
|
||||
|
||||
onDragPrepare(_source, x, y) {
|
||||
onDragPrepare(_source: Gtk.DragSource, x: number, y: number): Gdk.ContentProvider {
|
||||
const value = new GObject.Value();
|
||||
value.init(PrefsBoxOrderItemRow);
|
||||
value.init(PrefsBoxOrderItemRow.$gtype);
|
||||
value.set_object(this);
|
||||
|
||||
this.#drag_starting_point_x = x;
|
||||
@ -67,7 +62,7 @@ export default class PrefsBoxOrderItemRow extends Adw.ActionRow {
|
||||
return Gdk.ContentProvider.new_for_value(value);
|
||||
}
|
||||
|
||||
onDragBegin(_source, drag) {
|
||||
onDragBegin(_source: Gtk.DragSource, drag: Gdk.Drag): void {
|
||||
let dragWidget = new Gtk.ListBox();
|
||||
let allocation = this.get_allocation();
|
||||
dragWidget.set_size_request(allocation.width, allocation.height);
|
||||
@ -78,20 +73,32 @@ export default class PrefsBoxOrderItemRow extends Adw.ActionRow {
|
||||
|
||||
let currentDragIcon = Gtk.DragIcon.get_for_drag(drag);
|
||||
currentDragIcon.set_child(dragWidget);
|
||||
// Even tho this should always be the case, ensure the values for the hotspot aren't undefined.
|
||||
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.
|
||||
// `value` is the thing getting dropped.
|
||||
onDrop(_target, value, _x, _y) {
|
||||
onDrop(_target: Gtk.DropTarget, value: any, _x: number, _y: number): boolean {
|
||||
// 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 (value === this) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the GtkListBoxes of `this` and the drop value.
|
||||
const ownListBox = this.get_parent();
|
||||
const valueListBox = value.get_parent();
|
||||
const ownListBox = this.get_parent() as PrefsBoxOrderListBox;
|
||||
const valueListBox = value.get_parent() as PrefsBoxOrderListBox;
|
||||
|
||||
// Get the position of `this` and the drop value.
|
||||
const ownPosition = this.get_index();
|
||||
@ -138,5 +145,7 @@ export default class PrefsBoxOrderItemRow extends Adw.ActionRow {
|
||||
valueListBox.saveBoxOrderToSettings();
|
||||
valueListBox.determineRowMoveActionEnable();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -3,6 +3,7 @@
|
||||
import Gtk from "gi://Gtk";
|
||||
import GObject from "gi://GObject";
|
||||
import GLib from "gi://GLib";
|
||||
import type Gio from "gi://Gio";
|
||||
|
||||
import { ExtensionPreferences } from "resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js";
|
||||
|
||||
@ -25,14 +26,15 @@ export default class PrefsBoxOrderListBox extends Gtk.ListBox {
|
||||
},
|
||||
Signals: {
|
||||
"row-move": {
|
||||
param_types: [PrefsBoxOrderItemRow, GObject.TYPE_STRING],
|
||||
param_types: [PrefsBoxOrderItemRow.$gtype, GObject.TYPE_STRING],
|
||||
},
|
||||
},
|
||||
}, this);
|
||||
}
|
||||
|
||||
#settings;
|
||||
#rowSignalHandlerIds = new Map();
|
||||
_boxOrder!: string;
|
||||
#settings: Gio.Settings;
|
||||
#rowSignalHandlerIds = new Map<PrefsBoxOrderItemRow, number[]>();
|
||||
|
||||
/**
|
||||
* @param {Object} params
|
||||
@ -41,18 +43,18 @@ export default class PrefsBoxOrderListBox extends Gtk.ListBox {
|
||||
super(params);
|
||||
|
||||
// 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
|
||||
// present.
|
||||
this.set_placeholder(new PrefsBoxOrderListEmptyPlaceholder());
|
||||
}
|
||||
|
||||
get boxOrder() {
|
||||
get boxOrder(): string {
|
||||
return this._boxOrder;
|
||||
}
|
||||
|
||||
set boxOrder(value) {
|
||||
set boxOrder(value: string) {
|
||||
this._boxOrder = value;
|
||||
|
||||
// Get the actual box order for the given box order name from settings.
|
||||
@ -73,10 +75,10 @@ export default class PrefsBoxOrderListBox extends Gtk.ListBox {
|
||||
* position.
|
||||
* Also handles stuff like connecting signals.
|
||||
*/
|
||||
insertRow(row, position) {
|
||||
insertRow(row: PrefsBoxOrderItemRow, position: number): void {
|
||||
this.insert(row, position);
|
||||
|
||||
const signalHandlerIds = [];
|
||||
const signalHandlerIds: number[] = [];
|
||||
signalHandlerIds.push(row.connect("move", (row, direction) => {
|
||||
this.emit("row-move", row, direction);
|
||||
}));
|
||||
@ -88,8 +90,8 @@ export default class PrefsBoxOrderListBox extends Gtk.ListBox {
|
||||
* Removes the given PrefsBoxOrderItemRow from this list box.
|
||||
* Also handles stuff like disconnecting signals.
|
||||
*/
|
||||
removeRow(row) {
|
||||
const signalHandlerIds = this.#rowSignalHandlerIds.get(row);
|
||||
removeRow(row: PrefsBoxOrderItemRow): void {
|
||||
const signalHandlerIds = this.#rowSignalHandlerIds.get(row) ?? [];
|
||||
|
||||
for (const id of signalHandlerIds) {
|
||||
row.disconnect(id);
|
||||
@ -102,11 +104,11 @@ export default class PrefsBoxOrderListBox extends Gtk.ListBox {
|
||||
* Saves the box order represented by `this` (and its
|
||||
* `PrefsBoxOrderItemRows`) to settings.
|
||||
*/
|
||||
saveBoxOrderToSettings() {
|
||||
let currentBoxOrder = [];
|
||||
saveBoxOrderToSettings(): void {
|
||||
let currentBoxOrder: string[] = [];
|
||||
for (let potentialPrefsBoxOrderItemRow of this) {
|
||||
// Only process PrefsBoxOrderItemRows.
|
||||
if (potentialPrefsBoxOrderItemRow.constructor.$gtype.name !== "PrefsBoxOrderItemRow") {
|
||||
if (!(potentialPrefsBoxOrderItemRow instanceof PrefsBoxOrderItemRow)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -120,10 +122,10 @@ export default class PrefsBoxOrderListBox extends Gtk.ListBox {
|
||||
* Determines whether or not each move action of each PrefsBoxOrderItemRow
|
||||
* should be enabled or disabled.
|
||||
*/
|
||||
determineRowMoveActionEnable() {
|
||||
determineRowMoveActionEnable(): void {
|
||||
for (let potentialPrefsBoxOrderItemRow of this) {
|
||||
// Only process PrefsBoxOrderItemRows.
|
||||
if (potentialPrefsBoxOrderItemRow.constructor.$gtype.name !== "PrefsBoxOrderItemRow") {
|
||||
if (!(potentialPrefsBoxOrderItemRow instanceof PrefsBoxOrderItemRow)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -4,6 +4,9 @@ import Gtk from "gi://Gtk";
|
||||
import GObject from "gi://GObject";
|
||||
import GLib from "gi://GLib";
|
||||
|
||||
import PrefsBoxOrderItemRow from "./PrefsBoxOrderItemRow.js";
|
||||
import type PrefsBoxOrderListBox from "./PrefsBoxOrderListBox.js";
|
||||
|
||||
export default class PrefsBoxOrderListEmptyPlaceholder extends Gtk.Box {
|
||||
static {
|
||||
GObject.registerClass({
|
||||
@ -14,10 +17,18 @@ export default class PrefsBoxOrderListEmptyPlaceholder extends Gtk.Box {
|
||||
|
||||
// Handle a new drop on `this` properly.
|
||||
// `value` is the thing getting dropped.
|
||||
onDrop(_target, value, _x, _y) {
|
||||
onDrop(_target: Gtk.DropTarget, value: any, _x: number, _y: number): boolean {
|
||||
// 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.
|
||||
const ownListBox = this.get_parent();
|
||||
const valueListBox = value.get_parent();
|
||||
const ownListBox = this.get_parent() as PrefsBoxOrderListBox;
|
||||
const valueListBox = value.get_parent() as PrefsBoxOrderListBox;
|
||||
|
||||
// Remove the drop value from its list box.
|
||||
valueListBox.removeRow(value);
|
||||
@ -31,5 +42,7 @@ export default class PrefsBoxOrderListEmptyPlaceholder extends Gtk.Box {
|
||||
ownListBox.determineRowMoveActionEnable();
|
||||
valueListBox.saveBoxOrderToSettings();
|
||||
valueListBox.determineRowMoveActionEnable();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
"use strict";
|
||||
|
||||
import Gdk from "gi://Gdk";
|
||||
import Gtk from "gi://Gtk";
|
||||
import GObject from "gi://GObject";
|
||||
import Adw from "gi://Adw";
|
||||
@ -7,6 +8,7 @@ import GLib from "gi://GLib";
|
||||
|
||||
import ScrollManager from "./ScrollManager.js";
|
||||
import PrefsBoxOrderListEmptyPlaceholder from "./PrefsBoxOrderListEmptyPlaceholder.js";
|
||||
import type PrefsBoxOrderItemRow from "./PrefsBoxOrderItemRow.js";
|
||||
|
||||
// Imports to make UI file work.
|
||||
// eslint-disable-next-line
|
||||
@ -25,6 +27,11 @@ export default class PrefsPage extends Adw.PreferencesPage {
|
||||
}, this);
|
||||
}
|
||||
|
||||
_dndEnded?: boolean;
|
||||
_left_box_order_list_box!: PrefsBoxOrderListBox;
|
||||
_center_box_order_list_box!: PrefsBoxOrderListBox;
|
||||
_right_box_order_list_box!: PrefsBoxOrderListBox;
|
||||
|
||||
constructor(params = {}) {
|
||||
super(params);
|
||||
|
||||
@ -37,23 +44,27 @@ export default class PrefsPage extends Adw.PreferencesPage {
|
||||
* operation is in progress and the user has their cursor either in the
|
||||
* upper or lower 10% of this widget respectively.
|
||||
*/
|
||||
#setupDNDScroll() {
|
||||
#setupDNDScroll(): void {
|
||||
// Pass `this.get_first_child()` to the ScrollManager, since this
|
||||
// `PrefsPage` extends an `Adw.PreferencesPage` and the first child of
|
||||
// an `Adw.PreferencesPage` is the built-in `Gtk.ScrolledWindow`.
|
||||
const scrollManager = new ScrollManager(this.get_first_child());
|
||||
const scrollManager = new ScrollManager(this.get_first_child() as Gtk.ScrolledWindow);
|
||||
|
||||
/// Setup GtkDropControllerMotion event controller and make use of its
|
||||
/// events.
|
||||
let controller = new Gtk.DropControllerMotion();
|
||||
|
||||
// Scroll, when the pointer is in the right places.
|
||||
// Make sure scrolling stops, when DND operation ends.
|
||||
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) => {
|
||||
if (y <= this.get_allocated_height() * 0.1) {
|
||||
if ((y <= this.get_allocated_height() * 0.1) && !this._dndEnded) {
|
||||
// If the pointer is currently in the upper ten percent of this
|
||||
// widget, then scroll up.
|
||||
scrollManager.startScrollUp();
|
||||
} else if (y >= this.get_allocated_height() * 0.9) {
|
||||
} else if ((y >= this.get_allocated_height() * 0.9) && !this._dndEnded) {
|
||||
// If the pointer is currently in the lower ten percent of this
|
||||
// widget, then scroll down.
|
||||
scrollManager.startScrollDown();
|
||||
@ -63,11 +74,9 @@ export default class PrefsPage extends Adw.PreferencesPage {
|
||||
}
|
||||
});
|
||||
|
||||
// Make sure scrolling stops, when DND operation ends.
|
||||
this._dndEnded = true;
|
||||
const stopScrollAllAtDNDEnd = () => {
|
||||
scrollManager.stopScrollAll();
|
||||
this._dndEnded = true;
|
||||
scrollManager.stopScrollAll();
|
||||
};
|
||||
controller.connect("leave", () => {
|
||||
stopScrollAllAtDNDEnd();
|
||||
@ -76,7 +85,14 @@ export default class PrefsPage extends Adw.PreferencesPage {
|
||||
// Make use of `this._dndEnded` to setup stopScrollAtDNDEnd only
|
||||
// once per DND operation.
|
||||
if (this._dndEnded) {
|
||||
let drag = controller.get_drop().get_drag();
|
||||
const drag = controller.get_drop()?.get_drag() ?? null;
|
||||
// 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", () => {
|
||||
stopScrollAllAtDNDEnd();
|
||||
});
|
||||
@ -93,7 +109,7 @@ export default class PrefsPage extends Adw.PreferencesPage {
|
||||
this.add_controller(controller);
|
||||
}
|
||||
|
||||
onRowMove(listBox, row, direction) {
|
||||
onRowMove(listBox: PrefsBoxOrderListBox, row: PrefsBoxOrderItemRow, direction: string): void {
|
||||
const rowPosition = row.get_index();
|
||||
|
||||
if (direction === "up") { // If the direction of the move is up.
|
||||
@ -1,23 +1,21 @@
|
||||
"use strict";
|
||||
|
||||
import GLib from "gi://GLib";
|
||||
import type Gtk from "gi://Gtk";
|
||||
|
||||
export default class ScrollManager {
|
||||
#gtkScrolledWindow;
|
||||
#scrollUp;
|
||||
#scrollDown;
|
||||
#gtkScrolledWindow: Gtk.ScrolledWindow;
|
||||
#scrollUp: boolean;
|
||||
#scrollDown: boolean;
|
||||
|
||||
/**
|
||||
* @param {Gtk.ScrolledWindow} gtkScrolledWindow
|
||||
*/
|
||||
constructor(gtkScrolledWindow) {
|
||||
constructor(gtkScrolledWindow: Gtk.ScrolledWindow) {
|
||||
this.#gtkScrolledWindow = gtkScrolledWindow;
|
||||
|
||||
this.#scrollUp = false;
|
||||
this.#scrollDown = false;
|
||||
}
|
||||
|
||||
startScrollUp() {
|
||||
startScrollUp(): void {
|
||||
// If the scroll up is already started, don't do anything.
|
||||
if (this.#scrollUp) {
|
||||
return;
|
||||
@ -44,7 +42,7 @@ export default class ScrollManager {
|
||||
});
|
||||
}
|
||||
|
||||
startScrollDown() {
|
||||
startScrollDown(): void {
|
||||
// If the scroll down is already started, don't do anything.
|
||||
if (this.#scrollDown) {
|
||||
return;
|
||||
@ -74,15 +72,15 @@ export default class ScrollManager {
|
||||
});
|
||||
}
|
||||
|
||||
stopScrollUp() {
|
||||
stopScrollUp(): void {
|
||||
this.#scrollUp = false;
|
||||
}
|
||||
|
||||
stopScrollDown() {
|
||||
stopScrollDown(): void {
|
||||
this.#scrollDown = false;
|
||||
}
|
||||
|
||||
stopScrollAll() {
|
||||
stopScrollAll(): void {
|
||||
this.stopScrollUp();
|
||||
this.stopScrollDown();
|
||||
}
|
||||
22
tsconfig.json
Normal file
22
tsconfig.json
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"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"
|
||||
]
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user