import { LitElement, html, css } from '../assets/lit-core-2.7.4.min.js'; export class PermissionHeader extends LitElement { static styles = css` :host { display: block; transition: opacity 0.3s ease-in, transform 0.3s ease-in; will-change: opacity, transform; } :host(.sliding-out) { opacity: 0; transform: translateY(-20px); } :host(.hidden) { opacity: 0; pointer-events: none; } * { font-family: 'Helvetica Neue', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; cursor: default; user-select: none; box-sizing: border-box; } .container { -webkit-app-region: drag; width: 285px; height: 220px; padding: 18px 20px; background: rgba(0, 0, 0, 0.3); border-radius: 16px; overflow: hidden; position: relative; display: flex; flex-direction: column; align-items: center; } .container::after { content: ''; position: absolute; top: 0; left: 0; right: 0; bottom: 0; border-radius: 16px; padding: 1px; background: linear-gradient(169deg, rgba(255, 255, 255, 0.5) 0%, rgba(255, 255, 255, 0) 50%, rgba(255, 255, 255, 0.5) 100%); -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); -webkit-mask-composite: destination-out; mask-composite: exclude; pointer-events: none; } .close-button { -webkit-app-region: no-drag; position: absolute; top: 10px; right: 10px; width: 14px; height: 14px; background: rgba(255, 255, 255, 0.1); border: none; border-radius: 3px; color: rgba(255, 255, 255, 0.7); cursor: pointer; display: flex; align-items: center; justify-content: center; transition: all 0.15s ease; z-index: 10; font-size: 14px; line-height: 1; padding: 0; } .close-button:hover { background: rgba(255, 255, 255, 0.2); color: rgba(255, 255, 255, 0.9); } .close-button:active { transform: scale(0.95); } .title { color: white; font-size: 16px; font-weight: 500; margin: 0; text-align: center; flex-shrink: 0; } .form-content { display: flex; flex-direction: column; align-items: center; width: 100%; margin-top: auto; } .subtitle { color: rgba(255, 255, 255, 0.7); font-size: 11px; font-weight: 400; text-align: center; margin-bottom: 12px; line-height: 1.3; } .permission-status { display: flex; align-items: center; justify-content: center; gap: 8px; margin-bottom: 12px; min-height: 20px; } .permission-item { display: flex; align-items: center; gap: 6px; color: rgba(255, 255, 255, 0.8); font-size: 11px; font-weight: 400; } .permission-item.granted { color: rgba(34, 197, 94, 0.9); } .permission-icon { width: 12px; height: 12px; opacity: 0.8; } .check-icon { width: 12px; height: 12px; color: rgba(34, 197, 94, 0.9); } .action-button { -webkit-app-region: no-drag; width: 100%; height: 34px; background: rgba(255, 255, 255, 0.2); border: none; border-radius: 10px; color: white; font-size: 12px; font-weight: 500; cursor: pointer; transition: background 0.15s ease; position: relative; overflow: hidden; margin-bottom: 6px; } .action-button::after { content: ''; position: absolute; top: 0; left: 0; right: 0; bottom: 0; border-radius: 10px; padding: 1px; background: linear-gradient(169deg, rgba(255, 255, 255, 0.5) 0%, rgba(255, 255, 255, 0) 50%, rgba(255, 255, 255, 0.5) 100%); -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); -webkit-mask-composite: destination-out; mask-composite: exclude; pointer-events: none; } .action-button:hover:not(:disabled) { background: rgba(255, 255, 255, 0.3); } .action-button:disabled { opacity: 0.5; cursor: not-allowed; } .continue-button { -webkit-app-region: no-drag; width: 100%; height: 34px; background: rgba(34, 197, 94, 0.8); border: none; border-radius: 10px; color: white; font-size: 12px; font-weight: 500; cursor: pointer; transition: background 0.15s ease; position: relative; overflow: hidden; margin-top: 4px; } .continue-button::after { content: ''; position: absolute; top: 0; left: 0; right: 0; bottom: 0; border-radius: 10px; padding: 1px; background: linear-gradient(169deg, rgba(255, 255, 255, 0.5) 0%, rgba(255, 255, 255, 0) 50%, rgba(255, 255, 255, 0.5) 100%); -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); -webkit-mask-composite: destination-out; mask-composite: exclude; pointer-events: none; } .continue-button:hover:not(:disabled) { background: rgba(34, 197, 94, 0.9); } .continue-button:disabled { background: rgba(255, 255, 255, 0.2); cursor: not-allowed; } /* ────────────────[ GLASS BYPASS ]─────────────── */ :host-context(body.has-glass) .container, :host-context(body.has-glass) .action-button, :host-context(body.has-glass) .continue-button, :host-context(body.has-glass) .close-button { background: transparent !important; border: none !important; box-shadow: none !important; filter: none !important; backdrop-filter: none !important; } :host-context(body.has-glass) .container::after, :host-context(body.has-glass) .action-button::after, :host-context(body.has-glass) .continue-button::after { display: none !important; } :host-context(body.has-glass) .action-button:hover, :host-context(body.has-glass) .continue-button:hover, :host-context(body.has-glass) .close-button:hover { background: transparent !important; } `; static properties = { microphoneGranted: { type: String }, screenGranted: { type: String }, isChecking: { type: String }, continueCallback: { type: Function } }; constructor() { super(); this.microphoneGranted = 'unknown'; this.screenGranted = 'unknown'; this.isChecking = false; this.continueCallback = null; } async connectedCallback() { super.connectedCallback(); await this.checkPermissions(); // Set up periodic permission check this.permissionCheckInterval = setInterval(() => { this.checkPermissions(); }, 1000); } disconnectedCallback() { super.disconnectedCallback(); if (this.permissionCheckInterval) { clearInterval(this.permissionCheckInterval); } } async checkPermissions() { if (!window.api || this.isChecking) return; this.isChecking = true; try { const permissions = await window.api.permissionHeader.checkSystemPermissions(); console.log('[PermissionHeader] Permission check result:', permissions); const prevMic = this.microphoneGranted; const prevScreen = this.screenGranted; this.microphoneGranted = permissions.microphone; this.screenGranted = permissions.screen; // if permissions changed == UI update if (prevMic !== this.microphoneGranted || prevScreen !== this.screenGranted) { console.log('[PermissionHeader] Permission status changed, updating UI'); this.requestUpdate(); } // if all permissions granted == automatically continue if (this.microphoneGranted === 'granted' && this.screenGranted === 'granted' && this.continueCallback) { console.log('[PermissionHeader] All permissions granted, proceeding automatically'); setTimeout(() => this.handleContinue(), 500); } } catch (error) { console.error('[PermissionHeader] Error checking permissions:', error); } finally { this.isChecking = false; } } async handleMicrophoneClick() { if (!window.api || this.microphoneGranted === 'granted') return; console.log('[PermissionHeader] Requesting microphone permission...'); try { const result = await window.api.permissionHeader.checkSystemPermissions(); console.log('[PermissionHeader] Microphone permission result:', result); if (result.microphone === 'granted') { this.microphoneGranted = 'granted'; this.requestUpdate(); return; } if (result.microphone === 'not-determined' || result.microphone === 'denied' || result.microphone === 'unknown' || result.microphone === 'restricted') { const res = await window.api.permissionHeader.requestMicrophonePermission(); if (res.status === 'granted' || res.success === true) { this.microphoneGranted = 'granted'; this.requestUpdate(); return; } } // Check permissions again after a delay // setTimeout(() => this.checkPermissions(), 1000); } catch (error) { console.error('[PermissionHeader] Error requesting microphone permission:', error); } } async handleScreenClick() { if (!window.api || this.screenGranted === 'granted') return; console.log('[PermissionHeader] Checking screen recording permission...'); try { const permissions = await window.api.permissionHeader.checkSystemPermissions(); console.log('[PermissionHeader] Screen permission check result:', permissions); if (permissions.screen === 'granted') { this.screenGranted = 'granted'; this.requestUpdate(); return; } if (permissions.screen === 'not-determined' || permissions.screen === 'denied' || permissions.screen === 'unknown' || permissions.screen === 'restricted') { console.log('[PermissionHeader] Opening screen recording preferences...'); await window.api.permissionHeader.openSystemPreferences('screen-recording'); } // Check permissions again after a delay // (This may not execute if app restarts after permission grant) // setTimeout(() => this.checkPermissions(), 2000); } catch (error) { console.error('[PermissionHeader] Error opening screen recording preferences:', error); } } async handleContinue() { if (this.continueCallback && this.microphoneGranted === 'granted' && this.screenGranted === 'granted') { // Mark permissions as completed if (window.api) { try { await window.api.permissionHeader.markPermissionsCompleted(); console.log('[PermissionHeader] Marked permissions as completed'); } catch (error) { console.error('[PermissionHeader] Error marking permissions as completed:', error); } } this.continueCallback(); } } handleClose() { console.log('Close button clicked'); if (window.api) { window.api.common.quitApplication(); } } render() { const allGranted = this.microphoneGranted === 'granted' && this.screenGranted === 'granted'; return html`