6 Commits

Author SHA1 Message Date
Lucas Oskorep
bc23c93a5f remove min-ratio setting and enforcement
All checks were successful
Build and Test / build (pull_request) Successful in 24s
Build and Test / release (pull_request) Has been skipped
- Drop minRatio parameter from container.adjustBoundary; guard is now
  newLeft/newRight <= 0 (i.e. only prevent a pane from inverting)
- Remove _getMinRatio() helper and all call sites in windowManager.ts
- Remove Window Sizing preferences group and min-window-size-percent
  spin button from prefs.ts
- Remove min-window-size-percent GSettings key from schema XML
2026-02-25 11:31:40 -05:00
Lucas Oskorep
22c9851886 refactor: code review cleanup pass
All checks were successful
Build and Test / build (pull_request) Successful in 23s
Build and Test / release (pull_request) Has been skipped
- Collapse getHorizontalBounds/getVerticalBounds into _computeBounds(axis)
- Delete dead adjustBoundaryBothAxes method
- Collapse double-loop in getContainerForWindow into one pass
- Remove _findActiveContainer/_findContainerHoldingWindow duplication;
  call _findContainerForWindowAcrossMonitors directly
- Pass Gio.Settings via WindowManager constructor instead of public field
- Introduce keybindingActions() in extension.ts to unify setupKeybindings
  and refreshKeybinding; collapse 7-block bindSettings pattern into a loop
- Promote WindowWrapper.RESIZE_TOLERANCE to private static readonly
- Simplify redundant else-if in minimized handler
- Remove unused imports in monitor.ts (queueEvent, Mtk, Window alias)
- Deduplicate get_active_workspace() call in monitor.tileWindows()
- Strip redundant JSDoc and inline comments throughout
2026-02-25 11:24:13 -05:00
Lucas Oskorep
241f90299c feat: new window gets 1/n ratio, existing windows scale proportionally
All checks were successful
Build and Test / build (pull_request) Successful in 25s
Build and Test / release (pull_request) Has been skipped
Previously addWindow() reset all ratios to equal splits, losing any
custom sizing. Now _addRatioForNewWindow() gives the new window 1/n of
the space and scales the existing windows by (n-1)/n, preserving their
ratios relative to each other.

e.g. [1/3, 1/6, 1/2] + new → [0.25, 0.125, 0.375, 0.25]
2026-02-24 19:23:16 -05:00
Lucas Oskorep
7083482d5c fix: min-window-size setting now read live from Gio.Settings on every boundary adjust
Previously _minRatio was baked into WindowContainer at construction time
and never updated. Now adjustBoundary() takes minRatio as a parameter,
WindowManager._getMinRatio() reads it from Gio.Settings on every call,
and the settings reference is injected from extension.ts on enable().
2026-02-24 17:46:20 -05:00
Lucas Oskorep
99778f3ef2 feat: add reset-ratios keybinding and min-window-size setting to prefs UI 2026-02-24 17:33:30 -05:00
Lucas Oskorep
20bac71b45 feat: percentage-based container sizing with live boundary resize
- Add _splitRatios[] to WindowContainer — each child owns a fraction of
  the parent that always sums to 1.0; bounds are computed via prefix-sum
  so the last item absorbs rounding remainder (no pixel gaps)
- addWindow/removeWindow reset to equal splits; itemDragged swaps only
  items (ratios stay slot-based) so windows take the size of the slot
  they move into
- Add adjustBoundary() for live edge-drag resizing clamped to a
  configurable minimum (default 10%, schema key min-window-size-percent)
- Add reset-ratios keybinding (Ctrl+Z) that resets the active container
  to equal splits via WindowManager.resetActiveContainerRatios()
- Connect size-changed signal alongside position-changed so east/south
  edge drags (width-only changes) are detected and the adjacent window
  is repositioned live
- Replace LIFO pop() event queue with name-keyed Map so duplicate events
  collapse to the latest callback and timers never fight each other
- Remove redundant move_frame() before move_resize_frame(); fix retry
  condition to use symmetric pixel tolerance
- Add _isTiling re-entrancy guard around all tileWindows() call sites
  so compositor position-changed callbacks cannot recurse
- Remove double tileWindows() call in monitor.tileWindows() (move()
  already calls it internally)
2026-02-24 17:26:27 -05:00
12 changed files with 265 additions and 804 deletions

View File

@@ -19,15 +19,10 @@ export default class aerospike extends Extension {
} }
enable() { enable() {
try {
Logger.log("STARTING AEROSPIKE!") Logger.log("STARTING AEROSPIKE!")
this.bindSettings(); this.bindSettings();
this.setupKeybindings(); this.setupKeybindings();
this.windowManager.enable() this.windowManager.enable()
Logger.log("AEROSPIKE ENABLED SUCCESSFULLY")
} catch (e) {
Logger.error("AEROSPIKE ENABLE FAILED", e);
}
} }
disable() { disable() {
@@ -44,7 +39,6 @@ export default class aerospike extends Extension {
'print-tree': () => { this.windowManager.printTreeStructure(); }, 'print-tree': () => { this.windowManager.printTreeStructure(); },
'toggle-orientation': () => { this.windowManager.toggleActiveContainerOrientation(); }, 'toggle-orientation': () => { this.windowManager.toggleActiveContainerOrientation(); },
'reset-ratios': () => { this.windowManager.resetActiveContainerRatios(); }, 'reset-ratios': () => { this.windowManager.resetActiveContainerRatios(); },
'toggle-tabbed': () => { this.windowManager.toggleActiveContainerTabbed(); },
}; };
} }

View File

@@ -25,7 +25,7 @@
"@girs/gnome-shell": "49.1.0", "@girs/gnome-shell": "49.1.0",
"@jest/globals": "^30.0.0", "@jest/globals": "^30.0.0",
"@types/jest": "^30.0.0", "@types/jest": "^30.0.0",
"eslint": "^10.0.0", "eslint": "^9.36.0",
"eslint-plugin-jsdoc": "^62.0.0", "eslint-plugin-jsdoc": "^62.0.0",
"jest": "^30.0.0", "jest": "^30.0.0",
"ts-jest": "^29.1.2", "ts-jest": "^29.1.2",

287
pnpm-lock.yaml generated
View File

