Last active
June 3, 2026 20:28
-
-
Save sandro/1224b973b9445814b368fbc01bf93a18 to your computer and use it in GitHub Desktop.
phoenix config
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Phoenix.set({ openAtLogin: true }); | |
| const RIGHT_PROPORTION = 0.55; | |
| const LEFT_PROPORTION = 1.0 - RIGHT_PROPORTION; | |
| const TOP_PROPORTION = 0.55; | |
| const BOTTOM_PROPORTION = 1.0 - TOP_PROPORTION; | |
| const MODIFIERS = ["ctrl", "alt", "cmd"]; | |
| const HINT_CHARS = "0123456789qertyuiop".split(""); | |
| const MOVE_STEP = 200; | |
| let animationTimer; | |
| function animateFrame(w, target, steps = 6, duration = 0.06) { | |
| if (animationTimer) clearInterval(animationTimer); | |
| const start = w.frame(); | |
| let step = 0; | |
| const interval = (duration / steps) * 1000; | |
| animationTimer = setInterval(() => { | |
| step++; | |
| const t = step / steps; | |
| const ease = 1 - Math.pow(1 - t, 3); | |
| w.setFrame({ | |
| x: Math.round(start.x + (target.x - start.x) * ease), | |
| y: Math.round(start.y + (target.y - start.y) * ease), | |
| width: Math.round(start.width + (target.width - start.width) * ease), | |
| height: Math.round(start.height + (target.height - start.height) * ease), | |
| }); | |
| if (step >= steps) clearInterval(animationTimer); | |
| }, interval); | |
| } | |
| class SimpleMover { | |
| constructor() { | |
| this.enabled = false; | |
| this.autoCloseId = undefined; | |
| this.keys = []; | |
| this.hintKeys = []; | |
| this.hintModals = []; | |
| this.savedFrames = new Map(); | |
| this.helpModal = Modal.build({ | |
| icon: App.get("Phoenix").icon(), | |
| text: [ | |
| "← ↑ → ↓ Move window", | |
| "⌘+← ↑ → ↓ Resize window", | |
| "w Raise app windows", | |
| "g Focus any window", | |
| "t Tile app windows", | |
| "T Restore app windows", | |
| "esc Exit", | |
| ].join("\n"), | |
| appearance: "light", | |
| font: "Menlo", | |
| weight: 16, | |
| animationDuration: 0.15, | |
| origin: () => ({ x: 0, y: 200 }), | |
| }); | |
| this.exitKey = new Key("escape", [], () => this.disable()); | |
| this.exitKey.disable(); | |
| this.setupKeys(); | |
| } | |
| resetAutoClose() { | |
| clearTimeout(this.autoCloseId); | |
| this.autoCloseId = setTimeout(() => this.disable(), 10000); | |
| } | |
| withFocusedWindow(fn) { | |
| return () => { | |
| if (!this.enabled) return; | |
| this.resetAutoClose(); | |
| const w = Window.focused(); | |
| if (w) fn(w, w.frame()); | |
| }; | |
| } | |
| moverAction(fn) { | |
| return () => { | |
| if (!this.enabled) return; | |
| this.resetAutoClose(); | |
| fn(); | |
| }; | |
| } | |
| killHints() { | |
| for (const key of this.hintKeys) key.disable(); | |
| this.hintKeys = []; | |
| for (const modal of this.hintModals) modal.close(); | |
| this.hintModals = []; | |
| } | |
| showWindowHints(windows, onSelect) { | |
| const screenFrame = Screen.main().flippedFrame(); | |
| for (const [index, win] of windows.entries()) { | |
| if (index >= HINT_CHARS.length) break; | |
| const winFrame = win.frame(); | |
| const hint = HINT_CHARS[index]; | |
| const modal = Modal.build({ | |
| icon: win.app().icon(), | |
| text: hint, | |
| appearance: "dark", | |
| animationDuration: 0.1, | |
| duration: 10, | |
| origin: () => ({ | |
| x: winFrame.x, | |
| y: screenFrame.height - winFrame.y - index * 100, | |
| }), | |
| }); | |
| this.hintModals.push(modal); | |
| modal.show(); | |
| const hintKey = new Key(hint, [], () => { | |
| onSelect(win); | |
| this.disable(); | |
| }); | |
| this.hintKeys.push(hintKey); | |
| } | |
| } | |
| raiseAppWindows(w) { | |
| this.killHints(); | |
| const windows = w.app().windows({ visible: true }); | |
| const mainIndex = windows.findIndex((win) => win.isMain()); | |
| if (mainIndex >= 0) windows.splice(mainIndex, 1); | |
| windows.sort((a, b) => a.frame().y - b.frame().y); | |
| windows.unshift(w); | |
| this.showWindowHints(windows, (selected) => { | |
| selected.raise(); | |
| selected.focus(); | |
| }); | |
| } | |
| refocusAnyWindow() { | |
| this.killHints(); | |
| this.showWindowHints(Window.recent(), (selected) => { | |
| selected.raise(); | |
| selected.focus(); | |
| }); | |
| } | |
| tileAppWindows() { | |
| clearTimeout(this.autoCloseId); | |
| const w = Window.focused(); | |
| if (!w) return; | |
| const windows = w.app().windows({ visible: true }); | |
| if (windows.length === 0) return; | |
| if (this.savedFrames.size === 0) { | |
| for (const win of windows) { | |
| this.savedFrames.set(win.hash(), win.frame()); | |
| } | |
| } | |
| const screen = w.screen(); | |
| const frame = screen.flippedVisibleFrame(); | |
| const n = windows.length; | |
| const cols = Math.ceil(Math.sqrt(n)); | |
| const rows = Math.ceil(n / cols); | |
| let windowIndex = 0; | |
| let y = frame.y; | |
| for (let row = 0; row < rows; row++) { | |
| const colsInRow = (row === rows - 1 && n % cols !== 0) ? n % cols : cols; | |
| const cellWidth = Math.round(frame.width / colsInRow); | |
| const cellHeight = Math.round(frame.height / rows); | |
| let x = frame.x; | |
| for (let col = 0; col < colsInRow; col++) { | |
| if (windowIndex >= n) break; | |
| const width = (col === colsInRow - 1) ? frame.x + frame.width - x : cellWidth; | |
| const height = (row === rows - 1) ? frame.y + frame.height - y : cellHeight; | |
| windows[windowIndex].raise(); | |
| windows[windowIndex].setFrame({ x, y, width, height }); | |
| x += cellWidth; | |
| windowIndex++; | |
| } | |
| y += cellHeight; | |
| } | |
| } | |
| restoreWindows() { | |
| if (this.savedFrames.size === 0) return; | |
| for (const [hash, savedFrame] of this.savedFrames) { | |
| const win = Window.all().find((w) => w.hash() === hash); | |
| if (win) win.setFrame(savedFrame); | |
| } | |
| this.disable(); | |
| } | |
| enable() { | |
| if (this.enabled) return; | |
| this.enabled = true; | |
| this.exitKey.enable(); | |
| for (const key of this.keys) key.enable(); | |
| this.helpModal.show(); | |
| this.resetAutoClose(); | |
| } | |
| disable() { | |
| if (!this.enabled) return; | |
| this.enabled = false; | |
| this.exitKey.disable(); | |
| for (const key of this.keys) key.disable(); | |
| this.helpModal.close(); | |
| this.killHints(); | |
| this.savedFrames = new Map(); | |
| } | |
| addKey(name, mods, handler) { | |
| const key = new Key(name, mods, handler); | |
| key.disable(); | |
| this.keys.push(key); | |
| } | |
| setupKeys() { | |
| this.addKey("left", [], this.withFocusedWindow((w, f) => w.setTopLeft({ x: f.x - MOVE_STEP, y: f.y }))); | |
| this.addKey("right", [], this.withFocusedWindow((w, f) => w.setTopLeft({ x: f.x + MOVE_STEP, y: f.y }))); | |
| this.addKey("up", [], this.withFocusedWindow((w, f) => w.setTopLeft({ x: f.x, y: f.y - MOVE_STEP }))); | |
| this.addKey("down", [], this.withFocusedWindow((w, f) => w.setTopLeft({ x: f.x, y: f.y + MOVE_STEP }))); | |
| this.addKey("left", ["cmd"], this.withFocusedWindow((w, f) => w.setSize({ width: f.width - MOVE_STEP, height: f.height }))); | |
| this.addKey("right", ["cmd"], this.withFocusedWindow((w, f) => w.setSize({ width: f.width + MOVE_STEP, height: f.height }))); | |
| this.addKey("up", ["cmd"], this.withFocusedWindow((w, f) => w.setSize({ width: f.width, height: f.height - MOVE_STEP }))); | |
| this.addKey("down", ["cmd"], this.withFocusedWindow((w, f) => w.setSize({ width: f.width, height: f.height + MOVE_STEP }))); | |
| this.addKey("w", [], this.withFocusedWindow((w) => this.raiseAppWindows(w))); | |
| this.addKey("g", [], this.moverAction(() => this.refocusAnyWindow())); | |
| this.addKey("t", [], this.withFocusedWindow(() => this.tileAppWindows())); | |
| this.addKey("t", ["shift"], this.moverAction(() => this.restoreWindows())); | |
| } | |
| } | |
| class CyclingKeyHandler { | |
| constructor() { | |
| this.pressCounts = {}; | |
| this.resetTimeoutId = undefined; | |
| } | |
| pressed(key) { | |
| clearTimeout(this.resetTimeoutId); | |
| this.resetTimeoutId = setTimeout(() => { this.pressCounts = {}; }, 2000); | |
| let count = this.pressCounts[key] || 0; | |
| if (count >= 3) count = 0; | |
| this.pressCounts[key] = count + 1; | |
| return this.pressCounts[key]; | |
| } | |
| on(key, modifiers, fn) { | |
| Key.on(key, modifiers, () => { | |
| simpleMover.disable(); | |
| const num = this.pressed(key); | |
| const w = Window.focused(); | |
| if (!w) return; | |
| const screen = w.screen().flippedFrame(); | |
| fn(screen, w, num); | |
| }); | |
| } | |
| } | |
| const simpleMover = new SimpleMover(); | |
| const cyclingKeyHandler = new CyclingKeyHandler(); | |
| function center(screen, w) { | |
| const x = screen.x + screen.width * 0.15; | |
| animateFrame(w, { x, y: screen.y, width: screen.width * 0.7, height: screen.height }); | |
| } | |
| function leftSide(screen, w, num) { | |
| const sizes = [ | |
| { x: screen.x, width: screen.width * LEFT_PROPORTION }, | |
| { x: screen.x, width: screen.width * LEFT_PROPORTION - 200 }, | |
| { x: screen.x, width: screen.width * LEFT_PROPORTION - 500 }, | |
| ]; | |
| const size = sizes[num - 1]; | |
| animateFrame(w, { x: size.x, y: screen.y, width: size.width, height: screen.height }); | |
| } | |
| function rightSide(screen, w, num) { | |
| const base = { | |
| x: screen.x + screen.width * LEFT_PROPORTION, | |
| width: screen.width * RIGHT_PROPORTION, | |
| }; | |
| const sizes = [ | |
| base, | |
| { x: base.x - 200, width: base.width + 200 }, | |
| { x: base.x + 100, width: base.width - 100 }, | |
| ]; | |
| const size = sizes[num - 1]; | |
| animateFrame(w, { x: size.x, y: screen.y, width: size.width, height: screen.height }); | |
| } | |
| function topSide(screen, w) { | |
| animateFrame(w, { | |
| x: screen.x, y: screen.y, | |
| width: screen.width, height: screen.height * TOP_PROPORTION, | |
| }); | |
| } | |
| function bottomSide(screen, w) { | |
| animateFrame(w, { | |
| x: screen.x, | |
| y: screen.y + screen.height * TOP_PROPORTION, | |
| width: screen.width, | |
| height: screen.height * BOTTOM_PROPORTION, | |
| }); | |
| } | |
| cyclingKeyHandler.on("c", MODIFIERS, center); | |
| cyclingKeyHandler.on("m", MODIFIERS, (screen, w) => w.maximize()); | |
| cyclingKeyHandler.on("left", MODIFIERS, leftSide); | |
| cyclingKeyHandler.on("right", MODIFIERS, rightSide); | |
| cyclingKeyHandler.on("up", MODIFIERS, topSide); | |
| cyclingKeyHandler.on("down", MODIFIERS, bottomSide); | |
| cyclingKeyHandler.on("f", MODIFIERS, () => simpleMover.enable()); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment