diff --git a/package-lock.json b/package-lock.json index 57174867..f756bc5b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,9 +27,9 @@ "@fortawesome/react-fontawesome": "^0.2.0", "@headlessui/react": "^2.2.8", "@hookform/resolvers": "^3.3.4", - "@nethesis/chat-island": "^0.1.51", + "@nethesis/chat-island": "^0.1.58", "@nethesis/nethesis-solid-svg-icons": "github:nethesis/Font-Awesome#ns-solid", - "@nethesis/phone-island": "^1.0.16", + "@nethesis/phone-island": "^1.1.1", "@tailwindcss/forms": "^0.5.7", "@types/lodash": "^4.14.202", "@types/node": "^18.19.9", @@ -2458,9 +2458,9 @@ } }, "node_modules/@nethesis/chat-island": { - "version": "0.1.51", - "resolved": "https://registry.npmjs.org/@nethesis/chat-island/-/chat-island-0.1.51.tgz", - "integrity": "sha512-hzI2XKJ6cJ+IyZhyyaJwbf2YnMG5xOnQXHZIoGuIKKeVi77Kv48tk6po54LnR9N9BiK40gYzYw9a4l9MeqvPLA==", + "version": "0.1.58", + "resolved": "https://registry.npmjs.org/@nethesis/chat-island/-/chat-island-0.1.58.tgz", + "integrity": "sha512-GvaHyZFYqUZmJWxux2MBFnRfueBPocZKQl/CVeyK8DUFf88KYarnmL3EFmBsYKFtKu4ipf4WgwPIAWd7SjYa5g==", "dev": true, "license": "AGPL-3.0-or-later", "dependencies": { @@ -2498,9 +2498,9 @@ } }, "node_modules/@nethesis/phone-island": { - "version": "1.0.16", - "resolved": "https://registry.npmjs.org/@nethesis/phone-island/-/phone-island-1.0.16.tgz", - "integrity": "sha512-frWDsySWJU+EvNPYFCNt6lRwjehhc7c3JHF7irG6ehgzkNAkcgFFFoKVKOaTbs7fiidc4H0CaAHP7G9EjL7uRw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@nethesis/phone-island/-/phone-island-1.1.1.tgz", + "integrity": "sha512-mmAa+3TwSCUoZZpFGYu5xwn6pFUPU25asePhhmwnWD9te/6MRCGxe0z4M6TkjArUd7wC8YBNPObeIatoJkCHYg==", "dev": true, "license": "GPL-3.0-or-later", "dependencies": { diff --git a/package.json b/package.json index 2409728a..0fa5e3bd 100644 --- a/package.json +++ b/package.json @@ -45,9 +45,9 @@ "@fortawesome/react-fontawesome": "^0.2.0", "@headlessui/react": "^2.2.8", "@hookform/resolvers": "^3.3.4", - "@nethesis/chat-island": "^0.1.51", + "@nethesis/chat-island": "^0.1.58", "@nethesis/nethesis-solid-svg-icons": "github:nethesis/Font-Awesome#ns-solid", - "@nethesis/phone-island": "^1.0.16", + "@nethesis/phone-island": "^1.1.1", "@tailwindcss/forms": "^0.5.7", "@types/lodash": "^4.14.202", "@types/node": "^18.19.9", diff --git a/src/main/classes/controllers/ChatIslandController.ts b/src/main/classes/controllers/ChatIslandController.ts index bd34d0c4..d6ce092a 100644 --- a/src/main/classes/controllers/ChatIslandController.ts +++ b/src/main/classes/controllers/ChatIslandController.ts @@ -43,7 +43,18 @@ export class ChatIslandController { resize(size: Size) { try { this.size = size - if (this.drag || (this.flip && size.corner !== this.corner)) return + if (this.drag || (this.flip && size.corner !== this.corner)) { + // Held until the drop or the new corner: logged to find a chat window left at the dock's size. + if (!this.drag) + Log.info( + 'chat island: size held for the new corner', + size.w, + size.h, + size.corner, + this.corner, + ) + return + } // A reloaded island starts from the default corner: tell it the current one. if (size.corner && size.corner !== this.corner) this.window.emit( @@ -68,12 +79,13 @@ export class ChatIslandController { ? y + this.anchor.y : y + height - this.anchor.y - h const was = window.getBounds() - window.setBounds({ + const bounds = { x: Math.min(Math.max(x - slack, left), x + width + slack - w), y: Math.min(Math.max(y, top), y + height - h), width: w, height: h, - }) + } + window.setBounds(bounds) // Same size: the page is already drawn for the new corner, only the window moved. if (this.flip) this.reveal(was.width === w && was.height === h ? 0 : 32) if (this.wantFocus) { @@ -81,6 +93,14 @@ export class ChatIslandController { window.show() window.focus() } else if (!window.isVisible()) window.showInactive() + const got = window.getBounds() + if ( + got.x !== bounds.x || + got.y !== bounds.y || + got.width !== bounds.width || + got.height !== bounds.height + ) + Log.warning('chat island: window not where it was put', bounds, got) } catch (e) { Log.warning('error during resizing ChatIslandWindow:', e) } diff --git a/src/renderer/src/pages/ChatIslandPage.tsx b/src/renderer/src/pages/ChatIslandPage.tsx index f635df5d..a29bc68f 100644 --- a/src/renderer/src/pages/ChatIslandPage.tsx +++ b/src/renderer/src/pages/ChatIslandPage.tsx @@ -12,6 +12,8 @@ const PAD = 24 const TIP = 240 // Linux gets no mouse moves through an ignoring window, so there the window widens for the tooltip instead. const THROUGH = !navigator.userAgent.includes('Linux') +// The chat windows can move to the CTI, and back from there: the person's choice, kept on the chat server. +const MOVE_TO_CTI = { host: 'cti' as const, label: 'NethVoice CTI' } // Island events NethLink listens to. const OUT = [ 'chat-island-status', @@ -27,6 +29,10 @@ export function ChatIslandPage() { const [corner, setCorner] = useState< 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left' >('bottom-right') + // Where the chat windows live: here, or moved to the CTI (then the island here only listens). + const [host, setHost] = useState<'nethlink' | 'cti'>('nethlink') + const hostRef = useRef(host) + hostRef.current = host const allowed = !!account?.data?.profile?.macro_permissions?.nethvoice_cti?.permissions @@ -145,6 +151,18 @@ export function ChatIslandPage() { // Last value of each event, sent again when NethLink (re)loads. const last: Record = {} window.electron.receive(IPC_EVENTS.CHAT_TO_ISLAND, (name, detail) => { + // Opening a chat from NethLink (command bar, notification) brings the chat windows back here. + if ( + hostRef.current === 'cti' && + (name === 'chat-island-open' || name === 'chat-island-new') + ) { + window.dispatchEvent( + new CustomEvent('chat-island-move', { + detail: { host: 'nethlink', open: detail?.username }, + }), + ) + if (name === 'chat-island-open') return + } if (name !== 'chat-sync') return window.dispatchEvent(new CustomEvent(name, { detail })) Object.entries(last).forEach(([n, d]) => @@ -179,11 +197,16 @@ export function ChatIslandPage() { const error = (e: Event) => Log.warning('chat island', (e as CustomEvent).detail) const moved = (e: Event) => setCorner((e as CustomEvent).detail) + const where = (e: Event) => { + const h = (e as CustomEvent).detail?.host + if (h === 'cti' || h === 'nethlink') setHost(h) + } OUT.forEach((n) => window.addEventListener(n, forward)) window.addEventListener('chat-island-call', call) window.addEventListener('chat-island-notify', notify) window.addEventListener('chat-island-error', error) window.addEventListener('chat-island-corner', moved) + window.addEventListener('chat-island-host', where) return () => { // Chat off: NethLink and the command bar must not keep showing it online. window.electron.send(IPC_EVENTS.CHAT_FROM_ISLAND, 'chat-island-status', { @@ -195,6 +218,7 @@ export function ChatIslandPage() { window.removeEventListener('chat-island-notify', notify) window.removeEventListener('chat-island-error', error) window.removeEventListener('chat-island-corner', moved) + window.removeEventListener('chat-island-host', where) } }, [on]) @@ -275,6 +299,8 @@ export function ChatIslandPage() { notifications='auto' accent='blue' language={i18next.language} + headless={host === 'cti'} + moveTo={MOVE_TO_CTI} /> )