@@ -43,11 +43,11 @@ importers:
specifier: ^30.0.0 specifier: ^30.0.0
version: 30.0.0 version: 30.0.0
eslint: eslint:
specifier: ^10.0.0 specifier: ^9.36.0
version: 10.0.2 version: 9.39.2
eslint-plugin-jsdoc: eslint-plugin-jsdoc:
specifier: ^62.0.0 specifier: ^62.0.0
version: 62.4.1(eslint@10.0.2) version: 62.4.1(eslint@9.39.2)
jest: jest:
specifier: ^30.0.0 specifier: ^30.0.0
version: 30.2.0(@types/node@25.1.0) version: 30.2.0(@types/node@25.1.0)
@@ -252,25 +252,33 @@ packages:
resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==}
engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
'@eslint/config-array@0.23.2': '@eslint/config-array@0.21.1':
resolution: {integrity: sha512-YF+fE6LV4v5MGWRGj7G404/OZzGNepVF8fxk7jqmqo3lrza7a0uUcDnROGRBG1WFC1omYUS/Wp1f42i0M+3Q3A==} resolution: {integrity: sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==}
engines: {node: ^20.19.0 || ^22.13.0 || >=24} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@eslint/config-helpers@0.5.2': '@eslint/config-helpers@0.4.2':
resolution: {integrity: sha512-a5MxrdDXEvqnIq+LisyCX6tQMPF/dSJpCfBgBauY+pNZ28yCtSsTvyTYrMhaI+LK26bVyCJfJkT0u8KIj2i1dQ==} resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==}
engines: {node: ^20.19.0 || ^22.13.0 || >=24} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@eslint/core@1.1.0': '@eslint/core@0.17.0':
resolution: {integrity: sha512-/nr9K9wkr3P1EzFTdFdMoLuo1PmIxjmwvPozwoSodjNBdefGujXQUF93u1DDZpEaTuDvMsIQddsd35BwtrW9Xw==} resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==}
engines: {node: ^20.19.0 || ^22.13.0 || >=24} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@eslint/object-schema@3.0.2': '@eslint/eslintrc@3.3.3':
resolution: {integrity: sha512-HOy56KJt48Bx8KmJ+XGQNSUMT/6dZee/M54XyUyuvTvPXJmsERRvBchsUVx1UMe1WwIH49XLAczNC7V2INsuUw==} resolution: {integrity: sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==}
engines: {node: ^20.19.0 || ^22.13.0 || >=24} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@eslint/plugin-kit@0.6.0': '@eslint/js@9.39.2':
resolution: {integrity: sha512-bIZEUzOI1jkhviX2cp5vNyXQc6olzb2ohewQubuYlMXZ2Q/XjBO0x0XhGPvc9fjSIiUN0vw+0hq53BJ4eQSJKQ==} resolution: {integrity: sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==}
engines: {node: ^20.19.0 || ^22.13.0 || >=24} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@eslint/object-schema@2.1.7':
resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@eslint/plugin-kit@0.4.1':
resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@girs/accountsservice-1.0@1.0.0-4.0.0-beta.38': '@girs/accountsservice-1.0@1.0.0-4.0.0-beta.38':
resolution: {integrity: sha512-6QzytM5dztmMynF2bxN73EuNK9ArMFxkP2L8wUC7IH45zBeBOfYcqL85BFh2PmkGmqRk+Rli5EFR8dAkx3Ig5Q==} resolution: {integrity: sha512-6QzytM5dztmMynF2bxN73EuNK9ArMFxkP2L8wUC7IH45zBeBOfYcqL85BFh2PmkGmqRk+Rli5EFR8dAkx3Ig5Q==}
@@ -566,9 +574,6 @@ packages:
'@types/babel__traverse@7.28.0': '@types/babel__traverse@7.28.0':
resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==}
'@types/esrecurse@4.3.1':
resolution: {integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==}
'@types/estree@1.0.8': '@types/estree@1.0.8':
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
@@ -714,13 +719,13 @@ packages:
peerDependencies: peerDependencies:
acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
acorn@8.16.0: acorn@8.15.0:
resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==}
engines: {node: '>=0.4.0'} engines: {node: '>=0.4.0'}
hasBin: true hasBin: true
ajv@6.14.0: ajv@6.12.6:
resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==} resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
ansi-escapes@4.3.2: ansi-escapes@4.3.2:
resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==}
@@ -757,6 +762,9 @@ packages:
argparse@1.0.10: argparse@1.0.10:
resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==}
argparse@2.0.1:
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
babel-jest@30.2.0: babel-jest@30.2.0:
resolution: {integrity: sha512-0YiBEOxWqKkSQWL9nNGGEgndoeL0ZpWrbLMNL5u/Kaxrli3Eaxlt3ZtIDktEvXt4L/R9r3ODr2zKwGM/2BjxVw==} resolution: {integrity: sha512-0YiBEOxWqKkSQWL9nNGGEgndoeL0ZpWrbLMNL5u/Kaxrli3Eaxlt3ZtIDktEvXt4L/R9r3ODr2zKwGM/2BjxVw==}
engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
@@ -785,10 +793,6 @@ packages:
balanced-match@1.0.2: balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
balanced-match@4.0.4:
resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==}
engines: {node: 18 || 20 || >=22}
baseline-browser-mapping@2.9.19: baseline-browser-mapping@2.9.19:
resolution: {integrity: sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==} resolution: {integrity: sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==}
hasBin: true hasBin: true
@@ -799,10 +803,6 @@ packages:
brace-expansion@2.0.2: brace-expansion@2.0.2:
resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==}
brace-expansion@5.0.4:
resolution: {integrity: sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==}
engines: {node: 18 || 20 || >=22}
braces@3.0.3: braces@3.0.3:
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
engines: {node: '>=8'} engines: {node: '>=8'}
@@ -949,21 +949,25 @@ packages:
peerDependencies: peerDependencies:
eslint: ^7.0.0 || ^8.0.0 || ^9.0.0 eslint: ^7.0.0 || ^8.0.0 || ^9.0.0
eslint-scope@9.1.1: eslint-scope@8.4.0:
resolution: {integrity: sha512-GaUN0sWim5qc8KVErfPBWmc31LEsOkrUJbvJZV+xuL3u2phMUK4HIvXlWAakfC8W4nzlK+chPEAkYOYb5ZScIw==} resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==}
engines: {node: ^20.19.0 || ^22.13.0 || >=24} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
eslint-visitor-keys@3.4.3: eslint-visitor-keys@3.4.3:
resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
eslint-visitor-keys@5.0.1: eslint-visitor-keys@4.2.1:
resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
eslint-visitor-keys@5.0.0:
resolution: {integrity: sha512-A0XeIi7CXU7nPlfHS9loMYEKxUaONu/hTEzHTGba9Huu94Cq1hPivf+DE5erJozZOky0LfvXAyrV/tcswpLI0Q==}
engines: {node: ^20.19.0 || ^22.13.0 || >=24} engines: {node: ^20.19.0 || ^22.13.0 || >=24}
eslint@10.0.2: eslint@9.39.2:
resolution: {integrity: sha512-uYixubwmqJZH+KLVYIVKY1JQt7tysXhtj21WSvjcSmU5SVNzMus1bgLe+pAt816yQ8opKfheVVoPLqvVMGejYw==} resolution: {integrity: sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==}
engines: {node: ^20.19.0 || ^22.13.0 || >=24} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
hasBin: true hasBin: true
peerDependencies: peerDependencies:
jiti: '*' jiti: '*'
@@ -971,14 +975,14 @@ packages:
jiti: jiti:
optional: true optional: true
espree@10.4.0:
resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
espree@11.1.0: espree@11.1.0:
resolution: {integrity: sha512-WFWYhO1fV4iYkqOOvq8FbqIhr2pYfoDY0kCotMkDeNtGpiGGkZ1iov2u8ydjtgM8yF8rzK7oaTbw2NAzbAbehw==} resolution: {integrity: sha512-WFWYhO1fV4iYkqOOvq8FbqIhr2pYfoDY0kCotMkDeNtGpiGGkZ1iov2u8ydjtgM8yF8rzK7oaTbw2NAzbAbehw==}
engines: {node: ^20.19.0 || ^22.13.0 || >=24} engines: {node: ^20.19.0 || ^22.13.0 || >=24}
espree@11.1.1:
resolution: {integrity: sha512-AVHPqQoZYc+RUM4/3Ly5udlZY/U4LS8pIG05jEjWM2lQMU/oaZ7qshzAl2YP1tfNmXfftH3ohurfwNAug+MnsQ==}
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
esprima@4.0.1: esprima@4.0.1:
resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==}
engines: {node: '>=4'} engines: {node: '>=4'}
@@ -1044,8 +1048,8 @@ packages:
resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==}
engines: {node: '>=16'} engines: {node: '>=16'}
flatted@3.3.4: flatted@3.3.3:
resolution: {integrity: sha512-3+mMldrTAPdta5kjX2G2J7iX4zxtnwpdA8Tr2ZSjkyPSanvbZAcy6flmtnXbEybHrDcU9641lxrMfFuUxVz9vA==} resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==}
foreground-child@3.3.1: foreground-child@3.3.1:
resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==}
@@ -1081,12 +1085,15 @@ packages:
glob@10.5.0: glob@10.5.0:
resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==}
deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
hasBin: true hasBin: true
glob@7.2.3: glob@7.2.3:
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me deprecated: Glob versions prior to v9 are no longer supported
globals@14.0.0:
resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==}
engines: {node: '>=18'}
graceful-fs@4.2.11: graceful-fs@4.2.11:
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
@@ -1114,6 +1121,10 @@ packages:
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
engines: {node: '>= 4'} engines: {node: '>= 4'}
import-fresh@3.3.1:
resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==}
engines: {node: '>=6'}
import-local@3.2.0: import-local@3.2.0:
resolution: {integrity: sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==} resolution: {integrity: sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==}
engines: {node: '>=8'} engines: {node: '>=8'}
@@ -1318,6 +1329,10 @@ packages:
resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==} resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==}
hasBin: true hasBin: true
js-yaml@4.1.1:
resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==}
hasBin: true
jsdoc-type-pratt-parser@7.1.0: jsdoc-type-pratt-parser@7.1.0:
resolution: {integrity: sha512-SX7q7XyCwzM/MEDCYz0l8GgGbJAACGFII9+WfNYr5SLEKukHWRy2Jk3iWRe7P+lpYJNs7oQ+OSei4JtKGUjd7A==} resolution: {integrity: sha512-SX7q7XyCwzM/MEDCYz0l8GgGbJAACGFII9+WfNYr5SLEKukHWRy2Jk3iWRe7P+lpYJNs7oQ+OSei4JtKGUjd7A==}
engines: {node: '>=20.0.0'} engines: {node: '>=20.0.0'}
@@ -1369,6 +1384,9 @@ packages:
lodash.memoize@4.1.2: lodash.memoize@4.1.2:
resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==}
lodash.merge@4.6.2:
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
lru-cache@10.4.3: lru-cache@10.4.3:
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
@@ -1396,15 +1414,11 @@ packages:
resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
engines: {node: '>=6'} engines: {node: '>=6'}
minimatch@10.2.4: minimatch@3.1.2:
resolution: {integrity: sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==} resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
engines: {node: 18 || 20 || >=22}
minimatch@3.1.5: minimatch@9.0.5:
resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
minimatch@9.0.9:
resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==}
engines: {node: '>=16 || 14 >=14.17'} engines: {node: '>=16 || 14 >=14.17'}
minimist@1.2.8: minimist@1.2.8:
@@ -1479,6 +1493,10 @@ packages:
package-json-from-dist@1.0.1: package-json-from-dist@1.0.1:
resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==}
parent-module@1.0.1:
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
engines: {node: '>=6'}
parse-imports-exports@0.2.4: parse-imports-exports@0.2.4:
resolution: {integrity: sha512-4s6vd6dx1AotCx/RCI2m7t7GCh5bDRUtGNvRfHSP2wbBQdMi67pPe7mtzmgwcaQ8VKK/6IB7Glfyu3qdZJPybQ==} resolution: {integrity: sha512-4s6vd6dx1AotCx/RCI2m7t7GCh5bDRUtGNvRfHSP2wbBQdMi67pPe7mtzmgwcaQ8VKK/6IB7Glfyu3qdZJPybQ==}
@@ -1554,6 +1572,10 @@ packages:
resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==}
engines: {node: '>=8'} engines: {node: '>=8'}
resolve-from@4.0.0:
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
engines: {node: '>=4'}
resolve-from@5.0.0: resolve-from@5.0.0:
resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==}
engines: {node: '>=8'} engines: {node: '>=8'}
@@ -2009,34 +2031,50 @@ snapshots:
'@es-joy/resolve.exports@1.2.0': {} '@es-joy/resolve.exports@1.2.0': {}
'@eslint-community/eslint-utils@4.9.1(eslint@10.0.2)': '@eslint-community/eslint-utils@4.9.1(eslint@9.39.2)':
dependencies: dependencies:
eslint: 10.0.2 eslint: 9.39.2
eslint-visitor-keys: 3.4.3 eslint-visitor-keys: 3.4.3
'@eslint-community/regexpp@4.12.2': {} '@eslint-community/regexpp@4.12.2': {}
'@eslint/config-array@0.23.2': '@eslint/config-array@0.21.1':
dependencies: dependencies:
'@eslint/object-schema': 3.0.2 '@eslint/object-schema': 2.1.7
debug: 4.4.3 debug: 4.4.3
minimatch: 10.2.4 minimatch: 3.1.2
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
'@eslint/config-helpers@0.5.2': '@eslint/config-helpers@0.4.2':
dependencies: dependencies:
'@eslint/core': 1.1.0 '@eslint/core': 0.17.0
'@eslint/core@1.1.0': '@eslint/core@0.17.0':
dependencies: dependencies:
'@types/json-schema': 7.0.15 '@types/json-schema': 7.0.15
'@eslint/object-schema@3.0.2': {} '@eslint/eslintrc@3.3.3':
'@eslint/plugin-kit@0.6.0':
dependencies: dependencies:
'@eslint/core': 1.1.0 ajv: 6.12.6
debug: 4.4.3
espree: 10.4.0
globals: 14.0.0
ignore: 5.3.2
import-fresh: 3.3.1
js-yaml: 4.1.1
minimatch: 3.1.2
strip-json-comments: 3.1.1
transitivePeerDependencies:
- supports-color
'@eslint/js@9.39.2': {}
'@eslint/object-schema@2.1.7': {}
'@eslint/plugin-kit@0.4.1':
dependencies:
'@eslint/core': 0.17.0
levn: 0.4.1 levn: 0.4.1
'@girs/accountsservice-1.0@1.0.0-4.0.0-beta.38': '@girs/accountsservice-1.0@1.0.0-4.0.0-beta.38':
@@ -2770,8 +2808,6 @@ snapshots:
dependencies: dependencies:
'@babel/types': 7.28.6 '@babel/types': 7.28.6
'@types/esrecurse@4.3.1': {}
'@types/estree@1.0.8': {} '@types/estree@1.0.8': {}
'@types/istanbul-lib-coverage@2.0.6': {} '@types/istanbul-lib-coverage@2.0.6': {}
@@ -2866,13 +2902,13 @@ snapshots:
'@unrs/resolver-binding-win32-x64-msvc@1.11.1': '@unrs/resolver-binding-win32-x64-msvc@1.11.1':
optional: true optional: true
acorn-jsx@5.3.2(acorn@8.16.0): acorn-jsx@5.3.2(acorn@8.15.0):
dependencies: dependencies:
acorn: 8.16.0 acorn: 8.15.0
acorn@8.16.0: {} acorn@8.15.0: {}
ajv@6.14.0: ajv@6.12.6:
dependencies: dependencies:
fast-deep-equal: 3.1.3 fast-deep-equal: 3.1.3
fast-json-stable-stringify: 2.1.0 fast-json-stable-stringify: 2.1.0
@@ -2906,6 +2942,8 @@ snapshots:
dependencies: dependencies:
sprintf-js: 1.0.3 sprintf-js: 1.0.3
argparse@2.0.1: {}
babel-jest@30.2.0(@babel/core@7.28.6): babel-jest@30.2.0(@babel/core@7.28.6):
dependencies: dependencies:
'@babel/core': 7.28.6 '@babel/core': 7.28.6
@@ -2960,8 +2998,6 @@ snapshots:
balanced-match@1.0.2: {} balanced-match@1.0.2: {}
balanced-match@4.0.4: {}
baseline-browser-mapping@2.9.19: {} baseline-browser-mapping@2.9.19: {}
brace-expansion@1.1.12: brace-expansion@1.1.12:
@@ -2973,10 +3009,6 @@ snapshots:
dependencies: dependencies:
balanced-match: 1.0.2 balanced-match: 1.0.2
brace-expansion@5.0.4:
dependencies:
balanced-match: 4.0.4
braces@3.0.3: braces@3.0.3:
dependencies: dependencies:
fill-range: 7.1.1 fill-range: 7.1.1
@@ -3078,7 +3110,7 @@ snapshots:
escape-string-regexp@4.0.0: {} escape-string-regexp@4.0.0: {}
eslint-plugin-jsdoc@62.4.1(eslint@10.0.2): eslint-plugin-jsdoc@62.4.1(eslint@9.39.2):
dependencies: dependencies:
'@es-joy/jsdoccomment': 0.83.0 '@es-joy/jsdoccomment': 0.83.0
'@es-joy/resolve.exports': 1.2.0 '@es-joy/resolve.exports': 1.2.0
@@ -3086,7 +3118,7 @@ snapshots:
comment-parser: 1.4.5 comment-parser: 1.4.5
debug: 4.4.3 debug: 4.4.3
escape-string-regexp: 4.0.0 escape-string-regexp: 4.0.0
eslint: 10.0.2 eslint: 9.39.2
espree: 11.1.0 espree: 11.1.0
esquery: 1.7.0 esquery: 1.7.0
html-entities: 2.6.0 html-entities: 2.6.0
@@ -3098,36 +3130,39 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
eslint-scope@9.1.1: eslint-scope@8.4.0:
dependencies: dependencies:
'@types/esrecurse': 4.3.1
'@types/estree': 1.0.8
esrecurse: 4.3.0 esrecurse: 4.3.0
estraverse: 5.3.0 estraverse: 5.3.0
eslint-visitor-keys@3.4.3: {} eslint-visitor-keys@3.4.3: {}
eslint-visitor-keys@5.0.1: {} eslint-visitor-keys@4.2.1: {}
eslint@10.0.2: eslint-visitor-keys@5.0.0: {}
eslint@9.39.2:
dependencies: dependencies:
'@eslint-community/eslint-utils': 4.9.1(eslint@10.0.2) '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2)
'@eslint-community/regexpp': 4.12.2 '@eslint-community/regexpp': 4.12.2
'@eslint/config-array': 0.23.2 '@eslint/config-array': 0.21.1
'@eslint/config-helpers': 0.5.2 '@eslint/config-helpers': 0.4.2
'@eslint/core': 1.1.0 '@eslint/core': 0.17.0
'@eslint/plugin-kit': 0.6.0 '@eslint/eslintrc': 3.3.3
'@eslint/js': 9.39.2
'@eslint/plugin-kit': 0.4.1
'@humanfs/node': 0.16.7 '@humanfs/node': 0.16.7
'@humanwhocodes/module-importer': 1.0.1 '@humanwhocodes/module-importer': 1.0.1
'@humanwhocodes/retry': 0.4.3 '@humanwhocodes/retry': 0.4.3
'@types/estree': 1.0.8 '@types/estree': 1.0.8
ajv: 6.14.0 ajv: 6.12.6
chalk: 4.1.2
cross-spawn: 7.0.6 cross-spawn: 7.0.6
debug: 4.4.3 debug: 4.4.3
escape-string-regexp: 4.0.0 escape-string-regexp: 4.0.0
eslint-scope: 9.1.1 eslint-scope: 8.4.0
eslint-visitor-keys: 5.0.1 eslint-visitor-keys: 4.2.1
espree: 11.1.1 espree: 10.4.0
esquery: 1.7.0 esquery: 1.7.0
esutils: 2.0.3 esutils: 2.0.3
fast-deep-equal: 3.1.3 fast-deep-equal: 3.1.3
@@ -3138,23 +3173,24 @@ snapshots:
imurmurhash: 0.1.4 imurmurhash: 0.1.4
is-glob: 4.0.3 is-glob: 4.0.3
json-stable-stringify-without-jsonify: 1.0.1 json-stable-stringify-without-jsonify: 1.0.1
minimatch: 10.2.4 lodash.merge: 4.6.2
minimatch: 3.1.2
natural-compare: 1.4.0 natural-compare: 1.4.0
optionator: 0.9.4 optionator: 0.9.4
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
espree@10.4.0:
dependencies:
acorn: 8.15.0
acorn-jsx: 5.3.2(acorn@8.15.0)
eslint-visitor-keys: 4.2.1
espree@11.1.0: espree@11.1.0:
dependencies: dependencies:
acorn: 8.16.0 acorn: 8.15.0
acorn-jsx: 5.3.2(acorn@8.16.0) acorn-jsx: 5.3.2(acorn@8.15.0)
eslint-visitor-keys: 5.0.1 eslint-visitor-keys: 5.0.0
espree@11.1.1:
dependencies:
acorn: 8.16.0
acorn-jsx: 5.3.2(acorn@8.16.0)
eslint-visitor-keys: 5.0.1
esprima@4.0.1: {} esprima@4.0.1: {}
@@ -3223,10 +3259,10 @@ snapshots:
flat-cache@4.0.1: flat-cache@4.0.1:
dependencies: dependencies:
flatted: 3.3.4 flatted: 3.3.3
keyv: 4.5.4 keyv: 4.5.4
flatted@3.3.4: {} flatted@3.3.3: {}
foreground-child@3.3.1: foreground-child@3.3.1:
dependencies: dependencies:
@@ -3254,7 +3290,7 @@ snapshots:
dependencies: dependencies:
foreground-child: 3.3.1 foreground-child: 3.3.1
jackspeak: 3.4.3 jackspeak: 3.4.3
minimatch: 9.0.9 minimatch: 9.0.5
minipass: 7.1.2 minipass: 7.1.2
package-json-from-dist: 1.0.1 package-json-from-dist: 1.0.1
path-scurry: 1.11.1 path-scurry: 1.11.1
@@ -3264,10 +3300,12 @@ snapshots:
fs.realpath: 1.0.0 fs.realpath: 1.0.0
inflight: 1.0.6 inflight: 1.0.6
inherits: 2.0.4 inherits: 2.0.4
minimatch: 3.1.5 minimatch: 3.1.2
once: 1.4.0 once: 1.4.0
path-is-absolute: 1.0.1 path-is-absolute: 1.0.1
globals@14.0.0: {}
graceful-fs@4.2.11: {} graceful-fs@4.2.11: {}
handlebars@4.7.8: handlebars@4.7.8:
@@ -3289,6 +3327,11 @@ snapshots:
ignore@5.3.2: {} ignore@5.3.2: {}
import-fresh@3.3.1:
dependencies:
parent-module: 1.0.1
resolve-from: 4.0.0
import-local@3.2.0: import-local@3.2.0:
dependencies: dependencies:
pkg-dir: 4.2.0 pkg-dir: 4.2.0
@@ -3676,6 +3719,10 @@ snapshots:
argparse: 1.0.10 argparse: 1.0.10
esprima: 4.0.1 esprima: 4.0.1
js-yaml@4.1.1:
dependencies:
argparse: 2.0.1
jsdoc-type-pratt-parser@7.1.0: {} jsdoc-type-pratt-parser@7.1.0: {}
jsesc@3.1.0: {} jsesc@3.1.0: {}
@@ -3713,6 +3760,8 @@ snapshots:
lodash.memoize@4.1.2: {} lodash.memoize@4.1.2: {}
lodash.merge@4.6.2: {}
lru-cache@10.4.3: {} lru-cache@10.4.3: {}
lru-cache@5.1.1: lru-cache@5.1.1:
@@ -3738,15 +3787,11 @@ snapshots:
mimic-fn@2.1.0: {} mimic-fn@2.1.0: {}
minimatch@10.2.4: minimatch@3.1.2:
dependencies:
brace-expansion: 5.0.4
minimatch@3.1.5:
dependencies: dependencies:
brace-expansion: 1.1.12 brace-expansion: 1.1.12
minimatch@9.0.9: minimatch@9.0.5:
dependencies: dependencies:
brace-expansion: 2.0.2 brace-expansion: 2.0.2
@@ -3811,6 +3856,10 @@ snapshots:
package-json-from-dist@1.0.1: {} package-json-from-dist@1.0.1: {}
parent-module@1.0.1:
dependencies:
callsites: 3.1.0
parse-imports-exports@0.2.4: parse-imports-exports@0.2.4:
dependencies: dependencies:
parse-statements: 1.0.11 parse-statements: 1.0.11
@@ -3869,6 +3918,8 @@ snapshots:
dependencies: dependencies:
resolve-from: 5.0.0 resolve-from: 5.0.0
resolve-from@4.0.0: {}
resolve-from@5.0.0: {} resolve-from@5.0.0: {}
semver@6.3.1: {} semver@6.3.1: {}
@@ -3956,7 +4007,7 @@ snapshots:
dependencies: dependencies:
'@istanbuljs/schema': 0.1.3 '@istanbuljs/schema': 0.1.3
glob: 7.2.3 glob: 7.2.3
minimatch: 3.1.5 minimatch: 3.1.2
tmpl@1.0.5: {} tmpl@1.0.5: {}

