archy/neode-ui/src/stores/appLauncher.ts

71 lines
1.9 KiB
TypeScript
Raw Normal View History

import { defineStore } from 'pinia'
import { ref } from 'vue'
/** Apps that set X-Frame-Options and/or don't support subpath proxy - open in new tab for correct display */
function mustOpenInNewTab(url: string): boolean {
try {
const u = new URL(url)
return (
u.port === '23000' || // BTCPay
u.port === '8123' || // Home Assistant
u.port === '8085' || // Nextcloud (subpath breaks CSS/assets)
u.port === '2283' // Immich (subpath breaks SPA)
)
} catch {
return false
}
}
/** Rewrite to same-origin proxy so iframe can embed (nginx strips X-Frame-Options) */
function toEmbeddableUrl(url: string): string {
try {
const u = new URL(url)
const origin = window.location.origin
// Only Vaultwarden and Penpot support subpath proxy; Nextcloud/Immich open in new tab
if (u.port === '8082') return `${origin}/app/vaultwarden/`
if (u.port === '9001') return `${origin}/app/penpot/`
} catch {
/* ignore */
}
return url
}
export const useAppLauncherStore = defineStore('appLauncher', () => {
const isOpen = ref(false)
const url = ref('')
const title = ref('')
let previousActiveElement: HTMLElement | null = null
function open(payload: { url: string; title: string; openInNewTab?: boolean }) {
if (payload.openInNewTab || mustOpenInNewTab(payload.url)) {
window.open(payload.url, '_blank', 'noopener,noreferrer')
return
}
previousActiveElement = (document.activeElement as HTMLElement) || null
url.value = toEmbeddableUrl(payload.url)
title.value = payload.title
isOpen.value = true
}
function close() {
const toRestore = previousActiveElement
previousActiveElement = null
isOpen.value = false
url.value = ''
title.value = ''
if (toRestore && typeof toRestore.focus === 'function') {
requestAnimationFrame(() => {
toRestore.focus()
})
}
}
return {
isOpen,
url,
title,
open,
close,
}
})