Compare commits
8 Commits
2f55d93a03
...
renovate/e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d73ed80a68 | ||
|
|
cbaa802797 | ||
|
|
e2a1792388 | ||
|
|
656e448927 | ||
|
|
93516b31fb | ||
|
|
918c07c419 | ||
|
|
15188b9990 | ||
|
|
8f6e8582c9 |
14
extension.ts
14
extension.ts
@@ -19,10 +19,15 @@ export default class aerospike extends Extension {
|
||||
}
|
||||
|
||||
enable() {
|
||||
Logger.log("STARTING AEROSPIKE!")
|
||||
this.bindSettings();
|
||||
this.setupKeybindings();
|
||||
this.windowManager.enable()
|
||||
try {
|
||||
Logger.log("STARTING AEROSPIKE!")
|
||||
this.bindSettings();
|
||||
this.setupKeybindings();
|
||||
this.windowManager.enable()
|
||||
Logger.log("AEROSPIKE ENABLED SUCCESSFULLY")
|
||||
} catch (e) {
|
||||
Logger.error("AEROSPIKE ENABLE FAILED", e);
|
||||
}
|
||||
}
|
||||
|
||||
disable() {
|
||||
@@ -39,6 +44,7 @@ export default class aerospike extends Extension {
|
||||
'print-tree': () => { this.windowManager.printTreeStructure(); },
|
||||
'toggle-orientation': () => { this.windowManager.toggleActiveContainerOrientation(); },
|
||||
'reset-ratios': () => { this.windowManager.resetActiveContainerRatios(); },
|
||||
'toggle-tabbed': () => { this.windowManager.toggleActiveContainerTabbed(); },
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
"@girs/gnome-shell": "49.1.0",
|
||||
"@jest/globals": "^30.0.0",
|
||||
"@types/jest": "^30.0.0",
|
||||
"eslint": "^9.36.0",
|
||||
"eslint": "^10.0.0",
|
||||
"eslint-plugin-jsdoc": "^62.0.0",
|
||||
"jest": "^30.0.0",
|
||||
"ts-jest": "^29.1.2",
|
||||
|
||||
287
pnpm-lock.yaml
generated
287
pnpm-lock.yaml
generated
@@ -43,11 +43,11 @@ importers:
|
||||
specifier: ^30.0.0
|
||||
version: 30.0.0
|
||||
eslint:
|
||||
specifier: ^9.36.0
|
||||
version: 9.39.2
|
||||
specifier: ^10.0.0
|
||||
version: 10.0.2
|
||||
eslint-plugin-jsdoc:
|
||||
specifier: ^62.0.0
|
||||
version: 62.4.1(eslint@9.39.2)
|
||||
version: 62.4.1(eslint@10.0.2)
|
||||
jest:
|
||||
specifier: ^30.0.0
|
||||
version: 30.2.0(@types/node@25.1.0)
|
||||
@@ -252,33 +252,25 @@ packages:
|
||||
resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==}
|
||||
engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
|
||||
|
||||
'@eslint/config-array@0.21.1':
|
||||
resolution: {integrity: sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
'@eslint/config-array@0.23.2':
|
||||
resolution: {integrity: sha512-YF+fE6LV4v5MGWRGj7G404/OZzGNepVF8fxk7jqmqo3lrza7a0uUcDnROGRBG1WFC1omYUS/Wp1f42i0M+3Q3A==}
|
||||
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
|
||||
|
||||
'@eslint/config-helpers@0.4.2':
|
||||
resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
'@eslint/config-helpers@0.5.2':
|
||||
resolution: {integrity: sha512-a5MxrdDXEvqnIq+LisyCX6tQMPF/dSJpCfBgBauY+pNZ28yCtSsTvyTYrMhaI+LK26bVyCJfJkT0u8KIj2i1dQ==}
|
||||
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
|
||||
|
||||
'@eslint/core@0.17.0':
|
||||
resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
'@eslint/core@1.1.0':
|
||||
resolution: {integrity: sha512-/nr9K9wkr3P1EzFTdFdMoLuo1PmIxjmwvPozwoSodjNBdefGujXQUF93u1DDZpEaTuDvMsIQddsd35BwtrW9Xw==}
|
||||
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
|
||||
|
||||
'@eslint/eslintrc@3.3.3':
|
||||
resolution: {integrity: sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
'@eslint/object-schema@3.0.2':
|
||||
resolution: {integrity: sha512-HOy56KJt48Bx8KmJ+XGQNSUMT/6dZee/M54XyUyuvTvPXJmsERRvBchsUVx1UMe1WwIH49XLAczNC7V2INsuUw==}
|
||||
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
|
||||
|
||||
'@eslint/js@9.39.2':
|
||||
resolution: {integrity: sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==}
|
||||
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}
|
||||
'@eslint/plugin-kit@0.6.0':
|
||||
resolution: {integrity: sha512-bIZEUzOI1jkhviX2cp5vNyXQc6olzb2ohewQubuYlMXZ2Q/XjBO0x0XhGPvc9fjSIiUN0vw+0hq53BJ4eQSJKQ==}
|
||||
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
|
||||
|
||||
'@girs/accountsservice-1.0@1.0.0-4.0.0-beta.38':
|
||||
resolution: {integrity: sha512-6QzytM5dztmMynF2bxN73EuNK9ArMFxkP2L8wUC7IH45zBeBOfYcqL85BFh2PmkGmqRk+Rli5EFR8dAkx3Ig5Q==}
|
||||
@@ -574,6 +566,9 @@ packages:
|
||||
'@types/babel__traverse@7.28.0':
|
||||
resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==}
|
||||
|
||||
'@types/esrecurse@4.3.1':
|
||||
resolution: {integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==}
|
||||
|
||||
'@types/estree@1.0.8':
|
||||
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
|
||||
|
||||
@@ -719,13 +714,13 @@ packages:
|
||||
peerDependencies:
|
||||
acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
|
||||
|
||||
acorn@8.15.0:
|
||||
resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==}
|
||||
acorn@8.16.0:
|
||||
resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
hasBin: true
|
||||
|
||||
ajv@6.12.6:
|
||||
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
|
||||
ajv@6.14.0:
|
||||
resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==}
|
||||
|
||||
ansi-escapes@4.3.2:
|
||||
resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==}
|
||||
@@ -762,9 +757,6 @@ packages:
|
||||
argparse@1.0.10:
|
||||
resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==}
|
||||
|
||||
argparse@2.0.1:
|
||||
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
|
||||
|
||||
babel-jest@30.2.0:
|
||||
resolution: {integrity: sha512-0YiBEOxWqKkSQWL9nNGGEgndoeL0ZpWrbLMNL5u/Kaxrli3Eaxlt3ZtIDktEvXt4L/R9r3ODr2zKwGM/2BjxVw==}
|
||||
engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
|
||||
@@ -793,6 +785,10 @@ packages:
|
||||
balanced-match@1.0.2:
|
||||
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:
|
||||
resolution: {integrity: sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==}
|
||||
hasBin: true
|
||||
@@ -803,6 +799,10 @@ packages:
|
||||
brace-expansion@2.0.2:
|
||||
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:
|
||||
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -949,25 +949,21 @@ packages:
|
||||
peerDependencies:
|
||||
eslint: ^7.0.0 || ^8.0.0 || ^9.0.0
|
||||
|
||||
eslint-scope@8.4.0:
|
||||
resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
eslint-scope@9.1.1:
|
||||
resolution: {integrity: sha512-GaUN0sWim5qc8KVErfPBWmc31LEsOkrUJbvJZV+xuL3u2phMUK4HIvXlWAakfC8W4nzlK+chPEAkYOYb5ZScIw==}
|
||||
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
|
||||
|
||||
eslint-visitor-keys@3.4.3:
|
||||
resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==}
|
||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||
|
||||
eslint-visitor-keys@4.2.1:
|
||||
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==}
|
||||
eslint-visitor-keys@5.0.1:
|
||||
resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==}
|
||||
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
|
||||
|
||||
eslint@9.39.2:
|
||||
resolution: {integrity: sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
eslint@10.0.2:
|
||||
resolution: {integrity: sha512-uYixubwmqJZH+KLVYIVKY1JQt7tysXhtj21WSvjcSmU5SVNzMus1bgLe+pAt816yQ8opKfheVVoPLqvVMGejYw==}
|
||||
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
jiti: '*'
|
||||
@@ -975,14 +971,14 @@ packages:
|
||||
jiti:
|
||||
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:
|
||||
resolution: {integrity: sha512-WFWYhO1fV4iYkqOOvq8FbqIhr2pYfoDY0kCotMkDeNtGpiGGkZ1iov2u8ydjtgM8yF8rzK7oaTbw2NAzbAbehw==}
|
||||
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:
|
||||
resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==}
|
||||
engines: {node: '>=4'}
|
||||
@@ -1048,8 +1044,8 @@ packages:
|
||||
resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==}
|
||||
engines: {node: '>=16'}
|
||||
|
||||
flatted@3.3.3:
|
||||
resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==}
|
||||
flatted@3.3.4:
|
||||
resolution: {integrity: sha512-3+mMldrTAPdta5kjX2G2J7iX4zxtnwpdA8Tr2ZSjkyPSanvbZAcy6flmtnXbEybHrDcU9641lxrMfFuUxVz9vA==}
|
||||
|
||||
foreground-child@3.3.1:
|
||||
resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==}
|
||||
@@ -1085,15 +1081,12 @@ packages:
|
||||
|
||||
glob@10.5.0:
|
||||
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
|
||||
|
||||
glob@7.2.3:
|
||||
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
|
||||
deprecated: Glob versions prior to v9 are no longer supported
|
||||
|
||||
globals@14.0.0:
|
||||
resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==}
|
||||
engines: {node: '>=18'}
|
||||
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
|
||||
|
||||
graceful-fs@4.2.11:
|
||||
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
|
||||
@@ -1121,10 +1114,6 @@ packages:
|
||||
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
|
||||
engines: {node: '>= 4'}
|
||||
|
||||
import-fresh@3.3.1:
|
||||
resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
import-local@3.2.0:
|
||||
resolution: {integrity: sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -1329,10 +1318,6 @@ packages:
|
||||
resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==}
|
||||
hasBin: true
|
||||
|
||||
js-yaml@4.1.1:
|
||||
resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==}
|
||||
hasBin: true
|
||||
|
||||
jsdoc-type-pratt-parser@7.1.0:
|
||||
resolution: {integrity: sha512-SX7q7XyCwzM/MEDCYz0l8GgGbJAACGFII9+WfNYr5SLEKukHWRy2Jk3iWRe7P+lpYJNs7oQ+OSei4JtKGUjd7A==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
@@ -1384,9 +1369,6 @@ packages:
|
||||
lodash.memoize@4.1.2:
|
||||
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:
|
||||
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
|
||||
|
||||
@@ -1414,11 +1396,15 @@ packages:
|
||||
resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
minimatch@3.1.2:
|
||||
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
|
||||
minimatch@10.2.4:
|
||||
resolution: {integrity: sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==}
|
||||
engines: {node: 18 || 20 || >=22}
|
||||
|
||||
minimatch@9.0.5:
|
||||
resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
|
||||
minimatch@3.1.5:
|
||||
resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==}
|
||||
|
||||
minimatch@9.0.9:
|
||||
resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==}
|
||||
engines: {node: '>=16 || 14 >=14.17'}
|
||||
|
||||
minimist@1.2.8:
|
||||
@@ -1493,10 +1479,6 @@ packages:
|
||||
package-json-from-dist@1.0.1:
|
||||
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:
|
||||
resolution: {integrity: sha512-4s6vd6dx1AotCx/RCI2m7t7GCh5bDRUtGNvRfHSP2wbBQdMi67pPe7mtzmgwcaQ8VKK/6IB7Glfyu3qdZJPybQ==}
|
||||
|
||||
@@ -1572,10 +1554,6 @@ packages:
|
||||
resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
resolve-from@4.0.0:
|
||||
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
resolve-from@5.0.0:
|
||||
resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -2031,50 +2009,34 @@ snapshots:
|
||||
|
||||
'@es-joy/resolve.exports@1.2.0': {}
|
||||
|
||||
'@eslint-community/eslint-utils@4.9.1(eslint@9.39.2)':
|
||||
'@eslint-community/eslint-utils@4.9.1(eslint@10.0.2)':
|
||||
dependencies:
|
||||
eslint: 9.39.2
|
||||
eslint: 10.0.2
|
||||
eslint-visitor-keys: 3.4.3
|
||||
|
||||
'@eslint-community/regexpp@4.12.2': {}
|
||||
|
||||
'@eslint/config-array@0.21.1':
|
||||
'@eslint/config-array@0.23.2':
|
||||
dependencies:
|
||||
'@eslint/object-schema': 2.1.7
|
||||
'@eslint/object-schema': 3.0.2
|
||||
debug: 4.4.3
|
||||
minimatch: 3.1.2
|
||||
minimatch: 10.2.4
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@eslint/config-helpers@0.4.2':
|
||||
'@eslint/config-helpers@0.5.2':
|
||||
dependencies:
|
||||
'@eslint/core': 0.17.0
|
||||
'@eslint/core': 1.1.0
|
||||
|
||||
'@eslint/core@0.17.0':
|
||||
'@eslint/core@1.1.0':
|
||||
dependencies:
|
||||
'@types/json-schema': 7.0.15
|
||||
|
||||
'@eslint/eslintrc@3.3.3':
|
||||
'@eslint/object-schema@3.0.2': {}
|
||||
|
||||
'@eslint/plugin-kit@0.6.0':
|
||||
dependencies:
|
||||
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
|
||||
'@eslint/core': 1.1.0
|
||||
levn: 0.4.1
|
||||
|
||||
'@girs/accountsservice-1.0@1.0.0-4.0.0-beta.38':
|
||||
@@ -2808,6 +2770,8 @@ snapshots:
|
||||
dependencies:
|
||||
'@babel/types': 7.28.6
|
||||
|
||||
'@types/esrecurse@4.3.1': {}
|
||||
|
||||
'@types/estree@1.0.8': {}
|
||||
|
||||
'@types/istanbul-lib-coverage@2.0.6': {}
|
||||
@@ -2902,13 +2866,13 @@ snapshots:
|
||||
'@unrs/resolver-binding-win32-x64-msvc@1.11.1':
|
||||
optional: true
|
||||
|
||||
acorn-jsx@5.3.2(acorn@8.15.0):
|
||||
acorn-jsx@5.3.2(acorn@8.16.0):
|
||||
dependencies:
|
||||
acorn: 8.15.0
|
||||
acorn: 8.16.0
|
||||
|
||||
acorn@8.15.0: {}
|
||||
acorn@8.16.0: {}
|
||||
|
||||
ajv@6.12.6:
|
||||
ajv@6.14.0:
|
||||
dependencies:
|
||||
fast-deep-equal: 3.1.3
|
||||
fast-json-stable-stringify: 2.1.0
|
||||
@@ -2942,8 +2906,6 @@ snapshots:
|
||||
dependencies:
|
||||
sprintf-js: 1.0.3
|
||||
|
||||
argparse@2.0.1: {}
|
||||
|
||||
babel-jest@30.2.0(@babel/core@7.28.6):
|
||||
dependencies:
|
||||
'@babel/core': 7.28.6
|
||||
@@ -2998,6 +2960,8 @@ snapshots:
|
||||
|
||||
balanced-match@1.0.2: {}
|
||||
|
||||
balanced-match@4.0.4: {}
|
||||
|
||||
baseline-browser-mapping@2.9.19: {}
|
||||
|
||||
brace-expansion@1.1.12:
|
||||
@@ -3009,6 +2973,10 @@ snapshots:
|
||||
dependencies:
|
||||
balanced-match: 1.0.2
|
||||
|
||||
brace-expansion@5.0.4:
|
||||
dependencies:
|
||||
balanced-match: 4.0.4
|
||||
|
||||
braces@3.0.3:
|
||||
dependencies:
|
||||
fill-range: 7.1.1
|
||||
@@ -3110,7 +3078,7 @@ snapshots:
|
||||
|
||||
escape-string-regexp@4.0.0: {}
|
||||
|
||||
eslint-plugin-jsdoc@62.4.1(eslint@9.39.2):
|
||||
eslint-plugin-jsdoc@62.4.1(eslint@10.0.2):
|
||||
dependencies:
|
||||
'@es-joy/jsdoccomment': 0.83.0
|
||||
'@es-joy/resolve.exports': 1.2.0
|
||||
@@ -3118,7 +3086,7 @@ snapshots:
|
||||
comment-parser: 1.4.5
|
||||
debug: 4.4.3
|
||||
escape-string-regexp: 4.0.0
|
||||
eslint: 9.39.2
|
||||
eslint: 10.0.2
|
||||
espree: 11.1.0
|
||||
esquery: 1.7.0
|
||||
html-entities: 2.6.0
|
||||
@@ -3130,39 +3098,36 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-scope@8.4.0:
|
||||
eslint-scope@9.1.1:
|
||||
dependencies:
|
||||
'@types/esrecurse': 4.3.1
|
||||
'@types/estree': 1.0.8
|
||||
esrecurse: 4.3.0
|
||||
estraverse: 5.3.0
|
||||
|
||||
eslint-visitor-keys@3.4.3: {}
|
||||
|
||||
eslint-visitor-keys@4.2.1: {}
|
||||
eslint-visitor-keys@5.0.1: {}
|
||||
|
||||
eslint-visitor-keys@5.0.0: {}
|
||||
|
||||
eslint@9.39.2:
|
||||
eslint@10.0.2:
|
||||
dependencies:
|
||||
'@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2)
|
||||
'@eslint-community/eslint-utils': 4.9.1(eslint@10.0.2)
|
||||
'@eslint-community/regexpp': 4.12.2
|
||||
'@eslint/config-array': 0.21.1
|
||||
'@eslint/config-helpers': 0.4.2
|
||||
'@eslint/core': 0.17.0
|
||||
'@eslint/eslintrc': 3.3.3
|
||||
'@eslint/js': 9.39.2
|
||||
'@eslint/plugin-kit': 0.4.1
|
||||
'@eslint/config-array': 0.23.2
|
||||
'@eslint/config-helpers': 0.5.2
|
||||
'@eslint/core': 1.1.0
|
||||
'@eslint/plugin-kit': 0.6.0
|
||||
'@humanfs/node': 0.16.7
|
||||
'@humanwhocodes/module-importer': 1.0.1
|
||||
'@humanwhocodes/retry': 0.4.3
|
||||
'@types/estree': 1.0.8
|
||||
ajv: 6.12.6
|
||||
chalk: 4.1.2
|
||||
ajv: 6.14.0
|
||||
cross-spawn: 7.0.6
|
||||
debug: 4.4.3
|
||||
escape-string-regexp: 4.0.0
|
||||
eslint-scope: 8.4.0
|
||||
eslint-visitor-keys: 4.2.1
|
||||
espree: 10.4.0
|
||||
eslint-scope: 9.1.1
|
||||
eslint-visitor-keys: 5.0.1
|
||||
espree: 11.1.1
|
||||
esquery: 1.7.0
|
||||
esutils: 2.0.3
|
||||
fast-deep-equal: 3.1.3
|
||||
@@ -3173,24 +3138,23 @@ snapshots:
|
||||
imurmurhash: 0.1.4
|
||||
is-glob: 4.0.3
|
||||
json-stable-stringify-without-jsonify: 1.0.1
|
||||
lodash.merge: 4.6.2
|
||||
minimatch: 3.1.2
|
||||
minimatch: 10.2.4
|
||||
natural-compare: 1.4.0
|
||||
optionator: 0.9.4
|
||||
transitivePeerDependencies:
|
||||
- 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:
|
||||
dependencies:
|
||||
acorn: 8.15.0
|
||||
acorn-jsx: 5.3.2(acorn@8.15.0)
|
||||
eslint-visitor-keys: 5.0.0
|
||||
acorn: 8.16.0
|
||||
acorn-jsx: 5.3.2(acorn@8.16.0)
|
||||
eslint-visitor-keys: 5.0.1
|
||||
|
||||
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: {}
|
||||
|
||||
@@ -3259,10 +3223,10 @@ snapshots:
|
||||
|
||||
flat-cache@4.0.1:
|
||||
dependencies:
|
||||
flatted: 3.3.3
|
||||
flatted: 3.3.4
|
||||
keyv: 4.5.4
|
||||
|
||||
flatted@3.3.3: {}
|
||||
flatted@3.3.4: {}
|
||||
|
||||
foreground-child@3.3.1:
|
||||
dependencies:
|
||||
@@ -3290,7 +3254,7 @@ snapshots:
|
||||
dependencies:
|
||||
foreground-child: 3.3.1
|
||||
jackspeak: 3.4.3
|
||||
minimatch: 9.0.5
|
||||
minimatch: 9.0.9
|
||||
minipass: 7.1.2
|
||||
package-json-from-dist: 1.0.1
|
||||
path-scurry: 1.11.1
|
||||
@@ -3300,12 +3264,10 @@ snapshots:
|
||||
fs.realpath: 1.0.0
|
||||
inflight: 1.0.6
|
||||
inherits: 2.0.4
|
||||
minimatch: 3.1.2
|
||||
minimatch: 3.1.5
|
||||
once: 1.4.0
|
||||
path-is-absolute: 1.0.1
|
||||
|
||||
globals@14.0.0: {}
|
||||
|
||||
graceful-fs@4.2.11: {}
|
||||
|
||||
handlebars@4.7.8:
|
||||
@@ -3327,11 +3289,6 @@ snapshots:
|
||||
|
||||
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:
|
||||
dependencies:
|
||||
pkg-dir: 4.2.0
|
||||
@@ -3719,10 +3676,6 @@ snapshots:
|
||||
argparse: 1.0.10
|
||||
esprima: 4.0.1
|
||||
|
||||
js-yaml@4.1.1:
|
||||
dependencies:
|
||||
argparse: 2.0.1
|
||||
|
||||
jsdoc-type-pratt-parser@7.1.0: {}
|
||||
|
||||
jsesc@3.1.0: {}
|
||||
@@ -3760,8 +3713,6 @@ snapshots:
|
||||
|
||||
lodash.memoize@4.1.2: {}
|
||||
|
||||
lodash.merge@4.6.2: {}
|
||||
|
||||
lru-cache@10.4.3: {}
|
||||
|
||||
lru-cache@5.1.1:
|
||||
@@ -3787,11 +3738,15 @@ snapshots:
|
||||
|
||||
mimic-fn@2.1.0: {}
|
||||
|
||||
minimatch@3.1.2:
|
||||
minimatch@10.2.4:
|
||||
dependencies:
|
||||
brace-expansion: 5.0.4
|
||||
|
||||
minimatch@3.1.5:
|
||||
dependencies:
|
||||
brace-expansion: 1.1.12
|
||||
|
||||
minimatch@9.0.5:
|
||||
minimatch@9.0.9:
|
||||
dependencies:
|
||||
brace-expansion: 2.0.2
|
||||
|
||||
@@ -3856,10 +3811,6 @@ snapshots:
|
||||
|
||||
package-json-from-dist@1.0.1: {}
|
||||
|
||||
parent-module@1.0.1:
|
||||
dependencies:
|
||||
callsites: 3.1.0
|
||||
|
||||
parse-imports-exports@0.2.4:
|
||||
dependencies:
|
||||
parse-statements: 1.0.11
|
||||
@@ -3918,8 +3869,6 @@ snapshots:
|
||||
dependencies:
|
||||
resolve-from: 5.0.0
|
||||
|
||||
resolve-from@4.0.0: {}
|
||||
|
||||
resolve-from@5.0.0: {}
|
||||
|
||||
semver@6.3.1: {}
|
||||
@@ -4007,7 +3956,7 @@ snapshots:
|
||||
dependencies:
|
||||
'@istanbuljs/schema': 0.1.3
|
||||
glob: 7.2.3
|
||||
minimatch: 3.1.2
|
||||
minimatch: 3.1.5
|
||||
|
||||
tmpl@1.0.5: {}
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
</key>
|
||||
|
||||
<key name="toggle-orientation" type="as">
|
||||
<default><![CDATA[['<Super><Shift>comma']]]></default>
|
||||
<default><![CDATA[['<Primary>comma']]]></default>
|
||||
<summary>Toggle active container orientation</summary>
|
||||
<description>Toggles the orientation of the container holding the active window between horizontal and vertical</description>
|
||||
</key>
|
||||
@@ -55,5 +55,11 @@
|
||||
<description>Resets all window size ratios in the active window's container to equal splits</description>
|
||||
</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>
|
||||
</schemalist>
|
||||
@@ -20,25 +20,38 @@ jest.mock('../utils/events.js', () => ({
|
||||
|
||||
describe('Container Logic Tests', () => {
|
||||
describe('Orientation Toggle Logic', () => {
|
||||
enum Orientation {
|
||||
enum Layout {
|
||||
HORIZONTAL = 0,
|
||||
VERTICAL = 1,
|
||||
TABBED = 2,
|
||||
}
|
||||
|
||||
const toggleOrientation = (current: Orientation): Orientation => {
|
||||
return current === Orientation.HORIZONTAL
|
||||
? Orientation.VERTICAL
|
||||
: Orientation.HORIZONTAL;
|
||||
const toggleOrientation = (current: Layout): Layout => {
|
||||
if (current === Layout.TABBED) return Layout.HORIZONTAL;
|
||||
return current === Layout.HORIZONTAL
|
||||
? Layout.VERTICAL
|
||||
: Layout.HORIZONTAL;
|
||||
};
|
||||
|
||||
test('should toggle from HORIZONTAL to VERTICAL', () => {
|
||||
const result = toggleOrientation(Orientation.HORIZONTAL);
|
||||
expect(result).toBe(Orientation.VERTICAL);
|
||||
const result = toggleOrientation(Layout.HORIZONTAL);
|
||||
expect(result).toBe(Layout.VERTICAL);
|
||||
});
|
||||
|
||||
test('should toggle from VERTICAL to HORIZONTAL', () => {
|
||||
const result = toggleOrientation(Orientation.VERTICAL);
|
||||
expect(result).toBe(Orientation.HORIZONTAL);
|
||||
const result = toggleOrientation(Layout.VERTICAL);
|
||||
expect(result).toBe(Layout.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');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -100,6 +113,71 @@ 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', () => {
|
||||
test('should find window index in array', () => {
|
||||
const windows = [
|
||||
|
||||
@@ -182,6 +182,15 @@ 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
|
||||
|
||||
@@ -2,10 +2,12 @@ import {WindowWrapper} from "./window.js";
|
||||
import {Logger} from "../utils/logger.js";
|
||||
import queueEvent from "../utils/events.js";
|
||||
import {Rect} from "../utils/rect.js";
|
||||
import {TabBar, TAB_BAR_HEIGHT} from "./tabBar.js";
|
||||
|
||||
enum Orientation {
|
||||
HORIZONTAL = 0,
|
||||
VERTICAL = 1,
|
||||
export enum Layout {
|
||||
ACC_HORIZONTAL = 0,
|
||||
ACC_VERTICAL = 1,
|
||||
TABBED = 2,
|
||||
}
|
||||
|
||||
// Returns equal ratios summing exactly to 1.0, with float drift absorbed by the last slot.
|
||||
@@ -22,10 +24,17 @@ export default class WindowContainer {
|
||||
|
||||
_tiledItems: (WindowWrapper | WindowContainer)[];
|
||||
_tiledWindowLookup: Map<number, WindowWrapper>;
|
||||
_orientation: Orientation = Orientation.HORIZONTAL;
|
||||
_orientation: Layout = Layout.ACC_HORIZONTAL;
|
||||
_workArea: Rect;
|
||||
|
||||
// -- Accordion Mode States
|
||||
|
||||
_splitRatios: number[];
|
||||
|
||||
// -- Tabbed mode state -----------------------------------------------------
|
||||
_activeTabIndex: number = 0;
|
||||
_tabBar: TabBar | null = null;
|
||||
|
||||
constructor(workspaceArea: Rect) {
|
||||
this._tiledItems = [];
|
||||
this._tiledWindowLookup = new Map<number, WindowWrapper>();
|
||||
@@ -33,7 +42,7 @@ export default class WindowContainer {
|
||||
this._splitRatios = [];
|
||||
}
|
||||
|
||||
// ─── Helpers ────────────────────────────────────────────────────────────────
|
||||
// --- Helpers ----------------------------------------------------------------
|
||||
|
||||
private _resetRatios(): void {
|
||||
this._splitRatios = equalRatios(this._tiledItems.length);
|
||||
@@ -46,41 +55,136 @@ export default class WindowContainer {
|
||||
return;
|
||||
}
|
||||
const newRatio = 1 / n;
|
||||
const scale = 1 - newRatio;
|
||||
const scaled = this._splitRatios.map(r => r * scale);
|
||||
const scale = 1 - newRatio;
|
||||
const scaled = this._splitRatios.map(r => r * scale);
|
||||
const partialSum = scaled.reduce((a, b) => a + b, 0) + newRatio;
|
||||
scaled[scaled.length - 1] += (1.0 - partialSum);
|
||||
this._splitRatios = [...scaled, newRatio];
|
||||
}
|
||||
|
||||
private _totalDimension(): number {
|
||||
return this._orientation === Orientation.HORIZONTAL
|
||||
return this._orientation === Layout.ACC_HORIZONTAL
|
||||
? this._workArea.width
|
||||
: this._workArea.height;
|
||||
}
|
||||
|
||||
// ─── Public API ─────────────────────────────────────────────────────────────
|
||||
isTabbed(): boolean {
|
||||
return this._orientation === Layout.TABBED;
|
||||
}
|
||||
|
||||
// --- Public API -------------------------------------------------------------
|
||||
|
||||
move(rect: Rect): void {
|
||||
this._workArea = rect;
|
||||
this.tileWindows();
|
||||
this.drawWindows();
|
||||
}
|
||||
|
||||
toggleOrientation(): void {
|
||||
this._orientation = this._orientation === Orientation.HORIZONTAL
|
||||
? Orientation.VERTICAL
|
||||
: Orientation.HORIZONTAL;
|
||||
Logger.info(`Container orientation toggled to ${this._orientation === Orientation.HORIZONTAL ? 'HORIZONTAL' : 'VERTICAL'}`);
|
||||
this.tileWindows();
|
||||
if (this._orientation === Layout.TABBED) {
|
||||
// Tabbed → Horizontal: restore accordion mode
|
||||
this.setAccordion(Layout.ACC_HORIZONTAL);
|
||||
} else {
|
||||
this._orientation = this._orientation === Layout.ACC_HORIZONTAL
|
||||
? 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 {
|
||||
this._tiledItems.push(winWrap);
|
||||
this._tiledWindowLookup.set(winWrap.getWindowId(), winWrap);
|
||||
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({
|
||||
name: "tiling-windows",
|
||||
callback: () => this.tileWindows(),
|
||||
callback: () => this.drawWindows(),
|
||||
}, 100);
|
||||
}
|
||||
|
||||
@@ -111,13 +215,28 @@ export default class WindowContainer {
|
||||
|
||||
removeWindow(win_id: number): void {
|
||||
if (this._tiledWindowLookup.has(win_id)) {
|
||||
// Get index before deleting from lookup to avoid race condition
|
||||
const index = this._getIndexOfWindow(win_id);
|
||||
this._tiledWindowLookup.delete(win_id);
|
||||
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._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 {
|
||||
for (const item of this._tiledItems) {
|
||||
if (item instanceof WindowContainer) {
|
||||
@@ -125,7 +244,7 @@ export default class WindowContainer {
|
||||
}
|
||||
}
|
||||
}
|
||||
this.tileWindows();
|
||||
this.drawWindows();
|
||||
}
|
||||
|
||||
disconnectSignals(): void {
|
||||
@@ -139,37 +258,159 @@ export default class WindowContainer {
|
||||
}
|
||||
|
||||
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._tiledWindowLookup.clear();
|
||||
this._splitRatios = [];
|
||||
this._activeTabIndex = 0;
|
||||
}
|
||||
|
||||
tileWindows(): void {
|
||||
drawWindows(): void {
|
||||
Logger.log("TILING WINDOWS IN CONTAINER");
|
||||
Logger.log("WorkArea", this._workArea);
|
||||
this._tileItems();
|
||||
|
||||
if (this.isTabbed()) {
|
||||
this._tileTab();
|
||||
} else {
|
||||
this._tileAccordion();
|
||||
}
|
||||
}
|
||||
|
||||
_tileItems() {
|
||||
_tileAccordion() {
|
||||
if (this._tiledItems.length === 0) return;
|
||||
|
||||
const bounds = this.getBounds();
|
||||
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(', ')}]`);
|
||||
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(', ')}]`);
|
||||
this._tiledItems.forEach((item, index) => {
|
||||
const rect = bounds[index];
|
||||
if (item instanceof WindowContainer) {
|
||||
item.move(rect);
|
||||
} else {
|
||||
Logger.info(`_tileItems: window[${index}] id=${item.getWindowId()} dragging=${item._dragging} → rect=(${rect.x},${rect.y},${rect.width},${rect.height})`);
|
||||
Logger.info(`_tileAccordion: window[${index}] id=${item.getWindowId()} dragging=${item._dragging} → rect=(${rect.x},${rect.y},${rect.width},${rect.height})`);
|
||||
item.safelyResizeWindow(rect);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ─── Bounds Calculation ──────────────────────────────────────────────────────
|
||||
private _tileTab(): void {
|
||||
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[] {
|
||||
return this._orientation === Orientation.HORIZONTAL
|
||||
if (this._orientation === Layout.TABBED) {
|
||||
// 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('vertical');
|
||||
}
|
||||
@@ -187,14 +428,15 @@ export default class WindowContainer {
|
||||
used += size;
|
||||
|
||||
return isHorizontal
|
||||
? { 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 + 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};
|
||||
});
|
||||
}
|
||||
|
||||
// ─── Boundary Adjustment ─────────────────────────────────────────────────────
|
||||
|
||||
adjustBoundary(boundaryIndex: number, deltaPixels: number): boolean {
|
||||
// No boundary adjustment in tabbed mode
|
||||
if (this.isTabbed()) return false;
|
||||
|
||||
if (boundaryIndex < 0 || boundaryIndex >= this._tiledItems.length - 1) {
|
||||
Logger.warn(`adjustBoundary: invalid boundaryIndex ${boundaryIndex}`);
|
||||
return false;
|
||||
@@ -204,22 +446,22 @@ export default class WindowContainer {
|
||||
if (totalDim === 0) return false;
|
||||
|
||||
const ratioDelta = deltaPixels / totalDim;
|
||||
const newLeft = this._splitRatios[boundaryIndex] + ratioDelta;
|
||||
const newRight = this._splitRatios[boundaryIndex + 1] - ratioDelta;
|
||||
const newLeft = this._splitRatios[boundaryIndex] + ratioDelta;
|
||||
const newRight = this._splitRatios[boundaryIndex + 1] - ratioDelta;
|
||||
|
||||
if (newLeft <= 0 || newRight <= 0) {
|
||||
Logger.log(`adjustBoundary: clamped — newLeft=${newLeft.toFixed(3)}, newRight=${newRight.toFixed(3)}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
this._splitRatios[boundaryIndex] = newLeft;
|
||||
this._splitRatios[boundaryIndex] = newLeft;
|
||||
this._splitRatios[boundaryIndex + 1] = newRight;
|
||||
|
||||
Logger.info(`adjustBoundary: boundary=${boundaryIndex} ratios=[${this._splitRatios.map(r => r.toFixed(3)).join(', ')}]`);
|
||||
return true;
|
||||
}
|
||||
|
||||
// ─── Container Lookup ────────────────────────────────────────────────────────
|
||||
// --- Container Lookup --------------------------------------------------------
|
||||
|
||||
getContainerForWindow(win_id: number): WindowContainer | null {
|
||||
for (const item of this._tiledItems) {
|
||||
@@ -248,6 +490,12 @@ export default class WindowContainer {
|
||||
|
||||
// TODO: update this to work with nested containers - all other logic should already be working
|
||||
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);
|
||||
|
||||
if (original_index === -1) {
|
||||
@@ -266,12 +514,12 @@ export default class WindowContainer {
|
||||
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[new_index], this._tiledItems[original_index]];
|
||||
this.tileWindows();
|
||||
this.drawWindows();
|
||||
}
|
||||
}
|
||||
|
||||
resetRatios(): void {
|
||||
this._resetRatios();
|
||||
this.tileWindows();
|
||||
this.drawWindows();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import {WindowWrapper} from "./window.js";
|
||||
import {Rect} from "../utils/rect.js";
|
||||
import {Logger} from "../utils/logger.js";
|
||||
import Meta from "gi://Meta";
|
||||
|
||||
import WindowContainer from "./container.js";
|
||||
|
||||
@@ -73,6 +72,25 @@ export default class Monitor {
|
||||
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 {
|
||||
this._workspaces[item.getWorkspace()].itemDragged(item, x, y);
|
||||
}
|
||||
|
||||
131
src/wm/tabBar.ts
Normal file
131
src/wm/tabBar.ts
Normal file
@@ -0,0 +1,131 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -47,6 +47,31 @@ export class WindowWrapper {
|
||||
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 {
|
||||
this._dragging = true;
|
||||
}
|
||||
@@ -89,6 +114,9 @@ export class WindowWrapper {
|
||||
this._window.connect("size-changed", () => {
|
||||
windowManager.handleWindowPositionChanged(this);
|
||||
}),
|
||||
this._window.connect('notify::title', () => {
|
||||
windowManager.handleWindowTitleChanged(this);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -119,6 +147,10 @@ export class WindowWrapper {
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
const new_rect = this._window.get_frame_rect();
|
||||
@@ -129,13 +161,27 @@ export class WindowWrapper {
|
||||
Math.abs(new_rect.height - rect.height) > WindowWrapper.RESIZE_TOLERANCE;
|
||||
|
||||
if (_retry > 0 && mismatch) {
|
||||
Logger.warn("RESIZE MISMATCH, retrying",
|
||||
`want(${rect.x},${rect.y},${rect.width},${rect.height})`,
|
||||
`got(${new_rect.x},${new_rect.y},${new_rect.width},${new_rect.height})`);
|
||||
queueEvent({
|
||||
name: `delayed_resize_${this.getWindowId()}`,
|
||||
callback: () => this.safelyResizeWindow(rect, _retry - 1),
|
||||
}, 50);
|
||||
// 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",
|
||||
`want(${rect.x},${rect.y},${rect.width},${rect.height})`,
|
||||
`got(${new_rect.x},${new_rect.y},${new_rect.width},${new_rect.height})`);
|
||||
queueEvent({
|
||||
name: `delayed_resize_${this.getWindowId()}`,
|
||||
callback: () => this.safelyResizeWindow(rect, _retry - 1),
|
||||
}, 50);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import {WindowWrapper} from './window.js';
|
||||
import * as Main from "resource:///org/gnome/shell/ui/main.js";
|
||||
import {Logger} from "../utils/logger.js";
|
||||
import Monitor from "./monitor.js";
|
||||
import WindowContainer from "./container.js";
|
||||
import WindowContainer, {Layout} from "./container.js";
|
||||
import {Rect} from "../utils/rect.js";
|
||||
|
||||
|
||||
@@ -22,6 +22,8 @@ export interface IWindowManager {
|
||||
|
||||
handleWindowPositionChanged(winWrap: WindowWrapper): void;
|
||||
|
||||
handleWindowTitleChanged(winWrap: WindowWrapper): void;
|
||||
|
||||
syncActiveWindow(): number | null;
|
||||
}
|
||||
|
||||
@@ -44,7 +46,7 @@ export default class WindowManager implements IWindowManager {
|
||||
_changingGrabbedMonitor: boolean = false;
|
||||
_showingOverview: boolean = false;
|
||||
|
||||
// ── Resize-drag tracking ──────────────────────────────────────────────────
|
||||
// -- Resize-drag tracking --------------------------------------------------
|
||||
_isResizeDrag: boolean = false;
|
||||
_resizeDragWindowId: number = _UNUSED_WINDOW_ID;
|
||||
_resizeDragOp: Meta.GrabOp = Meta.GrabOp.NONE;
|
||||
@@ -103,6 +105,7 @@ export default class WindowManager implements IWindowManager {
|
||||
}),
|
||||
global.display.connect("in-fullscreen-changed", () => {
|
||||
Logger.log("IN FULL SCREEN CHANGED");
|
||||
this._syncFullscreenTabBars();
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -132,10 +135,16 @@ export default class WindowManager implements IWindowManager {
|
||||
Logger.log("HIDING OVERVIEW")
|
||||
this._showingOverview = false;
|
||||
this._tileMonitors();
|
||||
for (const monitor of this._monitors.values()) {
|
||||
monitor.showTabBars();
|
||||
}
|
||||
}),
|
||||
Main.overview.connect("showing", () => {
|
||||
this._showingOverview = true;
|
||||
Logger.log("SHOWING OVERVIEW");
|
||||
for (const monitor of this._monitors.values()) {
|
||||
monitor.hideTabBars();
|
||||
}
|
||||
}),
|
||||
];
|
||||
}
|
||||
@@ -329,7 +338,7 @@ export default class WindowManager implements IWindowManager {
|
||||
const itemIndex = container._getIndexOfWindow(winId);
|
||||
if (itemIndex === -1) return;
|
||||
|
||||
const isHorizontal = container._orientation === 0;
|
||||
const isHorizontal = container._orientation === Layout.ACC_HORIZONTAL;
|
||||
|
||||
// E/S edge → boundary after the item; W/N edge → boundary before it.
|
||||
let adjusted = false;
|
||||
@@ -350,7 +359,7 @@ export default class WindowManager implements IWindowManager {
|
||||
if (adjusted) {
|
||||
this._isTiling = true;
|
||||
try {
|
||||
container.tileWindows();
|
||||
container.drawWindows();
|
||||
} finally {
|
||||
this._isTiling = false;
|
||||
}
|
||||
@@ -413,6 +422,11 @@ export default class WindowManager implements IWindowManager {
|
||||
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) {
|
||||
Logger.log("ADDING WINDOW TO MONITOR", window, window);
|
||||
var wrapper = new WindowWrapper(window, (winWrap) => this.handleWindowMinimized(winWrap))
|
||||
@@ -428,12 +442,24 @@ 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 {
|
||||
this._isTiling = true;
|
||||
try {
|
||||
for (const monitor of this._monitors.values()) {
|
||||
monitor.tileWindows();
|
||||
}
|
||||
} catch (e) {
|
||||
Logger.error("_tileMonitors FAILED", e);
|
||||
} finally {
|
||||
this._isTiling = false;
|
||||
}
|
||||
@@ -512,6 +538,29 @@ 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 {
|
||||
Logger.info("=".repeat(80));
|
||||
Logger.info("WINDOW TREE STRUCTURE");
|
||||
@@ -531,8 +580,11 @@ export default class WindowManager implements IWindowManager {
|
||||
monitor._workspaces.forEach((workspace, workspaceIndex) => {
|
||||
const isActiveWorkspace = workspaceIndex === activeWorkspaceIndex;
|
||||
Logger.info(` Workspace ${workspaceIndex}${isActiveWorkspace && isActiveMonitor ? ' *' : ''}:`);
|
||||
Logger.info(` Orientation: ${workspace._orientation === 0 ? 'HORIZONTAL' : 'VERTICAL'}`);
|
||||
Logger.info(` Orientation: ${Layout[workspace._orientation]}`);
|
||||
Logger.info(` Items: ${workspace._tiledItems.length}`);
|
||||
if (workspace.isTabbed()) {
|
||||
Logger.info(` Active Tab: ${workspace._activeTabIndex}`);
|
||||
}
|
||||
this._printContainerTree(workspace, 4);
|
||||
});
|
||||
});
|
||||
@@ -547,7 +599,7 @@ export default class WindowManager implements IWindowManager {
|
||||
if (item instanceof WindowContainer) {
|
||||
const containsActive = this._activeWindowId !== null &&
|
||||
item.getWindow(this._activeWindowId) !== undefined;
|
||||
Logger.info(`${indent}[${index}] Container (${item._orientation === 0 ? 'HORIZONTAL' : 'VERTICAL'})${containsActive ? ' *' : ''}:`);
|
||||
Logger.info(`${indent}[${index}] Container (${Layout[item._orientation]})${containsActive ? ' *' : ''}:`);
|
||||
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}`);
|
||||
this._printContainerTree(item, indentLevel + 4);
|
||||
|
||||
@@ -1,37 +1,33 @@
|
||||
/* Add your custom extension styling here */
|
||||
.active-window-border {
|
||||
/*border: 2px solid rgba(191, 0, 255, 0.8);*/
|
||||
/*border-radius: 3px;*/
|
||||
|
||||
/* 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-bar {
|
||||
background-color: rgba(30, 30, 30, 0.95);
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
||||
spacing: 1px;
|
||||
padding: 2px 2px 0 2px;
|
||||
}
|
||||
|
||||
/*.border-gradient-purple {*/
|
||||
/* border-image-source: linear-gradient(to left, #743ad5, #d53a9d);*/
|
||||
/*}*/
|
||||
.aerospike-tab {
|
||||
background-color: rgba(50, 50, 50, 0.8);
|
||||
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;
|
||||
}
|
||||
|
||||
/*@keyframes rainbow-border {*/
|
||||
/* 0% {*/
|
||||
/* 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:hover {
|
||||
background-color: rgba(70, 70, 70, 0.9);
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
|
||||
/*.active-window-border {*/
|
||||
/* border: 4px solid transparent;*/
|
||||
/* border-radius: 5px;*/
|
||||
.aerospike-tab-active {
|
||||
background-color: rgba(80, 80, 80, 1);
|
||||
color: rgba(255, 255, 255, 1);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* !* Initial gradient border *!*/
|
||||
/* border-image: linear-gradient(0deg, red, orange, yellow, green, blue, indigo, violet, red) 1;*/
|
||||
|
||||
/* !* Apply animation *!*/
|
||||
/* animation: rainbow-border 5s linear infinite;*/
|
||||
/*}*/
|
||||
.aerospike-tab-label {
|
||||
font-size: 11px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user