View File

@@ -44,7 +44,7 @@
</key> </key>
<key name="toggle-orientation" type="as"> <key name="toggle-orientation" type="as">
<default><![CDATA[['<Primary>comma']]]></default> <default><![CDATA[['<Super><Shift>comma']]]></default>
<summary>Toggle active container orientation</summary> <summary>Toggle active container orientation</summary>
<description>Toggles the orientation of the container holding the active window between horizontal and vertical</description> <description>Toggles the orientation of the container holding the active window between horizontal and vertical</description>
</key> </key>
@@ -55,11 +55,5 @@
<description>Resets all window size ratios in the active window's container to equal splits</description> <description>Resets all window size ratios in the active window's container to equal splits</description>
</key> </key>
<key name="toggle-tabbed" type="as">
<default><![CDATA[['<Primary>slash']]]></default>
<summary>Toggle tabbed container mode</summary>
<description>Toggles the active window's container between tabbed and accordion layout modes</description>
</key>
</schema> </schema>
</schemalist> </schemalist>

View File

@@ -20,38 +20,25 @@ jest.mock('../utils/events.js', () => ({
describe('Container Logic Tests', () => { describe('Container Logic Tests', () => {
describe('Orientation Toggle Logic', () => { describe('Orientation Toggle Logic', () => {
enum Layout { enum Orientation {
HORIZONTAL = 0, HORIZONTAL = 0,
VERTICAL = 1, VERTICAL = 1,
TABBED = 2,
} }
const toggleOrientation = (current: Layout): Layout => { const toggleOrientation = (current: Orientation): Orientation => {
if (current === Layout.TABBED) return Layout.HORIZONTAL; return current === Orientation.HORIZONTAL
return current === Layout.HORIZONTAL ? Orientation.VERTICAL
? Layout.VERTICAL : Orientation.HORIZONTAL;
: Layout.HORIZONTAL;
}; };
test('should toggle from HORIZONTAL to VERTICAL', () => { test('should toggle from HORIZONTAL to VERTICAL', () => {
const result = toggleOrientation(Layout.HORIZONTAL); const result = toggleOrientation(Orientation.HORIZONTAL);
expect(result).toBe(Layout.VERTICAL); expect(result).toBe(Orientation.VERTICAL);
}); });
test('should toggle from VERTICAL to HORIZONTAL', () => { test('should toggle from VERTICAL to HORIZONTAL', () => {
const result = toggleOrientation(Layout.VERTICAL); const result = toggleOrientation(Orientation.VERTICAL);
expect(result).toBe(Layout.HORIZONTAL); expect(result).toBe(Orientation.HORIZONTAL);
});
test('should toggle from TABBED to HORIZONTAL', () => {
const result = toggleOrientation(Layout.TABBED);
expect(result).toBe(Layout.HORIZONTAL);
});
test('enum reverse mapping should return string names', () => {
expect(Layout[Layout.HORIZONTAL]).toBe('HORIZONTAL');
expect(Layout[Layout.VERTICAL]).toBe('VERTICAL');
expect(Layout[Layout.TABBED]).toBe('TABBED');
}); });
}); });
@@ -113,71 +100,6 @@ describe('Container Logic Tests', () => {
}); });
}); });
describe('Tabbed Bounds Calculation', () => {
const TAB_BAR_HEIGHT = 24;
test('should give all items the same content rect in tabbed mode', () => {
const workArea = { x: 100, y: 0, width: 1000, height: 500 };
const itemCount = 3;
const contentRect = {
x: workArea.x,
y: workArea.y + TAB_BAR_HEIGHT,
width: workArea.width,
height: workArea.height - TAB_BAR_HEIGHT,
};
const bounds = Array.from({ length: itemCount }, () => contentRect);
expect(bounds.length).toBe(3);
// All bounds should be identical
bounds.forEach(b => {
expect(b.x).toBe(100);
expect(b.y).toBe(TAB_BAR_HEIGHT);
expect(b.width).toBe(1000);
expect(b.height).toBe(500 - TAB_BAR_HEIGHT);
});
});
test('tab bar rect should occupy top of work area', () => {
const workArea = { x: 200, y: 50, width: 800, height: 600 };
const tabBarRect = {
x: workArea.x,
y: workArea.y,
width: workArea.width,
height: TAB_BAR_HEIGHT,
};
expect(tabBarRect.x).toBe(200);
expect(tabBarRect.y).toBe(50);
expect(tabBarRect.width).toBe(800);
expect(tabBarRect.height).toBe(TAB_BAR_HEIGHT);
});
test('active tab index should clamp after removal', () => {
let activeTabIndex = 2;
const itemCount = 2; // after removing one from 3
if (activeTabIndex >= itemCount) {
activeTabIndex = itemCount - 1;
}
expect(activeTabIndex).toBe(1);
});
test('active tab index should stay at 0 when first item removed', () => {
let activeTabIndex = 0;
const itemCount = 2; // after removing one from 3
if (activeTabIndex >= itemCount) {
activeTabIndex = itemCount - 1;
}
expect(activeTabIndex).toBe(0);
});
});
describe('Window Index Finding', () => { describe('Window Index Finding', () => {
test('should find window index in array', () => { test('should find window index in array', () => {
const windows = [ const windows = [

View File

@@ -182,15 +182,6 @@ export default class AerospikeExtensions extends ExtensionPreferences {
}) })
); );
keybindingsGroup.add(
new EntryRow({
title: _('Toggle Tabbed Mode'),
settings: settings,
bind: 'toggle-tabbed',
map: keybindingMap
})
);
} }
// Helper function to create a keybinding mapping object // Helper function to create a keybinding mapping object

View File

@@ -2,12 +2,10 @@ import {WindowWrapper} from "./window.js";
import {Logger} from "../utils/logger.js"; import {Logger} from "../utils/logger.js";
import queueEvent from "../utils/events.js"; import queueEvent from "../utils/events.js";
import {Rect} from "../utils/rect.js"; import {Rect} from "../utils/rect.js";
import {TabBar, TAB_BAR_HEIGHT} from "./tabBar.js";
export enum Layout { enum Orientation {
ACC_HORIZONTAL = 0, HORIZONTAL = 0,
ACC_VERTICAL = 1, VERTICAL = 1,
TABBED = 2,
} }
// Returns equal ratios summing exactly to 1.0, with float drift absorbed by the last slot. // Returns equal ratios summing exactly to 1.0, with float drift absorbed by the last slot.
@@ -24,17 +22,10 @@ export default class WindowContainer {
_tiledItems: (WindowWrapper | WindowContainer)[]; _tiledItems: (WindowWrapper | WindowContainer)[];
_tiledWindowLookup: Map<number, WindowWrapper>; _tiledWindowLookup: Map<number, WindowWrapper>;
_orientation: Layout = Layout.ACC_HORIZONTAL; _orientation: Orientation = Orientation.HORIZONTAL;
_workArea: Rect; _workArea: Rect;
// -- Accordion Mode States
_splitRatios: number[]; _splitRatios: number[];
// -- Tabbed mode state -----------------------------------------------------
_activeTabIndex: number = 0;
_tabBar: TabBar | null = null;
constructor(workspaceArea: Rect) { constructor(workspaceArea: Rect) {
this._tiledItems = []; this._tiledItems = [];
this._tiledWindowLookup = new Map<number, WindowWrapper>(); this._tiledWindowLookup = new Map<number, WindowWrapper>();
@@ -42,7 +33,7 @@ export default class WindowContainer {
this._splitRatios = []; this._splitRatios = [];
} }
// --- Helpers ---------------------------------------------------------------- // ─── Helpers ────────────────────────────────────────────────────────────────
private _resetRatios(): void { private _resetRatios(): void {
this._splitRatios = equalRatios(this._tiledItems.length); this._splitRatios = equalRatios(this._tiledItems.length);
@@ -63,128 +54,33 @@ export default class WindowContainer {
} }
private _totalDimension(): number { private _totalDimension(): number {
return this._orientation === Layout.ACC_HORIZONTAL return this._orientation === Orientation.HORIZONTAL
? this._workArea.width ? this._workArea.width
: this._workArea.height; : this._workArea.height;
} }
isTabbed(): boolean { // ─── Public API ─────────────────────────────────────────────────────────────
return this._orientation === Layout.TABBED;
}
// --- Public API -------------------------------------------------------------
move(rect: Rect): void { move(rect: Rect): void {
this._workArea = rect; this._workArea = rect;
this.drawWindows(); this.tileWindows();
} }
toggleOrientation(): void { toggleOrientation(): void {
if (this._orientation === Layout.TABBED) { this._orientation = this._orientation === Orientation.HORIZONTAL
// Tabbed → Horizontal: restore accordion mode ? Orientation.VERTICAL
this.setAccordion(Layout.ACC_HORIZONTAL); : Orientation.HORIZONTAL;
} else { Logger.info(`Container orientation toggled to ${this._orientation === Orientation.HORIZONTAL ? 'HORIZONTAL' : 'VERTICAL'}`);
this._orientation = this._orientation === Layout.ACC_HORIZONTAL this.tileWindows();
? Layout.ACC_VERTICAL
: Layout.ACC_HORIZONTAL;
Logger.info(`Container orientation toggled to ${Layout[this._orientation]}`);
this.drawWindows();
}
}
/**
* Switch this container to tabbed mode.
*/
setTabbed(): void {
if (this._orientation === Layout.TABBED) return;
Logger.info("Container switching to TABBED mode");
this._orientation = Layout.TABBED;
// Clamp active tab index
if (this._activeTabIndex < 0 || this._activeTabIndex >= this._tiledItems.length) {
this._activeTabIndex = 0;
}
// Create tab bar
this._tabBar = new TabBar((index) => {
this.setActiveTab(index);
});
this.drawWindows();
}
/**
* Switch this container back to accordion (H or V) mode.
*/
setAccordion(orientation: Layout.ACC_HORIZONTAL | Layout.ACC_VERTICAL): void {
if (this._orientation !== Layout.TABBED) {
// Already accordion — just set the orientation
this._orientation = orientation;
this.drawWindows();
return;
}
Logger.info(`Container switching from TABBED to ${Layout[orientation]}`);
this._orientation = orientation;
// Destroy tab bar
if (this._tabBar) {
this._tabBar.destroy();
this._tabBar = null;
}
// Show all windows (they may have been hidden in tabbed mode)
this._showAllWindows();
this.drawWindows();
}
/**
* Set the active tab by index. Shows that window, hides others, updates tab bar.
*/
setActiveTab(index: number): void {
if (!this.isTabbed()) return;
if (index < 0 || index >= this._tiledItems.length) return;
this._activeTabIndex = index;
Logger.info(`Active tab set to ${index}`);
this._applyTabVisibility();
this._updateTabBar();
// Tile to resize the active window to the content area
this.drawWindows();
}
getActiveTabIndex(): number {
return this._activeTabIndex;
}
hideTabBar(): void {
this._tabBar?.hide();
}
showTabBar(): void {
if (this.isTabbed() && this._tabBar) {
this._tabBar.show();
}
} }
addWindow(winWrap: WindowWrapper): void { addWindow(winWrap: WindowWrapper): void {
this._tiledItems.push(winWrap); this._tiledItems.push(winWrap);
this._tiledWindowLookup.set(winWrap.getWindowId(), winWrap); this._tiledWindowLookup.set(winWrap.getWindowId(), winWrap);
this._addRatioForNewWindow(); this._addRatioForNewWindow();
if (this.isTabbed()) {
// TODO: make it so that when tabs are added they are made the current active tab
this._applyTabVisibility();
this._updateTabBar();
}
queueEvent({ queueEvent({
name: "tiling-windows", name: "tiling-windows",
callback: () => this.drawWindows(), callback: () => this.tileWindows(),
}, 100); }, 100);
} }
@@ -215,28 +111,13 @@ export default class WindowContainer {
removeWindow(win_id: number): void { removeWindow(win_id: number): void {
if (this._tiledWindowLookup.has(win_id)) { if (this._tiledWindowLookup.has(win_id)) {
// Get index before deleting from lookup to avoid race condition
const index = this._getIndexOfWindow(win_id); const index = this._getIndexOfWindow(win_id);
this._tiledWindowLookup.delete(win_id); this._tiledWindowLookup.delete(win_id);
if (index !== -1) { if (index !== -1) {
// If removing the window that was hidden in tabbed mode,
// make sure to show it first so it doesn't stay invisible
const item = this._tiledItems[index];
if (item instanceof WindowWrapper) {
item.showWindow();
}
this._tiledItems.splice(index, 1); this._tiledItems.splice(index, 1);
} }
this._resetRatios(); this._resetRatios();
if (this.isTabbed()) {
if (this._tiledItems.length === 0) {
this._activeTabIndex = 0;
} else if (this._activeTabIndex >= this._tiledItems.length) {
this._activeTabIndex = this._tiledItems.length - 1;
}
this._applyTabVisibility();
this._updateTabBar();
}
} else { } else {
for (const item of this._tiledItems) { for (const item of this._tiledItems) {
if (item instanceof WindowContainer) { if (item instanceof WindowContainer) {
@@ -244,7 +125,7 @@ export default class WindowContainer {
} }
} }
} }
this.drawWindows(); this.tileWindows();
} }
disconnectSignals(): void { disconnectSignals(): void {
@@ -258,159 +139,37 @@ export default class WindowContainer {
} }
removeAllWindows(): void { removeAllWindows(): void {
// tabbed mode hides all windows - this ensures they are available before removal
this._showAllWindows();
if (this._tabBar) {
this._tabBar.destroy();
this._tabBar = null;
}
this._tiledItems = []; this._tiledItems = [];
this._tiledWindowLookup.clear(); this._tiledWindowLookup.clear();
this._splitRatios = []; this._splitRatios = [];
this._activeTabIndex = 0;
} }
drawWindows(): void { tileWindows(): void {
Logger.log("TILING WINDOWS IN CONTAINER"); Logger.log("TILING WINDOWS IN CONTAINER");
Logger.log("WorkArea", this._workArea); Logger.log("WorkArea", this._workArea);
this._tileItems();
if (this.isTabbed()) {
this._tileTab();
} else {
this._tileAccordion();
}
} }
_tileAccordion() { _tileItems() {
if (this._tiledItems.length === 0) return; if (this._tiledItems.length === 0) return;
const bounds = this.getBounds(); const bounds = this.getBounds();
Logger.info(`_tileAccordion: ratios=[${this._splitRatios.map(r => r.toFixed(3)).join(', ')}] bounds=[${bounds.map(b => `(${b.x},${b.y},${b.width},${b.height})`).join(', ')}]`); Logger.info(`_tileItems: ratios=[${this._splitRatios.map(r => r.toFixed(3)).join(', ')}] bounds=[${bounds.map(b => `(${b.x},${b.y},${b.width},${b.height})`).join(', ')}]`);
this._tiledItems.forEach((item, index) => { this._tiledItems.forEach((item, index) => {
const rect = bounds[index]; const rect = bounds[index];
if (item instanceof WindowContainer) { if (item instanceof WindowContainer) {
item.move(rect); item.move(rect);
} else { } else {
Logger.info(`_tileAccordion: window[${index}] id=${item.getWindowId()} dragging=${item._dragging} → rect=(${rect.x},${rect.y},${rect.width},${rect.height})`); Logger.info(`_tileItems: window[${index}] id=${item.getWindowId()} dragging=${item._dragging} → rect=(${rect.x},${rect.y},${rect.width},${rect.height})`);
item.safelyResizeWindow(rect); item.safelyResizeWindow(rect);
} }
}); });
} }
private _tileTab(): void { // ─── Bounds Calculation ──────────────────────────────────────────────────────
if (this._tiledItems.length === 0) return;
const tabBarRect: Rect = {
x: this._workArea.x,
y: this._workArea.y,
width: this._workArea.width,
height: TAB_BAR_HEIGHT,
};
const contentRect: Rect = {
x: this._workArea.x,
y: this._workArea.y + TAB_BAR_HEIGHT,
width: this._workArea.width,
height: this._workArea.height - TAB_BAR_HEIGHT,
};
// Position and show the tab bar
if (this._tabBar) {
this._tabBar.setPosition(tabBarRect);
if (!this._tabBar.isVisible()) {
this._rebuildAndShowTabBar();
}
}
this._applyTabVisibility();
const activeItem = this._tiledItems[this._activeTabIndex];
if (activeItem) {
if (activeItem instanceof WindowContainer) {
activeItem.move(contentRect);
} else {
Logger.info(`_tileTabbed: active tab[${this._activeTabIndex}] id=${activeItem.getWindowId()} → rect=(${contentRect.x},${contentRect.y},${contentRect.width},${contentRect.height})`);
activeItem.safelyResizeWindow(contentRect);
}
}
}
/**
* Show the active tab window, hide all others.
*/
private _applyTabVisibility(): void {
this._tiledItems.forEach((item, index) => {
if (item instanceof WindowWrapper) {
if (index === this._activeTabIndex) {
item.showWindow();
} else {
item.hideWindow();
}
}
});
}
/**
* Show all windows (used when leaving tabbed mode).
*/
private _showAllWindows(): void {
this._tiledItems.forEach((item) => {
if (item instanceof WindowWrapper) {
item.showWindow();
}
});
}
/**
* Rebuild the tab bar buttons and show it.
*/
private _rebuildAndShowTabBar(): void {
if (!this._tabBar) return;
const windowItems = this._tiledItems.filter(
(item): item is WindowWrapper => item instanceof WindowWrapper
);
this._tabBar.rebuild(windowItems, this._activeTabIndex);
this._tabBar.show();
}
/**
* Public entry point to refresh tab titles (e.g. when a window title changes).
*/
refreshTabTitles(): void {
this._updateTabBar();
}
/**
* Update tab bar state (active highlight, titles) without a full rebuild.
*/
private _updateTabBar(): void {
if (!this._tabBar) return;
// Rebuild is cheap — just recreate buttons from the current items
const windowItems = this._tiledItems.filter(
(item): item is WindowWrapper => item instanceof WindowWrapper
);
this._tabBar.rebuild(windowItems, this._activeTabIndex);
}
getBounds(): Rect[] { getBounds(): Rect[] {
if (this._orientation === Layout.TABBED) { return this._orientation === Orientation.HORIZONTAL
// In tabbed mode, all items share the same content rect
const contentRect: Rect = {
x: this._workArea.x,
y: this._workArea.y + TAB_BAR_HEIGHT,
width: this._workArea.width,
height: this._workArea.height - TAB_BAR_HEIGHT,
};
return this._tiledItems.map(() => contentRect);
}
return this._orientation === Layout.ACC_HORIZONTAL
? this._computeBounds('horizontal') ? this._computeBounds('horizontal')
: this._computeBounds('vertical'); : this._computeBounds('vertical');
} }
@@ -428,15 +187,14 @@ export default class WindowContainer {
used += size; used += size;
return isHorizontal return isHorizontal
? {x: this._workArea.x + offset, y: this._workArea.y, width: size, height: this._workArea.height} ? { x: this._workArea.x + offset, y: this._workArea.y, width: size, height: this._workArea.height }
: {x: this._workArea.x, y: this._workArea.y + offset, width: this._workArea.width, height: size}; : { x: this._workArea.x, y: this._workArea.y + offset, width: this._workArea.width, height: size };
}); });
} }
adjustBoundary(boundaryIndex: number, deltaPixels: number): boolean { // ─── Boundary Adjustment ─────────────────────────────────────────────────────
// No boundary adjustment in tabbed mode
if (this.isTabbed()) return false;
adjustBoundary(boundaryIndex: number, deltaPixels: number): boolean {
if (boundaryIndex < 0 || boundaryIndex >= this._tiledItems.length - 1) { if (boundaryIndex < 0 || boundaryIndex >= this._tiledItems.length - 1) {
Logger.warn(`adjustBoundary: invalid boundaryIndex ${boundaryIndex}`); Logger.warn(`adjustBoundary: invalid boundaryIndex ${boundaryIndex}`);
return false; return false;
@@ -461,7 +219,7 @@ export default class WindowContainer {
return true; return true;
} }
// --- Container Lookup -------------------------------------------------------- // ─── Container Lookup ────────────────────────────────────────────────────────
getContainerForWindow(win_id: number): WindowContainer | null { getContainerForWindow(win_id: number): WindowContainer | null {
for (const item of this._tiledItems) { for (const item of this._tiledItems) {
@@ -490,12 +248,6 @@ export default class WindowContainer {
// TODO: update this to work with nested containers - all other logic should already be working // TODO: update this to work with nested containers - all other logic should already be working
itemDragged(item: WindowWrapper, x: number, y: number): void { itemDragged(item: WindowWrapper, x: number, y: number): void {
// In tabbed mode, dragging reorders tabs but doesn't change layout
if (this.isTabbed()) {
// Don't reorder during tabbed mode — tabs have a fixed visual layout
return;
}
const original_index = this.getIndexOfItemNested(item); const original_index = this.getIndexOfItemNested(item);
if (original_index === -1) { if (original_index === -1) {
@@ -514,12 +266,12 @@ export default class WindowContainer {
Logger.info(`itemDragged: swapped slots ${original_index}<->${new_index}, ratios=[${this._splitRatios.map(r => r.toFixed(3)).join(', ')}]`); Logger.info(`itemDragged: swapped slots ${original_index}<->${new_index}, ratios=[${this._splitRatios.map(r => r.toFixed(3)).join(', ')}]`);
[this._tiledItems[original_index], this._tiledItems[new_index]] = [this._tiledItems[original_index], this._tiledItems[new_index]] =
[this._tiledItems[new_index], this._tiledItems[original_index]]; [this._tiledItems[new_index], this._tiledItems[original_index]];
this.drawWindows(); this.tileWindows();
} }
} }
resetRatios(): void { resetRatios(): void {
this._resetRatios(); this._resetRatios();
this.drawWindows(); this.tileWindows();
} }
} }

View File

@@ -1,6 +1,7 @@
import {WindowWrapper} from "./window.js"; import {WindowWrapper} from "./window.js";
import {Rect} from "../utils/rect.js"; import {Rect} from "../utils/rect.js";
import {Logger} from "../utils/logger.js"; import {Logger} from "../utils/logger.js";
import Meta from "gi://Meta";
import WindowContainer from "./container.js"; import WindowContainer from "./container.js";
@@ -72,25 +73,6 @@ export default class Monitor {
this._workspaces.push(new WindowContainer(this._workArea)); this._workspaces.push(new WindowContainer(this._workArea));
} }
refreshTabTitlesForWindow(winWrap: WindowWrapper): void {
const wsId = winWrap.getWorkspace();
if (wsId >= 0 && wsId < this._workspaces.length) {
this._workspaces[wsId].refreshTabTitles();
}
}
hideTabBars(): void {
for (const container of this._workspaces) {
container.hideTabBar();
}
}
showTabBars(): void {
for (const container of this._workspaces) {
container.showTabBar();
}
}
itemDragged(item: WindowWrapper, x: number, y: number): void { itemDragged(item: WindowWrapper, x: number, y: number): void {
this._workspaces[item.getWorkspace()].itemDragged(item, x, y); this._workspaces[item.getWorkspace()].itemDragged(item, x, y);
} }

View File

@@ -1,131 +0,0 @@
import Clutter from 'gi://Clutter';
import Pango from 'gi://Pango';
import St from 'gi://St';
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
import {Logger} from "../utils/logger.js";
import {WindowWrapper} from "./window.js";
import {Rect} from "../utils/rect.js";
export const TAB_BAR_HEIGHT = 24;
type TabClickedCallback = (index: number) => void;
export class TabBar {
private _bar: St.BoxLayout;
private _buttons: St.Button[] = [];
private _activeIndex: number = 0;
private _onTabClicked: TabClickedCallback;
private _visible: boolean = false;
constructor(onTabClicked: TabClickedCallback) {
this._onTabClicked = onTabClicked;
this._bar = new St.BoxLayout({
style_class: 'aerospike-tab-bar',
vertical: false,
reactive: true,
can_focus: false,
track_hover: false,
});
// Force all tabs to equal width regardless of text length
(this._bar.layout_manager as Clutter.BoxLayout).homogeneous = true;
}
/**
* Rebuild all tab buttons from the current list of window items.
*/
rebuild(items: WindowWrapper[], activeIndex: number): void {
// Remove old buttons
this._bar.destroy_all_children();
this._buttons = [];
items.forEach((item, index) => {
const label = new St.Label({
text: item.getTabLabel(),
style_class: 'aerospike-tab-label',
y_align: Clutter.ActorAlign.CENTER,
x_align: Clutter.ActorAlign.CENTER,
x_expand: true,
});
label.clutter_text.ellipsize = Pango.EllipsizeMode.END;
const button = new St.Button({
style_class: 'aerospike-tab',
reactive: true,
can_focus: false,
track_hover: true,
x_expand: true,
child: label,
});
button.connect('clicked', () => {
this._onTabClicked(index);
});
this._bar.add_child(button);
this._buttons.push(button);
});
this.setActive(activeIndex);
}
/**
* Update just the title text of a single tab (e.g. when a window title changes).
*/
updateTabTitle(index: number, title: string): void {
if (index < 0 || index >= this._buttons.length) return;
const label = this._buttons[index].get_child() as St.Label;
if (label) label.set_text(title);
}
/**
* Highlight the active tab and dim the rest.
*/
setActive(index: number): void {
this._activeIndex = index;
this._buttons.forEach((btn, i) => {
if (i === index) {
btn.add_style_class_name('aerospike-tab-active');
} else {
btn.remove_style_class_name('aerospike-tab-active');
}
});
}
/**
* Position and size the tab bar at the given screen rect.
*/
setPosition(rect: Rect): void {
this._bar.set_position(rect.x, rect.y);
this._bar.set_size(rect.width, rect.height);
}
show(): void {
if (this._visible) return;
this._visible = true;
Main.layoutManager.uiGroup.add_child(this._bar);
this._bar.show();
Logger.log("TabBar shown");
}
hide(): void {
if (!this._visible) return;
this._visible = false;
this._bar.hide();
if (this._bar.get_parent()) {
Main.layoutManager.uiGroup.remove_child(this._bar);
}
Logger.log("TabBar hidden");
}
destroy(): void {
this.hide();
this._bar.destroy_all_children();
this._buttons = [];
this._bar.destroy();
Logger.log("TabBar destroyed");
}
isVisible(): boolean {
return this._visible;
}
}

View File

@@ -47,31 +47,6 @@ export class WindowWrapper {
return this._window.get_frame_rect(); return this._window.get_frame_rect();
} }
getTabLabel(): string {
const rawAppName = this._window.get_wm_class() ?? '';
// Strip reverse-domain prefix (e.g. "org.gnome.Nautilus" -> "Nautilus")
const lastName = rawAppName.includes('.')
? (rawAppName.split('.').pop() ?? rawAppName)
: rawAppName;
// Capitalize first letter
const appName = lastName.charAt(0).toUpperCase() + lastName.slice(1);
const title = this._window.get_title() ?? 'Untitled';
if (appName && appName.toLowerCase() !== title.toLowerCase()) {
return `${appName} | ${title}`;
}
return title;
}
hideWindow(): void {
const actor = this._window.get_compositor_private() as Clutter.Actor | null;
if (actor) actor.hide();
}
showWindow(): void {
const actor = this._window.get_compositor_private() as Clutter.Actor | null;
if (actor) actor.show();
}
startDragging(): void { startDragging(): void {
this._dragging = true; this._dragging = true;
} }
@@ -114,9 +89,6 @@ export class WindowWrapper {
this._window.connect("size-changed", () => { this._window.connect("size-changed", () => {
windowManager.handleWindowPositionChanged(this); windowManager.handleWindowPositionChanged(this);
}), }),
this._window.connect('notify::title', () => {
windowManager.handleWindowTitleChanged(this);
}),
); );
} }
@@ -147,10 +119,6 @@ export class WindowWrapper {
} }
actor.remove_all_transitions(); actor.remove_all_transitions();
// Move first to guarantee the window reaches the correct position even
// if the subsequent resize is clamped by minimum-size hints.
this._window.move_frame(true, rect.x, rect.y);
this._window.move_resize_frame(true, rect.x, rect.y, rect.width, rect.height); this._window.move_resize_frame(true, rect.x, rect.y, rect.width, rect.height);
const new_rect = this._window.get_frame_rect(); const new_rect = this._window.get_frame_rect();
@@ -161,19 +129,6 @@ export class WindowWrapper {
Math.abs(new_rect.height - rect.height) > WindowWrapper.RESIZE_TOLERANCE; Math.abs(new_rect.height - rect.height) > WindowWrapper.RESIZE_TOLERANCE;
if (_retry > 0 && mismatch) { if (_retry > 0 && mismatch) {
// If the window's actual size is larger than requested, it has a
// minimum-size constraint — retrying won't help. Just make sure
// it's at the correct position with its actual size.
const sizeConstrained =
new_rect.width > rect.width + WindowWrapper.RESIZE_TOLERANCE ||
new_rect.height > rect.height + WindowWrapper.RESIZE_TOLERANCE;
if (sizeConstrained) {
Logger.info("Window has min-size constraint, accepting actual size",
`want(${rect.x},${rect.y},${rect.width},${rect.height})`,
`actual(${new_rect.x},${new_rect.y},${new_rect.width},${new_rect.height})`);
this._window.move_frame(true, rect.x, rect.y);
} else {
Logger.warn("RESIZE MISMATCH, retrying", Logger.warn("RESIZE MISMATCH, retrying",
`want(${rect.x},${rect.y},${rect.width},${rect.height})`, `want(${rect.x},${rect.y},${rect.width},${rect.height})`,
`got(${new_rect.x},${new_rect.y},${new_rect.width},${new_rect.height})`); `got(${new_rect.x},${new_rect.y},${new_rect.width},${new_rect.height})`);
@@ -183,5 +138,4 @@ export class WindowWrapper {
}, 50); }, 50);
} }
} }
}
} }

View File

@@ -5,7 +5,7 @@ import {WindowWrapper} from './window.js';
import * as Main from "resource:///org/gnome/shell/ui/main.js"; import * as Main from "resource:///org/gnome/shell/ui/main.js";
import {Logger} from "../utils/logger.js"; import {Logger} from "../utils/logger.js";
import Monitor from "./monitor.js"; import Monitor from "./monitor.js";
import WindowContainer, {Layout} from "./container.js"; import WindowContainer from "./container.js";
import {Rect} from "../utils/rect.js"; import {Rect} from "../utils/rect.js";
@@ -22,8 +22,6 @@ export interface IWindowManager {
handleWindowPositionChanged(winWrap: WindowWrapper): void; handleWindowPositionChanged(winWrap: WindowWrapper): void;
handleWindowTitleChanged(winWrap: WindowWrapper): void;
syncActiveWindow(): number | null; syncActiveWindow(): number | null;
} }
@@ -46,7 +44,7 @@ export default class WindowManager implements IWindowManager {
_changingGrabbedMonitor: boolean = false; _changingGrabbedMonitor: boolean = false;
_showingOverview: boolean = false; _showingOverview: boolean = false;
// -- Resize-drag tracking -------------------------------------------------- // ── Resize-drag tracking ──────────────────────────────────────────────────
_isResizeDrag: boolean = false; _isResizeDrag: boolean = false;
_resizeDragWindowId: number = _UNUSED_WINDOW_ID; _resizeDragWindowId: number = _UNUSED_WINDOW_ID;
_resizeDragOp: Meta.GrabOp = Meta.GrabOp.NONE; _resizeDragOp: Meta.GrabOp = Meta.GrabOp.NONE;
@@ -105,7 +103,6 @@ export default class WindowManager implements IWindowManager {
}), }),
global.display.connect("in-fullscreen-changed", () => { global.display.connect("in-fullscreen-changed", () => {
Logger.log("IN FULL SCREEN CHANGED"); Logger.log("IN FULL SCREEN CHANGED");
this._syncFullscreenTabBars();
}), }),
); );
@@ -135,16 +132,10 @@ export default class WindowManager implements IWindowManager {
Logger.log("HIDING OVERVIEW") Logger.log("HIDING OVERVIEW")
this._showingOverview = false; this._showingOverview = false;
this._tileMonitors(); this._tileMonitors();
for (const monitor of this._monitors.values()) {
monitor.showTabBars();
}
}), }),
Main.overview.connect("showing", () => { Main.overview.connect("showing", () => {
this._showingOverview = true; this._showingOverview = true;
Logger.log("SHOWING OVERVIEW"); Logger.log("SHOWING OVERVIEW");
for (const monitor of this._monitors.values()) {
monitor.hideTabBars();
}
}), }),
]; ];
} }
@@ -338,7 +329,7 @@ export default class WindowManager implements IWindowManager {
const itemIndex = container._getIndexOfWindow(winId); const itemIndex = container._getIndexOfWindow(winId);
if (itemIndex === -1) return; if (itemIndex === -1) return;
const isHorizontal = container._orientation === Layout.ACC_HORIZONTAL; const isHorizontal = container._orientation === 0;
// E/S edge → boundary after the item; W/N edge → boundary before it. // E/S edge → boundary after the item; W/N edge → boundary before it.
let adjusted = false; let adjusted = false;
@@ -359,7 +350,7 @@ export default class WindowManager implements IWindowManager {
if (adjusted) { if (adjusted) {
this._isTiling = true; this._isTiling = true;
try { try {
container.drawWindows(); container.tileWindows();
} finally { } finally {
this._isTiling = false; this._isTiling = false;
} }
@@ -422,11 +413,6 @@ export default class WindowManager implements IWindowManager {
this._tileMonitors(); this._tileMonitors();
} }
handleWindowTitleChanged(window: WindowWrapper): void {
const mon_id = window._window.get_monitor();
this._monitors.get(mon_id)?.refreshTabTitlesForWindow(window);
}
public addWindowToMonitor(window: Meta.Window) { public addWindowToMonitor(window: Meta.Window) {
Logger.log("ADDING WINDOW TO MONITOR", window, window); Logger.log("ADDING WINDOW TO MONITOR", window, window);
var wrapper = new WindowWrapper(window, (winWrap) => this.handleWindowMinimized(winWrap)) var wrapper = new WindowWrapper(window, (winWrap) => this.handleWindowMinimized(winWrap))
@@ -442,24 +428,12 @@ export default class WindowManager implements IWindowManager {
} }
} }
private _syncFullscreenTabBars(): void {
for (const [monitorId, monitor] of this._monitors.entries()) {
if (global.display.get_monitor_in_fullscreen(monitorId)) {
monitor.hideTabBars();
} else if (!this._showingOverview) {
monitor.showTabBars();
}
}
}
_tileMonitors(): void { _tileMonitors(): void {
this._isTiling = true; this._isTiling = true;
try { try {
for (const monitor of this._monitors.values()) { for (const monitor of this._monitors.values()) {
monitor.tileWindows(); monitor.tileWindows();
} }
} catch (e) {
Logger.error("_tileMonitors FAILED", e);
} finally { } finally {
this._isTiling = false; this._isTiling = false;
} }
@@ -538,29 +512,6 @@ export default class WindowManager implements IWindowManager {
} }
} }
public toggleActiveContainerTabbed(): void {
if (this._activeWindowId === null) {
Logger.warn("No active window, cannot toggle tabbed mode");
return;
}
const container = this._findContainerForWindowAcrossMonitors(this._activeWindowId);
if (container) {
if (container.isTabbed()) {
container.setAccordion(Layout.ACC_HORIZONTAL);
} else {
// Set the active tab to the focused window
const activeIndex = container._getIndexOfWindow(this._activeWindowId);
if (activeIndex !== -1) {
container._activeTabIndex = activeIndex;
}
container.setTabbed();
}
this._tileMonitors();
} else {
Logger.warn("Could not find container for active window");
}
}
public printTreeStructure(): void { public printTreeStructure(): void {
Logger.info("=".repeat(80)); Logger.info("=".repeat(80));
Logger.info("WINDOW TREE STRUCTURE"); Logger.info("WINDOW TREE STRUCTURE");
@@ -580,11 +531,8 @@ export default class WindowManager implements IWindowManager {
monitor._workspaces.forEach((workspace, workspaceIndex) => { monitor._workspaces.forEach((workspace, workspaceIndex) => {
const isActiveWorkspace = workspaceIndex === activeWorkspaceIndex; const isActiveWorkspace = workspaceIndex === activeWorkspaceIndex;
Logger.info(` Workspace ${workspaceIndex}${isActiveWorkspace && isActiveMonitor ? ' *' : ''}:`); Logger.info(` Workspace ${workspaceIndex}${isActiveWorkspace && isActiveMonitor ? ' *' : ''}:`);
Logger.info(` Orientation: ${Layout[workspace._orientation]}`); Logger.info(` Orientation: ${workspace._orientation === 0 ? 'HORIZONTAL' : 'VERTICAL'}`);
Logger.info(` Items: ${workspace._tiledItems.length}`); Logger.info(` Items: ${workspace._tiledItems.length}`);
if (workspace.isTabbed()) {
Logger.info(` Active Tab: ${workspace._activeTabIndex}`);
}
this._printContainerTree(workspace, 4); this._printContainerTree(workspace, 4);
}); });
}); });
@@ -599,7 +547,7 @@ export default class WindowManager implements IWindowManager {
if (item instanceof WindowContainer) { if (item instanceof WindowContainer) {
const containsActive = this._activeWindowId !== null && const containsActive = this._activeWindowId !== null &&
item.getWindow(this._activeWindowId) !== undefined; item.getWindow(this._activeWindowId) !== undefined;
Logger.info(`${indent}[${index}] Container (${Layout[item._orientation]})${containsActive ? ' *' : ''}:`); Logger.info(`${indent}[${index}] Container (${item._orientation === 0 ? 'HORIZONTAL' : 'VERTICAL'})${containsActive ? ' *' : ''}:`);
Logger.info(`${indent} Items: ${item._tiledItems.length}`); Logger.info(`${indent} Items: ${item._tiledItems.length}`);
Logger.info(`${indent} Work Area: x=${item._workArea.x}, y=${item._workArea.y}, w=${item._workArea.width}, h=${item._workArea.height}`); Logger.info(`${indent} Work Area: x=${item._workArea.x}, y=${item._workArea.y}, w=${item._workArea.width}, h=${item._workArea.height}`);
this._printContainerTree(item, indentLevel + 4); this._printContainerTree(item, indentLevel + 4);

View File

@@ -1,33 +1,37 @@
.aerospike-tab-bar { /* Add your custom extension styling here */
background-color: rgba(30, 30, 30, 0.95); .active-window-border {
border-bottom: 1px solid rgba(255, 255, 255, 0.08); /*border: 2px solid rgba(191, 0, 255, 0.8);*/
spacing: 1px; /*border-radius: 3px;*/
padding: 2px 2px 0 2px;
/* border-image-source: linear-gradient(to left, #743ad5, #d53a9d);*/
/* !*border: 4px solid transparent;*!*/
/* !*border-radius: 5px;*!*/
/* !*!* Gradient border using border-image *!*/
/* border-image: linear-gradient(45deg, red, orange, yellow, green, blue, indigo, violet) 1;*/
} }
.aerospike-tab { /*.border-gradient-purple {*/
background-color: rgba(50, 50, 50, 0.8); /* border-image-source: linear-gradient(to left, #743ad5, #d53a9d);*/
border-radius: 6px 6px 0 0; /*}*/
padding: 2px 12px;
margin: 0 1px;
color: rgba(255, 255, 255, 0.5);
font-size: 11px;
font-weight: 400;
min-width: 0;
}
.aerospike-tab:hover { /*@keyframes rainbow-border {*/
background-color: rgba(70, 70, 70, 0.9); /* 0% {*/
color: rgba(255, 255, 255, 0.8); /* border-image: linear-gradient(0deg, red, orange, yellow, green, blue, indigo, violet, red) 1;*/
} /* }*/
/* 100% {*/
/* border-image: linear-gradient(360deg, red, orange, yellow, green, blue, indigo, violet, red) 1;*/
/* }*/
/*}*/
.aerospike-tab-active { /*.active-window-border {*/
background-color: rgba(80, 80, 80, 1); /* border: 4px solid transparent;*/
color: rgba(255, 255, 255, 1); /* border-radius: 5px;*/
font-weight: 500;
}
.aerospike-tab-label { /* !* Initial gradient border *!*/
font-size: 11px; /* border-image: linear-gradient(0deg, red, orange, yellow, green, blue, indigo, violet, red) 1;*/
min-width: 0;
} /* !* Apply animation *!*/
/* animation: rainbow-border 5s linear infinite;*/
/*}*/